コンテンツにスキップ

EEIC前期実験i2 第5日

本日のメニュー

  • ニューラルネットワーク

はじめに

本日はニューラルネットワークについて取り扱う。現在のAIの根幹をなす深層学習は巨大なニューラルネットワークである。PyTorchなどのフレームワークにより、ニューラルネットワークを用いて問題を解くことそのものは非常に容易になった。本日の実験では、その基本理論を理解するため線形回帰/線形分類から接続して、小規模なニューラルネットワークを扱ってみる。

ニューラルネットワークの動機

これまで学んだ線形回帰では、入力ベクトル \(\mathbf{x}\) に対して基底関数

\[ \phi(\mathbf{x}) = \begin{bmatrix} \phi_1(\mathbf{x}), \phi_2(\mathbf{x}), \ldots, \phi_M(\mathbf{x}) \end{bmatrix}^\top \]

を導入することで、線形モデルであっても複雑な非線形関係を表現できた。

例えば多項式回帰では、\(\phi(x)=[1,x,x^2,x^3]^\top\) のような基底関数を用いた。

このときモデルは

\[ y = w_0 + w_1\phi_1(x) + \cdots + w_M\phi_M(x) \]

と表される。

しかし実際には、

  • どのような基底関数を選ぶべきか
  • 何個の基底関数を用いるべきか

を人手で決める必要がある。交差検証によるモデル選択などは基底関数の個数などを決める上で有力な方法のひとつではある。しかしやはり手間がかかる作業である。

そこで、「基底関数そのものもデータから学習できないだろうか」という考え方が生まれる。ニューラルネットワークは、この考え方を実現するための代表的な手法である。

表現学習

後述の通り、ニューラルネットワークを活用して線形回帰や線形分類を解く場合、回帰/分類のタスクは出力層で行われる。深層学習は複数回の線形変換/非線形変換をスタックして実現されるが、出力に至るまでのこの積み上げられた変換が、基底関数そのものの学習過程に相当する。深層学習はこの変換を経て得た特徴量を使って最終的に単純な回帰/分類を行っているという見方もできる。この捉え方を踏まえ、あらかじめ設定された予測問題を解くニューラルネットワークを構成し、その変換を特徴量抽出器として利用する考え方を表現学習と呼ぶ。


フィードフォワードニューラルネットワーク

最も基本的なニューラルネットワークとして、隠れ層を1層だけ持つモデルを考える。

入力 \(\mathbf{x}\) に対して、

まず線形変換

\[ \mathbf{a} = \mathbf{W}^{(1)}\mathbf{x} + \mathbf{b}^{(1)} \]

を計算する。

続いて活性化関数 \(h(\cdot)\) (ベクトル入力ベクトル出力の関数)を適用し、

\[ \mathbf{z} = h(\mathbf{a}) \]

を得る。

さらに出力層で

\[ \mathbf{y} = \mathbf{W}^{(2)}\mathbf{z} + \mathbf{b}^{(2)} \]

を計算する。

このとき、

\[ \mathbf{x} \rightarrow \mathbf{a} \rightarrow \mathbf{z} \rightarrow \mathbf{y} \]

という一方向の情報伝達を行うため、この構造をフィードフォワードニューラルネットワークと呼ぶ。


なぜ活性化関数が必要なのか

活性化関数を使わず、線形変換だけを何層重ねたとする。

すると

\[ \mathbf{y} = \mathbf{W}_3\mathbf{W}_2\mathbf{W}_1\mathbf{x} \]

となる。

ここで

\[ \mathbf{W}=\mathbf{W}_3\mathbf{W}_2\mathbf{W}_1 \]

と置けば、

\[ \mathbf{y} = \mathbf{W}\mathbf{x} \]

であり、結局は1回の線形変換と等価である。つまり、線形変換を何層重ねても表現能力は向上しない。

ニューラルネットワークが複雑な関数を表現できるのは、各層の間に非線形な活性化関数を挿入するためである。

ニューラルネットワークの万能近似定理

