CNTK是微軟用于搭建深度神經(jīng)網(wǎng)絡(luò)的計算網(wǎng)絡(luò)工具包,,此項目已在Github上開源。因為我最近寫了關(guān)于TensorFlow的文章,,所以想比較一下這兩個系統(tǒng)的相似和差異之處,。畢竟,CNTK也是許多圖像識別挑戰(zhàn)賽的衛(wèi)冕冠軍,。為了內(nèi)容的完整性,,我應(yīng)該也對比一下Theano、Torch和Caffe,。后三者也是現(xiàn)在非常流行的框架,。但是本文僅限于討論CNTK和TensorFlow,其余的框架將在今后討論,。Kenneth Tran對這五個深度學(xué)習(xí)工具包做過一次高水平(以他個人觀點)的分析,。本文并不是一個CNTK或者TensorFlow的使用教程。我的目的在于從程序員的角度對它們做高層次的對比,。本文也不屬于性能分析,,而是編程模型分析。文中會夾雜著大量的代碼,,如果你討厭閱讀代碼,,請直接跳到結(jié)論部分,。 CNTK有一套極度優(yōu)化的運行系統(tǒng)來訓(xùn)練和測試神經(jīng)網(wǎng)絡(luò),,它是以抽象的計算圖形式構(gòu)建。如此看來,,CNTK和TensorFlow長得非常相似,。但是,,它們有一些本質(zhì)上的區(qū)別。為了演示這些特性和區(qū)別,,我會用到兩個標(biāo)準(zhǔn)示例,,它們分別包括了兩個系統(tǒng)及調(diào)用各自系統(tǒng)完成的任務(wù)。第一個例子是用較淺的卷積神經(jīng)網(wǎng)絡(luò)來解決標(biāo)準(zhǔn)的MNIST手寫數(shù)字集的識別任務(wù),。我會針對它們兩種遞歸神經(jīng)網(wǎng)絡(luò)方法的差異性做一些點評總結(jié),。 TensorFlow和CNTK都屬于腳本驅(qū)動型的。我的意思是說神經(jīng)網(wǎng)絡(luò)構(gòu)建的流程圖都是在一個腳本里完成,,并調(diào)用一些智能的自動化步驟完成訓(xùn)練,。TensorFlow的腳本是與Python語言捆綁的,Python操作符能夠用來控制計算圖的執(zhí)行過程,。CNTK目前還沒有和Python或是C++綁定(盡管已經(jīng)承諾過),,所以它目前訓(xùn)練和測試的流程控制還是需要精心編制設(shè)計的。等會我將展示,,這個過程并不能算是一種限制,。CNTK網(wǎng)絡(luò)需要用到兩個腳本:一個控制訓(xùn)練和測試參數(shù)的配置文件和一個用于構(gòu)建網(wǎng)絡(luò)的網(wǎng)絡(luò)定義語言(Network Definition Language, NDL)文件。 我會首先描述神經(jīng)網(wǎng)絡(luò)的流程圖,,因為這是與TensorFlow最相似之處,。CNTK支持兩種方式來定義網(wǎng)絡(luò)。一種是使用“Simple Network Builder”,,只需設(shè)置幾個參數(shù)就能生成一個簡單的標(biāo)準(zhǔn)神經(jīng)網(wǎng)絡(luò),。另一種是使用網(wǎng)絡(luò)定義語言(NDL)。此處例子(直接從Github下載的)使用的是NDL,。下面就是Convolution.ndl文件的縮略版本,。(為了節(jié)省頁面空間,我把多行文件合并到同一行,,并用逗號分隔) CNTK網(wǎng)絡(luò)圖有一些特殊的節(jié)點,。它們是描述輸入數(shù)據(jù)和訓(xùn)練標(biāo)簽的FeatureNodes和LabelNodes,用來評估訓(xùn)練結(jié)果的CriterionNodes和EvalNodes,,和表示輸出的OutputNodes,。當(dāng)我們在下文中遇到它們的時候我再具體解釋。在文件頂部還有一些用來加載數(shù)據(jù)(特征)和標(biāo)簽的宏定義,。如下所示,,我們將MNIST數(shù)據(jù)集的圖像作為特征讀入,經(jīng)過歸一化之后轉(zhuǎn)化為若干浮點數(shù)組,。得到的數(shù)組“featScaled”將作為神經(jīng)網(wǎng)絡(luò)的輸入值,。
DNN小節(jié)定義了網(wǎng)絡(luò)的結(jié)構(gòu)。此神經(jīng)網(wǎng)絡(luò)包括了兩個卷積-最大池化層,,接著是有一個128節(jié)點隱藏層的全連接標(biāo)準(zhǔn)網(wǎng)絡(luò),。 在卷積層I 我們使用5x5的卷積核函數(shù),,并且在參數(shù)空間定義了16個(cMap1)。操作符ConvReLULayer實際上是在宏文件中定義的另一個子網(wǎng)絡(luò)的縮寫,。 在計算時,,我們想把卷積的參數(shù)用矩陣W和向量B來表示,那么如果輸入的是X,,網(wǎng)絡(luò)的輸出將是f(op(W, X) + B)的形式,。在這里操作符op就是卷積運算,f是標(biāo)準(zhǔn)規(guī)則化函數(shù)relu(x)=max(x,0),。
矩陣W和向量B是模型的參數(shù),,它們會被賦予一個初始值,并在訓(xùn)練的過程中不斷更新直到生成最終模型,。這里,,convW是一個16行25列的矩陣,B是長度為16的向量,。Convolution是內(nèi)置的卷積函數(shù),,默認(rèn)不使用補零的方法。也就是說對28x28的圖像做卷積運算,,實際上只是對24x24的中心區(qū)域操作,,得到的結(jié)果是16個24x24的sudo-image。 接著我們用2x2的區(qū)域應(yīng)用最大池化操作,,最后得到的結(jié)果是16個12x12的矩陣,。 對于第二個卷積層,我們把卷積濾波器的個數(shù)由16個提升到32個,。這一次我們有16通道的輸入數(shù)據(jù),,因此W矩陣的尺寸為32行25×16 = 400列,向量B的長度為32,。這次的卷積運算針對12x12圖像幀的中心區(qū)域,,所以得到的結(jié)果是32個8x8的矩陣。第二次池化操作的結(jié)果是32個4x4的幀,,或者32x16=512,。 最后兩層,是由512個池化輸出結(jié)果經(jīng)過128個節(jié)點的隱藏層連接到10個輸出節(jié)點,,經(jīng)歷了兩次運算操作,。
如你所見,這些運算步驟都是標(biāo)準(zhǔn)的線性代數(shù)運算形式W*x+b,。 圖定義的最后部分是交叉熵和誤差節(jié)點,,以及將它們綁定到特殊的節(jié)點名稱。 我們接著要來定義訓(xùn)練的過程,但是先把它與用TensorFlow構(gòu)建相似的網(wǎng)絡(luò)模型做個比較,。我們在之前的文章里討論過這部分內(nèi)容,,這里再討論一次,。你是否注意到我們使用了與CNTK相同的一組變量,,只不過這里我們把它稱作變量,而在CNTK稱作參數(shù),。維度也略有不同,。盡管卷積濾波器都是5x5,在CNTK我們前后兩級分別使用了16個和32個濾波器,,但是在TensorFlow的例子里我們用的是32個和64個,。
網(wǎng)絡(luò)的構(gòu)建過程也大同小異。
卷積運算的唯一不同之處是這里定義了補零,,因此第一次卷積運算的輸出是28x28,,經(jīng)過池化后,降為14x14,。第二次卷積運算和池化之后的結(jié)果降為了7x7,,所以最后一層的輸入是7x7x64 = 3136維,有1024個隱藏節(jié)點(使用relu而不是sigmoid函數(shù)),。(在訓(xùn)練時,,最后一步用到了dropout函數(shù)將模型數(shù)值隨機地置零。如果keep_prob=1則忽略這步操作,。) 網(wǎng)絡(luò)訓(xùn)練CNTK中設(shè)置網(wǎng)絡(luò)模型訓(xùn)練的方式與TensorFlow差別巨大,。訓(xùn)練和測試步驟是在一個convolution.config的文件內(nèi)設(shè)置。CNTK和TensorFlow都是通過符號化分析流程圖來計算梯度下降訓(xùn)練算法中所用到的梯度值,。CNTK組給出了一本非常贊的“書”來闡述梯度是如何計算的?,F(xiàn)階段CNTK只支持一種學(xué)習(xí)方法:Mini-batch隨機梯度下降法,但他們承諾今后加入更多的算法,。He, Zhang, Ren 和 Sun發(fā)表了一篇優(yōu)秀的論文介紹他們是如何用嵌套殘留還原法(nested residual reduction)來訓(xùn)練極度深層(深達(dá)1000層)的網(wǎng)絡(luò)模型,,所以讓我們拭目以待這個方法被融入到CNTK中。配置文件的縮略版如下所示,。
命令行顯示了執(zhí)行的順序:先訓(xùn)練后測試,。先聲明了各種文件的路徑,然后訓(xùn)練模塊設(shè)置了待訓(xùn)練的網(wǎng)絡(luò)模型以及隨機梯度下降(SGD)的參數(shù),。讀取模塊根據(jù)NDL文件中的設(shè)置讀取了“特征”和“標(biāo)簽”數(shù)據(jù),。測試模塊設(shè)置了用于測試的參數(shù)。 16核(沒有GPU)的Linux VM需要消耗62.95分鐘來執(zhí)行訓(xùn)練和測試過程,,999.01分鐘的用戶時間和4分鐘的系統(tǒng)時間,。用戶時間指的是所有16個核都在滿負(fù)荷運轉(zhuǎn)(999/63 = 15.85)。但這并不算什么,因為CNTK是為并行計算而設(shè)計的,,大規(guī)模GPU支持才是真正的設(shè)計點,。 TensorFlow的訓(xùn)練步驟在Python控制流程中設(shè)置得更清晰。而使用的算法Adam也是基于梯度計算的,,由Kingma和Ba發(fā)明,。TensorFlow的函數(shù)庫里有大量基于梯度的優(yōu)化方法,但我沒有嘗試其它的方法,。 如下所以,,cross_entropy是按照標(biāo)準(zhǔn)形式定義的,然后傳入優(yōu)化器生成一個 “train_step”對象,。
隨后Python腳本每批處理50條數(shù)據(jù),,以50%的舍棄率執(zhí)行train_step,迭代20000次,。測試步驟在整個測試集上評估準(zhǔn)確率,。 除了巧妙的自動求積分和Adam優(yōu)化器的構(gòu)建,一切都是直截了當(dāng)?shù)?。我?6核的服務(wù)器上用CNTK例子中相同的數(shù)據(jù)集又跑了一遍,。出乎我意料的是所需的時間與CNTK幾乎一模一樣。實際運行時間是62.02分鐘,,用戶時間為160.45分鐘,,所以幾乎沒用利用并行運算。我覺得這些數(shù)字不能說明什么,。CNTK和TensorFlow都是為大規(guī)模GPU運算而設(shè)計的,,它們運行的訓(xùn)練算法并不完全一致。 遞歸神經(jīng)網(wǎng)絡(luò)在CNTK和TensorFlow的實現(xiàn)遞歸神經(jīng)網(wǎng)絡(luò)(RNNs)在語言建模方面用途廣泛,,例如打字時預(yù)測下一個輸入單詞,,或是用于自動翻譯系統(tǒng)。(更多例子請參見Andrej Karpathy的博客)真是個有趣的想法,。系統(tǒng)的輸入是一個單詞(或者一組單詞)以及系統(tǒng)基于目前所出現(xiàn)單詞而更新的狀態(tài),,輸出的是一個預(yù)測單詞列表和系統(tǒng)的新狀態(tài),如圖1所示,。 圖1 當(dāng)然,,RNN還有許多變種形式。其中一種常見的形式是長短期記憶模型(LSTM),,其定義公式如下: 圖2:LSTM方程組(來源于CNTKBook) 此處 \sigma 表示sigmoid函數(shù),。 如果你有興趣讀一篇關(guān)于LSTM及其工作原理的博文,我推薦Christopher Olah所寫的這篇,。他繪制了一張示意圖,,使得上面的等式更容易理解。我把它稍作修改,使它符合CNTK版本的方程式,,結(jié)果如下圖所示,。 圖3:改編自Christopher Olah的優(yōu)秀文章 圖中使用了sigmoid和tanh函數(shù),并且級聯(lián)變量得到了下面的表達(dá)式: 其中W和b是學(xué)習(xí)得到的權(quán)重,。 CNTK版本下面是為LSTM模型設(shè)置的NDL,。有兩件事需要注意。一個是網(wǎng)絡(luò)模型中使用了一個稱作“PastValue”的延遲操作符直接處理了遞歸的邏輯,,它用到了維度和延遲時間兩個變量,,返回該值的一個副本,。第二件事情是注意W矩陣的處理方式,,它與上面以及圖3中所示的級聯(lián)操作有何區(qū)別。在這里,,它們把屬于x和h的所有W壓入堆棧,,把所有b值也存入堆棧。然后計算一個W*x和一個W*h并求和,,再加上b的值,。然后再使用一個行切分操作符,分別用獨立的sigmoid函數(shù)處理它們,。還需關(guān)注的是針對c的W矩陣都是對角陣,。 TensorFlow版本 TensorFlow版本的LSTM遞歸神經(jīng)網(wǎng)絡(luò)模型與CNTK版本完全不同。盡管它們所執(zhí)行的操作符都一樣,,但TensorFlow的表示方式充分發(fā)揮了Python控制流的作用,。這個概念模型非常簡單。我們創(chuàng)建了一個LSTM單元,,并且定義一個“狀態(tài)”作為此單元的輸入,,同時也是此單元的輸出。偽代碼如下所示:
這段摘自教程的偽代碼版本很好地反映了圖1的內(nèi)容,。折磨人的地方在于微妙細(xì)節(jié)的處理,。記住大部分時間TensorFlow的python代碼是在搭建流程圖,所以我們需要下一點功夫來繪制用于訓(xùn)練和執(zhí)行的循環(huán)流程圖,。 這里最大的挑戰(zhàn)在于如何在一個循環(huán)內(nèi)創(chuàng)建并重復(fù)使用權(quán)重矩陣和偏置向量,。CNTK使用了“PastValue”操作符來創(chuàng)建所需的循環(huán)。TensorFlow則使用了上面提到的所謂遞歸機制,,和一個非常聰明的變量保存和調(diào)用機制來完成同樣的任務(wù),。“PastValue”在TensorFlow中對應(yīng)的是一個函數(shù),, tf.get_variable( “name”, size, initializer = None) ,,它的行為取決于當(dāng)前變量域中的“reuse”這個標(biāo)志位。如果reuse==False而且在當(dāng)時不存在其它的同名變量,那么get_variable 用那個變量名返回一個新的變量,,并用初始化器對其初始化,。否則將會返回錯誤。如果reuse == True,,那么get_variable返回之前已經(jīng)存在的那個變量,。如果不存在這樣的變量,則返回一個錯誤,。 為了演示這種用法,,以下是TensorFlow的一個函數(shù)的簡化版本,用來創(chuàng)建上面等式一的sigmoid函數(shù),。它只是W*x+b 的一個演化版本,,其中x是一個list[a,b,c,…]
接下來定義BasicLSTMCell,大致的寫法如下所示,。(想要查看這些函數(shù)的完整版本,,請前往TensorFlow Github代碼庫里的rnn_cell.py腳本。)
你可以看到,,這里相當(dāng)準(zhǔn)確地再現(xiàn)圖3中的圖示,。你會注意到上面的split操作符正是對應(yīng)于CNTK的row slice操作符。 現(xiàn)在我們可以創(chuàng)建一個可以用于訓(xùn)練的遞歸神經(jīng)網(wǎng)絡(luò)模型,,在同樣的變量域我們能用共享的W和b變量創(chuàng)建另一個網(wǎng)絡(luò)模型用于測試,。具體的做法在TensorFlow的遞歸神經(jīng)網(wǎng)絡(luò)教程ptb_word_lm.py腳本中有介紹。還有兩點值得留意,。(應(yīng)該說它們對于我理解這個例子,,有著至關(guān)重要的作用)他們創(chuàng)建一個lstmModel類來訓(xùn)練和測試網(wǎng)絡(luò)模型。
我們在主程序中創(chuàng)建一個訓(xùn)練實例和一個測試實例,,并調(diào)用它們(事實上還要創(chuàng)建一個實例,,為了簡化過程我暫時先把它忽略)。
在上述代碼中,,創(chuàng)建了實例m,,初始化設(shè)置20步且不用reuse。從初始化這一步你能觀察到,,在計算流程圖中該單元被展開成20個副本,,并且在首次迭代后reuse標(biāo)志置為True,此時所有的實例都將共享同一組W和b,。訓(xùn)練過程在這個展開的版本上完成,。第二個版本mtest設(shè)置reuse=True,且在圖中只有該單元的一個實例,。但是變量域和m相同,,因此它與m共享同一組訓(xùn)練得到的變量,。 一旦訓(xùn)練完成,我們可以用一個內(nèi)核來調(diào)用這個網(wǎng)絡(luò)模型,。
x和y是輸入變量,。這和教程示例中的完整過程相去甚遠(yuǎn)。舉個例子,,我完全沒有深入到訓(xùn)練過程的細(xì)節(jié)中去,,完整的示例使用了stacked LSTM并設(shè)置了dropout的比例。我的希望是,,我在此羅列的細(xì)節(jié)將有助于讀者了解代碼的最基本結(jié)構(gòu),。 總結(jié)我對兩個系統(tǒng)的編程模型做了比較。這里是一些頂層的想法,。 1. TensorFlow和CNTK在卷積神經(jīng)網(wǎng)絡(luò)那個簡單例子中的做法非常相似,。然而,我發(fā)現(xiàn)tensorflow版本更容易進行實驗,,因為它是由Python驅(qū)動的,。我能用IPython notebook加載它并做一些其它嘗試,。而CNTK則需要用戶完全理解如何用配置文件表達(dá)想法,。我覺得這很困難。我用TensorFlow能很容易寫一個簡單的k-means聚類算法(詳見我之前關(guān)于TensorFlow的文章),。我卻無法用CNTK來實現(xiàn),,不過這可能是由于我的無知,而不是CNTK的局限性,。如果有人能提示我該怎么做,,我會很感激的)。 2. 在LSTM遞歸神經(jīng)網(wǎng)絡(luò)的例子里,,我發(fā)現(xiàn)CNTK的版本相當(dāng)?shù)耐该?。我發(fā)現(xiàn)TensorFlow版本的頂層想法非常優(yōu)雅,但我也發(fā)現(xiàn)想了解它的所有細(xì)節(jié)卻非常困難,,因為涉及到了變量作用域和變量共享的巧妙用法,。我不得不深入地了解它的工作原理。但到現(xiàn)在我也不是十分清楚,!我在TensorFlow版本里確實發(fā)現(xiàn)了一個微小但很容易修復(fù)的bug,,而且我不相信變量作用域和reuse標(biāo)志是解決封裝問題的最好方法。但是TensorFlow的好處在于我能很容易地修改實驗,。 3. 我也必須說CNTK書和TensorFlow教程都是優(yōu)秀入門級讀物,。我相信有更多詳細(xì)的、深入的書馬上就會面世,。 我也相信,,隨著兩個系統(tǒng)的不斷成熟,,它們都會有改進,并且能更容易地使用,。我在此不討論性能問題,,但CNTK目前在解決某些挑戰(zhàn)難題的速度方面略勝一籌。但隨著這些系統(tǒng)的快速發(fā)展,,我希望看到競爭也隨之升溫,。
想了解IT產(chǎn)品研發(fā)背后的那些人、技術(shù)和故事,,請關(guān)注CSDN(資訊)微信公眾號:CSDNnews |
|