からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

シグモイド関数の逆伝播の導出【ゼロつく1のノート(数学)】

はじめに

 「機械学習・深層学習」初学者のための『ゼロから作るDeep Learning』の攻略ノートです。『ゼロつくシリーズ』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。

 ニューラルネットワーク内部の計算について、数学的背景の解説や計算式の導出を行い、また実際の計算結果やグラフで確認していきます。

 この記事は、5.5.2項「Sigmoidレイヤ」の内容です。Sigmoid関数を微分します。

【元の記事】

www.anarchive-beta.com

【他の記事一覧】

www.anarchive-beta.com

【この記事の内容】

シグモイド関数の逆伝播の導出

 シグモイド関数(sigmoid関数)の微分(逆伝播)を導出します。順伝播については「3.2.4:シグモイド関数の実装【ゼロつく1のノート(実装)】 - からっぽのしょこ」を参照してください。

・定義式の確認

 シグモイド関数は、入力を$x$、出力を$y$として、次の式で定義されます。

$$ y = \frac{1}{1 + \exp(-x)} \tag{5.9} $$

 この式が順伝播の計算式です。

 逆伝播(微分)は次の式になります。

$$ \frac{\partial y}{\partial x} = y (1 - y) $$

 この式を導出します。

・順伝播の確認

 シグモイド関数(5.9)の計算は、入力$x$に「$-1$を掛ける」「指数をとる」「$1$を足す」「$1$を割る」の4つのノード(ステップ)に分解できます(図5-19)。
 まずは、この順伝播の計算を確認します。ただし、ステップ番号の振り方が本と異なるので注意してください。

・ステップ1

 最初の計算は、「入力に$-1$を掛ける」です。「$\times$」ノード(乗算ノード)にシグモイド関数の入力と$-1$を入力します。

 1つ目のノードの入力を$x^{(0)}$、出力を$x^{(1)}$とすると、「入力に$-1$を掛ける」は次の式で表せます。

$$ x^{(1)} = -x^{(0)} $$

 $x^{(1)}$を次のノードに入力します。

・ステップ2

 2つ目の計算は、「入力の指数をとる」です。「$\exp$」ノード(指数ノード)に$x^{(1)}$を入力します。

 2つ目のノードの出力を$x^{(2)}$とすると、「入力の指数をとる」は次の式で表せます。

$$ x^{(2)} = \exp(x^{(1)}) $$

 $x^{(2)}$を次のノードに入力します。

・ステップ3

 3つ目の計算は、「入力に$1$を足す」です。「$+$」ノード(加算ノード)に$x^{(2)}$と$1$を入力します。

 3つ目のノードの出力を$x^{(3)}$とすると、「入力に$1$を足す」は次の式で表せます。

$$ x^{(3)} = 1 + x^{(2)} $$

 $x^{(3)}$を次のノードに入力します。

・ステップ4

 最後の計算は、「入力で割る」です。「$/$」ノード(除算ノード)に$x^{(3)}$を入力します。

 4つ目のノードの出力を$y$とすると、「入力で割る」は次の式で表せます。

$$ y = \frac{1}{x^{(3)}} $$

 $y$がシグモイド関数(sigmoidレイヤ)の出力です。次のレイヤがあればそのレイヤに$y$を入力します。

 以上が、シグモイド関数で行う計算です。4つのノードをまとめてSigmoidレイヤとして扱います。

・Sigmoidレイヤ

 4つのノードをそれぞれ関数$f_1(x), \cdots, f_4(x)$とすると、Sigmoidレイヤの計算は合成関数で表現できます。

$$ y = f_4(f_3(f_2(f_1(x^{(0)})))) = \frac{1}{1 + \exp(-x^{(0)})} \tag{5.9} $$

 4つの関数が入れ子になっています。それぞれ分けて書くと

$$ \begin{aligned} x^{(1)} &= f_1(x^{(0)}) \\ x^{(1)} &= f_2(x^{(1)}) \\ x^{(2)} &= f_3(x^{(2)}) \\ y &= f_4(x^{(3)}) \end{aligned} $$

と同じ意味です。

 ここまでで、順伝播の計算を確認しました。次は、逆伝播の計算を確認します。