ニューラルネットワークの表現能力は数学的に証明されており、万能近似定理と呼ばれている。これは「十分なパラメータと適切な構造をもつニューラルネットワークは理論上任意の複雑な関数を高い精度で近似できる」というものである。これは非線形な活性化関数が大きな役割を果たしている。なお、この定理は「十分な数のニューロンがあれば」という理論保証であるため、実際に学習が可能か、汎化するかなどについての保証はない。ちなみにこの万能近似定理が証明された頃(1989年)は、現在のような多数の層を積み重ねてパラメータ数を増やす考え方ではなく、隠れ層のベクトルの次元(ノード数とも呼ばれる)を増やして実現する考え方が主流であった。現在のようなニューラルネットワークをディープニューラルネットワークと呼ぶのに対比してこのようなネットワークはシャローネットワークと呼ばれている。

Deep Linear Networks

非線形な活性化関数を含まない線形変換の積み重ねは、数学的には1回の線形変換と等価である。しかし実際にこのようなネットワークが研究対象となることがあり、Deep Linear Networks(DLN)と名前がついている。DLNは現在の深層学習における、層を増やしていく構造が理論的にどのような役割を果たしているかを解析するために用いられている。興味深いことにこの自明な線形変換を勾配降下法で学習すると、解析的に解く場合では現れないはずのパラメータの正則化効果などを観察することができる。机上の理論と現実の実装との間にある不思議の探索と見ることもでき、興味深い一例といえる。我々人類も自身の知識の学習過程を1対1の直接対応(暗記)や、単純な線形変換で行わず、コツコツと知識の多層化を行うことで新しい地平が開けるのかもしれない。


活性化関数

以下に代表的な活性化関数を示す。ここで記載されている関数はスカラ関数として記述されているが、要素毎に適用することでベクトル関数として考えることができる。

シグモイド関数

\[ \sigma(x) = \frac{1}{1+\exp(-x)} \]

出力値が常に\(0 < \sigma(x) < 1\) となる。

双曲線正接関数

\[ \tanh(x) = \frac{e^x-e^{-x}}{e^x+e^{-x}} \]

出力範囲は\(-1 < \tanh(x) < 1\) である。

ReLU

\[ \mathrm{ReLU}(x) = \max(0,x) \]

現在のニューラルネットワークでは最も広く利用される活性化関数の一つである。ReLUは電気工学・制御工学の文脈ではランプ関数(Ramp function)とも呼ばれる。

演習(活性化関数の可視化)

上記の活性化関数のグラフをmatplotlib等で描け。

出力層と活性化関数

隠れ層と出力層では活性化関数の役割が異なる。

回帰

連続値を予測する場合、最終出力は実数全体をとることが想定されるため、活性化前の値をそのまま出力することになるが、これは

\[ y=a \]

という恒等関数を活性化関数に用いたことと等価と考えることができる。

二値分類

あるクラスに属する確率を予測する場合、

\[ y = \sigma(a) \]

を用いる。

多クラス分類

クラス数を \(K\) とすると、出力としては1-of-\(K\) 表現を想定し、\(k\)番目の値として

\[ y_k = \frac{\exp(a_k)} {\sum_{j=1}^{K}\exp(a_j)} \]

で定義されるSoftmax関数を用いる。

Softmaxの出力は

\[ \sum_k y_k = 1 \]

を満たし、確率として解釈できる。


損失関数

ニューラルネットワークの学習では、予測値と正解値のずれを表す損失関数を最小化する。

回帰

平均二乗誤差

\[ E = \sum_n (y_n-t_n)^2 \]

を用いる。理論上、前に係数\(\frac{1}{2}\)をつける場合もある。

二値分類

交差エントロピー

\[ E= - \sum_n \left( t_n\log y_n + (1-t_n)\log(1-y_n) \right) \]

を用いる。

多クラス分類

\[ E = - \sum_n \sum_k t_{nk} \log y_{nk} \]

を用いる。

確率モデルとの対応

