如何通過 JavaScript 實現機器學習和神經學網路?

七月線上實驗室發表於2018-06-20

英文:JeffHeaton  譯文: 雲+社群/白加黑大人

https://cloud.tencent.com/developer/article/1035890


基本介紹

在本文中,你會對如何使用JavaScript實現機器學習這個話題有一些基本的瞭解。我會使用Encog(一個先進的神經網路和機器學習框架)這個框架,並向你們展示如何用這個框架來實現光學字元辨識,模擬退火法,遺傳演算法和神經網路。Encog同時包括了幾個GUI窗體小部件,這些小部件可以更方便地顯示出一般機器學習任務的輸出。

執行環境

Encog是一個面向Java,C#,JavaScript和C/C++的高階機器學習平臺。除此之外,

Encog還可以為http://www.heatonresearch.com/wiki/Meta_Trader_4的實際案例生成對應程式碼,本文將重點介紹如何使用支援JavaScript的Encog框架,該框架允許你建立利用人工智慧實現的互動式web應用程式。訪問以下網址獲取有關Encog的更多資訊。

http://www.heatonresearch.com/encog


程式碼呼叫

本文介紹的所有示例程式碼都可以在附帶的下載中找到。

這段程式碼同時被託管在下面的GitHub庫中。

https://github.com/encog/encog-javascript

你可以看到本文討論的所有示例都執行在下面的URL中:

http://www.heatonresearch.com/fun

Encog框架包含在兩個JavaScript檔案中。第一個JavaScript檔案包含了所有核心的機器學習函式。這個JavaScript檔案被命名為encog-js-x.js。第二個檔案包含了所有的GUI窗體小元件,並被命名為encog-widget.js。你可以在下面的引用路徑中閱讀這兩個檔案。

<script src=" encog-js-1.0.js"></script>

<script src="encog-widget-1.0.js"></script>


歐氏距離

我們從歐氏距離開始談起,歐氏距離是一個非常簡單的概念,適用於不同的機器學習技術。歐氏距離提供的結果是一個數字,該數字決定了相同長度的兩個陣列之間的相似性。思考下面三個陣列:

Array 1: [ 1, 2, 3]

Array 2: [ 1, 3, 2]

Array 3: [ 3, 2, 1]

我們可以計算出上面任意兩個陣列之間的歐氏距離,這對於確定陣列之間的相似度是非常有用的。思考一下,假如我們想要確定一下陣列2或者陣列3哪個距離陣列1更近。為了做到這一點,我們需要計算陣列1和陣列2之間的歐氏距離。然後再計算陣列1和陣列3之間的歐氏距離。兩相比較,最短的就是最相似的。

從數學角度來講,歐氏距離由以下方程式進行計算。

圖 1: 歐氏距離

640?wx_fmt=jpeg

使用這個公式,我們現在可以計算上文中的歐氏距離

d(a1,a2) = sqrt( (a2[0]-a1[0])^2 + (a2[1]-a1[1])^2 +  (a2[1]-a1[1])^2  )

d(a1,a2) = sqrt( (1-1)^2 + (3-2)^2 + (2-3)^2 )

d(a1,a2) = sqrt( 0+1+1 )

d(a1,a2) = sqrt(2) = 1.4

d(a1,a3) = sqrt( (a3[0]-a1[0])^2 + (a3[1]-a1[1])^2 +  (a3[1]-a1[1])^2  )

d(a1,a3) = sqrt( (3-1)^2 + (2-2)^2 + (1-3)^2 )

d(a1,a3) = sqrt( 4+0+4 )

d(a1,a3) = sqrt(8) = 2.3

從結果中我們可以看出陣列2比陣列1更為接近陣列3。

下面的JavaScript程式碼實現了歐氏距離的計算。

ENCOG.MathUtil.euclideanDistance = function (a1, a2, startIndex, len) {

    'use strict';

    var result = 0, i, diff;

    for (i = startIndex; i < (startIndex + len); i += 1) {

        diff = a1[i] - a2[i];

        result += diff * diff;

    }

    return Math.sqrt(result);

};

