【学習メモ】ゼロから作るDeepLearning 〜Dropoutの実装〜

#背景
最近Deep Learningを
「[ゼロから作るDeep Learning](https://www.amazon.co.jp/ゼロから作るDeep-Learning-―Pythonで学ぶディープラーニングの理論と実装-斎藤-康毅/dp/4873117585)」
で勉強し始めています。
dropoutの実装も載っているのですが、中身がどう動いているのかもう少し掘ってみました。

#droputとは？
機械学習では、学習データに特化してしまう過学習という問題があります。
過学習に関してですが、文章と意味を暗記して他の文章の意味を理解しようとした場合を例に挙げてみます。暗記した文章で「はし」という言葉が「橋」という意味でよく使われたとして、他の文章で「はし」が「端」として使われてても「橋」として理解してしまうような状況を指します。（あくまでも僕の現状の理解なので、間違ってたらコメントください。）
それを防ぐのがDropoutという手法です。もう少し詳しい説明は[ここ](https://deepage.net/deep_learning/2016/10/17/deeplearning_dropout.html)を参照してください。

#dropoutの実装
ここでは、
・python3
・numpy
を使っています。
では、Dropoutレイヤーの実装を見てみましょう。

```python
import numpy as np

class Dropout:
    """
    http://arxiv.org/abs/1207.0580
    """
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask
```

#動かしながら中身の処理を確認
まず、初期化の部分です。

```python
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None
```
ここは単純に引数のdropout_ratioを内部変数につめるのと、dropoutのマスクを初期化します。

次に、順伝搬（forward）部分です。

```python
    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)
```
train_flgは学習時にTrue、推論時にFalseにします。この辺の詳しい話は「[ゼロから作るDeep Learning](https://www.amazon.co.jp/ゼロから作るDeep-Learning-―Pythonで学ぶディープラーニングの理論と実装-斎藤-康毅/dp/4873117585)」を読んでみてください。
それぞれの処理を分解して、Shellで処理内容を見ていきましょう（ここでの値は一例です）。

```sh
#まずは適当なinputとなるxを生成
>>> import numpy as np
>>> x = np.array([0,1,2,3,4])
>>> x
array([0, 1, 2, 3, 4])

#np.random(rand(*x.shape)の出力
>>> rand = np.random.rand(*x.shape)
>>> rand
array([0.15816005, 0.03610269, 0.86484777, 0.2235985 , 0.64981875])

#np.random.rand(*x.shape) > self.dropout_ratio（ここでは0.5にしています）の出力
>>> dropout_ratio = 0.5
>>> mask = rand > dropout_ratio
>>> mask
array([False, False,  True, False,  True])

#return x * self.maskの出力
#maskでFalseになっている部分はxに掛け合わせると0になる
>>> x * mask
array([0, 0, 2, 0, 4])

#return x * (1.0 - self.dropout_ratio)の出力
>>> x * (1.0 - dropout_ratio)
array([0. , 0.5, 1. , 1.5, 2. ])
```

次に、逆伝搬（backward）部分です。

```python
    def backward(self, dout):
        return dout * self.mask
```
forwardと同じようにShellで実行してみます。
maskはforwardで生成したをもの利用し、doutは適当なものを生成します。
実際にもforwardで生成したmaskはbackwardに引き継がれます。

```sh
#doutを生成
>>> dout = [0.0, 0.1, 0.2, 0.3, 0.4]

#return dout * self.maskの出力
>>> dout * mask
array([0. , 0. , 0.2, 0. , 0.4])
```

最後に、実装したDropoutの動きをみます。今回は、順伝（forward）のみ。
一度、sample.pyというファイルに今回のDropoutを記載しておいたのを使っています。

```sh
>>> import numpy as np
>>> from sample import Dropout
>>> 
>>> dropout = Dropout()
>>> 
>>> x = np.array([0,1,2,3,4])

#乱数生成しているので、forwardの出力は実行のたびに変わる
>>> dropout.forward(x)
array([0, 0, 2, 3, 0])
>>> dropout.forward(x)
array([0, 0, 0, 0, 4])
>>> dropout.forward(x)
array([0, 0, 2, 0, 4])
```

forwardの出力が確認できました。
train_flg=Falseやbackwardの動きも動かして確認してみてください。