本文章是由機器翻譯。

測試回合

適合程式設計人員的中立網路後端傳播

James McCaffrey

下載代碼示例

James McCaffrey
人工神經網路可以看作一個 meta 的功能,接受固定的數目的數位輸入,並生成固定的數量的數位輸出。在大多數情況下,一個神經網路有一層隱藏神經元的每個隱藏的神經元完全輸入的神經元和輸出神經元連接的位置。每個單獨的隱藏關聯神經元和每個單個輸出神經元是一套的權重值和一個所謂偏差值。重量和偏見確定一組給定的輸入值的輸出值。

當神經網路用於模式的現有資料集,這樣可以對新資料作出預測時, 面臨的主要挑戰是找到生成最佳匹配的現有資料的輸出的重量和偏差值的集合。估計神經網路最優權重和偏見的最常見技術被稱為反向傳播。雖然有很多優秀參考描述複雜的數學基礎反向傳播,但有極少數指南供清楚解釋如何程式設計的反向傳播演算法的程式師。這篇文章解釋了如何實現反向傳播。使用 C# 語言,但您應該在這裡提交給其他語言的代碼重構不麻煩。

查看我的最佳方法是看一看的截圖中的演示程式圖 1。演示計畫創建擁有三個輸入的神經元,隱藏圖層與四個神經元的神經網路和兩個輸出神經元。神經網路的一個隱藏圖層需要兩個啟動功能。在許多情況下,不過,兩個啟動功能是相同的通常的乙狀結腸函數。但在此演示中,這說明啟動功能和反向傳播之間的關係,我使用不同啟動功能:隱藏輸入計算,乙狀結腸函數和隱藏輸出計算的雙曲正切 (雙曲正切值) 函數。

Back-Propagation Algorithm in Action
圖 1 反向傳播演算法在行動

A 完全連接 3-4-2 神經網路需要 3 * 4 + 4 * 2 = 20 重量值和 4++ 2 = 26 重量和偏見的共有 6 偏差值。這些權重和偏見被初始化為更多或更少的任意值。三個虛擬輸入的值設置為 1.0、 2.0 和 3.0。與初始重量、 偏見和輸入的值,初始的輸出值的計算將 {0.7225、-0.8779} 神經網路。演示計畫任意假定正確的兩個輸出值是 {-0.8500、 0.7500}。反向傳播演算法的目標是找到一套新的重量和生成非常接近投入 {1.0、 2.0、 3.0} 的正確值的產出的偏見。

反向傳播需要兩個自由參數。學習率,通常由希臘字母 eta 反向傳播文學、 控制如何快速演算法彙聚到一個最後的估計。勢頭,通常給予希臘字母 Alpha,有助於避免的局勢的演算法振盪和從未彙聚到最後估計的反向傳播演算法。演示計畫將學習速率設置為 0.90 和至 0.04 勢頭。通常這些值找到的試驗和錯誤。

為一個神經網路尋找最佳套重量和偏見有時稱為網路培訓。培訓與反向傳播是一個反覆運算過程。在每次反覆運算,反向傳播計算一組新的神經網路重量和偏差值,在理論上生成輸出值越接近目標值。演示計畫在第一次訓練反覆運算之後, 的反向傳播演算法發現新的重量和偏差值生成新的產出的 {-0.8932、-0.8006}。第一個新的-0.8932 輸出價值是更接近于-0.8500 的第一個目標輸出值。-0.8006 的第二個新輸出值從其目標值的 0.7500 仍遠了。

培訓過程可以多種方式終止。演示計畫反覆運算培訓,直到輸出值和目標值的絕對差異的總和 < = 0.01 或培訓達到 1,000 反覆運算。在演示中,培訓,六個反覆運算後回繁殖發現一組神經網路生成的輸出的重量和偏差值 {-0.8423、 0.7481},這是非常接近于 {-0.8500、 0.7500} 所需的目標值。

