《實戰Google深度學習框架第二版》之TensorFlow入門

3.1 TensorFlow計算模型——計算圖

TensorFlow 的名字中己經說明了它最重要的兩個概念一一Tensor 和 Flow。Tensor 就是張量。在 TensorFlow 中,張量可以被簡單地理解爲多維數組。如果說 TensorFlow 的第一個詞 Tensor 表明了它的數據結構,那麼 Flow 則 體現了它的計算模型。Flow 翻譯成中文就是“流”,它直觀地表達了張量之間通過計算相 互轉化的過程。 TensorFlow 是一個通過計算圖的形式來表述計算的編程系統。TensorFlow 中的每一個計算都是計算圖上的一個節點,而節點之間的邊描述了計算之間的依賴關係。圖 3-1 展示了通過 TensorBoard畫出來的第 2 章中兩個向量相加樣例的計算圖。

圖 3 -1 中的每一個節點都是一個運算,而每一條邊代表了計算之間的依賴關係。 如果一個運算的輸入依賴於另一個運算的輸出,那麼這兩個運算有依賴關係。在圖 3-1 中, a 和 b 這兩個常量不依賴任何其他計算。 而 add 計算則依賴讀取兩個常量的取值。 於是在 圖 3- 1 中可以看到有一條從 a 到 add 的邊和一條從 b 到 add 的邊。在圖 3-1 中,沒有任何計 算依賴 add 的結果,於是代表加法的節點 add 沒有任何指向其他節點的邊。所有 TensorFlow 的程序都可以通過類似圖 3-1 所示的計算圖的形式來表示,這就是 TensorFlow 的基本計算 模型。

3.1.2 計算圖的使用

TensorFlow 程序一般可以分爲兩個階段。在第一個階段需要定義計算圖中所有的計算。比如在第 2 章的向量加法樣例程序中首先定義了兩個輸入,然後定義了一個計算來得到它 們的和。第二個階段爲執行計算。以下代碼給出了計算定義 階段的樣例。

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

TensorFlow 程序中,系統會自動維護一個默認的計算圖,通過 tf.get_default_graph 函數可以獲取當前默認的計算圖。以下 代碼示意瞭如何獲取默認計算圖以及如何查看一個運算所屬的計算圖 。

#通過 a . graph 可以查看張-111:所屬的計算圈。因爲沒有特意指定,所以這個計算圖應該等於 

#當前默認的計算圈。所以下面這個操作輸出值爲 True 。 

print(a . graph is tf. get default graph())

除了使用默認的計算圖, TensorFlow 支持通過 tf.Graph 函數來生成新的計算圖。不同計 算圖上的張量和運算都不會共享。以下代碼示意瞭如何在不同計算圖上定義和使用變量。 

import tensorflow as tf
gl = tf . Graph() 
with gl. as_default () : 
    #在計算閣 gl 中定義變量“v”,並設置初始值爲 0 。
    v = tf. get_variable( 
        ” v ”, initializer=tf. zeros_initializer (shape=[1]))
g2 = tf. Graph() 
with g2.as_default() : 
    #在計算閣 g2 中定義變量“v飛並設置初始值爲 l 。
    v = tf. get_variable( 
        ” v ”, initializer=tf. ones_ initializer (shape= [1]))
#在計算圖 gl 中讀取變盤“v”的取值。 
with tf. Session(graph=gl) as sess : 
    tf . global_variables_initializer (). run () 
    with tf. variable_scope (””, r euse=True ) : 
        #在計算圖 gl 中,變量“v”的取值應該爲 0,所以下面這行會輸出[ 0. ]。 
        print(sess . run(tf. get_variable (”v ”)))
