誤差逆伝播法とは何か

プログラマの良いところは離散的な考え方に親しんでいる点である。だいたいのプログラミングなんて組み合わせの列挙と計算量の解析ができればなんとかなる。微分計算なんて大学1年の数学で見納めたはずだった。しかしながら、機械学習を勉強するとやたら微分計算が登場するのである。特に、ニューラルネットワークバックプロパゲーション誤差逆伝播法)は意味が分からん。ってことで今回は気合を入れてバックプロパゲーションについて調べてメモってみた。

説明の方針

世の中のバックプロパゲーションの説明を読んでて何が苦しいかと言うと  i とか  j とか  k とか添字が多すぎて混乱を招くことである。人間は添字が多すぎるとやる気を失う。やる気を出して添字を細かく見ていっても、全体として結局なにが言いたかったのか腑に落ちない。

そこで本記事では、添字の煩わしさを軽減するためにベクトルや行列を "そのまま" 微分する方式で説明する。"ベクトルをベクトルで微分する" とか "スカラーを行列で微分する" みたいな説明になる。こういうのは Matrix calculus と呼ばれるらしい。たぶん日本語では "行列微分積分学" って感じだと思う。ちなみに英語版 WikipediaBackpropagation のページはこの方式を取っていて分かりやすい。説明の上手さに感心した。

プログラマであれば変数の "型" が明確でないのは気持ち悪いだろう。数学者の気持ちとしては「違うものが同じように見える」ことが嬉しいんだろうが、プログラマとしては異なる型を持つものは異なる表記であってほしい。そこで本記事ではベクトルは  \vec{v} のように矢印を付けて表記し、行列は  W のように大文字で、スカラー c のように小文字で書く。

準備:Matrix calculus

まずはベクトルや行列を偏微分するところから始める。内容は このページ を参考にした。

スカラーをベクトルで偏微分

スカラー  y をベクトル  \vec{x} = [x_1 x_2 \dots x_n]^{\top}偏微分した結果は以下のベクトルとなる。


\frac{\partial y}{\partial \vec{x}} = 
\begin{bmatrix}
\frac{\partial y}{\partial x_{1}} & \frac{\partial y}{\partial x_{2}} & \dots, \frac{\partial y}{\partial x_{n}} 
\end{bmatrix}

ベクトルをベクトルで偏微分

ベクトル  \vec{y} = [y_1 \dots y_m]^{\top} をベクトル  \vec{x} = [x_1 \dots x_n]^{\top} 偏微分した結果は以下の行列となる。


\frac{\partial \vec{y}}{\partial \vec{x}} = 
\begin{bmatrix}
\frac{\partial y_{1}}{\partial x_{1}} & \frac{\partial y_{1}}{\partial x_{2}} & \dots & \frac{\partial y_{1}}{\partial x_{n}} \\\
\frac{\partial y_{2}}{\partial x_{1}} & \frac{\partial y_{2}}{\partial x_{2}} & \dots & \frac{\partial y_{2}}{\partial x_{n}} \\\
\vdots &  \vdots & \ddots & \vdots \\\
\frac{\partial y_{m}}{\partial x_{1}} & \frac{\partial y_{m}}{\partial x_{2}} & \dots & \frac{\partial y_{m}}{\partial x_{n}}
\end{bmatrix}

スカラーを行列で偏微分

スカラー  y p \times q の行列  X偏微分した結果は以下の行列となる。


\frac{\partial y}{\partial X} = 
\begin{bmatrix}
\frac{y}{x_{11}} & \frac{y}{x_{21}} & \dots & \frac{y}{x_{p1}} \\\
\frac{y}{x_{12}} & \frac{y}{x_{22}} & \dots & \frac{y}{x_{p2}} \\\
\vdots &  \vdots & \ddots & \vdots \\\
\frac{y}{x_{1q}} & \frac{y}{x_{2q}} & \dots & \frac{y}{x_{pq}}
\end{bmatrix}

ニューラルネットワーク

