1. 程式人生 > >tensorflow學習筆記——常見概念的整理

tensorflow學習筆記——常見概念的整理

  TensorFlow的名字中已經說明了它最重要的兩個概念——Tensor和Flow。Tensor就是張量,張量這個概念在數學或者物理學中可以有不同的解釋,但是這裡我們不強調它本身的含義。在TensorFlow中,張量可以被簡單地理解為多維陣列,Flow翻譯成中文就是“流”,它直觀的表述計算的程式設計系統。TensorFlow中的每一個計算都是計算圖上的一個節點,而節點之間的邊描述了計算之間的依賴關係。

張量的概念

  在TensorFlow程式中,所有的資料都通過張量的形式來表示。從功能的角度上看,張量可以被簡單理解為多為陣列。其中零階張量表示標量(scalar),也即是一個數(張量的型別也可以是字串)。第一階張量為向量(vector),也就是一個一維陣列;第 n 階張量可以理解為一個 n 維陣列。但是張量在TensorFlow中的實現並不是直接採用陣列的形式,它只是對TensorFlow中運算結果的引用。在張量中並沒有真正儲存數字,它儲存的是如何得到這些數字的計算過程。以向量加法為例,當執行如下程式碼的時候,得到的不是加法的結果,而是對結果的一個引用。

#_*_coding:utf-8_*_
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 = a + b
# print(result)   # Tensor("add:0", shape=(2,), dtype=float32)

  從上面的結果來看,TensorFlow的張量和Numpy的陣列不同,他計算的結果不是一個具體的數字,而是一個張量的結構。從上面結果來看,一個張量主要儲存了三個屬性,名字(name),維度(shape)和型別(type)。

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

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

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

#_*_coding:utf-8_*_
import tensorflow as tf

# tf.constant 是一個計算,這個計算的結果為一個張量,儲存在變數a中
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([1, 2], name='a', dtype=tf.float32)

  那麼兩個加數的型別相同,就不會報錯了。如果不指定型別,則會預設為 int32,而帶小數的則會預設為float32.所以一般建議通過指定dtype來明確指出變數或者常量的型別。

tensorflow中的資料型別列表

張量的使用

  和TensorFlow的計算模型相比,TensorFlow的資料模型相比較簡單。張量使用主要可以總結為兩大類。

  第一類用途是對中間計算結果的引用。當一個計算包含很多中間結果時,使用張量可以大大提高程式碼的可讀性。比如上面的例子。

  第二類是當計算圖構造完成之後,張量可以用來獲得計算結果,也就是得到真實的數字,雖然張量本身沒有儲存具體的數字,但是通過下面的Session就可以得到具體的數字。

  

會話(TensorFlow執行模型——Session)

  下面學習如何使用會話(session)來執行定義好的運算,會話擁有並管理TensorFlow程式執行時的所有資源。當所有計算完成之後需要關閉會話來幫助系統回收資源,否則就可能出現資源洩露的問題。TensorFlow中使用會話的模式一般有兩種,第一種模式需要明確呼叫會話生成函式和關閉會話函式,這麼模式如下:

# 建立一個會話
sess  =  tf.Session()
# 使用這個建立好的會話來得到關心的運算結果
# 比如可以呼叫 sess.run(result) 來得到張量計算的結果
sess.run(...)
# 關閉會話使得本次運算中使用到的資源可以被釋放
sess.close()

  使用這種模式的時候,在所有計算完成之後,需要明確呼叫Session.close 函式來關閉會話並釋放資源。然而,當程式因為異常而退出時,關閉會話的函式可能就不會被執行而導致資源洩露。為了解決異常退出時資源釋放的問題,TensorFlow可以通過Python的上下文管理器來使用會話,也就是可以利用 with 程式碼塊生成Session,限制作用域,程式碼如下:

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

  通過Python上下文管理器的機制,只要將所有的計算放在'with' 的內部就可以。當上下文管理器退出時候會自動釋放所有資源。這樣即解決了因為異常退出時資源釋放的問題,同時也解決了忘記呼叫Session.close 函式而產生的資源洩露問題。

  Session 函式中沒有傳入引數,表明該程式碼將會依附於(如果還沒有建立會話,則會建立新的會話)預設的本地會話。生成會話之後,所有的 tf.Variable 例項都會通過呼叫各自初始化操作的 sess.run() 函式進行初始化。

