TensorFlow-Slimを逆にたどっていきながら理解する【学習（トレーニング）編】

TensorFlowの簡単便利ライブラリ「[TensorFlow-Slim](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim)」に関して、わかりやすい資料が無かったので、勉強しながらまとめていきます。

ただ普通の順番で勉強していってもつまんないので、肝心の関数から、そこに渡すこの値はなんだ？みたいな感じで、逆にたどっていくようにします。

なお、長くなりそうなので、いくつかに分割します。

# 前提情報
TensorFlow-Slimは「tensorflow/contrib/slim」にあります。
そこで、すでに

```python
import tensorflow as tf
import tensorflow.contrib.slim as slim
```
が行われているものとします。

# 学習（トレーニング）
学習を行う関数は「slim.learning.train()」です。
session.run()とか呼ばずに、この関数を呼ぶとすぐに学習を始めます。
学習データの読み込みも、この関数が呼ばれたときに実行されます。
（この関数内でsession.run()が呼ばれているようなイメージです）

こんな書き方をします。

```python
slim.learning.train(
    train_op,
    logdir,
    number_of_steps=1000,
    save_summaries_secs=300,
    save_interval_secs=600):
```
引数の意味を調べてみます。

| 変数名 | 意味 | 初期値 |
|---|---|---|
| train_op | 「train_step_fn」に渡される学習処理<br/>実行時に勾配を計算してロス値を返す | － |
| logdir | 学習ログが書き込まれるディレクトリ<br/>Noneの場合、モデルのチェックポイントとサマリは書き込まれません | － |
| train_step_fn | 1回の勾配計算を実行するために呼び出す関数<br/>4つの引数（現在のセッション、学習処理、グローバルトレーニングのステップ、キーワード引数の辞書）が必要です | train_step |
| train_step_kwargs | 「train_step_fn」に渡されるキーワード引数の辞書<br/>デフォルトでは「should_stop」と「should_log」と呼ばれるスカラー演算の2つのbool値が提供されます | _USE_DEFAULT |
| log_every_n_steps | グローバル・ステップの観点から、損失とグローバル・ステップがログに記録される頻度| 1 |
| graph | スーパーバイザに渡すグラフ<br/>設定されていない場合、デフォルトグラフが使用されます | None |
| master | TensorFlowマスターのアドレス | '' |
| is_chief | レプリカトレーニング中にプライマリレプリカによってトレーニングが実行されているかどうかを指定します | True |
| global_step | 「train_step_fn」に渡されるグローバルトレーニングのステップ<br/>Noneのままにすると、training_util.get_or_create_global_step()つまりtf.contrib.framework.global_step()が使用されます | None |
| number_of_steps | 「global_step」で測定した、学習中に行う配計算の最大数<br/>「global_step」が「number_of_steps」より大きい場合、学習は停止します<br/>値がNoneの場合、学習は無期限に実行されます | None |
| init_op | 初期化処理<br/>デフォルト値のままにすると、セッションはtf.global_variables_initializer()で初期化されます | _USE_DEFAULT |
| init_feed_dict | 「init_op」を実行するときに使うフィード辞書 | None |
| local_init_op | ローカル初期化処理<br/>デフォルト値のままにすると、セッションはtf.local_variables_initializer()とtf.tables_initializer()で初期化されます | _USE_DEFAULT |
| init_fn | 「init_op」の後に実行されるオプションの呼び出し関数<br/>初期化されたセッションを引数として渡します | None |
| ready_op | モデルが使用可能かどうかを確認する処理<br/>デフォルト値のままにすると、セッションはtf.report_uninitialized_variables()を呼び出すことによって準備が整っているかどうかをチェックします | _USE_DEFAULT |
| summary_op | サマリー処理 | _USE_DEFAULT |
| save_summaries_secs | サマリーを保存する頻度（秒単位） | 600 |
| summary_writer | 「SummaryWriter」を使用します<br/>サマリーを書く必要がないときはNoneを指定します<br/>未設定の場合、SummaryWriterを作成します | _USE_DEFAULT |
| startup_delay_steps | 開始するまでに待機するステップ数<br/>「sync_optimizer」が指定されている場合、これは0でなければならないことに注意してください | 0 |
| saver | チェックポイントを保存するSaver<br/>Noneの場合、デフォルトのものが作成され使用されます | None |
| save_interval_secs | モデルを「logdir」に保存する頻度（秒単位） | 600 |
| sync_optimizer | tf.train.SyncReplicasOptimizerのインスタンス、またはそれらのリスト<br/>引数を指定すると、勾配の更新は同期します<br/>Noneのままにすると、勾配の更新は非同期になります | None |
| session_config | 「Session」を設定するために使われるtf.ConfigProtoのインスタンス<br/>Noneのままにすると、デフォルトが使用されます | None |
| session_wrapper | tf.Sessionオブジェクトを唯一の引数としてとり、元のオブジェクトと同じメソッドを持つラップされたセッションオブジェクトを返す関数、または、Noneになります<br/>Noneでなければ、ラップされたオブジェクトは学習に使用されます | None |
| trace_every_n_steps | Chromeのトレース形式で「Timeline」を生成して保存し、「trace_every_n_steps」ごとにサマリーに追加します<br/>Noneの場合、トレース情報は生成されず保存されません | None |
| ignore_live_threads | Trueの場合は、RuntimeErrorを送出するのではなく、スーパーバイザを停止するときに猶予期間のあいだに実行されているスレッドを無視します | False |

