DJL 之 Java 玩轉多維陣列,就像 NumPy 一樣
阿新 • • 發佈:2020-09-03
> 本文適合有 Java 基礎的人群
![](https://img2020.cnblogs.com/blog/759200/202009/759200-20200902192041033-519825032.png)
作者:**DJL-Lanking**
HelloGitHub 推出的[《講解開源專案》](https://github.com/HelloGitHub-Team/Article)系列。有幸邀請到了亞馬遜 + Apache 的工程師:Lanking( https://github.com/lanking520 ),為我們講解 DJL —— 完全由 Java 構建的深度學習平臺,本文為系列的第二篇。
## 一、前言
![](https://img2020.cnblogs.com/blog/759200/202009/759200-20200902192049791-985485922.png)
隨著資料科學在生產中的應用逐步增加,使用 N維陣列 靈活的表達資料變得愈發重要。我們可以將過去資料科學運算中的多維迴圈巢狀運算簡化為簡單幾行。由於進一步釋放了計算並行能力,這幾行簡單的程式碼運算速度也會比傳統多維迴圈快很多。
這種數學計算的包已經成為資料科學、圖形學以及機器學習領域的標準。同時它的影響力還在不斷的擴大到其他領域。
在 Python 的世界,呼叫 NDArray(N維陣列)的標準包叫做 NumPy。但是如今在 Java 領域中,並沒有與之同樣標準的庫。為了給 Java 開發者創造同一種使用環境,亞馬遜雲服務開源了 DJL 一個基於 Java 的深度學習庫。
儘管它包含了深度學習模組,但是它最核心的 NDArray 系統可以被用作 N維陣列 的標準。它具備優良的可擴充套件性、全平臺支援以及強大的後端引擎支援 (TensorFlow、PyTorch、Apache MXNet)。無論是 CPU 還是 GPU、PC 還是安卓,DJL 都可以輕而易舉的完成任務。
> 專案地址:https://github.com/awslabs/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 可跳過此步):
```java
try(NDManager manager = NDManager.newBaseManager()) {
}
```
NDManager 是 DJL 中的一個 class 可以幫助管理 NDArray 的記憶體使用。通過建立 NDManager 我們可以更及時的對記憶體進行清理。當這個 block 裡的任務執行完成時,內部產生的 NDArray 都會被清理掉。這個設計保證了我們在大規模使用 NDArray 的過程中,可以通過清理其中的 NDManager 來更高效的利用記憶體。
為了做對比,我們可以參考 NumPy 在 Python 之中的應用。
```python
import numpy as np
```
### 3.1 建立 NDArray
`ones` 是一個建立全是1的N維陣列操作.
**Python (Numpy)**
```python
nd = np.ones((2, 3))
"""
[[1. 1. 1.]
[1. 1. 1.]]
"""
```
**Java (DJL NDArray)**
```java
NDArray nd = manager.ones(new Shape(2, 3));
/*
ND: (2, 3) cpu() float32
[[1., 1., 1.],
[1., 1., 1.],
]
*/
```
你也可以嘗試生成隨機數。比如我們需要生成一些從 0 到 1 的隨機數:
**Python (Numpy)**
```python
nd = np.random.uniform(0, 1, (1, 1, 4))
# [[[0.7034806 0.85115891 0.63903668 0.39386125]]]
```
**Java (DJL NDArray)**
```java
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 進行一系列的數學操作。假設你想做對資料做一個[轉置](https://baike.baidu.com/item/%E8%BD%AC%E7%BD%AE)操作,然後對所有資料加一個數的操作。你可以參考如下的實現:
**Python (Numpy)**
```python
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)**
```java
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)**
```python
nd = np.arange(5, 14)
nd = nd[nd >= 10]
# [10 11 12 13]
```
**Java (DJL NDArray)**
```java
NDArray nd = manager.arange(5, 14);
nd = nd.get(nd.gte(10));
/*
ND: (4) cpu() int32
[10, 11, 12, 13]
*/
```
是不是非常簡單?接下來,我們看一下一個稍微複雜一些的應用場景。假設我們現在有一個3x3的矩陣,然後我們想把第二列的資料都乘以2:
**Python (Numpy)**
```python
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 是一