init = tf.initialize_all_variables()
sess.run(init)

  在通過initializer給變數賦值固然可行,但是當變數的資料增多後,或者變數之間存在依賴關係時,單個呼叫的方案就比較麻煩了。所以使用上述程式碼更加便捷。

  sess.run() 方法將會執行圖表中與作為引數傳入的操作相對應的完整子集。在初始呼叫時, init操作只包含了變數初始化程式 tf.group。圖示的其他部分不會再這裡,而是在下面的訓練訓練執行。

  在互動式環境中(比如Python指令碼或者Jupyter的編譯器下),通過設定預設會話的方式來獲得張量的取值更加方便。所有TensorFlow提供了一種在互動式環境下直接構建預設會話的函式,這和函式就是tf.InteractiveSession.使用這個函式會自動將生成的會話註冊為預設會話。下面程式碼展示了tf.InteractiveSession 函式的用法:

sess = tf.InteractiveSession()
print(result.eval())
# 其實 sess.run(result) 和 result.eval(session=sess)) 功能相同
sess.close()

  通過tf.InteractiveSession 函式可以省去將產生的會話註冊為預設會話的過程。

  變數在被使用前,需要通過會話(session)執行其初始化方法完成初始化賦值。

sess.run(tf.global_variables_initializer)

  注意:在新版本的tensorflow中,使用下面程式碼替換上面程式碼,不然會報 Warning。

sess.run(tf.global_variables_initializer)

 

神經網路引數與TensorFlow變數tf.Variable()

  神經網路中的引數是神經網路實現分類或者回歸問題中重要的部分。在TensorFlow中變數(tf.Variable()的作用就是儲存和更新神經網路中的引數),下面學習一下變數的定義:

def __init__(self,
               initial_value=None, 
               trainable=True,
               collections=None,
               validate_shape=True,
               caching_device=None,
               name=None,
               variable_def=None,
               dtype=None,
               expected_shape=None,
               import_scope=None):
  • initial_value:初始化的值,可以是隨機數,常數或者通過其他變數的初始值得到的。
  • trainable:標記是否加入GraphKeys.TRAINABLE_VARIABLES集合
  • validate_shape:如果為False則可以更改shape
  • dtype:變數的型別,不可改變

  下面程式碼給出了一種在TensorFlow中宣告一個2*3的矩陣變數的方法:

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

  首先它呼叫了TensorFlow變數的宣告函式tf.Variable。在變數宣告函式中給出了初始化這個變數的方法。TensorFlow中變數的初始值可以設定成隨機數,常數或者是同其他變數的初始值計算得到。在上面的樣例中,tf.reandom_normal([2, 3], stddev=2)會產生一個2*3的矩陣,矩陣的元素均值為0,標準差為2 的隨機數。tf.random_normal函式可以通過引數mean來指定平均值,在沒有指定時預設為0,通過滿足正態分佈的隨機數來初始化神經網路中的引數是一個非常有用的方法,除了正態分佈的隨機數,TensorFlow還提供了一些其他的隨機數生成器,下圖列出了TensorFlow目前支援的所有隨機數生成器

  TensorFlow也支援通過常數來初始化一個變數,下圖給出了TensorFlow中常用的常量宣告方法:

  在神經網路中,偏置項(bias)通常會使用常數來設定初始值,下面程式碼給出了一個例子:

# 下面會產生一個初始值為0且長度為3 的變數
biases = tf.Variable(tf.zeros([3]))

  當然,TensorFlow也支援通過其他變數的初始值來初始化新的變數,下面給出了具體的方法:

# 宣告w1 w2兩個變數
w1 = tf.Variable(weights.initialized_value())
w2 = tf.Variable(weights.initialized_value() * 2.0)

  以上程式碼中,w1的初始值被設定成了與weights變數相同,w2的初始值則是weights初始值的兩倍。在TensorFlow中,一個變數的初始化過程需要被明確的呼叫。

   類似於張量,維度(shape)和型別(type)也是變數最重用的兩個屬性,和大部分程式語言類似,變數的型別是不可改變的。一個變數在被構建之後,它的型別就不能再改變數。

  如下程式碼會報出型別不匹配的錯誤:

# 宣告w1 w2兩個變數
# 定義神經網路的引數
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1), name='w1')
w2 = tf.Variable(tf.random_normal([2, 3], dtype=tf.float64, stddev=1), name='w2')

w1.assign(w2)
'''
程式會報錯:
TypeError: Input 'value' of 'Assign' Op has type float64 that does not 
match type float32 of argument 'ref'.
'''

  維度是另外一個重要的屬性,和型別不大一樣的是,維度在程式執行中是有可能改變的,但是需要設定引數,如下:

# 宣告w1 w2兩個變數
# 定義神經網路的引數
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1), name='w1')
w2 = tf.Variable(tf.random_normal([2, 3], stddev=1), name='w2')

w1.assign(w1, w2)
'''
程式會報錯(維度不匹配的錯誤):
TypeError: Expected bool for argument 'use_locking' not 
<tf.Variable 'w2:0' shape=(2, 3) dtype=float32_ref>.
'''
# 下面程式碼可以被成功執行
tf.assign(w1, w2, validate_shape=False)

  雖然TensorFlow支援更改變數的維度,但是這種做法比較罕見。

通過TensorFlow遊樂場瞭解神經網路

   首先我們通過TensorFlow遊樂場來快速瞭解神經網路的主要功能。TensorFlow遊樂場是一個通過網頁瀏覽器就可以訓練的簡單神經網路並實現了視覺化訓練過程的工具。

  TensorFlow遊樂場的地址:http://playground.tensorflow.org/

  從上圖中可以看出,TensorFlow的左側提供四個不同的資料集來測試神經網路。預設的資料為左上角被框出來的那個。被選中的資料也會顯示在上面最右邊的“OUTPUT”欄目下。在這個資料中,可以看到一個二維平面上有藍色或者橙色的點,每一個小點都代表了一個樣例,而點的顏色代表了樣例的標籤。因為點的顏色只有兩種,所有這是一個二分類問題。在這裡舉這麼一個例子來說明這個資料可以代表的實際問題。假設需要判斷某工廠生產的零件是否合格,那麼藍色的點可以表示所有合格的零件,而橙色代表不合格的零件。這樣判斷一個零件是否合格就變成了區分點的顏色。

  為了將一個實際問題對應到螢幕上不同顏色點的劃分,還需要將實際問題中的實體,比如上述例子中的零件,變成螢幕上的一個點。這就是特徵提取解決的問題。還是以零件為例,可以用零件的長度和質量來大致描述一個零件。這樣一個物理意義上的零件就可以被轉化成長度和質量這兩個數字。在機器學習中,所有用於描述實體的數字的組合就是一個實體的特徵向量(feature vector)。而特徵向量的提取對機器學習的效果至關重要,通過特徵提取就可以將實際問題中的實體轉化為空間中的點。假設使用長度和質量作為一個零件的特徵向量,那麼每個零件就是二維平面上的一個點。TensorFlow遊樂園中Features一欄對應了特徵向量。

  特徵向量是神經網路的輸入,神經網路的主體結構顯示了在上圖的中間位置。目前主流的神經網路都是分層的結構,第一層是輸入層,代表特徵向量中每一個特徵的取值。比如如果一個零件的長度是0.5,那麼x1的值就是0.5。同一層的節點不會相互連線,而且每一層只和下一層連線,直到最後一層作為輸出層得到計算的結果。在二分類問題中,比如判斷零件是否合格,神經網路的輸出層往往只包含一個節點。在二分類問題中,比如判斷零件是否合格,神經網路的輸出層往往只包含一個節點,而這個節點會輸出一個實數值。通過這個輸出值和一個事先設定的閾值,就可以判斷結果是零件合格,反之則零件不合格,一般可以認為當輸出值離閾值越遠得到的答案越可靠。

   在輸入和輸出層之間的神經網路叫做隱藏層,一般一個神經網路的隱藏層越多,這個神經網路就越“深”。而所謂深度學習中的這個“深度”和神經網路的層數也是密切相關的。在TensorFlow遊樂場中可以通過點選加或者減來增加或者減少神經網路隱藏層的數量。處理可以選擇深刻網路的深度,TensorFlow遊樂場也支援選擇神經網路每一層的節點數以及學習率(learning rate),啟用函式(activation),正則化(regularization)。

  所以通過神經網路解決分類問題主要可以分為以下四個步驟:

  1,提取問題中實體的特徵向量作為神經網路的輸入。不同的實體可以提取不同的特徵向量。

  2,定義神經網路的結構,並定義如何從神經網路的輸入得到輸出。這個過程可以是神經網路的前向傳播演算法

  3,通過訓練資料來調整神經網路中引數的取值,這就是訓練神經網路的過程。

  4,使用訓練好的神經網路來預測未知的資料。

