2017 年 8 月

第 32 卷,第 8 期

本文章是由機器翻譯。

測試回合 - 使用 C# 的深度類神經網路

James McCaffrey |2017 年 8 月

James McCaffrey許多新的進階功能 (使用資料進行預測) 的機器學習中已知道使用深度類神經網路。範例包括 Microsoft Cortana 和 Apple Siri 語音辨識,並幫助啟用自我驅動汽車的映像辨識。

詞彙深度類神經網路 (DNN) 是一般且有數個特定的變化,包括循環的類神經網路 (RNNs) 和 convolutional 類神經網路 (CNNs)。沒有特殊的名稱,DNN,我說明本文中,最基本形式,因此我會如同 DNN,參考。

本文將介紹 DNNs 因此您必須是具象示範程式試驗,可以幫助您了解文獻 DNNs。我將不會出現程式碼可直接在生產系統中,但該程式碼可以延伸以建立這類系統,會說明。即使您不想要實作 DNN,您可能會為了自己起見,發現它們的運作方式的說明有趣。

DNN 最適合以視覺化方式說明。讓我們看一下 [圖 1],深層的網路有兩個輸入的節點,在左側,val 待 (1.0,2.0)。在右側,0.3269、 0.3333 (0.3398) 的值有三個輸出節點。您可以將 DNN 視為複雜的數學函式通常會接受兩個以上的數字輸入的值,並傳回一個或多個數值的輸出值。

基本的深度類神經網路
圖 1 基本深度類神經網路

顯示 DNN 可能對應到其目標是要預測政治合作對象關係的問題 (Democrat,共和、 其他) 的人員,根據存在時間和收入,會以某種方式進行縮放的輸入的值。如果 Democrat 的編碼方式 (1,0,0) 和共和的編碼方式 (0,1,0) 和其他的編碼方式 (0,0,1),然後在 DNN圖 1預測其他人具有短使用期限 = 1.0 且 income = 2.0,因為最後一個輸出值 (0.3398) 是最大。

一般的類神經網路會有單一隱藏層級的處理節點。DNN 有兩個或多個隱藏的層,而且可以處理的問題很難預測。特定的型別的 DNNs,例如 RNNs 和 CNNs,也會有多個圖層的處理節點,但更複雜連線架構,以及。

