2018 年 3 月

第 33 卷,第 3 期

本文章是由機器翻譯。

測試回合 - 使用 CNTK 的類神經二進位分類

James McCaffrey

James McCaffrey二進位分類問題的目標是進行預測,而所要預測的值可從兩個可能的值中選出。例如,您可能想要預測醫院病患是否核心疾病,根據預測變數,例如年齡、 性別捐血不足的壓力,依此類推。有許多可用來處理二元分類問題的技術。在本文中,我將說明如何使用 Microsoft 認知 Toolkit (CNTK) 程式庫建立類神經網路二元分類模型。

看看圖 1查看這篇文章方向的位置。示範程式會建立克里夫蘭核心疾病資料集的預測模型。資料集有 297 項目。每個項目有 13 預測變數而定: 年齡、 性別、 痛苦型別、 捐血壓力、 cholesterol、 捐血 sugar ECG 核心速率、 angina、 ST depression、 ST 斜率、 容器和 thallium 數目。要預測的值是核心疾病存在。

使用 CNTK 類神經網路的二元分類
圖 1 二元分類使用 CNTK 類神經網路

在幕後,原始的資料已標準化和編碼,導致 18 預測變數而定。示範建立類神經網路 18 輸入的節點、 20 隱藏的處理節點與兩個輸出節點。類神經網路模型是使用隨機梯度下降設 0.005 且迷你批次大小為 10 的學習速率來定型。

在訓練期間,平均遺失/錯誤和平均分類精確度上目前的 10 個項目會顯示每個 500 的反覆項目。您可以看到,在一般情況下,遺失/錯誤逐漸減少以及精確度增加 5,000 次反覆運算。定型之後, 297 資料的所有項目上模型的分類精確度計算出 84.18%(250 正確、 47 不正確)。

本文假設您有中繼或更好的程式設計技術,但是不會假設您了解太多 CNTK 或類神經網路。示範編碼使用 Python,但即使您不知道 Python,您應該能夠依照步驟進行,不需要太多的困難。示範程式的程式碼所示整個本文。使用隨附的下載使用的資料檔案。

了解的資料

有數個版本的克里夫蘭核心疾病資料集bit.ly/2EL9Leo。示範使用已處理的版本中,有 13 的原始 76 預測量變數。未經處理資料 303 的項目,且看起來像:

[001] 63.0,1.0,1.0,145.0,233.0,1.0,2.0,150.0,0.0,2.3,3.0,0.0,6.0,0
[002] 67.0,1.0,4.0,160.0,286.0,0.0,2.0,108.0,1.0,1.5,2.0,3.0,3.0,2
[003] 67.0,1.0,4.0,120.0,229.0,0.0,2.0,129.0,1.0,2.6,2.0,2.0,7.0,1
...
[302] 57.0,0.0,2.0,130.0,236.0,0.0,2.0,174.0,0.0,0.0,2.0,1.0,3.0,1
[303] 38.0,1.0,3.0,138.0,175.0,0.0,0.0,173.0,0.0,0.0,1.0,?,3.0,0

中每一行的第一次 13 值是預測值。每一行中的最後一個項目是介於 0 到 4 之間的值,其中 0 表示沒有核心疾病和 1、 2、 3 或 4 表示核心疾病存在。一般而言,大部分的機器學習案例最耗時的部分正在準備您的資料。有兩個以上的預測量變數,因為它不圖形的未經處理資料。中所示,您可以藉由查看只年齡和捐血不足的壓力,取得概略瞭解問題情況,但是圖 2

克里夫蘭核心疾病部分未經處理資料
圖 2 克里夫蘭核心疾病部分未經處理資料

第一個步驟是處理遺失的資料,請注意"?"項目 [303] 中。有遺漏值只有六個項目,因為這些六個項目已只丟出離開 297 項目。

下一個步驟是將正規化數值的預測值,例如 「 年齡中第一個資料行。示範使用最小值最大值正規化,其中由取代資料行中的值 (值-min) / (max-min)。例如,最短使用期限值為 29 和最大值為 77,即,因此第一個的年齡值 63,會正規化為 (63 29) / (77-29) = 34 / 48 = 0.70833。