前向傳播演算法

  下面學習一下最簡單的全連線網路結構的前向傳播演算法,並且將展示如何通過TensorFlow來實現這個演算法。

  下面首先了解神經元的結構,神經元是一個神經網路的最小單位,下面顯示一個最簡單的神經元結構:

  從上圖可以看出,一個神經元有多個輸入和一個輸出。每個神經元的輸入既可以是其他神經元的輸出,也可以是整個神經網路的輸入。所謂神經網路的結構就是指的不同神經元之間的連線結構。一個最簡單的神經元結構的輸出就是所有輸入的加權和,而不同輸入的權重就是神經元的引數。神經網路的優化過程就是優化神經元中的引數取值的過程。

   下圖給出了一個簡單的判斷零件是否合格的三層全連線神經網路,之所以稱為全連線神經網路是因為相鄰兩層之間任意兩個節點之間都有連線。

  下圖展示一個判斷零件是否合格的三層神經網路結構圖:

  計算神經網路的前向傳播結構需要三部分資訊。第一個部分是神經網路的輸入,這個輸入就是從實體中提取的特徵向量。比如上面有兩個輸入,一個是零件的長度x1,一個是零件的質量 x2,第二個部分為神經網路的連線結構。神經網路是由神經元構成的,神經網路的結構給出不同神經元之間輸入輸出的連線關係。神經網路中的神經元也可以稱為節點。在上圖中 a11節點有兩個輸入 ,分別是x1 和 x2的輸出。而 a11 的輸出則是節點 y 的輸入。最後一個部分是每個神經元中的採納數。我們用W來表示神經元中的引數。W的上標表名了神經網路的層數,比如W(1) 表示第一層節點的引數,而W(2) 表示第二層節點的引數。W的下標表明瞭連線節點編號,比如W(1) 1,2 表示連線 x1 和 a12節點的邊上的權重。這裡我們假設權重是已知的。

  當我們給定神經網路的輸入,神經網路的結構以及邊上權重,就可以通過前向傳播演算法來計算出神經網路的輸出,下圖展示了這個神經網路前向傳播的過程:

  上圖給出來輸入層的取值,從輸入層開始一層一層地使用前向傳播演算法,首先隱藏層中有三個節點,每一個節點的取值都是輸入層取值的加權和。當求出輸出值的閾值,判斷是否大於0,這樣就可以判斷是否合格。上面整個過程就是前向傳播的演算法。

  當然前向傳播的演算法可以表示為矩陣乘法,將輸入 x1  x2 組織成一個1*2 的矩陣x = [x1, x2],而W(1) 組織成一個2*3 的矩陣:

  這樣通過矩陣乘法可以得到隱藏層三個節點所組成的向量取值:

  類似的輸出層可以表示為:

  這樣就可以將前向傳播演算法通過矩陣乘法的方式表達出來了。在TensorFlow中矩陣政法是非常容易實現的。以下程式碼實現了神經網路的前向傳播過程:

# 定義神經網路前向傳播的過程
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

  其中 tf.matmul 實現了矩陣乘法的功能。

  以下樣例介紹瞭如何通過遍歷實現神經網路的引數並實現前向傳播的過程:

#_*_coding:utf-8_*_
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是一個1*2的矩陣
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 都還沒有執行初始化過程,下面分別初始化兩個變數
sess.run(w1.initializer)
sess.run(w2.initializer)
print(sess.run(y))   # 輸出[[3.957578]]
sess.close()

  從程式碼中可以看出,當聲明瞭變數w1 w2之後,可以通過w1  w2來定義神經網路的前向傳播過程並得到中間結果 a 和最後答案 y 。但是這些被定義的計算在這一步中並不是真正的運算,當需要執行這些計算並得到具體的數字的時候,需要進入TensorFlow程式第二步。

  在第二步,我們會宣告一個會話(session),然後通過會話計算結果。

 通過TensorFlow訓練神經網路模型

  使用監督學習的方式設定神經網路引數需要有一個標註好的訓練資料集。以判斷零件是否合格為例,這個標註好的訓練資料集就是手機的一批合格零件和一批不合格零件。監督學習最重要的思想就是在已知答案的標註資料集上,模型給出的預測結果要儘量接近真實的答案。通過調整神經網路中的引數對訓練資料進行擬合,可以使得模型對未知的樣本提供預測的能力。

  在神經網路優化演算法中,最常用的方法是反向傳播演算法(backpropagation),下圖展示了使用反向傳播演算法訓練神經網路的流程圖:

  從上圖可以看出,反向傳播演算法實現了一個迭代的過程。在每次迭代的開始,首先需要選取一小部分訓練資料,這一小部分資料叫做一個batch。然後這個batch的樣例會通過前向傳播演算法得到神經網路模型的額預測結果。因為訓練資料都是由正確答案標註的,所以可以計算出當前神經網路模型的預測答案與正確答案之間的差距。最後,基於這預測值和真實值之間的差距,反向傳播演算法會相應的更新神經引數的取值,使得在這個batch上神經網路模型的預測結果和真實答案更加接近。

   通過TensorFlow實現反向傳播演算法的第一步是使用TensorFlow表達一個batch的資料,在之前我們使用常量來表達,但是如果每輪迭代中選取的資料都要通過常量來表示,那麼TensorFlow都會在計算圖中增加一個節點。一般來說,一個神經網路的訓練過程會需要經過幾百萬輪甚至幾億輪的迭代,這樣計算圖就會非常大,而且利用率很低。為了避免這個問題,TensorFlow提供了placeholder機制用於提供輸入資料。placeholder相當於定義了一個位置,這個位置中的資料在程式執行時再指定。這樣在程式中就不需要生成大量常量來提供輸入資料,而只需要將資料通過placeholder傳入TensorFlow計算圖。在placeholder定義時,這個位置上的資料型別是需要指定的。和其他張量一樣,placeholder的型別也是不可以改變的。placeholder中資料的維度資訊是可以根據提供的資料推匯出來,所以不一定給出。

  下面給出了通過placeholder實現前向傳播演算法:

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))

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

# 定義神經網路前向傳播的過程
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()
# 這裡不能直接通過sess.run(y)來獲取y的取值
# 因為w1和w2 都還沒有執行初始化過程,下面分別初始化兩個變數
init_op = tf.global_variables_initializer()
sess.run(init_op)
'''
下面一行將報錯:
InvalidArgumentError (see above for traceback): You must feed a value
 for placeholder tensor 'input' with dtype float and shape [1,2]
'''
# print(sess.run(y))

# 下面一行將會得到之前一樣的輸出結果
print(sess.run(y, feed_dict={x: [[0.7, 0.9]]}))   # [[3.957578]]
sess.close()

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

  在上面的樣例程式中,如果將輸入的1*2 矩陣改為 n*2 的矩陣,那麼就可以得到 n 個樣例的前向傳播結果了。其中 n*2 的矩陣的每一行為一個樣例資料。這樣前向傳播的結果為 n*1 的矩陣,這個矩陣的每一行就代表了一個樣例的前向傳播結果,下面程式給出一個例項:

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))

