言語処理100本ノック(2020)-73: 確率的勾配降下法による学習(TensorFlow)

[言語処理100本ノック 2020 (Rev2)](https://nlp100.github.io/ja/)の[「第8章: ニューラルネット」](https://nlp100.github.io/ja/ch08.html)の[73本目「確率的勾配降下法による学習」](https://nlp100.github.io/ja/ch08.html#73-%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92)記録です。いよいよ訓練が始まり、機械学習っぽくなってきました。ここまで来るとやったことがある内容でスラスラ解けるようになりました(`Keras`がやってくれるし)。逆に改めて`keras`ではなく自分で計算すべきとも感じました。
[記事「まとめ: 言語処理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/)についても更新します。

# 参考リンク

|リンク|備考|
|:--|:--|
|[73_確率的勾配降下法による学習.ipynb](https://github.com/YoheiFukuhara/nlp100-2020/blob/main/08.%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A9%E3%83%AB%E3%83%8D%E3%83%83%E3%83%88/73_%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92.ipynb)|回答プログラムのGitHubリンク|
|[言語処理100本ノック 2020 第8章: ニューラルネット](https://qiita.com/nymwa/items/9c8484ff511123e03ba8)|(PyTorchだけど)解き方の参考|
|[【言語処理100本ノック 2020】第8章: ニューラルネット](https://qiita.com/yamaru/items/b54989d772c4323bde69)|(PyTorchだけど)解き方の参考|
|[まとめ: 言語処理100本ノックで学べることと成果](https://qiita.com/FukuharaYohei/items/6f0af3c4e270d8af2d99)|言語処理100本ノックまとめ記事|
|[【Keras入門(1)】単純なディープラーニングモデル定義](https://qiita.com/FukuharaYohei/items/579cc0184885b4ba75df)|Keras入門|

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

|種類|バージョン|内容|
|:--|:--|:--|
|Python|3.7.12|Google Colaboratoryのバージョン|
|google|2.0.3|Google Driveのマウントに使用|
|tensorflow|2.6.0|ディープラーニングの主要処理|

# [第8章: ニューラルネット](https://nlp100.github.io/ja/ch08.html)
## 学習内容
> 深層学習フレームワークの使い方を学び，ニューラルネットワークに基づくカテゴリ分類を実装します．

## ノック内容
> 第6章で取り組んだニュース記事のカテゴリ分類を題材として，ニューラルネットワークでカテゴリ分類モデルを実装する．なお，この章ではPyTorch, TensorFlow, Chainerなどの機械学習プラットフォームを活用せよ．

### [73. 確率的勾配降下法による学習](https://nlp100.github.io/ja/ch08.html#73-%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92)
> 確率的勾配降下法（SGD: Stochastic Gradient Descent）を用いて，行列$W$を学習せよ．なお，学習は適当な基準で終了させればよい（例えば「100エポックで終了」など）．

# 回答
## 回答プログラム [73_確率的勾配降下法による学習.ipynb](https://github.com/YoheiFukuhara/nlp100-2020/blob/main/08.%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A9%E3%83%AB%E3%83%8D%E3%83%83%E3%83%88/73_%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95%E3%81%AB%E3%82%88%E3%82%8B%E5%AD%A6%E7%BF%92.ipynb)
GitHubには確認用コードも含めていますが、ここには必要なものだけ載せています。

```python
import tensorflow as tf
from google.colab import drive

drive.mount('/content/drive')

BASE_PATH = '/content/drive/MyDrive/ColabNotebooks/ML/NLP100_2020/08.NeuralNetworks/'
train_raw = tf.data.TFRecordDataset(BASE_PATH+'train.tfrecord')

def _parse_function(example_proto):
    # 特徴の記述
    feature_description = {
        'title': tf.io.FixedLenFeature([], tf.string),
        'category': tf.io.FixedLenFeature([], tf.string)}
  
  # 上記の記述を使って入力の tf.Example を処理
    features = tf.io.parse_single_example(example_proto, feature_description)
    X = tf.io.decode_raw(features['title'], tf.float32)
    y = tf.io.decode_raw(features['category'], tf.int32)
    return X, y

model = tf.keras.Sequential(
    [tf.keras.layers.Dense(
        4, 
        activation='softmax', 
        use_bias=False,
        input_dim=300, 
        kernel_initializer='random_uniform') ])
model.compile(loss="categorical_crossentropy", optimizer="sgd")

train_ds = train_raw.map(_parse_function).shuffle(1000).batch(64)

model.fit(train_ds, epochs=100)
```

## 回答解説
### モデル定義
`keras`でSequentialモデルを定義します。

```python:
model = tf.keras.Sequential(
    [tf.keras.layers.Dense(
        4, 
        activation='softmax', 
        use_bias=False,
        input_dim=300, 
        kernel_initializer='random_uniform') ])
model.compile(loss="categorical_crossentropy", optimizer="sgd")
model.summary()
```

`summary`関数でモデル内容を出力。300次元×4クラスで1200の学習可能なパラメータ(行列W)があることがわかります。基本的な`keras`の使い方は[記事「【Keras入門(1)】単純なディープラーニングモデル定義」](https://qiita.com/FukuharaYohei/items/a23576f63795428ec651)に書きました。

```:結果
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 4)                 1200      
=================================================================
Total params: 1,200
Trainable params: 1,200
Non-trainable params: 0
```

### データセット設定
バッチサイズを64にしてデータセットを作ります。`shuffle`については[記事「TensorFlowのDataset.shuffleのbuffer_size」](https://qiita.com/exy81/items/d1388f6f02a11c8f1d7e)を見て理解しました。データセット全件をシャッフルしたかったのですが、TFrecord形式のデータから件数を取得する方法が、少し調べただけだとよくわからず、1000件シャッフルにしています。

```python:
train_ds = train_raw.map(_parse_function).shuffle(1000).batch(64)
print(train_ds)
```

```:結果
<BatchDataset shapes: ((None, None), (None, None)), types: (tf.float32, tf.int32)>
```

### 訓練実行
100エポック訓練をまわします。Google ColaboratoryでGPU使って2分半程度かかります。

```python:
%%time
model.fit(train_ds, epochs=100)
```

Lossが0.45くらいまで下がりました。そんな良くない結果です。後のノックで改善していくので結果は気にしないようにしました。

```:結果
Epoch 1/100
167/167 [==============================] - 5s 6ms/step - loss: 1.2911
Epoch 2/100
167/167 [==============================] - 1s 6ms/step - loss: 1.1737

中略

Epoch 99/100
167/167 [==============================] - 1s 6ms/step - loss: 0.4591
Epoch 100/100
167/167 [==============================] - 1s 5ms/step - loss: 0.4578
CPU times: user 1min 49s, sys: 36.6 s, total: 2min 25s
Wall time: 2min 1s
```