從Caffe2到TensorFlow,十種框架構(gòu)建相同神經(jīng)網(wǎng)絡(luò)效率對(duì)比
近日,Ilia Karmanov 在 Medium 發(fā)表了一篇題為《Neural Net in 10 Frameworks (Lessons Learned)》的文章,其內(nèi)容源自一個(gè) GitHub 項(xiàng)目,其中作者通過(guò)構(gòu)建同一個(gè)神經(jīng)網(wǎng)絡(luò),對(duì)比了當(dāng)前***的 10 種深度學(xué)習(xí)框架,其中 Caffe2 和 MXNet 在準(zhǔn)確度和訓(xùn)練時(shí)長(zhǎng)上處于領(lǐng)先位置。該項(xiàng)目甚至還得到了 FAIR 研究者、各大框架創(chuàng)始人(比如賈揚(yáng)清)的支持。機(jī)器之心對(duì)該文進(jìn)行了編譯。
項(xiàng)目GitHub鏈接:https://github.com/ilkarman/DeepLearningFrameworks
除卻所有的技術(shù)元素之外,我發(fā)現(xiàn)關(guān)于這一項(xiàng)目最有趣的事情是來(lái)自開源社區(qū)的驚人貢獻(xiàn)。社區(qū)發(fā)起的請(qǐng)求(pull request)、提出的問(wèn)題(issue)非常有助于在準(zhǔn)確度和訓(xùn)練時(shí)間方面統(tǒng)合所有框架??吹?FAIR 研究者、框架的創(chuàng)始人(比如賈揚(yáng)清)以及 GitHub 的其他用戶所做出的貢獻(xiàn),我很震驚。沒(méi)有他們,就不會(huì)有這個(gè)項(xiàng)目的完成。他們不僅給出了代碼建議,還提供了不同框架的整個(gè) notebook。
你可以在這里看到貢獻(xiàn)之前該項(xiàng)目的最初狀態(tài):
https://github.com/ilkarman/DeepLearningFrameworks/tree/0143957489e8adbecaa975f9b541443421db5c4b
問(wèn)題
搜索 Tensorflow + MNIST 會(huì)出現(xiàn)這個(gè)看起來(lái)很復(fù)雜的教程,它規(guī)避了更高級(jí)的 API(tf.layers or tf.nn),并且似乎沒(méi)有從輸入數(shù)據(jù)中充分分離,因此使用 CIFAR(舉例來(lái)說(shuō))替代 MNIST 更加讓人舒服。一些教程為了避免冗長(zhǎng)加載 MNIST 有一個(gè)自定義的封裝器,比如 framework.datasets.mnist,但是對(duì)此我有兩個(gè)問(wèn)題:
- 初學(xué)者可能并不太清楚如何在數(shù)據(jù)上重新運(yùn)行。
- 將其與另一個(gè)框架對(duì)比也許更加棘手(預(yù)處理會(huì)有所不同嗎?)
其他教程把 MNIST 作為文本文件(或自定義數(shù)據(jù)庫(kù))保存到硬盤,接著使用 TextReaderDataLoader 再次加載。這個(gè)想法表明,如果用戶有一個(gè)大型數(shù)據(jù)集,它太大以至于無(wú)法加載到 RAM,并且需要大量的即時(shí)轉(zhuǎn)換,那么會(huì)發(fā)生什么。對(duì)于初學(xué)者來(lái)說(shuō),這也許是誤導(dǎo)性的,使人膽怯;我經(jīng)常被問(wèn)到:「為什么我需要保存它,我明明有一個(gè)數(shù)組!」
目標(biāo)
本文的目標(biāo)是如何使用 10 個(gè)***的框架(在一個(gè)常見(jiàn)的自定義數(shù)據(jù)集上)構(gòu)建相同的神經(jīng)網(wǎng)絡(luò)——一個(gè)深度學(xué)習(xí)框架的羅塞塔石碑,從而允許數(shù)據(jù)科學(xué)家在不同框架之間(通過(guò)轉(zhuǎn)譯而不是從頭學(xué)習(xí))發(fā)揮其專長(zhǎng)。不同框架具有相同模型的一個(gè)結(jié)果就是框架在訓(xùn)練時(shí)間和默認(rèn)選項(xiàng)上變得越發(fā)透明,我們甚至可以對(duì)比特定的元素。
能夠快速地把你的模型轉(zhuǎn)換為另一個(gè)框架意味著你能夠交換 hats。如果另一個(gè)框架有一個(gè)層需要你從頭編寫,用更有效的方式處理數(shù)據(jù)資源,或者使其更匹配正運(yùn)行于其上的平臺(tái)(比如安卓)。
對(duì)于這些教程,我嘗試不顧違反默認(rèn)選項(xiàng),使用***級(jí)別的 API,從而更加便捷地對(duì)比不同框架。這意味著 notebook 并不是專為速度而寫。
這將證明如果使用更高級(jí)的 API,代碼結(jié)構(gòu)將變得相似,并可被大體表征為:
- Load data into RAM; x_train, x_test, y_train, y_test = cifar_for_library(channel_first=?, one_hot=?)
- 把數(shù)據(jù)加載到 RAM;x_train, x_test, y_train, y_test = cifar_for_library(channel_first=?, one_hot=?)
- 生成 CNN 符號(hào)(在***的密集層上通常沒(méi)有激活)
- 指定損失(交叉熵通常與 softmax 綁定)、優(yōu)化器和初始化權(quán)重,也許還有 session
- 使用自定義迭代器(所有框架的通用數(shù)據(jù)源)在訓(xùn)練集的小批量上進(jìn)行訓(xùn)練
- 對(duì)測(cè)試集的小批量進(jìn)行預(yù)測(cè),也許為層(比如 dropout)指定測(cè)試標(biāo)記
- 評(píng)估準(zhǔn)確率
注意事項(xiàng)
我們實(shí)際上比較了一系列確定的數(shù)學(xué)操作(盡管初始化比較隨意),因此比較框架的準(zhǔn)確率并無(wú)意義,相反,我們想匹配框架的準(zhǔn)確率,來(lái)確保我們?cè)趯?duì)同樣的模型架構(gòu)進(jìn)行對(duì)比。
我說(shuō)比較速度沒(méi)有意義的原因是:使用數(shù)據(jù)裝載器(僅)可以減少幾秒,因?yàn)?shuffling 應(yīng)該異步執(zhí)行。但是,對(duì)于一個(gè)合適的項(xiàng)目,你的數(shù)據(jù)不可能適合 RAM,可能需要大量預(yù)處理和操作(數(shù)據(jù)增強(qiáng))。這就是數(shù)據(jù)裝載器的作用。賈揚(yáng)清認(rèn)為:
我們?cè)诙鄠€(gè)網(wǎng)絡(luò)中經(jīng)歷了主要瓶頸 I/O,因此告訴人們?nèi)绻胍?**的性能,使用異步 I/O 會(huì)有很大幫助。
這一實(shí)例中僅使用若干個(gè)層(conv2d、max_pool2d、dropout、全連接)。對(duì)于一個(gè)合適的項(xiàng)目,你也許有 3D 卷積、GRU、LSTM 等等。
輕松添加自定義層(或者層的可用性,比如 k ***池化或者分層 softmax),及其運(yùn)行速度可以促成或毀掉你的框架選擇。能夠用 python 代碼寫一個(gè)自定義層并快速執(zhí)行它對(duì)研究項(xiàng)目至關(guān)重要。
結(jié)果
在 CIFAR-10 上的 VGG-style CNN
IMDB 上的 LSTM(GRU)
心得體會(huì)(匹配準(zhǔn)確率/時(shí)間)
下列是我對(duì)多個(gè)框架測(cè)試準(zhǔn)確率進(jìn)行匹配,并根據(jù) GitHub 收集到的問(wèn)題/PR 得到的一些觀點(diǎn)。
1. 為方便對(duì)比,上文中的實(shí)例(除了 Keras)使用同等水平的 API 和同樣的生成器函數(shù)。我在 MXNet 和 CNTK 的實(shí)驗(yàn)中使用了更高水平的 API,在該 API 上使用框架的訓(xùn)練生成器函數(shù)。該實(shí)例中的速度提升幾乎微不足道,原因在于整個(gè)數(shù)據(jù)集作為 NumPy 數(shù)組在 RAM 中加載,每個(gè) epoch 所做的唯一的處理是 shuffle。我懷疑該框架的生成器也在異步執(zhí)行 shuffle 操作。奇怪的是,似乎框架在一個(gè)批次水平上進(jìn)行 shuffle,而不是在觀察層面上,因此測(cè)試準(zhǔn)確率稍稍降低(至少在 10 epoch 之后)。在框架運(yùn)行時(shí)進(jìn)行的 IO 活動(dòng)、預(yù)處理和數(shù)據(jù)增強(qiáng)的場(chǎng)景中,自定義生成器對(duì)性能的影響更大。
2. 啟用 CuDNN 的自動(dòng)調(diào)整/窮舉搜索參數(shù)(對(duì)固定大小的圖像選擇***效的 CNN 算法)會(huì)使性能大幅提升。在 Caffe2、PyTorch 和 Theano 中,必須手動(dòng)啟用。而在 CNTK、MXNet 和 Tensorflow 中,該操作默認(rèn)進(jìn)行。我不確定 Chainer 是什么情況。賈揚(yáng)清提到 cudnnGet(默認(rèn))和 cudnnFindi 之間的性能提升比 Titan X GPU 上要小;看起來(lái) K80 + new cudnn 使該問(wèn)題在這種情況下更加突出。在目標(biāo)檢測(cè)的每一次規(guī)模連接中運(yùn)行 cudnnFind 會(huì)帶來(lái)嚴(yán)重的性能回歸,但是,正因如此,可以在目標(biāo)檢測(cè)時(shí)禁用 exhaustive_search。
3. 使用 Keras 時(shí),選擇匹配后端框架的 [NCHW] 排序很重要。CNTK 首先使用通道運(yùn)行,我錯(cuò)誤地將 Keras 配置為***使用通道。之后,Keras 在每一批次必須改變順序,這引起性能的嚴(yán)重下滑。
4. Tensorflow、PyTorch、Caffe2 和 Theano 要求向池化層提供一個(gè)布爾值,來(lái)表明我們是否在訓(xùn)練(這對(duì)測(cè)試準(zhǔn)確率帶來(lái)極大影響,72% vs 77%)。
5. Tensorflow 有一點(diǎn)麻煩,它需要兩個(gè)改變:?jiǎn)⒂?TF_ENABLE_WINOGRAD_NONFUSED 來(lái)提升速度;首先改變通道的維度,而不是***再改變(data_format=』channels_first』)。TF 作為后端時(shí),在卷積層上啟用 WINOGRAD 自然也能改善 Keras 的性能。
6. 對(duì)于大多數(shù)函數(shù),Softmax 通常與 cross_entropy_loss() 綁定在一起,有必要檢查一下***的全連接層是否需要激活,以省下應(yīng)用兩次激活的時(shí)間。
7. Kernel 初始程序在不同的框架中會(huì)發(fā)生改變(我發(fā)現(xiàn)這對(duì)準(zhǔn)確率有+/- 1% 的影響),我試圖在可能/不是很長(zhǎng)的情況下指定統(tǒng)一的 xavier/gloro。
8. SGD 動(dòng)量實(shí)現(xiàn)的動(dòng)量類型。我必須關(guān)閉 unit_gain(只在 CNTK 中默認(rèn)開啟),以匹配其他框架的實(shí)現(xiàn)。
9. Caffe2 在網(wǎng)絡(luò)***層需要額外的優(yōu)化(no_gradient_to_input=1),通過(guò)不計(jì)算輸入的梯度產(chǎn)生小幅提速。有可能 Tensorflow 和 MXNet 已經(jīng)默認(rèn)啟用該項(xiàng)。計(jì)算梯度對(duì)搜索和 deep-dream 網(wǎng)絡(luò)有用。
10. 在***池化之后(而不是之前)應(yīng)用 ReLU 激活意味著你在降維之后執(zhí)行計(jì)算,并減少幾秒時(shí)間。這幫助 MXNet 時(shí)間減少了 3 秒。
11. 一些可能有用的進(jìn)一步檢查:
- 指定 kernel 為 (3) 變成對(duì)稱元組 (3, 3) 或 1D 卷積 (3, 1)?
- 步幅(用于***池化)默認(rèn)為 (1, 1),還是等同于 kernel(Keras 會(huì)這樣做)?
- 默認(rèn)填充通常是 off (0, 0)/valid,但是對(duì)檢查它不是 on/』same』很有用
- 卷積層上的默認(rèn)激活是『None』還是『ReLu』(Lasagne)?
- 偏差初始程序可能會(huì)改變(有時(shí)不包含任何偏差)。
- 不同框架中的梯度截?cái)嗪?inifinty/NaNs 處理可能會(huì)不同。
- 一些框架支持稀疏標(biāo)簽,而不是獨(dú)熱標(biāo)簽(如,Tensorflow 中有 f.nn.sparse_softmax_cross_entropy_with_logits)。
- 數(shù)據(jù)類型的假設(shè)可能會(huì)不同:我嘗試使用 float32 和 int32 作為 X、y。但是,舉例來(lái)說(shuō),torch 需要 y 變成 2 倍(強(qiáng)制轉(zhuǎn)換成 torch.LongTensor(y).cuda)
- 如果框架 API 的水平稍微低了一點(diǎn),請(qǐng)確保你在測(cè)試過(guò)程中,不通過(guò)設(shè)置 training=False 等來(lái)計(jì)算梯度。
原文:
https://medium.com/@iliakarmanov/neural-net-in-8-frameworks-lessons-learned-6a5e8e78b481
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號(hào)“機(jī)器之心( id: almosthuman2014)”】