January 2017

Volume 32 Number 1

Machine Learning - Microsoft CNTK Machine Learning Tool の調査

James McCaffrey

Microsoft Computational Network Toolkit (CNTK) とは非常に強力なコマンドライン システムで、これを使ってニューラル ネットワーク予測システムを作成できます。CNTK はもともとマイクロソフト社内で使うために開発されたため、ドキュメントにはやや威圧的な表現があります。今回は、CNTK のインストール、デモ予測問題のセットアップ、ニューラル ネットワーク モデルの作成、予測の実行、そして結果の解釈を順番に見ていきます。

図 1 は、CNTK の考え方と、今回何を目指しているのかをプレビューしています。画像には表示されていませんが、CNTK ツールは以下のコマンドを入力して実行しました。

> cntk.exe configFile=MakeModel.cntk makeMode=false

実行中の CNTK
図 1 実行中の CNTK

画像では、出力メッセージの最後の部分のみが表示されています。拡張子が .cntk の構成ファイルには、入力ファイルに関する情報と、使用するニューラル ネットワークの設計に関する情報を含みます。予測モデルを作成し、ディスクに保存しています。

その後、作成した予測モデルを使って予測を実行します。入力値は NewData.txt ファイルに含み、値は (4.0, 7.0) です。CNTK ツールを MakePrediction.cntk という名前の 2 つ目の構成ファイルと共に使って、予測を計算します。Prediction_txt.pn ファイルに保存された予測結果は、(0.271139, 0.728821, 0.000040) でした。つまり、予測結果は 3 つの候補出力値のうちの 2 つ目になります。

今回は、コマンドライン プログラムの操作とニューラル ネットワークの概略についての知識があることを前提としますが、機械学習 (ML: Machine Learning) の専門知識や、CNTK についての理解は問いません。また、付属のダウンロードからコードとデータを入手できます。

問題の設定

個人の年齢と年収から政治傾向 (保守派、穏健派、リベラル派) を予測するシナリオがあるとします。これは、分類問題と呼ばれます。考え方としては、既知の値を持つデータをいくつか利用して予測モデルを作成します。このモデルはある種複雑な数学関数と考えることができます。この関数は、2 つの数値型の入力値を受け取り、3 つのクラスのいずれかを示す値を出力します。

ここで、図 2 のグラフをご覧ください。グラフには 24 個のデータ点があります。2 つの入力変数 (ML 用語では「フィーチャー」) は、x0 と x1 です。3 つの色は、3 つの異なるクラス (ML 用語では「ラベル」) を示します。人間はこうしたパターンを簡単に理解できますが、コンピューター システムにとっては、この単純なデータ セットでさえ、ここから予測モデルを作成するのは非常に困難です。

トレーニング データ
図 2 トレーニング データ

CNTK が図 2 に示されているデータを使ってニューラル ネットワーク予測モデルを作成した後、そのモデルが図 3 に示す 9 個のテスト データ点に適用されます。ご覧のとおり、CNTK は 9 個のテスト ケースのうち 8 個を正しく予測しています。テスト項目 (8.0, 8.0) は実際には「赤」のクラスですが、間違って「青」のクラスと予測しています。

テスト データ (白丸)
図 3 テスト データ (白丸)

CNTK のインストール

CNTK をインストールするには、GitHub から .zip フォルダーをダウンロードしてそのファイルを展開するだけです。CNTK のメイン ポータル サイトは、github.com/Microsoft/CNTK にあります。そのページで、現在のリリースへのリンクが見つかります (github.com/Microsoft/CNTK/releases)。CNTK の能力にとって重要なことの 1 つは、オプションとして、コンピューターの CPU でなく GPU を使えることです。リリース ページでは、CPU-only のバージョンと、GPU+CPU のバージョンのどちらのバイナリをダウンロードするかを選択できます。

GPU+CPU バージョンでも CPU だけを使えますが、わかりやすいように、CPU-only バージョンを選ぶことをお勧めします。リリース ページで関連リンクをクリックしたときに表示されるページでは、使用許諾契約書に同意する必要があります。そのページで [I accept] (同意する) をクリックすると、CNTK-1-6-Windows-64bit-CPU-Only.zip (当然、バージョン番号は異なる場合があります) のような名前のファイルの保存先を指定するダイアログが表示されます。

.zip ファイルをデスクトップまたは任意のディレクトリにダウンロードします。次に、すべてのファイルを C: ドライブ (最も一般的) または C:\Program Files ディレクトリに直接展開します。

展開したダウンロードには cntk というルート ディレクトリが 1 つ含まれています。そのルート ディレクトリにはいくつかディレクトリが含まれていて、その中には重要な cntk.exe ファイルなど、すべてのバイナリが格納された cntk という別のディレクトリもあります。インストール プロセスを完了するには、システムの PATH 環境変数に cntk.exe ファイルへのパス (通常は C:\cntk\cntk) を追加します。

