大神帶你分分鍾超越最好結果——基於分布式CPU計算的Deeplearning4j遷移學習應用實例
首發地址:https://yq.aliyun.com/articles/114669
2016“2017AI”2017GPUCPUGPUCPUCPUGPU“”GPUDeeplearning4jHadoop
——Apache SparkApache HadoopDeeplearning4jCommodity Hardware
Deeplearning4j
Deeplearning4j2014DL4JSparkGPUSkymindJavadeeplearning4jJVMNd4jCPUGPUJava
Caltech-256
本文介紹如何使用Apache Spark、Apache Hadoop和deeplearning4j來解決圖像分類問題。簡單來說,就是通過構建一個卷積神經網絡來對Caltech-256Caltech-256數據集中,實際上有257個對象類別,每類數量大概是80到800個圖像,該數據集總共30,607個圖像。值得注意的是,72 - 75下麵我將帶領大家DL4J輕鬆超越這個結果。
目前,卷積網絡可以有幾億個參數,比如在大型視覺識別挑戰 “ImageNet”中表現最佳的神經網絡之一,有1.4億個參數需要訓練!這些網絡不僅需要大量的計算和存儲資源(即使是使用一組GPU,也可能需要幾周時間才能完成計算),而且還需要大量數據。而Caltech-25630000多張圖像,在這個數據集上訓練這樣一個複雜的模型是不現實的,因為沒有足夠的樣本來充分學習這麼多參數。相反,可以采用一種的方法來實現。簡單來說,就是將已學到的知識應用到其它領域,使其能夠更好地完成新領域的學習。這是因為卷積神經網絡在對圖像數據集進行訓練時往往會學習,因此這種類型的特征學習通常對其他圖像數據集也是通用的。例如,在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
==================================================================================================
上麵代碼顯示VGG16ConvolutionLayer
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”,如下圖所示:垂線左邊部分。
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)。
VGG16
VGG16是在ImageNet數據集上進行訓練的,而ImageNet數據集具有1000種不同對象類別。在典型的圖像分類神經網絡中,輸出層的最後一層使用其輸入來為數據集中的每個對象生成概率(哪一類的概率大就判斷為哪一類)。因此,該輸入可以被認為是關於圖像的抽象視覺特征,提供關於其包含的對象的有用信息。直觀地說,上述步驟生成的新數據集於Caltech-256數據集中識別對象應該是有用的。因此,定義一個新的模型“f2”,將維度從原先的1000,正好對應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)
直觀圖如下,可以看到隻是改變了預測層的維度:
該模型現在已準備好使用DL4J進行大量計算,而且還使用Spark進行規模化。簡單來說是切分大規模的數據集,然後將分片交給spark使用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的負對數似然率,分數越小,效果越好。
這次將學習率調低後,該模型似乎能比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進行了大量訪問,而且在Sparkdeeplearning4j的定位是花費更少的時間部署和減少開銷,從而企業公司可以立即開始從深度學習中提取數據。它利用ND4J進行大量計算,這是一種高度優化的庫,可與商用CPU配合使用,但在需要性能提升時也支持GPUDeeplearning4j提供了一個全功能的深度學習庫,具有從采集到部署的工具,可
用於各種任務,如圖像/視頻識別,音頻處理等。
Nisha MuktewarCloudera
Seth Hendrickson,以前是電氣工程師,現在是數據科學家和軟件工程師,研究方向是分布式機器學習。
Deep learning on Apache Spark and Apache Hadoop with Deeplearning4j | Cloudera Engineering BlogSeth Hendrickson
最後更新:2017-07-27 09:32:42