これらの損失関数は恣意的に選ばれているわけではない。

  • 回帰:正規分布
  • 二値分類:ベルヌーイ分布
  • 多クラス分類:カテゴリカル分布

を仮定したときの負の対数尤度に対応している。この記述、どこかで見たような...と思った人は記憶力がよい。これらは線形モデル(線形回帰/線形識別)でも同様に登場したものである。現象に対する適切な確率過程を考えて立式する考え方は、ニューラルネットワークにおいても根幹にあるると言える。

計算グラフと連鎖律

ニューラルネットワークでは多数の演算が連続して行われる。

例えば

\[ x \rightarrow u=x^2 \rightarrow y=3u+1 \]

という計算を考える。

このとき

\[ y = 3x^2+1 \]

である。

連鎖律\(y\)\(x\)で微分した場合

\[ \frac{dy}{dx} = \frac{dy}{du} \frac{du}{dx} \]

となる。

実際に計算すると

\[ \frac{dy}{du} = 3, \frac{du}{dx} = 2x \]

なので

\[ \frac{dy}{dx} = 6x \]

となる。

これから述べるニューラルネットワークの誤差逆伝播法は、この連鎖律を効率的に適用する方法である。

演習(手計算による逆伝播)

次の計算グラフを考える。

\[ x \rightarrow u=x^2 \rightarrow v=u+2 \rightarrow y=v^3 \]
  • 順伝播により \(x=2\) のときの \(y\) を求めよ。
  • 連鎖律を用いて\(x=2\) のときの以下の微分値を求めよ。
\[ \frac{dy}{dx} \]
  • 各ノードで
\[ \frac{dy}{dv}, \quad \frac{dy}{du}, \quad \frac{dy}{dx} \]

を順に計算し、逆向きに微分が伝播することを確認せよ。


誤差逆伝播法

ニューラルネットワークは

  • 線形変換
  • 活性化関数

の繰り返しで構成される。

仮に全て代入した損失関数を重みについて微分すると、非常に複雑な形式となる上、柔軟にネットワークを組み替えたりする場合に現実的ではない。そのため損失関数の重みパラメータに関する勾配は連鎖律によって求められる。このように損失の勾配を計算する方法のことを誤差逆伝播法(Backpropagation; Backprop)という。誤差逆伝播法は、コンピュータの「自動微分」における逆進モード(リバースモード)計算を損失の勾配算出に適用したものと解釈できる。

誤差逆伝播法では、

  1. 順伝播で各層の出力を計算する
  2. 出力層で誤差を計算する
  3. 誤差を後ろから前へ伝播させる
  4. 各重みの勾配を計算する

という流れで効率的に勾配を求める。誤差逆伝播法という名前は特に3番から4番の流れに由来するものである。

「でんぱ」「でんぱん」 ぐみどっといんく...

さて、声に出して読みたい日本語「誤差逆伝播法」である。班員で声を合わせて読んでみよう。

「ごさ ぎゃくでん ほう」である。「でんぱん」ではない。最近の日本語入力は賢いため、ローマ字入力でここの"NN"を入れなかったかは大して影響がないのかもしれない。しかしながらこれはある意味重要である。「伝播(でんぱ)」は物事や情報、波などが伝わっていく、広がっていく様子を表す言葉である。誤差逆伝播法では「誤差」が入力側に伝わり広がっていく。

読み方もさることながら、違う漢字を書いている可能性もある。「誤差逆伝搬」と書いている事例はたまにある。本の名前は出さないが、先日はなんと出版物でも見かけた。これは誤読からの派生である可能性が高い。ちなみに「伝搬」の意味はエネルギー、波、ウィルスなどが空間や媒体を伝わって広がることを指す。あれ、似ている? ニューラルネットワークの重みを物理媒体とみなすかが焦点となる気もするが、ここでは概念として考えよう。言葉は変わりゆくものなので、正しい日本語という言い方はよろしくないと思っているが、原義や先人たちの緻密な翻訳に想いを馳せてここはしっかり「ごさぎゃくでんぱ」である。不安な人は「バックプロパゲーション」で踏襲しても問題ない。ちゃんと上記のアルゴリズムを理解していれば、君をカタカナ語を並べたてるエセAIコンサルと思ったりはしないだろう。

