2018 年 2 月

第 33 卷,第 2 期

本文章是由機器翻譯。

機器學習服務 - 使用 CNTK 的深度神經網路分類器

James McCaffrey

Microsoft 認知 Toolkit (CNTK) 程式庫是一組強大的函數,可讓您建立的機器學習 (ML) 的預測系統。我所提供的簡介 2017 年 7 月問題的第 2 版 (msdn.com/magazine/mt784662)。在本文中,我會說明如何使用 CNTK 進行深度類神經網路分類。若要查看在螢幕擷取畫面是好的方法,請參閱這篇文章會向其中圖 1

小麥種子各種預測示範
圖 1 小麥種子各種預測示範

CNTK 程式庫以 c + + 撰寫基於效能考量,但呼叫應用程式庫函式的最常見方式是使用 CNTK Python 語言應用程式開發介面。我叫用示範程式發出下列命令在一般的 Windows 10 中的命令殼層:

> python seeds_dnn.py

示範程式的目標是建立可預測的各種小麥種子的深度類神經網路。在幕後,示範程式會使用整組定型資料看起來像這樣:

|properties 15.26 14.84 ... 5.22 |variety 1 0 0
|properties 14.88 14.57 ... 4.95 |variety 1 0 0
...
|properties 17.63 15.98 ... 6.06 |variety 0 1 0
...
|properties 10.59 12.41 ... 4.79 |variety 0 0 1

定型資料會有 150 項目。每一行代表一個小麥種子的三種:「 Kama,""Rosa 」 或 「 加拿大 」。 在每一行的前七個數值是預測值,通常稱為屬性或功能,在機器學習術語。預測值為種子區域、 周邊、 壓縮度、 長度、 寬度、 為中心不對稱係數和 groove 長度。項目-因為預測 (通常稱為 「 類別 」 或 「 標籤 」) 會填滿的最後三個資料行,並編碼為 1 0 0 Kama,0,1 Rosa,和加拿大的 0 0 1 0。

示範程式也會使用 60 項目,20,每個識別值種子各種不同的測試資料集。測試資料會有相同的定型資料的格式。

示範程式建立 7-(4-4-4) 3 深度類神經網路。網路如下所示圖 2。有七個輸入的節點 (一個用於每個預測量值),三個隱藏的層,每一個都有四個處理節點,而三個輸出對應至三個可能的編碼的小麥種子種類的節點。

深度類神經網路結構
圖 2 深度類神經網路結構

示範程式培訓網路使用 5000 批次的 10 個項目的每個,使用隨機梯度下降 (SGD) 演算法。預測模型已培訓之後,它會套用至 60 個項目的測試資料集。此模型來達成 78.33 百分比精確度,這表示它正確預測 47 60 的測試項目。

示範程式結束時,會使未知的小麥種子的預測。七個輸入的值均為 17.6、 15.9、 0.8、 6.2、 3.5、 4.1 (6.1)。計算原始輸出節點值 1.0530、 2.5276 (-3.6578),而相關聯的輸出節點的機率值 0.1859、 0.8124 (0.0017)。因為最大的中間值,輸出會對應至 (0,1,0) 也就是各種不同 Rosa。

 本文假設您有中繼或更好的程式設計技巧,使用 C 系列的語言和類神經網路基本的認識。但是,不論您的背景,您應該可以跟著沒什麼問題。本文章中會呈現 seeds_dnn.py 程式的完整原始程式碼。程式碼中,與關聯的定型和測試資料的檔案,也會是檔案下載本文章中項目。

安裝 CNTK v2

因為 CNTK v2 是相當新,可能不會熟悉的安裝程序。簡言之,您先安裝 Python 語言通訊群組 (強烈建議您 Anaconda 發佈) 包含核心 Python 語言和必要的 Python 封裝,然後您安裝 CNTK 其他的 Python 封裝。換句話說,CNTK 不是獨立安裝。

在撰寫本文時,目前版本的 CNTK 是 v2.3。因為 CNTK 低於加強程式開發,依時間閱讀本文時,也可能有較新版本。我使用 Anaconda 發佈版本 4.1.1 (其中包含 Python 版本 3.5.2,NumPy 版本 1.11.1 和 SciPy 版本 0.17.1)。安裝之後 Anaconda,我已安裝僅限 CPU CNTK 使用 pip 公用程式版本。安裝 CNTK 可能很困難的位元,如果您隨意版本相容性,但 CNTK 文件說明安裝程序,在詳細資料。

