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

  • 2019 年 10 月 3 日
  • 筆記

為什麼選擇TensorFlow?

  自從12年AlexNet獲得ImageNet大賽的冠軍後,深度學習開始流行起來,也因為硬體的快速發展GPU並行計算配合易用的API,讓深度學習以及神經網路大方光彩。

  深度學習的框架其實有很多,目前來說最火的還要數PyTorch,TensorFlow以及Keras。其中Pytorch比較適合學術研究,自己搞著玩,如果工業實踐就不太適合了。TensorFlow由於時間比較久,學起來比較困難,不過有完整的開發,部署方案,還有大量的GitHub項目可供參考。Keras則是TensorFlow的一個高級API,同類的還有TensorFlow的TFLearn等等。

  總結來說,如果是學生的話,只是為了論文或者學習,那麼推薦Pytorch;如果是公司的開發者,想要在業務員中使用深度學習,推薦直接使用TensorFlow,如果使用最新的1.14,那麼官網的示例裡面就已經是Keras了;如果是從GitHub上面下載了源碼想要學習,那就得去學習對應版本的TensorFlow API了。

  在總結一下TensorFlow的優點:

  • 易用性:有對應Python的API
  • 可移植性:一套程式碼就可以適應單個或者多個CPU、GPU、移動設備等
  • 靈活性:可以部署在樹莓派、Android、windows、ios、linux等上
  • 可視化:有tensorboard提供開發的可視化介面,方便跟蹤調參
  • 檢查點:可以通過檢查點記錄保存實驗數據
  • 自動微積分:自動求解梯度
  • 龐大的社區:一年內擁有10000+的開發者,3000+的項目
  • 大量基於TensorFlow的項目程式碼

  在使用TensorFlow的公司包括:Google ,OpenAI,DeepMind,SnapChat,Airbus,eBay等。

  下面就來學習下TensorFlow的基礎知識,TensorFlow不僅提供了基礎的語法,還提供了一些簡化的API:

  • TF Learn,tf.contrib.learn,基於scikit-learn風格的API
  • TF Slim,tf.contrib.slim,輕量級的tf構建API,可以自動配置默認值,簡化使用
  • Keras,更高級更抽象的API,使用Keras之後,就像疊積木一樣創建模型,不過對於背後的原理隱藏的太深太深

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

TensorFlow計算模型——計算圖的概念

  計算圖是TensorFlow中最基本的一個概念,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/72232807

  此文是自己的學習筆記總結,學習於《TensorFlow深度學習框架》,俗話說,好記性不如爛筆頭,寫寫總是好的,所以若侵權,請聯繫我,謝謝。