CNTK-リカレントニューラルネットワーク

ここで、CNTKでリカレントニューラルネットワーク(RNN)を構築する方法を理解しましょう。

前書き

ニューラルネットワークで画像を分類する方法を学びました。これはディープラーニングの象徴的な仕事の1つです。しかし、ニューラルネットワークが得意で多くの研究が行われているもう1つの分野は、リカレントニューラルネットワーク(RNN)です。ここでは、RNNとは何か、時系列データを処理する必要があるシナリオでRNNをどのように使用できるかを理解します。

リカレントニューラルネットワークとは何ですか?

リカレントニューラルネットワーク(RNN)は、時間の経過とともに推論できる特殊な種類のNNとして定義できます。RNNは主に、時間の経過とともに変化する値、つまり時系列データを処理する必要があるシナリオで使用されます。それをよりよく理解するために、通常のニューラルネットワークとリカレントニューラルネットワークを少し比較してみましょう-

  • ご存知のように、通常のニューラルネットワークでは、1つの入力しか提供できません。これにより、予測が1つだけになるように制限されます。例を挙げると、通常のニューラルネットワークを使用してテキストの翻訳ジョブを実行できます。

  • 一方、リカレントニューラルネットワークでは、単一の予測をもたらす一連のサンプルを提供できます。言い換えると、RNNを使用して、入力シーケンスに基づいて出力シーケンスを予測できます。たとえば、翻訳タスクでRNNを使用した実験はかなり成功しています。

リカレントニューラルネットワークの使用

RNNはいくつかの方法で使用できます。それらのいくつかは次のとおりです-

単一の出力を予測する

手順を深く掘り下げる前に、RNNがシーケンスに基づいて単一の出力を予測する方法について、基本的なRNNがどのように見えるかを見てみましょう-

上の図でわかるように、RNNには入力へのループバック接続が含まれており、値のシーケンスをフィードすると、シーケンス内の各要素がタイムステップとして処理されます。

さらに、ループバック接続により、RNNは生成された出力をシーケンス内の次の要素の入力と組み合わせることができます。このようにして、RNNは、予測を行うために使用できるシーケンス全体にわたってメモリを構築します。

RNNで予測を行うために、次の手順を実行できます-

  • まず、初期の非表示状態を作成するには、入力シーケンスの最初の要素をフィードする必要があります。

  • その後、更新された非表示状態を生成するには、初期の非表示状態を取得して、入力シーケンスの2番目の要素と組み合わせる必要があります。

  • 最後に、最終的な非表示状態を生成し、RNNの出力を予測するには、入力シーケンスの最後の要素を取得する必要があります。

このように、このループバック接続の助けを借りて、RNNに時間の経過とともに発生するパターンを認識するように教えることができます。

シーケンスの予測

上で説明したRNNの基本モデルは、他のユースケースにも拡張できます。たとえば、これを使用して、単一の入力に基づいて値のシーケンスを予測できます。このシナリオでは、RNNを使用して予測を行うために、次の手順を実行できます。

  • まず、初期の非表示状態を作成し、出力シーケンスの最初の要素を予測するには、入力サンプルをニューラルネットワークにフィードする必要があります。

  • その後、更新された非表示状態と出力シーケンスの2番目の要素を生成するには、初期の非表示状態を同じサンプルと組み合わせる必要があります。

  • 最後に、非表示状態をもう一度更新し、出力シーケンスの最後の要素を予測するために、サンプルをもう一度フィードします。

シーケンスの予測

シーケンスに基づいて単一の値を予測する方法と、単一の値に基づいてシーケンスを予測する方法を見てきました。次に、シーケンスのシーケンスを予測する方法を見てみましょう。このシナリオでは、RNNを使用して予測を行うために、次の手順を実行できます。

  • まず、初期の非表示状態を作成し、出力シーケンスの最初の要素を予測するには、入力シーケンスの最初の要素を取得する必要があります。

  • その後、非表示状態を更新し、出力シーケンスの2番目の要素を予測するには、最初の非表示状態を取得する必要があります。

  • 最後に、出力シーケンスの最後の要素を予測するには、更新された非表示状態と入力シーケンスの最後の要素を取得する必要があります。

