大神帶你分分鐘超越最好結果——基於分佈式CPU計算的Deeplearning4j遷移學習應用實例...

原文鏈接:http://click.aliyun.com/m/26852/
摘要: 本文通過應用開源工具Apache Spack、Apache Hadoop和Deeplearning4j以分佈式CPU運算完成VGG16模型遷移學習的應用,在Caltech-256數據集上實現了最好結果。

首發地址:https://yq.aliyun.com/articles/114669

更多深度文章,請關注:https://yq.aliyun.com/cloud

2016年,歐萊禮媒體公司首席數據科學家羅瑞卡宣稱:“2017年將是數據科學和大數據圈參與AI技術合作的一年。”在2017年之前,對基於GPU的深度學習已經滲透到大學和研究機構,但基於CPU分散式深度學習開始在不同的公司和領域得到廣泛採用。雖然GPU提供了頂級的數字計算性能,但CPU也在變得更加高效,並且現有的大部分硬件已經有大量可用的CPU計算能力。另外GPU的價格比CPU的價格要相對而言貴好多,相信大家最近一陣也發現顯卡的價格暴漲,這源於數字貨幣比特幣的暴漲,而比特幣是通過電腦計算得到,計算能力越強,其每天的計算量也就越多,相當於每天“挖礦”的量。涉及深度學習的研究員都應該瞭解一個事實,基於GPU跑一個網絡和基於CPU跑同一個網絡,二者的仿真速度可以達到20倍左右的差距。因此,基於CPU的分散式深度學習也會成爲後續研究的一個方向。而開源工具Deeplearning4j的出現將快速深度學習擴展到Hadoop堆棧,這將是未來幾年影響深度學習的主要催化劑。
本文將詳細介紹如何使用開源工具——Apache Spark、Apache Hadoop和Deeplearning4j(DL4J),再加上商用硬件(Commodity Hardware,便宜、被廣泛使用、容易被買到),能夠使用有限的訓練集在圖像識別任務上獲得最先進的結果。
Deeplearning4j:JVM的深度學習工具集
Deeplearning4j是許多開源深度學習工具包之一,創建於2014年。DL4J集成了Hadoop和Spark,設計用於運行在分佈式GPU和CPU上的商用環境。它由總部位於舊金山的商用智能和企業軟件公司Skymind牽頭開發。團隊成員包括數據專家、深度學習專家、Java系統工程師和具有一定感知力的機器人。雖然deeplearning4j是爲JVM構建的,但它使用高性能原生線性代數庫Nd4j,可以對CPU或GPU進行大量優化的計算。另外使用Java編寫的DL4J API對於熟悉Java虛擬機(JVM)的Java和Scala開發人員特別有吸引力。此外,Spark模型的並行訓練能力使得我們輕鬆利用現有的羣集資源來加快訓練時間,而不會犧牲精度。
基於Caltech-256圖像數據集的對象分類
本文介紹如何使用Apache Spark、Apache Hadoop和deeplearning4j來解決圖像分類問題。簡單來說,就是通過構建一個卷積神經網絡來對Caltech-256數據集中的圖像進行分類。在Caltech-256數據集中,實際上有257個對象類別,每類數量大概是80到800個圖像,該數據集總共30,607個圖像。值得注意的是,該數據集上目前最先進的分類精度在72 - 75%範圍內。下面我將帶領大家使用DL4J和Spark輕鬆超越這個結果。

小數據上的有效深度學習
目前,卷積網絡可以有幾億個參數,比如在大型視覺識別挑戰 “ImageNet”中表現最佳的神經網絡之一,有1.4億個參數需要訓練!這些網絡不僅需要大量的計算和存儲資源(即使是使用一組GPU,也可能需要幾周時間才能完成計算),而且還需要大量數據。而Caltech-256只有30000多張圖像,在這個數據集上訓練這樣一個複雜的模型是不現實的,因爲沒有足夠的樣本來充分學習這麼多參數。相反,可以採用一種遷移學習的方法來實現。簡單來說,就是將已學到的知識應用到其它領域,使其能夠更好地完成新領域的學習。這是因爲卷積神經網絡在對圖像數據集進行訓練時往往會學習非常普遍的特徵,因此這種類型的特徵學習通常對其他圖像數據集也是通用的。例如,在ImageNet上訓練的網絡可能已經學會了如何識別形狀、面部特徵、圖案、文本等,這無疑對於Caltech-256數據集是有用的。
加載預訓練的模型
下面講解如何使用訓練好的模型來完成自己的任務,以下示例使用VGG16 模型,該模型奪得了2014 ImageNet競賽中的亞軍(網絡結構及訓練好的參數已公開)。由於使用了不同的圖像數據集,所以需要對VGG16模型進行微小修改以適用於Caltech-256數據集預測任務。該模型具有約1.4億個參數,大約佔用500 MB空間。