# 定義placeholder作為存放輸入資料的地方,這裡維度也不一定要定義
# 但是如果維度是確定的,那麼給出維度可以降低出錯的概率
# x = tf.placeholder(tf.float32, shape=(1, 2), name='input')
x =tf.placeholder(tf.float32, shape=(3, 2), name='input')

# 定義神經網路前向傳播的過程
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

sess = tf.Session()
# 這裡不能直接通過sess.run(y)來獲取y的取值
# 因為w1和w2 都還沒有執行初始化過程,下面分別初始化兩個變數
init_op = tf.global_variables_initializer()
sess.run(init_op)


# 因為x 在定義時指定了 n 為3,所以在執行前向傳播過程時需要提供三個樣例資料

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

  上面的樣例中展示了一次性計算多個樣例的前向傳播結果。在執行時,需要將3個樣例組成一個3*2的矩陣傳入placeholder。計算得到的結果為3*1 的矩陣。

  在得到一個batch的前向傳播結果之後,需要定義一個損失函式來刻畫當前的預測值和真實答案之間的差距。然後通過反向傳播演算法來調整神經網路引數的取值使得差距可以被縮小。下面定義一個簡單的額損失函式,並通過TensorFlow定義反向傳播的演算法。

# 定義損失函式來刻畫預測值與真實值的差距
cross_entropy = -tf.reduce_mean(
    y_ * tf.log(tf.clip_by_value(y, le-10, 1.0))
)
# 定義學習率
learning_rate = 0.001
# 定義反向傳播演算法來優化神經網路中的採納數
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

  在上面程式碼中,cross_entropy 定義了真實值和預測值之間的交叉熵(cross entropy),這是分類問題中一個常用的損失函式,第二行 train_step 定義了反向傳播的優化方法。目前TensorFlow支援7種不同的優化器,比較常用的優化方法有三種:

tf.train.GradientDescentOptimizer
tf.train.AdamOptimizer
tf.train.MomentumOptimizer

  

完整神經網路樣例程式

  程式碼如下:

# _*_coding:utf-8_*_
import tensorflow as tf

# Numpy 是一個科學計算的工具包,這裡通過Numpy工具包生成模擬資料集
from numpy.random import RandomState

# 定義訓練資料batch的大小
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大小,
# 在訓練時需要把資料分成比較小的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))
)
# 定義學習率
learning_rate = 0.001
# 定義反向傳播演算法來優化神經網路中的採納數
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

# 通過隨機數生成一個模擬資料集
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)

# 定義規則來給出樣本的標籤,在這裡所有x1+x2<1 的樣例都被認為是正樣本(比如零件合格)
# 而其他為負樣本(比如零件不合格)和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))
    '''
    在訓練之前神經網路引數的值
    w1   =  [[-0.8113182   1.4845988   0.06532937]
     [-2.4427042   0.0992484   0.5912243 ]]
     
    w2 = [[-0.8113182 ]
     [ 1.4845988 ]
     [ 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 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

            通過這個結果可以發現隨著訓練的進行,交叉熵是逐漸變小的
            交叉熵越小說明預測的結果和真實的結果差距越小
            '''
    print(sess.run(w1))
    print(sess.run(w2))
    '''
    在訓練之後神經網路引數的值
    w1 = [[-1.9618275  2.582354   1.6820377]
     [-3.4681718  1.0698231  2.11789  ]]
    w2 = [[-1.824715 ]
     [ 2.6854665]
     [ 1.418195 ]]
     
     從和開始的神經網路引數值對比,我們發現這兩個引數的取值是已經發生變化
      這個變化就是訓練的結果,它使得這個神經網路能更好的擬合提供的訓練資料集
    '''

  上面的程式實現了訓練神經網路的全部過從,從這段程式中可以總結出訓練神經網路的過程分為以下三個步驟:

  • 1,定義神經網路的結構和前向傳播的輸出結果
  • 2,定義損失函式以及選擇反向傳播優化的演算法
  • 3,生成會話(tf.Session)並且在訓練資料上反覆進行反向傳播優化演算法