RNNの働き

リカレントニューラルネットワーク(RNN)の動作を理解するには、まずネットワーク内のリカレント層がどのように機能するかを理解する必要があります。したがって、最初に、eが標準の反復層を使用して出力を予測する方法について説明しましょう。

標準のRNNレイヤーを使用した出力の予測

前に説明したように、RNNの基本レイヤーは、ニューラルネットワークの通常のレイヤーとはかなり異なります。前のセクションでは、RNNの基本的なアーキテクチャも図で示しました。初めてのステップインシーケンスの非表示状態を更新するには、次の式を使用できます。

上記の式では、初期の非表示状態と一連の重みの間の内積を計算することにより、新しい非表示状態を計算します。

次のステップでは、現在のタイムステップの非表示状態が、シーケンス内の次のタイムステップの初期非表示状態として使用されます。そのため、2番目のタイムステップの非表示状態を更新するには、次のように1番目のタイムステップで実行された計算を繰り返すことができます。

次に、以下のように、シーケンスの3番目の最後のステップで非表示状態を更新するプロセスを繰り返すことができます。

そして、上記のすべてのステップをシーケンスで処理したら、次のように出力を計算できます。

上記の式では、3番目の重みのセットと最後のタイムステップからの非表示状態を使用しました。

高度な回帰ユニット

基本的な反復層の主な問題は勾配消失問題であり、このため、長期的な相関関係の学習はあまり得意ではありません。簡単に言えば、基本的な反復層は長いシーケンスをうまく処理しません。これが、より長いシーケンスでの作業にはるかに適した他のいくつかの反復レイヤータイプが次のとおりである理由です。

長短期記憶(LSTM)

長短期記憶(LSTM)ネットワークは、Hochreiter&Schmidhuberによって導入されました。これにより、基本的な反復レイヤーに長い間物事を記憶させるという問題が解決されました。LSTMのアーキテクチャは、上の図に示されています。ご覧のとおり、入力ニューロン、メモリセル、出力ニューロンがあります。勾配消失問題と戦うために、長短期記憶ネットワークは明示的記憶セル(以前の値を格納)と次のゲートを使用します-

  • Forget gate−名前が示すように、以前の値を忘れるようにメモリセルに指示します。メモリセルは、ゲート、つまり「ゲートを忘れる」が値を忘れるように指示するまで値を格納します。

  • Input gate−名前が示すように、セルに新しいものを追加します。

  • Output gate−名前が示すように、出力ゲートは、セルから次の非表示状態にベクトルを渡すタイミングを決定します。

ゲート付き回帰ユニット(GRU)

Gradient recurrent units(GRU)は、LSTMネットワークのわずかなバリエーションです。ゲートが1つ少なく、LSTMとはわずかに異なる配線になっています。そのアーキテクチャは上の図に示されています。入力ニューロン、ゲートメモリセル、および出力ニューロンがあります。ゲート付き回帰ユニットネットワークには、次の2つのゲートがあります-

  • Update gate−次の2つを決定します−

    • 最後の状態からどのくらいの量の情報を保持する必要がありますか?

    • 前のレイヤーからどのくらいの量の情報を取り込む必要がありますか?

  • Reset gate−リセットゲートの機能は、LSTMネットワークの忘却ゲートの機能とよく似ています。唯一の違いは、位置が少し異なることです。

長期短期記憶ネットワークとは対照的に、ゲート付き回帰ユニットネットワークはわずかに高速で実行が簡単です。

RNN構造の作成

開始する前に、データソースからの出力について予測を行う前に、まずRNNを構築する必要があります。RNNの構築は、前のセクションで通常のニューラルネットワークを構築した場合とまったく同じです。以下はそれを構築するためのコードです-