下一個步驟是將編碼類別預測量值,例如性別 (0 = female,1 = male) 中的第二個資料行和痛苦類型 (1、 2、 3、 4) 中的第三個資料行。編碼是性別示範使用 1-of-(N-1) 會編碼為女性 =-1,男性 = + 1。痛苦型別會編碼成 1 = (1,0,0)、 2 = 0、 1 (0),3 = (0,0,1)、 4 = (-1,-1,-1)。

最後一個步驟是將編碼要預測的值。當使用二元分類的類神經網路,您可以編碼要預測的值是 0 或 1,使用只有一個節點的值,或者您可以使用兩個節點的值 (0,1) 或 (1,0)。基於很快會使用 CNTK 時,它是使用兩個節點的技巧,比較好。因此,0 (沒有核心疾病) 已編碼為 (0,1) 並且值為 1 到 4 (核心疾病) 已編碼為 (1,0)。

最後的正規化和編碼資料的 tab 鍵分隔,看起來:

|symptoms  0.70833  1  1  0  0  0.48113 ... |disease  0  1
|symptoms  0.79167  1 -1 -1 -1  0.62264 ... |disease  1  0
...

標記"| 徵狀"和"| 疾病 」 已插入,因此 CNTK 資料讀取器物件無法輕鬆地讀取資料。

示範程式

中會顯示完整的範例程式,以節省空間,少數稍加編輯圖 3。已移除所有一般錯誤檢查。使用兩個空格字元,而不是個人的喜好設定,並儲存空間為一般的四個縮排。"\"字元是由 Python 用於行接續符號。

圖 3 示範程式結構

# cleveland_bnn.py
# CNTK 2.3 with Anaconda 4.1.1 (Python 3.5, NumPy 1.11.1)
import numpy as np
import cntk as C
def create_reader(path, input_dim, output_dim, rnd_order, sweeps):
  x_strm = C.io.StreamDef(field='symptoms', shape=input_dim,
   is_sparse=False)
  y_strm = C.io.StreamDef(field='disease', shape=output_dim,
    is_sparse=False)
  streams = C.io.StreamDefs(x_src=x_strm, y_src=y_strm)
  deserial = C.io.CTFDeserializer(path, streams)
  mb_src = C.io.MinibatchSource(deserial, randomize=rnd_order, \
    max_sweeps=sweeps)
  return mb_src
# ===================================================================
def main():
  print("\nBegin binary classification (two-node technique) \n")
  print("Using CNTK version = " + str(C.__version__) + "\n")
  input_dim = 18
  hidden_dim = 20
  output_dim = 2
  train_file = ".\\Data\\cleveland_cntk_twonode.txt"
  # 1. create network
  X = C.ops.input_variable(input_dim, np.float32)
  Y = C.ops.input_variable(output_dim, np.float32)
  print("Creating a 18-20-2 tanh-softmax NN ")
  with C.layers.default_options(init=C.initializer.uniform(scale=0.01,\
    seed=1)):
    hLayer = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
      name='hidLayer')(X) 
    oLayer = C.layers.Dense(output_dim, activation=None,
     name='outLayer')(hLayer)
  nnet = oLayer
  model = C.ops.softmax(nnet)
  # 2. create learner and trainer
  print("Creating a cross entropy batch=10 SGD LR=0.005 Trainer ")
  tr_loss = C.cross_entropy_with_softmax(nnet, Y)
  tr_clas = C.classification_error(nnet, Y)
  max_iter = 5000
  batch_size = 10
  learn_rate = 0.005
  learner = C.sgd(nnet.parameters, learn_rate)
  trainer = C.Trainer(nnet, (tr_loss, tr_clas), [learner])
  # 3. create reader for train data
  rdr = create_reader(train_file, input_dim, output_dim,
    rnd_order=True, sweeps=C.io.INFINITELY_REPEAT)
  heart_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  # 4. train
  print("\nStarting training \n")
  for i in range(0, max_iter):
    curr_batch = rdr.next_minibatch(batch_size, \
      input_map=heart_input_map)
    trainer.train_minibatch(curr_batch)
    if i % int(max_iter/10) == 0:
      mcee = trainer.previous_minibatch_loss_average
      macc = (1.0 - trainer.previous_minibatch_evaluation_average) \
        * 100
      print("batch %4d: mean loss = %0.4f, accuracy = %0.2f%% " \
        % (i, mcee, macc))
  print("\nTraining complete")
  # 5. evaluate model using all data
  print("\nEvaluating accuracy using built-in test_minibatch() \n")
  rdr = create_reader(train_file, input_dim, output_dim,
    rnd_order=False, sweeps=1)
  heart_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  num_test = 297
  all_test = rdr.next_minibatch(num_test, input_map=heart_input_map)
  acc = (1.0 - trainer.test_minibatch(all_test)) * 100
  print("Classification accuracy on the %d data items = %0.2f%%" \
    % (num_test,acc))
  # (could save model here)
  # (use trained model to make prediction)
  print("\nEnd Cleveland Heart Disease classification ")
