本文適合有 Java 基礎的人群
作者:DJL-Lanking
HelloGitHub 推出的《講解開源項目》系列。有幸邀請到了亞馬遜 + Apache 的工程師:Lanking( https://github.com/lanking520 ),為我們講解 DJL —— 完全由 Java 構建的深度學習平台,本文為系列的第二篇。
一、前言
隨着數據科學在生產中的應用逐步增加,使用 N維數組 靈活的表達數據變得愈發重要。我們可以將過去數據科學運算中的多維循環嵌套運算簡化為簡單幾行。由於進一步釋放了計算並行能力,這幾行簡單的代碼運算速度也會比傳統多維循環快很多。
這種數學計算的包已經成為數據科學、圖形學以及機器學習領域的標准。同時它的影響力還在不斷的擴大到其他領域。
在 Python 的世界,調用 NDArray(N維數組)的標准包叫做 NumPy。但是如今在 Java 領域中,並沒有與之同樣標准的庫。為了給 Java 開發者創造同一種使用環境,亞馬遜雲服務開源了 DJL 一個基於 Java 的深度學習庫。
盡管它包含了深度學習模塊,但是它最核心的 NDArray 系統可以被用作 N維數組 的標准。它具備優良的可擴展性、全平台支持以及強大的后端引擎支持 (TensorFlow、PyTorch、Apache MXNet)。無論是 CPU 還是 GPU、PC 還是安卓,DJL 都可以輕而易舉的完成任務。
在這個文章中,我們將帶你了解 NDArray,並且教你如何寫與 Numpy 同樣簡單的 Java 代碼以及如何將 NDArray 使用在現實中的應用之中。
二、安裝 DJL
可以通過下方的配置來配置你的 gradle 項目。或者你也可以跳過設置直接使用我們在線 JShell 。
在線 JShell 鏈接: https://djl.ai/website/demo.html#jshell
plugins {
id 'java'
}
repositories {
jcenter()
}
dependencies {
implementation "ai.djl:api:0.6.0"
// PyTorch
runtimeOnly "ai.djl.pytorch:pytorch-engine:0.6.0"
runtimeOnly "ai.djl.pytorch:pytorch-native-auto:1.5.0"
}
然后,我們就可以開始上手寫代碼了。
三、基本操作
我們首先嘗試建立一個 try block 來包含我們的代碼(如果使用在線 JShell 可跳過此步):
try(NDManager manager = NDManager.newBaseManager()) {
}
NDManager 是 DJL 中的一個 class 可以幫助管理 NDArray 的內存使用。通過創建 NDManager 我們可以更及時的對內存進行清理。當這個 block 里的任務運行完成時,內部產生的 NDArray 都會被清理掉。這個設計保證了我們在大規模使用 NDArray 的過程中,可以通過清理其中的 NDManager 來更高效的利用內存。
為了做對比,我們可以參考 NumPy 在 Python 之中的應用。
import numpy as np
3.1 創建 NDArray
ones
是一個創建全是1的N維數組操作.
Python (Numpy)
nd = np.ones((2, 3))
"""
[[1. 1. 1.]
[1. 1. 1.]]
"""
Java (DJL NDArray)
NDArray nd = manager.ones(new Shape(2, 3));
/*
ND: (2, 3) cpu() float32
[[1., 1., 1.],
[1., 1., 1.],
]
*/
你也可以嘗試生成隨機數。比如我們需要生成一些從 0 到 1 的隨機數:
Python (Numpy)
nd = np.random.uniform(0, 1, (1, 1, 4))
# [[[0.7034806 0.85115891 0.63903668 0.39386125]]]
Java (DJL NDArray)
NDArray nd = manager.randomUniform(0, 1, new Shape(1, 1, 4));
/*
ND: (1, 1, 4) cpu() float32
[[[0.932 , 0.7686, 0.2031, 0.7468],
],
]
*/
這只是簡單演示一些常用功能。現在 NDManager 支持多達 20 種在 NumPy 中 NDArray 創建的方法。
3.2 數學運算
你可以使用 NDArray 進行一系列的數學操作。假設你想做對數據做一個轉置操作,然后對所有數據加一個數的操作。你可以參考如下的實現:
Python (Numpy)
nd = np.arange(1, 10).reshape(3, 3)
nd = nd.transpose()
nd = nd + 10
"""
[[11 14 17]
[12 15 18]
[13 16 19]]
"""
Java (DJL NDArray)
NDArray nd = manager.arange(1, 10).reshape(3, 3);
nd = nd.transpose();
nd = nd.add(10);
/*
ND: (3, 3) cpu() int32
[[11, 14, 17],
[12, 15, 18],
[13, 16, 19],
]
*/
DJL 現在支持 60 多種不同的 NumPy 數學運算,基本涵蓋了大部分的應用場景。
3.3 Get 和 Set
其中一個對於 NDArray 最重要的亮點就是它輕松簡單的數據設置/獲取功能。我們參考了 NumPy 的設計,將 Java 過去對於數據表達中的困難做了精簡化處理。
假設我們想篩選一個N維數組所有小於10的數:
Python (Numpy)
nd = np.arange(5, 14)
nd = nd[nd >= 10]
# [10 11 12 13]
Java (DJL NDArray)
NDArray nd = manager.arange(5, 14);
nd = nd.get(nd.gte(10));
/*
ND: (4) cpu() int32
[10, 11, 12, 13]
*/
是不是非常簡單?接下來,我們看一下一個稍微復雜一些的應用場景。假設我們現在有一個3x3的矩陣,然后我們想把第二列的數據都乘以2:
Python (Numpy)
nd = np.arange(1, 10).reshape(3, 3)
nd[:, 1] *= 2
"""
[[ 1 4 3]
[ 4 10 6]
[ 7 16 9]]
"""
Java (DJL NDArray)
NDArray nd = manager.arange(1, 10).reshape(3, 3);
nd.set(new NDIndex(":, 1"), array -> array.mul(2));
/*
ND: (3, 3) cpu() int32
[[ 1, 4, 3],
[ 4, 10, 6],
[ 7, 16, 9],
]
*/
在上面的案例中,我們在 Java 引入了一個 NDIndex 的 class。它復刻了大部分在 NumPy 中對於 NDArray 支持的 get/set 操作。只需要簡單的放進去一個字符串表達式,開發者在 Java 中可以輕松玩轉各種數組的操作。
四、現實中的應用場景
上述的操作對於龐大的數據集是十分有幫助的。現在我們來看一下這個應用場景:基於單詞的分類系統訓練。在這個場景中,開發者想要利用從用戶中獲取的數據來進行情感分析預測。
NDArray 被應用在了對於數據進行前后處理的工作中。
4.1 分詞操作
在輸入到 NDArray 數據前,我們需要對於輸入的字符串進行分詞操作並編碼成數字。下面代碼中看到的 tokenizer 是一個 Map<String, Integer>
,它是一個單詞到字典位置的映射。
String text = "The rabbit cross the street and kick the fox";
String[] tokens = text.toLowerCase().split(" ");
int[] vector = new int[tokens.length];
/*
String[9] { "the", "rabbit", "cross", "the", "street",
"and", "kick", "the", "fox" }
*/
for (int i = 0; i < tokens.length; i++) {
vector[i] = tokenizer.get(tokens[i]);
}
vector
/*
int[9] { 1, 6, 5, 1, 3, 2, 8, 1, 12 }
*/
4.2 NDArray 處理
經過了編碼操作后,我們創建了 NDArray 之后,我們需要轉化數據的結構:
NDArray array = manager.create(vector);
array = array.reshape(new Shape(vector.length, 1)); // form a batch
array = array.div(10.0);
/*
ND: (9, 1) cpu() float64
[[0.1],
[0.6],
[0.5],
[0.1],
[0.3],
[0.2],
[0.8],
[0.1],
[1.2],
]
*/
最后,我們將數據傳入深度學習模型中。如果使用 Java 要達到這些需要更多的工作量:如果我們需要實現類似於 reshape 的方法,我們需要創建一個N維數組:List<List<List<...List<Float>...>>>
來保證不同維度的可操作性。同時我們需要能夠支持插入新的 List<Float>
來創建最終的數據格式。
五、NDArray 的實現過程
你也許會好奇 NDArray 究竟是如何在 DJL 之中構建的呢?接下來,我們會講解一下 NDArray 在 DJL 內部中的架構。架構圖如下:
如上圖所示 NDArray 有三個關鍵的層。
界面層 (Interface) 包含了你所用到的 NDArray ,它只是一個 Java 的界面並定義了 NDArray 的輸入輸出結構。我們很仔細的分析了每一個方式的使用方法以便盡可能的將它們和用戶的應用場景統一以及便於使用。
在引擎提供者層 (EngineProvider),是 DJL 各種深度學習引擎為 NDArray 界面開發的包。這個層把原生的深度學習引擎算子表達映射在 NumPy 之上。這樣經過這樣一層轉譯,我們在不同引擎上看到 NDArray 的表現都是一致的而且同時兼顧了 NumPy 的表現。
在 C++ 層,為了更便於 Java 使用,我們構建了 JNI 和 JNA 暴露出 C/C++ 的等方法,它可以保證我們有足夠的方法來構建 NDArray 所需要的功能。同時 C++ 與 Java 的直接調用也可以保證 NDArray 擁有最好的性能。
六、為什么應該使用 NDArray 呢?
經過了這個教程,你應該獲得了基本的 NDArray 在 Java 中的使用體驗。但是這仍然只是表象,它的很多內在價值只有在生產環境中才能體現出來。總結一下 NDArray 具有如下幾個優點:
- 易如反掌:輕松使用超過 60+ 個在 Java 中的方式實現與 NumPy 相同的結果。
- 快如閃電:具備各路深度學習框架加持,DJL NDArray 具備了各種硬件平台的加速,比如在 CPU 上的 MKLDNN 加速以及 GPU 上的 CUDA 加速,無論多大的數據集都可以輕松應對。
- 深度學習:同時具備高維數組、離散數組支持。你可以輕松的將 DJL 與其他大數據或者流數據平台結合起來應用:比如分布式處理的 Apache Spark 平台以及 Apache Flink 流數據平台。為你現有的方案構建一層深度學習的中間件。
NDArray 的到來幫助 DJL 成功轉變為 Java 在深度學習領域中最好的工具。它具備平台自檢測機制,無需任何額外設置,便可以在應用中構建基於 CPU/GPU 的代碼。感興趣的小伙伴快跟着教程感受下吧!
更多詳情盡在 NDArray 文檔:https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDArray.html
關於 DJL
Deep Java Library (DJL) 是一個基於 Java 的深度學習框架,同時支持訓練以及推理。 DJL 博取眾長,構建在多個深度學習框架之上 (TenserFlow、PyTorch、MXNet 等) 也同時具備多個框架的優良特性。你可以輕松使用 DJL 來進行訓練然后部署你的模型。
它同時擁有着強大的模型庫支持:只需一行便可以輕松讀取各種預訓練的模型。現在 DJL 的模型庫同時支持高達 70 個來自 GluonCV、 HuggingFace、TorchHub 以及 Keras 的模型。
在最新的版本中 DJL 0.6.0 添加了對於 MXNet 1.7.0、PyTorch 1.5.0、TensorFlow 2.2.0 的支持。我們同時也添加了 ONNXRuntime 以及 PyTorch 在安卓平台的支持。
關注 HelloGitHub 公眾號