歐氏距離可以被用來建立一個簡單的光學字元辨識例項。你可以在下圖中看到應用程式執行例項:

圖 2: JavaScript光學字元辨識

640?wx_fmt=jpeg

你可以在下面這個URL中檢視程式的執行例項:

http://www.heatonresearch.com/fun/ocr

HTML5(啟用觸控裝置)的JavaScript應用程式可以通過簡單的歐氏距離來實現基本的光學字元辨識。為了使用這個示例,需要在下面這個大的矩形中繪製一個數字,然後點選“Recognize”(識別)按鈕,程式會嘗試猜測你畫的這個數字。雖然準確性並不是特別高,但它做的確實已經很不錯了。

該程式已經通過了資料訓練,你可以移除這些數字條目中的任何一個,或者建立你自己的條目。如果需要訓練一個新字元的OCR,只要簡單繪出那個字元,然後點選“Teach”(教學)按鈕。則該字元就會被增加到已知的字元列表中。

你會發現,你所繪製的任何東西都是先剪裁然後向下取樣的。程式會對你所繪製的高解析度字元向下取樣並將取樣點分配到5×8網格中。然後將這個通過向下取樣得到的網格與每個數字的向下取樣網格進行比較。如果要檢視程式中經訓練後得到的網格,需要在字元列表中單擊你希望看到的字元。然後程式會將這個網格轉換成一個一維陣列,而一個5×8的網格會有40個陣列元素。

以下JavaScript程式碼執行了這個搜尋,並且實現了一個歐氏距離的計算

var c, data, sum, i, delta;

for(c in charData )

{

 data = charData[c];

// 現在我們將會識別出這個畫出來的字母.

// 為此,我們需要利用歐氏距離來計算

// http://www.heatonresearch.com/wiki/Euclidean_Distance (這是歐氏距離執行例項的URL)

 sum = 0;

 for(var i = 0; i&lt;data.length; i++ )

 {

 delta = data[i] - downSampleData[i];

 sum = sum + (delta*delta);

 }

 sum = Math.sqrt(sum);

// 最基本的,我們需要計算的是歐氏距離

// 我們畫上去的字母,我們學習的每一個樣本

// 程式將會返回歐氏距離最小的那個字元

if( sum&lt;bestScore || bestChar=='??' )

{

 bestScore = sum;

 bestChar = c;

}

}


蜂擁演算法

這個例子展示一個名為flocking(蜂擁)的迷人的簡單演算法。此處的粒子是成群存在的。起初的時候,它們各自隨機出現在某個位置,然而,這些粒子會很快地填充成各種形式的組,並以看似複雜的模式路線飛行。或者你也可以手動點選(或者觸控)一個位置,這些粒子會排斥並遠離你的接觸點。

圖3:flocking(蜂擁演算法)

640?wx_fmt=jpeg

你可以線上執行以下URL的例項程式:

http://www.heatonresearch.com/fun/flock

這個例項可能需要一分鐘(大約),才能讓成熟的蜂擁叢集出現。即使這些叢集出現了,它們也經常會再次分裂和重組。重啟時點選“Clear”(清除),或者也可以點選“Big Bang”(大爆炸模式),該模式不會使用任何隨機的初始化,而是將粒子統一放置在皮膚中央,並且以粒子設定的“複雜模式”迅速向外移動。

克雷格·雷諾茲在1986年首次用他的模擬程式Boids在計算機上成功模擬出了蜂擁演算法。蜂擁叢集是一種非常複雜的行為。他在不同種類的動物中有各自表現形式,各自使用了很多不同的名字。比如一群小鳥,一群昆蟲,一個學校的魚群,一群牛等等。其實就是用不同的名字來描述本質相同的行為。