データ ファイルの作成

CNTK プロジェクトを作成して実行するには、拡張子が .cntk の構成ファイルが 1 つと、トレーニング データを含むファイルが少なくとも 1 つ必要です。ほとんどの CNTK プロジェクトでは、テスト データのファイルも用意します。これに加えて、プロジェクトの実行時に CNTK ツールがいくつか出力ファイルを作成します。

CNTK プロジェクトで使うファイルを整理する方法はたくさんあります。データ ファイルと .cntk 構成ファイルを保持するプロジェクト ルート ディレクトリを作成し、そのディレクトリから CNTK を実行するのがお勧めです。

今回使うコンピューターには既に C:\Data ディレクトリがあったので、そのディレクトリに CNTK_Projects という新しいサブディレクトリをデモ用に作成しました。そのディレクトリ内に、デモ プロジェクトのルート ディレクトリとして機能する SimpleNeuralNet というサブディレクトリを作成し、.cntk ファイル、トレーニング データ ファイル、テスト データ ファイルを格納します。

CNTK システムは、複数の異なる種類のデータ ファイルで機能します。このデモではシンプルなテキスト ファイルを使います。メモ帳のインスタンスを開いて図 4 の 24 個のデータ項目を入力するか、図 2 の情報を使って手動でデータを作成してから、そのファイルを TrainData.txt という名前で SimpleNeuralNet ディレクトリに保存します。

図 4 トレーニング データ

|features 1.0 5.0 |labels 1 0 0
|features 1.0 2.0 |labels 1 0 0
|features 3.0 8.0 |labels 1 0 0
|features 4.0 1.0 |labels 1 0 0
|features 5.0 8.0 |labels 1 0 0
|features 6.0 3.0 |labels 1 0 0
|features 7.0 5.0 |labels 1 0 0
|features 7.0 6.0 |labels 1 0 0
|features 1.0 4.0 |labels 1 0 0
|features 2.0 7.0 |labels 1 0 0
|features 2.0 1.0 |labels 1 0 0
|features 3.0 1.0 |labels 1 0 0
|features 5.0 2.0 |labels 1 0 0
|features 6.0 7.0 |labels 1 0 0
|features 7.0 4.0 |labels 1 0 0
|features 3.0 5.0 |labels 0 1 0
|features 4.0 4.0 |labels 0 1 0
|features 5.0 5.0 |labels 0 1 0
|features 4.0 6.0 |labels 0 1 0
|features 4.0 5.0 |labels 0 1 0
|features 6.0 1.0 |labels 0 0 1
|features 7.0 1.0 |labels 0 0 1
|features 8.0 2.0 |labels 0 0 1
|features 7.0 2.0 |labels 0 0 1
The training data looks like:
|features 1.0 5.0 |labels 1 0 0
|features 1.0 2.0 |labels 1 0 0
. . .
|features 7.0 2.0 |labels 0 0 1

"|features" タグは入力値を示し、"|labels" タグは出力値を示します。"features" も "labels" も予約語ではないため、"|predictors" と "|predicteds" などを使ってもかまいません。値は空白文字やタブ文字を使って区切ることができます (デモ データでは空白文字を使っています)。ニューラル ネットワークは数値しか理解しないので、「赤」や「青」のようなクラス ラベルは数値にエンコードしなければなりません。ニューラル ネットワークの分類子モデルでは、1-of-N エンコードを使用します。3 つの候補クラス ラベルについては、最初のクラス (デモでは「赤」) には 1 0 0、2 つ目のクラス (「青」) には 0 1 0、3 つ目のクラス (「緑」) には 0 0 1 を使います。各ラベル値がどのようにエンコードされるかを決めるのは開発者の仕事です。

デモ以外のシナリオでは、入力データの正規化について考えなければならない場合もあります。入力値に大きな差がある状況では、すべての値がほぼ同じになるようにデータのスケールを変えると、より適切な結果が得られます。たとえば、入力データが年齢 (32.0 など) と年収 (48,500.00 ドルなど) だとします。このような場合、すべての年齢値を 10 で除算し、すべての年収値を 10,000 で除算して、(3.20, 4.85) のように正規化した入力値になるようにデータを前処理します。入力データ正規化の最も一般的な 3 つの形式は、z スコア正規化、最大最小正規化、桁正規化と呼ばれます。

TrainData.txt ファイルを作成して保存したら、次の 9 個のテスト データ項目を TestData.txt という名前で SimpleNeuralNet ディレクトリに保存します。

