1. 程式人生 > >TensorFlow搭建神經網路入門

TensorFlow搭建神經網路入門

深度學習解決的核心問題之一就是自動地將簡單的特徵組合成更加複雜的特徵,一旦解決了資料表達和特徵提取,很多人工智慧任務也就解決了90%。

TensorFlow計算框架可以很好地支援深度學習的各種演算法,但它的應用不限於深度學習。

一、TensorFlow的主要依賴包

- Protocol Buffer

將結構化的資料(不同於大資料中的機構化資料,這裡的結構化資料指的是擁有多種屬性的資料)序列化,並從序列化之後的資料流中還原出原來的結構化資料。

三種常見的結構化資料處理工具對比:

1. Protocol Buffer:

name
: 張三 id: 12345 email: [email protected]

2. XML:

<user>
    <name>張三</name>
    <id>12345</id>
    <email>[email protected]</email>
</user>

3. JSON:

{
    "name": "張三",
    "id": "12345",
    "email": "[email protected]",
}

相比較另外兩種工具,使用Protocol Buffer時需要先定義資料的格式,Protocol Buffer序列化出來的資料要比XML格式的資料小3到10倍,解析時間要快20到100倍。以下程式碼給出了上述使用者資訊樣例的資料格式定義檔案(存放在 .proto 檔案中):

message user{
    optional string name = 1; # 可以為空
    required int32 id = 2; # 必須有
    repeated string email =3; # 取值可以為一個列表
}

- Bazel

谷歌開源的自動化構建工具,TensorFlow本身以及谷歌給出的很多官方樣例都是通過Bazel來編譯的。

專案空間是Bazel的一個基本概念,專案空間(workspace)包含了編譯一個軟體所需要的原始碼以及輸出編譯結果的symbolic link地址,一個專案的根目錄需要有一個WORKSPACE

檔案,此檔案定義了對外部資源的依賴關係。

Bazel對Python支援的編譯方式只有三種:py_binary(將Python程式編譯為可執行檔案)、py_library(將Python程式編譯成庫函式供其他呼叫)和py_test(編譯Python測試程式)

二、TensorFlow計算模型

TensorFlow程式一般可以分為兩個階段:在第一個階段定義計算圖中所有的計算,第二個階段執行計算。

1. 計算圖

TensorFlow是一個通過計算圖的形式來表達計算的程式設計系統,每一個計算都是計算圖上的一個節點,節點之間的邊描述了計算之間的依賴關係。

import tensorflow as tf
a = tf.constant([1.0,2.0],name='a')
print(a.graph is tf.get_default_graph())

a.graph獲得張量a所屬的計算圖,tf.get_default_graph函式可以獲取當前執行緒預設的計算圖,所以輸出結果如下:

True

tf.Graph.device可以指定執行計算的裝置:

g = tf.Graph()

# 指定計算執行的裝置
with g.device('/gpu:0'):
    result = a + b

通過tf.Graph函式生成新的計算圖,不同計算圖上的張量和運算不會共享:

import tensorflow as tf

# 在計算圖g1中定義變數“v”,並設定初值為0
g1 = tf.Graph()
with g1.as_default():
    v = tf.get_variable("v", initializer=tf.zeros_initializer()(shape = [1]))

# 在計算圖g2中定義變數“v”,並設定初值為1
g2 = tf.Graph()
with g2.as_default():
    v = tf.get_variable("v", initializer=tf.ones_initializer()(shape = [1]))

# 在計算圖g1中讀取變數“v”的取值
with tf.Session(graph=g1) as sess:
    tf.global_variables_initializer().run()
    with tf.variable_scope("", reuse=True):
        print(sess.run(tf.get_variable("v")))
# 在計算圖g1中,變數“v”的取值應該為0,所以上面這行會輸出[0.]

# 在計算圖g2中讀取變數“v”的取值
with tf.Session(graph=g2) as sess:
    tf.global_variables_initializer().run()
    with tf.variable_scope("", reuse=True):
        print(sess.run(tf.get_variable("v")))