サマリーはTensorBoardで表示する際に使います。
※ここでは説明していませんが、tf.Graph()を呼ぶとスコープ内で宣言した変数が全部確認できます

```python
with tf.Graph().as_default() as graph:
```

このうち「train_op」「logdir」が（ほぼ）必須となります。
（あと、止めるために「number_of_steps」も必要かも（説明は省略））
ということで、それぞれ何者か、もう少し詳しく見ていきます。

## train_op
これが肝心の学習処理になります。

一番簡単に作成する方法は、slimの関数を使用して、loss処理と最適化処理から作成する「slim.learning.create_train_op()」になります。

```python
train_op = slim.learning.create_train_op(total_loss, optimizer)
```

もちろん、ガリガリと自分で細かく記述することも可能です。

### 損失処理
損失処理もslimの関数「slim.losses.get_total_loss()」で簡単に作ることが出来ます。

```python
total_loss = slim.losses.get_total_loss()
```

これはlossの合計になりますので、そこにlossをため込む必要があります。
例えば複数クラス分類の場合、「slim.losses.softmax_cross_entropy()」が使えます。

```python
slim.losses.softmax_cross_entropy(
        predictions,
        one_hot_labels)
```

#### 学習処理
ここはいろいろと工夫のし甲斐があります。

まず、slimにいくつかの種類のレイヤーが用意されています。
※代表的なものだけ

| 関数 | 種類 |
|---|---|
| slim.conv2d() | 2次元Convolution |
| slim.batch_norm() | Batch Normalization |
| slim.max_pool2d() | 2次元MaxPooling |
| slim.flatten() | 1次元化 |
| slim.fully_connected() | 全結合 |
| slim.dropout() | ドロップアウト |

```python
def lenet(images):
    net = slim.conv2d(images, 20, [5,5], scope='conv1')
    net = slim.max_pool2d(net, [2,2], scope='pool1')
    net = slim.conv2d(net, 50, [5,5], scope='conv2')
    net = slim.max_pool2d(net, [2,2], scope='pool2')
    net = slim.flatten(net, scope='flatten3')
    net = slim.fully_connected(net, 500, scope='fc4')
    net = slim.fully_connected(net, 10, activation_fn=None, scope='fc5')
    return net
```

レイヤーの定義は簡単なのですが、学習データを用意するところが意外と手間なので、[別の機会](https://qiita.com/mine820/items/c8ea5d0af07290e114a0)に説明したいと思います。

##### スコープ
スコープという機能を使うと、同じレイヤーに同じ値をいちいち設定しなくて済むという利点があります。

```python
with slim.arg_scope([slim.conv2d], padding='SAME',
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.01)
                      weights_regularizer=slim.l2_regularizer(0.0005)):
    net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
    net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
    net = slim.conv2d(net, 256, [11, 11], scope='conv3')
```
この場合、「slim.arg_scope()」で「slim.conv2d()」のパラメータ

- padding
- weights_initializer
- weights_regularizer
をまとめて設定しています。

### 最適化処理
最適化処理はいろいろの手法があって、それら全部の実装方法を説明していくわけにはいかないので、やはり簡単な方法を。

「tf.train.RMSPropOptimizer()」はRMSPropアルゴリズムの実装になります。

```python
optimizer = tf.train.RMSPropOptimizer(0.001, 0.9)
```

「tf.train.AdamOptimizer()」はAdamアルゴリズムの実装になります。

```python
optimizer = tf.train.AdamOptimizer(0.001)
```


## logdir
学習済みのモデル（ここでは「チェックポイント」と呼んでいます）を保存するディレクトリになります。学習してチェックポイントを保存しないわけにはいかないので、必ず指定することになります。

チェックポイントはtf.train.Saverで保存します。
出力されてるファイルは、以下の4つになります。

| 種類 | 内容 | ファイル名（例） |
|---|---|---|
| 管理ファイル | 保存されているチェックポイントの管理を行います<br/>テキストファイルなので中身が読めます | checkpoint |
| モデル構造記述 | 構築したモデルの構造を記述<br/>パラメータ値（重みとか）は保存しない | model.ckpt-0.meta |
| インデックス | 各パラメータへの参照インデックス | model.ckpt-0.index |
| パラメータ値 | 実際のパラメータ値（重みとか）を保存している | model.ckpt-0.data-00000-of-00001 |
※デフォルトは、5世代分しかチェックポイントを保存しない

これらのファイルは、評価や識別時に読み込まれて使用されます。
（tf.train.Saver.restore()で読み込みます）

保存する間隔は「save_interval_secs」で設定します。ステップ数ではなく時間というのがいまいちな感じですが。

# 備考
評価に関しては[こちら](https://qiita.com/mine820/items/4272d7998d9bf1ce459a)を参照してください。