|features 1.0 1.0 |labels 1 0 0
|features 3.0 9.0 |labels 1 0 0
|features 8.0 8.0 |labels 1 0 0
|features 3.0 4.0 |labels 0 1 0
|features 5.0 6.0 |labels 0 1 0
|features 3.0 6.0 |labels 0 1 0
|features 8.0 3.0 |labels 0 0 1
|features 8.0 1.0 |labels 0 0 1
|features 9.0 2.0 |labels 0 0 1

ニューラル ネットワークの入出力について

CNTK の使用方法を理解するには、ニューラル ネットワークとはどのようなものか、そしてニューラル ネットワークが出力値を計算する方法と出力値を解釈する方法についての基礎知識が必要です。図 5 をご覧ください。この図は、今回のデモ問題に対応するニューラル ネットワークを示しています。

ニューラル ネットワークの入出力
図 5 ニューラル ネットワークの入出力

値 (8.0、3.0) を保持する 2 つの入力ノードと、値 (0.3090、0.0055、0.6854) を保持する 3 つの出力ノードがあります。また、ネットワークには 5 個の隠しノードもあります。

各入力ノードは、すべての隠しノードと線で結ばれています。各隠しノードは、すべての出力ノードと線で結ばれています。これらの線は重みと呼ばれる数値定数を表します。ノードはゼロから始まるインデックスを使って識別されるため、最上位のノードは [0] です。したがって、入力ノード [0] から隠しノード [0] への重みは 2.41、隠しノード [4] から出力ノード [2] への重みは -0.85 になります。

各隠しノードと各出力ノードには、追加の矢印があります。それらはバイアス値と呼ばれます。隠しノード [0] のバイアスは -1.42、出力ノード [2] のバイアスは -1.03 です。

入出力の計算は、例を使って説明するとよくわかります。まず、隠しノードの値を計算します。中央の隠しノード [2] の値は 0.1054 です。この隠しノードに接続されているすべての入力とそれらの入力に関連付けられた重みの積和に、バイアス値を加算し、その合計の双曲正接 (tanh) を求めることでこの値を計算します。

hidden[2] = tanh( (8.0)(-0.49) + (3.0)(0.99) + 1.04) )
          = tanh( -3.92 + 2.98 + 1.04 )
          = tanh( 0.1058 )
          = 0.1054

この tanh 関数は隠し層の活性化関数と呼ばれます。ニューラル ネットワークでは、複数の活性化関数のいずれかを使うことができます。tanh 以外に最もよく使われるのは、logistic sigmoid (ロジスティック シムモイド: 通称 "sigmoid") と rectified linear (正規化線形) の 2 つです。

すべての隠しノードの値を計算したら、次に出力ノードを計算します。まず、接続されている隠しノードとそれらのノードに関連付けられた重みの積和に、バイアス値を加算します。たとえば、今回の出力ノード [0] は 1.0653 で、次のように計算します。

output[0] = (1.0000)(-2.24) + (-0.1253)(1.18) +
            (0.1054)(0.55) + (-0.1905)(1.83) +
            (-1.0000)(-1.23) + 2.51
          = (-2.2400) + (-0.1478) +
            (0.0580) + (-0.3486) +
            (1.2300) + 2.51
          = 1.0653

同様の方法で計算すると、出力ノード [1] は -2.9547 に、出力ノード [2] は 1.8619 になります。

次に、softmax 関数を使って合計が 1.0 になるように 3 つの仮出力値をスケーリングします。

output[0] = e^1.0653 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.3090
output[1] = e^-2.9547 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.0055
output[2] = e^1.8619 / (e^1.0653 + e^-2.9547 + e^1.8619)
          = 0.6854

これら 3 つの値は確率と解釈されます。したがって、入力 (8.0, 3.0) と、指定された重みとバイアス値から、出力は (0.3090, 0.0055, 0.6854) になります。最も高い確率は 3 つ目の値なので、このケースでの予測は 3 つ目のクラス「緑」になります。

出力値を解釈する別の方法として、出力値をマップして、最も高い確率が 1、その他すべてがゼロになるようにすることもできます。この例では、(0, 0, 1) となり、エンコード値「緑」にマップされます。

重みとバイアスの値を決めるプロセスはネットワークのトレーニングと呼ばれ、それこそが CNTK が行うことです。

構成ファイルの作成

図 1 は、CNTK の使用方法の考え方を示しています。通常のコマンド シェルで、プロジェクトのルート ディレクトリ C:\Data\SimpleNeuralNet に移動します。プロジェクトのルート ディレクトリには、TrainData.txt ファイル、TestData.txt ファイル、MakeModel.cntk 構成ファイルが含まれています。CNTK ツールは、以下のコマンドを実行して起動します。

> cntk.exe configFile=MakeModel.cntk makeMode=false