本稿ではニューラルネットワークの構成要素を以下のように表記する。

  •  \vec{x}: 入力データ。特徴ベクトルのこと。
  •  \vec{y}: 出力データ。教師データとして与えられるやつ。
  •  c: 誤差関数(目的関数)。二乗誤差など。
  •  L: 層の総数
  •  W^{l}: 第  l 層への重みの行列。第  l-1 層の  k 番目のユニットから第  l 層の  j 番目のユニットへの重みを行列の成分 w^{l}_{jk} とする。
  •  f^{l}: 第  l 層の活性化関数。ロジスティック関数やシグモイド関数など。
  •  \vec{z}^{l}: 第  l 層の入力ベクトル
  •  \vec{a}^{l}: 第  l 層の出力ベクトル

これらの表記を用いてニューラルネットワークを図示すると以下のようになる。

f:id:t-keita:20210815021420p:plain:w600

よくあるニューラルネットワークの図といえばたくさんの円と線が並んだ複雑なやつだが、ここではすべての要素がベクトル or 行列で表現されている。実はニューラルネットワークを図示するにはこれで十分なのである。

ニューラルネットワークは、ベクトルを入力としてそのベクトルを順に処理してゆくだけの機械  g(\vec{x}) だと思えばよい。具体的には以下の関係が成り立つ。


g(\vec{x}) = f^{L} ( W^{L} f^{L-1} ( W^{L-1} \dots f^{1} ( W^{1} \vec{x}) \dots )  )

ニューラルネットワークの学習では、誤差関数  c を用いて  c(\vec{y}, g(\vec{x})) をなるべく小さくするような重 W^{1}, \dots, W^{L} を求める。この計算には確率的勾配降下法が用いられることが一般的であり、第  l 層の重み  W^{l} を更新するには誤差関数  c を行列  W^{l}偏微分した値  \frac{\partial c}{\partial W^{l}} が必要となる。ただし、この偏微分スカラーを行列で偏微分したものであるため結果も行列であることに注意する。

ちなみに確率的勾配降下法については以前の記事で紹介した。 t-keita.hatenadiary.jp

バックプロパゲーションの入出力

プログラマであれば手法を理解するときに入出力を明示してほしいと思うだろう。そこでバックプロパゲーションの入出力を以下に示す。

出力の偏微分(行列)を転置したものは勾配と呼ばれる。要するに、バックプロパゲーションは勾配法に用いるための勾配を求めるためのものである。

ちなみに、出力となる偏微分は出力層の第  L 層の  \frac{\partial c}{\partial W^{L}} から第1層の  \frac{\partial c}{\partial W^{1}} へと順に求まる。このような順序になるのはアルゴリズム動的計画法Dynamic Programming, いわゆる DP)になっているからである。詳細は後述する。

一方で、バックプロパゲーションは以下のようなものではない

  • 出力層に近い順に重みを更新するもの:重みが更新されるのは勾配法の実行時である。ちなみに、勾配法によって重みを更新するときは出力層に近い順である必然性はない。
  • 出力層から順に誤差を伝えるもの:バックプロパゲーションでは学習データを使わないので具体的な誤差は計算されない。具体的な誤差が考慮されるのは勾配法の実行時である。

とはいえ、バックプロパゲーションという言葉は学習アルゴリズム全体を指すようになってきているらしい。

バックプロパゲーションの仕組み

それでは本題のバックプロパゲーションアルゴリズムについて見てゆく。説明のうえで第  l-1 層と第  l 層の関係が重要になってくるので図を抜粋して再掲しておく。 f:id:t-keita:20210815103211p:plain:w600

まずは誤差関数について説明する。今回考える誤差関数は前述した通り以下である。


c(\vec{y}, f^{L} ( W^{L} f^{L-1} ( W^{L-1} \dots f^{1} ( W^{1} \vec{x}) \dots )  ) )

ここで、具体的な学習データを用いる代わりにニューラルネットワークの入出力として  (\vec{x}, \vec{y}) は固定であるとみなす。そのうえで重み  W^{1}, \dots, W^{L} を動かしたときの誤差関数 c の変化を調べることが目的である。

つぎに、誤差関数  c を第  l 層の入力ベクトル  \vec{z}^{l}偏微分して得られるベクトルを  \vec{\delta}^{l} とする。 \vec{\delta}^{l} に対して以下の関係が成り立つ。