了解的資料

建立大部分的機器學習系統以相當耗時而且需要經常造成困擾程序,設定定型和測試資料檔案的開頭。原始小麥種子資料集,請參閱bit.ly/2idhoRK。原始 210 項目以 tab 分隔資料看起來像這樣:

14.11  14.1   0.8911  5.42  3.302  2.7  5      1
16.63  15.46  0.8747  6.053 3.465  2.04 5.877  1

我已在 CNTK 所處理的格式產生檔案的公用程式。產生的 210 項目檔案看起來像:

|properties 14.1100 14.1000 ... 5.0000 |variety 1 0 0
|properties 16.6300 15.4600 ... 5.8770 |variety 1 0 0

公用程式中加入前置"| 屬性"標記來識別的功能、 位置和"| 各種"標記來識別要預測之類別的位置。未經處理的類別值是 1-的-N 編碼 (有時稱為一個熱編碼),索引標籤已取代單一空格字元,而且所有的預測值已格式化為四個小數位數。

在大部分情況下,您要讓它們全部都面臨相同範圍內正規化數值的預測值。我沒有正規化這項資料,以保持本文比較簡單。兩個常見的正規化形式包括 z-score 正規化和最小值最大值正規化。一般情況下,非示範案例中,您應該正規化您預測的值。

接下來,撰寫 210 項目資料的 CNTK 格式檔案,並接著用於產生名為 seeds_train_data.txt (每個不同的第 50 部) 和 60 項目測試檔名為 seeds_test_data.txt (150 項目定型資料檔案的檔案的另一個公用程式最後一個 20 的每個不同)。

由於有七個預測量變數,並不可行,進行完整的資料圖形。但您可以取得大概知道的資料結構中的部分資料的圖形圖 3。我使用種子周邊和壓縮度預測工具初始值 60 項目測試資料集。

測試資料的部分圖表
圖 3 的測試資料的部分圖表

深度類神經網路範例程式