無論神經網路的結構如何變化,這三個步驟是不變的。

 

tf.Variable()  & tf.get_variable()

  tf.Variable() 和 tf.get_variable() 都可以用來建立變數,但是前者會自動保證唯一性,而後者不能保證唯一性。

  我們可以對比兩個函式:

# 新建一個變數,變數值是 initial_value
Variable(initial_value=None, trainable=True, 
         collections=None, validate_shape=True, 
         caching_device=None,name=None, 
         expected_shape=None, import_scope=None,
         constraint=None)


# 獲取具有這些引數的現有變數或者建立一個新變數。(可以建立共享變數)
# 如果該name的變數還未定義,則新建立一個,如果依據定義了,則直接獲取該變數
get_variable(name, shape=None, dtype=None, 
             initializer=None, regularizer=None, 
             trainable=True, collections=None,
             caching_device=None, partitioner=None, 
             validate_shape=True, use_resouce=None, 
             constraint=None)

  下面舉個例子來說明二者的不同之處:

#_*_coding:utf-8_*_
'''
下面例子來說明 tf.Variable()  和 tf.get_variable() 的不同之處
'''

import tensorflow as tf

with tf.variable_scope('scope1'):
    w1 = tf.Variable(1, name='w1')
    w2 = tf.get_variable(name='w2', initializer=2.)

with tf.variable_scope('scope1', reuse=True):
    w1_p = tf.Variable(1, name='w1')
    w2_p = tf.get_variable(name='w2', initializer=3.)

print('w1', w1)
print('w1_p', w1_p)
# w1          <tf.Variable 'scope1/w1:0' shape=() dtype=int32_ref>
# w1_p        <tf.Variable 'scope1_1/w1:0' shape=() dtype=int32_ref>

print('w2', w2)
print('w2_p', w2_p)
# w2          <tf.Variable 'scope1/w2:0' shape=() dtype=float32_ref>
# w2_p        <tf.Variable 'scope1/w2:0' shape=() dtype=float32_ref>

print(w1 is w1_p, w2 is w2_p)
# False True

  我們可以看出, tf.Variable()會自動處理衝突問題,如上面程式碼所示。而tf.get_variable()會判斷是否已經存在該name的變數,如果有,且該變數空間的reuse=True,那麼就可以直接共享之前的值,如果沒有,則重新建立。(注意:如果沒有將reuse設定為True,則會提示衝突發生)。錯誤如下:

ValueError: Variable scope1/w2 already exists, disallowed. Did you 
mean to set reuse=True in VarScope? Originally defined at:

  因為程式碼的最後一句語句是是判斷上述變數是否相等,可以看出,通過get_variable()定義的變數是完全等價的,即使後一句 get_variable 是將 initializer 設為3,但是由於 name='w2' 的變數已經存在,並且 reuse=True,則直接引用之前定義的,這樣就可以用 get_variable() 來定義共享變數。

  在生成上下文管理器時,若設定reuse=True,tf.variable_scope將只能獲取已經建立過的變數,如果空間中沒有變數則會報錯。如果reuse=False 或者 reuse=None,tf.get_variable將建立新的變數。而且同名變數已經存在,會報錯。

tf.get_variable  & tf.variable_scope

  tf.get_variable 函式可以用來建立或者獲取變數,當建立變數時,與 tf.Variable是一樣的。

  tf.variable_scope 函式生成一個上下文管理器,用於控制 tf.get_variable。

  這裡,我們會發現, tf.get_variable() 在使用時,一般會和 tf.varibale_scope() 配套使用,需要指定它的作用域空間,這樣在引用的使用的使用就可以通過設定指定的scope的 reuse=True進行引用。

