編者按:今年4月,谷歌在TensorFlow開發(fā)者峰會上發(fā)布TensorFlow的JavaScript版本,引起開發(fā)者廣泛關(guān)注。如今3個月過去了,大家學(xué)會使用這個機器學(xué)習(xí)新框架了嗎?在這篇文章中,我們將用一個初級項目Neural Titanic來演示如何使用TensorFlow.js,聯(lián)系到最近發(fā)生在泰國的事故,這次我們選用的數(shù)據(jù)集是“泰坦尼克號”,目標(biāo)是分析哪些人更能從悲劇中幸免于難(二元分類)。
Demo
注:本文適合對前端JavaScript開發(fā)有基本了解的讀者。
經(jīng)過Geoffrey Hinton、Yoshua Bengio、Andrew Ng和Yann LeCun等人的不懈努力,如今神經(jīng)網(wǎng)絡(luò)終于可以正大光明地站在陽光下,并在現(xiàn)實中有了用武之地。眾所周知,傳統(tǒng)統(tǒng)計模型可以處理結(jié)構(gòu)化的數(shù)據(jù),但對非結(jié)構(gòu)化的數(shù)據(jù),如圖像、音頻和自然語言卻無可奈何。現(xiàn)在,通過往神經(jīng)網(wǎng)絡(luò)中添加更多層神經(jīng)元,也就是我們常說的深度學(xué)習(xí)研究,對非結(jié)構(gòu)化數(shù)據(jù)建模已經(jīng)不再是難事。
以圖像建模為例,圖像中最簡單的特征是邊緣,這些邊緣是形成紋理的基礎(chǔ),紋理是形成簡單對象的基礎(chǔ),而簡單對象又是形成復(fù)雜對象的基礎(chǔ)。這種關(guān)系正好契合深層神經(jīng)網(wǎng)絡(luò)的多層結(jié)構(gòu),因此我們也能學(xué)習(xí)這些可組合的特征。(考慮到文章的目的是介紹TensorFlow.js,我們對深度學(xué)習(xí)的介紹就此打住。)
如需要以上PPT,歡迎私信哦
在過去這幾十年中,隨著計算機算力和可用數(shù)據(jù)的急劇增加,神經(jīng)網(wǎng)絡(luò)已經(jīng)成為解決諸多現(xiàn)實世界問題的可行方案。與此同時,像TensorFlow這樣的機器學(xué)習(xí)庫也在快速崛起,鼓勵開發(fā)者嘗試用神經(jīng)網(wǎng)絡(luò)解決問題。雖然完全搞懂神經(jīng)網(wǎng)絡(luò)不是一時半會兒就能做到的,但我們希望這篇文章能激發(fā)開發(fā)者興趣,鼓勵他們?nèi)?chuàng)建自己的的神經(jīng)網(wǎng)絡(luò)程序。
項目概述
如上所述,神經(jīng)網(wǎng)絡(luò)非常適合對非結(jié)構(gòu)化數(shù)據(jù)進(jìn)行建模,而本文的示例數(shù)據(jù)集是泰坦尼克號,它只包含表格數(shù)據(jù)。這里我們先澄清一個誤區(qū),看完之前的介紹,一些人可能會認(rèn)為神經(jīng)網(wǎng)絡(luò)是萬能的,它比傳統(tǒng)統(tǒng)計模型更好,但事實上,對于簡單數(shù)據(jù),模型結(jié)構(gòu)越簡單,它的性能就越好,因為那樣越不容易出現(xiàn)過擬合。
例如泰坦尼克號數(shù)據(jù)集,或者其他幾乎所有類型的表格數(shù)據(jù),神經(jīng)網(wǎng)絡(luò)在處理它們時需要用到的超參數(shù)有batch-size、激活函數(shù)和神經(jīng)元數(shù)量等,但像決策樹這樣的常規(guī)算法只需調(diào)整更少超參數(shù),最后性能也差不多。所以雖然鼓勵新手多多嘗試,但當(dāng)我們建模時,真的沒有必要事事都用神經(jīng)網(wǎng)絡(luò)。
在這個項目中,因為神經(jīng)網(wǎng)絡(luò)處理的是簡單數(shù)據(jù)集的二元分類任務(wù),我們會結(jié)合可視化技術(shù),具體介紹最后的單隱藏層神經(jīng)網(wǎng)絡(luò)。如果你已經(jīng)精通前端JavaScript開發(fā),也能熟練使用像React這樣的前端框架,你可以在讀完本文后再去學(xué)習(xí)官方文檔,相信它會讓你對TensorFlow.js產(chǎn)生更多興趣:js.tensorflow.org/tutorials/mnist.html
數(shù)據(jù)集和建模概述
泰坦尼克號數(shù)據(jù)集適合初學(xué)者,由于比較小,影響輸出結(jié)果的各項特征也比較好找。我們的任務(wù)是根據(jù)表格數(shù)據(jù)預(yù)測乘客的生存概率,因此可以被用來輔助預(yù)測的列是X,預(yù)測的目標(biāo)列則是Y。下面是數(shù)據(jù)集中的部分?jǐn)?shù)據(jù):
對應(yīng)X和Y,我們可以獲得:
預(yù)測特征(X)
pClass:船票等級(1等、2等、3等)
name:乘客的姓名
sex:乘客的性別
age:乘客的年齡
sibsp:船上和乘客相關(guān)的兄弟姐妹、配偶人數(shù)
parch:船上與乘客相關(guān)的父母和孩子人數(shù)
ticket:乘客的票號
fare:乘客為船票支付的金額
cabin:乘客所在船艙
Embarked:登船港口(C=Cherbourg, Q=Queenstown, S=Southampton)
目標(biāo)標(biāo)簽(Y)
survived:乘客幸存為1,死亡為0
原數(shù)據(jù)集中包含超過1000名乘客的信息,這里為了簡潔直觀,我們假裝上表就是我們的數(shù)據(jù)集,X和y的映射關(guān)系如下所示:
從技術(shù)意義上講,神經(jīng)網(wǎng)絡(luò)為非線性函數(shù)擬合提供了一個強大的框架。如果把上圖轉(zhuǎn)換成函數(shù)形式,它就是:
對于神經(jīng)網(wǎng)絡(luò),如果我們要模型學(xué)會其中的映射關(guān)系,這個學(xué)習(xí)過程被稱為訓(xùn)練。最后得到的結(jié)果必定是個近似值,而不是精確函數(shù),因為如果是后者,這個神經(jīng)網(wǎng)絡(luò)就過擬合了,它強行記住了數(shù)據(jù)集的所有結(jié)果,這樣的模型是沒法用在其他數(shù)據(jù)上的,泛化(通用化)水平太低。作為深度學(xué)習(xí)實踐者,我們的目標(biāo)是構(gòu)建近似上述函數(shù)的神經(jīng)網(wǎng)絡(luò)體系結(jié)構(gòu),讓它不僅在訓(xùn)練集上表現(xiàn)出色,也能被推廣到從未見過的數(shù)據(jù)上。
項目設(shè)置
為了防止每次開發(fā)都要重新綁定源代碼,這里我們先用webpack bundler把JavaScript源代碼和webpack dev服務(wù)器捆綁起來。
在開始項目前,我們先做一些設(shè)置:
安裝Node.js
到github上下載這個repo:github.com/Andrewnetwork/NeuralTitanic
打開終端,再打開下載的repo
設(shè)置終端類型:npm install
鍵入以下命令啟動dev服務(wù)器:npm run dev
單擊終端中顯示的URL,或在Web瀏覽器中輸入:localhost:8080/
在步驟4中,用npm來安裝package.json中列出的項目依賴項。在步驟5中,啟動開發(fā)服務(wù)器,上面會顯示步驟6中需要點擊的URL。這之后,每當(dāng)我們保存對源代碼的修改時,網(wǎng)頁上會實時刷新內(nèi)容,并顯示更改。
如果需要捆綁源代碼,只需運行npm run build,它會自動生成文件放進(jìn)./dist/文件夾中。
代碼
雖然文章開頭我們展示了一個比較美觀的Demo,但這里我們沒有介紹index.html、index.js、ui.js等內(nèi)容,一方面是因為本文假設(shè)讀者已經(jīng)熟悉現(xiàn)代前端JavaScript開發(fā),另一方面是這些細(xì)枝末節(jié)介紹起來太復(fù)雜,容易講不清楚。如果確實有需要,可以直接用步驟2中提到的repo,或者Python了解下?學(xué)起來很快的!:stuckouttongue:
preprocessing.js
function prepTitanicRow(row){
var sex = [0,0];
var embarked = [0,0,0];
var pclass = [0,0,0];
var age = row["age"];
var sibsp = row["sibsp"];
var parch = row["parch"];
var fare = row["fare"];
// Process Categorical Variables
if(row["sex"] == "male"){
sex = [0,1];
}elseif(row["sex"] == "female"){
sex = [1,0];
}
if(row["embarked"] == "S"){
embarked = [0,0,1];
}
elseif(row["embarked"] == "C"){
embarked = [0,1,0];
}
elseif(row["embarked"] == "Q"){
embarked = [1,0,0];
}
if(row["Pclass"] == 1){
pclass = [0,0,1];
}
elseif(row["Pclass"] == 2){
pclass = [0,1,0];
}
elseif(row["Pclass"] == 3){
pclass = [1,0,0];
}
// Process Quantitative Variables
if(parseFloat(age) == NaN){
age = 0;
}
if(parseFloat(sibsp) == NaN){
sibsp = 0;
}
if(parseFloat(parch) == NaN){
parch = 0;
}
if(parseFloat(fare) == NaN){
fare = 0;
}
return pclass.concat(sex).concat([age,sibsp,parch,fare]).concat(embarked);
}
對于任何數(shù)據(jù)分析工作,數(shù)據(jù)預(yù)處理是非常重要的,也是十分有必要的,上面的代碼就在進(jìn)行預(yù)處理:把分類變量轉(zhuǎn)換為one-hot編碼,并用0替代缺失值(NaN)。因為這是個簡單數(shù)據(jù)集,事實上我們還可以更優(yōu)雅一點,用算法來填補缺失值,但考慮到篇幅因素,這里我們都做簡化處理。
}
exportfunction titanicPreprocess(data){
const X = _.map(_.map(data,(x)=>x.d),prepTitanicRow);
const y = _.map(data,(x)=>x.d["survived"]);
return [X,y];
}
在這里,我們把預(yù)處理函數(shù)prepTitanicRow映射到數(shù)據(jù)的每一行,這個函數(shù)的輸出是特征變量X和目標(biāo)向量y。
modeling.js
exportfunction createModel(actFn,nNeurons){
const initStrat = "leCunNormal";
const model = tf.sequential();
model.add(tf.layers.dense({units:nNeurons,activation:actFn,kernelInitializer:initStrat,inputShape:[12]}));
model.add(tf.layers.dense({units:1,activation:"sigmoid",kernelInitializer:initStrat}));
model.compile({optimizer: "adam", loss: tf.losses.logLoss});
return model;
}
現(xiàn)在我們就可以創(chuàng)建單隱藏層神經(jīng)網(wǎng)絡(luò)了,它已經(jīng)被actFn和nNeurons兩個變量參數(shù)化。可以發(fā)現(xiàn),我們要近似的函數(shù)有多個輸入,卻只有一個輸出,這是因為我們在上面的預(yù)處理步驟中擴展了特征空間的維度;也就是說,我們現(xiàn)在有一個步長為3的one-hot輸入,而不是只有一個輸入端口,如下圖所示:
const initStrat = "leCunNormal";
上圖中這些帶箭頭的線被稱為“邊”,它們自帶權(quán)重,我們訓(xùn)練神經(jīng)網(wǎng)絡(luò)的最終目標(biāo)就是把這些權(quán)重調(diào)整到最佳值。在剛開始訓(xùn)練的時候,因為對情況一無所知,這些邊會被隨機分配一個初始值,我們把它稱為初始化策略。
一般情況下,這個初始化不用你自己聲明,TensorFlow提供了通用性較強的默認(rèn)初始化策略,在大多數(shù)情況下都表現(xiàn)良好。但就事論事,這個策略確實會影響神經(jīng)網(wǎng)絡(luò)性能,尤其是我們這次用到的數(shù)據(jù)集太小了,權(quán)重的初始值會對訓(xùn)練過程造成明顯影響。所以這里我們自選一種初始化策略。
const model = tf.sequential();
這個序列模型對象就是我們用來構(gòu)建神經(jīng)網(wǎng)絡(luò)的東西。它意味著當(dāng)我們往里面添加神經(jīng)網(wǎng)絡(luò)層時,它們會按順序堆疊,先輸入層,再隱藏層,最后是輸出層。
model.add(tf.layers.dense({units:nNeurons,activation:actFn,kernelInitializer:initStrat,inputShape:[12]}));
在這里,我們添加了一個輸入層(大小為12),并在它后面又加了個密集連接的隱藏層。密集連接表示這一層的所有神經(jīng)元都與上一層的每個神經(jīng)元相連,在圖中,神經(jīng)元被表示為圓,但需要注意的是,它是個存儲單位,我們的輸入不是神經(jīng)元。
隱藏層會繼承定義圖層的參數(shù)詞典:我們定義了多少參數(shù),它就接收多少參數(shù)。除了我們提供的參數(shù),它還有一些默認(rèn)參數(shù):
units:神經(jīng)元個數(shù),這是個可調(diào)整的超參數(shù)。
activation:該層中應(yīng)用于每個神經(jīng)元的激活函數(shù),對于本文已超綱,請自學(xué)選擇。
kernelInitializer:初始化。
inputShape:輸入空間大小,在我們的例子里是12。
model.add(tf.layers.dense({units:1,activation:"sigmoid",kernelInitializer:initStrat}));
這是我們整個神經(jīng)網(wǎng)絡(luò)的最后一層,它只是一個密集連接到隱藏層的單個輸出神經(jīng)元。我們用sigmoid函數(shù)作為該神經(jīng)元的激活函數(shù),因為函數(shù)的范圍是[0,1],剛好適合二元分類問題。如果你還要深究“為什么這個函數(shù)能用于預(yù)測概率”,我只能簡單告訴你,它和邏輯回歸息息相關(guān)。
model.compile({optimizer: "adam", loss: tf.losses.logLoss});
截至目前,我們已經(jīng)完成網(wǎng)絡(luò)的搭建工作,最后就只剩下TensorFlow編譯了。在編譯過程中,我們會遇到兩個新參數(shù):
optimizer:這是我們在訓(xùn)練期間使用的優(yōu)化算法。如果是新手,用Adam;如果很要求高,梯度下降會是你的最愛。
loss:這個參數(shù)的選擇要多加注意,因為不同的建模問題需要不同的損失函數(shù),它決定了我們會如何測量神經(jīng)網(wǎng)絡(luò)預(yù)測結(jié)果和實際結(jié)果之間的差異。這個誤差會結(jié)合優(yōu)化算法、反向傳播算法進(jìn)一步訓(xùn)練模型,一般情況下,我們用交叉熵。
最后就是神經(jīng)網(wǎng)絡(luò)模型的實際訓(xùn)練:
export async function trainModel(data,trainState){
// Disable Form Inputs
d3.select("#modelParameters").selectAll(".form-control").attr('disabled', 'disabled');
d3.select("#tableControls").selectAll(".form-control").attr('disabled', 'disabled');
// Create Model
const model = createModel(d3.select("#activationFunction").property("value"),
parseInt(d3.select("#nNeurons").property("value")));
// Preprocess Data
const cleanedData = titanicPreprocess(data);
const X = cleanedData[0];
const y = cleanedData[1];
// Train Model
const lossValues = [];
var lastBatchLoss = null;
// Get Hyperparameter Settings
const epochs = d3.select("#epochs").property("value");
const batchSize = d3.select("#batchSize").property("value")
// Init training curve plotting.
initPlot();
for(let epoch = 0; epoch < epochs && trainState.s; epoch++ ){
try{
var i = 0;
while(trainState.s){
// Select Batch
const [xs,ys] = tf.tidy(() => {
const xs = tf.tensor(X.slice(i*batchSize,(i+1)*batchSize))
const ys = tf.tensor(y.slice(i*batchSize,(i+1)*batchSize))
return [xs,ys];
});
const history = await model.fit(xs, ys, {batchSize: batchSize, epochs: 1});
lastBatchLoss = history.history.loss[0];
tf.dispose([xs, ys]);
await tf.nextFrame();
i++;
}
}catch(err){
// End of epoch.
//console.log("Epoch "+epoch+"/"+epochs+" ended.");
const xs = tf.tensor(X);
const pred = model.predict(xs).dataSync();
updatePredictions(pred);
const accuracy = _.sum(_.map(_.zip(pred,y),(x)=> (Math.round(x[0]) == x[1]) ? 1 : 0))/pred.length;
lossValues.push(lastBatchLoss);
plotLoss(lossValues,accuracy);
}
}
trainState.s = true;
createTrainBttn("train",data);
console.log("End Training");
// Enable Form Controls
d3.select("#modelParameters").selectAll(".form-control").attr('disabled', null);
d3.select("#tableControls").selectAll(".form-control").attr('disabled', null);
}
在具體介紹前,我們先看看一些常用的術(shù)語:
Epoch:在整個數(shù)據(jù)集上訓(xùn)練一次被稱為一個epoch。
Mini-Batch:完整訓(xùn)練數(shù)據(jù)的子集。對于每個epoch,我們會把訓(xùn)練數(shù)據(jù)分成較小的子集一批批進(jìn)行訓(xùn)練,通過對比,想必你也應(yīng)該理解上一個術(shù)語的含義了。
如果還是覺得有困難,這里是完整版:
創(chuàng)建神經(jīng)網(wǎng)絡(luò)
預(yù)處理數(shù)據(jù)
Epoch Loop:我們手動設(shè)置的迭代次數(shù)
Mini-Batch Loop:我們還沒有完成一個epoch,還有剩余數(shù)據(jù),訓(xùn)練也沒有停止——在Mini-Batch上訓(xùn)練模型。
End of epoch:已經(jīng)進(jìn)一步訓(xùn)練了模型,并讓它對數(shù)據(jù)做了預(yù)測,而且已經(jīng)用函數(shù)updatePredictions更新了預(yù)測結(jié)果。
什么是“async”和“await”?
ES6允許我們定義異步函數(shù),常見的有async函數(shù),這里應(yīng)該沒問題。當(dāng)我們訓(xùn)練模型時,用await,它也是個異步函數(shù),這樣我們就能讓模型在進(jìn)入下一個epoch前先完成訓(xùn)練。
tf.tidy和tf.dispose?
這些函數(shù)涉及所創(chuàng)建的張量。你可以在張量或變量上調(diào)用dispose來清除它并釋放其GPU內(nèi)存;或者用tf.tidy執(zhí)行一個函數(shù)并清除所有創(chuàng)建的中間張量,釋放它們的GPU內(nèi)存(它不清除內(nèi)部函數(shù)的返回值)。
await tf.nextFrame();
如果沒有這個,模型訓(xùn)練會凍結(jié)你的瀏覽器。其實查遍資料,關(guān)于它的記錄非常少,這大概是TensorFlow.js早起開發(fā)時的產(chǎn)物。
tf.tensor()和dataSync();
因為我們的數(shù)據(jù)存儲在標(biāo)準(zhǔn)JavaScript數(shù)組中,所以我們需要用tf.tensor()將它們轉(zhuǎn)換為TensorFlow的張量格式。反之,如果要從張量轉(zhuǎn)回數(shù)組,用dataSync()。
小結(jié)
如果你下載了repo,而且準(zhǔn)確無誤地理解了上述內(nèi)容,你會得到之前動圖的演示結(jié)果,其中紅色表示死亡,綠色表示幸存,亮綠色表示幸存幾率更高。
本文探討了一個完整的現(xiàn)代JavaScript項目,該項目使用TensorFlow.js可視化單層神經(jīng)網(wǎng)絡(luò)的演化預(yù)測,使用的數(shù)據(jù)集是泰坦尼克號,問題類型是二元分類。希望讀者能根據(jù)這篇文章開始理解如何使用TensorFlow.js。
-
谷歌
+關(guān)注
關(guān)注
27文章
6231瀏覽量
108110 -
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4814瀏覽量
103568 -
數(shù)據(jù)集
+關(guān)注
關(guān)注
4文章
1224瀏覽量
25444
原文標(biāo)題:海難幸存者:基于項目的TensorFlow.js簡介
文章出處:【微信號:jqr_AI,微信公眾號:論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
谷歌發(fā)布基于機器學(xué)習(xí)框架TensorFlow模塊 改善AI模型的隱私保護(hù)
TF下載量已超4600萬!首屆TensorFlow World大會,谷歌大牛Jeff Dean激情演講
關(guān)于 TensorFlow
谷歌深度學(xué)習(xí)插件tensorflow
TensorFlow是什么
TensorFlow的特點和基本的操作方式
TensorFlow的2.0 版本將來臨
TensorFlow2.0終于問世,Alpha版可以搶先體驗

評論