我可以使用 [記事本] 來撰寫範例程式。我喜歡 [記事本],但大部分的 [我的同事偏好下列其中一個可用的許多絕佳 Python 編輯器。免費 Visual Studio Code 編輯器的 Python 語言增益集是特別好用。會以完整的範例程式的原始程式碼,以節省空間,少數稍加編輯圖 4。請注意,反斜線字元是由 Python 的行接續符號。

圖 4 完成種子分類器的範例程式

# seeds_dnn.py
# classify wheat seed variety
import numpy as np
import cntk as C
def create_reader(path, is_training, input_dim, output_dim):
  strm_x = C.io.StreamDef(field='properties',
    shape=input_dim, is_sparse=False)
  strm_y = C.io.StreamDef(field='variety',
    shape=output_dim, is_sparse=False)
  streams = C.io.StreamDefs(x_src=strm_x,
    y_src=strm_y)
  deserial = C.io.CTFDeserializer(path, streams)
  sweeps = C.io.INFINITELY_REPEAT if is_training else 1
  mb_source = C.io.MinibatchSource(deserial,
    randomize=is_training, max_sweeps=sweeps)
  return mb_source
def main():
  print("\nBegin wheat seed classification demo  \n")
  print("Using CNTK verson = " + str(C.__version__) + "\n")
  input_dim = 7
  hidden_dim = 4
  output_dim = 3
  train_file = ".\\Data\\seeds_train_data.txt"
  test_file = ".\\Data\\seeds_test_data.txt"
  # 1. create network and model
  X = C.ops.input_variable(input_dim, np.float32)
  Y = C.ops.input_variable(output_dim, np.float32)
  print("Creating a 7-(4-4-4)-3 tanh softmax NN for seed data ")
  with C.layers.default_options(init= \
    C.initializer.normal(scale=0.1, seed=2)):
    h1 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer1')(X)
    h2 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer2')(h1)
    h3 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer3')(h2)
    oLayer = C.layers.Dense(output_dim, activation=None,
      name='outLayer')(h3)
  nnet = oLayer
  model = C.softmax(nnet)
  # 2. create learner and trainer
  print("Creating a cross entropy, SGD with LR=0.01, \
    batch=10 Trainer \n")
  tr_loss = C.cross_entropy_with_softmax(nnet, Y)
  tr_clas = C.classification_error(nnet, Y)
  learn_rate = 0.01
  learner = C.sgd(nnet.parameters, learn_rate)
  trainer = C.Trainer(nnet, (tr_loss, tr_clas), [learner])
  max_iter = 5000  # maximum training iterations
  batch_size = 10   # mini-batch size
  # 3. create data reader
  rdr = create_reader(train_file, True, input_dim,
    output_dim)
  my_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  # 4. train
  print("Starting training \n")
  for i in range(0, max_iter):
    curr_batch = rdr.next_minibatch(batch_size,
      input_map=my_input_map)
    trainer.train_minibatch(curr_batch)
    if i % 1000 == 0:
      mcee = trainer.previous_minibatch_loss_average
      pmea = trainer.previous_minibatch_evaluation_average
      macc = (1.0 - pmea) * 100
      print("batch %6d: mean loss = %0.4f, \
        mean accuracy = %0.2f%% " % (i,mcee, macc))
  print("\nTraining complete")
  # 5. evaluate model on the test data
  print("\nEvaluating test data \n")
  rdr = create_reader(test_file, False, input_dim, output_dim)
  my_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  numTest = 60
  allTest = rdr.next_minibatch(numTest, input_map=my_input_map)
  acc = (1.0 - trainer.test_minibatch(allTest)) * 100
  print("Classification accuracy on the \
    60 test items = %0.2f%%" % acc)
  # (could save model here)
  # 6. use trained model to make prediction
  np.set_printoptions(precision = 4)
  unknown = np.array([[17.6, 15.9, 0.8, 6.2, 3.5, 4.1, 6.1]],
    dtype=np.float32)
  print("\nPredicting variety for (non-normalized) seed features: ")
  print(unknown[0])
  raw_out = nnet.eval({X: unknown})
  print("\nRaw output values are: ")
  for i in range(len(raw_out[0])):
    print("%0.4f " % raw_out[0][i])
  pred_prob = model.eval({X: unknown})
  print("\nPrediction probabilities are: ")
  for i in range(len(pred_prob[0])):
    print("%0.4f " % pred_prob[0][i])
  print("\nEnd demo \n ")
# main()
if __name__ == "__main__":
  main()

這個示範開始匯入必要的 NumPy 和 CNTK 封裝,並將 np 和 C 的快顯別名指派給它們。函式 create_reader 是程式定義的 helper,可以用來讀取定型資料 (如果 is_training 參數設定為 True) 或測試資料 (如果 is_training 設為 False)。

您可以考慮 create_reader 函式為類神經分類問題中的未定案程式碼。您必須在大部分情況下變更唯一事情就會欄位中的引數呼叫 StreamDef 函式、 「 屬性 」 和 「 種類 」 的示範兩個字串值。

所有程式控制邏輯被都包含在單一的 main 函式。清除所有一般的錯誤檢查程式碼移除了保持此示範的大小,並讓主要概念。請注意,縮排兩個空格,而不是更一般的四個空格,以節省空間。

建立網路和模型

Main 函式一開始會設定類神經網路架構維度:

def main():
  print("Begin wheat seed classification demo")
  print("Using CNTK verson = " + str(C.__version__) )
  input_dim = 7
  hidden_dim = 4
  output_dim = 3
...

因為 CNTK 低於快速開發,所以最好先列印或註解所使用的版本。示範具有三個隱藏的層,其中具有四個節點。必須試驗來判斷隱藏層,數目和每個圖層中的節點數目。如果您想在每個圖層中有不同的節點數目。例如,hidden_dim = [10,8,10,12] 就會分別對應至四個隱藏層,其中包含 10,8,10,12 節點的深層的網路。

接下來,指定定型和測試資料檔案的位置,及網路輸入和輸出向量所建立:

train_file = ".\\Data\\seeds_train_data.txt"
test_file = ".\\Data\\seeds_test_data.txt"
# 1. create network and model
X = C.ops.input_variable(input_dim, np.float32)
Y = C.ops.input_variable(output_dim, np.float32)

請注意,我將放在定型和測試檔案在不同的資料子目錄,這是常見的作法,因為您經常會在模型建立期間有許多不同的資料檔案。使用 np.float32 資料類型為比 np.float64 型別更多的常見因為其他有效位數獲得使用 64 位元,通常並不需要您所帶來的效能負面影響。

接下來,建立網路:

print("Creating a 7-(4-4-4)-3 NN for seed data ")
with C.layers.default_options(init= \
  C.initializer.normal(scale=0.1, seed=2)):
  h1 = C.layers.Dense(hidden_dim,
    activation=C.ops.tanh, name='hidLayer1')(X)
  h2 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
    name='hidLayer2')(h1)
  h3 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
    name='hidLayer3')(h2)
  oLayer = C.layers.Dense(output_dim, activation=None,
    name='outLayer')(h3)