#_*_coding:utf-8_*_
'''
變數生成之  tf.get_variable 與 tf.variable_scope   reuse引數
'''
import tensorflow as tf

with tf.variable_scope('a'):
    v1 = tf.get_variable("v", [1], initializer=tf.constant_initializer(1.0))

# with tf.variable_scope("a"):
    # 報錯 ValueError: Variable a/v already exists,
#     v2 = tf.get_variable("v", [1])   

with tf.variable_scope("a", reuse=True):
    v3 = tf.get_variable("v", [1])
    print(v3 == v1)  # True

with tf.variable_scope("b", reuse=True):
    # 報錯 ValueError: Variable b/v does not exist, or was not created with tf.get_variable().
    v4 = tf.get_variable("v",[1])  

  最後我們看一下 tf.variable_scope()  和 tf.name_scope()。 其中name_scope() 是給 op_name加字首,指定op的作用域空間 ,op是指操作。而variable_scope() 是給get_variable() 建立的變數的名字加字首,表明作用域空間,也可以用於處理命名衝突。

 

tf.cast() 資料型別轉換

  tf.cast() 函式的作用是執行 tensorflow中張量資料型別轉換,比如讀入的圖片如果是 int8 型別的,一般在訓練前把影象的資料格式轉換為float32。

  cast()定義:

cast(x, dtype, name=None)

  第一個引數 x:待轉換的資料(張量)

  第二個引數dtype:目標資料型別

  第三個引數name:可選引數,定義操作的名稱

int32轉換為float32 程式碼:

#_*_coding:utf-8_*_
import tensorflow as tf

t1 = tf.Variable([1, 2, 3, 4, 5])
t2 = tf.cast(t1, dtype=tf.float32)

print('t1: {}'.format(t1))
print('t2:{}'.format(t2))

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(t2)
    print(t2.eval())
    print(sess.run(t2))

  輸出如下:

t1: <tf.Variable 'Variable:0' shape=(5,) dtype=int32_ref>
t2:Tensor("Cast:0", shape=(5,), dtype=float32)
[1. 2. 3. 4. 5.]
[1. 2. 3. 4. 5.]

 

 batch

  深度學習的優化演算法,說白了就是梯度下降。每次的引數更新有兩種方式。

1,遍歷全部資料集算一次損失函式,然後算函式對各個引數的梯度,更新梯度。這種方法每更新一次引數都要把資料集裡的所有樣本都看一遍,計算量開銷大,計算速度慢,不支援線上學習,這種稱謂Batch gradient descent,批梯度下降。

2,每看一個數據就算一下損失函式,然後求梯度更新引數,這個稱為隨機梯度下降,stochastic gradient descent。這個方法速度比較快,但是收斂效能不太好,可能在最優點附近晃來晃去,hit不到最優點。兩次引數的更新也有可能互相抵消掉,造成目標函式震盪的比較劇烈。

  為了克服兩種方法的缺點,現在一般採用的是一種折中手段,mini-batch gradient decent ,小批的梯度下降,這種方法把資料分成若干個批,按批來更新引數,這樣一個批中的一組資料共同決定了本次梯度的方向,下降起來就不容易跑偏,減少了隨機性,另一方面因為批的樣本數與整個資料集相比小了很多,計算量也不是很大。

  基本上現在的梯度下降都是基於mini-batch的,所以深度學習框架的函式中經常會出現batch_size,就指的是這個。

 tf.argmax的使用

   tf.argmax(vector, 1):返回的是vector中的最大值的索引號,如果vector是一個向量,那就返回一個值,如果是一個矩陣,那就返回一個向量,這個向量的每一個維度都是相對應矩陣行的最大值元素的索引號。

import tensorflow as tf
import numpy as np
 
A = [[1,3,4,5,6]]
B = [[1,3,4], [2,4,1]]
 
with tf.Session() as sess:
    print(sess.run(tf.argmax(A, 1)))
    print(sess.run(tf.argmax(B, 1)))

--------------------- 

輸出:
[4]
[2 1]

  

 

參考文獻:

https://blog.csdn.net/uestc_c2_403/article/details/7223