初看上去,蜂擁演算法可能看似複雜。因為我們需要建立一個物件來處理叢集中的個體、需要定義一個蜂擁物件來容納叢集成員、需要為確定蜂擁叢集應該向哪個方向移動而制定常規行為。我們還必須確定如何將蜂擁叢集分成兩群或者更多的群。那麼問題是什麼樣的標準可以決定某個群體可以得到成員數量?新成員如何被確認是屬於哪一個叢集?你可以在下面內容中看到一些真實的蜂擁叢集例子。

蜂擁演算法其實很簡單,它只有三條規則:

  • 分離 –遠離擁擠的鄰居(短距離相互排斥)

  • 對齊 - 趨近於鄰居的平均方向

  • 內聚 - 轉向鄰居的平均距離位置(長距離相互吸引)

這三個基本規則是必需的。蜂擁演算法其實就是“簡單的複雜”的典型例子。

我希望這個例子能夠儘可能的簡單,但是仍然表現出看似複雜的行為方式。其實這些粒子都是以恆定的速度執行的,每個粒子都有一個角度屬性來定義粒子運動的方向。所以這些粒子不可以加速或者減速,唯一可以做到是轉向。

上述的三種規則其實是分別為粒子的運動設定好了一個“理想的角度”,遵守這三種規則的期望被特定的百分比屬性所抑制。這些抑制因子是你在底部看到的三個數字。你可以嘗試填入一些數字,看看它們是如何影響叢集粒子的運動軌跡的。其實有很多的數字組合不會產生叢集的行為,而我在例項中填入的預設值是比較合適的。

如果你想單獨檢視這三種規則中單獨一條生效時的結果,那麼可以將該規則設定為1.0,其它的規則設定為0.0。例如當內聚的規則單獨生效時,你會所有的粒子會聚集在皮膚區域中少數的幾個位置。

在這個區域中不存在任何隨機性。除了粒子最初出現的位置是隨機的之外,不會產生更多的隨機數。你甚至可以點選“Big Bang”(大爆炸模式)按鈕,來消除系統中所有的隨機性。如果你點選了“Big Bang”按鈕,則所有的粒子都會被放置到區域的中心位置,並以同樣的方向運動。如此一來,要形成一幅複雜的運動模式並不會花費很長時間。所以對於用非常簡單的規則來實現非常複雜的系統來說,蜂擁演算法是一個非常典型的例子。

理解歐氏距離對於例子很重要。因為每個粒子都有兩個維度,分別是x座標和y座標。利用歐氏距離的計算方法,我們就可以很快找到最近的鄰居。由此即引入了另一種重要的機器學習演算法,即“K-鄰近演算法”。這個K就是你希望找到的鄰居的數量。

這三種規則可以很容易的用JavaScript實現。首先,我們計算出理想的分離角度。

// 1. 隔離-避免擁擠的鄰居

(短距離的排斥力) 

separation = 0;

if (nearest.length > 0) {

  meanX = ENCOG.ArrayUtil.arrayMean(nearest, 0);

  meanY = ENCOG.ArrayUtil.arrayMean(nearest, 1);

  dx = meanX - this.agentsi;

  dy = meanY - this.agentsi;

  separation = (Math.atan2(dx, dy) \* 180 / Math.PI) - this.agentsi;

  separation += 180;

}

首先,我們需要計算出所有鄰居粒子的x座標的平均值和y座標的平均值,這個平均座標點就是鄰近叢集的中心點。然後,借用一些三角函式中的知識,計算出我們和鄰近叢集中心點之間的夾角值。對這個夾角值加上180,因為我們是希望遠離這個鄰近的鄰居的(進而我們就不會撞到它們)。這個才是我們應該努力爭取的理想分離角度。

緊接著,我們會計算出理想的對齊角度。如下程式碼所示。

// 2. 對齊-轉向鄰居的平均方向

alignment = 0;

if (neighbors.length > 0) {

  alignment = ENCOG.ArrayUtil.arrayMean(neighbors, 2) - this.agents[i][2];

}

對齊非常簡單,其實就是所有鄰居的平均角度。

