DJL 如何正確打開 [ 深度學習 ]
本文轉載自微信公眾號「小明菜市場」,作者小明菜市場。轉載本文請聯(lián)系小明菜市場公眾號。
前言
很長時間,Java都是一個相當受歡迎的企業(yè)編程語言,其框架豐富,生態(tài)完善。Java擁有龐大的開發(fā)者社區(qū),盡管深度學習應用不斷推進和演化,但是相關的深度學習框架對于Java來說相當的稀少,現如今,主要模型都是Python編譯和訓練,對于Java開發(fā)者來說,如果想要學習深度學習,就需要接受一門新的語言的洗禮。為了減少Java開發(fā)者學習深度學習的成本,AWS構建了一個Deep Java Library(DJL),一個為Java開發(fā)者定制的開源深度學習框架,其為開發(fā)者對接主流深度學習框架,提供了一個接口。
什么是深度學習
在開始之前,先了解機器學習和深度學習基礎概念。機器學習是一個利用統(tǒng)計學知識,把數據輸入到計算機中進行訓練并完成特定目標任務的過程,這種歸納學習方法可以讓計算機學習一些特征并進行一系列復雜的任務,比如識別照片中的物體。深度學習是機器學習的一個分支,主要側重于對于人工神經網絡的開發(fā),人工神經網絡是通過研究人腦如何學習和實現目標的過程中,歸納出的一套計算邏輯。通過模擬部分人腦神經間信息傳遞的過程,從而實現各種復雜的任務,深度學習中的深度來源于會在人工神經網絡中編制出,構建出許多層,從而進一步對數據信息進行更為深層次的傳導。
訓練 MNIST 手寫數字識別
項目配置
利用 gradle 配置引入依賴包,用DJL的api包和basicdataset包來構建神經網絡和數據集,這個案例,使用 MXNet作為深度學習引擎,所以引入mxnet-engine和mxnet-native-auto兩個包,依賴如下
- plugins {
- id 'java'
- }
- repositories {
- jcenter()
- }
- dependencies {
- implementation platform("ai.djl:bom:0.8.0")
- implementation "ai.djl:api"
- implementation "ai.djl:basicdataset"
- // MXNet
- runtimeOnly "ai.djl.mxnet:mxnet-engine"
- runtimeOnly "ai.djl.mxnet:mxnet-native-auto"
- }
NDArry 和 NDManager
NDArray 是 DJL 存儲數據結構和數學運算的基本結構,一個NDArry表達了一個定長的多維數組,NDArry的使用方法,類似于Python的numpy.ndarry。NDManager是NDArry的管理者,其負責管理NDArry的產生和回收過程,這樣可以幫助我們更好的對Java內存進行優(yōu)化,每一個NDArry都會由一個NDManager創(chuàng)造出來,同時他們會在NDManager關閉時一同關閉,
Model
在 DJL 中,訓練和推理都是從 Model class 開始構建的,我們在這里主要訓練過程中的構建方法,下面我們?yōu)?Model 創(chuàng)建一個新的目標,因為 Model 也是繼承了 AutoClosable 結構體,用一個 try block實現。
- try (Model model = Model.newInstance()) {
- ...
- // 主體訓練代碼
- ...
- }
準備數據
MNIST 數據庫包含大量的手寫數字的圖,通常用來訓練圖像處理系統(tǒng),DJL已經把MNIST的數據收集到了 basicdataset 數據里,每個 MNIST 的圖的大小是 28 * 28, 如果有自己的數據集,同樣可以使用同理來收集數據。
數據集導入教程 http://docs.djl.ai/docs/development/how_to_use_dataset.html#how-to-create-your-own-dataset
- int batchSize = 32; // 批大小
- Mnist trainingDataset = Mnist.builder()
- .optUsage(Usage.TRAIN) // 訓練集
- .setSampling(batchSize, true)
- .build();
- Mnist validationDataset = Mnist.builder()
- .optUsage(Usage.TEST) // 驗證集
- .setSampling(batchSize, true)
- .build();
這段代碼分別制作了訓練和驗證集,同時我們也隨機的排列了數據集從而更好的訓練,除了這些配置以外,也可以對圖片進行進一步的設置,例如設置圖片大小,歸一化處理。
制作 model 建立 block
當數據集準備就緒以后,就可以構建神經網絡,在DJL 中,神經網絡是由 Block 代碼塊構成的,一個Block是一個具備多種神經網絡特性的結構,他們可以代表一個操作神經網絡的一部分,甚至一個完整的神經網絡,然后 block 就可以順序的執(zhí)行或者并行。同時 block 本身也可以帶參數和子block,這種嵌套結構可以快速的幫助更新一個可維護的神經網絡,在訓練過程中,每個block附帶參數也會實時更新,同時也會更新其子 block。當我們構建這些 block 的過程中,最簡單的方式就是把他們一個一個嵌套起來,直接使用準備好的 DJL的 Block 種類,我們就可以快速制作各種神經網絡。
block 變體
根據幾種基本的神經網絡工作模式,我們提供幾種Block的變體,
- SequentialBlock 是為了輸出作為下一個block的輸入繼續(xù)執(zhí)行到底。
- parallelblock 是用于將一個輸入并行輸入到每一個子block中,同時也將輸出結果根據特定的合并方程合并起來。
- lambdablock 是幫助用戶進行快速操作的一個block,其中不具備任何參數,所以在訓練的過程中沒有任何部分在訓練過程中更新。
構建多層感知機 MLP 神經網絡
我們構建一個簡單的多層感知機神經網絡,多層感知機是一個簡單的前向型神經網絡,只包含幾個全連接層,構建這個網路可以直接使用 sequentialblock
- int input = 28 * 28; // 輸入層大小
- int output = 10; // 輸出層大小
- int[] hidden = new int[] {128, 64}; // 隱藏層大小
- SequentialBlock sequentialBlock = new SequentialBlock();
- sequentialBlock.add(Blocks.batchFlattenBlock(input));
- for (int hiddenSize : hidden) {
- // 全連接層
- sequentialBlock.add(Linear.builder().setUnits(hiddenSize).build());
- // 激活函數
- sequentialBlock.add(activation);
- }
- sequentialBlock.add(Linear.builder().setUnits(output).build());
可以使用直接提供好的 MLP Block
- Block block = new Mlp(
- Mnist.IMAGE_HEIGHT * Mnist.IMAGE_WIDTH,
- Mnist.NUM_CLASSES,
- new int[] {128, 64});
訓練
使用如下幾個步驟,
完成一個訓練過程初始化:我們會對每一個Block的參數進行初始化,初始化每個參數的函數都是由設定的 initializer決定的。前向傳播:這一步把輸入數據在神經網絡中逐層傳遞,然后產生輸出數據。計算損失:我們會根據特定的損失函數 loss 來計算輸出和標記結果的偏差。反向傳播:在這一步中,利用損失反向求導計算出每一個參數的梯度。更新權重,會根據選擇的優(yōu)化器,更新每一個在 Block 上的參數的值。
精簡
DJL 利用了 Trainer 結構體精簡了整個過程,開發(fā)者只需要創(chuàng)建Trainer 并指定對應的initializer,loss,optimizer即可,這些參數都是由TrainingConfig設定,來看參數的設置。TrainingListener 訓練過程設定的監(jiān)聽器,可以實時反饋每個階段的訓練結果,這些結果可以用于記錄訓練過程或者幫助 debug 神經網絡訓練過程中遇到的問題。用戶可以定制自己的 TrainingListener 來訓練過程進行監(jiān)聽
- DefaultTrainingConfig config = new DefaultTrainingConfig(Loss.softmaxCrossEntropyLoss())
- .addEvaluator(new Accuracy())
- .addTrainingListeners(TrainingListener.Defaults.logging());
- try (Trainer trainer = model.newTrainer(config)){
- // 訓練代碼
- }
訓練產生以后,可以定義輸入的 Shape,之后可以調用 git函數進行訓練,結果會保存在本地目錄下
- /*
- * MNIST 包含 28x28 灰度圖片并導入成 28 * 28 NDArray。
- * 第一個維度是批大小, 在這里我們設置批大小為 1 用于初始化。
- */
- Shape inputShape = new Shape(1, Mnist.IMAGE_HEIGHT * Mnist.IMAGE_WIDTH);
- int numEpoch = 5;
- String outputDir = "/build/model";
- // 用輸入初始化 trainer
- trainer.initialize(inputShape);
- TrainingUtils.fit(trainer, numEpoch, trainingSet, validateSet, outputDir, "mlp");
輸出的結果圖
- [INFO ] - Downloading libmxnet.dylib ...
- [INFO ] - Training on: cpu().
- [INFO ] - Load MXNet Engine Version 1.7.0 in 0.131 ms.
- Training: 100% |████████████████████████████████████████| Accuracy: 0.93, SoftmaxCrossEntropyLoss: 0.24, speed: 1235.20 items/sec
- Validating: 100% |████████████████████████████████████████|
- [INFO ] - Epoch 1 finished.
- [INFO ] - Train: Accuracy: 0.93, SoftmaxCrossEntropyLoss: 0.24
- [INFO ] - Validate: Accuracy: 0.95, SoftmaxCrossEntropyLoss: 0.14
- Training: 100% |████████████████████████████████████████| Accuracy: 0.97, SoftmaxCrossEntropyLoss: 0.10, speed: 2851.06 items/sec
- Validating: 100% |████████████████████████████████████████|
- [INFO ] - Epoch 2 finished.NG [1m 41s]
- [INFO ] - Train: Accuracy: 0.97, SoftmaxCrossEntropyLoss: 0.10
- [INFO ] - Validate: Accuracy: 0.97, SoftmaxCrossEntropyLoss: 0.09
- [INFO ] - train P50: 12.756 ms, P90: 21.044 ms
- [INFO ] - forward P50: 0.375 ms, P90: 0.607 ms
- [INFO ] - training-metrics P50: 0.021 ms, P90: 0.034 ms
- [INFO ] - backward P50: 0.608 ms, P90: 0.973 ms
- [INFO ] - step P50: 0.543 ms, P90: 0.869 ms
- [INFO ] - epoch P50: 35.989 s, P90: 35.989 s
訓練結束以后,就可以對模型進行識別了和使用了。
關于作者
我是小小,一個生于二線城市活在一線城市的小小,本期結束,我們下期再見。