首先,獲取DL4J可以理解和使用的VGG16型號的版本。事實證明,這種東西是建立在DL4J的API中的,它可以通過幾行Scala代碼完成。


val modelImportHelper = new TrainedModelHelper(TrainedModels.VGG16)
val vgg16 = modelImportHelper.loadModel()
val savePath = "./dl4j-models/vgg16.zip"
val locationToSave = new File(savePath)
// save the model in DL4J native format, which is faster for future reads
ModelSerializer.writeModel(vgg16, locationToSave, saveUpdater = true)

該模型採用的格式易於DL4J使用,使用內置的模型進行檢查。


val modelFile = new File("./dl4j-models/vgg16.zip")
val vgg16 = ModelSerializer.restoreComputationGraph(modelFile)
println(vgg16.summary())


==================================================================================================

VertexName (VertexType) nIn,nOut TotalParams ParamsShape Vertex Inputs

==================================================================================================

input_2 (InputVertex) -,- - - -

block1_conv1 (ConvolutionLayer) 3,64 1792 b:{1,64}, W:{64,3,3,3} [input_2]

block1_conv2 (ConvolutionLayer) 64,64 36928 b:{1,64}, W:{64,64,3,3} [block1_conv1]

block1_pool (SubsamplingLayer) -,- 0 - [block1_conv2]

block2_conv1 (ConvolutionLayer) 64,128 73856 b:{1,128}, W:{128,64,3,3} [block1_pool]

block2_conv2 (ConvolutionLayer) 128,128 147584 b:{1,128}, W:{128,128,3,3} [block2_conv1]

block2_pool (SubsamplingLayer) -,- 0 - [block2_conv2]

block3_conv1 (ConvolutionLayer) 128,256 295168 b:{1,256}, W:{256,128,3,3} [block2_pool]

block3_conv2 (ConvolutionLayer) 256,256 590080 b:{1,256}, W:{256,256,3,3} [block3_conv1]

block3_conv3 (ConvolutionLayer) 256,256 590080 b:{1,256}, W:{256,256,3,3} [block3_conv2]

block3_pool (SubsamplingLayer) -,- 0 - [block3_conv3]

block4_conv1 (ConvolutionLayer) 256,512 1180160 b:{1,512}, W:{512,256,3,3} [block3_pool]

block4_conv2 (ConvolutionLayer) 512,512 2359808 b:{1,512}, W:{512,512,3,3} [block4_conv1]

block4_conv3 (ConvolutionLayer) 512,512 2359808 b:{1,512}, W:{512,512,3,3} [block4_conv2]

block4_pool (SubsamplingLayer) -,- 0 - [block4_conv3]

block5_conv1 (ConvolutionLayer) 512,512 2359808 b:{1,512}, W:{512,512,3,3} [block4_pool]

block5_conv2 (ConvolutionLayer) 512,512 2359808 b:{1,512}, W:{512,512,3,3} [block5_conv1]

block5_conv3 (ConvolutionLayer) 512,512 2359808 b:{1,512}, W:{512,512,3,3} [block5_conv2]

block5_pool (SubsamplingLayer) -,- 0 - [block5_conv3]

flatten (PreprocessorVertex) -,- - - [block5_pool]

fc1 (DenseLayer) 25088,4096 102764544 b:{1,4096}, W:{25088,4096} [flatten]

fc2 (DenseLayer) 4096,4096 16781312 b:{1,4096}, W:{4096,4096} [fc1]

predictions (DenseLayer) 4096,1000 4097000 b:{1,1000}, W:{4096,1000} [fc2]

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

Total Parameters: 138357544

Trainable Parameters: 138357544

Frozen Parameters: 0

==================================================================================================


上面代碼顯示VGG16網絡的結構及參數,ConvolutionLayer表示卷積層、SubsamplingLayer表示採樣層、DenseLayer表示全連接層。下圖簡明扼要的展示了該網絡結構:

7b68f25bb9d9cf6fe4cb67cdbaa6dae34a2b2f05

VGG16具有13個卷積層,中間間隔放置最大池化層以收縮圖像,降低計算複雜度。卷積層中的權重實際上是過濾器,可以學習從圖像中挑選出視覺特徵,當使用最大池化層時,它們會“收縮”圖像,這意味着後來的卷積層中的濾波器實際上提取更加抽象的特徵。這樣,卷積層的輸出是輸入圖像的抽象的視覺特徵,如“這個圖像中有臉嗎?”還是“有日落?”卷積層的輸出被饋送到連續的三個全連接層,全連接層能夠學習這些視覺特徵與輸出之間的非線性關係。