# 在計算圖g2中,變數“v”的取值應該為0,所以上面這行會輸出[1.]

張量

張量可以簡單地理解為多維陣列,在張量中並沒有真正儲存數字,它儲存的是如何得到這些數字的計算過程,TensorFlow計算的結果不是一個具體的數字,而是一個張量的結構,一個張量中主要儲存了三個屬性:名字(name)、維度(shape)和型別(type)

import tensorflow as tf
# tf.constant是一個計算,這個計算的結果是一個張量,儲存在變數a中
a = tf.constant([1, 2], name='a', dtype=tf.float32)
b = tf.constant([2.0, 3.0], name='b')
print(a)
result = tf.add(a,b, name='add')
print(result)

上述程式的執行結果為:

Tensor(“a:0”, shape=(2,), dtype=float32)
Tensor(“add:0”, shape=(2,), dtype=float32)

張量的使用:
- 對中間計算結果的引用
- 獲得計算結果

通過集合(collection)來管理不同類別的資源:

通過tf.add_to_collection可以將資源加入一個或多個集合中,通過tf.get_collection獲取一個集合裡面的所有資源,下表給出了常用的幾個自動維護的集合:

集合名稱 集合內容 使用場景
tf.GraphKeys.VARIABLES 所有變數 持久化TensorFlow模型
tf.GraphKeys.TRAINABLE_VARIABLES 可學習的變數(一般指神經網路中的引數) 模型訓練、生成模型視覺化內容
tf.GraphKeys.SUMMARIES 日誌生成相關的張量 TensorFlow計算視覺化
tf.GraphKeys.QUEUE_RUNNERS 處理輸入的QueueRunner 輸入處理
tf.GraphKeys.MOVING_AVERAGE_VARIABLES 所有計算了滑動平均值的變數 計算變數的滑動平均值

2. 會話

1.會話擁有並管理TensorFlow程式執行時的所有資源,所有計算完成之後需要關閉會話來幫助系統回收資源,TensorFlow中使用會話的模式一般有兩種:

sess = tf.Session()
sess.run(xxx)
sess.close()
with tf.Session as sess:
    sess.run(xxx)
# 不需要再呼叫Session.close()來關閉會話,當上下文退出時會話關閉和資源釋放也自動完成了

2. TensorFlow會自動生成一個預設的計算圖,但是不會自動生成預設的會話,需要手動指定,當預設的會話被指定之後可以通過tf.xxxTensor.eval函式來計算一個張量的取值(與 print(sess.run(result)) 結果相同):

sess = tf.Session()
with sess.as_default():
    print(result.eval())
sess = tf.Session()
print(result.eval(session = sess))

3. 使用tf.InteractiveSession會自動將生成的會話註冊為預設會話

sess = tf.InteractiveSession()
print(result.eval())
sess.close()

4. 通過ConfigProto可以配置類似並行的執行緒數、GPU分配策略、運算超時時間等引數,經常用到的引數有下面這兩個:

config = tf.ConfigProto(allow_soft_placement = True, log_device_placement = True)
sess1 = tf.InteractiveSession(config = config)
sess2 = tf.Session(config = config)

(1) 其中allow_soft_placement為True時,以下任意一個條件成立時,GPU上的運算可以放到CPU上執行(一般是為了增強程式碼的可移植性):

  1. 運算無法在GPU上執行
  2. 沒有GPU資源(在GPU只有一個的情況下運算被指定在第二個GPU上執行)
  3. 運算輸入包含對CPU計算結果的引用

(2) log_device_placement為True時日誌中將會記錄每個節點被安排在哪個裝置上以方便除錯,設為False可以減少日誌量

三、TensorFlow實現神經網路

真實問題中,一般會從實體中抽取很多特徵,每一個特徵為一個維度,一個實體便被對映為高維空間中的一個點。

結合TensorFlow_PlayGround示例,我們可以發現使用神經網路解決分類問題主要可以分為以下4個步驟:

  1. 提取問題中實體的特徵向量作為神經網路的輸入
  2. 定義神經網路結構,並定義如何從神經網路的輸入得到輸出
  3. 通過訓練資料來調整神經網路中引數的值
  4. 使用神經網路來預測未知的資料

