誤差逆伝播法について (E資格対策)

今日は誤差逆伝播法（ごさぎゃくでんぱんほう）について


#誤差逆伝播法とは

NNの最適化を行う一つの手法です。別名BackPropagationとも。



![多層パーセプトロン_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/3d9d32bb-7d1b-bf9a-8de4-6f0ef8e6a774.jpeg)


上図のNNではそれぞれのw ~~（多すぎて途中で書くのを断念しました）~~ を最適化する必要があります。
その最適化の手法が誤差逆伝播法です。

##重みの可視化：計算グラフ

このwがNNでどう作用しているか可視化した図を計算グラフといいます。


<img width="576" alt="計算グラフ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/b8f76741-ccd0-2276-0a56-ac0b00548032.png">

ちなみにすべての重みに関する偏微分を計算して並べたベクトルを勾配（gradient）と呼びます。
このすべての重みのパラメータに対して

###連鎖率

今回知りたいのが出力を変化させたときにパラメータの影響がどれくらいあるか。つまり値を入力したときの偏微分を求めたいわけです。


<img width="576" alt="計算グラフの逆伝播.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/60eca8e2-8b18-1685-91cc-3157c667b78d.png">
イメージ図。中央はfです。
偏微分の値（＝傾き）が分かれば、重みを最適な値にまで持っていけます。

んでこの偏微分の値ですが、図だと一層だけなので値はすぐわかりますが、では2，3個も重なっていたらどうなるんでしょうか。
結論を言うとそれぞれ偏微分した値をかけ合わせればおっけーです。

<img width="770" alt="計算グラフの逆伝播実際の例.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/0ef1be22-63ed-324c-5731-09ad2d76b899.png">


数式で表すならfがyの何かの関数(f=yとかなんでも）,yがwの何かの関数であるとき

$$
\frac{\partial f}{\partial w} = \frac{\partial f}{\partial y} \frac{\partial y}{\partial w}
$$

と表せます。


####足し算:そのまま

では実際に代表的な演算だけ逆伝播のやり方を見てみましょう。

足し算ではそのまま値が伝わります。


<img width="770" alt="１４.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/16ae7630-edb7-d58c-f41b-3d783641a564.png">
1.4を逆伝播させると、それぞれに1.4が伝わる。

コードはこんな感じ

```python:addlayer
#加算レイヤ

class AddLayer:
  def __init__(self):
    pass

  def forward(self,x,y):#図の黒矢印の方向
    out=x+y
    return out

  def backward(self,dout):#図の青矢印の方向, dout:上流から流れてきた数
    dx=dout*1
    dy=dout*1
    return dx,dy


a=2
b=3


#layer
alayer=AddLayer()

#forward
c=alayer.forward(a,b)
print(c) #5(=2+3)

#reverse
dout=1.4
da,db=alayer.backward(dout)

print(da,db) #1.4(=そのまま出力するから）
```




####掛け算:反対の係数を

掛け算では反対側の係数をかけます。


<img width="770" alt="１７.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/bf4d6afb-7847-a75c-67ac-404abea37c30.png">
1.7を伝播させると反対側のノードにある係数を乗算した値が逆伝播する。

コードはこう

```python:Mullayer
#乗算レイヤ
class MulLayer:
  def __init__(self):
    self.x=None
    self.y=None
  def forward(self,x,y):
    self.x=x
    self.y=y
    out=x*y

    return out

  def backward(self, dout):
    dx=dout*self.y#反対側の係数をかける
    dy=dout*self.x

    return dx,dy

a=2
b=3



#layer
mlayer=MulLayer()

#forward
c=mlayer.forward(a,b)
print(c)#6

#reverse
dout=1.7
da,db=mlayer.backward(dout)
print(da,db)#5.1 3.4
```



####RelU
入力された値によって変化します。
xが0以上の時はそのまま、xが0以下の時は0です。

<img width="770" alt="れぅ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/a50b295e-0ac8-470d-b42f-49743f949dfc.png">


```python:RelU

class Relu:
  def __init__(self):
    self.mask=None

  def forward(self,x):
    self.mask=(x>0)
    out=x if self.mask else 0
    return out

  def backward(self, dout):
    dx=dout if self.mask else 0
    return dx

a=8.2
relu=Relu()
ra=relu.forward(a)
print(ra)#8.2

ut=1.2
rb=relu.forward(ut)
print(rb)#1.2

mrelu=Relu()
b=-9.2
rb=mrelu.forward(b)
print(rb)#0

ut=1.2
rc=mrelu.backward(ut)
print(rc)#0


```



####sigmoid

$$
f(x)'=(1-f(x))f(x)
$$

が成り立つのでそのまま使います。（つまりRelUのように入力xを覚える必要はない）

<img width="770" alt="しｇもいｄ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604787/37b2ac20-7636-bed3-1e0b-075ee610da67.png">

sigmoidは入力の絶対値が大きいと勾配が0に近くなり、学習が止まります。

```python:sigmoid

import numpy as np

class Sigmoid:
  def __init__(self):
    self.out=None

  def forward(self,x):
    out=1/(1+np.exp(-x))
    self.out=out

    return out

  def backward(self,dout):
    dx = dout*(1.0-self.out)*self.out
    return dx

sig=Sigmoid()

a=1
aout=sig.forward(a)
print(aout)#0.7310585786300049

b=0.006
bout=sig.forward(b)
print(bout)#0.5014999955000162


```
ここまで


参考文献
ゼロから作るDeep Learning: Pythonで学ぶディープラーニングの理論と実装