#在計算圖 g2 中讀取變量“v”的取值。 
with tf. Session (graph=g2 ) as sess: 
    tf.global_variables_initializer().run() 
    with tf. variable scope ("”, reuse=True ) : 
        #在計算圖 g2 中,變量“v”的取值應該爲 1,所以下面這行會輸出[ l. ]。 
        print(sess . run(tf.get_variable (”v ”)))

以上代碼產生了兩個計算圖,每個計算圖中定義了一個名字爲“v”的變量。在計算圖g1 中,將 v 初始化爲 0;在計算圖 g2 中,將 v 初始化爲1 。可以看到當運行不同計算圖時, 變量 v 的值也是不一樣的。 

3.2 TensorFlow 數據模型一一張量

張量是 TensorFlow 管理數據的形式,在 3.2.1 節中將介紹張 量的一些基本屬性。然後在 3.2.2 節中將介紹如何通過張量來保存和獲取 TensorFlow 計算 的結果。

3.2.1 張量的概念

從 TensorFlow 的名字就可以看出張量 (tensor)是一個很重要的概念。 在 TensorFlow程序中,所有的數據都通過張量的形式來表示。從功能的角度上看,張量可以被簡單理解 爲多維數組。其中零階張量表示標量(scalar ) ,也就是一個數: 第一階張量爲向量 (vector), 也就是一個一維數組;第 n 階張量可以理解爲一個 n 維數組。但張量在 TensorFlow 中的實現並不是直接採用數組的形式,它只是對 TensorFlow 中運算結果的引用。在張量中並沒有 真正保存數字,它保存的是如何得到這些數字的計算過程。還是以向量加法爲例,當運行 如下代碼時,並不會得到加法的結果,而會得到對結果的一個引用。

import tensorflow as tf 
# tf.constant 是一個計算,這個計算的結果爲一個張量,保存在變量 a 中。
a = tf. constant([1 . O, 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)
'''

從以上代碼可以看出 TensorFlow 中的張量和 NumPy 中的數組不同, TensorFlow 計算的結果不是一個具體的數字, 而且一個張量的結構。從上面代碼的運行結果可以看出, 一 個張量中主要保存了三個屬性: 名字(name)、維度(shape)和類型( type )。

張量的第一個屬性名字不僅是一個張量的唯一標識符, 它同樣也給出了這個張量是如 何計算出來的。在 3 . 1.2 節中介紹了 TensorFlow 的計算都可以通過計算圖的模型來建立, 而計算圖上的每一個節點代表了一個計算,計算的結果就保存在張量之中。所以張量和計算圖上節點所代表的計算結果是對應的。這樣張量的命名就可以通過 “ node:src_output”的 形式來給出。其中 node 爲節點的名稱, src_output 表示當前張量來自節點的第幾個輸出。 比如上面代碼打出來的“add :0”就說明了 result 這個張量是計算節點“add” 輸出的第一個 結果(編號從0開始)。

張量的第二個屬性是張量的維度( shape)。這個屬性描述了一個張量的維度信息。比如上面樣例中 shape=(2,)說明了張量 result 是一個一維數組, 這個數組的長度爲 2。維度是 張量一個很重要的屬性, 圍繞張量的維度 TensorFlow 也給出了很多有用的運算。

張量的第三個屬性是類型(type ),每一個張量會有一個唯一的類型。 TensorFlow 會對 參與運算的所有張量進行類型的檢查, 當發現類型不匹配時會報錯。 比如運行以下程序時 就會得到類型不匹配的錯誤:

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

這段程序和上面的樣例基本一模一樣,唯一不同的是把其中-個加數的小數點去掉了。 這會使得加數 a 的類型爲整數而加數 b 的類型爲實數,這樣程序會報類型不匹配的錯誤:

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

如果將第一個加數指定成實數類型"a = tf.constant([l, 2], name="a” , dtype=tf.float32)”, 那麼兩個加數的類型相同就不會報錯了 。 如果不指定類型, TensorFlow 會給出默認的類型, 比如不帶小數點的數會被默認爲 int32,帶小數點的會默認爲 float32 。 因爲使用默認類型有 可能會導致潛在的類型不匹配問題,所以一般建議通過指定 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 張量的使用

和 TensorFlow 的計算模型相比, TensorFlow 的數據模型相對比較簡單。 張量使用主要 可以總結爲兩大類。 第一類用途是對中間計算結果的引用 。 當一個計算包含很多中間結果時,使用張量可以大大提高代碼的可讀性。以下爲使用張量和不使用張量記錄中間結果來完成向量相加的 功能的代碼對比。

#使用張量記錄中間結果 
a = tf. constant ( [1.0, 2.0], name=” a ”)
b = tf. constant ( [2.0, 3.0], name=” b ”) 
result = a + b

#直接計算向量的和,這樣可讀性會比較差。
result= tf.constant([l.O, 2.0] ,name=” a ”)+
        tf.constant([2.0, 3.0] ,name=” b ”)

從上面的樣例程序可以看到, a 和 b 其實就是對常量生成這個運算結果的引用,這樣在做加法時就可以直接使用這兩個變量,而不需要再去生成這些常量。 當計算的複雜度增 加時(比如在構建深層神經網絡時〉通過張量來引用計算的中間結果可以使代碼的可閱讀 性大大提升。 同時,通過張量來存儲中間結果可以方便獲取中間結果。 

使用張量的第二類情況是當計算圖構造完成之後,張量可以用來獲得計算結果,也就是得到真實的數字。雖然張量本身沒有存儲具體的數字,但是通過 3.3 節中介紹的會話,就可以得到這些具體的數字。 比如在上述代碼中,可以使用 tf. Session().run( result)語句得到 計算結果。

3.3 TensorFlow 運行模型一一會話

前面的兩節介紹了 TensorFlow 是如何組織數據和運算的。本節將介紹如何使用TensorFlow 中的會話( session)來執行定義好的運算。會話擁有並管理 TensorFlow 程序運 行時的所有資源。所有計算完成之後需要關閉會話來幫助系統回收資源,否則就可能出現 資源泄漏的問題。 TensorFlow 中使用會話的模式一般有兩種,第一種模式需要明確調用會 話生成函數和關閉會話函數,這種模式的代碼流程如下。

#創建一個會話。 
sess = tf.Session()
#使用這個創建好的會話來得到關心的運算的結果。比如可以調用 sess.run(result) , 
#來得到 3.1 節樣例中張量 result 的取值。 
sess.run (...)
#關閉會話使得本次運行中使用到的資源可以被釋放。 
sess.close ()

使用這種模式時,在所有計算完成之後,需要明確調用 Session.close 函數來關閉會話 並釋放資源。然而,當程序因爲異常而退出時,關閉會話的函數可能就不會被執行從而導 致資源泄漏。 爲了解決異常退出時資源釋放的問題, TensorFlow 可以通過 Python 的上下文 管理器來使用會話。以下代碼展示瞭如何使用這種模式。

#創建一個會話,並通過 Python 中的上下文管理器來管理這個會話。
with tf. Session() as sess : 
    #使用創建好的會話來計算關心的結果。 
    sess.run(...) 
#不需要再調用“Session.close()”函數來關閉會話, 
#當上下文退出時會話關閉和資源釋放也自動完成了。

通過 Python 上下文管理器的機制,只要將所有的計算放在 “ with”的內部就可以。 當 上下文管理器退出時候會自動釋放所有資源。這樣既解決了因爲異常退出時資源釋放的問 題,同時也解決了忘記調用 Session.close 函數而產生的資源泄漏。

3.1 節介紹過 TensorFlow 會自動生成一個默認的計算圖,如果沒有特殊指定,運算會自動加入這個計算圖中。 TensorFlow 中的會話也有類似的機制,但 TensorFlow 不會自動生成默認的會話,而是需要手動指定。 當默認的會話被指定之後可以通過 tf.Tensor.eval 函數來計算一個張量的取值。以下代碼展示了通過設定默認會話計算張量的取值。

sess = tf.Session() 
with sess.as default(): 
    print(result. eval ())

以下代碼也可以完成相同的功能。

sess = tf.Session()

#以下兩個命令有相同的功能。 
print(sess.run(result))
print(result.eval(session=sess))

在交互式環境下(比如 Python 腳本或者 Jupyter 的編輯器下),通過設置默認會話的方式來獲取張量的取值更加方便。 所以 TensorFlow 提供了一種在交互式環境下直接構建默認會話的函數。這個函數就是 tf.lnteractiveSession。使用這個函數會自動將生成的會話註冊爲默認會話。以下代碼展示了tf.InteractiveSession 函數的用法。

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

通過 tf.InteractiveSession 函數可以省去將產生的會話註冊爲默認會話的過程。無論使 用哪種方法都可以通過 ConfigProto Protocol BufferCD來配置需要生成的會話。 下面給出了通 過 ConfigProto 配置會話的方法:

config = tf.ConfigProto(allow soft placement=True, 
                        log_device_placement=True)
sess1 = tf.InteractiveSession(config=config) 
sess2 = tf.Session(config=config)

3.4、TensorFlow實現神經網絡

3.4.3 神經網絡參數與TensorFlow變量

下面一段代碼給出了一種在 TensorFlow 中聲明一個 2*3 的矩陣變量的方法:

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

這段代碼調用了 TensorFlow 變量的聲明函數 tf. Variable。在變量聲明函數中給出了初始化這個變量的方法。 TensorFlow 中變量的初始值可以設置成隨機數、 常數或者是通過其 他變量的初始值計算得到。 在上面的樣例中, tf.random_ normal([2, 3], stddev=2)會產生一個 2*3 的矩陣,矩陣中的元素是均值爲 0,標準差爲 2 的隨機數。 

在神經網絡中,偏置項(bias)通常會使用常數來設置初始值。 以下代碼給出了一個樣例。

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

以上代碼將會生成一個初始值全部爲 0 且長度爲 3 的變量。除了使用隨機數或者常數,TensorFlow 也支持通過其他變量的初始值來初始化新的變量。 以下代碼給出了具體的方法。

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

以上代碼中, w2 的初始值被設置成了與 weights 變量相同。 w3的初始值則是 weights初始值的兩倍。在 TensorFlow 中, 一個變量的值在被使用之前,這個變量的初始化過程需要 被明確地調用。 以下樣例介紹瞭如何通過變量實現神經網絡的參數並實現前向傳播的過程。

import tensorflow as tf
#聲明 w1 、 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 是一個 1x2 的矩陣。
x = tf.constant([[0.7, 0.9)))
#通過 3.4.2 節描述的前向傳播算法獲得神經網絡的輸出。 
a = tf.matmul(x, w1) 
y = tf.matmul(a, w2)

sess = tf.Session()
#與 3.4.2 中的計算不同,這裏不能直接通過 sess . run (y)來獲取 y 的取值, 
#因爲 wl 和 w2 都還沒有運行初始化過程。以下兩行分別初始化了 w1 和 w2 兩個變量。 sess.run(w1.initializer) #初始化 w1. 
sess.run(w2.initializer) #初始化 w2 。 
#輸出[( 3.95757794 ))。 
print(sess.run(y)) 
sess.close ()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.4.5 完整神經網絡樣例程序

# -*- coding: utf-8 -*-
"""
Created on Sun Apr 21 14:18:53 2019

@author: Lenovo
"""
import tensorflow as tf
# NumPy 是一個科學計算的工具包,這裏通過 NumPy工具包生成模擬數據袋 。
from numpy.random import RandomState
#定義訓練數據 batch的大小 。
batch_size = 8
#定義神經網絡的參數,這裏還是沿用 3.4.2 小節中給出的神經網絡結構 。
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 大小。在訓練時需要把數據分
#成比較小的 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)
#定義損失函數和反向傳播的算法。
y=tf.sigmoid(y)
cross_entropy=-tf.reduce_mean(
y_*tf.log(tf.clip_by_value(y,1e-10,1.0))
+(1-y)*tf.log(tf.clip_by_value (1-y,1e-10,1.0)))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
#通過隨機數生成一個模擬數據集。
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size,2)
#定義規則來給出樣本的標籤。在這裏所有 x1+x2<1 的樣例都被認爲是正樣本( 比如i零件合格) ,
#而其他爲負樣本( 比如零件不合格)。和 TensorFlow 遊樂場中的表示法不大一樣的地方是,
#在這裏使用 0來表示負樣本 ,1來表示正樣本。大部分解決分類問題的神經網絡都會採用
# 0 和 1 的表示方法。
Y = [[int(x1+x2<1)] for (x1,x2) in X]
#創建一個會話來運行 TensorFlow 程序。
with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    #初始化變量。
    sess.run(init_op)
    
    print(sess.run(w1))
    print(sess.run(w2))
    '''
    在訓練之前神經網絡參數的值:
    wl = [ [-0.81131822 , 1 . 48459876 , 0 . 06532937)
    [-2 . 44270396 , 0 . 0992484 , 0 . 59122431))
    w2 = [ [-0 . 81131822) , [1.48459876) , [0 . 06532937 ) )
    '''
    
    #設定訓練的輪數。
    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]})
        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))
            '''
            輸出結果 :
            After 0 training step (s), cross entropy on all data is 1.89805
            After 1000 training step (s), cross entropy on all data is 0.655075
            After 2000 training step (s) , cross entropy on all data is 0.626172
            After 3000 training step (s), cross entropy on all data is 0.615096
            After 4000 training step (s), cross entropy on all data is 0.610309
            通過這個結果可以發現隨着訓練的進行,交叉熵是逐漸變小的。交叉熵越小說明
            預測的結果和真實的結果差距越小。
            '''
            print(sess.run(w1))
            print(sess.run(w2))
            '''
            在訓練之後神經網絡參數的值 :
            w1 = [[0 . 02476984 , 0 . 5694868 , 1 . 69219422]
            [- 2 . 19773483 , - 0 . 23668921 , 1.11438966] J
            w2 = [[“ 0 . 45544702 ], (0 . 49110931 ], (- 0 . 9811033]]
        可以發現這兩個參數的取值已經發生了變化,這個變化就是訓練的結果。
        它使得這個神經網絡能更好地擬合提供的訓練數據。
        '''

 

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