ちなみに我々電気系において、読み方関係の鬼門はまだいくつかある。たとえば「電波伝搬」。これは「でんぱでんぱん」である。理学系の電磁気学では伝播と呼ぶこともあるとかないとか。なんと重力波だと伝播らしい。これは大変である。最後に「歪み」。これは「ひずみ」である。電気系(もしくは多くの工学で)においてこれを「ゆがみ」と読んではいけない。ちなみに相対性理論だと「時空の歪み(ゆがみ)」だそうだ。地雷が多いので気をつけよう。

出力層の誤差

誤差逆伝播法では、まず出力層における誤差を求める。

多クラス分類を考え、出力層にSoftmax関数、損失関数に交差エントロピーを用いる。

Softmaxは

\[ y_k = \frac{\exp(a_k)} {\sum_j \exp(a_j)} \]

であり、損失関数は

\[ E = - \sum_k t_k \log y_k \]

である。

ここで、

\[ \delta_k = \frac{\partial E}{\partial a_k} \]

を求めたい。

導出を行うと、

\[ \frac{\partial E}{\partial a_k} = y_k-t_k \]

となる。

つまり、出力層における誤差は「予測値 − 正解値」で表されることが分かる。これはニューラルネットワークにおいて極めて重要な結果である。

例えば、

\[ \mathbf{t} = (0,1,0) \]

であり、

\[ \mathbf{y} = (0.1,0.8,0.1) \]

であれば、

\[ \mathbf{\delta} = (0.1,-0.2,0.1) \]

となる。

正解クラスでは負の値となり、予測確率を増やす方向へ重みが更新される。

一方、不正解クラスでは正の値となり、予測確率を減らす方向へ重みが更新される。

演習

次の予測結果に対して、\(\mathbf{\delta} = \mathbf{y}-\mathbf{t}\)を求めよ。

\[ \mathbf{t} = (0,0,1) \]
\[ \mathbf{y} = (0.2,0.3,0.5) \]

単純ではあるが、numpyでも実装してみるとよい


出力層の重み勾配

出力層の線形変換を

\[ a_k = \sum_j w_{kj} z_j + b_k \]

とする。

連鎖律より、

\[ \frac{\partial E}{\partial w_{kj}} = \frac{\partial E}{\partial a_k} \frac{\partial a_k}{\partial w_{kj}} \]

である。

ここで

\[ \frac{\partial E}{\partial a_k} = \delta_k \]

および

\[ \frac{\partial a_k}{\partial w_{kj}} = z_j \]

なので、

\[ \frac{\partial E}{\partial w_{kj}} = \delta_k z_j \]

となる。

つまり、重み勾配は「入力 × 誤差」で表されることが分かる。この掛け算は実はロジスティック回帰や線形回帰でも出てきた。すなわち重みをつけた線形変換において本質的な部分と言える。


隠れ層への誤差伝播

出力層の誤差が求まれば、連鎖律によって隠れ層へ伝播できる。

隠れ層の出力を

\[ z_j = h(a_j) \]

とする。

このとき、

\[ \delta_j = \frac{\partial E}{\partial a_j} \]