全連線網路:

全連線網路: 相鄰兩層之間任意兩個節點之間都有連線

前向傳播:

前向傳播:可以簡單地理解為一個線性模型,這個過程主要用到的函式是tf.matmul(),其功能為實現矩陣乘法

  1. 在TensorFlow中,變數(tf.Variable)的作用就是儲存和更新神經網路中的引數,一般使用隨機數來進行變數初始化:
weights = tf.Variable(tf.random_normal([2,3],stddev=2))

TensorFlow隨機數生成函式:

函式名 隨機數分佈 主要引數
tf.random_normal 正態分佈 tf.random_normal(shape,mean=0.0,stddev=1.0,dtype=tf.float32)
tf.truncated_normal 偏離均值不超過兩個標準差的正態分佈 同上
tf.random_uniform 均勻分佈 tf.random_uniform(shape,minval=0,maxval=None,dtype=tf.float32)
tf.random_gamma Gamma分佈 tf.random_gamma(shape,alpha,beta=0,,dtype=tf.float32)

2. 在神經網路中,偏置項(bias)通常會使用常數來設定初值:

biases = tf.Variable(tf.zeros([3]))

或通過其他變數的初試值來初始化新的變數:

w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value() * 2.0)

TensorFlow常數生成函式:

函式名 樣例 結果
tf.zeros tf.zeros([2,3],int32) [[0,0,0],[0,0,0]]
tf.ones tf.zeros([2,3],int32) [[1,1,1],[1,1,1]]
tf.fill tf.fill([2,3],9) [[9,9,9],[9,9,9]]
tf.constant tf.constant([1,2,3]) [1,2,3]

在TensorFlow中,一個變數的值在被使用之前,這個變數的初始化過程需要被明確地呼叫,變數是特殊的張量:

import tensorflow as tf

# 設定隨機種子,保證每次執行得到的結果一樣
w1 = tf.Variable(tf.random_normal((2,3),stddev=1,seed=1))
w2 = tf.Variable(tf.random_normal((3,1),stddev=1,seed=1))
x = tf.constant([0.7,0.9])

# 通過前向傳播演算法獲得神經網路的輸出
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)

sess = tf.Session()
# 這裡不能直接通過sess.run(y)來獲取y的取值,因為w1和w2都還沒有執行初始化過程
# 以下兩行分別初始化了w1和w2兩個變數
# sess.run(w1.initializer)
# sess.run(w1.initializer)
# 當變數變多的時候上面的初始化過程就比較麻煩了,通常用下面這種呼叫方式
init_op = tf.global_variables_initializer()
sess.run(init_op)

print(sess.run(y))
sess.close()

所有的變數都會被自動地加入到GraphKeys.VARIABLES這個集合中,通過tf.global_variables()函式可以拿到當前計算圖上所有的變數,在構建機器學習模型時,可以通過變數宣告函式中的trainable引數來區分需要優化的引數(比如神經網路中的引數)和其他引數(比如迭代的輪數),當trainable為True時,這個變數將會被將入到GraphKeys.TRAINABLE_VARIABLES集合,可以通過tf.trainable_variables函式得到所有需要優化的引數。

維度(shape)和型別(type)是變數最重要的兩個屬性,變數的型別在構建之後不能改變,但是維度可以(和reshape的更改不同):

程式示例:

import tensorflow as tf

w1 = tf.Variable(tf.random_normal([2,3],stddev=1),name="w1")
w2 = tf.Variable(tf.random_normal([2,2],stddev=1),name="w2")

# 會報錯維度不匹配
tf.assign(w1,w2)

# 成功執行
tf.assign(w1,w2,validate_shape=False)

四、TensorFlow訓練神經網路

神經網路優化流程圖如下所示:

這裡寫圖片描述

其中反向傳播(back propagation)演算法是神經網路優化演算法中最常用的演算法:

TensorFlow實現反向傳播演算法的第一步是使用TensorFlow表達一個batch的資料,但如果每輪迭代中選取的資料都要通過常量來表示,那麼TensorFlow的計算圖會非常大,為了避免這個問題,TensorFlow提供了placeholder機制用於提供輸入資料,placeholder相當於定義了一個位置,這個位置中的資料在程式執行時再指定,在placeholder定義時,資料型別是需要被指定的,維度可以根據提供的資料推導得出:

import tensorflow as tf

w1= tf.Variable(tf.random_normal([2, 3], stddev=1))
w2= tf.Variable(tf.random_normal([3, 1], stddev=1))

# 定義placeholder作為存放輸入資料的地方,這裡維度不一定要定義
# 但是如果維度是實現確定的,這裡給出維度可以降低出錯概率
x = tf.placeholder(tf.float32, shape=(1, 2), name="input")
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()
init_op = tf.global_variables_initializer()
sess.run(init_op)

# 需要提供feed_dict來指定x的值,feed_dict是一個字典
# 在字典中需要給出每個用到的placeholder的取值
print(sess.run(y, feed_dict={x: [[0.7,0.9]]}))

接下來需要定義一個損失函式

這裡用的是交叉熵函式 H ( p , q ) = x p ( x ) · log q ( x ) ,其中 p 表示真實標記的分佈, q 為訓練後的模型的預測標記分佈

來刻畫當前的預測值和真實答案之間的差距,然後通過反向傳播演算法來調整神經網路引數的取值使得差距可以被縮小:

import tensorflow as tf

# 使用sigmoid函式將y轉換為0~1之間的數值,轉換後y代表預測是正樣本的概率
# 1-y代表也測是負樣本的概率
y = tf.sigmoid(y)

# 定義損失函式來刻畫預測值與真實值的差距,y_為真實值
cross_entropy = -tf.reduce_mean(y_ * tf.log(y) + (1 - y_) * tf.log(1 - y))

# 定義學習速率
learning_rate = 0.001

# 定義反向傳播演算法來優化神經網路中的引數
# TensorFlow支援10種不同的優化器,比較常用的有:
# tf.train.AdamOptimizer / tf.train.GradientDescentOptimizer / tf.train.MomentumOptimizer
train_step = tf.train.AdamOptimizer(leraning_rate).minimize(cross_entropy)

接下來給出一個完整的程式來訓練神經網路解決二分類問題:

import tensorflow as tf
from numpy.random import RandomState

# 1.定義神經網路引數,輸入和輸出節點
batch_size = 8
w1= tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2= tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
# 在shape的一個維度上使用None可以方便使用不同的batch大小
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
y_= tf.placeholder(tf.float32, shape=(None, 1), name='y-input')

# 2.定義前向傳播過程,損失函式及反向傳播演算法
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
y = tf.sigmoid(y)
# 定義損失函式和反向傳播的演算法
cross_entropy = -tf.reduce_mean(y_ * tf.log(y) + (1 - y_) * tf.log(1 - y))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)

# 3.生成模擬資料集
# 資料集的樣本是兩個在0-1之間的實數,標籤為0(當實數之和大於1)和1(當實數之和小於1)
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size,2)
Y = [[int(x1+x2 < 1)] for (x1, x2) in X]

# 4.建立一個會話來執行TensorFlow程式
with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    # 輸出目前(未經訓練)的引數取值
    print(sess.run(w1))
    print(sess.run(w2))
    print("\n")

    # 訓練模型
    STEPS = 5000
    for i in range(STEPS):
        start = (i * batch_size) % dataset_size
        end = (i * batch_size) % dataset_size + batch_size
        sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})

        # 每訓練1000次計算交叉熵並輸出
        if i % 1000 == 0:
            total_cross_entropy = sess.run(cross_entropy, feed_dict={x: X, y_: Y})
            print("After %d training step(s), cross entropy on all data is %g" % (i, total_cross_entropy))

    # 輸出訓練後的引數取值,與未經訓練時的取值相比已經有了變化
    print("\n")
    print(sess.run(w1))
    print(sess.run(w2))