・逆伝播の導出

 逆伝播では、「順伝播の入力$x^{(0)}$」に関する「順伝播の出力$y$」の微分$\frac{\partial y}{\partial x^{(0)}}$を求めます。

 $\frac{\partial y}{\partial x^{(0)}}$は、合成関数の微分と言えます。よって、連鎖律より、4つの関数(ノード)の微分の積で求められます。連鎖律については「5.2:連鎖率【ゼロつく1のノート(数学)】 - からっぽのしょこ」を参照してください。

$$ \frac{\partial y}{\partial x^{(0)}} = \frac{\partial y}{\partial x^{(3)}} \frac{\partial x^{(3)}}{\partial x^{(2)}} \frac{\partial x^{(2)}}{\partial x^{(1)}} \frac{\partial x^{(1)}}{\partial x^{(0)}} $$

 次のように表記しても同じ意味です。

$$ \frac{\partial y}{\partial x^{(0)}} = \frac{\partial f_4(x^{(3)})}{\partial x^{(3)}} \frac{\partial f_3(x^{(2)})}{\partial x^{(2)}} \frac{\partial f_2(x^{(1)})}{\partial x^{(1)}} \frac{\partial f_1(x^{(0)})}{\partial x^{(0)}} $$

 ここでは、上の表記で統一します。

 各ノードの微分$\frac{\partial y}{\partial x^{(3)}}, \cdots, \frac{\partial x^{(1)}}{\partial x^{(0)}}$を求めていきます。

・ステップ4

 4つ目のノード(「$/$」ノード)の順伝播は、次の計算でした。

$$ y = \frac{1}{x^{(3)}} $$

 分数はマイナスの指数を使って$\frac{1}{x} = x^{-1}$と表すことができます。よって、「$/$」ノードは、次のように書き換えられます。微分の公式に当てはめやすい形にします。

$$ y = (x^{(3)})^{-1} $$

 $y$を$x^{(3)}$で微分すると

$$ \begin{aligned} \frac{\partial y}{\partial x^{(3)}} &= - 1 * (x^{(3)})^{-1-1} \\ &= - (x^{(3)})^{-2} \\ &= - \frac{1}{(x^{(3)})^{2}} \end{aligned} $$

となります。$x^{-2} = \frac{1}{x^2}$です。

 後々扱いやすいように、式を変形しておきます。最初の式の両辺を2乗すると$y^2 = (\frac{1}{x^{(3)}})^2 = \frac{1}{(x^{(3)})^2}$なので、置き換えます。

$$ \frac{\partial y}{\partial x^{(3)}} = - y^2 \tag{5.10} $$

 順伝播の出力$y$を使った形になりました。

・ステップ3

 3つ目のノード(「$+$」ノード)の順伝播は、次の計算でした。

$$ x^{(3)} = 1 + x^{(2)} $$

 $x^{(3)}$を$x^{(2)}$で微分すると

$$ \begin{aligned} \frac{\partial x^{(3)}}{\partial x^{(2)}} &= 0 + 1 \\ &= 1 \end{aligned} $$

となります。

・ステップ2

 2つ目のノード(「$\exp$」ノード)の順伝播は、次の計算でした。

$$ x^{(2)} = \exp(x^{(1)}) $$

 $x^{(2)}$を$x^{(1)}$で微分すると

$$ \frac{\partial x^{(2)}}{\partial x^{(1)}} = \exp(x^{(1)}) $$

となります。指数関数$\exp(x)$の微分$\frac{d \exp(x)}{d x}$はそのままの値$\frac{d \exp(x)}{d x} = \exp(x)$です。

 ステップ1の式より、$x^{(1)} = - x^{(0)}$で置き替えます。

$$ \frac{\partial x_2}{\partial x^{(1)}} = \exp(-x^{(0)}) $$

 順伝播の入力$x^{(0)}$を使った形になりました。

・ステップ1

 1つ目のノード(「$\times$」ノード)の順伝播は、次の計算でした。

$$ x^{(1)} = - x^{(0)} $$

 $x^{(1)}$を$x^{(0)}$で微分すると

$$ \frac{\partial x^{(1)}}{\partial x^{(0)}} = -1 $$

となります。

 以上で、各ノードの微分が求まりました。続いて、Sigmoidレイヤの微分を考えます。

・Sigmoidレイヤ

 各ノードの微分をそれぞれ連鎖律の式に代入します。

