《Tensorflow 實戰Google深度學習框架》學習筆記(二)

第三章 TensorFlow入門

TensorFlow最重要的概念就是Tensor和Flow。Tensor就是張量,可以被簡單地理解爲多維數組;Flow就是流,直觀地表達了張量之間通過計算相互轉化的過程。

3.1 TensorFlow計算模型——計算圖

3.1.1 計算圖的概念

TensorFlow是一個通過計算圖的形式來表述計算的編程系統。TensorFlow中的每一個計算都是計算圖中的一個節點,而節點之間的邊描述了計算之間的依賴關係。一個運算的輸入依賴於另一個運算的輸出,那麼這兩個運算有依賴關係。

3.1.2 計算圖的使用

TensorFlow程序一般分爲兩個階段:第一個階段,定義計算圖中所有的計算,有如下實例代碼;第二個階段,執行計算,在3.3中做詳細介紹。

#使用“tf”來代替“tensorflow”作爲模塊名稱,使這個程序更加簡潔
import tensorflow as tf
#爲了建模的方便,TensorFlow將常量轉化成一個永遠輸出固定值的運算
a = tf.constant([1.0,2.0], name="a")
b = tf.constant([2.0,3.0], name="b")
result = a + b
#通過a.graph可以查看張量所屬的計算圖,通過tf.get_default_graph()可以獲取當前默認的計算圖
print(a.graph is tf.get_default_graph())

在TensorFlow程序中,會自動維護一個默認的計算圖,在上述代碼中,因爲沒有特意指定,所以a所在計算圖等於當前默認的計算圖,輸出爲True。

TensorFlow中的計算圖主要有三個功能:
第一,隔離張量和計算:TensorFlow支持通過tf.Graph()函數來生成新的計算圖,不同計算圖上的張量和運算都不會共享;

第二,管理張量和計算,計算圖可以通過tf.Graph().device()函數來指定運行計算的設備,爲TensorFlow使用GPU提供了機制,如以下代碼:

g = tf.Graph()
#指定計算的設備
with g.device('/gpu:0'):
    result = a + b

第三,有效地整理TensorFlow程序中的資源,這裏的資源包括張量、變量或者運行TensorFlow程序所需要的隊列資源,在一個計算圖中,可以通過集合(collection)來管理不同類別的資源,通過tf.add_to_collection()函數可以將資源加入一個或多個集合當中,通過tf.get_collection()函數可以獲得一個集合裏的所有資源,TensorFlow也自動管理了一些最常用的集合:
在這裏插入圖片描述

3.2 TensorFlow數據模型——張量

張量是TensorFlow管理數據的形式。

3.2.1 張量的概念

在TensorFlow程序中,所有的數據都通過張量的形式來表示。張量可以被簡單地理解爲多維數組,零階張量表示標量即一個數,一階張量表示向量即一維數組,n階張量表示一個n維數組。

注意,張量在TensorFlow中的實現並不是直接採用數組的形式,而是對TensorFlow中計算結果的引用,在張量中並沒有真正保存數字,它保存的是如何得到這些數字的計算過程,有以下示例代碼:

import tensorflow as tf
#tf.constant是一個計算,這個計算的結果是張量,保存在變量a中
a = tf.constant([1.0,2.0], name="a")
b = tf.constant([2.0,3.0], name="b")
result = tf.add(a, b, name="add")
print(result)

輸出結果爲:

Tensor("add:0", shape=(2,), dtype=float32)

從上述代碼的運行結果可以看出,一個張量中主要保存了三個屬性:名字(name)、維度(shape)和類型(type):

①名字不僅是一個張量的唯一標識符,還描述了這個張量是如何計算出來的。張量的命名可以通過“node:src_output“的形式給出來,其中node爲節點的名稱,src_output表示當前張量來自節點的第幾個輸出(編號從0開始)。在計算圖上每一個節點代表一個計算,計算的結果就保存在張量中;

②維度描述了張量的維度;

③類型描述了張量的類型,每一個張量會有一個唯一的類型,TensorFlow會對參與計算的所有張量進行類型的檢查,當發現類型不匹配時會報錯,如以下代碼:

import tensorflow as tf
a = tf.constant([1,2], name="a")
b = tf.constant([2.0,3.0], name="b")
result = a + b

運行程序會報類型不匹配的錯誤:

ValueError: Tensor conversion requested dtype int32 for Tensor with dtype float32: 'Tensor("b:0", shape=(2,), dtype=float32)'

