Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文適合有 Java 基礎(chǔ)的人群
作者:DJL-Lanking
HelloGitHub 推出的《講解開源項(xiàng)目》系列。有幸邀請(qǐng)到了亞馬遜 + Apache 的工程師:Lanking( https://github.com/lanking520 ),為我們講解 DJL —— 完全由 Java 構(gòu)建的深度學(xué)習(xí)平臺(tái),本文為本系列的第二篇。
隨著數(shù)據(jù)科學(xué)在生產(chǎn)中的應(yīng)用逐步增加,使用 N維數(shù)組 靈活的表達(dá)數(shù)據(jù)變得愈發(fā)重要。我們可以將過去數(shù)據(jù)科學(xué)運(yùn)算中的多維循環(huán)嵌套運(yùn)算簡(jiǎn)化為簡(jiǎn)單幾行。由于進(jìn)一步釋放了計(jì)算并行能力,這幾行簡(jiǎn)單的代碼運(yùn)算速度也會(huì)比傳統(tǒng)多維循環(huán)快很多。
這種數(shù)學(xué)計(jì)算的包已經(jīng)成為數(shù)據(jù)科學(xué)、圖形學(xué)以及機(jī)器學(xué)習(xí)領(lǐng)域的標(biāo)準(zhǔn)。同時(shí)它的影響力還在不斷的擴(kuò)大到其他領(lǐng)域。
在 Python 的世界,調(diào)用 NDArray(N維數(shù)組)的標(biāo)準(zhǔn)包叫做 NumPy。但是如今在 Java 領(lǐng)域中,并沒有與之同樣標(biāo)準(zhǔn)的庫。為了給 Java 開發(fā)者創(chuàng)造同一種使用環(huán)境,亞馬遜云服務(wù)開源了 DJL 一個(gè)基于 Java 的深度學(xué)習(xí)庫。
盡管它包含了深度學(xué)習(xí)模塊,但是它最核心的 NDArray 系統(tǒng)可以被用作 N維數(shù)組 的標(biāo)準(zhǔn)。它具備優(yōu)良的可擴(kuò)展性、全平臺(tái)支持以及強(qiáng)大的后端引擎支持 (TensorFlow、PyTorch、Apache MXNet)。無論是 CPU 還是 GPU、PC 還是安卓,DJL 都可以輕而易舉的完成任務(wù)。
項(xiàng)目地址:https://github.com/awslabs/djl/
在這個(gè)文章中,我們將帶你了解 NDArray,并且教你如何寫與 Numpy 同樣簡(jiǎn)單的 Java 代碼以及如何將 NDArray 使用在現(xiàn)實(shí)中的應(yīng)用之中。
可以通過下方的配置來配置你的 gradle 項(xiàng)目。或者你也可以跳過設(shè)置直接使用我們?cè)诰€ 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"
}
然后,我們就可以開始上手寫代碼了。
我們首先嘗試建立一個(gè) try block 來包含我們的代碼(如果使用在線 JShell 可跳過此步):
try(NDManager manager = NDManager.newBaseManager()) {
}
NDManager 是 DJL 中的一個(gè) class 可以幫助管理 NDArray 的內(nèi)存使用。通過創(chuàng)建 NDManager 我們可以更及時(shí)的對(duì)內(nèi)存進(jìn)行清理。當(dāng)這個(gè) block 里的任務(wù)運(yùn)行完成時(shí),內(nèi)部產(chǎn)生的 NDArray 都會(huì)被清理掉。這個(gè)設(shè)計(jì)保證了我們?cè)诖笠?guī)模使用 NDArray 的過程中,可以通過清理其中的 NDManager 來更高效的利用內(nèi)存。
為了做對(duì)比,我們可以參考 NumPy 在 Python 之中的應(yīng)用。
import numpy as np
ones 是一個(gè)創(chuàng)建全是1的N維數(shù)組操作.
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.],
]
*/
你也可以嘗試生成隨機(jī)數(shù)。比如我們需要生成一些從 0 到 1 的隨機(jī)數(shù):
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],
],
]
*/
這只是簡(jiǎn)單演示一些常用功能。現(xiàn)在 NDManager 支持多達(dá) 20 種在 NumPy 中 NDArray 創(chuàng)建的方法。
NDManager 文檔:https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDManager.html
你可以使用 NDArray 進(jìn)行一系列的數(shù)學(xué)操作。假設(shè)你想做對(duì)數(shù)據(jù)做一個(gè)轉(zhuǎn)置操作,然后對(duì)所有數(shù)據(jù)加一個(gè)數(shù)的操作。你可以參考如下的實(shí)現(xiàn):
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 現(xiàn)在支持 60 多種不同的 NumPy 數(shù)學(xué)運(yùn)算,基本涵蓋了大部分的應(yīng)用場(chǎng)景。
其中一個(gè)對(duì)于 NDArray 最重要的亮點(diǎn)就是它輕松簡(jiǎn)單的數(shù)據(jù)設(shè)置/獲取功能。我們參考了 NumPy 的設(shè)計(jì),將 Java 過去對(duì)于數(shù)據(jù)表達(dá)中的困難做了精簡(jiǎn)化處理。
假設(shè)我們想篩選一個(gè)N維數(shù)組所有小于10的數(shù):
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]
*/
是不是非常簡(jiǎn)單?接下來,我們看一下一個(gè)稍微復(fù)雜一些的應(yīng)用場(chǎng)景。假設(shè)我們現(xiàn)在有一個(gè)3x3的矩陣,然后我們想把第二列的數(shù)據(jù)都乘以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],
]
*/
在上面的案例中,我們?cè)?Java 引入了一個(gè) NDIndex 的 class。它復(fù)刻了大部分在 NumPy 中對(duì)于 NDArray 支持的 get/set 操作。只需要簡(jiǎn)單的放進(jìn)去一個(gè)字符串表達(dá)式,開發(fā)者在 Java 中可以輕松玩轉(zhuǎn)各種數(shù)組的操作。
上述的操作對(duì)于龐大的數(shù)據(jù)集是十分有幫助的。現(xiàn)在我們來看一下這個(gè)應(yīng)用場(chǎng)景:基于單詞的分類系統(tǒng)訓(xùn)練。在這個(gè)場(chǎng)景中,開發(fā)者想要利用從用戶中獲取的數(shù)據(jù)來進(jìn)行情感分析預(yù)測(cè)。
NDArray 被應(yīng)用在了對(duì)于數(shù)據(jù)進(jìn)行前后處理的工作中。
在輸入到 NDArray 數(shù)據(jù)前,我們需要對(duì)于輸入的字符串進(jìn)行分詞操作并編碼成數(shù)字。下面代碼中看到的 tokenizer 是一個(gè) Map<String, Integer>,它是一個(gè)單詞到字典位置的映射。
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 }
*/
經(jīng)過了編碼操作后,我們創(chuàng)建了 NDArray 之后,我們需要轉(zhuǎn)化數(shù)據(jù)的結(jié)構(gòu):
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],
]
*/
最后,我們將數(shù)據(jù)傳入深度學(xué)習(xí)模型中。如果使用 Java 要達(dá)到這些需要更多的工作量:如果我們需要實(shí)現(xiàn)類似于 reshape 的方法,我們需要?jiǎng)?chuàng)建一個(gè)N維數(shù)組:List<List<List<...List<Float>...>>> 來保證不同維度的可操作性。同時(shí)我們需要能夠支持插入新的 List<Float> 來創(chuàng)建最終的數(shù)據(jù)格式。
你也許會(huì)好奇 NDArray 究竟是如何在 DJL 之中構(gòu)建的呢?接下來,我們會(huì)講解一下 NDArray 在 DJL 內(nèi)部中的架構(gòu)。架構(gòu)圖如下:
如上圖所示 NDArray 有三個(gè)關(guān)鍵的層。
界面層 (Interface) 包含了你所用到的 NDArray ,它只是一個(gè) Java 的界面并定義了 NDArray 的輸入輸出結(jié)構(gòu)。我們很仔細(xì)的分析了每一個(gè)方式的使用方法以便盡可能的將它們和用戶的應(yīng)用場(chǎng)景統(tǒng)一以及便于使用。
在引擎提供者層 (EngineProvider),是 DJL 各種深度學(xué)習(xí)引擎為 NDArray 界面開發(fā)的包。這個(gè)層把原生的深度學(xué)習(xí)引擎算子表達(dá)映射在 NumPy 之上。這樣經(jīng)過這樣一層轉(zhuǎn)譯,我們?cè)诓煌嫔峡吹?NDArray 的表現(xiàn)都是一致的而且同時(shí)兼顧了 NumPy 的表現(xiàn)。
在 C++ 層,為了更便于 Java 使用,我們構(gòu)建了 JNI 和 JNA 暴露出 C/C++ 的等方法,它可以保證我們有足夠的方法來構(gòu)建 NDArray 所需要的功能。同時(shí) C++ 與 Java 的直接調(diào)用也可以保證 NDArray 擁有最好的性能。
經(jīng)過了這個(gè)教程,你應(yīng)該獲得了基本的 NDArray 在 Java 中的使用體驗(yàn)。但是這仍然只是表象,它的很多內(nèi)在價(jià)值只有在生產(chǎn)環(huán)境中才能體現(xiàn)出來。總結(jié)一下 NDArray 具有如下幾個(gè)優(yōu)點(diǎn):
NDArray 的到來幫助 DJL 成功轉(zhuǎn)變?yōu)?Java 在深度學(xué)習(xí)領(lǐng)域中最好的工具。它具備平臺(tái)自檢測(cè)機(jī)制,無需任何額外設(shè)置,便可以在應(yīng)用中構(gòu)建基于 CPU/GPU 的代碼。感興趣的小伙伴快跟著教程感受下吧!
更多詳情盡在 NDArray 文檔:https://javadoc.io/doc/ai.djl/api/latest/ai/djl/ndarray/NDArray.html
Deep Java Library (DJL) 是一個(gè)基于 Java 的深度學(xué)習(xí)框架,同時(shí)支持訓(xùn)練以及推理。DJL 博取眾長,構(gòu)建在多個(gè)深度學(xué)習(xí)框架之上 (TenserFlow、PyTorch、MXNet 等) 也同時(shí)具備多個(gè)框架的優(yōu)良特性。你可以輕松使用 DJL 來進(jìn)行訓(xùn)練然后部署你的模型。
它同時(shí)擁有著強(qiáng)大的模型庫支持:只需一行便可以輕松讀取各種預(yù)訓(xùn)練的模型。現(xiàn)在 DJL 的模型庫同時(shí)支持高達(dá) 70 個(gè)來自 GluonCV、 HuggingFace、TorchHub 以及 Keras 的模型。
項(xiàng)目地址:https://github.com/awslabs/djl/
在最新的版本中 DJL 0.6.0 添加了對(duì)于 MXNet 1.7.0、PyTorch 1.5.0、TensorFlow 2.2.0 的支持。我們同時(shí)也添加了 ONNXRuntime 以及 PyTorch 在安卓平臺(tái)的支持。
源:升學(xué)就業(yè)幫講師——肖云銳
1. 數(shù)組的聲明和賦值
方式一: new Array()構(gòu)造函數(shù)方法
// 1. 使用構(gòu)造函數(shù)創(chuàng)建數(shù)組對(duì)象
// 創(chuàng)建了一個(gè)空數(shù)組var arr=new Array();
// 創(chuàng)建了一個(gè)數(shù)組,里面存放了3個(gè)字符串
var arr=new Array('zs', 'ls', 'ww');
// 創(chuàng)建了一個(gè)數(shù)組,里面存放了4個(gè)數(shù)字
var arr=new Array(1, 2, 3, 4);
方式二: 字面量方式
// 2. 使用字面量創(chuàng)建數(shù)組對(duì)象
var arr=[1, 2, 3];
// 獲取數(shù)組中元素的個(gè)數(shù)
console.log(arr.length);
注意事項(xiàng):
1. 定義空數(shù)組的方式
var arr1=[];
2. 定義一個(gè)數(shù)組可以存入不同的數(shù)據(jù)類型. 但是一般不建議這樣使用.
var arr3=[25,true,'abc'];
3. 訪問數(shù)組的元素通過索引,索引從開始
var arr6 = [];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
console.log(arr6);
4. js中數(shù)組的下標(biāo)是可以不連續(xù)的,如果不連續(xù)默認(rèn)補(bǔ)充empty
var arr6 = [];
arr6[0] = 10;
arr6[1] = 20;
arr6[2] = 30;
console.log(arr6);
arr6[4] = 50;
console.log(arr6)
執(zhí)行結(jié)果如下圖所示:
5. 數(shù)組的擴(kuò)容和縮容
var arr = [1, 1.2, new Date(), false, "呵呵"];
console.log("前:" + arr);
// 數(shù)組的擴(kuò)容arr.length = 10;
// 數(shù)組的縮小
//arr.length = 3;
console.log("后:" + arr);
6. 清空數(shù)組
// 方式1 推薦
arr=[];
// 方式2
arr.length=0;
// 方式3
arr.splice(0, arr.length);
2. 數(shù)組的遍歷
方式一:for循環(huán),也是最常見的
for (let i=0; i < arr.length; i++) {
console.log(arr[i])
}
方式二:for......in 遍歷數(shù)組
for(let item in arr){
console.log(arr[item])
}
方式三: foreach遍歷數(shù)組
arr.forEach(function(item, index){
console.log(item + "=" + index);
});
3. 數(shù)組的常用方法
數(shù)組常用的一些方法:
首尾數(shù)據(jù)操作
push() //在數(shù)組末尾添加一個(gè)或多個(gè)元素,并返回?cái)?shù)組操作后的長度
pop() //刪除數(shù)組最后一項(xiàng),返回刪除項(xiàng)
shift() //刪除數(shù)組第一項(xiàng),返回刪除項(xiàng)
unshift() //在數(shù)組開頭添加一個(gè)或多個(gè)元素,并返回?cái)?shù)組的新長度
合并和拆分
concat()
// 將兩個(gè)數(shù)組合并成一個(gè)新的數(shù)組,原數(shù)組不受影響。
// 參數(shù)位置可以是一個(gè)數(shù)組字面量、數(shù)組變量、零散的值。
slice(start,end)
// 從當(dāng)前數(shù)組中截取一個(gè)新的數(shù)組,不影響原來的數(shù)組,返回一個(gè)新的數(shù)組,
// 包含從 start 到 end (不包括該元素)的元素。
// 參數(shù)區(qū)分正負(fù),正值表示下標(biāo)位置,負(fù)值表示從后面往前數(shù)第幾個(gè)位置,
// 參數(shù)可以只傳遞一個(gè),表示從開始位置截取到字符串結(jié)尾。
刪除、插入、替換
splice(index,howmany,element1,element2,……)
//用于插入、刪除或替換數(shù)組的元素
//index:刪除元素的開始位置
//howmany:刪除元素的個(gè)數(shù),可以是0
//element1,element2:要替換的新的數(shù)據(jù)。
位置方法
indexOf() //查找數(shù)據(jù)在數(shù)組中最先出現(xiàn)的下標(biāo)
lastIndexOf() //查找數(shù)據(jù)在數(shù)組中最后一次出現(xiàn)的下標(biāo)
//如果沒找到返回-1
排序和倒序
reverse() //將數(shù)組完全顛倒,第一項(xiàng)變成最后一項(xiàng),最后一項(xiàng)變成第一項(xiàng)。
sort(); //默認(rèn)根據(jù)字符編碼順序,從小到大排序
//如果想要根據(jù)數(shù)值大小進(jìn)行排序,必須添加sort的比較函數(shù)參數(shù)。
//該函數(shù)要比較兩個(gè)值,然后返回一個(gè)用于說明這兩個(gè)值的相對(duì)順序的數(shù)字。比較函數(shù)應(yīng)該具有兩個(gè)參數(shù) a 和 b,根據(jù)a和b的關(guān)系作為判斷條件,返回值根據(jù)條件分為三個(gè)分支,正數(shù)、負(fù)數(shù)、0:
//返回值是負(fù)數(shù)-1:a排在b前面。
//返回值是整數(shù)1:a排在b后面。
//返回值是0:a和b的順序保持不變。
//人為能控制的是判斷條件。
轉(zhuǎn)字符串方法
// 將數(shù)組的所有元素連接到一個(gè)字符串中。
join() //通過參數(shù)作為連字符將數(shù)組中的每一項(xiàng)用連字符連成一個(gè)完整的字符串
迭代方法
//不會(huì)修改原數(shù)組(可選) HTML5新增
every()、filter()、forEach()、map()、some()
代碼示例
近給京東2022秋招做了一道算法題。 問題需要輸出的數(shù)據(jù)格式是二維數(shù)組。 但是我的回答在ac時(shí)有問題,二維數(shù)組的每個(gè)子數(shù)組中的值都是一樣的。 當(dāng)時(shí)一直卡在尋找二層for循環(huán)的bug,但是忽略了問題出在我定義二維數(shù)組的方式上,所以這里想講一下如何定義一個(gè)真正的二- 維數(shù)組。
我們先看一個(gè)例子,網(wǎng)上最常用的定義二維數(shù)組的方法
const n=3
let arr=new Array(n). fill( new Array(n). fill( 0));
console. log(arr)
copy code
看起來是對(duì)的,數(shù)組里面嵌套了三個(gè)數(shù)組,和我們想象的二維數(shù)組一樣,但是問題就在這里。
看起來對(duì)嗎? 我們來做一個(gè)操作給二維數(shù)組中的元素賦值
arr[ 0][ 1]=1;
console. log( "after assignment", arr);
copy code
我勒個(gè)去!什么情況下我只給二維數(shù)組的第0行第1列賦值1。為什么每個(gè)的第一列都變成1?
相信很多人第一次遇到這種情況時(shí),都會(huì)有和我一樣的疑惑。問題出在哪里?
問題在于 Array.fill()
不熟悉的朋友可以看看MDN上的簡(jiǎn)單解釋
Array.prototype.fill()
我希望你會(huì)注意到這句話:“當(dāng)一個(gè)對(duì)象被傳遞給 fill 方法時(shí),數(shù)組被填充了對(duì)該對(duì)象的引用。 "
注意:如果填充的內(nèi)容是'object'類型,那么對(duì)象分配了相同的內(nèi)存地址,而不是深拷貝對(duì)象。
這就解釋了上面的問題。我們根據(jù)它的內(nèi)存地址找到了對(duì)象并修改了對(duì)象本身。這樣,其他子數(shù)組也發(fā)生了變化,因?yàn)檫@些子數(shù)組都取自同一個(gè)內(nèi)存地址。因此,上述方法定義的二維數(shù)組不能稱為真正的二維數(shù)組(我這里說的對(duì)象其實(shí)就是js中的引用類型數(shù)據(jù),array、object、map、Set,這些都是。 )
如何定義一個(gè)真正的二維數(shù)組?
其實(shí)很簡(jiǎn)單,兩個(gè)for循環(huán)就可以完成
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
讓我們給它賦值,看看之前和之后的結(jié)果
console. log( "before assignment", arr1);
arr1[ 0][ 1]=1;
console. log( "after assignment", arr1);
copy code
沒錯(cuò),我們定義了一個(gè)真正的二維數(shù)組
希望大家以后在遇到二維數(shù)組問題的時(shí)候放棄第一種寫法。 我們需要的是一個(gè)真正的二維數(shù)組。
關(guān)注七爪網(wǎng),獲取更多APP/小程序/網(wǎng)站源碼資源!
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。