文適合有 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 都可以輕而易舉的完成任務。
項目地址:https://github.com/awslabs/djl/
在這個文章中,我們將帶你了解 NDArray,并且教你如何寫與 Numpy 同樣簡單的 Java 代碼以及如何將 NDArray 使用在現實中的應用之中。
可以通過下方的配置來配置你的 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
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 創建的方法。
NDManager 文檔:https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDManager.html
你可以使用 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 數學運算,基本涵蓋了大部分的應用場景。
其中一個對于 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 被應用在了對于數據進行前后處理的工作中。
在輸入到 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 }
*/
經過了編碼操作后,我們創建了 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 究竟是如何在 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 在 Java 中的使用體驗。但是這仍然只是表象,它的很多內在價值只有在生產環境中才能體現出來。總結一下 NDArray 具有如下幾個優點:
NDArray 的到來幫助 DJL 成功轉變為 Java 在深度學習領域中最好的工具。它具備平臺自檢測機制,無需任何額外設置,便可以在應用中構建基于 CPU/GPU 的代碼。感興趣的小伙伴快跟著教程感受下吧!
更多詳情盡在 NDArray 文檔:https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDArray.html
Deep Java Library (DJL) 是一個基于 Java 的深度學習框架,同時支持訓練以及推理。DJL 博取眾長,構建在多個深度學習框架之上 (TenserFlow、PyTorch、MXNet 等) 也同時具備多個框架的優良特性。你可以輕松使用 DJL 來進行訓練然后部署你的模型。
它同時擁有著強大的模型庫支持:只需一行便可以輕松讀取各種預訓練的模型。現在 DJL 的模型庫同時支持高達 70 個來自 GluonCV、 HuggingFace、TorchHub 以及 Keras 的模型。
項目地址:https://github.com/awslabs/djl/
在最新的版本中 DJL 0.6.0 添加了對于 MXNet 1.7.0、PyTorch 1.5.0、TensorFlow 2.2.0 的支持。我們同時也添加了 ONNXRuntime 以及 PyTorch 在安卓平臺的支持。
源:升學就業幫講師——肖云銳
1. 數組的聲明和賦值
方式一: new Array()構造函數方法
// 1. 使用構造函數創建數組對象
// 創建了一個空數組var arr=new Array();
// 創建了一個數組,里面存放了3個字符串
var arr=new Array('zs', 'ls', 'ww');
// 創建了一個數組,里面存放了4個數字
var arr=new Array(1, 2, 3, 4);
方式二: 字面量方式
// 2. 使用字面量創建數組對象
var arr=[1, 2, 3];
// 獲取數組中元素的個數
console.log(arr.length);
注意事項:
1. 定義空數組的方式
var arr1=[];
2. 定義一個數組可以存入不同的數據類型. 但是一般不建議這樣使用.
var arr3=[25,true,'abc'];
3. 訪問數組的元素通過索引,索引從開始
var arr6 = [];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
console.log(arr6);
4. js中數組的下標是可以不連續的,如果不連續默認補充empty
var arr6 = [];
arr6[0] = 10;
arr6[1] = 20;
arr6[2] = 30;
console.log(arr6);
arr6[4] = 50;
console.log(arr6)
執行結果如下圖所示:
5. 數組的擴容和縮容
var arr = [1, 1.2, new Date(), false, "呵呵"];
console.log("前:" + arr);
// 數組的擴容arr.length = 10;
// 數組的縮小
//arr.length = 3;
console.log("后:" + arr);
6. 清空數組
// 方式1 推薦
arr=[];
// 方式2
arr.length=0;
// 方式3
arr.splice(0, arr.length);
2. 數組的遍歷
方式一:for循環,也是最常見的
for (let i=0; i < arr.length; i++) {
console.log(arr[i])
}
方式二:for......in 遍歷數組
for(let item in arr){
console.log(arr[item])
}
方式三: foreach遍歷數組
arr.forEach(function(item, index){
console.log(item + "=" + index);
});
3. 數組的常用方法
數組常用的一些方法:
首尾數據操作
push() //在數組末尾添加一個或多個元素,并返回數組操作后的長度
pop() //刪除數組最后一項,返回刪除項
shift() //刪除數組第一項,返回刪除項
unshift() //在數組開頭添加一個或多個元素,并返回數組的新長度
合并和拆分
concat()
// 將兩個數組合并成一個新的數組,原數組不受影響。
// 參數位置可以是一個數組字面量、數組變量、零散的值。
slice(start,end)
// 從當前數組中截取一個新的數組,不影響原來的數組,返回一個新的數組,
// 包含從 start 到 end (不包括該元素)的元素。
// 參數區分正負,正值表示下標位置,負值表示從后面往前數第幾個位置,
// 參數可以只傳遞一個,表示從開始位置截取到字符串結尾。
刪除、插入、替換
splice(index,howmany,element1,element2,……)
//用于插入、刪除或替換數組的元素
//index:刪除元素的開始位置
//howmany:刪除元素的個數,可以是0
//element1,element2:要替換的新的數據。
位置方法
indexOf() //查找數據在數組中最先出現的下標
lastIndexOf() //查找數據在數組中最后一次出現的下標
//如果沒找到返回-1
排序和倒序
reverse() //將數組完全顛倒,第一項變成最后一項,最后一項變成第一項。
sort(); //默認根據字符編碼順序,從小到大排序
//如果想要根據數值大小進行排序,必須添加sort的比較函數參數。
//該函數要比較兩個值,然后返回一個用于說明這兩個值的相對順序的數字。比較函數應該具有兩個參數 a 和 b,根據a和b的關系作為判斷條件,返回值根據條件分為三個分支,正數、負數、0:
//返回值是負數-1:a排在b前面。
//返回值是整數1:a排在b后面。
//返回值是0:a和b的順序保持不變。
//人為能控制的是判斷條件。
轉字符串方法
// 將數組的所有元素連接到一個字符串中。
join() //通過參數作為連字符將數組中的每一項用連字符連成一個完整的字符串
迭代方法
//不會修改原數組(可選) HTML5新增
every()、filter()、forEach()、map()、some()
代碼示例
近給京東2022秋招做了一道算法題。 問題需要輸出的數據格式是二維數組。 但是我的回答在ac時有問題,二維數組的每個子數組中的值都是一樣的。 當時一直卡在尋找二層for循環的bug,但是忽略了問題出在我定義二維數組的方式上,所以這里想講一下如何定義一個真正的二- 維數組。
我們先看一個例子,網上最常用的定義二維數組的方法
const n=3
let arr=new Array(n). fill( new Array(n). fill( 0));
console. log(arr)
copy code
看起來是對的,數組里面嵌套了三個數組,和我們想象的二維數組一樣,但是問題就在這里。
看起來對嗎? 我們來做一個操作給二維數組中的元素賦值
arr[ 0][ 1]=1;
console. log( "after assignment", arr);
copy code
我勒個去!什么情況下我只給二維數組的第0行第1列賦值1。為什么每個的第一列都變成1?
相信很多人第一次遇到這種情況時,都會有和我一樣的疑惑。問題出在哪里?
問題在于 Array.fill()
不熟悉的朋友可以看看MDN上的簡單解釋
Array.prototype.fill()
我希望你會注意到這句話:“當一個對象被傳遞給 fill 方法時,數組被填充了對該對象的引用。 "
注意:如果填充的內容是'object'類型,那么對象分配了相同的內存地址,而不是深拷貝對象。
這就解釋了上面的問題。我們根據它的內存地址找到了對象并修改了對象本身。這樣,其他子數組也發生了變化,因為這些子數組都取自同一個內存地址。因此,上述方法定義的二維數組不能稱為真正的二維數組(我這里說的對象其實就是js中的引用類型數據,array、object、map、Set,這些都是。 )
如何定義一個真正的二維數組?
其實很簡單,兩個for循環就可以完成
let arr1=new Array(n);
for ( let i=0; i < n; i++) {
for ( let j=0; j < n; j++) {
arr1[i]=new Array(n). fill( 0);
}
}
copy code
讓我們給它賦值,看看之前和之后的結果
console. log( "before assignment", arr1);
arr1[ 0][ 1]=1;
console. log( "after assignment", arr1);
copy code
沒錯,我們定義了一個真正的二維數組
希望大家以后在遇到二維數組問題的時候放棄第一種寫法。 我們需要的是一個真正的二維數組。
關注七爪網,獲取更多APP/小程序/網站源碼資源!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。