如果將第一個張量指定爲實數類型:

a = tf.constant([1,2], name="a",dtype=tf.float32)

那麼計算就不會報錯了。

如果不指定類型,TensorFlow會給出默認的類型。使用默認類型有可能導致潛在的類型不匹配問題,因此一般通過指定dtype來明確指出變量或常量的類型。

TensorFlow支持14種不同的類型,主要包括:
①實數類型:tf.float32,tf.float64;
②整數類型:tf.int8,tf.int16,tf.int32,tf.int64、tf.uint8;
③布爾型:tf.bool;
④複數類型: tf.complex64,tf.complex128。

3.2.2 張量的使用

張量主要有兩種用途:

第一種用途是對中間計算結果的引用,當一個計算包含很多中間結果時,使用張量可以大大提高代碼的可讀性,比如在構建深層神經網絡時就不可避免地要用張量來引用中間結果,同時通過張量來存儲中間結果可以方便獲取中間結果,比如在卷積神經網絡中,卷積層或者池化層有可能改變張量的維度,通過張量.get_shape()函數來獲取張量的維度信息可以免去人工計算的麻煩。

第二種用途是當計算圖構造完成之後,張量可以用來獲得計算結果,也就是真實的數字,雖然張量本身不存儲真實的數字,但通過tf.Session().run()函數可以得到計算結果。

3.3 TensorFlow運行模型——會話

會話(Session)擁有並管理TensorFlow程序運行時的所有資源。當所有計算完成之後需要關閉會話來幫助系統回收資源,否則就可能出現資源泄露問題。

TensorFlow中使用會話的模式有兩種:

第一種模式需要明確調用會話生成函數和關閉會話函數,這個模式的代碼流程如下:

#創建一個會話
sess = tf.Session()
#使用這個創建好的會話來得到關心的運算的結果
sess.run(……)
#關閉會話使得本次運行中使用到的資源可以被釋放
sess.close()

使用這種模式時,在所有計算完成之後,需要明確調用close()函數來關閉會話並釋放資源,但是當程序因爲異常而退出時,關閉會話的函數可能就不會被執行從而導致資源泄露。

爲了解決資源泄露這個問題,一般使用第二種模式,通過Python的上下文管理器來使用會話,代碼流程如下:

#創建一個會話,並通過Python的上下文管理器來管理這個會話
with tf.Session() as sess:
	#使用這個創建好的會話來得到關心的運算的結果
	sess.run(……)

通過Python的上下文管理器機制,只要將所有的計算放在“with”內部,當上下文管理器退出時就會自動釋放所有資源。

TensorFlow會自動生成一個默認的計算圖,如果沒有特殊指定,計算會自動加到這個計算圖當中,但TensorFlow不會自動生成默認的會話,而是需要手動指定,當默認的會話被指定後可以通過張量.eval()函數來計算一個張量的取值,代碼示例如下:

import tensorflow as tf
a = tf.constant([1.0,2.0], name="a")
b = tf.constant([2.0,3.0], name="b")
result = a + b
sess = tf.Session()
with sess.as_default():
    print(result.eval())
print(sess.run(result))
print(result.eval(session=sess))
sess.close()

三個輸出結果相同:

[3. 5.]
[3. 5.]
[3. 5.]

在交互環境下(如Python腳本或Jupyter編輯器),TensorFlow還提供了一種直接構造默認會話的函數,省去了將生成的會話註冊爲默認會話的過程,如下:

import tensorflow as tf
a = tf.constant([1.0,2.0], name="a")
b = tf.constant([2.0,3.0], name="b")
result = a + b
#自動將生成的會話註冊爲默認會話
sess = tf.InteractiveSession()
print(result.eval())
sess.close()

TensorFlow還支持在生成會話時通過tf.ConfigProto()函數配置相應參數,代碼如下:

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

以上代碼中有兩個參數:

①allow_soft_placement參數爲True:某些計算無法被當前GPU支持時,可以自動調整到CPU上,而不是報錯,提高代碼的可移植性(默認爲False);

②log_device_placement參數爲True:日誌中會記錄每個節點被安排在了哪個設備上以方便調試(參數被設置爲False可以減少日誌量)。

3.4 TensorFlow實現神經網絡

3.4.1 TensorFlow遊樂場及神經網絡的簡介

TensorFlow遊樂場是一個通過網頁瀏覽器就可以訓練的簡單神經網絡並實現了可視化訓練過程的工具,網址:http://playground.tensorflow.org,網頁截圖如下:
在這裏插入圖片描述
網頁上有幾個部分:

①DATA所示的位置是4個不同的數據集用來測試神經網絡。

②FEATURES對應的是實體的特徵向量,特徵向量是神經網絡的輸入,一般神經網絡的第一層是輸入層,代表特徵向量中每一個特徵的取值,OUTPUT代表的是輸出層,在OUTPUT下面的平面是輸出的可視化,平面上或深或淺的顏色表示了神經網絡模型做出的判斷,顏色越深表示神經網絡模型對它的判斷越有信心,輸入層和輸出層之間的是HIDDEN LAYERS對應的隱藏層。同一層的節點不會相互連接,而且每一層只和下一層連接,直到最後一層輸出層得到輸出結果。

③每一個小格子代表神經網絡中的一個節點,每一個節點上的顏色代表了這個節點的區分平面,平面上的一個點代表了一種取值,而點的顏色就對應其輸出值,輸出值的絕對值越大,顏色越深,黃色越深表示負得越大,藍色越深表示正得越大。

④每一條邊代表了神經網絡中的一個參數,它可以是任意實數,顏色越深,表示參數值的絕對值越大,黃色越深表示負得越大,藍色越深表示正得越大,當邊的顏色接近白色時,參數取值接近於0。

TensorFlow遊樂場簡潔明瞭地展示了使用神經網絡解決分類問題的四個步驟:

第一,提取問題中實體的特徵向量;

第二,定義神經網絡的結構,定義如何從神經網絡的輸入到輸出,設置隱藏層數和節點個數(前向傳播);

第三,通過訓練數據來調整神經網絡中參數的取值,就是訓練神經網絡的過程(反向傳播);

第四,使用訓練好的神經網絡來預測未知的數據。

3.4.2 前向傳播算法簡介

以全連接神經網絡爲例,全連接神經網絡是指相鄰兩層之間任意兩個節點之間都有連接,如下所示:
在這裏插入圖片描述
計算神經網絡的前向傳播的結果需要三部分的信息:

第一部分是神經網絡的輸入,這個輸入就是從實體中提取的特徵向量;

第二部分是神經網絡的連接結構,即不同神經元之間輸入輸出的連接關係;

第三部分是神經網絡中神經元之間的邊的權重。

設X爲某一層結點的值所組成的矩陣,W爲該層和下一層之間的邊的權重所組成的矩陣,Y爲下一層結點的值的矩陣,則Y可通過TensorFlow程序得到:

#實際上是實現了矩陣的乘法
Y = tf.matmul(X,W)

3.4.3 神經網絡參數與TensorFlow變量

在TensorFlow中,變量(tf.Variable)的作用就是保存和更新神經網絡中的參數。

變量的初始值設置方法有三種:

①使用隨機數設置初始值:

TensorFlow支持以下四個隨機數生成函數:
在這裏插入圖片描述
其中tf.random_normal比較常用,如:

weights = tf.Variable(tf.random_normal([2,3],stddev=2))

在上面的樣例中,tf.random_normal([2,3],stddev=2)用於產生一個2 x 3的矩陣,矩陣中的元素服從一個平均值mean爲0(沒有指定時默認爲0),標準差stddev爲2的正態分佈。

②使用常數設置初始值:

在神經網絡中,偏置項(bias)通常會使用常數來設置初始值:
在這裏插入圖片描述
③使用其他變量的初始值設置初始值:

#w1的初始值與weights相同,w2的初始值是weights的兩倍
w1 = tf.Variable(weights.initialized_value())
w2 = tf.Variable(weights.initialized_value() * 2.0)

在TensorFlow中,一個變量的值在使用之前,需要明確地調用變量的初始化過程,通常有兩種方法:

#第一種方法,直接調用每個變量的初始化過程,比較麻煩
sess.run(weights.initializer)
sess.run(w1.initializer)
sess.run(w2.initializer)
#第二種方法,通過tf.initialize_all_variables函數實現初始化所有變量的過程
init_op = tf.initialize_all_variables()
sess.run(init_op)

實際上,所有的變量都會被自動加入GraphKeys.VARIABLES這個集合。通過tf.all_variables函數可以拿到當前計算圖上所有的變量。拿到計算圖上所有的變量有助於持久化整個計算圖的運行狀態。