$$ \begin{aligned} \frac{\partial y}{\partial x^{(0)}} &= \frac{\partial y}{\partial x^{(3)}} \frac{\partial x^{(3)}}{\partial x^{(2)}} \frac{\partial x^{(2)}}{\partial x^{(1)}} \frac{\partial x^{(1)}}{\partial x^{(0)}} \\ &= - y^2 * 1 * \exp(-x^{(0)}) * (-1) \\ &= y^2 \exp(-x^{(0)}) \end{aligned} $$

 計算上はこれでいいのですが、もう少し扱いように式を整理します。

 $y^2 = y * y$の片方に、シグモイド関数の定義式(5.9)を代入します。

$$ \begin{aligned} \frac{\partial y}{\partial x^{(0)}} &= y \frac{1}{1 + \exp(-x^{(0)})} \exp(-x^{(0)}) \\ &= y \frac{\exp(-x^{(0)})}{1 + \exp(-x^{(0)})} \end{aligned} $$

 さらに、分子に$1 - 1 = 0$を足して、分数の項を分割します。(0を足しているだけなので、値は変わりません。)

$$ \begin{aligned} \frac{\partial y}{\partial x^{(0)}} &= y \frac{1 + \exp(-x^{(0)}) - 1}{1 + \exp(-x^{(0)})} \\ &= y \left( \frac{1 + \exp(-x^{(0)})}{1 + \exp(-x^{(0)})} - \frac{1}{1 + \exp(-x^{(0)})} \right) \end{aligned} $$

 1行目から2行目の変形は、$1 + \exp(-x)$を$a$とでもおいて$\frac{a - 1}{a}$を$\frac{a}{a} - \frac{1}{a}$に分割しているだけです。
 最後に、式(5.9)を使って$y$に戻します。

$$ \frac{\partial y}{\partial x^{(0)}} = y (1 - y) \tag{5.12} $$

 シグモイド関数の微分$\frac{\partial y}{\partial x^{(0)}}$が、順伝播の出力$y$から求められることが分かりました。
 Sigmoidレイヤの実装において、順伝播の入力の情報を保存せず、出力の情報だけを保存しておけばいいため、メモリ効率の良い実装ができます。

 前後にレイヤがある場合は、「逆伝播の入力(後のレイヤの出力)$\frac{\partial L}{\partial y}$」と「このレイヤの微分$\frac{\partial y}{\partial x}$」の積$\frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial x}$を出力します(前のレイヤに入力します)。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning』オライリー・ジャパン,2016年.

おわりに

 詳しく書き忘れたのですが、ノードごとの微分の積が全体の微分になります。これが連鎖率による分解に対応しています(というかそのものです)。あとノードとレイヤの区別も適当な気がします。1つずつの計算がノードで、同列の計算(ノード)をひとまとめにしてレイヤと呼んでます。$a_k$に対してはシグモイド関数ノードで、$\mathbf{a} = (a_1, \cdots, a_M)$に対してはシグモイド関数レイヤと言い分けて(いたりいなかったりしてると思)います。
 これは言い訳なのですが、今回は順番通りに書かなかったので色々統一感に欠けてます(何なら連鎖率の節は最後に書いています(まだ完成していません))。すみません…

2020年8月1日:「ハロー!プロジェクト」&「アンジュルム」元リーダー和田彩花さんのお誕生日!

 おめでとうございます!!!

  • 2021.09.03:加筆修正しました。

 逆伝播の導出の記事に関してかなり構成を変えました。図を使わずに誤差逆伝播法っぽく説明するのが難しかったので止めて、連鎖律を軸に説明することにしました。元の記事よりは流れを整理できて、話が前後せずに読めるようになったと思います。ただしその分、伝播している感が伝わりにくくなりました。
 それと、「レイヤの入力$x$」と各ノードを説明するための「ノードの入力$x$」がごっちゃになってて分かりにくかったので、代わりに中間変数($x^{(n)}$)がいくつも登場することになりました。私の中ではこっちの方が分かりやすかったんです!!!

 あと、初めて書いたとき以上に順番を無視して修正を進めています。というか、数学系の記事を飛ばしています。めんどいので。連鎖律の記事はまた最後かなー。
 でも2周目なので、レイヤとノードなどの表現、実装や導出の流れなど、色々と統一感は増しているはずです。良ければ他の記事も読んでね。まだまだ、他の人が読んだ総時間よりも、自分で書きながら何度も読み返した時間の方が長い気がする。

【関連する記事】

www.anarchive-beta.com

www.anarchive-beta.com