Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

誤差逆伝搬法について動かしながら確認してみる

前回、Deep Learning勉強している中で誤差逆伝搬法について勉強しましたが、
ちょっと分かりにくかったので自分で手を動かして勉強してみることにしました!!
elsammit-beginnerblg.hatenablog.com

勉強には、こちらの本を利用しました。

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

新品価格
¥3,740から
(2020/11/13 23:29時点)



■計算グラフ

実際に行列や数式に対して誤差逆伝搬法で最小値を算出してみる前に、
算出するために用いる計算グラフについてまとめておきます。

数式グラフとは、計算の過程をグラフによって表したものです。
ここでのグラフとは、データ構造としてのグラフであり、複数のノード + エッジによって表現します。

例えば、こちらの数式ですと、
f:id:Elsammit:20201113224907p:plain

計算グラフは、
f:id:Elsammit:20201113225525p:plain

で表せます。

■誤差逆伝搬法で最小値を導き出す

行列だとパラメータが多すぎて理解しづらい部分があるため、まずは
f:id:Elsammit:20201113224012p:plain
といった2次関数での誤差逆伝搬法について確認していきたいと思います。

まず計算グラフはこんな感じになります。
f:id:Elsammit:20201113225642p:plain

前回のブログの通り、誤差逆伝搬時の返り値は微分値となりますので、
こちらのような数式が適用されます。
f:id:Elsammit:20201113230628p:plain

こちらの数式を元に誤差逆伝搬により最小値を導くコードはこちらになります。

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

class MulLayer:
    def __init__(self):
        self.x = 0.0
        self.y = 0.0
        self.dx = 0.0
        self.dy = 0.0
    
    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
        self.dx = dx
        self.dy = dy
        return dx, dy

class AddLayer:
    def __init__(self):
        pass
    
    def forward(self, x, y):
        out = x + y
        return out

    def backward(self ,dout):
        dx = dout * 1
        dy = dout * 1
        return dx,dy

class Square:
    def __init__(self):
        self.x = None
        self.dx = None
        
    def forward(self,x):
        self.x = x
        out = x **2
        return out

    def backward(self, dout):
        self.dx = 2 * dout * self.x
        return self.dx

pltx = 8
plty = 1

Square1 = Square()
Square2 = Square()
Mul1 = MulLayer()
Add = AddLayer()

listx = []
listy = []
listz = []
katamuki = 1

for i in range(100):
    x0 = Square1.forward(pltx)
    x1 = Mul1.forward(x0, 1/20)
    y1 = Square2.forward(plty)
    z = Add.forward(x1, y1)
    listx.append(pltx)
    listy.append(plty)
    listz.append(z)
    print(pltx, plty, z)

    bx0,by1 = Add.backward(katamuki)
    bx1,by_b = Mul1.backward(bx0)
    bx2 = Square1.backward(bx1)
    by2 = Square2.backward(by1)

    pltx = pltx - 0.9*bx2
    plty = plty - 0.9*by2

ListX, ListY = np.meshgrid(listx, listy)
fig = plt.figure()
plt.plot(listx, listy)
plt.show()

こちらを実行すると、結果としてこちらの通り、
(x, y) = (0, 0)
に収束しているのがわかるかと思います。
f:id:Elsammit:20201113231044p:plain

■収束方法にAdaGradを用いる

今回の方法よりも収束させるにあたりより効率のよい式があります。
それは、AdaGradと呼ばれる手法です。
AdaGradについてはこちらを参考に下さい。
https://www.slideshare.net/nishio/ss-66840545

コードはこちらになります。

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

class MulLayer:
    def __init__(self):
        self.x = 0.0
        self.y = 0.0
        self.dx = 0.0
        self.dy = 0.0
    
    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
        self.dx = dx
        self.dy = dy
        return dx, dy

class AddLayer:
    def __init__(self):
        pass
    
    def forward(self, x, y):
        out = x + y
        return out

    def backward(self ,dout):
        dx = dout * 1
        dy = dout * 1
        return dx,dy

class Square:
    def __init__(self):
        self.x = None
        self.dx = None
        
    def forward(self,x):
        self.x = x
        out = x **2
        return out

    def backward(self, dout):
        self.dx = 2 * dout * self.x
        return self.dx

class AdaGrad:
    def __init__(self, lr=0.1):
        self.lr = lr
        self.h = 0.0
    
    def update(self, params, grads):
        self.h = self.h + grads * grads
        sabun = self.lr * grads/(np.sqrt(self.h)+ 1e-7)
        params = params - sabun

        return params

pltx = 8
plty = 1

Square1 = Square()
Square2 = Square()
Mul1 = MulLayer()
Add = AddLayer()
AdaGrad = AdaGrad(1)

listx = []
listy = []
listz = []
katamuki = 1

for i in range(100):
    x0 = Square1.forward(pltx)
    x1 = Mul1.forward(x0, 1/20)
    y1 = Square2.forward(plty)
    z = Add.forward(x1, y1)
    listx.append(pltx)
    listy.append(plty)
    listz.append(z)

    bx0,by1 = Add.backward(katamuki)
    bx1,by_b = Mul1.backward(bx0)
    bx2 = Square1.backward(bx1)
    by2 = Square2.backward(by1)
    
    pltx = AdaGrad.update(pltx,bx2)
    plty = AdaGrad.update(plty,by2)

ListX, ListY = np.meshgrid(listx, listy)
fig = plt.figure()
plt.plot(listx, listy)
plt.show()

追加したコードは、

class AdaGrad:
    def __init__(self, lr=0.1):
        self.lr = lr
        self.h = 0.0
    
    def update(self, params, grads):
        self.h = self.h + grads * grads
        sabun = self.lr * grads/(np.sqrt(self.h)+ 1e-7)
        params = params - sabun

        return params

と、

    pltx = AdaGrad.update(pltx,bx2)
    plty = AdaGrad.update(plty,by2)

になります。
適用する数式をAdaGradに変更するのみです。

結果はこちらになります。
f:id:Elsammit:20201113233249p:plain

効率的に収束していることが見て分かるかと思います!!

■最後に

今回は行列だと分かりにくいところもあったので2次関数を元に誤差逆伝搬について勉強しました。
また、AdaGradについても簡単に触れました。
自分で手を動かすことで理解が深まりました。

さらにDeep Learningについて理解深めていきたいと思います!!