\[ \delta_j = h'(a_j) \sum_k w_{kj} \delta_k \]

となる。

つまり、

  1. 後段から誤差を受け取る
  2. 活性化関数の微分を掛ける

ことで隠れ層の誤差が求められる。

ReLUの場合、

\[ h'(a) = \begin{cases} 1 &(a>0)\\ 0 &(a\le 0) \end{cases} \]

であるため、計算は非常に簡単である。


誤差逆伝播法のアルゴリズム

ニューラルネットワークの学習では、以下を繰り返す。

Step 1 順伝播

入力から出力まで計算する。

\[ \mathbf{x} \rightarrow \mathbf{a} \rightarrow \mathbf{z} \rightarrow \mathbf{y} \]

Step 2 損失計算

予測値と正解値から損失を求める。

\[ E = L(\mathbf{y},\mathbf{t}) \]

Step 3 出力層の誤差

\[ \delta_k = y_k-t_k \]

を求める。

Step 4 隠れ層へ伝播

\[ \delta_j = h'(a_j) \sum_k w_{kj}\delta_k \]

を求める。

Step 5 勾配計算

各重みについて

\[ \frac{\partial E}{\partial w} \]

を求める。

Step 6 パラメータ更新

勾配降下法によって更新する。

\[ w \leftarrow w - \eta \frac{\partial E}{\partial w} \]

ここで \(\eta\) は学習率である。


勾配降下法

勾配は誤差関数が最も増加する方向を表す。

したがって、その逆方向へ少しずつ移動することで誤差を減少できる。

更新式は

\[ w \leftarrow w - \eta \frac{\partial E}{\partial w} \]

である。

学習率が大きすぎると発散し、小さすぎると収束が遅くなる。

演習(単純な関数での勾配降下法)

関数\(E(w)=(w-3)^2\) を考える。

  1. 勾配を求めよ。
  2. 初期値 \(w=0\) とする。
  3. 学習率 \(\eta=0.1\) として3回更新せよ。

この演習についてはnumpyで実装してみる。


演習(NumPyによる隠れ層1層のニューラルネットワーク実装)

誤差逆伝播法により隠れ層1層のニューラルネットワークを実装せよ。2次元ベクトル入力の分類問題とする。隠れ層のノード数は適宜設定して良い。あまり大きい必要はない。学習率\(\eta\) は自由に設定できるようにせよ。

XOR問題

ニューラルネットワークの有効性を確認するため、XOR問題を考える。

入力と正解は次の通りである。

x1 x2 t
0 0 0
0 1 1
1 0 1
1 1 0

この問題は線形分類器では解けない。

実際、入力空間においてクラスを1本の直線で分離できないためである。これはパーセプトロンのところで事例として学んだ。しかし隠れ層を持つニューラルネットワークでは解くことができる。

演習(XOR問題)

実装したニューラルネットワークを用いてXOR問題を学習せよ。

  1. 学習前後の損失を比較せよ。
  2. 学習回数による損失の変化を確認せよ。
  3. 最終的な予測値を確認せよ。

まとめ

ニューラルネットワークは、基底関数そのものを学習することで高い表現能力を実現するモデルである。

ニューラルネットワーク学習では、誤差逆伝播法が用いられ、 $$ \text{順伝播}\rightarrow\text{損失計算}\rightarrow\text{誤差逆伝播}\rightarrow\text{パラメータ更新} $$

を繰り返すことでモデル学習が進む。

特に、

\[ \delta = y-t \]

という出力層の誤差は、ニューラルネットワークの学習を理解する上で最も重要な式の一つである。この誤差がネットワーク内部へ伝播することで、全ての重みの勾配を効率的に計算できる。

発展トピック

ここまで学んだ内容により、小規模なニューラルネットワークを実装し、分類や回帰を行うことができる。しかし、実際の深層学習ではさらに多くの工夫が用いられている。本日の最後に、その代表例を簡単に紹介する。

ミニバッチ学習

これまでの説明では、1つのサンプルごとに勾配を計算して重みを更新する方法を考えてきた。これをオンライン学習と呼ぶ。もう一つの方策としては全ての学習データサンプルで計算した勾配の総和をとってから重みを更新する方法もある。こちらをバッチ学習と呼ぶ。

一方で実際には、

$$ B $$ 個のサンプルをまとめて処理し、その平均的な勾配を用いて更新することが多い。

このような学習方法をミニバッチ学習という。

ミニバッチ学習には、

  • 計算効率がよい
  • 勾配のばらつきを抑えられる
  • GPUとの相性がよい

といった利点がある。

現在の深層学習では、ほぼすべての学習がミニバッチ学習によって行われている。ミニバッチ学習はオンライン学習とバッチ学習の中間として位置付けられる。ミニバッチ学習では学習データを適当なミニバッチに分割した上で、ミニバッチごとに重み更新をしていき、全ての学習データを使う。この時全ての学習データを使う単位のことを「エポック」、ミニバッチごとの重み更新のことを「イテレーション」と呼ぶ。すなわち1エポック中にどの順番でミニバッチを用いるかについては、任意性があり、重み更新の順番が変わるのでエポックごとの結果はこの順序には影響される。しかし現実的にはこの点はほとんど考慮せずに学習を続けることが多い。


確率的勾配降下法(SGD)

勾配降下法では、

\[ w \leftarrow w-\eta \frac{\partial E}{\partial w} \]

によってパラメータを更新する。

ミニバッチから計算された勾配を用いて更新する方法を、特に確率的勾配降下法(Stochastic Gradient Descent, SGD)という。SGDでは勾配にばらつきが含まれるため、必ずしも最急降下方向へ進むわけではない。しかし、そのばらつきによって局所的な解から抜け出しやすくなるという利点もある。

現在でも深層学習における基本的な最適化手法として利用されている。


Adam

SGDは単純である反面、学習率の設定が難しいという欠点がある。

そこで広く利用されているのが Adam (Adaptive Moment Estimation) である。Adamでは、

  • 勾配の平均
  • 勾配の二乗平均

を利用しながら学習率を自動調整する。

その結果、

  • 初期値に対する頑健性が高い
  • 学習が安定しやすい
  • ハイパーパラメータ調整が容易

という特徴を持つ。

現在の深層学習ライブラリでは、最初に試す最適化手法としてAdamが利用されることが多い。


勾配消失問題

ニューラルネットワークを深くすると、誤差逆伝播の際に勾配が非常に小さくなってしまう場合がある。

例えば、

\[ 0.1 \times 0.1 \times 0.1 \times \cdots \]

のような計算を繰り返すと、値は急速に0へ近づく。すると入力層に近い部分の重みがほとんど更新されなくなり、学習が進まなくなる。これを勾配消失問題という。特にシグモイド関数や双曲線正接関数を用いた深いネットワークで発生しやすいことが知られている。

ReLUの普及は、この問題を軽減する大きな要因となった。


Batch Normalization

深いネットワークでは、学習中に各層の出力分布が変化し続けるため、学習が不安定になることがある。そこで導入されたのが Batch Normalization である。

Batch Normalizationでは、ミニバッチごとに各層の出力を正規化する。

概念的には、バッチ中の平均と分散を用いて

\[ x \rightarrow \frac{x-\mu}{\sigma} \]

のような変換を行う。

これにより、

  • 学習が安定する
  • 大きな学習率が利用できる
  • 学習速度が向上する

といった効果が得られる。

深層学習の発展において非常に重要な技術の一つである。


深層学習へ

本章で扱ったネットワークは、

  • 入力層
  • 隠れ層1層
  • 出力層

からなる単純なモデルであった。

しかし現在の深層学習では、

  • 数十〜数百層のニューラルネットワーク
  • 画像向けのCNN
  • 時系列向けのRNN
  • 自然言語処理のみならず近年あらゆるタスクで広く利用されるTransformer

など、より大規模で高度なモデルが利用されている。

これらのモデルは一見すると複雑に見えるが、その根底にある考え方は本日学んだものと同じである。

すなわち、

  1. 順伝播で出力を計算する
  2. 損失関数を計算する
  3. 誤差逆伝播法で勾配を求める
  4. 最適化手法で重みを更新する

という流れで学習が行われている。すなわち本日の内容は現代の深層学習を理解するための最も重要な基礎となる。PyTorchやTensorflowなどのフレームワークもこの誤差逆伝播法の実装をフレームワーク内で済ませることで、ユーザが表層的に誤差関数とネットワーク構造を設定するだけでニューラルネットワークの学習を可能としている。