tf.layers で重み減衰

TensorFlowで`tf.layers`モジュールを使った場合の正則化の仕方の個人的なメモです．

## 環境
- Python 3.6.4
- TensorFlow 1.6.1 (cpu)

## 重み減衰
`tf.layers`モジュールを使うと，TensorFlowが楽に使えることを最近やっと知りました．ほぼ`tf.keras`モジュールと同じ様に使えてハッピーです．しかし，ある時過学習におちいり，ふと正則化にシンプルな重み減衰をしてみたいなぁと思いました．

`tf.layers.dence`などの`Variable`をともなう`tf.layers`モジュールには引数に`kernel_regularizer`や`bias_regularizer`があることは知っていたのですが，ここに正則化を行う関数(例えば`tf.contrib.layers.l2_regularizer`)を渡せば損失関数に勝手に正則化項が加わるのか? なわけないよな，と思い調べてみました．

おそらくですが`tf.layers`モジュールを使うときの`Variable`はモジュール内で`tf.get_variable`を用いて定義されているのだと思います[^1]．この関数の`tf.get_variable`の引数に`regularizer`があり，以下の様に書かれていました．

> **regularizer**: A (Tensor -> Tensor or None) function; the result of applying it on a newly created variable will be added to the collection `tf.GraphKeys.REGULARIZATION_LOSSES` and can be used for regularization.

つまり，`tf.layers.dence`などのモジュールの引数`kernel_regularizer`，`bias_regularizer`に正則化を行う関数`tf.contrib.layers.l2_regularizer`を渡せば，その関数が`tf.get_variable`の引数の`regularizer`に渡り，`Variables`の重みの二乗和が`tf.GraphKeys.REGULARIZATION_LOSSES`でアクセスできる様になるということだと思います．

## 検証
### 実験コード

```python:hoge
import tensorflow as tf
sess = tf.InteractiveSession()
x = tf.placeholder(tf.float32, shape=[None, 784])

init = tf.constant_initializer(value=1)
regularizer = tf.contrib.layers.l2_regularizer(scale=0.01)

dence = tf.layers.dense(
    inputs=x,
    units=10,
    kernel_initializer=init,
    kernel_regularizer=regularizer)
weight_decay = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)

print(weight_decay)  # 何が入っているのか見てみる

sess.run(tf.global_variables_initializer())

print(weight_decay[0].eval())  # 値を見てみる
```

### 実行結果

```
[<tf.Tensor 'dense/kernel/Regularizer/l2_regularizer:0' shape=() dtype=float32>]
39.2
```

ここで，この実行結果が先ほどの推測とあっているのか確かめます．損失関数に加わる重み減衰の式は次の様になります．

$$
\frac{1}{2}\times\lambda \times\boldsymbol{w}^T \boldsymbol{w}
$$
$\lambda$は正則化係数です．

まず，重み行列$W$は$784 \times 10$の行列です．また全て$1$で初期化しました．

$$
W = \begin{pmatrix}
w_{1, 1} = 1 & \cdots & w_{1, j}=1 & \cdots & w_{1, 10}=1 \\\
\vdots &  &   \vdots     &        & \vdots \\\
w_{i, 1} = 1 &        & w_{i, j} = 1 &        & w_{i, 10}=1 \\\
\vdots &        &    \vdots    &  & \vdots \\\
w_{784, 1} =1 & \cdots & w_{784, j}=1 & \cdots & w_{784, 10}=1
\end{pmatrix}
$$

`tf.contrib.layers.l2__regularizer`は重みの二乗和の半分に正則化係数(今だと0.01)をかける関数でした．
なので，重み行列$W$に`tf.contrib.layers.l2__regularizer`を適用した後の値は
$$
(784 \times 10) \times 1^2 \times 0.01 \times \frac{1}{2} = 39.2
$$
です．実行結果とあってます．よかった．
ちなみに`kernel_regularizer`引数に`tf.keras.regularizers.l2`を渡すことができますが，こちらは$1/2$倍をしないので注意です．

## 重み減衰の力を知る

重み減衰の威力を説明するいい実験結果ではないのですが，mnistを使った実験をしたので載せときます．
モデルはMultiPerceptronで層数は３つで，隠れ層の隠れ素子を256としました．コードはgistにのせときます．

- https://gist.github.com/haru-256/92167c724f7aaf9d8c3e74ab7f16c3c8

結果は下の図の様になりました．見にくくてごめんなさい．
重み減衰を課した場合の方が早く収束していることがわかります．また，200エポックまで学習を進めたのにも関わらず，過学習を抑制していることがわかります．
重み減衰を課さなかった場合は，最後らへんに過学習を引き起こしているのが見て取れます．

![without_weight_decay.png](https://qiita-image-store.s3.amazonaws.com/0/200359/13ea2fcc-16d2-d938-42d8-b034e9bda957.png "without_weight_decay.png")

![weight_decay.png](https://qiita-image-store.s3.amazonaws.com/0/200359/d6b20963-97c0-7c9a-af63-50a951da1cca.png "weight_decay.png")


## Seed値について
どうでもいいことなのですがSeedについて．実験を行うときにSeed値を固定しないと実験の意味がないのですが，かなりハマったのでメモしときます．
TensorFlowでSeed値を決めるときは`tf.set_random_seed`を使うのですが，実は`tf.set_random_seed`は現在の`default graph`にのみに影響するらしいです．なので，`default graph`を決定した後に`tf.set_random_seed`を使うのが良いです．

以下の記事に丁寧に書いてありました．

- https://stackoverflow.com/questions/36288235/how-to-get-stable-results-with-tensorflow-setting-random-seed
- https://qiita.com/yuyakato/items/9a5d80e6c7c41e9a9d22

ここからが自分的にかなりハマりました(笑)
今回の実験の題材にTensorFlowが提供するmnistを扱いましたが，コードで`mnist.train.next_batch`を使っています．`tf.set_random_seed`を適切に設定したはずなのになぜか結果が違ってイライラしましたが[mnist.train.next_batch()のコード](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/learn/python/learn/datasets/mnist.py)をみて思いました．

**numpy.random.shuffle**つこうてるやん!!!

`numpy.random.seed`を設定してめでたく再現性を保てました．疲れた．
numpyは`mnist.train.next_batch`を使う前であれば多分どこに`np.random.seed`を記述しても大丈夫だと思います．たぶん…

[^1]: 以下に`tf.layers`のコードがあるのですが，めんどくさくて追うのをやめました．  https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/layers/core.py。