內容翻譯:@穆文(微信公衆號 數據挖掘機養成記) && 寒小陽
校正調整:寒小陽 && 龍心塵
時間:2016年7月
出處:http://blog.csdn.net/han_xiaoyang/article/details/51871068
說明:本文爲斯坦福大學CS224d課程的中文版內容筆記整理,已得到斯坦福大學課程@Richard Socher教授的授權翻譯
0.前言
之前的課程裏介紹了自然語言處理當中的一些問題,以及設計出來的一些相應的算法。research的東西還是落地到工程應用上比較有價值,之前也手擼過一些toy project,不過這些實現要用在工程中,總是有那麼些虛的,畢竟穩定性和效率未必能夠保證。所幸的是,深度學習熱度持續升溫的大環境下,各種大神和各家大廠也陸續造福民衆,開源了一些深度學習框架,在這些開源框架的基礎上去搭建和實現自己想要的深度學習網絡結構就簡單和穩定得多了。
有時候選擇多了也是麻煩,對框架感興趣的同學可以查看深度學習框架對比維基百科中對12個開源的package比對。這裏簡單提幾個最常見和可能會用到的深度學習開源框架的特點。
caffe提供了很便捷的神經網絡搭建和命令行工具,加之model zoo裏面大量預訓練好的模型(主要是圖像相關的)可以做fine-tuning,因此使用在圖像相關的研究和應用上非常方便。
Theano以及搭建於其之上的Keras和Lasagne似乎頗受research派系同學的偏愛,自動求導是它的優勢之一。
MXnet對顯存的利用率高,並且支持C++, Python, Julia, Matlab, JavaScript, Go, R, Scala這麼多種語言,編寫起來也比較簡易。
Torch是facebook用的深度學習package,定義新網絡層比較簡單,不過Lua倒不算大家熟知的編程語言。
Tensorflow是Google提供資金研發的,比較全,支持分佈式,同時有Google這樣的親爹在,我猜資源傾斜也是遲早的事情。
今天的重點自然是Tensorflow,其他的框架也都很好,大家可以自行嘗試。
1.Tensorflow
首先提提Tensorflow和theano,它倆都是python封裝的深度學習庫,非常容易上手,說起來Tensorflow還是受Theano啓發,借鑑了一部分它的思想。不同之處在於,Tensorflow 對分佈式系統支持更好,同時還是Google提供資金研發的,而Theano 是一個學術性質的項目。
Tensorflow 可以對定義在張量(tensors,你可以先簡單理解成標量、向量或者矩陣,一會兒會提到)上的函數自動求導,因此神經網絡中BP算法可以很輕鬆地實現。
在開始Tensorflow之前,需要先讓大家對Tensorflow的過程有個直觀的理解。
在Tensorflow裏:
使用
張量(tensor)
表示數據.使用
圖(graph)
來表示計算任務.在被稱之爲
會話(Session)
的上下文 (context)
中執行圖.通過
變量 (Variable)
維護狀態.使用
feed
和fetch
可以爲任意的操作(arbitrary operation)賦值或者從其中獲取數據.
嚴格意義上來說TensorFlow算是一個編程系統,它使用圖
來表示計算任務,圖中的節點
被稱之爲operation(可以縮寫成op
),一個節點獲得0個或者多個張量
(tensor,下文會介紹到),執行計算,產生0個或多個張量。TensorFlow的一個圖
描述了一個計算過程,爲了進行計算,圖
必須在會話(Session)
裏被啓動,會話(Session)
將圖
的op
分發到CPU或GPU之類的設備上,同時提供執行op
的方法,這些方法執行後,將產生的張量(tensor)
返回。返回的張量因語言不同而有不同,在python裏是numpy ndarry對象;在C/C++語言中,是tensorflow::Tensor實例。
下面咱們來詳細說說上面提到的概念。
1.1 什麼是張量
既然Tensorflow裏面的定義和運算都是基於張量這個概念,我們就先來看看,什麼是張量。
張量的正式定義:從向量空間到實數域的多重現性映射(multilinear maps)(V是向量空間,V是對偶空間)
f:V×Vp copies×V×Vq copies→R
標量是張量(f:R→R,f(e1)=c)(譯者注: 標量是用實數表示零維空間的點)
向量是張量(f:Rn→R,f(ei)=vi)(譯者注: 向量是用實數表示一維空間的點,也即向量中的某個元素)
矩陣是張量( f:Rn×Rm→R,f(ei,ej)=Aij)(譯者注: 矩陣是用實數表示二維空間的點,也即矩陣的某個元素)
通常來說,張量可以用多維數組來表示
1.2 Tensorflow 與 Numpy
看似差別甚遠的2個package,說起來可能也很少有人把這兩者作對比,但他們“長得”確實很相似(都是提供N維數組的庫)
Numpy 有 Ndarray(N維數組) 支持,但不提供創建張量函數和自動求導的方法,也不提供GPU支持
1.3 Numpy 與 Tensorflow 定義與操作對比
# numpy定義與操作 In [23]: import numpy as np In [24]: a = np.zeros((2,2)); b = np.ones((2,2)) In [25]: np.sum(b, axis=1) Out[25]: array([ 2., 2.]) In [26]: a.shape Out[26]: (2, 2) In [27]: np.reshape(a, (1,4)) Out[27]: array([[ 0., 0., 0., 0.]])123456789123456789
# 對應的Tensorflow定義與操作 In [31]: import tensorflow as tf In [32]: tf.InteractiveSession() In [33]: a = tf.zeros((2,2)); b = tf.ones((2,2)) In [34]: tf.reduce_sum(b, reduction_indices=1).eval() Out[34]: array([ 2., 2.], dtype=float32) In [35]: a.get_shape() Out[35]: TensorShape([Dimension(2), Dimension(2)]) In [36]: tf.reshape(a, (1, 4)).eval() Out[36]: array([[ 0., 0., 0., 0.]], dtype=float32)1234567891012345678910
以上代碼中提到的 session
和.eval()
將在下文細述,而關於TensorShape
,大家可以簡單理解成類似Python中tuple的類型。
爲了方便記憶,我們把numpy和Tensorflow中的部分定義和操作做成了一張一一對應的表格,方便大家查看。
Numpy | Tensorflow |
---|---|
a = np.zeros((2,2)); b = np.ones((2,2)) | a = tf.zeros((2,2)), b = tf.ones((2,2)) |
np.sum(b, axis=1) | tf.reduce_sum(a,reduction_indices=[1]) |
a.shape | a.get_shape() |
np.reshape(a, (1,4)) | tf.reshape(a, (1,4)) |
b*5+1 | b*5+1 |
np.dot(a,b) | tf.matmul(a, b) |
a[0,0], a[:,0], a[0,:] | a[0,0], a[:,0], a[0,:] |
Tensorflow的輸出要稍微注意一下,我們需要顯式地輸出(evaluation,也就是說藉助eval()函數)!
In [37]: a = np.zeros((2,2)) In [38]: ta = tf.zeros((2,2)) In [39]: print(a) [[ 0. 0.] [ 0. 0.]] In [40]: print(ta) Tensor("zeros_1:0", shape=(2, 2), dtype=float32) In [41]: print(ta.eval()) [[ 0. 0.] [ 0. 0.]]1234567891012345678910
上面是一個示例的代碼,大家可以理解Tensorflow是通過計算圖(computation graph)定義一個計算過程的,這個過程不產生數值結果,那想看到具體內容怎麼辦呢?我們要藉助.eval()
函數輸出。
1.4 Tensorflow 的計算圖
用Tensorflow編寫的程序一般由兩部分構成,一是構造部分,包含了計算流圖,二是執行部分,通過session 來執行圖中的計算,具體可以參考Tensorflow文檔
我們先來看看怎麼構建圖。構件圖的第一步是創建源節點
(source op)。源節點
不需要任何輸入,它的輸出傳遞給其它節點(op)做運算。python庫中,節點構造器的返回值即當前節點的輸出,這些返回值可以傳遞給其它節點(op)作爲輸入。
TensorFlow Python庫中有一個默認圖
(default graph),在默認圖的基礎上,節點構造器(op 構造器)
可以爲其增加節點。這個默認圖對許多程序來說已經足夠用了,更多管理視圖的細節可以閱讀官方Graph類文檔。
我們來看一個簡單的構建圖例子:
import tensorflow as tf # 創建一個常量節點, 產生一個1x2矩陣,這個op被作爲一個節點 # 加到默認視圖中 # 構造器的返回值代表該常量節點的返回值 matrix1 = tr.constant([[3., 3.]]) # 創建另一個常量節點, 產生一個2x1的矩陣 matrix2 = tr.constant([[2.], [2.]]) # 創建一個矩陣乘法matmul節點,把matrix1和matrix2作爲輸入: product = tf.matmul(matrix1, matrix2)12345678910111234567891011
上面代碼裏的默認圖現在有三個節點,兩個constant()節點和matmul() 節點。不過這僅僅是構建圖,爲了真正進行矩陣的乘法,你必須在會話(Session,馬上提到)
裏啓動這個圖。
1.5 Tensorflow與Session對象
上面我們知道了Tensorflow需要先構造一個圖用於計算,但是圖怎麼啓動呢?啓動圖的第一步需要創建一個Session對象。比如:
# 創建session,啓動默認圖 sess = tf.Session() # 調用sess的'run()' 方法來執行矩陣乘法節點操作,傳入'product'作爲該方法的參數。'product'代表了矩陣乘法節點的輸出,傳入它是告訴方法我們希望取回矩陣乘法節點的輸出。 #整個執行過程是自動化的,會話負責傳遞節點所需的全部輸入。節點通常是併發執行的。 # 函數調用'run(product)'會觸發圖中三個節點(上面例子裏提到的兩個常量節點和一個矩陣乘法節點)的執行。 # 返回值'result'是一個numpy 'ndarray'對象。 result = sess.run(product) print result # 結果爲`12`.`` # 完成任務,記得關閉會話 sess.close()12345678910111213141516171234567891011121314151617
Session對象在使用完成後,記得關閉以釋放資源,當然,除了顯式調用close關閉外,也可以使用with代碼來自動完成關閉動作:
# 用with代碼來自動完成session裏的圖運算並關閉 with tf.Session() as sess: result = sess.run([product]) print result12341234
爲了便於使用像IPython這樣的python交互環境,可以使用InteractiveSession代替Session類,使用Tensor.eval()和Operation.run()方法代替Session.run()。這樣做的好處是可以在ipython中保持默認session處於打開狀態:
# 進入一個交互式Tensorflow會話 import tensorflow as tf sess = tf.InteractiveSession() x = tf.Variable([1.0, 2.0]) a = tf.constant([3.0, 3.0]); # 使用初始化器的run()方法初始化x x.initializer.run() # 增加一個減法節點,從x減去a。運行減法op,輸出結果 sud = tf.sub(x, a) print sub.eval() # 結果爲[-2. -1.]12345678910111213141234567891011121314
1.6 關於session和多GPU運算
我們一直在說,Tensorflow是支持分佈式的深度學習框架/包,這是因爲它能將圖定義轉換成分佈式執行的操作,以充分利用可以利用的計算資源(如CPU或GPU)。不過一般情況下,你不需要顯式指定使用CPU還是GPU,Tensorflow能自動檢測。如果檢測到GPU,Tensorflow會優先使用找到的第一個GPU來執行操作。
如果機器上有超過一個可用的GPU,默認狀況下除了第一個外的其他GPU是不參與計算的。爲了讓Tensorflow使用這些GPU,你必須將節點運算明確地指派給它們執行。其中with…Device語句用來指派特定的CPU或GPU操作:
# 手動指定給某個gpu執行 with tf.Session() as sess: with tf.device("/gpu:1"): matrix1 = tf.constant([[3., 3.]]) matrix2 = tf.constant([[2.], [2.]]) product = tf.matmul(matrix1, matrix2)123456123456
指定設備的書寫格式如下:
/cpu:0
:機器的CPU/gpu:0
:機器的第一個GPU,如果有的話/gpu:1
:機器的的第二個GPU,其他GPU以此類推
1.7 Tensorflow的變量(Variables)
我們訓練一個模型的時候,會用到Tensorflow中的變量(Variables)
,我們需要它來保持和更新參數值,和張量
一樣,變量
也保存在內存緩衝區當中。
有很多同學會問,前面不是提到了一個概念叫做張量,爲什麼還需要這個新的變量呢?需要說明一下的是,如果大家仔細看之前的代碼,會發現我們所用到的張量都是常值張量(constant tensors),而非變量,而參數值是需要動態調整的內容。
比如下面的代碼裏我們設定了一組權重爲變量:
In [32]: W1 = tf.ones((2,2)) In [33]: W2 = tf.Variable(tf.zeros((2,2)), name="weights") In [34]: with tf.Session() as sess: print(sess.run(W1)) sess.run(tf.initialize_all_variables()) print(sess.run(W2)) ....: [[ 1. 1.] [ 1. 1.]] [[ 0. 0.] [ 0. 0.]]12345678910111234567891011
說一個小細節,注意到上面第34步tf.initialize_all_variables
,我們要預先對變量初始化(initialization)
Tensorflow 的變量必須先初始化然後纔有值!而常值張量是不需要的
再具體一點,比如下面的代碼,其實38和39步,我們初始化定義初值是可以通過常數或者隨機數等任何一種方式初始化的,但是直到第40步才真正通過Tensorflow的initialize_all_variables對這些變量賦初值。
In [38]: W = tf.Variable(tf.zeros((2,2)), name="weights") In [39]: R = tf.Variable(tf.random_normal((2,2)), name="random_weights") In [40]: with tf.Session() as sess: ....: sess.run(tf.initialize_all_variables()) ....: print(sess.run(W)) ....: print(sess.run(R)) ....:12345671234567
比如我們來看一個計算圖
中變量的狀態更新過程,代碼如下:
In [63]: state = tf.Variable(0, name="counter") In [64]: new_value = tf.add(state, tf.constant(1)) In [65]: update = tf.assign(state, new_value) In [66]: with tf.Session() as sess: sess.run(tf.initialize_all_variables()) print(sess.run(state)) for _ in range(3): sess.run(update) print(sess.run(state)) 0 1 2 31234567891011121312345678910111213
上面的代碼定義了一個如下的計算圖,同時其中變量的狀態是循環變化的:*
開始sess.run(tf.initialize_all_variables()) (e.g. state=0)sess.run(update) (e.g. state = new_value = state+1)循環結束?結束yesno
1.8 Tensorflow的Fetch(獲取)操作
如果想取回定義的計算圖中的節點運算輸出結果,可以在使用Session對象的run()調用執行圖時,傳入一些張量,這些張量可以幫助你取回結果。而且不僅僅是單個節點的狀態或者結果,可以輸出多個節點的結果,比如下面這個簡單例子:
input1 = tf.constant(3.0) input2 = tf.constant(4.0) input3 = tf.constant(5.0) intermed = tf.add(input2, input3) mul = tf.mul(input1, intermed) with tf.Session() as sess: result = sess.run([mul, intermed]) print result # print # 輸出最後的乘法結果,和之前的加法結果[27.0, 9.0]123456789101112123456789101112
1.9 Tensorflow與Feed(傳入)操作
1.8裏我們提到了在計算圖中引入張量,以獲取節點狀態或者輸出結果。Tensorflow還提供了feed機制,該機制可以臨時替代圖中的任意操作中的張量,也就是說,可以對圖中任何操作提交補丁,直接插入一個新的張量。
feed可以使用一個張量值臨時替換某個操作的輸出結果,你只需要提供feed數據作爲run()調用的參數。需要說明的是,feed只在調用它的方法內有效,方法結束則feed就會消失。最常見的用例是將某些特殊的操作指定爲feed操作,標記的方法是使用tf.placeholder()爲這些操作創建佔位符(可以先想成一個容器,這個在之後的內容裏會提到,不要着急)。
input1 = tf.placeholder(tf.types.float32) input2 = tf.placeholder(tf.types.float32) output = tf.mul(input1, input2) # 手動提供feed數據作爲run的參數 with tf.Session() as see: print sess.run([output], feed_dict={input:[7.], input2:[2.]}) # print # 結果是[array([ 14.], dtype=float32)]1234567891012345678910
2.結語
這個部分呢,就先簡單給大家介紹Tensorflow的一些常用對象,基本操作和設計思想。之後會針對常見的問題(迴歸,圖像分類/CNN, 自然語言處理/RNN)逐個進行講解。歡迎大家繼續關注。