配置環(huán)境、安裝合適的庫、下載數(shù)據(jù)集……有時(shí)候?qū)W習(xí)深度學(xué)習(xí)的前期工作很讓人沮喪,如果只是為了試試現(xiàn)在人人都談的深度學(xué)習(xí),做這些麻煩事似乎很不值當(dāng)。但好在我們也有一些更簡單的方法可以體驗(yàn)深度學(xué)習(xí)。本文介紹了一種僅用 30 行 JavaScript 代碼就創(chuàng)建出了一個(gè)神經(jīng)網(wǎng)絡(luò)的教程,而且使用的工具也只有 Node.js、Synaptic.js 和瀏覽器而已。另外,本文還做了一個(gè)交互式 Scrimba 教程,也許能幫你理解其中的復(fù)雜概念。
Synaptic.js:https://synaptic.juancazala.com
Node.js:https://nodejs.org
Scrimba 教程:https://scrimba.com/casts/cast-1980
Synaptic.js 讓你可以使用 Node.js 和瀏覽器做深度學(xué)習(xí)。這篇文章將介紹如何使用 Synaptic.js 創(chuàng)建和訓(xùn)練神經(jīng)網(wǎng)絡(luò)。
我們將創(chuàng)建一個(gè)最簡單的神經(jīng)網(wǎng)絡(luò):一個(gè)可以執(zhí)行異或運(yùn)算的網(wǎng)絡(luò)。
// 創(chuàng)建網(wǎng)絡(luò)
const { Layer, Network } = window.synaptic;
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(3);
var outputLayer = new Layer(1);
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
var myNetwork = new Network({
input: inputLayer,
hidden: [hiddenLayer],
output: outputLayer
});
// 訓(xùn)練網(wǎng)絡(luò)——學(xué)習(xí)異或運(yùn)算
var learningRate = .3;
for (var i = 0; i < 20000;="">
{
// 0,0 => 0
myNetwork.activate([0,0]);
myNetwork.propagate(learningRate, [0]);
// 0,1 => 1
myNetwork.activate([0,1]);
myNetwork.propagate(learningRate, [1]);
// 1,0 => 1
myNetwork.activate([1,0]);
myNetwork.propagate(learningRate, [1]);
// 1,1 => 0
myNetwork.activate([1,1]);
myNetwork.propagate(learningRate, [0]);
}
// 測試網(wǎng)絡(luò)
console.log(myNetwork.activate([0,0])); // [0.015020775950893527]
console.log(myNetwork.activate([0,1])); // [0.9815816381088985]
console.log(myNetwork.activate([1,0])); // [0.9871822457132193]
console.log(myNetwork.activate([1,1])); // [0.012950087641929467]
[Image: file:///-/blob/MPZAAATahJG/1brNKXn6wtUlFOpmGZ1AEA]
上面就是這個(gè)網(wǎng)絡(luò)的全部代碼,但在我們深入解讀這些代碼之前,首先我們先了解一下神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)知識(shí)。
神經(jīng)網(wǎng)絡(luò)的基本構(gòu)造模塊是神經(jīng)元。
神經(jīng)元就像是一個(gè)函數(shù),有幾個(gè)輸入,然后可以得到一個(gè)輸出。
神經(jīng)元的種類有很多。我們的網(wǎng)絡(luò)將使用 sigmoid 神經(jīng)元,它可以輸入任何數(shù)字并將其壓縮到 0 到 1 之間。
下圖就是一個(gè) sigmoid 神經(jīng)元。它的輸入是 5,輸出是 1。箭頭被稱為突觸,可以將該神經(jīng)元與網(wǎng)絡(luò)中的其它層連接到一起。
所以,紅色的數(shù)字 5 是哪里來的?它是左邊的三個(gè)突觸的和,讓我們來剖析一下。
在最左邊我們可以看到兩個(gè)值和一個(gè)所謂偏置(bias)值。這兩個(gè)值是 1 和 0,用綠色表示。偏置值是 -2,用棕色表示。
首先,這兩個(gè)輸入與它們的權(quán)重(weight)相乘,即藍(lán)色的數(shù)字 7 和 3。
最后我們將這兩個(gè)值與偏置加到一起就得到了紅色的 5。這就是這個(gè)人工神經(jīng)元的輸入。
因?yàn)檫@是一個(gè) sigmoid 神經(jīng)元,會(huì)將任何值壓縮到 0 到 1 之間,那么這個(gè)輸出可以被壓縮成 1.
如果你將這些神經(jīng)元連接成一個(gè)網(wǎng)絡(luò),你就得到了一個(gè)神經(jīng)網(wǎng)絡(luò)。通過突觸彼此相連的神經(jīng)元可以向前傳播輸入,從而得到輸出,如下圖所示:
訓(xùn)練神經(jīng)網(wǎng)絡(luò)的目的是讓它能夠進(jìn)行泛化,比如識(shí)別手寫的數(shù)字或垃圾郵件。實(shí)現(xiàn)很好的泛化涉及為整個(gè)網(wǎng)絡(luò)找到合適的權(quán)重和偏置值,就像我們上面案例中的藍(lán)色和棕色數(shù)字。
當(dāng)訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)時(shí),你只需要向其展示大量樣本(比如手寫數(shù)字),然后讓其預(yù)測正確的答案即可。
在每次預(yù)測之后,你要計(jì)算這個(gè)預(yù)測的錯(cuò)誤程度,并調(diào)整其權(quán)重和偏置值讓該網(wǎng)絡(luò)在下一輪預(yù)測時(shí)能更正確一點(diǎn)。這個(gè)學(xué)習(xí)過程被稱為反向傳播(backpropagation)。如此反復(fù)幾千次,你的網(wǎng)絡(luò)很快就擅長泛化了。
本教程不會(huì)解釋反向傳播的具體技術(shù)細(xì)節(jié),但如果你有興趣了解,可以參閱下面的文章:
反向傳播的一步步示例:http://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
神經(jīng)網(wǎng)絡(luò)黑客指南:http://karpathy.github.io/neuralnets/
神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí):http://neuralnetworksanddeeplearning.com/chap1.html
現(xiàn)在你已經(jīng)了解了基本的知識(shí),就開始寫代碼吧!首先我們需要?jiǎng)?chuàng)建層。我們可以使用 synaptic 中的 new Layer() 函數(shù)。傳遞給該函數(shù)的數(shù)字表示每層應(yīng)該有多少個(gè)神經(jīng)元。
如果你不知道層是什么,可以看看上面提到的交互式教程。
const { Layer, Network } = window.synaptic;
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(3);
var outputLayer = new Layer(1);
接下來,我們將這些層連接到一起,并實(shí)例化一個(gè)新網(wǎng)絡(luò),如下:
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
var myNetwork = new Network({
input: inputLayer,
hidden: [hiddenLayer],
output: outputLayer
});
所以,這就是一個(gè)「2 層-3 層-1 層」的網(wǎng)絡(luò),可以可視化為下圖的形式:
現(xiàn)在訓(xùn)練這個(gè)網(wǎng)絡(luò):
// train the network - learn XOR
var learningRate = .3;
for (var i = 0; i < 20000;="" i++)="">
// 0,0 => 0
myNetwork.activate([0,0]);
myNetwork.propagate(learningRate, [0]);
// 0,1 => 1
myNetwork.activate([0,1]);
myNetwork.propagate(learningRate, [1]);
// 1,0 => 1
myNetwork.activate([1,0]);
myNetwork.propagate(learningRate, [1]);
// 1,1 => 0
myNetwork.activate([1,1]);
myNetwork.propagate(learningRate, [0]);
}
這里我們運(yùn)行該網(wǎng)絡(luò) 20000 次。每一次我們都前向和反向傳播 4 次,為該網(wǎng)絡(luò)輸入 4 組可能的輸入:[0,0] [0,1] [1,0] [1,1]。
首先我們執(zhí)行 myNetwork.activate([0,0]),其中 [0,0] 是我們發(fā)送給該網(wǎng)絡(luò)的數(shù)據(jù)點(diǎn)。這是前向傳播,也稱為激活這個(gè)網(wǎng)絡(luò)。在每次前向傳播之后,我們需要執(zhí)行反向傳播,這時(shí)候網(wǎng)絡(luò)會(huì)更新自己的權(quán)重和偏置。
反向傳播是通過這行代碼完成的:myNetwork.propagate(learningRate, [0]),其中 learningRate 是一個(gè)常數(shù),給出了網(wǎng)絡(luò)每次應(yīng)該調(diào)整的權(quán)重的量。第二個(gè)參數(shù) 0 是給定輸入 [0,0] 對(duì)應(yīng)的正確輸出。
然后,該網(wǎng)絡(luò)將自己的預(yù)測與正確的標(biāo)簽進(jìn)行比較,從而了解自己的正確程度有多少。
然后網(wǎng)絡(luò)使用這個(gè)比較為基礎(chǔ)來校正自己的權(quán)重和偏置值,這樣讓自己的下一次猜測更加正確一點(diǎn)。
這個(gè)過程如此反復(fù) 20000 次之后,我們可以使用所有四種可能的輸入來檢查網(wǎng)絡(luò)的學(xué)習(xí)情況:
console.log(myNetwork.activate([0,0]));
-> [0.015020775950893527]
console.log(myNetwork.activate([0,1]));
->[0.9815816381088985]
console.log(myNetwork.activate([1,0]));
-> [0.9871822457132193]
console.log(myNetwork.activate([1,1]));教程。
聯(lián)系客服