接下來我們計算內聚力。為此我們再來看看鄰居,不過這回考慮的是一個更大的集合,幾乎包括了所有的粒子。

// 3. 內聚-轉向鄰居的平均位置(長距離的吸引力)

cohesion = 0;

if (neighbors.length > 0) {

  meanX 

= ENCOG.ArrayUtil.arrayMean(this.agents, 0); 

  meanY = ENCOG.ArrayUtil.arrayMean(this.agents, 1);

  dx = meanX - this.agents[i][0];

  dy = meanY - this.agents[i][1];

  cohesion = (Math.atan2(dx, dy) * 180 / Math.PI) - this.agents[i][2];

}

現在我們從這個規則中得到了理想的角度,那麼必須要開始轉動粒子(或者說是代理)了。

// 執行轉向操作

// 這三種規則的引數應用值是可以配置的

// 我提供的這三個預設值比例的執行表現很好

turnAmount = (cohesion * this.constCohesion) + (alignment * this.constAlignment) + (separation * this.constSeparation);

this.agents[i][2] += turnAmount;

到目前為止,我們研究的技術並不是隨機性的,而是可以被認定為決定性的。也就是說得到的結果總是可以預測的。對於本文的內容的排版,我們會做出180度的調整,剩下的技術都是研究隨機性的。也就是用隨機性來解決問題。

旅行推銷員問題(TSP問題)

旅行推銷員問題(TSP)意為存在一名“推銷員”,他必須經過一定數量的城市,而這條最短的旅行路線就是我們尋找的目標。其中允許推銷員從任意一個城市開始或者結束。唯一的要求是“推銷員”必須經過每一個城市並且只能經過一次。

如果用一般的迭代程式實現,這似乎是一個簡單的任務。思考一下隨著城市數量的增加,可能的排列組合數量會增加多少。如果只有一兩個城市,那隻需要一步迭代就夠了。如果是三個城市呢,迭代步驟就變成了6步。表格8-1列舉出了迭代步驟的增長速度。

表1:用常規程式解決TSP問題的步驟數目

城市數目

步驟數目

1

1

2

1

3

6

4

24

5

120

6

720

7

5040

8

40,320

9

362,880

10

3,628,800

11

39,916,800

12

479,001,600

13

6,227,020,800

...

...

50

3.041 * 10^64

表格中的計算公式就是階乘。步驟數目n的數量就是用階乘符號!計算的。任意n值的階乘計算方式是n×(n−1) ×(n−2).......3×2×1

由這個公式不難看出當一個程式必須使用“暴力”方式進行搜尋時,這些數值會變得非常大。在下一節的討論中,我們使用的示例程式會在幾分鐘內找到一個能解決50個城市問題的解決方案,這個程式用到是模擬退火法的思路,而不是使用普通的暴力搜尋。

模擬退火法

模擬退火法是一種模擬退火的物理過程的程式設計方法,退火是指將某種材料(比如鋼鐵或者玻璃)加熱後再冷卻的方法,通常用來軟化材料以及降低材料硬度。由此可知,模擬退火法就是將一個“解決方案”暴露在“熱處理”的環境中,然後進行冷卻處理進而產生一個更好的解決方案。你可以在下面的URL中執行模擬退火法的示例程式。

http://www.heatonresearch.com/fun/tsp/anneal

模擬退火法是通過從起始溫度到結束溫度的多次迭代進行實現的。迴圈計數允許你指定溫度下降的粒度。溫度越高,系統引入的隨機性就越高。你可以配置這三個引數的值。

下面的JavaScript程式碼實現了模擬退火法