本文假定您有專家級的程式設計技巧和你有神經網路的一個非常基本的瞭解。(神經網路的基本資訊,在看到我 2012 年 5 月的文章"潛水到神經網路," msdn.microsoft.com/magazine/hh975375.)演示計畫中所示的代碼圖 1 有點太長,目前在這篇文章,所以我將重點介紹如何解釋該演算法的關鍵區段。演示計畫的完整原始程式碼,在 archive.msdn.microsoft.com/mag201210TestRun

定義一個神經網路類

編碼使用反向傳播神經網路本身很好的物件導向的方法。類定義用於演示計畫列出的圖 2

圖 2 神經網路類

class NeuralNetwork
{
  private int numInput;
  private int numHidden;
  private int numOutput;
  // 15 input, output, weight, bias, and other arrays here
  public NeuralNetwork(int numInput, 
    int numHidden, int numOutput) {...}
  public void UpdateWeights(double[] tValues, 
    double eta, double alpha) {...}
  public void SetWeights(double[] weights) {...}
  public double[] GetWeights() {...}
  public double[] ComputeOutputs(double[] xValues) {...}
  private static double SigmoidFunction(double x)
  {
    if (x < -45.0) return 0.0;
    else if (x > 45.0) return 1.0;
    else return 1.0 / (1.0 + Math.Exp(-x));
  }
  private static double HyperTanFunction(double x)
  {
    if (x < -10.0) return -1.0;
    else if (x > 10.0) return 1.0;
    else return Math.Tanh(x);
  }
}

成員欄位、 numInput、 numHidden 和 numOutput 定義的神經網路架構的特點。 除了簡單的建構函式,類有四個可公開存取方法和兩個説明器方法。 方法 UpdateWeights 包含反向傳播演算法的所有的邏輯。 方法 SetWeights 接受重量和偏見的陣列,並將複製這些值 sequen­一到成員的陣列。 方法 GetWeights 執行反向 oper­現代化建設通過複製到單個陣列中的權重和偏見並返回該陣列。 ComputeOutputs 方法確定使用當前的輸入、 重量和偏差值的神經網路輸出值。

