リカレント ネットワークを使用したハンズオン ラボのLanguage Understanding
このチュートリアルでは、最新のマスター バージョン、または近日リリース予定の CNTK 1.7.1 が必要であることに注意してください。
この実践的なラボでは、スロットタグ付けと意図分類の Air Travel Information Services (ATIS) タスクに対して、テキストを処理する繰り返しネットワークを実装する方法を示します。 まず、直進埋め込みの後に、再発 LSTM を使用します。 次に、近隣の単語を含むように拡張し、双方向で実行します。 最後に、このシステムを意図分類子に変換します。
練習する手法は次のとおりです。
- 数式を記述する代わりにレイヤー ブロックを作成してモデルの説明を作成する
- 独自のレイヤー ブロックを作成する
- 同じネットワーク内のシーケンス長が異なる変数
- 並列トレーニング
ディープ ラーニングの基本と、次の具体的な概念について理解していることを前提としています。
- リカレント ネットワーク (Wikipedia ページ)
- テキスト埋め込み (Wikipedia ページ)
前提条件
CNTK が既にインストールされており、CNTK コマンドを実行できることを前提としています。 このチュートリアルは KDD 2016 で開催され、最近のビルドが必要です。セットアップ手順 については、こちらを参照 してください。 そのページからバイナリ インストール パッケージをダウンロードする手順に従ってください。
次に、ZIP アーカイブ (約 12 MB) をダウンロードしてください。 このリンクをクリックし、[ダウンロード] ボタンをクリックします。
アーカイブには、このチュートリアルのファイルが含まれています。 アーカイブを行い、作業ディレクトリ SLUHandsOn
を .
作業するファイルは次のとおりです。
SLUHandsOn.cntk
: 以下で紹介する CNTK 構成ファイルを使用します。slu.forward.nobn.cmf
、slu.forward.cmf
、slu.forward.lookahead.cmf
およびslu.forward.backward.cmf
: このチュートリアル全体で開発しているそれぞれの構成の結果である事前トレーニング済みのモデル。atis.train.ctf
andatis.test.ctf
: トレーニングおよびテスト コーパス。CNTK テキスト形式 (CTF) に既に変換されています。
最後に、CUDA と互換性のある GPU を搭載したマシンでこれを実行することを強くお勧めします。 GPU を使用しないディープ ラーニングは楽しくありません。
タスクとモデルの構造
このチュートリアルで取り組みたいタスクはスロットタグ付けです。 ATIS コーパスを使用します。 ATIS には、Air Travel Information Services のドメインからの人間とコンピューターのクエリが含まれており、クエリの各単語が特定の情報項目 (スロット) に属しているかどうか、およびどれに注釈を付ける (タグ付け) するかがタスクになります。
作業フォルダー内のデータは、既に "CNTK テキスト形式" に変換されています。テスト セット ファイル atis.test.ctf
の例を見てみましょう。
19 |S0 178:1 |# BOS |S1 14:1 |# flight |S2 128:1 |# O
19 |S0 770:1 |# show |S2 128:1 |# O
19 |S0 429:1 |# flights |S2 128:1 |# O
19 |S0 444:1 |# from |S2 128:1 |# O
19 |S0 272:1 |# burbank |S2 48:1 |# B-fromloc.city_name
19 |S0 851:1 |# to |S2 128:1 |# O
19 |S0 789:1 |# st. |S2 78:1 |# B-toloc.city_name
19 |S0 564:1 |# louis |S2 125:1 |# I-toloc.city_name
19 |S0 654:1 |# on |S2 128:1 |# O
19 |S0 601:1 |# monday |S2 26:1 |# B-depart_date.day_name
19 |S0 179:1 |# EOS |S2 128:1 |# O
このファイルには、次の 7 つの列があります。
- シーケンス ID (19)。 このシーケンス ID を持つエントリは 11 個あります。つまり、シーケンス 19 は 11 個のトークンで構成されます。
- 数値の単語インデックスを含む列
S0
。 - 人間の読者が数字の単語インデックスの意味を知ることができるように、で示される
#
コメント列。コメント列はシステムによって無視されます。BOS
文の先頭とEOS
末尾をそれぞれ示す特別な単語です。 - column
S1
は意図ラベルであり、このラベルはチュートリアルの最後の部分でのみ使用します。 - 数値意図インデックスの人間が判読できるラベルを示す別のコメント列。
- column
S2
はスロット ラベルで、数値インデックスとして表されます。 - 数値ラベル インデックスの人間が判読できるラベルを示す別のコメント列。
ニューラル ネットワークのタスクは、クエリ (列 S0
) を見てスロット ラベル (列) を予測することです S2
。
ご覧のように、入力内の各単語には、最初の単語で始まるB-
空のラベルO
またはスロット ラベルが割り当てられ、I-
同じスロットに属する追加の連続する単語が割り当てられます。
使用するモデルは、埋め込みレイヤー、再発 LSTM セル、および事後確率を計算する高密度レイヤーで構成される反復モデルです。
slot label "O" "O" "O" "O" "B-fromloc.city_name"
^ ^ ^ ^ ^
| | | | |
+-------+ +-------+ +-------+ +-------+ +-------+
| Dense | | Dense | | Dense | | Dense | | Dense | ...
+-------+ +-------+ +-------+ +-------+ +-------+
^ ^ ^ ^ ^
| | | | |
+------+ +------+ +------+ +------+ +------+
0 -->| LSTM |-->| LSTM |-->| LSTM |-->| LSTM |-->| LSTM |-->...
+------+ +------+ +------+ +------+ +------+
^ ^ ^ ^ ^
| | | | |
+-------+ +-------+ +-------+ +-------+ +-------+
| Embed | | Embed | | Embed | | Embed | | Embed | ...
+-------+ +-------+ +-------+ +-------+ +-------+
^ ^ ^ ^ ^
| | | | |
w ------>+--------->+--------->+--------->+--------->+------...
BOS "show" "flights" "from" "burbank"
または、CNTK ネットワークの説明として。 簡単に見て、上記の説明と一致してください。
model = Sequential (
EmbeddingLayer {150} :
RecurrentLSTMLayer {300} :
DenseLayer {labelDim}
)
これらの関数の説明については、次のページを参照してください。 Sequential()
EmbeddingLayer{}
RecurrentLSTMLayer{}
DenseLayer{}
CNTK の構成
構成ファイル
CNTK でモデルをトレーニングしてテストするには、実行する操作 (command
変数) と、すべてのコマンドのパラメーター セクションを CNTK に通知する構成ファイルを提供する必要があります。
トレーニング コマンドの場合は、CNTK に次の情報を伝える必要があります。
- データの読み取り方法 (
reader
セクション) - モデル関数とその入力と出力を計算グラフ (
BrainScriptNetworkBuilder
セクション) に表示する - 学習者のハイパーパラメーター (
SGD
セクション)
評価コマンドの場合、CNTK はテスト データ (reader
セクション) の読み取り方法を認識している必要があります。
最初に使用する構成ファイルを次に示します。 ご覧のように、CNTK 構成ファイルは、レコードの階層に編成されたパラメーターの定義で構成されるテキスト ファイルです。 また、CNTK が構文を使用して基本的なパラメーター置換をサポートする方法を $parameterName$
確認することもできます。 実際のファイルには、上記の数個のパラメーターが含まれていますが、スキャンして、先ほど説明した構成項目を探してください。
# CNTK Configuration File for creating a slot tagger and an intent tagger.
command = TrainTagger:TestTagger
makeMode = false ; traceLevel = 0 ; deviceId = "auto"
rootDir = "." ; dataDir = "$rootDir$" ; modelDir = "$rootDir$/Models"
modelPath = "$modelDir$/slu.cmf"
vocabSize = 943 ; numLabels = 129 ; numIntents = 26 # number of words in vocab, slot labels, and intent labels
# The command to train the LSTM model
TrainTagger = {
action = "train"
BrainScriptNetworkBuilder = {
inputDim = $vocabSize$
labelDim = $numLabels$
embDim = 150
hiddenDim = 300
model = Sequential (
EmbeddingLayer {embDim} : # embedding
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
DenseLayer {labelDim} # output layer
)
# features
query = Input {inputDim}
slotLabels = Input {labelDim}
# model application
z = model (query)
# loss and metric
ce = CrossEntropyWithSoftmax (slotLabels, z)
errs = ClassificationError (slotLabels, z)
featureNodes = (query)
labelNodes = (slotLabels)
criterionNodes = (ce)
evaluationNodes = (errs)
outputNodes = (z)
}
SGD = {
maxEpochs = 8 ; epochSize = 36000
minibatchSize = 70
learningRatesPerSample = 0.003*2:0.0015*12:0.0003
gradUpdateType = "fsAdaGrad"
gradientClippingWithTruncation = true ; clippingThresholdPerSample = 15.0
firstMBsToShowResult = 10 ; numMBsToShowResult = 100
}
reader = {
readerType = "CNTKTextFormatReader"
file = "$DataDir$/atis.train.ctf"
randomize = true
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
}
}
# Test the model's accuracy (as an error count)
TestTagger = {
action = "eval"
modelPath = $modelPath$
reader = {
readerType = "CNTKTextFormatReader"
file = "$DataDir$/atis.test.ctf"
randomize = false
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
}
}
データとデータの読み取りを簡単に見る
既にデータを調べていました。
しかし、どのようにこの形式を生成しますか?
テキストを読む場合、このチュートリアルでは CNTKTextFormatReader
. ここでは、入力データが特定の形式であることを想定 しています。
このチュートリアルでは、次の 2 つの手順でコーパスを作成しました。
生データを、スペース区切りテキストの TAB 区切り列を含むプレーン テキスト ファイルに変換します。 次に例を示します。
BOS show flights from burbank to st. louis on monday EOS (TAB) flight (TAB) O O O O B-fromloc.city_name O B-toloc.city_name I-toloc.city_name O B-depart_date.day_name O
これは、コマンドの
paste
出力と互換性があることを意味します。次のコマンドを使用して、CNTK テキスト形式 (CTF) に変換します。
python Scripts/txt2ctf.py --map query.wl intent.wl slots.wl --annotated True --input atis.test.txt --output atis.test.ctf
ここで、3 つの
.wl
ファイルは、単語ごとに 1 行のプレーン テキスト ファイルとしてボキャブラリを提供します。
これらの CTFG ファイルでは、列にラベルが付けられますS0
。 S1
S2
これらは、リーダー定義内の対応する行によって実際のネットワーク入力に接続されます。
input = {
query = { alias = "S0" ; dim = $vocabSize$ ; format = "sparse" }
intentLabels = { alias = "S1" ; dim = $numIntents$ ; format = "sparse" }
slotLabels = { alias = "S2" ; dim = $numLabels$ ; format = "sparse" }
}
それを実行する
上記の構成ファイルは、作業フォルダーの名前 SLUHandsOn.cntk
の下にあります。
これを実行するには、次のコマンドで上記の構成を実行してください。
cntk configFile=SLUHandsOn.cntk
これにより、名前付け TrainTagger
されたセクションで定義されているモデル トレーニングから始めて、構成が実行されます。
ややおしゃべりな初期ログ出力の後、すぐにこれが表示されます。
Training 721479 parameters in 6 parameter tensors.
次のような出力が続きます。
Finished Epoch[ 1 of 8]: [Training] ce = 0.77274927 * 36007; errs = 15.344% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.27009664 * 36001; errs = 5.883% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.16390425 * 36005; errs = 3.688% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.13121604 * 35997; errs = 2.761% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.09308497 * 36000; errs = 2.028% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.08537533 * 35999; errs = 1.917% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.07477648 * 35997; errs = 1.686% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.06114417 * 36018; errs = 1.380% * 36018
これは、エポック (データを通過) で学習がどのように進むかを示しています。
たとえば、2 つのエポックの後、構成ファイルで指定 ce
したクロス エントロピー条件は、このエポックの 36001 サンプルで測定された 0.27 に達し、同じ 36016 トレーニング サンプルでエラー率が 5.883% に達しました。
36001 は、構成でエポック サイズが 36000 として定義されたという事実に由来します。 エポック サイズは、モデル チェックポイント間で処理する文ではなく、 単語トークンとしてカウントされるサンプルの数です。 文の長さはさまざまで、必ずしも正確に 36000 単語の倍数まで合計するとは限らないので、いくつかの小さなバリエーションが表示されます。
トレーニングが完了すると (Titan-X または Surface Book で 2 分未満)、CNTK はアクションをEvalTagger
続行します
Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111
つまり、テスト セットでは、スロット ラベルは 2.9% のエラー率で予測されています。 まったく悪くない、このような単純なシステムのために!
CPU 専用マシンでは、4 回以上低速になる場合があります。 システムが進行していることを早期に確認するために、トレースを有効にして部分的な結果を確認できます。これは、合理的に迅速に表示されます。
cntk configFile=SLUHandsOn.cntk traceLevel=1
Epoch[ 1 of 8]-Minibatch[ 1- 1, 0.19%]: ce = 4.86535690 * 67; errs = 100.000% * 67
Epoch[ 1 of 8]-Minibatch[ 2- 2, 0.39%]: ce = 4.83886670 * 63; errs = 57.143% * 63
Epoch[ 1 of 8]-Minibatch[ 3- 3, 0.58%]: ce = 4.78657442 * 68; errs = 36.765% * 68
...
これが完了するまで待ちたくない場合は、中間モデルを実行できます(例:
cntk configFile=SLUHandsOn.cntk command=TestTagger modelPath=Models/slu.cmf.4
Final Results: Minibatch[1-1]: errs = 3.851% * 10984; ce = 0.18932937 * 10984; perplexity = 1.20843890
または、事前トレーニング済みのモデルもテストします。これは作業フォルダーにあります。
cntk configFile=SLUHandsOn.cntk command=TestTagger modelPath=slu.forward.nobn.cmf
Final Results: Minibatch[1-1]: errs = 2.922% * 10984; ce = 0.14306181 * 10984; perplexity = 1.15380111
モデルの変更
次に、CNTK 構成の変更を練習するタスクを示します。 ソリューションは、このドキュメントの最後に示されています...しかし、せずに試してみてください!
単語の概要 Sequential()
タスクにジャンプする前に、先ほど実行したモデルをもう一度見てみましょう。 このモデルは、 関数コンポジション スタイルと呼ばれるものに記述されています。
model = Sequential (
EmbeddingLayer {embDim} : # embedding
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
DenseLayer {labelDim, initValueScale=7} # output layer
)
ここで、コロン (:
) は、配列を表す BrainScript の構文です。 たとえば、 (F:G:H)
3 つの要素 F
、、 G
および H
.
他のニューラル ネットワーク ツールキットの "シーケンシャル" 表記をよく知っている場合があります。
そうでない場合は、 Sequential()
一言で言えば、層の進行を介して入力を伝達することによって入力が処理されるニューラル ネットワークで非常に一般的な状況をコンパクトに表現できる強力な操作です。
Sequential()
関数の配列を引数として受け取り、次の関数の出力を渡すたびに、これらの関数を順番に呼び出す 新しい 関数を返します。
たとえば、次のように入力します。
FGH = Sequential (F:G:H)
y = FGH (x)
は と同じ意味になります
y = H(G(F(x)))
これは "関数の構成" と呼ばれ、ニューラル ネットワークを表現する場合に特に便利です。多くの場合、次のような形式になります。
+-------+ +-------+ +-------+
x -->| F |-->| G |-->| H |--> y
+-------+ +-------+ +-------+
現在のモデルに戻ると、式は単に Sequential
モデルに次の形式があることを示しています。
+-----------+ +----------------+ +------------+
x -->| Embedding |-->| Recurrent LSTM |-->| DenseLayer |--> y
+-----------+ +----------------+ +------------+
タスク 1: バッチ正規化を追加する
ここで、モデルに新しいレイヤー (特にバッチ正規化) を追加します。
バッチ正規化は、収束を高速化するための一般的な手法です。 画像認識に関する他の実践的なラボなど、画像処理のセットアップ によく使用されます。 しかし、再発モデルでも機能しますか?
そのため、タスクは、反復 LSTM レイヤーの前後にバッチ正規化レイヤーを挿入することです。 画像処理に関する実践的なラボを完了している場合は、バッチ正規化レイヤーに次の形式があることを覚えている場合があります。
BatchNormalizationLayer{}
そのため、先に進んで構成を変更し、何が起こるかを確認してください。
すべてが正しく行けば、以前の構成と比較して収束速度 (ce
および errs
) が向上するだけでなく、エラー率が 2.0% (2.9% に比べて) 向上したことがわかります。
Training 722379 parameters in 10 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 0.29396894 * 36007; errs = 5.621% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.10104186 * 36001; errs = 2.280% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.05012737 * 36005; errs = 1.258% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.04116407 * 35997; errs = 1.108% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.02602344 * 36000; errs = 0.756% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.02234042 * 35999; errs = 0.622% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.01931362 * 35997; errs = 0.667% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.01714253 * 36018; errs = 0.522% * 36018
Final Results: Minibatch[1-1]: errs = 2.039% * 10984; ce = 0.12888706 * 10984; perplexity = 1.13756164
(トレーニングが完了するのを待ちたくない場合は、結果のモデルを名前 slu.forward.cmf
の下に見つけることができます。)
ソリューションについては、 こちらを参照してください。
タスク 2: 先読みを追加する
再発モデルは構造的な欠陥に苦しんでいます。繰り返しは左から右に実行されるため、スロット ラベルの決定には今後の単語に関する情報はありません。 モデルは少し偏りがあります。 タスクは、繰り返しへの入力が現在の単語だけでなく、次の単語 (先読み) で構成されるようにモデルを変更することです。
ソリューションは関数コンポジション スタイルである必要があります。 そのため、次の処理を行う BrainScript 関数を記述する必要があります。
- 1 つの入力引数を受け入れます。
- 関数を使用して
FutureValue()
この入力の即時の"将来の値"を計算します(この特定の形式を使用します:FutureValue (0, input, defaultHiddenActivation=0)
); - を使用して
Splice()
埋め込み次元の 2 倍のベクトルに 2 つ連結します (次の形式を使用します: スプライス(x:y)
)
次に Sequence()
、埋め込みレイヤーと再発層の間にこの関数を挿入します。
すべて問題がなければ、次の出力が表示されます。
Training 902679 parameters in 10 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 0.30500536 * 36007; errs = 5.904% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.09723847 * 36001; errs = 2.167% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.04082365 * 36005; errs = 1.047% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.03219930 * 35997; errs = 0.867% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.01524993 * 36000; errs = 0.414% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.01367533 * 35999; errs = 0.383% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.00937027 * 35997; errs = 0.278% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.00584430 * 36018; errs = 0.147% * 36018
Final Results: Minibatch[1-1]: errs = 1.839% * 10984; ce = 0.12023170 * 10984; perplexity = 1.12775812
これはうまくいきました! 次の単語が何であるかを知ることで、スロット タガーはエラー率を 2.0% から 1.84% に減らすことができます。
(トレーニングが完了するのを待ちたくない場合は、結果のモデルを名前 slu.forward.lookahead.cmf
の下に見つけることができます。)
ソリューションについては、 こちらを参照してください。
タスク 3: 双方向リカレント モデル
Aha、将来の単語の知識が役立ちます。 したがって、1 単語の先読みではなく、後ろ向きの繰り返しを通して、文の最後まで先を見てみてはいかがでしょうか。 双方向モデルを作成しましょう。
タスクは、データに対して前方再帰と後方再帰の両方を実行し、出力ベクトルを連結する新しいレイヤーを実装することです。
ただし、これは、双方向レイヤーに学習可能なモデル パラメーターが含まれているという点で、前のタスクとは異なります。 関数構成スタイルでは、モデル パラメーターを持つレイヤーを実装するパターンは、関数オブジェクトを作成するファクトリ関数を記述することです。
関数オブジェクト ( ファンクターとも呼ばれます) は、関数とオブジェクトの両方であるオブジェクトです。 つまり、まだデータが含まれている他の何も、関数であるかのように呼び出すことができます。
たとえば、重み行列W
、LinearLayer{outDim}
バイアスb
、および計算W * input + b
する別の関数を含む関数オブジェクトを返すファクトリ関数です。
たとえば、 LinearLayer{1024}
この関数オブジェクトが作成され、他の関数 LinearLayer{1024}(x)
と同様にすぐに使用できます。
混乱。 例を見てみましょう。線形レイヤーと後続のバッチ正規化を組み合わせた新しいレイヤーを実装してみましょう。 関数の構成を許可するには、レイヤーをファクトリ関数として実現する必要があります。これは次のようになります。
LinearLayerWithBN {outDim} = {
F = LinearLayer {outDim}
G = BatchNormalization {normalizationTimeConstant=2048}
apply (x) = G(F(x))
}.apply
このファクトリ関数を呼び出すと、最初に 3 つのメンバーを持つレコード (によって{...}
示される) がG
apply
作成されます。 F
この例では、 F
G
関数オブジェクト自体であり apply
、データに適用する関数です。
この式に追加すると .apply
、レコード メンバーにアクセスするための BrainScript の意味が常に意味 .x
されます。 したがって、例えば、呼び出し LinearLayerWithBN{1024}
は、バッチ正規化関数オブジェクトと呼ばれる F
線形レイヤー関数オブジェクトを含むオブジェクト G
を作成します apply
。これは、このレイヤーの実際の操作を F
実装する関数です G
。 その後、返 apply
されます。 外部では、 apply()
関数のように見え、動作します。 しかし、内部では、 apply()
それが属するレコードを保持し、その特定の F
インスタンスへのアクセスを保持します G
。
次に、現在のタスクに戻ります。 ここで、上記の例と同様に、ファクトリ関数を作成する必要があります。
2 つの繰り返しレイヤー インスタンス (1 つは前方、1 つは後方) を作成し、両方のレイヤー インスタンスを apply (x)
同じ x
に適用し、2 つの結果を連結する関数を定義するファクトリ関数を作成します。
さて、それを試してみてください! CNTK で後方再帰を実現する方法を知るには、前方再帰の実行方法からヒントを取得してください。 次の操作も行ってください。
- 前のタスクで追加した 1 単語の先読みを削除します。これは置換を目指しています。そして
- パラメーターを
hiddenDim
300 から 150 に変更して、モデル パラメーターの合計数を制限します。
このモデルを正常に実行すると、次の出力が生成されます。
Training 542379 parameters in 13 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 0.27651655 * 36007; errs = 5.288% * 36007
Finished Epoch[ 2 of 8]: [Training] ce = 0.08179804 * 36001; errs = 1.869% * 36001
Finished Epoch[ 3 of 8]: [Training] ce = 0.03528780 * 36005; errs = 0.828% * 36005
Finished Epoch[ 4 of 8]: [Training] ce = 0.02602517 * 35997; errs = 0.675% * 35997
Finished Epoch[ 5 of 8]: [Training] ce = 0.01310307 * 36000; errs = 0.386% * 36000
Finished Epoch[ 6 of 8]: [Training] ce = 0.01310714 * 35999; errs = 0.358% * 35999
Finished Epoch[ 7 of 8]: [Training] ce = 0.00900459 * 35997; errs = 0.300% * 35997
Finished Epoch[ 8 of 8]: [Training] ce = 0.00589050 * 36018; errs = 0.161% * 36018
Final Results: Minibatch[1-1]: errs = 1.830% * 10984; ce = 0.11924878 * 10984; perplexity = 1.12665017
魅力のように動作します! このモデルは 1.83% を達成し、上記の先読みモデルよりも少し優れています。 双方向モデルのパラメーターは先読みよりも 40% 少なくなります。 ただし、戻って完全なログ出力 (この Web ページには示されていません) をよく見ると、先読みの方が約 30% 速くトレーニングされていることがわかります。 これは、先読みモデルの水平方向の依存関係 (2 つの繰り返しではなく 1 つ) とより大きなマトリックス積の両方があり、より高い並列処理を実現できるためです。
ソリューションについては、 こちらを参照してください。
タスク 4: 意図の分類
これまでに構築したモデルは、意図分類子に簡単に変換できることがわかります。
データ ファイルには、この追加の列 S1
が含まれていることに注意してください。
この列には、文ごとに 1 つのラベルが含まれています。これは、クエリがトピックに関する情報を検索する意図をairport
airfare
示します。
シーケンス全体を 1 つのラベルに分類するタスクは、 シーケンス分類と呼ばれます。 シーケンス分類子は、最後の ステップの隠れた状態を取る再発LSTM(我々はすでにそれがあります)として実装されます。 これにより、シーケンスごとに 1 つのベクターが提供されます。 次に、このベクターは、softmax 分類のために高密度レイヤーに送り込まれます。
CNTK には、シーケンス BS.Sequences.Last()
から最後の状態を抽出する操作があります。
この操作は、同じミニバッチに非常に異なる長さのシーケンスが含まれている可能性があり、パックされた形式でメモリに配置されるという事実を尊重します。
同様に、後方再帰の場合も使用 BS.Sequences.First()
できます。
タスクは、タスク 3 から双方向ネットワークを変更して、最後のフレームが前方再帰から抽出され、最初のフレームが後方再帰から抽出され、2 つのベクトルが連結されるようにすることです。 連結ベクトル ( 思考ベクトルとも呼ばれます) は、密層の入力になります。
また、ラベルをスロットから意図ラベルに変更する必要があります。入力変数 (slotLabels
) の名前を変更するだけで、意図ラベルの閲覧者セクションで使用されている名前と一致し、ディメンションと一致します。
変更をお試しください。 しかし、正しく行うと、次のエラー メッセージと長いエラー メッセージが表示されます。
EXCEPTION occurred: Dynamic axis layout '*' is shared between inputs 'intentLabels'
and 'query', but layouts generated from the input data are incompatible on this axis.
Are you using different sequence lengths? Did you consider adding a DynamicAxis()
to the Input nodes?
"異なるシーケンス長を使用していますか?ああ、そうです! クエリと意図ラベル -- 意図ラベルは、クエリごとに 1 つのトークンのみです。 1 要素のシーケンスです。 それでは、これを修正する方法は?
CNTK を使用すると、ネットワーク内のさまざまな変数に異なるシーケンス長を指定できます。 シーケンスの長さは、追加のシンボリックテンソル次元と考えることができます。 同じ長さの変数は、同じシンボリック長ディメンションを共有します。 2 つの変数の長さが異なる場合は、これを明示的に宣言する必要があります。それ以外の場合、CNTK はすべての変数が同じシンボリック長を共有していると見なします。
これを行うには、次のように、新しい 動的軸 オブジェクトを作成し、それをいずれかの入力に関連付けます。
n = DynamicAxis()
query = Input {inputDim, dynamicAxis=n}
CNTK には既定の軸があります。 上記の例外から推測したように、その名前は '*' です。
したがって、新しい軸を 1 つだけ宣言する必要があります。もう一方の入力 (intentLabels
) は、引き続き既定の軸を使用します。
次に、実行して、次の出力を確認します。
Training 511376 parameters in 13 parameter tensors.
Finished Epoch[ 1 of 8]: [Training] ce = 1.17365003 * 2702; errs = 21.318% * 2702
Finished Epoch[ 2 of 8]: [Training] ce = 0.40112341 * 2677; errs = 9.189% * 2677
Finished Epoch[ 3 of 8]: [Training] ce = 0.17041608 * 2688; errs = 4.167% * 2688
Finished Epoch[ 4 of 8]: [Training] ce = 0.09521124 * 2702; errs = 2.739% * 2702
Finished Epoch[ 5 of 8]: [Training] ce = 0.08287138 * 2697; errs = 2.262% * 2697
Finished Epoch[ 6 of 8]: [Training] ce = 0.07138554 * 2707; errs = 2.032% * 2707
Finished Epoch[ 7 of 8]: [Training] ce = 0.06220047 * 2677; errs = 1.419% * 2677
Finished Epoch[ 8 of 8]: [Training] ce = 0.05072431 * 2686; errs = 1.340% * 2686
Final Results: Minibatch[1-1]: errs = 4.143% * 893; ce = 0.27832144 * 893; perplexity = 1.32091072
多くの労力をかけずに、エラー率は 4.1% を達成しました。 最初のショットには非常に良いです(このタスクの最先端ではありませんが、これは3%です)。
1つのことに気付くかもしれません:エポックあたりのサンプル数は現在約2700です。 これは、ラベル サンプルの数であり、文ごとに 1 つだけであるためです。 このタスクでは、監督シグナルの数が大幅に減っています。 これにより、ミニバッチのサイズを大きくすることをお勧めします。 70 ではなく 256 を試してみましょう。
Finished Epoch[ 1 of 8]: [Training] ce = 1.11500325 * 2702; errs = 19.282% * 2702
Finished Epoch[ 2 of 8]: [Training] ce = 0.29961089 * 2677; errs = 6.052% * 2677
Finished Epoch[ 3 of 8]: [Training] ce = 0.09018802 * 2688; errs = 2.418% * 2688
Finished Epoch[ 4 of 8]: [Training] ce = 0.04838102 * 2702; errs = 1.258% * 2702
Finished Epoch[ 5 of 8]: [Training] ce = 0.02996789 * 2697; errs = 0.704% * 2697
Finished Epoch[ 6 of 8]: [Training] ce = 0.02142932 * 2707; errs = 0.517% * 2707
Finished Epoch[ 7 of 8]: [Training] ce = 0.01220149 * 2677; errs = 0.299% * 2677
Finished Epoch[ 8 of 8]: [Training] ce = 0.01312233 * 2686; errs = 0.186% * 2686
このシステムは、はるかに良い学習! (ただし、この違いは、グラデーション正規化スキームによって fsAdagrad
引き起こされるアーティファクトである可能性が高く、通常は大きなデータ セットを使用するとすぐに消える可能性があることに注意してください)。
しかし、結果として得られるエラー率は高くなります。
Final Results: Minibatch[1-1]: errs = 4.479% * 893; ce = 0.31638223 * 893; perplexity = 1.37215463
しかし、この違いは実際には3つのエラーに相当しますが、これは重要ではありません。
ソリューションについては、 こちらを参照してください。
タスク 5: 並列トレーニング
最後に、複数の GPU がある場合、CNTK では、MPI (メッセージ渡しインターフェイス) を使用してトレーニングを並列化できます。 このモデルは小さすぎて、速度が上がるとは思いません。このような小さなモデルを並列化すると、使用可能な GPU の使用率が大幅に低下します。 ただし、実際のワークロードに移行した後に実行する方法を把握できるように、モーションを実行してみましょう。
ブロックに次の行を SGD
追加してください。
SGD = {
...
parallelTrain = {
parallelizationMethod = "DataParallelSGD"
parallelizationStartEpoch = 1
distributedMBReading = true
dataParallelSGD = { gradientBits = 2 }
}
}
次のコマンドを実行します。
mpiexec -np 4 cntk configFile=SLUHandsOn_Solution4.cntk stderr=Models/log parallelTrain=true command=TrainTagger
これにより、1 ビット SGD アルゴリズム (この場合は実際には 2 ビット SGD) を使用して、4 つの GPU でトレーニングが実行されます。
その近似値は精度を損ないませんでした:エラー率は4.367%で、さらに2つのエラーがあります(1つのGPUでアクションを TestTagger
個別に実行してください)。
まとめ
このチュートリアルでは、ネットワークを表すコンパクトな手段として関数構成スタイルを導入しました。 多くのニューラル ネットワークの種類は、このように表現するのに適しています。これは、グラフをネットワークの説明に直接、エラーが発生しやすい変換です。
このチュートリアルでは、関数構成スタイルで既存の構成を取得し、特定の方法で変更する方法を練習しました。
- レイヤーの追加 (定義済みのレイヤーのギャラリーから)
- 関数の定義と使用
- レイヤー ファクトリ関数の定義と使用
このチュートリアルでは、複数の時間ディメンションの処理についても説明し、トレーニングを並列化する方法についても説明しました。
ソリューション
上記のタスクの解決策を次に示します。 ねえ、不正行為はありません!
解決策 1: バッチ正規化の追加
変更されたモデル関数には、次の形式があります。
model = Sequential (
EmbeddingLayer {embDim} : # embedding
BatchNormalizationLayer {} : ##### added
RecurrentLSTMLayer {hiddenDim, goBackwards=false} : # LSTM
BatchNormalizationLayer {} : ##### added
DenseLayer {labelDim} # output layer
)
解決策 2: 先読みを追加する
先読み関数は次のように定義できます。
OneWordLookahead (x) = Splice (x : DelayLayer {T=-1} (x))
次のようにモデルに挿入されます。
model = Sequential (
EmbeddingLayer {embDim} :
OneWordLookahead : ##### added
BatchNormalizationLayer {} :
RecurrentLSTMLayer {hiddenDim, goBackwards=false} :
BatchNormalizationLayer {} :
DenseLayer {labelDim}
)
解決策 3: 双方向リカレント モデル
双方向のリカレント 層は、次のように記述できます。
BiRecurrentLSTMLayer {outDim} = {
F = RecurrentLSTMLayer {outDim, goBackwards=false}
G = RecurrentLSTMLayer {outDim, goBackwards=true}
apply (x) = Splice (F(x):G(x))
}.apply
次のように使用されます。
hiddenDim = 150 ##### changed from 300 to 150
model = Sequential (
EmbeddingLayer {embDim} :
###OneWordLookahead : ##### removed
BatchNormalizationLayer {} :
BiRecurrentLSTMLayer {hiddenDim} :
BatchNormalizationLayer {} :
DenseLayer {labelDim}
)
解決策 4: 意図の分類
シーケンスを、繰り返しレイヤーの最後または最初の非表示に減らします。
apply (x) = Splice (BS.Sequences.Last(F(x)):BS.Sequences.First(G(x)))
##### added Last() and First() calls ^^^
ラベル入力をスロットから意図に変更します。
intentDim = $numIntents$ ###### name change
...
DenseLayer {intentDim} ##### different dimension
...
intentLabels = Input {intentDim}
...
ce = CrossEntropyWithSoftmax (intentLabels, z)
errs = ErrorPrediction (intentLabels, z)
...
labelNodes = (intentLabels)
新しい動的軸を使用します。
n = DynamicAxis() ##### added
query = Input {inputDim, dynamicAxis=n} ##### use dynamic axis
Acknowledgement (受信確認)
このチュートリアルの基礎を準備してくれた Derek Liu に感謝します。