anneal.randomize = function(path, temperature) {

 var length = path.length - 1;

 // 調整路徑上城市的次序(即模擬退火)

 for (var i = 0; i < temperature; i++) {

 var index1 = Math.floor(length * Math.random());

 var index2 = Math.floor(length * Math.random());

 var d = universe.pathDistance(path, index1, index1 + 1)

 + universe.pathDistance(path, index2, index2 + 1)

 - universe.pathDistance(path, index1, index2)

 - universe.pathDistance(path, index1 + 1, index2 + 1);

 if (d > 0) {

 // 如果需要的話對index1 和 index2進行排序

 if (index2 < index1) {

 var temp = index1;

 index1 = index2;

 index2 = temp;

 }

 for (; index2 > index1; index2--) {

 var temp = path[index1 + 1];

 path[index1 + 1] = path[index2];

 path[index2] = temp;

 index1++;

 }

 }

 }

}

上面的隨機化函式是專門為TSP問題定義的。在Encog框架中模擬退火法是通用的,相對於TSP獨立。所以你必須為你希望解決的問題提供一個隨機函式。

基本來說,隨機化函式會根據溫度對城市的旅行路線進行修正。上面的函式只是簡單地根據溫度將旅行路線中的路線上經過城市次序進行對換。溫度越高,對換的次數越多。

隨機城市

這個程式的常見用法是將隨機幾個城市放置地圖上,這些城市出現在地圖上的隨機的幾個位置。隨機城市問題的排列組合相比於其他的固定城市組合要更困難一些。在下圖中你可以看到包含了50個隨機的城市的地圖。

圖 4:隨機城市

640?wx_fmt=jpeg

一旦解決了這組隨機城市TSP問題,結果就如下圖所示。

圖 5:可能的解決方案

640?wx_fmt=jpeg

你可能想要通過改變引數來評估模擬退火法的實際效果,為此需要重新執行該程式,並且你應該隨機化旅行路線。這樣你就可以用相同的城市配置重新開始。

城市圈

你可以將城市位置以橢圓的形狀進行排列,這樣就更容易理解模擬退火法是如何演化出最佳解決方案的。圍繞一個橢圓的最優路徑與它的周長形狀類似。在這裡你可以利用模擬退火法,找到一條几乎就是最優的路徑。

圖 6:城市圈

640?wx_fmt=jpeg

遺傳演算法

利用遺傳演算法(GA)可以得到TSP問題的潛在解決方案。GA是通過簡單的進化操作來建立一個能夠不斷改進的解決方案。這整個過程就相當於生物遺傳進化的精簡版。進化其實就是通過交叉和突變實現的,所以當兩個解決方案“交配”併產生後代時,就相當於發生了交叉。而當單一的解決方案稍微有所改變時就相當於引發了突變。

類似於模擬退火法,GA(遺傳演算法)也是隨機的。在交叉過程中會由隨機性來決定父本和母本會遺產什麼樣的特徵給子代。

你可以在下面的URL中線上檢視TSP(旅行推銷員問題)的遺傳演算法應用程式:

http://www.heatonresearch.com/fun/tsp/genetic

為了使用Encog框架中自帶的遺傳演算法,你必須定義變異和交叉這兩個操作,它們的實現取決於你正在尋找的解決方案的型別。

下面的程式碼定義了TSP問題的突變操作。

genetic.mutate = function performMutation(data)

{

  var iswap1 = Math.floor(Math.random() * data.length);

  var iswap2 = Math.floor(Math.random() * data.length);

  // 不相等時

  if (iswap1 == iswap2)

  {

    // 繼續下一步

    // 但是,不要出界

    if (iswap1 > 0)

    {

      iswap1--;

    } else {

      iswap1++;

    }

  }

  var t = data[iswap1];

  data[iswap1] = data[iswap2];

  data[iswap2] = t;

}

這段程式碼與模擬退火法的隨機化操作非常類似。本質上,程式對列表中的兩個城市進行了交換操作。所以我們必須保證這兩個隨機城市是不相同的,因為一旦相同,這兩個城市就不會發生交換。

交叉操作比較複雜。下面的程式碼實現了交叉函式。

genetic.crossover = function performCrossover(motherArray, fatherArray, child1Array, child2Array)

