DeepLearning4j實戰(7):手寫體數字識別GPU實現與效能比較
在之前的部落格中已經用單機、Spark分散式兩種訓練的方式對深度神經網路進行訓練,但其實DeepLearning4j也是支援多GPU訓練的。這篇文章我就總結下用GPU來對DNN/CNN進行訓練和評估過程。並且我會給出CPU、GPU和多卡GPU之前的效能比較圖表。不過,由於重點在於說明Mnist資料集在GPU上訓練的過程,所以對於一些環境的部署,比如Java環境和CUDA的安裝就不再詳細說明了。
軟體環境的部署主要在於兩個方面,一個是JDK的安裝,另外一個是CUDA。目前最新版本的DeepLearning4j以及Nd4j支援CUDA-8.0,JDK的話1.7以上。
環境部署完後,分別用java -version和nvidia-smi來確認環境是否部署正確,如果出現類似以下的資訊,則說明環境部署正確,否則需要重新安裝。
GPU配置:
Java環境截圖:
從系統返回的資訊可以看到,jdk是openJDK1.7,GPU是2張P40的卡。
下面說明下程式碼的構成:
由於我這裡用了DeepLearning4j最新的版本--v0.8,所以和之前部落格的pom檔案有些修改,具體如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>DeepLearning</groupId> <artifactId>DeepLearning</artifactId> <version>2.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <nd4j.version>0.8.0</nd4j.version> <dl4j.version>0.8.0</dl4j.version> <datavec.version>0.8.0</datavec.version> <scala.binary.version>2.11</scala.binary.version> </properties> <dependencies> <dependency> <groupId>org.nd4j</groupId> <artifactId>nd4j-native</artifactId> <version>${nd4j.version}</version> </dependency> <dependency> <groupId>org.deeplearning4j</groupId> <artifactId>deeplearning4j-core</artifactId> <version>${dl4j.version}</version> </dependency> <dependency> <groupId>org.nd4j</groupId> <artifactId>nd4j-cuda-8.0</artifactId> <version>${nd4j.version}</version> </dependency> <dependency> <groupId>org.deeplearning4j</groupId> <artifactId>deeplearning4j-parallel-wrapper_${scala.binary.version}</artifactId> <version>${dl4j.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <source>1.7</source> <target>1.7</target> <archive> <manifest> <mainClass>cn.live.wangongxi.cv.CNNMnist</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>
建立完Maven工程以及添加了上面POM檔案的內容之後,就可以開始著手上層應用邏輯的構建。這裡我參考了官網的例子,具體由以下幾個部分構成:
1.初始化CUDA的環境(底層邏輯包括硬體檢測、CUDA版本校驗和一些GPU引數)
2.讀取Mnist二進位制檔案(和之前的部落格內容一致)
3.CNN的定義,這裡我還是用的LeNet
4.訓練以及評估模型的指標
首先貼一下第一部分的程式碼:
通常我們需要根據需要來設定GPU計算的精度,常用的就像程式碼中寫的那樣有單、雙、半精度三種。通過選擇DataBuffer中定義的enum型別Type中的值來達到設定精度的目的。如果不設定,預設的是單精度。//精度設定,常用精度有單、雙、半精度 //HALF : 半精度 DataTypeUtil.setDTypeForContext(DataBuffer.Type.HALF); //FLOAT : 單精度 //DataTypeUtil.setDTypeForContext(DataBuffer.Type.FLOAT); //DOUBLE : 雙精度 //DataTypeUtil.setDTypeForContext(DataBuffer.Type.DOUBLE); //建立CUDA上下文例項並設定引數 CudaEnvironment.getInstance().getConfiguration() //是否允許多GPU .allowMultiGPU(false) //設定視訊記憶體中快取資料的容量,單位:位元組 .setMaximumDeviceCache(2L * 1024L * 1024L * 1024L) //是否允許多GPU間點對點(P2P)的記憶體訪問 .allowCrossDeviceAccess(false);
再下面就是設定CUDA的一些上下文引數,比如程式碼中羅列的cache資料的視訊記憶體大小,P2P訪問記憶體和多GPU執行的標誌位等等。對於網路結構相對簡單,資料量不大的情況下,預設的引數就夠用了。這裡我們也只是簡單設定了幾個引數,這對於用LeNet來訓練Mnist資料集來說已經足夠了。
從2~4部分的邏輯和之前的部落格裡幾乎是一樣的,就直接上程式碼了:
int nChannels = 1;
int outputNum = 10;
int batchSize = 128;
int nEpochs = 10;
int iterations = 1;
int seed = 123;
log.info("Load data....");
DataSetIterator mnistTrain = new MnistDataSetIterator(batchSize,true,12345);
DataSetIterator mnistTest = new MnistDataSetIterator(batchSize,false,12345);
log.info("Build model....");
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(seed)
.iterations(iterations)
.regularization(true).l2(0.0005)
.learningRate(.01)
.weightInit(WeightInit.XAVIER)
.optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
.updater(Updater.NESTEROVS).momentum(0.9)
.list()
.layer(0, new ConvolutionLayer.Builder(5, 5)
.nIn(nChannels)
.stride(1, 1)
.nOut(20)
.activation(Activation.IDENTITY)
.build())
.layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
.kernelSize(2,2)
.stride(2,2)
.build())
.layer(2, new ConvolutionLayer.Builder(5, 5)
.stride(1, 1)
.nOut(50)
.activation(Activation.IDENTITY)
.build())
.layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)
.kernelSize(2,2)
.stride(2,2)
.build())
.layer(4, new DenseLayer.Builder().activation(Activation.RELU)
.nOut(500).build())
.layer(5, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.nOut(outputNum)
.activation(Activation.SOFTMAX)
.build())
.setInputType(InputType.convolutionalFlat(28,28,1))
.backprop(true).pretrain(false).build();
MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();
log.info("Train model....");
model.setListeners(new ScoreIterationListener(100));
long timeX = System.currentTimeMillis();
for( int i=0; i<nEpochs; i++ ) {
long time1 = System.currentTimeMillis();
model.fit(mnistTrain);
long time2 = System.currentTimeMillis();
log.info("*** Completed epoch {}, time: {} ***", i, (time2 - time1));
}
long timeY = System.currentTimeMillis();
log.info("*** Training complete, time: {} ***", (timeY - timeX));
log.info("Evaluate model....");
Evaluation eval = new Evaluation(outputNum);
while(mnistTest.hasNext()){
DataSet ds = mnistTest.next();
INDArray output = model.output(ds.getFeatureMatrix(), false);
eval.eval(ds.getLabels(), output);
}
log.info(eval.stats());
log.info("****************Example finished********************");
以上邏輯就是利用一塊GPU卡進行Mnist資料集進行訓練和評估的邏輯。如果想在多GPU下進行並行訓練的話,需要修改一些設定,例如在之前第一步的建立CUDA環境上下文的時候,需要允許多GPU和P2P記憶體訪問,即設定為true。然後在邏輯裡新增並行訓練的邏輯:
ParallelWrapper wrapper = new ParallelWrapper.Builder(model)
.prefetchBuffer(24)
.workers(4)
.averagingFrequency(3)
.reportScoreAfterAveraging(true)
.useLegacyAveraging(true)
.build();
這樣如果有多張GPU卡就可以進行單機多卡的並行訓練。
下面貼一下訓練Mnist資料集在CPU/GPU/多GPU下的效能比較還有訓練時候的GPU使用情況:
單卡訓練截圖:
雙卡並行訓練截圖:
訓練時間評估:
最後做下簡單的總結。由於Deeplearning4j本身支援GPU單卡,多卡以及叢集的訓練方式,而且對於底層的介面都已經進行了很多的封裝,暴露的介面都是比較hig-level的介面,一般設定一些屬性就可以了。當然前提是硬體包括CUDA都要正確安裝。