另外卷積網絡的關鍵性質之一是允許我們進行遷移學習——可以通過已經訓練好的VGG16網絡傳遞新的圖像數據,並獲取每個圖像的特徵。一旦提取了這些特徵,就只需要送人最後的預測網絡就可以完成相應的任務,這在計算和複雜度上都是非常容易解決的問題。

使用VGG16進行圖像特徵化
數據集可以從Caltech-256 網站下載,拆分爲三個數據集,分別爲訓練/驗證/測試數據集,並存儲在HDFS中。一旦完成該步驟,下一步就是將整個圖像數據集傳遞到網絡的所有卷積層和第一個全連接層,並將該輸出保存到HDFS。
樣做的原因是是因爲卷積網絡中的大多數內存佔用和耗時計算都是發生在卷積層中,VGG16中的大多數參數(權重)調用發生在全連接層。遷移學習利用預先訓練的卷積層來獲取關於新輸入圖像的特徵,這意味着只有原始模型的一小部分——全連接層被重新訓練。其餘的參數是靜態不變的。通過這種操作,遷移學習可以節省大量的訓練時間和計算量。
首先提取用於特徵化步驟的網絡部分,Deeplearning4j具有內置的遷移學習API可用於此任務。即拆分VGG16模型,在拆分之前和之後獲取整個圖層列表,代碼如下。


val modelFile = new File("./dl4j-models/vgg16.zip")
val vgg16 = ModelSerializer.restoreComputationGraph(modelFile)
val (frozenLayers: Array[Layer], unfrozenLayers: Array[Layer]) = {
vgg16.getLayers.splitAt(vgg16.getLayers.map(_.conf().getLayer.getLayerName).indexOf("fc2") + 1)
}
現在使用org.deeplearning4j.nn遷移學習包來提取全連接“fc2”層之前(包括“fc2”層)的網絡模型,如下圖所示:垂線左邊部分。

dc9a57ad25f7dcb047bd3f695301bcad81315d14


val builder = new TransferLearning.GraphBuilder(model)
.setFeatureExtractor(frozenLayers.last.conf().getLayer.getLayerName)
// remove all the unfrozen layers, leaving just the un-trainable part of the model
unfrozenLayers.foreach { layer =>
builder.removeVertexAndConnections(layer.conf().getLayer.getLayerName)
}
builder.setOutputs(frozenLayers.last.conf().getLayer.getLayerName)
val frozenGraph = builder.build()
接下來是讀取數據庫中的圖像文件。在這種情況下,這些文件被單獨保存到HDFS作爲JPEG文件。圖像被組織成子目錄,其中每個子目錄包含屬於特定類的一組圖像。首先通過使用sc.binaryFiles 加載存儲在HDFS中的圖像,並使用DataVec庫(DL4J的ETL庫)中的圖像處理工具將它們轉換爲INDArrays,這是DL4J處理的本機張量表示(此處爲完整代碼)。最後,使用上圖中的凍結網絡部分對輸入圖像進行特徵提取,本質上是將它們傳遞到VGG16模型中的預測層前。


val finalOutput = Utils.getPredictions(data, frozenGraph, sc)
val df = finalOutput.map { ds =>
(Nd4j.toByteArray(ds.getFeatureMatrix), Nd4j.toByteArray(ds.getLabels))
}.toDF()

df.write.parquet("hdfs:///user/leon/featurizedPredictions/train")
經過上述操作後,得到一個保存到HDFS中新的數據集。接下來可以開始構建使用這種特徵化數據的傳輸學習模型,從而大大減少訓練時間和計算複雜度。在上述示例中,得到的新數據集由30607個長度爲4096的向量組成(這是由於VGG16模型中的全連接層“f2”維度爲4096)。

替換VGG16的預測層
VGG16模型是在ImageNet數據集上進行訓練的,而ImageNet數據集具有1000種不同對象類別。在典型的圖像分類神經網絡中,輸出層的最後一層使用其輸入來爲數據集中的每個對象生成概率(哪一類的概率大就判斷爲哪一類)。因此,該輸入可以被認爲是關於圖像的抽象視覺特徵,提供關於其包含的對象的有用信息。直觀地說,上述步驟生成的新數據集於Caltech-256數據集中識別對象應該是有用的。因此,定義一個新的模型,“f2”層前的模型不變,只是替換VGG16模型的最後一層預測層,將維度從原先的1000變成257,正好對應Caltech256數據集的257個類別。


val conf = new NeuralNetConfiguration.Builder()
.seed(42)
.optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
.iterations(1)
.activation(Activation.SOFTMAX)
.weightInit(WeightInit.XAVIER)
.learningRate(0.01)
.updater(Updater.NESTEROVS)
.momentum(0.8)
.graphBuilder()
.addInputs("in")
.addLayer("layer0",
new OutputLayer.Builder(LossFunction.NEGATIVELOGLIKELIHOOD)
.activation(Activation.SOFTMAX)
.nIn(4096)
.nOut(257)
.build(),
"in")
.setOutputs("layer0")
.backprop(true)
.build()
val model = new ComputationGraph(conf)
直觀圖如下,可以看到只是改變了預測層的維度:

5d799bde174c83a77bb5082b6ea162a67dc56c79

該模型現在已準備好使用DL4J進行大量計算,而且還使用Spark進行規模化。簡單來說是切分大規模的數據集,然後將分片交給spark羣集中的每個工作核心上運行SGD,最後使用Spark RDD聚合操作對每個核心上學習的不同模型進行平均,實現分佈式訓練。


val tm = new ParameterAveragingTrainingMaster.Builder(1)
.averagingFrequency(5)
.workerPrefetchNumBatches(2)
.batchSizePerWorker(32)
.rddTrainingApproach(RDDTrainingApproach.Export)
.build()
val model = new SparkComputationGraph(sc, graph, tm)
現在針對具體的迭代次數訓練SparkComputationGraph,並監控一些訓練統計數據以跟蹤進度。


model.setListeners(new ScoreIterationListener(1))
(1 to param.numEpochs).foreach { i =>
logger4j.info(s"epoch $i starting")
model.fit(trainRDD)

// print model accuracy and score on entire train and validation sets every 5 iterations
if (i % 5 == 0) {
logger4j.info(s"Train score: ${model.calculateScore(trainRDD, true)}")
logger4j.info(s"Train stats:\n${Utils.evaluate(model.getNetwork, trainRDD, 16)}")
if (validRDD.isDefined) {
logger4j.info(s"Validation stats:\n${Utils.evaluate(model.getNetwork, validRDD.get, 16)}")
logger4j.info(s"Validation score: ${model.calculateScore(validRDD.get, true)}")
}
}
}
最後,通過spark提交訓練工作,然後使用DL4J webui監控進度並診斷問題。下圖繪製的是模型得分與迭代次數的關係,注意到分數是minibatch的負對數似然率,分數越小,效果越好。

b58c5b4d78ad0cd097854d0083f4593507d379ea

這次將學習率調低後,該模型似乎能比Imagenet模型能更快地學習,因爲這次使用的特徵比ImageNet概率更具預測性。


17/05/12 16:06:12 INFO caltech256.TrainFeaturized$: Train score: 0.6663876733861492
17/05/12 16:06:39 INFO caltech256.TrainFeaturized$: Train stats:
Accuracy: 0.8877570632327504
Precision: 0.8937314411403346
Recall: 0.876864905154427

17/05/12 16:07:17 INFO caltech256.TrainFeaturized$: Validation stats:
Accuracy: 0.7625918867410836
Precision: 0.7703367671469078
Recall: 0.7383574179140013

17/05/12 16:07:26 INFO caltech256.TrainFeaturized$: Validation score: 1.08481537405921
由於訓練準確率爲88.8%,但驗證準確率僅爲76.3%,從結果上看該模型似乎已經過擬合了。爲了確保模型不會過擬合到驗證集,在測試集上評估該模型。


Accuracy: 0.7530218882718066
Precision: 0.7613121478786196
Recall: 0.7286152891276695
雖然準確率有所降低,但是使用基於現有Hadoop集羣和商用CPU的簡單深度學習架構仍然打破了該數據集的最好結果!雖然這可能不是一個突破性的成就,但這仍然是一個令人興奮的結果。
結論
雖然deeplearning4j只是許多深度學習可用的工具之一,但它具有本機Apache Spark集成,並且採用Java編寫,使其特別適合整個Hadoop生態系統。由於現有的企業數據已經通過Hadoop進行了大量訪問,而且在Spark上進行處理,所以deeplearning4j的定位是花費更少的時間部署和減少開銷,從而企業公司可以立即開始從深度學習中提取數據。它利用ND4J進行大量計算,這是一種高度優化的庫,可與商用CPU配合使用,但在需要性能提升時也支持GPU。Deeplearning4j提供了一個全功能的深度學習庫,具有從採集到部署的工具,可
用於各種任務,如圖像/視頻識別,音頻處理等。

作者信息
Nisha Muktewar,數據科學家,目前就職於Cloudera的數據科學團隊,專注於專業服務、售前工作。
Seth Hendrickson,以前是電氣工程師,現在是數據科學家和軟件工程師,研究方向是分佈式機器學習。

本文由北郵@愛可可-愛生活老師推薦,阿里云云棲社區組織翻譯。

文章原標題《Deep learning on Apache Spark and Apache Hadoop with Deeplearning4j | Cloudera Engineering Blog》,作者:Nisha Muktewar、Seth Hendrickson,譯者:海棠,審閱:
原文鏈接:http://click.aliyun.com/m/26852/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章