在 DNN圖 1已處理節點的三個隱藏的層。第一個隱藏的層有四個節點,第二個和第三個隱藏的層有兩個節點。每個長箭號從左以右代表數值常數,稱為權數。如果節點 zero-base 索引與 [0] 頂端的圖中,則連接 input [0] 的權數為 hid den [0] [0] ([節點 0 中的 [層級 0) 具有值 0.01,連接到隱藏 [0] [3] (層級 0、 節點 3) 輸入 [1] 的加權值 0.08,依此類推。有 26 節點加權值。

每個隱藏八和三個輸出節點有一個代表呼叫偏差的數值常數的小箭號。例如,隱藏 [2] [0] 0.33 的偏差值而且輸出 [1] 具有 0.36 偏差值。在圖表中,標記為 [不是所有的加權和偏差值,但因為介於 0.01 到 0.37 之間連續數值,您可以輕易地判斷非標示為加權或偏差值。

在下列各節,我說明 DNN 輸入輸出機制的運作方式,並示範如何實作它。使用 C# 程式碼示範程式,但如果您想要這樣做,您不應該有重構至另一種語言,例如 Python 或 JavaScript 程式碼太困難。示範程式會呈現在本文中,將整個太長,但完整的程式隨附的程式碼下載中。

示範程式

若要查看這篇文章會向其中的好方法是檢查示範程式中的螢幕擷取畫面圖 2。示範對應於所示 DNN圖 1並說明網路中顯示 13 節點的值輸入輸出機制。示範程式碼產生輸出中顯示的程式碼的開頭圖 3

基本的深度類神經網路示範執行
圖 2 基本深度類神經網路示範執行

圖 3 開始處的輸出產生的程式碼

using System;
namespace DeepNetInputOutput
{
  class DeepInputOutputProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin deep net IO demo");
      Console.WriteLine("Creating a 2-(4-2-2)-3 deep network");
      int numInput = 2;
      int[] numHidden = new int[] { 4, 2, 2 };
      int numOutput = 3;
      DeepNet dn = new DeepNet(numInput, numHidden, numOutput);

請注意,範例程式會使用只有一般的 C# 除了系統未命名空間的。DNN 會建立每個圖層的節點數目傳入 DeepNet 程式定義的類別建構函式。隱藏層,3,數目會以隱含方式的項目數為傳遞 numHidden 陣列中。替代的設計是要明確地傳遞隱藏層的數目。

26 加權和 11 徵才偏差的值會設定如下所示:

int nw = DeepNet.NumWeights(numInput, numHidden, numOutput);
Console.WriteLine("Setting weights and biases to 0.01 to " +
  (nw/100.0).ToString("F2") );
double[] wts = new double[nw];
for (int i = 0; i < wts.Length; ++i)
  wts[i] = (i + 1) * 0.01; 
dn.SetWeights(wts);

使用靜態類別方法 NumWeights 計算的加權和徵才偏差總數。如果您參考回圖 1,您可以發現,每個節點都會連接到右邊的圖層中的所有節點,因為權數的數目 (2 * 4) + (4 * 2) + (2 * 2) + (2 * 3) = 8 + 8 + 4 + 6 = 26。因為沒有一個偏誤觸達隱藏和輸出節點,所以徵才偏差的總數是 4 + 2 + 2 + 3 = 11。

名為 wts 陣列具現化且 37 資料格,然後透過 0.37 0.01 來設定值。這些值會插入到 DeepNet 物件使用 SetWeights 方法。表現出逼真、 非示範 DNN,加權和徵才偏差的值會判斷使用一組輸入的值和已知的正確輸出值有已知的資料。這稱為定型網路。最常見的定型演算法稱為回傳播。

示範程式的 Main 方法於結尾提供:

...
    Console.WriteLine("Computing output for [1.0, 2.0] ");
    double[] xValues = new double[] { 1.0, 2.0 };
    dn.ComputeOutputs(xValues);
    dn.Dump(false); 
    Console.WriteLine("End demo");
    Console.ReadLine();
  } // Main
} // Class Program

方法 ComputeOutputs 接受輸入值的陣列,並使用輸入輸出機制,我會說明,計算並儲存輸出節點的值。傾印 helper 方法,顯示 13 節點的值,以及"false"的引數的方法不會顯示 37 加權和徵才偏差的值。

輸入輸出機制

DNN 的輸入輸出機制最具體範例說明。第一個步驟是使用輸入的節點中的值來計算第一個隱藏層內節點的值。第一個隱藏層中,最高的隱藏節點的值為:
tanh ((1.0)(0.01) + (2.0)(0.05) + 0.27) =
tanh(0.38) = 0.3627

文字的方式,在"計算每個輸入的節點和其相關聯的加權之產品的總和,偏差值,然後新增採取的雙曲正切值的總和。 」 雙曲正切值縮寫 tanh,稱為啟用函數。Tanh 函式接受到正無限大、 負無限大的任何值,並傳回值為-1.0 和 +1.0 之間。這是本文章的範圍外重要替代的啟用函數包括羅吉斯 sigmoid 並加以改正線性 (ReLU) 函數。

在其餘的隱藏層節點的值是以完全相同的方式計算。例如,隱藏 [1] [0] 是:
tanh ((0.3627)(0.09) + (0.3969)(0.11) + (0.4301)(0.13) + (0.4621)(0.15) + 0.31) =
tanh(0.5115) = 0.4711
並隱藏 [2] [0] 是:
tanh ((0.4711)(0.17) + (0.4915)(0.19) + 0.33) =
tanh(0.5035) = 0.4649

使用不同的啟用函式,呼叫 softmax 計算輸出節點的值。初步、 預先啟動產品總和加上偏差步驟是一樣的:
預先啟動輸出 [0] =
(.4649)(0.21) + (0.4801)(0.24) + 0.35 =
0.5628
預先啟動輸出 [1] =
(.4649)(0.22) + (0.4801)(0.25) + 0.36 =
0.5823
預先啟動輸出 [2] =
(.4649)(0.23) + (0.4801)(0.26) + 0.37 =
0.6017

三個的任意值、 x、 y softmax,y 是:
softmax(x) = e ^ x / (e ^ x + e ^ y + e ^ z)
softmax(y) = e ^ y / (e ^ x + e ^ y + e ^ z)
softmax(z) = e ^ z / (e ^ x + e ^ y + e ^ z)

其中 e 為歐拉數,大約 2.718282。因此,在 DNN圖 1,最終輸出的值為:

輸出 [0] = e ^0.5628 / (e ^0.5628 + e ^0.5823 + e ^0.6017) = 0.3269
輸出 [1] = e ^0.5823 / (e ^0.5628 + e ^0.5823 + e ^0.6017) = 0.3333
輸出 [2] = e ^0.6017 / (e ^0.5628 + e ^0.5823 + e ^0.6017) = 0.3398

Softmax 啟用函式的目的是要加到 1.0 的輸出值強制轉型,以便它們可以解譯為機率和類別值的對應。在此範例中,第三個輸出值是最大,因為已編碼 (0,0,1) 為任何類別值會是預測的類別目錄,輸入 = (1.0,2.0)。

實作 DeepNet 類別

若要建立範例程式,已啟動 Visual Studio,選取 C# 主控台應用程式範本並將它命名為 DeepNetInputOutput。我使用 Visual Studio 2015,但示範沒有顯著的.NET 相依性,以便能使用任何版本的 Visual Studio。

載入的範本程式碼,在方案總管] 視窗中之後, 我以滑鼠右鍵按一下 Program.cs 檔案並重新將它命名為更具描述性的 DeepNetInputOutputProgram.cs 而允許 Visual Studio 會自動為我重新命名類別的程式。在編輯器視窗的頂端,我已刪除所有不必要使用陳述式,讓只有一個參考系統命名空間。

我示範 DNN 實作為名為 DeepNet 類別時。類別定義為開頭:

public class DeepNet
{
  public static Random rnd; 
  public int nInput; 
  public int[] nHidden; 
  public int nOutput; 
  public int nLayers; 
...

所有類別成員會都宣告具有公用的領域,為了簡單起見。靜態隨機物件成員,名為 rnd DeepNet 類別用來初始化加權和徵才偏差以較小的隨機值 (這會覆寫值 0.01 到 0.37)。成員 nInput 和 nOuput 是輸入和輸出的節點數目。讓隱藏層的數目由長度屬性指定的陣列,就會儲存至方便成員 nLayers 陣列成員 hHidden 會保存在每個隱藏層中,節點的數目。類別定義會繼續:

public double[] iNodes;
public double [][] hNodes;
public double[] oNodes;

深度類神經網路實作會有許多的設計選項。陣列成員 iNodes oNodes 保存輸入和輸出值,如您所預期。陣列的陣列成員 hNodes 保存的隱藏的節點的值。替代的設計是儲存在單一陣列的陣列結構 nnNodes,其中的示範 nnNodes [0] 是輸入的節點值的陣列,而 nnNodes [4] 是一個輸出節點值的陣列中的所有節點。

節點對節點加權會使用這些資料結構來儲存:

public double[][] ihWeights; 
public double[][][] hhWeights;
public double[][] hoWeights;

成員 ihWeights 是保存的輸入要為第一個-隱藏-層加權陣列的陣列樣式矩陣。成員 hoWeights 為陣列的陣列樣式矩陣保存連接到輸出節點的最後一個隱藏的層節點的加權。成員 hhWeights 是陣列,每個資料格會指向陣列的陣列矩陣,持有的隱藏來-隱藏的加權。例如,hhWeights [0] [3] [1] 保存連接到隱藏層 [0 + 1] 中的隱藏節點 [1] 的 [隱藏層 [0] 中的隱藏的節點 [3] 的加權。這些資料結構是 DNN 輸入輸出機制的核心,比較困難。這些概念性圖表所示圖 4

加權和徵才偏差的資料結構
圖 4 加權和徵才偏差的資料結構

最後兩個類別成員保存隱藏的節點徵才的偏差和輸出節點徵才偏差:

public double[][] hBiases;
public double[] oBiases;

我能夠使用任何軟體系統,DNNs 有許多替代資料結構的設計,並讓這些資料結構的草圖務必撰寫輸入輸出程式碼時。

計算加權和徵才偏差的數目

若要設定的加權和徵才偏差值,則需要知道多少加權和有徵才偏差。示範程式會實作靜態方法 NumWeights 計算並傳回這個數字。前文提過 2-(4-2-2) 3 示範網路具有 (2 * 4) + (4 * 2) + (2 * 2) + (2 * 3) = 26 加權和 4 + 2 + 2 + 3 = 11 徵才偏差。索引鍵的程式碼方法 NumWeights,計算隱藏輸入、 隱藏來-隱藏和隱藏-到-輸出加權的數目是:

int ihWts = numInput * numHidden[0];
int hhWts = 0;
for (int j = 0; j < numHidden.Length - 1; ++j) {
  int rows = numHidden[j];
  int cols = numHidden[j + 1];
  hhWts += rows * cols;
}
int hoWts = numHidden[numHidden.Length - 1] * numOutput;

而不是做為沒有 NumWeights 方法傳回的加權和徵才偏差總數,您可能要考慮兩個資料格的整數陣列中的個別傳回加權和徵才偏差的數目。

設定加權和徵才偏差

非示範 DNN 通常會初始化所有的加權和徵才偏差以較小的隨機值。示範程式會設定為 0.01 到 0.26,並以透過使用類別方法 SetWeights 0.37 0.27 徵才偏差的 26 加權。定義為開頭:

public void SetWeights(double[] wts)
{
  int nw = NumWeights(this.nInput, this.nHidden, this.nOutput);
  if (wts.Length != nw)
    throw new Exception("Bad wts[] length in SetWeights()");
  int ptr = 0;
...

輸入的參數 wts 保留值的加權和徵才偏差,,而且都假設擁有正確的長度。變數 ptr wts 陣列中的點。示範程式有很少的錯誤檢查,以保留主要清楚的想法。輸入要為第一個-隱藏-層加權會設定如下所示:

for (int i = 0; i < nInput; ++i) 
  for (int j = 0; j < hNodes[0].Length; ++j) 
    ihWeights[i][j] = wts[ptr++];

接下來,隱藏來-隱藏加權會設定:

for (int h = 0; h < nLayers - 1; ++h) 
  for (int j = 0; j < nHidden[h]; ++j)  // From 
    for (int jj = 0; jj < nHidden[h+1]; ++jj)  // To 
      hhWeights[h][j][jj] = wts[ptr++];

如果您不習慣使用多維陣列,則索引可以是相當困難。務必要加權和徵才偏差的資料結構的圖表 (,效果對我而言,還是)。最後一個隱藏層圖層-到-輸出加權會設定如下:

int hi = this.nLayers - 1;
for (int j = 0; j < this.nHidden[hi]; ++j)
  for (int k = 0; k < this.nOutput; ++k)
    hoWeights[j][k] = wts[ptr++];

此程式碼使用的事實,如果沒有 nLayers 隱藏 (此示範中 3),則索引的最後一個隱藏層是 nLayers-1。SetWeights 方法結束時,會隱藏的節點徵才的偏差和輸出節點徵才偏差設定:

... 
  for (int h = 0; h < nLayers; ++h) 
    for (int j = 0; j < this.nHidden[h]; ++j)
      hBiases[h][j] = wts[ptr++];

  for (int k = 0; k < nOutput; ++k)
    oBiases[k] = wts[ptr++];
}

計算的輸出值

類別方法 ComputeOutputs 的定義為開頭:

public double[] ComputeOutputs(double[] xValues)
{
  for (int i = 0; i < nInput; ++i) 
    iNodes[i] = xValues[i];
...

輸入的值為陣列參數 xValues 中。類別成員 nInput 保存輸入節點的數目,而在類別建構函式中設定。XValues 中的第一個 nInput 值複製到輸入的節點,因此 xValues 都假設擁有至少 nInput 中的第一個資料格的值。接下來,在隱藏的目前值和輸出節點會歸零外:

for (int h = 0; h < nLayers; ++h)
  for (int j = 0; j < nHidden[h]; ++j)
    hNodes[h][j] = 0.0;
 
for (int k = 0; k < nOutput; ++k)
  oNodes[k] = 0.0;

這裡的想法是直接到隱藏和輸出節點中,將會累積,產品詞彙的總和,因此這些節點必須明確地重設為每個方法呼叫為 0.0。替代方法是用於宣告和使用的名稱可能類似 hSums [] 和 [oSums] 區域陣列。接下來,會計算第一個隱藏層內節點的值:

for (int j = 0; j < nHidden[0]; ++j) {
  for (int i = 0; i < nInput; ++i)
    hNodes[0][j] += ihWeights[i][j] * iNodes[i];
  hNodes[0][j] += hBiases[0][j];  // Add the bias
  hNodes[0][j] = Math.Tanh(hNodes[0][j]);  // Activation
}

程式碼是機制的幾乎稍早所述的一對一對應。內建 Math.Tanh 用來隱藏的節點啟動。如前所述,重要的替代方式為羅吉斯 sigmoid 函數和修正線性單元 (ReLU) 函式,將在未來的文章說明。接下來,會計算剩餘的隱藏的層節點:

for (int h = 1; h < nLayers; ++h) {
  for (int j = 0; j < nHidden[h]; ++j) {
    for (int jj = 0; jj < nHidden[h-1]; ++jj) 
      hNodes[h][j] += hhWeights[h-1][jj][j] * hNodes[h-1][jj];
    hNodes[h][j] += hBiases[h][j];
    hNodes[h][j] = Math.Tanh(hNodes[h][j]);
  }
}

這是示範程式,通常是由於多個所需的陣列索引造成最棘手的部分。接下來,輸出節點會計算預先啟動總和---產品:

for (int k = 0; k < nOutput; ++k) {
  for (int j = 0; j < nHidden[nLayers - 1]; ++j)
    oNodes[k] += hoWeights[j][k] * hNodes[nLayers - 1][j];
   oNodes[k] += oBiases[k];  // Add bias 
}

ComputeOutputs 方法結束時,會將 softmax 啟用函式,套用在個別的陣列傳回的輸出計算的值:

...     
  double[] retResult = Softmax(oNodes); 
  for (int k = 0; k < nOutput; ++k)
    oNodes[k] = retResult[k];
  return retResult; 
}

Softmax 方法為靜態 helper。請參閱隨附的程式碼下載的詳細資料。請注意,由於 softmax 啟用要求將會啟動 (於分母詞彙) 的所有值,它是用來計算一次而不是個別的所有 softmax 值更有效率。最後的輸出值會儲存成輸出節點,但也會傳回個別呼叫方便。

總結

已遭龐大的研究活動和許多突破相關深度類神經網路過去幾年。特製化 DNNs,例如 convolutional 類神經網路、 循環的類神經網路、 LSTM 類神經網路和剩餘的類神經網路會非常強大,卻很複雜。在 [我的意見,了解如何基本 DNNs 運作非常重要,有助於了解更複雜的變化。

在未來的文章中,我將說明詳細如何使用定型基本 DNN 後傳播演算法 (公認最知名且重要的演算法在機器學習中)。後傳播或至少某種形式的用於定型大部分 DNN 變化太。這項說明將介紹的概念 vanishing 漸層、 依序將說明設計和許多現在使用非常複雜的預測系統 DNNs 的動機。


Dr。James McCaffrey適用於 Microsoft Research Redmond,Wash.他已投入許多 Microsoft 產品,包括 Internet Explorer 和 Bing。Dr。在可到達 McCaffrey jamccaff@microsoft.com

非常感謝下列 Microsoft 技術專家已檢閱本文章:Li 鄧、 Pingjun Hu、 Po Sen Huang、 Kirk Li、 Alan Liu、 Ricky Loynd、 Baochen Sun,Henrik Turbell。


MSDN Magazine 論壇中的這篇文章的討論