nnet = oLayer
model = C.softmax(nnet)

還有許多事。陳述式與 Python 是一組常見的值套用至網路的多個圖層的快顯語法。此處,所有的加權會指定高斯 (鐘型曲線) 隨機值的平均值為 0 與 0.1 的標準差。設定一個初始值,以確保能。CNTK 支援大量的初始化演算法,包括 「"統一,"glorot,""he"和"xavier。 」 深度類神經網路通常是初始化演算法的選擇出乎意料區分的因此定型失敗,第一個項目,可以嘗試的其中一個時,替代初始化演算法。

三個隱藏的層被定義密集函式,因此如此命名,因為每個節點都會完全連接到圖層中的節點之前和之後。使用的語法可能會造成混淆。以下是做為輸入隱藏的層 h1 X。H1 層做為輸入到隱藏的層 h2,依此類推。

請注意輸出層會使用任何啟用的功能,因此輸出節點會有不一定等於 1 的值。如果您有其他的類神經網路程式庫的經驗,這需要說明。與許多其他神經程式庫會使用 softmax 啟用輸出圖層上讓該輸出值永遠為 1 的總和,並可解譯為機率。然後,在訓練期間,您會使用交叉 entropy 錯誤 (也稱為記錄檔遺失),這需要一組值加總為 1。

但有些出乎意料,CNTK v2.3 沒有用於定型的基本交叉 entropy 錯誤函式。相反地,CNTK 有交叉 entropy softmax 函式。這表示,在訓練期間,輸出節點值會轉換至機率來計算錯誤詞彙使用 softmax 即時。

因此,CNTK,訓練深層網路上未經處理的輸出節點的值,但進行預測時,如果您想預測機率如通常是如此,您必須明確地套用 softmax 函式。此示範所用的方法是訓練 」 nnet"物件 (在輸出層中沒有啟用),但建立具有 softmax 套用,以進行預測時使用的其他 「 模型 」 的物件。

現在,它是,事實上,能夠在輸出層中,使用 softmax 啟動,然後使用交叉 entropy softmax 在定型期間。這個方法會導致 softmax 正在兩次,先套用至未經處理的輸出值,然後再正規化的輸出節點值。結果顯示,雖然這種方法,而不是複雜的技術原因,訓練不是更有效率。

鏈結在隱藏的層是可行的為止。若為非常深入的網路,CNTK 可支援名為提供的快顯語法建立多層的網路的循序中繼函數。CNTK 程式庫也有中輟函式,可用來協助防止模型過度配適。例如,若要新增的第一個隱藏層中輟,您可以修改示範程式碼如下:

h1 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
  name='hidLayer1')(X)
d1 = C.layers.Dropout(0.50, name='drop1')(h1)
h2 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
  name='hidLayer2')(d1)
h3 = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
  name='hidLayer3')(h2)
oLayer = C.layers.Dense(output_dim, activation=None,
  name='outLayer')(h3)

我的同事的許多想要永遠使用循序,即使只有幾個隱藏的層的深度類神經網路。我想手動鏈結,但這只需樣式。

定型網路

建立類神經網路和模型之後,範例程式會建立學習模組物件和訓練物件:

print("Creating a Trainer \n")
tr_loss = C.cross_entropy_with_softmax(nnet, Y)
tr_clas = C.classification_error(nnet, Y)
learn_rate = 0.01
learner = C.sgd(nnet.parameters, learn_rate)
trainer = C.Trainer(nnet, (tr_loss, tr_clas), [learner])

您可以將做為演算法的學習因子和訓練為使用學習演算法的物件。Tr_loss (「 定型遺失 」) 物件會定義如何測量網路計算輸出值與定型資料中的已知正確的輸出值之間的錯誤。代表分類,幾乎一律會使用交叉 entropy,但 CNTK 支援多個替代方案。函式名稱的 「 with_softmax"部分表示函式需要原始輸出節點的值而非值正規化的 softmax。這就是為什麼輸出層不會使用啟用函數。