既に説明したように、システムの PATH 変数は cntk.exe プログラムの場所を把握しているため、完全修飾の必要はありません。.cntk 構成ファイルには多くの情報が含まれています。makeMode=false パラメーターは、プログラムを実行し、前の結果を上書きすることを指定しています。CNTK のコマンドライン引数は、大文字と小文字を区別しません。

図 6 に、構成ファイルの全体構造を示します。構成ファイルの完全なリストについては、図 7 をご覧ください。

図 6 構成ファイルの構造

# MakeModel.cntk
command=Train:WriteProbs:DumpWeights:Test
# system parameters go here
Train = [
  action="train"
  BrainScriptNetworkBuilder = [
    # define network here
  ]
]
Test = [
  # training commands here
]
WriteProbs = [
  # output commands here
]
DumpWeights = [
  # output commands here
]

図 7 トレーニング構成ファイル

# MakeModel.cntk
command=Train:WriteProbs:DumpWeights:Test
modelPath = "Model\SimpleNet.snn"
deviceId = -1
dimension = 2
labelDimension = 3
precision = "float"
# =====
Train = [
  action="train"
  # network description
  BrainScriptNetworkBuilder = [
    FDim = $dimension$
    HDim = 5
    LDim = $labelDimension$
    # define the neural network
    neuralDef (ftrs) = [
      W0 = Parameter (HDim, FDim)
      b0 = Parameter (HDim, 1) 
      W1 = Parameter (LDim, HDim)
      b1 = Parameter (LDim, 1)
      hn = Tanh (W0 * ftrs + b0)
      zn = W1 * hn + b1
    ].zn
    # specify inputs
    features = Input (FDim)
    labels   = Input (LDim)
    # create network
    myNet = neuralDef (features)
    # define training criteria and output(s)
    ce   = CrossEntropyWithSoftmax (labels, myNet)
    err  = ErrorPrediction (labels, myNet)
    pn   = Softmax (myNet)
    # connect to the NDL system.
    featureNodes    = (features)
    inputNodes      = (labels)
    criterionNodes  = (ce)
    evaluationNodes = (err)
    outputNodes     = (pn)
  ]
  # stochastic gradient descent
  SGD = [
    epochSize = 0
    minibatchSize = 1
    learningRatesPerSample = 0.04
    maxEpochs = 500
    momentumPerMB = 0.90
  ]
  # configuration for reading data
  reader = [
    readerType = "CNTKTextFormatReader"
    file = "TrainData.txt"
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
]
# test
Test = [
  action = "test"
  reader = [
    readerType = "CNTKTextFormatReader"
    file="TestData.txt"
    randomize = "false"
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
]
# log the output node values
WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="TestData.txt"       
    input = [
      features = [
        dim = $dimension$
        format = "dense"
      ]
      labels = [
        dim = $labelDimension$
        format = "dense"
      ]
    ]
  ]
  outputPath = "TestProbs_txt"
]
# dump weight and bias values
DumpWeights = [
  action = "dumpNode"
  printValues = "true"
]

CNTK 構成ファイルの名前は指定できますが、拡張子は .cntk を使うのが標準です。コメントには # 文字または // トークンを使えますが、複数行にまたがってはいけません。構成ファイルの先頭で、実行するモジュールをコロン区切りのリストで指定します。この場合は次の 4 つです。

command=Train:WriteProbs:DumpWeights:Test

モジュールの実行順序は、モジュールの定義順序と必ずしも一致しません。モジュール名 (この例では、 Train、WriteProbs、DumpWeights、Test) はキーワードではないため、自由に名前を付けることができます。Train モジュールには指示 action="train" が含まれています。 こに 2 つの単語はキーワードです。

Train モジュールはトレーニング データ ファイルを使って予測モデルを作成します。そのため、多くの CNTK 構成ファイルには action="train" コマンドを含むモジュールがあります。このモジュールの名前は、通常 Train にします。Train モジュールは、結果のモデル情報をバイナリ形式でディスクに書き込みます。

Test モジュールは省略できます。ただし、モデルを作成する場合はこのモジュールを含めるのが一般的です。Test モジュールは新しく作成された予測モデルを使って、トレーニング データの全体的な予測精度と予測誤差を評価します。

WriteProbs モジュールは省略できます。このモジュールは、テスト データ項目の実際の予測値を、プロジェクトのルート ディレクトリのテキスト ファイルに書き込みます。これにより、正しく予測されたテスト ケースとそうでないテスト ケースを正確に見分けることができます。

DumpWeights モジュールは、予測モデルを定義するニューラル ネットワークの重みとバイアスを含むテキスト ファイルを作成します。この情報を使って、問題が発生しやすい箇所を明らかにし、前例のない新しいデータに対して予測を実行できるようにします。