# ===================================================================
if __name__ == "__main__":
  main()

Cleveland_bnn.py 示範都有一個 helper 函數,create_reader。所有控制邏輯都是在單一的 main 函式。由於 CNTK 是年輕及加強開發,最好先新增註解詳述正在使用哪一個版本 (在此情況下 2.3)。

安裝 CNTK 可能很有點困難。首先,您可以安裝 Anaconda 發佈的 Python,其中包含所需的 Python 解譯器,所需 SciPy、 NumPy 等的封裝和有用的公用程式,例如 pip。我使用 Anaconda3 4.1.1 64 位元,其中包含 Python 3.5。在安裝之後 Anaconda,您安裝 CNTK 為 Python 封裝,而不是在獨立系統上,使用 pip 公用程式。從一般的殼層,我所用的命令為:

>pip install https://cntk.ai/PythonWheel/CPU-Only/cntk-2.3-cp35-cp35m-win_amd64.whl

幾乎所有的 CNTK 安裝失敗,我看到了已因為 Anaconda CNTK 版本不相容。

示範一開始會準備建立類神經網路:

input_dim = 18
hidden_dim = 20
output_dim = 2
train_file = ".\\Data\\cleveland_cntk_twonode.txt"
X = C.ops.input_variable(input_dim, np.float32)
Y = C.ops.input_variable(output_dim, np.float32)

輸入和輸出的節點數目取決於您的資料,但隱藏的處理節點數目是可用的參數,而且必須由試驗。使用 32 位元變數是常見的類神經網路,因為使用 64 位元模式而獲得的有效位數不值得會降低效能所造成。

建立網路就像這樣:

with C.layers.default_options(init=C.initializer.uniform(scale=0.01,\
  seed=1)):
  hLayer = C.layers.Dense(hidden_dim, activation=C.ops.tanh,
    name='hidLayer')(X) 
  oLayer = C.layers.Dense(output_dim, activation=None,
   name='outLayer')(hLayer)
nnet = oLayer
model = C.ops.softmax(nnet)

陳述式與 Python 是一組通用的引數套用至多個函式的語法捷徑。示範 tanh 啟動節點上使用隱藏的層。一般替代方式是,sigmoid 函數。請注意,套用至輸出節點未啟動。這是實際的 CNTK,因為 CNTK 訓練函式必須要有原始、 未啟動的值。Nnet 物件是只是方便別名。模型物件有 softmax 啟動,因此它可用於定型之後進行預測。Python 指派所參考,因為定型 nnet 物件也培訓模式物件。

定型類神經網路

類神經網路會用於定型的準備:

tr_loss = C.cross_entropy_with_softmax(nnet, Y)
tr_clas = C.classification_error(nnet, Y)
max_iter = 5000
batch_size = 10
learn_rate = 0.005
learner = C.sgd(nnet.parameters, learn_rate)
trainer = C.Trainer(nnet, (tr_loss, tr_clas), [learner])

Tr_loss (「 定型遺失 」) 物件會告知 CNTK 如何測量定型時的錯誤。交叉 entropy softmax 與替代方式是平方的誤差。Tr_clas (「 定型分類錯誤 」) 物件可以用來自動計算不正確預測的百分比,期間或之後的訓練。