(「 定型的分類錯誤 」) 的 tr_clas 物件定義在定型期間的正確和不正確的預測數目的計算方式。CNTK 定義分類錯誤 (不正確預測的百分比表示) 程式庫函式,而不是由其他程式庫的分類精確度函式。因此,會有兩種形式的錯誤在訓練期間所計算。Tr_loss 錯誤來調整加權和徵才偏差。Tr_clas 用來監視預測精確度。

學習模組物件會使用與常數學習速率設 0.01 SGD 演算法。SGD 是最簡單的定型演算法,但不自動執行。CNTK 支援大型的學習演算法,其中有些是非常複雜許多。為根據經驗法則,建議 SGD 開始,並只想許多外來的演算法,如果定型集失敗。Adam 演算法 (Adam 不縮略字) 通常是我的第二個選擇。

請注意不尋常的語法來建立定型模組物件。Python tuple,以括號,表示會傳遞兩個遺失函式物件,但學習模組物件傳遞為 Python 清單以方括號。您可以多個款將物件傳遞給定型,雖然示範程式會傳遞其中一個。

定型會實際執行程式碼是:

for i in range(0, max_iter):
  curr_batch = rdr.next_minibatch(batch_size,
    input_map=my_input_map)
  trainer.train_minibatch(curr_batch)
  if i % 1000 == 0:
    mcee = trainer.previous_minibatch_loss_average
    pmea = trainer.previous_minibatch_evaluation_average
    macc = (1.0 - pmea) * 100
    print("batch %6d: mean loss = %0.4f, \
      mean accuracy = %0.2f%% " % (i, mcee, macc))

請務必監視訓練進度,因為定型通常會失敗。在這裡,會顯示 10 個定型項目只使用批次的平均交叉 entropy 錯誤每 1000 反覆項目。這個示範顯示我認為是更自然的標準分類錯誤 (不正確預測的百分比表示) 高於平均的分類精確度 (目前的 10 個項目上的正確預測的百分比表示)。

儲存已定型的模型

因為僅有 150 定型項目,則可以在短短幾秒線上訓練示範類神經網路。但是在非示範案例中,定型非常深度類神經網路可能需要數小時、 天或甚至倍。定型之後,您要儲存您的模型,因此您不需要從頭重新定型。儲存及載入定型的 CNTK 模型是很容易。若要儲存,您可以將類似的程式碼加入示範程式:

mdl = ".\\Models\\seed_dnn.model"
model.save(mdl, format=C.ModelFormat.CNTKv2)

第一個引數傳遞至儲存函式是只能是 filename,可能也包含路徑。沒有必要的檔案副檔名,但使用".model 」 有意義。格式參數有預設值 ModelFormat.CNTKv2,因此它可能也已經省略了。另一個方法是使用新開啟的類神經網路交換格式 = ONNX。

前文提過,示範程式中建立 nnet 物件 (包含在輸出上沒有 softmax) 與 (具有 softmax) 的模型物件。您通常會想要將定型的模型,softmax 版本儲存,但如果您想要您可以將儲存的非 softmax 物件。

一旦儲存的模型,可以載入到記憶體就像這樣:

model = C.ops.functions.Function.Load(".\\Models\\seed_dnn.model")

然後模型可以使用如同只具有尚未定型。請注意,有許多程式為中心不對稱的呼叫中儲存和載入 — 儲存函式物件上的方法,而負載從函式類別的靜態方法。

總結

分類的許多問題,可以使用簡單的前饋類神經網路 (FNN) 具有單一隱藏層來處理。理論上,指定做出特定假設,FNN 可以處理深度類神經網路可以處理任何問題。不過,在實務上,有時深度類神經網路是容易比 FNN 定型。這些概念的數學基礎稱為通用近似值理論 (或有時 Cybenko 理論)。

如果您還不熟悉類神經網路分類,您必須進行的決策數目可以看起來令人卻步。您必須決定隱藏層,每個圖層,每個隱藏的層、 定型演算法,以及定型演算法的初始化結構描述和啟用函式中的節點數目的數字參數,例如學習率和動量詞彙。不過,與做法您將快速開發一組法則您處理的問題類型。


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

非常感謝下列 Microsoft 技術專家已檢閱本文章:Chris Lee、 Ricky Loynd、 肯尼斯 Tran


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