システム パラメーター

MakeModel.cntk 構成ファイルでは、以下の 5 つのシステム パラメーターをセットアップします。

modelPath = "Model\SimpleNet.snn"
deviceId = -1
dimension = 2
labelDimension = 3
precision = "float"

modelPath 変数は、結果のバイナリ モデルを配置する場所と、そのモデルの名前を指定します。ここで、"snn" は simple neural network (シンプル ニューラル ネットワーク) の略ですが、任意の拡張子を使用できます。deviceId 変数は、CPU (-1) と GPU (0) のどちらを使うかを CNTK に指示します。

dimension 変数は、入力ベクトル値の数を指定します。labelDimension は、出力可能な値の数を指定します。precision 変数には、"float" (浮動小数点型) または "double" (倍精度浮動小数点型) を指定できます。多くの場合、"float" は "double" よりもトレーニングの実行速度が速いので、"float" の使用がお勧めです。

トレーニング モジュール

構成ファイル内のデモ Train モジュールには、3 つの主要サブセクションとして BrainScriptNetworkBuilder、SGD、reader があります。これらのサブセクションでは、ニューラル ネットワークのアーキテクチャ、ネットワークをトレーニングする方法、トレーニング データを読み取る方法をそれぞれ定義します。

トレーニング モジュールの定義は以下のように始まっています。