{

  // 染色體(此處泛指遺傳特性)必須在兩個位置被切割,並確定他們。

  var cutLength = motherArray.length / 5;

  var cutpoint1 = Math.floor(Math.random() * (motherArray.length - cutLength));

  var cutpoint2 = cutpoint1 + cutLength;

  // 記錄這兩個子代中每一個染色體中所帶的基因,預設為false

  var taken1 = {};

  var taken2 = {};

  // 處理削減的染色體部分

  for (var i = 0; i < motherArray.length; i++)

  {

    if (!((i < cutpoint1) || (i > cutpoint2)))

    {

      child1Array[i] = fatherArray[i];

      child2Array[i] = motherArray[i];

      taken1[fatherArray[i]] = true;

      taken2[motherArray[i]] = true;

    }

  }

  // 處理外部的染色體部分

  for (var i = 0; i < motherArray.length; i++)

  {

    if ((i < cutpoint1) || (i > cutpoint2))

    {

      child1Array[i] = getNotTaken(motherArray,taken1);

      child2Array[i] = getNotTaken(fatherArray,taken2);

    }

  }

};


上面程式碼的原理如下:在城市的道路上取兩個“切點”,這就意味著把父本和母本的特性都各自分割成了三份,父本和母本有著相同的切點。這些切割的規模是隨機的,然後通過交換父輩的三份來建立兩個子代。例如,觀察下面的父本和母本。


[m1, m2, m3 ,m4, m5, m6, m7, m8, m9, m10]

[f1, f2, f3 ,f4, f5, f6, f7, f8, f9, f10]

現在我們將這些切點加進去。

[m1, m2] [m3 ,m4, m5, m6] [m7, m8, m9, m10]

[f1, f2] [f3 ,f4, f5, f6] [f7, f8, f9, f10]

如此會產生下面兩個子代。

[m1, m2] [f3 ,f4, f5, f6] [m7, m8, m9, m10]

[f1, f2] [m3 ,m4, m5, m6] [f7, f8, f9, f10]

根據另一個隨機事件,每個解決方案都可能會發生突變。突變就是將“新產生的資訊”新增到種群遺傳的過程。否則就是簡單的傳遞已經存在的遺傳特徵。

XOR神經網路

神經網路是另外一種基於生物學的機器學習方法,它非常鬆散地建立在人腦的基礎上。神經網路是由神經突觸連線的神經元組成的,每一個突觸本身都具有權重,眾多突觸的權重構成了神經網路的記憶。如下所示的神經網路示意圖。

圖 7:一個神經網路

640?wx_fmt=jpeg

如圖所示的結構,其實就是我們下一節要建立的神經網路,你可以在上面的神經網路中看到有一個輸入層和一個輸出層。神經網路接收來自輸入層的刺激,並交由輸出層進行相應輸出。神經網路內部也可能存在隱藏層,該層中同樣包含有神經元。隱藏層也有助於資訊的處理。XOR神經網路(異或神經網路),有兩個輸入和一個輸出。兩個輸入端接收布林值(0或者1),輸出神經元也輸出布林值。其目的就是讓神經網路實現和XOR(異或運算)操作符相同的功能。

0 XOR 0 = 0

1 XOR 0 = 1

0 XOR 1 = 1

1 XOR 1 = 0

當兩個輸入不一致時,異或XOR操作符的輸出必為1。

你可以在下面看到異或XOR的示例輸出。

Training XOR with Resilient Propagation (RPROP)

Training Iteration #1, Error: 0.266564333804989

Training Iteration #2, Error: 0.2525674154011323

Training Iteration #3, Error: 0.2510141208338126

Training Iteration #4, Error: 0.2501895607116004

Training Iteration #5, Error: 0.24604660296617512

Training Iteration #6, Error: 0.24363697465430123

Training Iteration #7, Error: 0.24007542622000883

Training Iteration #8, Error: 0.23594361591893737

Training Iteration #9, Error: 0.23110199069041137

Training Iteration #10, Error: 0.22402031408256806

...

Training Iteration #41, Error: 0.0169149539750981

Training Iteration #42, Error: 0.012983289628979862

