誤差逆伝播法 その1


AIって結局何なのかよく分からないので、とりあえず100日間勉強してみた Day57


経緯についてはこちらをご参照ください。



■本日の進捗

  • 逆伝播を理解


■はじめに

今回も「ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装(オライリー・ジャパン)」で、深層学習を学んでいきます。

今回は、勾配降下法で機能し始めたニューラルネットワークの学習を発展させていきます。

■連鎖律

まずは事前知識として連鎖律を考えます。

連鎖律(chain rule)は、複数の関数の合成を微分すると、各導関数の積で考えることができるというものです。

$$ z = f(y), \ \ \ y = g(u) \ \ \ u = h(x) $$

上記のような関数を微分するとすると、次のように記述できます。

$$ \frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{du} \cdot \frac{du}{dx} $$

ニューラルネットワークでは複数の層が存在し、各層における関数の出力が次の層への入力になり、その影響によってニューラルネットワークの最終的な出力が得られます。まさに最初の関数のように入力と出力が順番に繋がっている関数とも言えます。

■逆伝播

逆伝播とは、順伝播の逆で出力層から入力層に向かって誤差(順伝播で得られた損失関数の値)を伝播させ、各層の勾配を計算してパラメータ(重みやバイアス)を更新していく手法です。

第1層目が入力(x)に対して出力(t)を返し、第2層目がその値(t)を入力として出力(z)を返すような順伝播はこのように記述できます。

$$ t = f(x), \ \ \ z = g(t) $$

これは先ほどの連鎖律から、

$$ \frac{dz}{dx} = \frac{dz}{dt} \cdot \frac{dt}{dx} $$

ここで第1層目の勾配は、$$\frac{dt}{dx}$$

そして第2層目の勾配は、$$ \frac{dz}{dt} $$

と記述できるので、ニューラルネットワークへの入力(x)が出力誤差(z)へ与える影響を隠れ層の各勾配を用いて記述できることになります。この勾配を計算すれば、隠れ層でのパラメータ更新が可能になります。

■逆伝播の意義

ニューラルネットワークの学習自体は(確率的)勾配降下法を用いた順伝播で可能になったはずでした。実際にこの手法を用いて損失関数の値を徐々に小さくしていくような重みやバイアスを学習している様子を確認しています。

では、なぜあえて逆伝播させる必要があるのでしょうか?

その理由の一つ目は、各層が全体に与える誤差影響が明確になること、そして二つ目は一度の勾配計算(順伝播と逆伝播)で全てのパラメータに対する勾配が求められることにより計算コストを削減し高速に学習できることにあります。

■ReLU層の逆伝播

逆伝播を用いたニューラルネットワークの学習を実装する前に、主要な活性化関数の(順伝播は実装済みなので)逆伝播を用意していきます。

順伝播でのReLUは下記の通りです。

$$ \begin{eqnarray} y = \begin{cases} x & ( x \gt 0 ) \\ 0 & ( x \leq 0 ) \end{cases} \end{eqnarray} $$

これを微分すれば、

$$ \begin{eqnarray} \frac{dy}{dx} = \begin{cases} 1 & ( x \gt 0 ) \\ 0 & ( x \leq 0 ) \end{cases} \end{eqnarray} $$

ReLU層はこれまでの順伝播のみでは、NumPyのmaximum関数を用いて下記のように実装していました。

def relu(x):
    return np.maximum(0, x)

逆伝播を追加すると下記のようになります。

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

    def forward(self, x):
        self.mask = (x <= 0)
        out = np.maximum(0, x)
        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout
        return dx



■Sigmoid層の逆伝播

Sigmoid関数は下記の通りです。

$$ y = \frac{1}{1 + e^{-x}} $$

これは順伝播として下記のように実装していました。

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

Sigmoid関数の分母をuと置けば、yはuの逆数と置換できるので、上式の微分は、

$$ \frac{dy}{dx} = \frac{d}{dx} \left( \frac{1}{u} \right) = – \frac{1}{u^2} \cdot \frac{du}{dx}$$

ここで、uの微分は、

$$ \frac{du}{dx} = -e^{-x} $$

なので、

$$ \frac{dy}{dx} = – \frac{1}{(1 + e^{-x})^2} \cdot (-e^{-x}) = \frac{e^{-x}}{(1 + e^{-x})^2} = \frac{1}{1 + e^{-x}} \cdot \left( 1 – \frac{1}{1 + e^{-x}} \right) $$

つまり、下記のように記述できます。

$$ \frac{dy}{dx} = y \cdot (1 – y) $$

損失をLと置いて連鎖律を用いれば、

$$ \frac{dL}{dx} = \frac{dL}{dy} \cdot \frac{dy}{dx} = \frac{dL}{dy} \cdot y \cdot (1 – y)$$

これを実装すれば下記のようになります。

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

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

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



■おわりに

今回は連鎖律から誤差逆伝播法の基本概念を学びました。実は参考文献では全く違うアプローチで誤差逆伝播法を導入しています。その説明は素晴らしく、本稿で書いたよりも圧倒的に理解しやすいです。この本が非常に人気がある理由が詰まっているかのような分かりやすさで、受験生用の親身な説明から実際の内容への発展はもはや理解できない人はいないでしょう。(今回は知見を深めるためにもあえて別のアプローチで導出してみました。)

今回の内容が良く分からなかったりイメージが付かなかった方はぜひ一度手に取ってみてください。誤差逆伝播法の部分だけでも一読の価値ありです。

■参考文献

  1. Andreas C. Muller, Sarah Guido. Pythonではじめる機械学習. 中田 秀基 訳. オライリー・ジャパン. 2017. 392p.
  2. 斎藤 康毅. ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装. オライリー・ジャパン. 2016. 320p.
  3. ChatGPT. 4o mini. OpenAI. 2024. https://chatgpt.com/
  4. API Reference. scikit-learn.org. https://scikit-learn.org/stable/api/index.html


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です