Train = [
  action="train"
  BrainScriptNetworkBuilder = [
    FDim = $dimension$
    HDim = 5
    LDim = $labelDimension$
...

Train モジュールの BrainScriptNetworkBuilder セクションでは、BrainScript という特殊なスクリプティング言語を使います。FDim、HDim、LDim の各変数は、それぞれニューラル ネットワークのフィーチャー、隠しノード、ラベル ノードの数を保持します。これらの名前は決められていないので、NumInput、NumHidden、NumOutput など、好みの名前を使用できます。入力ノードと出力ノードの数は問題データによって決まりますが、隠しノードの数は自由パラメーターなので試行錯誤しながら決めていかなければなりません。$ トークンは置換演算子です。Train モジュールの定義は次のように続いています。

neuralDef (ftrs) = [
  W0 = Parameter (HDim, FDim)
  b0 = Parameter (HDim, 1) 
  W1 = Parameter (LDim, HDim)
  b1 = Parameter (LDim, 1)
  hn = Tanh (W0 * ftrs + b0)
  zn = W1 * hn + b1
].zn
...

これが BrainScript 関数です。W0 変数は入力ノードから隠しノードへの重みを保持する行列です。Parameter 関数は「行列の構成」を意味します。 b0 変数は隠しノードのバイアス値を保持します。BrainScript 内でのすべての計算は行列計算になるため、b0 は配列でではなく 1 列の行列にします。

W1 変数と b1 変数はそれぞれ、隠しノードから出力ノードへの重みと、出力ノードのバイアス値を保持します。隠しノードの値は、上記のとおり、積和と tanh 関数を使って計算し、hn 変数に格納します。zn 変数は ­softmax 関数適用前の出力値を保持します。"] (右角かっこ) +. (ドット) + 変数" という表記は、BrainScript 関数が値を返す方法を示しています。Train の定義はさらに以下のように続いています。

features = Input (FDim)
labels   = Input (LDim)
myNet = neuralDef (features)
...

ここでは、入力の features と出力の labels を定義しています。変数名 "features" と "labels" はキーワードではありませんが、トレーニング ファイルとテスト データ ファイルで使用している文字列と一致する必要があります。neuralDef 関数を呼び出して、ニューラル ネットワークを作成します。次に、トレーニング中にモジュールが使用する情報を定義します。

ce   = CrossEntropyWithSoftmax (labels, myNet)
err  = ErrorPrediction (labels, myNet)
pn   = Softmax (myNet)
...

CrossEntropyWithSoftmax 関数は、計算した出力値がトレーニング データの実際の出力値とどの程度近いかを計算するときに、交差エントロピー誤差を使用することを指定します。交差エントロピー誤差は標準メトリックですが、代わりに二乗誤差を使うこともできます。

ErrorPrediction 関数は、予測モデルの精度 (トレーニング データと正確な予測との割合) と、交差エントロピー誤差、パープレキシティを計算して表示するよう CNTK に指示します。これにより、計算後の出力と実際の出力との誤差が測定されます。

Softmax 関数は、計算後の出力値の合計が 1.0 になり、確率と解釈できるように正規化することを CNTK に指示します。ニューラル ネットワーク分類では、非常にまれな状況を除き Softmax を使います。トレーニング モジュールの定義は、以下のように結ばれています。

...
  featureNodes    = (features)
  inputNodes      = (labels)
  criterionNodes  = (ce)
  evaluationNodes = (err)
  outputNodes     = (pn)
]

ここでは、必須のシステム変数 featureNodes、inputNodes、criterionNodes、outputNodes と、省略可能な evaluationNodes 変数を、ユーザー定義変数に関連付けています。

確率的勾配降下法 (SGD) サブセクションでは、CNTK がニューラル ネットワークをトレーニングする方法を定義します。ニューラル ネットワークのコンテキストでは、SGD は一般に誤差逆伝搬法と呼ばれます。サブセクションの定義は以下のとおりです。

SGD = [
  epochSize = 0
  minibatchSize = 1
  learningRatesPerSample = 0.04
  maxEpochs = 500
  momentumPerMB = 0.90
]

epochSize 変数は、使用するトレーニング データの量を指定します。ゼロという特殊な値は、利用可能なトレーニング データをすべて使用することを意味します。minibatchSize 変数は、トレーニングを繰り返すたびに処理するトレーニング データの量を指定します。値 1 は、各トレーニング項目を処理した後で重みとバイアスを更新することを意味します。多くの場合、これは「オンライン」トレーニングまたは「確率」トレーニングと呼ばれます。

minibatchSize の値をトレーニング項目の数 (デモでは 24) に設定している場合、24 個すべての項目を処理し、結果を集計してから、重みとバイアスを更新することになります。これは、「完全バッチ」トレーニングまたは「バッチ」トレーニングと呼ばれることがあります。1 からトレーニング セットのサイズまでの値を使用する場合は、「ミニ バッチ」トレーニングと呼ばれます。

learningRatesPerSample 変数は、繰り返しのたびに重みとバイアスを調整する率を指定します。この学習率の値は、SGD サブセクション内の他のパラメーターの値と共に、試行錯誤で決めることになります。ニューラル ネットワークは通常、学習率の値に大きく左右されます。たとえば、0.04 を使えば非常に精度の高い予測システムになるのに、0.039 や 0.041 を使うと非常に精度の低いシステムになることもあります。

maxEpochs 変数は、トレーニングの繰り返し回数を指定します。この回数少なすぎると精度の低いモデル (「モデルの学習不足」) となりますが、多すぎるとトレーニング データの過学習が起こります。過学習が生じると、トレーニング データでは非常に適切に予測できても、新しいデータについては非常に精度の低い予測モデルになります。

momentumPerMB (ミニバッチ単位の運動量) は、重みとバイアスを更新する量を増減する係数です。学習率と同様、運動量の値は試行錯誤で決める必要があり、ニューラル ネットワークのトレーニングは通常、この運動量の値によって大きく左右されます。デモで使用している値 0.90 は既定値なので、この momentumPerMB パラメーターは省略してもかまいません。

デモ構成ファイルのトレーニング モジュールは、以下のように、reader サブセクションのパラメーターの値を設定することで終わります。

...
  reader = [
    readerType = "CNTKTextFormatReader"
    file = "TrainData.txt"
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
] # end Train

CNTK ツールには、さまざまな種類の入力データで使うさまざまな種類のリーダーが多数含まれています。デモの reader サブセクション内のパラメーターの意味を説明しておきます。ここでは複数のステートメントをセミコロンで区切って 1 行に含めています。

Test モジュール

ここまでをまとめると、MakeModel.cntk 構成ファイルには、いくつかのグローバル システム パラメーター (modelPath など) と、4 つのモジュール Train、Test、WriteProbs、DumpWeights が含まれています。Train モジュールには 3 つのサブセクション BrainScriptNetworkBuilder、SGD、reader があります。

図 8 からわかるように、Test モジュールは非常にシンプルです。

図 8 Test モジュール

Test = [
  action = "test"
  reader = [
    readerType="CNTKTextFormatReader"
    file = "TestData.txt"
    randomize = "false"
    input = [
      features = [ dim = $dimension$
        format = "dense" ]
      labels = [ dim = $labelDimension$
        format = "dense" ]
    ]
  ]
]

Test モジュールの reader サブセクションは、file パラメーター値と、randomize パラメーターが追加されていること以外は、トレーニング モジュールの reader サブセクションと同じです。SGD でトレーニングを実行する場合、データ項目をランダムに処理することが非常に重要なので、randomize の既定値は true になります。ただし、テスト データを順に処理する場合、データ項目の順序をランダムにする必要はありません。

Test モジュールは、1 つの精度メトリックと 2 つの誤差メトリックをシェルに出力します。図 1 に戻って確認すると、"Action test complete" (test アクションの完了) メッセージが表示される直前に、以下が表示されています。

err = 0.11111111 * 9
ce = 0.33729280 * 9
perplexity = 1.40114927

err = 0.1111 * 9 は、このモデルを使った予測が 9 個のテスト データ項目の 11 パーセントで間違ったことを意味します。つまり、9 個のテスト項目のうちの 8 個が正確に予測されたことになります。ただし、トレーニングの出力からは、どのデータ項目が正確に予測され、どのデータ項目が間違った予測かはわかりません。

ce = 0.3372 * 9 は、平均交差エントロピー誤差が 0.3372 であることを意味します。今回の CNTK の説明では、交差エントロピーを誤差項と考えるため、値は小さいほどよいと言えます。

perplexity = 1.4011 はマイナー メトリックです。パープレキシティは予測の厳密度の測定と考えることができるため、値は小さいほどよいと言えます。たとえば、デモのように候補出力値が 3 つあると、予測が (0.33, 0.33, 0.33) の場合、厳密に予測することはできません。この場合のパープレキシティは 3.0 で、出力値が 3 つの場合の最大値になります。

WriteProbs モジュール

デモ CNTK 構成ファイルの 3 つ目のモジュールは Write­Probs です。このモジュールは省略できますが、テスト データに対して実行された予測に関する追加情報を提供するため、非常に役立ちます。図 9 に、このモジュールの定義を示します。

図 9 WriteProbs モジュール

WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="TestData.txt"       
    input = [
      features = [  dim = $dimension$
        format = "dense" ]
      labels = [ dim = $labelDimension$
        format = "dense"  ]
    ]
  ]
  outputPath = "TestProbs_txt"
]

