言語処理100本ノック(2020)-83: ミニバッチ化・GPU上での学習(Keras)

[言語処理100本ノック 2020 (Rev2)](https://nlp100.github.io/ja/)の[「第9章: RNN, CNN」](https://nlp100.github.io/ja/ch09.html)の[83本目「ミニバッチ化・GPU上での学習」](https://nlp100.github.io/ja/ch09.html#83-%E3%83%9F%E3%83%8B%E3%83%90%E3%83%83%E3%83%81%E5%8C%96gpu%E4%B8%8A%E3%81%A7%E3%81%AE%E5%AD%A6%E7%BF%92)記録です。基本的にほとんど前回ノックと変わらないです。プログラム自体はもともとGPUで動くように気をつけていましたし、Google Colaboratry使っていたのでCPUからGPUへの切替も非常に簡単。バッチサイズも関数にパラメータ渡すだけです。
[記事「まとめ: 言語処理100本ノックで学べることと成果」](https://qiita.com/FukuharaYohei/items/6f0af3c4e270d8af2d99)に[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)についてはまとめていますが、追加で差分の[言語処理100本ノック 2020 (Rev2)](https://nlp100.github.io/ja/)についても更新します。

# 参考リンク

|リンク|備考|
|:--|:--|
|[83_ミニバッチ化・GPU上での学習.ipynb](https://github.com/YoheiFukuhara/nlp100-2020/blob/main/09.RNN%2CCNN/83_%E3%83%9F%E3%83%8B%E3%83%90%E3%83%83%E3%83%81%E5%8C%96%E3%83%BBGPU%E4%B8%8A%E3%81%A7%E3%81%AE%E5%AD%A6%E7%BF%92.ipynb)|回答プログラムのGitHubリンク|
|[言語処理100本ノック 2020 第9章: RNN, CNN](https://qiita.com/nymwa/items/4542b45837a10766890b)|(PyTorchだけど)解き方の参考|
|[【言語処理100本ノック 2020】第9章: RNN, CNN](https://qiita.com/yamaru/items/6dbd1a44f5910d8848f6)|(PyTorchだけど)解き方の参考|
|[まとめ: 言語処理100本ノックで学べることと成果](https://qiita.com/FukuharaYohei/items/6f0af3c4e270d8af2d99)|言語処理100本ノックまとめ記事|

# 環境
後々GPUを使わないと厳しいので、Google Colaboratory使いました。Pythonやそのパッケージでより新しいバージョンありますが、新機能使っていないので、プリインストールされているものをそのまま使っています。

|種類|バージョン|内容|
|:--|:--|:--|
|Python|3.7.12|Google Colaboratoryのバージョン|
|google|2.0.3|Google Driveのマウントに使用|
|tensorflow|2.7.0|ディープラーニングの主要処理|
|nltk|3.2.5|Tokenの辞書作成に使用|
|pandas|1.1.5|行列に関する処理に使用|

# [第8章: ニューラルネット](https://nlp100.github.io/ja/ch08.html)
## 学習内容
> 深層学習フレームワークを用い，再帰型ニューラルネットワーク（RNN）や畳み込みニューラルネットワーク（CNN）を実装します．

### [83. ミニバッチ化・GPU上での学習](https://nlp100.github.io/ja/ch09.html#83-%E3%83%9F%E3%83%8B%E3%83%90%E3%83%83%E3%83%81%E5%8C%96gpu%E4%B8%8A%E3%81%A7%E3%81%AE%E5%AD%A6%E7%BF%92)
> 問題82のコードを改変し，B事例ごとに損失・勾配を計算して学習を行えるようにせよ（Bの値は適当に選べ）．また，GPU上で学習を実行せよ．

# 回答
## 回答結果
回答でもないですが訓練時間について言及します。
GPUを使うことで、訓練時間が13分25秒から7分25秒へと約半減しました。Jupyterのマジックコマンド`%%time`で測っています。Google ColaboratoryのGPUはあまり速くない印象があります。ただ、それ以上に今回はRNNだからかそこまで速くならないです。

```:GPU使用時
CPU times: user 7min 39s, sys: 1min 7s, total: 8min 47s
Wall time: 7min 25s
```

```:CPU使用時
CPU times: user 20min 50s, sys: 47.2 s, total: 21min 37s
Wall time: 13min 25s
```

## 回答プログラム [83_ミニバッチ化・GPU上での学習.ipynb](https://github.com/YoheiFukuhara/nlp100-2020/blob/main/09.RNN%2CCNN/83_%E3%83%9F%E3%83%8B%E3%83%90%E3%83%83%E3%83%81%E5%8C%96%E3%83%BBGPU%E4%B8%8A%E3%81%A7%E3%81%AE%E5%AD%A6%E7%BF%92.ipynb)
GitHubには確認用コードも含めていますが、ここには必要なものだけ載せています。

```python
import nltk
import pandas as pd
import tensorflow as tf
from google.colab import drive

drive.mount('/content/drive')

BASE_PATH = '/content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/06.MachineLearning/'
max_len = 0
vocabulary = []


def read_dataset(type_):
    global max_len
    global vocabulary
    df = pd.read_table(BASE_PATH+type_+'.feature.txt')
    df.info()
    sr_title = df['title'].str.split().explode()
    max_len_ = df['title'].map(lambda x: len(x.split())).max()
    if max_len < max_len_:
        max_len = max_len_
    if len(vocabulary) == 0:
        vocabulary = [k for k, v in nltk.FreqDist(sr_title).items() if v > 1]
    else:
        vocabulary.extend([k for k, v in nltk.FreqDist(sr_title).items() if v > 1])
    y = df['category'].replace({'b':0, 't':1, 'e':2, 'm':3})
    return df['title'], tf.keras.utils.to_categorical(y, dtype='int32')  # 4値分類なので訓練・検証・テスト共通でone-hot化


X_train, y_train = read_dataset('train')
X_valid, y_valid = read_dataset('valid')
X_test, y_test = read_dataset('test') # あまりこだわらずにテストデータセットも追加

# setで重複削除し、タプル形式に設定
tup_voc = tuple(set(vocabulary))

print(f'vocabulary size before removing duplicates: {len(vocabulary)}')
print(f'vocabulary size after removing duplicates: {len(tup_voc)}')
print(f'sample vocabulary: {tup_voc[:10]}')
print(f'max length is {max_len}')

vectorize_layer = tf.keras.layers.TextVectorization(
 output_mode='int',
 vocabulary=tup_voc,
 output_sequence_length=max_len)

print(f'sample vocabulary: {vectorize_layer.get_vocabulary()[:10]}')

model = tf.keras.models.Sequential()
model.add(tf.keras.Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)
model.add(tf.keras.layers.Embedding(vectorize_layer.vocabulary_size(), 300, mask_zero=True))
model.add(tf.keras.layers.GRU(50))
model.add(tf.keras.layers.Dense(4, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['acc'])
model.summary()

model.fit(X_train, y_train, batch_size=32, epochs=100, validation_data=(X_valid, y_valid))

model.evaluate(X_test, y_test)
```

## 回答解説
### バッチサイズ変更
バッチサイズとして`batch_size`に値を渡しています。とはいえ、32はデフォルト値だから実質的に何も変わっていません。CPUとGPUで時間比較をしたかったので、あえて同じにしました。

```python:
model.fit(X_train, y_train, batch_size=32, epochs=100, validation_data=(X_valid, y_valid))
```

### GPU使用
GPUを使用するためには、Google ColaboratoryのメニューでRuntime -> Change Runtime Type からGPUを選択するだけです。


### RNNでGRUの条件
[公式のGRU API説明](https://www.tensorflow.org/api_docs/python/tf/keras/layers/GRU/)に以下の前提記述があります。前提記述に従ってパラメータをデフォルトにしています。また、`Embedding`層での
mask使用とTextVectorization層もright-paddedも条件に準じています。TextVectorization層についてはAPI説明にright-paddedに関する明記はないですが、[「言語処理100本ノック(2020)-80: ID番号への変換(keras)」](https://qiita.com/FukuharaYohei/items/b87d55fa9cb10e0e7e02)で確認済)。

> Based on available runtime hardware and constraints, this layer will choose different implementations (cuDNN-based or pure-TensorFlow) to maximize the performance. If a GPU is available and all the arguments to the layer meet the requirement of the cuDNN kernel (see below for details), the layer will use a fast cuDNN implementation.

> The requirements to use the cuDNN implementation are:
>
> 1. activation == tanh
> 2. recurrent_activation == sigmoid
> 3. recurrent_dropout == 0
> 4. unroll is False
> 5. use_bias is True
> 6. reset_after is True
> 7. Inputs, if use masking, are strictly right-padded.
> 8. Eager execution is enabled in the outermost context.

```python:
model = tf.keras.models.Sequential()
model.add(tf.keras.Input(shape=(1,), dtype=tf.string))
model.add(vectorize_layer)
model.add(tf.keras.layers.Embedding(vectorize_layer.vocabulary_size(), 300, mask_zero=True))
model.add(tf.keras.layers.GRU(50))
```