Training Iteration #43, Error: 0.010217909135985562

Training Iteration #44, Error: 0.007442433731742264

Testing neural network

Input: 0 ; 0   Output: 0.000005296759326400659   Ideal: 0

Input: 1 ; 0   Output: 0.9176637562838892   Ideal: 1

Input: 0 ; 1   Output: 0.9249242746585553   Ideal: 1

Input: 1 ; 1   Output: 0.036556423402042126   Ideal: 0


正如上文所示,它用了44個迭代訓練來教神經網路執行XOR操作,神經網路的初始化權重是從隨機數字開始的。資料訓練的過程中會逐漸調整權重,以產生期望的輸出。神經網路的隨機部分是權重的初始化量值。除了這些,神經網路是決定性的。給定相同的權重和輸入,神經網路始終會產生相同的輸出。

在上面的輸出中,你可能會注意到輸出的結果並不是非常精確的。因為神經網路永遠不會為1的輸出精確到1.0。由於開始的權重是隨機的,所以你不可能從這個神經網路中得到兩個相同的結果。另外,由於一些隨機的初始化權重量值是完全不可訓練的,正因如此,有時你會看到XOR神經網路達到了5000的最大訓練值,然而就還是放棄了。

你可以在下面URL中看到這個案例的執行例項。

http://www.heatonresearch.com/fun/ann/xor

我們現在來觀察這個程式是如何構建的。首先,我們建立輸入和理想輸出。

var XOR_INPUT = [

  [0,0],

  [1,0],

  [0,1],

  [1,1]

];

var XOR_IDEAL = [

  [0],

  [1],

  [1],

  [0]

];

上面的兩個陣列分別包含了輸入和理想輸出。這個“真相表”將被用來訓練神經網路。

接著我們來建立一個三層神經網路。輸入層有兩個神經元,隱藏的神經元有三個,輸出層有一個神經元。

var network = ENCOG.BasicNetwork.create( [

  ENCOG.BasicLayer.create(ENCOG.ActivationSigmoid.create(),2,1),

  ENCOG.BasicLayer.create(ENCOG.ActivationSigmoid.create(),3,1),

  ENCOG.BasicLayer.create(ENCOG.ActivationSigmoid.create(),1,0)] );

network.randomize();

建立和隨機化神經網路的時候,將會呼叫隨機化函式將權重填充為隨機值。

訓練神經網路有很多不同方法,對於本例,我們會採用RPROP(一種基於彈性反向傳播的神經網路演算法原理)來實現。

var train = ENCOG.PropagationTrainer.create(network,XOR_INPUT,XOR_IDEAL,"RPROP",0,0);

現在,我們將通過迭代訓練進行迴圈處理,直到出錯率降到可以接受的水平線以下為止。

var iteration = 1;

do

{

  train.iteration();

  var str = "Training Iteration #" + iteration + ", Error: " + train.error;

con.writeLine(str); 

  iteration++;

} while( iteration<1000 && train.error>0.01);

現在神經網路的訓練已完成,我們將對輸入陣列進行迴圈處理,並將其提交給神經網路。神經網路會顯示出對應輸出。

var input = [0,0];

var output = [];

con.writeLine("Testing neural network");

for(var i=0;i<XOR_INPUT.length;i++)

{

  network.compute(XOR_INPUT[i],output);

  var str = "Input: " + String(XOR_INPUT[i][0])

    + " ; " + String(XOR_INPUT[i][1])

    + "   Output: " + String(output[0])

    + "   Ideal: " + String(XOR_IDEAL[i][0]);

  con.writeLine(str);

}

這是對神經網路的一個非常簡單的介紹,我還做了一個關於Java和C#神經網路內容,你如果只對神經網路感興趣,下面的內容應該會有所幫助。

  • <ahref="http:>Introduction to Neural Networks for Java

  • <ahref="http:>Introduction to Neural Networks for C# </ahref="http:>

此外,如果你想了解神經網路的基本介紹,下面的文章可能會對你有用。