WriteProbs モジュールは、3 つの違いを除き、Test モジュールと同じです。まず、action パラメーターを "test" ではなく "write" に設定しています。 次に、randomize パラメーターを削除しています (false が既定値のためです)。最後に、outputPath パラメーターを追加しています。

WriteProbs モジュールを実行すると、テスト データの正確な出力値が指定したファイルに書き込まれます。今回の場合、ファイル名に ".pn" を付加しています。これは、トレーニング モジュールの出力ノードに使用している変数名であったためです。

9 個のデモ テスト項目の場合、TestProbs_txt.pn ファイルのコンテンツは以下のようになります。

0.837386 0.162606 0.000008
0.990331 0.009669 0.000000
0.275697 0.724260 0.000042
0.271172 0.728788 0.000040
0.264680 0.735279 0.000041
0.427661 0.572313 0.000026
0.309024 0.005548 0.685428
0.000134 0.000006 0.999860
0.000190 0.000008 0.999801

最初の 3 つの確率ベクトルは、最初の 3 つのテスト項目に対応しています。正しい出力 (1, 0, 0) に対応しているため、最初の 2 つのテスト項目は正しく予測されています。しかし、3 つ目の確率ベクトル (0.27, 0.74, 0.00) は (0, 1, 0) に対応するため、誤った予測です。

次の 3 つの確率ベクトルは、テスト項目の出力 (0, 1, 0) に対応するため、これらの予測はすべて正確です。同様に、最後の 3 つの確率ベクトルは (0, 0, 1) に対応するため、これらの予測もすべて正確です。

まとめると、Test モジュールは精度メトリックと誤差メトリックをシェルに出力しますが、個別のテスト項目のうちどれが正確で、どれが間違っているかは示しません。WriteProbs モジュールは、正確な出力値をファイルに書き込むため、それらの出力値を使ってどのテスト項目が誤って予測されているかを判断できます。

DumpWeights モジュール

デモ構成ファイルに含まれている 4 つのモジュールの最後は DumpWeights です。このモジュールは以下のように定義されています。

DumpWeights = [
  action = "dumpNode"
  printValues = "true"
]

このモジュールを実行すると、トレーニング済みのモデルの重みとバイアスの値がファイルに保存されます。既定では、ファイル名はバイナリ モデルと同じ (デモでは SimpleNet.snn) で、".__AllNodes__.txt" が付加されたものになります。このファイルは modelPath パラメーターで指定したディレクトリ (デモでは "Model") に保存されます。

MakeModel.cntk デモの実行後、エクスプローラーを開いて \SimpleNeuralNet\Model ディレクトリを参照すると、503 個のファイルが見つかります。

SimpleNet.snn
SimpleNet.snn.__AllNodes__.txt
SimpleNet.snn.0
...
SimpleNet.snn.499
SimpleNet.ckp

SimpleNet.snn は CNTK で使うためにバイナリ形式で保存されるトレーニング後のモデルです。名前が数字で終わる 500 個のファイルと、".ckp" 拡張子で終わるバイナリ チェックポイント ファイルが 1 つあります。複雑なニューラル ネットワークをトレーニングする場合は数時間から数日間かかると考えられます。デモで maxEpochs パラメーターを 500 に設定したことを思い出してください。CNTK ツールは定期的にトレーニング情報を保存するため、システム障害が発生した場合、最初からトレーニングをやり直す必要はありません。