方法 SigmoidFunction 用作隱藏輸入啟動功能。 它接受真正的值 (在 C# 中 double 類型),並返回一個值 0.0 和 1.0 之間。 方法 HyperTanFunction 也接受真正的價值,但返回-1.0 和 +1.0 之間的一個值。 C# 語言都有內置的雙曲正切函數的 Math.Tanh,但如果您使用一種語言,沒有本機雙曲正切函數,你得從零開始到代碼之一。

設置陣列

成功的程式設計神經網路反向傳播演算法的關鍵之一是演算法的要充分瞭解的陣列,用於存儲重量和偏差值、 存儲不同類型的輸入和輸出值、 存儲來自以前的反覆運算,值和存儲草稿計算。 中的大圖圖 3 包含您需要知道,瞭解如何程式設計反向傳播的所有資訊。 您最初的反應圖 3 是可能的大意是"忘了它 — — 這是太複雜了。"掛在那裡。 反向傳播不是微不足道的但一旦你理解了圖中您可以執行反向傳播使用任何程式設計語言。

The Back-Propagation Algorithm
圖 3 的反向傳播演算法

圖 3 有主的投入和產出的圖中,但也幾個地方輸入和輸出值的出現在關係圖中的內部邊緣時。 不應低估困難的編碼一種神經網路和需要不斷的名稱和所有這些投入和產出明確的含義。 圖根據我的經驗,喜歡在一個圖 3 是絕對必要的。

15 陣列中概述的神經網路定義中使用的第一個五圖 2 處理輸入隱藏圖層,並有:

public class NeuralNetwork
{
  // Declare numInput, numHidden, numOutput
  private double[] inputs;
  private double[][] ihWeights;
  private double[] ihSums;
  private double[] ihBiases;
  private double[] ihOutputs;
...

第一個陣列,命名的投入,保存輸入的數值。 這些值通常直接來自一些正常化的資料來源,如文本的檔。 神經網路建構函式具現化的投入:

this.inputs = new double[numInput];

陣列 ihWeights (隱藏輸入權重) 是一個虛擬的二維陣列,陣列的陣列作為實現。 第一個索引表示輸入的神經元,第二個索引表示隱藏的神經元。 陣列是由作為建構函式具現化:

this.ihWeights = Helpers.MakeMatrix(numInput, numHidden);

在這裡,傭工是説明簡化神經網路類的靜態方法的實用程式類:

public static double[][] MakeMatrix(int rows, int cols)
{
  double[][] result = new double[rows][];
  for (int i = 0; i < rows; ++i)
    result[i] = new double[cols];
  return result;
}

陣列 ihSums 是一個暫存的陣列,用於保存在 ComputeOutputs 方法中的中間計算。 陣列中包含的值,將成為隱藏神經元當地投入作為具現化:

this.ihSums = new double[numHidden];

陣列 ihBiases 持有的隱藏的神經元的偏差值。 神經網路重量值是應用與本地輸入值與其相乘的常量。 偏差值添加到中間的總和,以產生本地輸出值,成為下一層的地方輸入。 陣列 ihBiases 作為具現化:

this.ihBiases = new double[numHidden];

陣列 ihOutputs 保存從隱藏層神經元 (,成為對輸出層的投入) 排放的值。

神經網路類中的四個陣列舉行有關隱藏輸出層的值:

private double[][] hoWeights;
private double[] hoSums;
private double[] hoBiases;
private double[] outputs;

這些四個數組作為建構函式中具現化:

this.hoWeights = Helpers.MakeMatrix(numHidden, numOutput);
this.hoSums = new double[numOutput];
this.hoBiases = new double[numOutput];
this.outputs = new double[numOutput];

神經網路類有直接相關的反向傳播演算法的六個陣列。 第一次兩個數組保存值輸出和隱藏層神經元的要求漸變。 漸變是間接地描述如何遠距、 值和朝什麼方向 (正或負),本地的產出是相對於目標輸出。 漸變值用於計算添加到當前的重量和偏差值,以產生新的、 更好的權重和偏見的增量值。 有一個漸變值的每個隱藏層神經元和每個輸出層神經元。 陣列聲明為:

private double[] oGrads; // Output gradients
private double[] hGrads; // Hidden gradients

陣列作為建構函式中具現化:

this.oGrads = new double[numOutput];
this.hGrads = new double[numHidden];

類神經網路中的最後四個陣列舉行培訓迴圈的以前反覆運算增量 (不能使用漸變)。 如果您使用的動力機制防止反向傳播非收斂性需要這些以前的三角洲。 我認為勢頭必不可少的但如果您決定不實施勢頭則可以省略這些陣列。 他們都聲明為:

private double[][] ihPrevWeightsDelta;  // For momentum
private double[] ihPrevBiasesDelta;
private double[][] hoPrevWeightsDelta;
private double[] hoPrevBiasesDelta;

這些陣列作為具現化:

ihPrevWeightsDelta = Helpers.MakeMatrix(numInput, numHidden);
ihPrevBiasesDelta = new double[numHidden];
hoPrevWeightsDelta = Helpers.MakeMatrix(numHidden, numOutput);
hoPrevBiasesDelta = new double[numOutput];

計算產出

在所示的培訓迴圈中的每個反覆運算圖 1 有兩個部分。 在第一部分中,產出的計算使用當前主要投入、 重量和偏見。 在第二部分,反向傳播用於修改的權重和偏見。 中的圖圖 3 闡釋了培訓過程的兩個部分。

使用從左至右,投入 x 0,x 1 和 x 2 分配 1.0、 2.0 和 3.0 的價值觀。 這些主要的輸入的值進入輸入層神經元,而無需修改發出。 雖然輸入層神經元可以修改他們的輸入,例如正常化的值可在一定範圍內,但在大多數情況下這種進行處理外部。 因此,神經網狀圖經常使用矩形或正方形框輸入神經元表明他們不處理同一意義上的隱藏層和輸出層神經元的神經元。 此外,這會影響使用的術語。 在某些情況下,所示的神經網路圖3將稱為三層網路,但因為輸入的層並不執行處理、 顯示的神經網路有時稱為兩層網路。

下一步,隱藏層神經元的每個計算本地輸入和當地的輸出。 最底層的隱藏的神經元,帶有索引 [3],例如,計算作為其空閒 sum (1.0)(0.4)+(2.0)(0.8)+(3.0)(1.2) = 5.6。 暫存總和是總和的將三個輸入時間關聯的輸入隱藏重量的產品。 每個箭頭之上的值是權重。 下一步,偏差值,-7.0,添加到暫存總和產量本地輸入值的 5.6 + (-7.0) =-1.40。 然後隱藏輸入啟動功能應用於此中間的輸入值,從而產生神經元的本地輸出值。 在這種情況下,啟動函數是乙狀結腸函數,因此本地輸出為 1 / (1 + exp(-(-1.40))) = 0.20。

輸出層神經元同樣計算的輸入和輸出。 例如,在圖 3,與索引 [1] 的最底層輸出層神經元計算作為其空閒的總和 (0.86)(1.4)+(0.17)(1.6)+(0.98)(1.8)+(0.20)(2.0) = 3.73。 關聯的偏見被添加給本地輸入:3.73 + (-5.0) = -1.37. 啟動函數應用給主輸出:tanh(-1.37) =-0.88。 如果您檢查代碼為 ComputeOutputs,您將看到該方法計算產出,完全按照我剛才描述。

反向傳播

儘管背後的反向傳播理論數學是相當複雜的一旦你知道這些數學結果是什麼,執行反向傳播並不太難。 反向傳播啟動工作從右至左顯示在關係圖中的圖 3。 第一步是計算每個輸出層神經元的漸變值。 記得漸變是具有程度和方向的錯誤有關的資訊的值。 輸出層神經元的梯度從隱藏層神經元漸變以不同的方式計算。

輸出層神經元的漸變等於 (需要) 的目標值減去計算所得的輸出值,在計算的輸出值計算的輸出層啟動函數的微積分導數倍。 例如,漸變值中的最底層輸出層神經元圖 3,與索引 [1],被計算為:

(0.75 – (-0.88)) * (1 – (-0.88)) * (1 + (-0.88)) = 0.37   

0.75 是所需的值。 -0.88 是從向前傳遞計算的計算所得的輸出值。 記得在此示例中的輸出層啟動功能是雙曲正切函數。 Tanh(x) 微積分導數等於 (1-tanh(x)) * (1 + tanh(x))。 數學分析是有點棘手,但最終,計算輸出層神經元的漸變受到此處所述的公式。

隱藏層神經元的漸變等於微積分衍生啟動功能的計算時間的時間為其關聯的隱藏輸出權重的主要產出的產品總和神經元的本地輸出的隱藏圖層。 例如,在圖 3,最底層隱藏層神經元與索引 [3] 的漸變是:

(0.20)(1 – 0.20) * [ (-0.76)(1.9) + (0.37)(2.0) ] = -0.03

如果我們調用乙狀結腸函數 g(x),事實證明乙狀結腸函數微積分導數等於 g(x) * (1-g(x))。 本示例隱藏輸入啟動函數使用乙狀結腸函數的召回。 在這裡 0.20 是神經元的本地輸出。 -0.76 和 0.37 是梯度的輸出層神經元,和 1.9 和 2.0 是使用兩個輸出層漸變相關的隱藏輸出權重。

計算的重量和偏見的增量

所有輸出層漸變和隱藏層漸變的都計算後,反向傳播演算法的下一步是要使用的漸變的值來計算每個重量和偏差值的增量值。 與不同的漸變,從右至左都必須計算,可以按任何順序計算的增量值。 任何重量或偏見的增量值等於 eta 倍漸變關聯的重量或偏見,時間的重量或偏見與關聯的輸入值。 例如,向隱藏神經元 [3] 從輸入神經元 [2] 隱藏輸入重量的三角洲值是:

三角洲 i h 重量 [2] [3] = eta * 隱藏的漸變 [3] * 輸入 [2]

= 0.90 * (-0.11) * 3.0

= -0.297

0.90 是 eta,控制如何快速反向傳播學習。Eta 的值越大,生成三角洲的過度調整好的回答風險的較大變化。0.11 值是隱藏神經元 [3] 的漸變。3.0 值輸入神經元 [2] 的輸入的值。在關係圖中的圖 3,如果體重箭頭從一個神經元表示,到另一個,來計算特定的重量,三角洲您使用指向右邊的神經元的漸變值和指出,從左側的神經元的輸入的值。

當計算偏差值增量,注意偏差值只添加到中間的總和,因為他們有沒有關聯的輸入的值。所以,來計算偏差值三角洲可以完全省略輸入的值一詞或使用偽 1.0 值作為一種形式的文檔。例如,在圖 3,最底層的隱藏層偏了-7.0 的值。增量來的偏差值是:

0.90 * 梯度的神經元指出 * 1.0

= 0.90 * (-0.11) * 1.0

= 0.099

添加一個勢頭術語

計算過所有的重量和偏見三角洲值後,就可以更新每個重量和偏見,只需添加關聯的增量值。然而,與神經網路的經驗表明某些資料集的反向傳播演算法可以擺動,一再過度,然後一團糟的目標值,永遠不會彙聚到最後一組的重量和偏差的估計。為減少這種趨勢的一個技術是將添加到每個新的重量和偏壓的額外的專業術語叫做勢頭。重量 (或偏見) 的勢頭是只是一些較小的值 (如演示計畫在 0.4) 有時候重量以前三角洲的價值。使用勢頭向反向傳播演算法添加少量的複雜性,因為必須存儲的以前的增量值。為什麼這種技術可防止振盪的背後數學是微妙的但結果非常簡單。

概括而言,以更新使用反向傳播,重量 (或偏見) 的第一步是計算所有輸出層神經元的漸變。第二步是計算所有隱藏的圖層神經元的漸變。第三步是計算使用學習率 eta 的所有字體粗細的三角洲。第四步是添加到每個重量的增量。第五步是將動量項添加到每個重量。

用 Visual Studio 2012 編碼

反向傳播本文的示例代碼,以及提出的解釋應該給你足夠的資訊來瞭解和代碼的反向傳播演算法。反向傳播只是可以用來估計的最佳重量和一組資料的偏差值的幾種技術中的一種。反向傳播與粒子群優化和進化優化演算法等替代相比,往往要更快。但反向傳播有缺點。它不能用使用非可微啟動功能的神經網路。確定為學習好值率和動量參數是更多的藝術,而不是科學和可能非常耗時。

有幾個重要的主題,這篇文章並不解決,特別是如何處理多個目標的資料項目目。我會解釋這一概念和其他神經網路技術在未來文章。

當我編碼的這篇文章的演示程式時,我使用 Visual Studio 2012 的 Beta 版本。即使很多 Visual Studio 2012 中的新功能涉及 Windows 8 的應用程式,我想要看到 Visual Studio 2012 如何處理好舊的主控台應用程式。驚喜驚訝我任何 Visual Studio 2012 中的新功能,令人討厭並不感到驚訝。我過渡到 Visual Studio 2012 非常輕鬆。雖然我沒有做出使用 Visual Studio 2012 年新的非同步功能,它本來有用時計算每個重量和偏見的所有增量值。我嘗試了新的調用層次結構功能,發現它有用和直觀。Visual Studio 2012 我初步印象是有利的並儘快我能,我計畫過渡到它。

Dr. James McCaffrey 為伏資訊科學 Inc.,他在管理軟體工程師工作在華盛頓雷德蒙的微軟校園的技術培訓工作。他曾對幾個 Microsoft 產品包括互聯網資源管理器和 MSN 搜索。他的".NET 測試自動化食譜"(Apress,2006年),作者,可以達成 jammc@microsoft.com

由於以下的技術專家對本文的審閱: Dan Liebling