http://www.heatonresearch.com/content/non-mathematical-introduction-using-neural-networks

神經網路分類

現在我們來看一個稍微複雜些的神經網路分類,這個神經網路將會學習如何進行分類。我們會學到神經網路是如何通過訓練資料集來學習對資料點進行分類,並且能夠對訓練資料集中不存在的資料點進行分類。

你可以在下面的URL中線上執行這個示例程式碼:

http://www.heatonresearch.com/ann/classification

本案例將利用前饋神經網路原理進行分類。為了充分利用這個程式,我們在畫圖區域繪製了幾個彩色點。必須保證你至少有兩個彩色點,否則程式就無法進行分類。一旦你開始畫點並且點選begin(開始),則神經網路也就開始訓練了。你將看到你提供的資料點附近的其他區域是如何進行分類的。

上一個神經網路案例有是兩個輸入神經元和三個輸出神經元的。隱藏層的結構是由drop列表決定的。舉例來說,如果你選擇了2:10:10:3,你將會得到一個與以下影象相似的網路,這個網路有兩個隱藏層,每層有10個神經元。

輸入神經元代表一個點的x座標和y座標。為了繪出上面的影象,該程式在x座標和y座標的網格上進行迴圈處理。每個網格元件都會對神經網路進行查詢。左上角的細胞是[0,0],右下角的細胞座標是[1,1]。對於具有sigmoid(常用的非線性啟用函式)啟用函式的神經網路資料,通常可以在0到1之間的範圍內接受輸入,因此這個範圍的表現良好。中心點是[0.5,0.5]。

神經網路的輸出即正方形中畫素點的RGB顏色值。[0,0,0]表示黑色,[1,1,1]表示白色。當你在繪圖區域畫點時,就等同於在提供訓練資料。輸入神經元將會根據你輸入的資料訓練出放置x座標和y座標的方式。期望或者理想中的輸出應該是與你在該位置選擇的顏色近似一致。

讓我們來看一個簡單的案例。如果你只畫出兩個資料點,那麼這個區域就會被分割成兩部分。如下圖所示,你可以看到一個紅色的資料點和一個藍色的資料點。

圖 8:兩個資料點的分類

640?wx_fmt=jpeg

該演算法為了讓應用程式得到的錯誤評級比較低,它僅需要保證藍色資料點位於藍色區域,而紅色資料點位於紅色區域。其他所有畫素點都是基於已知畫素點的“猜測”。但由於這樣已知的資料非常少,所以神經網路很難真正猜到這兩個區域之間的邊界到底在哪裡。

如果你提供了更多的訓練資料,那你會得到一個更加複雜的形狀。如果你選擇建立一個雙色的隨機影象,那你會得到與下圖類似的資料點。

圖 9:多個資料點的分類

640?wx_fmt=jpeg

在此圖中,神經網路建立了一種更加複雜的模式試圖來適應所有的資料點。

你還可以選擇建立一個複雜的多顏色模式。下面的案例中為資料點隨機生成了顏色值。神經網路甚至會將顏色進行混合,試圖做出妥協,以此來儘可能地降低誤差。

圖 10:多顏色資料點分類

640?wx_fmt=jpeg

此演算法甚至有可能學習複雜的相互螺旋的形狀,如下圖所示。

圖 11:螺旋資料點的分類

640?wx_fmt=jpeg

延伸閱讀

本文介紹了JavaScript中的機器學習,如果想了解更多關於機器學習的知識,那麼你可能會對下面的連結感興趣。

  • Encog 專案

  • Encog 維基百科

  • 關於Encog的更多資訊

  • Facebook上關於Encog的內容

歷史

2012年10月16日的第一個版本,引用檔案版本 Encog JS v1.0

640?wx_fmt=gif

《機器學習 第九期》從零到機器學習實戰專案,提供GPU&CPU雙雲平臺,作業考試1V1批改(優秀學員內推BAT等);點選文末“閱讀原文”瞭解詳情。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

相關文章