反覆定型的定型時間和學習速率,在批次中的項目數的最大數目的值都必須由試驗的所有可用參數。您可以將學習模組物件做為演算法和訓練物件做為尋找的類神經網路的加權和徵才偏差良好的值會使用學習模組的物件。

使用這些陳述式,建立讀取器物件:

rdr = create_reader(train_file, input_dim, output_dim,
  rnd_order=True, sweeps=C.io.INFINITELY_REPEAT)
heart_input_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}

如果您檢查中的 create_reader 定義圖 3,您會看到它指定資料檔中所使用的標記名稱 (< 症狀 > 和 < 疾病")。您可以考慮 create_reader 和類神經二元分類問題的未定案程式碼以建立讀取器物件的程式碼。您必須變更均標記名稱以及對應字典 (heart_input_map) 的名稱。

定型的所有項目已備妥之後,會執行如下所示:

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

具有固定數目的反覆項目訓練的替代方式是遺失/錯誤低於閾值時停止定型。請務必在定型期間顯示遺失/錯誤,因為定型失敗規則,而不是例外狀況。交叉 entropy 錯誤有點難解譯直接,但您想要查看會變得較小的值。而不是顯示平均分類遺失/錯誤,示範計算,並會列印平均分類精確度,也就是更自然我認為度量。

評估與使用模型

網路已培訓之後,您通常要判斷用於定型的整個資料集的遺失/錯誤及分類精確度。示範計算的值與整體的分類精確度:

rdr = create_reader(train_file, input_dim, output_dim,
  rnd_order=False, sweeps=1)
heart_input_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}
num_test = 297
all_test = rdr.next_minibatch(num_test, input_map=heart_input_map)
acc = (1.0 - trainer.test_minibatch(all_test)) * 100
print("Classification accuracy on the %d data items = %0.2f%%" \
  % (num_test,acc))

建立新的資料讀取器。請注意不同於讀取器,用於定型,新的讀取器將不會周遊隨機順序的資料,並清除數目已設為 1。Heart_input_map 字典物件會重新建立。常見的錯誤是嘗試使用原始的物件,但 rdr 物件已變更,因此您需要重新建立對應。Test_minibatch 函式會傳回其迷你批次的引數,在此案例中為整個資料集的平均分類錯誤。

示範程式不會計算整個資料集遺失/錯誤。您可以使用 previous_minibatch_loss_average 函式,但您一定要小心,不要執行額外的定型反覆項目,會變更的網路。

定型,或在定型期間,您通常要儲存模型。在 CNTK,節約看起來像:

mdl_name = ".\\Models\\cleveland_bnn.model"
model.save(mdl_name)

這可以節省使用預設 CNTK v2 格式。替代方法是使用開啟的類神經網路 Exchange (ONNX) 格式。請注意,您通常會想要儲存 (使用 softmax 啟用) 的模型物件,而不是 nnet 物件。

從不同的程式,已儲存的模型無法載入記憶體中的行沿著:

mdl_name = ".\\Models\\cleveland_bnn.model"
model = C.ops.functions.Function.load(mdl_name)

載入之後, 模型可以使用視為只具有尚未定型。

示範程式不會使用定型的模型來進行預測。您可以撰寫類似的程式碼:

unknown = np.array([0.5555, -1, ... ], dtype=np.float32)
predicted = model.eval(unknown)

傳回至變數來預測的結果會是 1 x 2 矩陣總和為 1.0,例如 [[0.2500,0.7500]] 的值。第二個值較大,因為結果會將對應至 (0,1),其接著會對應至 「 沒有疾病。 」

總結

最深入了解程式碼程式庫執行類神經網路使用的單一節點技巧的二元分類。使用這個方法,來預測變數的值會編碼為 0 或 1。輸出維度會設定為 1,而不是 2。您必須使用二進位交叉 entropy 錯誤,而不是一般交叉 entropy 錯誤。CNTK 沒有可用於一個節點,因此您必須實作自己的函式,從可用的內建的分類錯誤函式。當訓練、 較少資訊通常獲得上每個反覆項目 (雖然定型會較快的位元),因此您通常必須在定型單一節點模型比使用兩個節點項技術的多個反覆項目。基於這些理由,我願意使用類神經二元分類的兩個節點項技術。


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

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


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