當構建機器學習模型時,比如神經網絡,可以通過變量聲明函數中的trainable參數來區分需要優化的參數(比如神經網絡中的參數)和其他參數(比如迭代的輪數)。如果聲明變量時參數trainable爲True(默認爲True,若不想變量被GraphKeys.TRAINABLE_VARIABLES集合收集,就設置爲False),那麼這個變量將會被加入GraphKeys.TRAINABLE_VARIABLES集合。在TensorFlow中可以通過tf.trainable_variables函數得到所有需要優化的參數。TensorFlow中提供的神經網絡優化算法會將GraphKeys.TRAINABLE_VARIABLES集合中的變量作爲默認的優化對象。

維度(shape)和類型(type)是變量最重要的兩個屬性。其中,變量的類型是不可改變的,一個變量在構建之後,它的類型就不能再改變了;和類型不大一樣的是,維度在程序運行中是有可能改變的,但是需要通過設置參數validate_shape=False(默認爲True),如:

w1 = tf.Variable(tf.random_normal([2,3], stddev=2), name="w1")
w2 = tf.Variable(tf.random_normal([2,2], stddev=2), name="w2")
#這一句會報維度不匹配的錯誤:ValueError: Shapes (2, 3) and (2, 2) are not compatible
tf.assign(w1, w2)
#這一句可以被成功執行
tf.assign(w1, w2, validate_shape=False)

以3.4.2中的前向傳播算法示意圖爲示例編寫一個簡單的TensorFlow程序:

import tensorflow as tf

with tf.Session() as sess:
    #聲明wl、W2兩個變量,通過seed參數設定了隨機種子,這樣可以保證每次運行得到的結果是一樣的
    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是一個1*2的矩陣
    x = tf.constant([[0.7, 0.9]])
    #如果像下面這樣寫,會發生報錯
    #x = tf.constant([0.7,0.9])

    a = tf.matmul(x, w1)
    y = tf.matmul(a, w2)

    #初始化變量
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    #輸出結果:[[3.957578]]
    print(sess.run(y))

3.4.4 通過TensorFlow訓練神經網絡模型

在神經網絡優化算法中,最常用的就是反向傳播算法。反向傳播算法實現了一個迭代的過程:

①在每次迭代的開始,都選取一小部分的數據,這一小部分數據叫做一個batch。

②這個batch的樣例會通過前向傳播算法得到神經網絡模型的預測結果。因爲訓練數據都是有真實標籤的,所以可以計算當前神經網絡模型的預測結果和真實結果的差距。

③基於預測結果和真實結果的差距,反向傳播算法會相應更新神經網絡模型參數的取值,使模型的預測結果在這個batch上更接近真實結果,流程如下圖:
在這裏插入圖片描述
通過TensorFlow實現反向傳播算法:

第一步,使用TensorFlow表達一個batch的數據。如果每輪迭代中選取的數據都要通過常量來表示,那麼TensorFlow的計算圖將會太大。因爲每生成一個常量,TensorFlow都會在計算圖中增加一個節點。一般來說,一個神經網絡的訓練過程會需要經過幾百萬輪甚至幾億輪的迭代,這樣計算圖就會非常大,而且利用率很低。爲了避免這個問題,TensorFlow提供了 placeholder機制用於提供輸入數據。placeholder相當於定義了一個位置,這個位置中的數據在程序運行時再指定。這樣在程序中就不需要生成大量常量來提供輸入數據,而只需要將數據通過placeholder傳入TensorFlow計算圖。在placeholder定義時,這個位置上的數據類型是需要指定的。和其他張量一樣,placeholder的類型也是不可以改變的。placeholder中數據的維度信息可以根據提供的數據推導得出,所以不一定要給出。下面給出了通過placeholder實現前向傳播算法的代碼:

import tensorflow as tf

with tf.Session() as sess:
    #聲明wl、W2兩個變量,通過seed參數設定了隨機種子,這樣可以保證每次運行得到的結果是一樣的
    w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
    w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
    
    #定義placeholder作爲存放輸入數據的地方,這裏維度也不一定要定義,但如果維度是確定的,那麼給出維度可以降低出錯的概率
    x = tf.placeholder(tf.float32, shape=[3,2], name="input")

    a = tf.matmul(x, w1)
    y = tf.matmul(a, w2)

    #初始化變量
    init_op = tf.global_variables_initializer()
    sess.run(init_op)

    '''輸出結果:
    [[3.957578 ]
     [1.1537654]
     [3.1674924]]'''
    print(sess.run(y, feed_dict={x:[[0.7,0.9], [0.1,0.4], [0.5,0.8]]}))

