MachineLearning
DeepLearning
深層学習
Keras

KerasによるLSTMの高速化: cuDNNLSTM


この記事のゴール

lstmを高速に学習させたい人向け


おまけ

Kerasに関する書籍を翻訳しました。画像識別、画像生成、自然言語処理、時系列予測、強化学習まで幅広くカバーしています。

直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ


早く試したい人へ

kerasを2.0.8以上にアップグレードすれば使用できます。

pip install git+https://github.com/fchollet/keras.git

使用方法

model = Sequential()

model.add(CuDNNLSTM(hidden_number))


結果

参考記事によるとベースラインに比べ11倍程度早くなるそうです。

下記の記事はcuDNN5の記事なので注意が必要です。

Optimizing Recurrent Neural Networks in cuDNN 5

下記ではcuDNN6に比べ、cuDNN7の方が3倍ほど早くなっています。

image.png


詳細

GPU上でしか動作しませんが使用方法はとても簡単です。

https://keras.io/layers/recurrent/#cudnnlstm

functional api形式で記述すると下記のようになります。

x = CuDNNLSTM(hidden_number)(x)

注意点:


  • TensorboardのEmbeddingsによる可視化には対応していません。

  • Dropoutにも対応していません。

  • Bidirectionalで使用する時はいくつかコメントアウトして使用する必要があります。

通常のLSTMとの違いは下記の処理です。

下記に詳細がありますがcuDNNLSTMは生成と生成の間でバファーのサイズが変更される可能性があります。そのため重みとバイアスを保存して使用する必要があるためです。

https://www.tensorflow.org/api_docs/python/tf/contrib/cudnn_rnn/CudnnLSTM

        params = self._canonical_to_params(

weights=[
self.kernel_r,
self.kernel_z,
self.kernel_h,
self.recurrent_kernel_r,
self.recurrent_kernel_z,
self.recurrent_kernel_h,
],
biases=[
self.bias_r_i,
self.bias_z_i,
self.bias_h_i,
self.bias_r,
self.bias_z,
self.bias_h,
],
)

https://github.com/fchollet/keras/blob/62d097c4ff6fa694a4dbc670e9c7eb9e2bc27c74/keras/layers/cudnn_recurrent.py#L468

Tensorflowには下記のようにパラメーターを保存する仕組みがあります。

RNNParamsSaveable

https://github.com/tensorflow/tensorflow/blob/408fd454d7d2a16269576ea12bcd516e25a6b0c5/tensorflow/contrib/cudnn_rnn/python/ops/cudnn_rnn_ops.py#L62


高速化の仕組み

上記の方法で使用可能です。

ここからはcuDNNがどのようにしてLSTMを高速化しているか見ていきます。

Optimizing Recurrent Neural Networks in cuDNN 5


単一のイテレーションの最適化


COMBINING GEMM OPERATIONS

GEMMという行列のオペレーションがあります。GPUの特性上、並列性を上げればあげるほど高速になります。

入力されるのは下記の4つであり、重みも各入力ごとに存在します。


  • 入力

  • 出力

  • 忘却ゲート

  • 以前の出力

8つのパラメータに対して、それぞれ行列で値が存在することになります。

通常の入力ステップで4つの重み行列を1つに合わせる

再機処理ステップで4つの重み行列を1つに合わせる

行列一つずつを処理した場合は8つの行列演算が必要ですが、これによって2つの行列で動作可能です。

出力行列は16倍になります。

これによって下記に示されるcudaの並列性を効果的に扱えるようになり、計算速度が2倍程度になるようです。

http://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#occupancy


STREAMING GEMMS

下記のようにスレッド形式で並列に行列演算が可能なのでその機能を使用します。

Screen Shot 2017-10-23 at 16.52.39.png

https://devblogs.nvidia.com/parallelforall/gpu-pro-tip-cuda-7-streams-simplify-concurrency/


FUSING POINT-WISE OPERATIONS

下記のようにカーネルが別れるのでこれをまとめて処理することにグローバルメモリーとのオーバーヘッドが減り、5倍程度高速に!!

image.png


Optimizing Many Iterations

今まで個別処理の最適化でした。ここからはRNN特有の何度もイテレーション処理をするのでその部分の最適化になります。


PRE-TRANSPOSING THE WEIGHT MATRIX

重み行列を予め転置しておくことで標準のBLAS APIでの各行列演算処理の負荷を若干減らせます。転置のコストの方が各行列演算処理に置いて転置するよりも低コストです。


COMBINING INPUT GEMMS

入力のサイズをRNNのハイパーパラメータによって最適化する。入力全てを使用する方が一見、並列性が高いですが、データによって入力のサイズが異なるため一概に全てを使用することが好ましくないのでこのような処理を行います。


Optimization with Many Layers

RNNは前のレイヤーの値と一つ前の値に依存するため、その処理が終わったものは並列処理可能。下記の図だと赤の部分

image.png


結果

下記のような結果になります。

Optimization
GFLOPS
Speedup

Baseline
349
(1.0x)

Combined GEMMs
724
2.1x

GEMM Streaming
994
2.8x

Fused point-wise operations
1942
5.5x

Matrix pre-transposition
2199
6.3x

Combining Inputs
2290
6.5x

Four layers
3898
11.1x


参考

Optimizing Recurrent Neural Networks in cuDNN 5

https://keras.io/layers/recurrent/#cudnnlstm