KerasによるLSTMの高速化: cuDNNLSTM


# この記事のゴール

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

## おまけ
Kerasに関する書籍を翻訳しました。画像識別、画像生成、自然言語処理、時系列予測、強化学習まで幅広くカバーしています。
[直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ](https://www.amazon.co.jp/Deep-Learning-%E2%80%95Python%C3%97Keras%E3%81%A7%E3%82%A2%E3%82%A4%E3%83%87%E3%82%A2%E3%82%92%E5%BD%A2%E3%81%AB%E3%81%99%E3%82%8B%E3%83%AC%E3%82%B7%E3%83%94-Antonio-Gulli/dp/4873118263/ref=sr_1_1?s=books&ie=UTF8&qid=1530227887&sr=1-1&keywords=%E7%9B%B4%E6%84%9F+Deep+Learning)

# 早く試したい人へ

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

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

使用方法

```py
model = Sequential()
model.add(CuDNNLSTM(hidden_number))
```

# 結果

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

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

[Optimizing Recurrent Neural Networks in cuDNN 5](https://devblogs.nvidia.com/parallelforall/optimizing-recurrent-neural-networks-cudnn-5/)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/10496/f4d334a3-1aeb-e661-c5c8-1d6c960e196c.png)


# 詳細

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

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

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

```py
x = CuDNNLSTM(hidden_number)(x)
```

注意点：

- TensorboardのEmbeddingsによる可視化には対応していません。
- Dropoutにも対応していません。
- Bidirectionalで使用する時はいくつかコメントアウトして使用する必要があります。

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

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

```py
        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](https://devblogs.nvidia.com/parallelforall/optimizing-recurrent-neural-networks-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://qiita-image-store.s3.amazonaws.com/0/10496/431f0f39-3221-7b12-de5a-b781fca9c7c4.png)


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

## FUSING POINT-WISE OPERATIONS

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/10496/d5486ca7-2830-08a9-ae3e-ac3e66fe7b13.png)

# Optimizing Many Iterations

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

## PRE-TRANSPOSING THE WEIGHT MATRIX

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

## COMBINING INPUT GEMMS

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

## Optimization with Many Layers

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/10496/4d5cd891-7dcb-5c06-6d2f-7bc7fbc86450.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://devblogs.nvidia.com/parallelforall/optimizing-recurrent-neural-networks-cudnn-5/)

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