在這段程序中替換了原來通過常量定義的輸入x。在新的程序中計算前向傳播結果時,需要提供一個feed_dict來指定x的取值,feed_dict是一個字典(map),在字典中需要給出每個用到的placeholder的取值。如果某個需要的placeholder沒有被指定取值,那麼程序在運行時將會報錯。

第二步,在得到一個batch的前向傳播結果之後,需要定義一個損失函數來刻畫當前的預測值
和真實值之間的差距:

#定義真實值與預測值之間的交叉熵損失函數,來刻畫真實值與預測值之間的差距
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))

第三步,通過反向傳播算法來調整神經網絡參數的取值使得差距可以被縮小:

#定義學習率
learning_rate = 0.001

#定義反向傳播算法的優化方法
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

TensorFlow支持7種不同的優化器,可以根據具體的應用選擇不同的優化算法。比較常用的優化方法有三種:tf.train.GradientDescentOptimizer、tf.train.AdamOptimizer 和tf.train.MomentumOptimizer。在定義了反向傳播算法之後,通過運行sess.run(train_step)就可以對所有在GraphKeys.TRAINABLE_VARIABLES集合中的變量進行優化,使得在當前batch下損失函數更小。

3.4.5 完整神經網絡樣例程序

用神經網絡模型解決二分類問題:

import tensorflow as tf

#NumPy是一個科學計算的工具包,這裏通過NumPy工具包生成模擬數據集
from numpy.random import RandomState

#聲明wl、W2兩個變量,通過seed參數設定了隨機種子,這樣可以保證每次運行得到的結果是一樣的
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))

'''定義placeholder作爲存放輸入數據的地方,在shape的一個維度上使用None可以方便使用不大的batch大小
在訓練時需要把數據分成比較小的batch,但是在測試時,可以一次性使用全部的數據
當數據集比較小時這樣比較方便測試,但數據集比較大時,將大量數據放入一個batch可能會導致內存溢出'''
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y-input")

#定義神經網絡結構
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

#定義真實值與預測值之間的交叉熵損失函數,來刻畫真實值與預測值之間的差距
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))

#定義反向傳播算法的優化方法
train_step = tf.train.AdamOptimizer(learning_rate=0.001).minimize(cross_entropy)

#設置隨機數種子
rdm = RandomState(seed=1)
#設置隨機數據集大小
dataset_size = 128
'''設置隨機數據集,定義規則來給出樣本的標籤
在這裏所有x1+x2<1的樣例都被認爲是正樣本,而其他爲負樣本
在這裏使用0來表示負樣本,1來表示正樣本。'''
X = rdm.rand(dataset_size, 2)
Y = [[int(x1 + x2 < 1)] for x1,x2 in X]

#創建會話
with tf.Session() as sess:
    #初始化變量
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    
    #設置batch訓練數據的大小
    batch_size = 8
    #設置訓練得輪數
    STEPS = 5000
    for i in range(STEPS):
        #每次選取batch_size個樣本進行訓練
        start = (i * batch_size) % dataset_size
        end = min(start + batch_size, dataset_size)
        
        #通過選取的樣本訓練神經網絡並更新參數
        sess.run(train_step, feed_dict={x:X[start:end], y_:Y[start:end]})
        
        '''輸出結果:
        After 0 training step(s), cross entropy on all data is 0.0674925
        After 1000 training step(s), cross entropy on all data is 0.0163385
        After 2000 training step(s), cross entropy on all data is 0.00907547
        After 3000 training step(s), cross entropy on all data is 0.00714436
        After 4000 training step(s), cross entropy on all data is 0.00578471
        通過這個結果可以發現隨着訓練的進行,交叉熵是逐漸變小的,交叉嫡越小說明預測的結果和真實的結果差距越小'''
        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))

代碼中有關numpy.random的使用可以參照:https://blog.csdn.net/jieshaoxiansen/article/details/82255191

上面的程序實現了訓練神經網絡的全部過程,從這段程序可以總結出訓練神經網絡的過程可以分爲以下3個步驟:

第一步,定義神經網絡的結構和前向傳播的輸出結果;

第二步,定義損失函數以及選擇反向傳播優化的算法;

第三步,生成會話(tf.Session)並且在訓練數據上反覆運行反向傳播優化算法。

3.5 學習資源

該書樣例代碼網址:https://github.com/caicloud/tensorflow-tutorial

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章