\begin{align}
\vec{\delta}^{l} & = \frac{\partial c}{\partial \vec{z}^{l}} \\\
                        & = 
\frac{\partial c}{\partial \vec{a}^{L}} \cdot
\frac{\partial \vec{a}^{L}}{\partial \vec{z}^{L}} \cdot
\dots \cdot
\frac{\partial \vec{z}^{l+1}}{\partial \vec{a}^{l}} \cdot
\frac{\partial \vec{a}^{l}}{\partial \vec{z}^{l}}
\end{align}

ここで偏微分の連鎖律(Chain rule)を用いている。この積は出力層の第  L 層から第  l 層まで下っている。それゆえ、 \vec{\delta}^{l} \vec{\delta}^{l-1} について以下の再帰的な関係が成り立つ。


\vec{\delta}^{l-1} = 
\vec{\delta}^{l} \cdot
\frac{\partial \vec{z}^{l}}{\partial \vec{a}^{l-1}} \cdot
\frac{\partial \vec{a}^{l-1}}{\partial \vec{z}^{l-1}}

ここで偏微分計算について以下が成り立つ。ただし  I単位行列である。いずれもベクトルをベクトルで微分しているので結果は行列になる。


\frac{\partial \vec{z}^{l + 1}}{\partial \vec{a}^{l}} = W^{l}, \hspace{0.3cm}
\frac{\partial \vec{a}^{l}}{\partial \vec{z}^{l}} = (f^{l})' \cdot I

したがって  \vec{\delta}^{l}再帰式は以下で計算できる。


\vec{\delta}^{l-1} = \vec{\delta}^{l} \cdot W^{l} \cdot (f^{l-1})'

ここまでの話を整理すると、 \vec{\delta}^{L} が求まれば  \vec{\delta}^{L-1} が求まる。そして  \vec{\delta}^{L-1} が求まれば  \vec{\delta}^{L-2} が求まる。これを繰り返すことで  \vec{\delta}^{1} まで求めることができる。なお、 \vec{\delta}^{L} は定義通りに誤差関数を偏微分することで求まる。このように、漸化式を用いてボトムアップにすべての数列の値を求めるアプローチは動的計画法に他ならない。この計算過程が出力層に近い第  L 層から入力層の方向へ向かう。この方向はニューラルネットワークが入力から出力を計算する方向と逆であり、それが "バック" プロパゲーションという名前の由来である。

あと残っているのは、手法の目的だった  \frac{\partial c}{\partial W^{l}} の計算である。これは以下のように計算できる(らしい)。


\begin{align}
\frac{\partial c}{\partial W^{l}} & = \vec{a}^{l-1} \cdot \frac{\partial c}{\partial \vec{z}^{l}} \\\
                                              & = \vec{a}^{l-1} \cdot \vec{\delta}
\end{align}

すでに  \vec{\delta} は計算できることが分かっているので、手法の目的の  \frac{\partial c}{\partial W^{l}} も計算できることが分かった。めでたし。

メモ:この最後の計算は厳密にはよく理解できていない。というのも行列で偏微分するときは連鎖律が成り立たないらしく、計算を理解するには行列の各成分の添字を真面目に追わないといけないっぽい。そこまでする気力はなかった。この計算のイメージはおそらく、 \vec{z}^{l} = W^{l} \cdot \vec{a}^{l-1} であることから、重み  W^{l} の各成分を微小に動かしたときの誤差関数  c の変化が、対応する  \vec{a}^{l-1} の成分に比例するということだと思われる。

最後に、確率的勾配降下法の実行時の流れを整理する。勾配法の実行時には現在の重み  W^{1}, \dots, W^{L} が決まっている。ランダムに選択した学習データ  (\vec{x}, \vec{y}) のうち  \vec{x}ニューラルネットワークに与えるとすべての  \vec{z}^{l}, \vec{a}^{l} の値が定まる。また、 a^{L} \vec{y} の値を用いて  \vec{\delta}^{L} が計算され、その後  W^{l} を用いながらすべての  \vec{\delta}^{l} の値が定まる。そして  \frac{\partial c}{\partial W^{l}} の値も定まる。得られた勾配を下る方向に重みを更新する。この重みの更新を繰り返すという流れ。

おしまい。