from cntk.losses import squared_error
from cntk.io import CTFDeserializer, MinibatchSource, INFINITELY_REPEAT, StreamDefs, StreamDef
from cntk.learners import adam
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
BATCH_SIZE = 14 * 10
EPOCH_SIZE = 12434
EPOCHS = 10

複数のレイヤーをステーキング

CNTKで複数の反復レイヤーをスタックすることもできます。たとえば、次のレイヤーの組み合わせを使用できます-

from cntk import sequence, default_options, input_variable
from cntk.layers import Recurrence, LSTM, Dropout, Dense, Sequential, Fold
features = sequence.input_variable(1)
with default_options(initial_state = 0.1):
   model = Sequential([
      Fold(LSTM(15)),
      Dense(1)
   ])(features)
target = input_variable(1, dynamic_axes=model.dynamic_axes)

上記のコードでわかるように、CNTKでRNNをモデル化する方法は次の2つあります。

  • まず、リカレントレイヤーの最終出力のみが必要な場合は、 Fold GRU、LSTM、さらにはRNNStepなどのリカレントレイヤーと組み合わせたレイヤー。

  • 次に、別の方法として、 Recurrence ブロック。

時系列データを使用したRNNのトレーニング

モデルを構築したら、CNTKでRNNをトレーニングする方法を見てみましょう-

from cntk import Function
@Function
def criterion_factory(z, t):
   loss = squared_error(z, t)
   metric = squared_error(z, t)
   return loss, metric
loss = criterion_factory(model, target)
learner = adam(model.parameters, lr=0.005, momentum=0.9)

データをトレーニングプロセスにロードするには、一連のCTFファイルからシーケンスを逆シリアル化する必要があります。次のコードにはcreate_datasource 関数。トレーニングデータソースとテストデータソースの両方を作成するための便利なユーティリティ関数です。

target_stream = StreamDef(field='target', shape=1, is_sparse=False)
features_stream = StreamDef(field='features', shape=1, is_sparse=False)
deserializer = CTFDeserializer(filename, StreamDefs(features=features_stream, target=target_stream))
   datasource = MinibatchSource(deserializer, randomize=True, max_sweeps=sweeps)
return datasource
train_datasource = create_datasource('Training data filename.ctf')#we need to provide the location of training file we created from our dataset.
test_datasource = create_datasource('Test filename.ctf', sweeps=1) #we need to provide the location of testing file we created from our dataset.

これで、データソース、モデル、および損失関数を設定したので、トレーニングプロセスを開始できます。これは、基本的なニューラルネットワークを使用して前のセクションで行ったのと非常によく似ています。

progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
   features: train_datasource.streams.features,
   target: train_datasource.streams.target
}
history = loss.train(
   train_datasource,
   epoch_size=EPOCH_SIZE,
   parameter_learners=[learner],
   model_inputs_to_streams=input_map,
   callbacks=[progress_writer, test_config],
   minibatch_size=BATCH_SIZE,
   max_epochs=EPOCHS
)

次のような出力が得られます-

出力-

average  since  average  since  examples
loss      last  metric  last
------------------------------------------------------
Learning rate per minibatch: 0.005
0.4      0.4    0.4      0.4      19
0.4      0.4    0.4      0.4      59
0.452    0.495  0.452    0.495   129
[…]

モデルの検証

実際にRNNを使用して予測することは、他のCNKモデルを使用して予測を行うことと非常に似ています。唯一の違いは、単一のサンプルではなくシーケンスを提供する必要があることです。

これで、RNNのトレーニングが最終的に完了したので、次のようにいくつかのサンプルシーケンスを使用してモデルをテストすることでモデルを検証できます。

import pickle
with open('test_samples.pkl', 'rb') as test_file:
test_samples = pickle.load(test_file)
model(test_samples) * NORMALIZE

出力-

array([[ 8081.7905],
[16597.693 ],
[13335.17 ],
...,
[11275.804 ],
[15621.697 ],
[16875.555 ]], dtype=float32)