デモの AllNodes__.txt ファイルの前半は以下のとおりです (数行を削除しています)。

myNet.b0=LearnableParameter [5,1]
-1.42185283
 1.84464693
 1.04422486
 2.57946277
 1.65035748
 ################################################
myNet.b1=LearnableParameter [3,1]
 2.51937032
-1.5136646
-1.03768802

これらは隠しノードのバイアス (b0) と出力ノードのバイアス (b1) の値です。図 4 のニューラル ネットワーク図をもう一度参照すると、これらの値が 2 桁に切り捨てられているのがわかります。AllNodes__.txt ファイルの後半は以下のとおりです。

myNet.W0=LearnableParameter [5,2]
 2.41520381 -0.806418538
-0.561291218 0.839902222
-0.490522146 0.995252371
-0.740959883 1.05180109
-2.72802472 2.81985259
 #################################################
myNet.W1=LearnableParameter [3,5]
-2.246624  1.186315  0.557211  1.837152 -1.232379
 0.739416  0.814771  1.095480  0.386835  2.120146
 1.549207 -1.959648 -1.627967 -2.235190 -0.850726

デモ ネットワークには 2 つの入力値、5 つの隠しノード、3 つの出力ノードがありました。したがって、W0 の入力ノードから隠しノードへの重みは 2 * 5 = 10 個になり、W1 の隠しノードから出力ノードへの重みは 5 * 3 = 15 個になります。

予測

トレーニング済みのモデルを用意したら、そのモデルを使って予測を実行します。これを行う方法の 1 つは、"eval" アクション モジュールと共に CNTK ツールを使うことです。デモではこの手法を使います。まず、1 つの項目を含む新しいデータ セットを作成し、NewData.txt というファイル名で保存します。

|features 4.0 7.0 |labels -1 -1 -1

これは新しいデータになるため、出力ラベルにはダミーの -1 値を使います。次に、2 つのモジュール Predict と WriteProbs を含む MakePrediction.cntk という構成ファイルを作成します。図 10 に、完全なファイルを示します。

図 10 予測の実行

# MakePrediction.cntk
stderr = "Log"   # write all messages to file
command=Predict:WriteProbs
modelPath = "Model\SimpleNet.snn" # where to find model
deviceId = -1 
dimension = 2 
labelDimension = 3 
precision = "float"
Predict = [
  action = "eval"
  reader = [
    readerType="CNTKTextFormatReader"
    file="NewData.txt"
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
]
WriteProbs = [
  action="write"
  reader=[
    readerType="CNTKTextFormatReader"
    file="NewData.txt"       
    input = [
      features = [ dim = $dimension$; format = "dense" ]
      labels = [ dim = $labelDimension$; format = "dense" ]
    ]
  ]
  outputPath = "Prediction_txt"  # dump with .pn extension
]

これを実行すると、確率の出力が Prediction_txt.pn というファイルに保存されます。

0.271139 0.728821 0.000040

これは、出力 (0, 1, 0) に対応付けられ、「青」になります。 図 2 のトレーニング データを見ると、(4.0, 7.0) は「赤」 (1, 0, 0) または「青」 (0, 1, 0) であることが簡単にわかります。

トレーニング済みのモデルを使うには、これ以外にも、C# プログラムを CNTK モデル評価ライブラリと共に使用する方法や、直接モデルの重みとバイアスの値を使うカスタム Python スクリプトを使用する方法があります。

まとめ

個人的見解ですが、CNTK は開発者が広く利用できる、Windows 向けの最も強力なニューラル ネットワーク システムです。今回は、CNTK で実行可能なことをほんの少しだけ紹介しましたが、シンプルなニューラル ネットワークを運用し、ドキュメントを理解するには十分だと考えています。CNTK の真の力は、緻密なニューラル ネットワーク (複数の隠し層とおそらくは複雑なノード間接続を含むネットワーク) を操作するところに発揮されます。

CNTK ツールは開発途中なので、本稿が公開されるころには一部の詳細が変更されている場合があります。ただし、CNTK チームからは変更はわずかなものになると聞いていますので、今回紹介したデモはそれほど苦労せずに変更できると思います。


Dr.James McCaffrey は、ワシントン州レドモンドの Microsoft Research に勤務しています。これまでに、Internet Explorer、Bing などの複数のマイクロソフト製品にも携わってきました。Dr.McCaffrey の連絡先は jammc@microsoft.com (英語のみ) です。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Adam Eversole、John Krumm、Frank Seide、および Adam Shirey に心より感謝いたします。


この記事について MSDN マガジン フォーラムで議論する