1. HOME
  2. ブログ
  3. IT技術
  4. 機械学習の要「誤差逆伝播学習法」を解説・実装してみる!

機械学習の要「誤差逆伝播学習法」を解説・実装してみる!

「誤差逆伝播学習法」とは?

誤差逆伝播学習法(BP: Backpropagation)とは、ニューラルネットワークの学習法の1つで、今現在もっとも主流で強力な学習法を指します。

その名の通り、ネットワークを誤差情報が逆伝播することから名前がつけられていますが、ちょっとそれだけでは分かりづらいですね。

この記事では、誤差逆伝播学習法の仕組みとその実装を解説していきます。

解説部では、少し数式が多いですが、ひとつひとつ丁寧に見ていけば必ず理解できると思います。

また、誤差逆伝播学習法の考え方は、様々な学習方法に応用されている学習法なのでしっかりと理解しておきましょう!

実行環境

以下は筆者の実行環境です。

今回も数値計算用のNumPyとグラフ描画用のmatplotlibを使います。

  1. Python 3.7.3
  2. NumPy 1.16.3
  3. matplotlib 3.0.3

「誤差逆伝播学習法」実装の前準備

ネットワークの初期化に関するもの

実装を踏まえて解説していきますが、今回も、多層パーセプトロンの時と同じように Network クラスを定義してオブジェクト指向で実装していきます。

Networkクラスのコンストラクタ __init__() や重みの初期化関数 init_weights() 、今回用いるIrisデータセットを読みこむ関数 load_iris() は以下のようになります。

実装の前準備【形式ニューロン編】

次の前準備として、形式ニューロンの処理に関するものを定義していきます。

形式ニューロンは以下の図と式で表されるような性質を持っています。

【図. 形式ニューロン】

(1)$$\displaystyle i = \sum_{i=1}^n{w_i x_i}$$
(2)$$\displaystyle o = f(i) $$

上の図と式の意味は、ある外部からの入力 \(x_i \{i=1,2,3,\cdots,n\}\) に対して、それぞれ重み \(w_i \{i=1,2,3,\cdots,n\}\) がかけられ、その和 \(i\) がニューロンへの最終的な入力値になります。

その後、その入力を引数とする活性化関数  \(f(x)\)  の値がそのニューロンの最終的な出力値 \(o\) となります。

シグモイド関数

形式ニューロンでは活性化関数はステップ関数でしたが、ここではシグモイド関数と呼ばれる活性化関数を用います。

(3)$$f(x)=\frac{1}{1+exp(-\epsilon x)} $$

ここで \(\epsilon\) は、シグモイド関数の傾きと言い、関係性は以下のような図になります。

なぜ、シグモイド関数を用いるかは、後で詳しく説明したいと思います!

【図. シグモイド関数】

実装

とりあえず、これらの特性を実装してみます。

このようになります!

ここでは、シグモイド関数の傾きはクラスインスタンスとして定義しています。

「誤差逆伝播学習法」について解説!

それでは、「誤差逆伝播学習法」について解説していきます。

考え方自体はシンプルですが、実装するために序盤は式変形など数学的な内容が多いです。

少し気合いが必要ですが頑張っていきましょう!

誤差逆伝播学習法についてザックリ知ろう!

まずは、誤差逆伝播学習法について大枠だけ理解しましょう。

先ほども言ったように、誤差逆伝播学習法は、教師信号と実際と出力信号との間に生じる誤差情報を使ってネットワーク全体を学習していきます。

【図. 誤差逆伝播学習法の概略】

今までの学習法と違い、全ての重みの学習が可能で、その性能の良さから現在でも主流な学習法です。

この記事では最終層(出力層)を第 \(m\) 層とし、中間層を第 \(k\) 層のように表記します。

誤差関数の定義

まずは、教師信号と実際の出力との誤差情報を示す、誤差関数を定義します。

  1. 誤差関数が大きいほど、理想状態とは遠い

誤差関数が大きいほど理想状態とは遠いということを示し、学習の要と言えます。

誤差逆伝播学習法では一般的に誤差関数は以下のような式で表されます。

(4)$$E=\frac{1}{2}\sum_{i=1}^{n_m}(t_i-o^m_i)^2 $$

ここで、 \(o^m_i\) は出力層( 第\(m\)層 ) \(i\) 番目ニューロンの出力値で、 \(t_i\) はそれに対応する教師信号を指します。

\(1/2\) が付いている理由は、後々計算を楽にするためだけなので、深い意味はありません。

このような誤差関数を、「二乗誤差関数」と言います。

  1. 誤差関数が小さくなれば、学習ができている

この誤差関数が小さくなれば、学習ができていると言えます。

勾配降下法

次に、先ほど定義した誤差関数を小さくするために、勾配降下法(または最急降下法)と呼ばれる手法をとります。

勾配降下法のイメーシとしては、以下のような図がよく用いられます。

【図. 勾配降下法の概略図】

上の図で示すように、最小化させたい誤差関数の傾き(勾配)を計算し、その傾きの大きさとは逆方向に重みを調整すれば、誤差関数の値を小さくできます。

「誤差逆伝播学習法」の重みの更新式

したがって、誤差逆伝播学習法では、「重みの更新式」は以下のように定義します。

(5)$$\Delta w_{i,j}^{k-1,k}=-\eta\frac{\partial E}{\partial w_{i,j}^{k-1,k}}  $$

パっと見ると複雑そうですが、そんなに難しくありません。

ここで \(w_{i,j}^{k-1,k}\) は、第 \(k-1\)層\(i\) 番目ニューロンと、第 \(k\)層\(j\) 番目ニューロンとの間の重みです。

【図. 重みの見方】

つまり式(5)は、現在の重みで誤差関数を偏微分して得られた傾きとは、逆方向に重みを更新している式を表しています。

ちなみに \(\eta\) は学習率で、通常0.1や0.01などの小さな値を使います。

これは学習の進行速度を表しています。

しかし式(5)の形では、まだ実装するには難しそうです。

そもそも \(E\) は、出力値 \(o\) の関数なので、重みで偏微分できません。

では、どのように計算したらよいでしょうか?

式変形をする

ここから少しややこしく、複雑になっていきますが、順を追ってゆっくり理解していきましょう。

まず、式(5)の右辺は、以下のように変形してみます(連鎖率)。

(6)$$\frac{\partial E}{\partial w_{i,j}^{k-1,k}}=\frac{\partial E}{\partial i_{i}^{k}}\cdot\frac{\partial i_{i}^{k}}{\partial w_{i,j}^{k-1,k}} $$

ここで \(i_{i}^{k}\) は、第 \(k\)層目\(j\) 番目ニューロンの入力値です。

さらに今出てきた、式(6)右辺について考えてみましょう。

まずは、入力値の偏微分の部分については、以下のように簡単な形に導出できます。

(7)$$\begin{align}\frac{\partial i_{i}^{k}}{\partial w_{i,j}^{k-1,k}}& =\frac{\partial \sum_{l=1}^{n_{k-1}}{w_{i,l}^{k-1,k}o_l^{k-1}}}{\partial w_{i,j}^{k-1,k}} \\ & = o^{k-1}_j\end{align} $$

問題は、式(6)右辺の前半です。

ここで一旦以下のように、新たに \(\delta\) という変数を定義してみます。

(8)$$ \delta_i^{k} = -\frac{\partial E}{\partial i_{i}^{k}}  $$

このように定義したことにより、式(5)は、以下のようなジンプルな形で一旦書き換えることができます。

(9)$$\Delta w_{i,j}^{k-1,k}=\eta\delta_i^k o^{k-1}_j  $$

あとは、この \(\delta\) が解決できれば実装ができそうです!

もう少し頑張っていきましょう。

δについて

この \(\delta\) の導出が、「誤差逆伝播学習法の要」と言えます。

まず、\(\delta\) について、連鎖率を使って分解してみます。

(10)$$\begin{align}\delta^k_i &= -\frac{\partial E}{\partial i^k_i} \\ &= -\frac{\partial E}{\partial o^k_i} \cdot \frac{\partial o^k_i}{\partial i^k_i}\end{align} $$

ここで、式(10)右辺の後半については、

(11)$$\frac{\partial o^k_i}{\partial i^k_i} = \frac{\partial f(i^k_i)}{\partial i^k_i}=f’(i^k_i) $$

とシンプルな活性化関数の導関数になります。

ですが、重要なのは、 \(\frac{\partial E}{\partial o^k_i}\) の部分です。

ここで以下の2パターンの場合で導出方法が異なってきます。

  1. 最終層(第 \(m\) 層)のとき:\(\delta^m_i\)
  2. 中間層(第 \(k\) 層)のとき:\(\delta^k_i\)

最終層のとき

最終層(第 \(m\) 層)のときは、シンプルです。

(4)$$E=\frac{1}{2}\sum_{i=1}^{n_m}(t_i-o^m_i)^2 $$

ですので、

(12)$$\frac{\partial E}{\partial o^m_i}=o^m_i – t_i $$

となります。

したがって、\(\delta\) は、以下のようになります。

(13)$$\delta^m_i=-(o^m_i – t_i)f’(i^k_i) $$

中間層のとき

中間層(第 \(k\) 層)のときは、少し工夫が必要です。

\(-\frac{\partial E}{\partial o^k_i}\) が中間層の時の \(\delta\) ですが、\(E\) には \( o^k_i\) が含まれていないのでこのままでは偏微分不可能です。

そこで、また「連鎖率」を使って式変形を行なっていきます。

(14)$$\begin{align}\frac{\partial E}{\partial o^k_i} &= \sum_{l=1}^{n_{k+1}}(\frac{\partial E}{\partial i^{k+1}_l}\cdot \frac{\partial i^{k+1}_l}{\partial o^k_i}) \\ &= \sum_{l=1}^{n_{k+1}}(\frac{\partial E}{\partial i^{k+1}_l}\cdot \frac{\partial (\sum_{h=1}^{n_{k+1}}w_{l,h}^{k,k+1}o_h^k)}{\partial o^k_i}) \\ &= \sum_{l=1}^{n_{k+1}}(\frac{\partial E}{\partial i^{k+1}_l}w_{l,i}^{k,k+1}) \\ &= -\sum_{l=1}^{n_{k+1}}(\delta_l^{k+1}w_{l,i}^{k,k+1})\end{align} $$

このように少し複雑ですが変形ができます。

したがって中間層(第\(k\)層)の時の \(\delta\) は、以下のようになります。

(15)$$\delta^k_i = f’(i_i^k)\sum_{l=1}^{n_{k+1}}(\delta_l^{k+1}w_{l,i}^{k,k+1}) $$

ここで重要なのは、最後に得られた式で、一つ後ろの層の情報\(\delta_l^{k+1}\)が含まれていることです。

すなわち、最終層で計算した誤差情報 \(\delta\) が連鎖的に入力層側に伝わっていくことがわかります。

これが「誤差逆伝播学習法」という名前の由縁です。

誤差逆伝播学習法の重み更新式

それでは、まとめると、誤差逆伝播学習法の重み更新式は以下のようになります。

(16)$$\Delta w^{k-1,k}_{i,j}=\eta\delta^k_io^{k-1}_j  $$
  1. 最終層(第\(m\)層)のとき
    $$\delta^m_i=-(o^m_i – t_i)f’(i^k_i) $$
  2. 中間層(第\(k\)層)のとき
    $$\delta^k_i = f’(i_i^k)\sum_{l=1}^{n_{k+1}}(\delta_l^{k+1}w_{l,i}^{k,k+1}) $$

活性化関数(シグモイド関数)の微分

解説の最後に「活性化関数(シグモイド関数)の微分」を考えます。

ここで、「なぜステップ関数ではダメだったのか」をお話します。

それは、ずばり不連続な関数(微分不可能)だからです。

また、\(f(x)=x\) のような単純な線形関数でもよいのですが、勾配=傾きなので常に一定になってしまい不適切です。

そこで、今回用いるようなシグモイド関数を使っているのです。

他にも、\(tanh(x)\)(ハイパボリックタンジェント)などもよく使われます。

シグモイド関数を微分すると以下のようになります。

$$f’(x)=\epsilon(1-f(x))f(x)$$

このように微分するとまた中にシグモイド関数が出てくるので、無限に微分が可能です。

これがシグモイド関数の良いところです。

シグモイド関数を使う場合の誤差逆伝播学習法の重み更新式

したがって、シグモイド関数を使う場合の誤差逆伝播学習法の重み更新式は、

(16’)$$\Delta w^{k-1,k}_{i,j}=\eta\delta^k_io^{k-1}_j  $$
  1. 最終層(第\(m\)層)のとき
    $$\begin{align}\delta^m_i &=-\epsilon(o^m_i – t_i)(1-f(i^m_i))f(i^m_i) \\ &= -\epsilon(o^m_i – t_i)(1-o^m_i)o^m_i\end{align} $$
  2. 中間層(第\(k\)層)のとき
    $$\begin{align}\delta^k_i &= \epsilon(1-f(i^k_i))f(i^k_i)\sum_{l=1}^{n_{k+1}}(\delta_l^{k+1}w_{l,i}^{k,k+1}) \\ &= \epsilon(1-o^k_i)o^k_i\sum_{l=1}^{n_{k+1}}(\delta_l^{k+1}w_{l,i}^{k,k+1}) \end{align}$$

以上が誤差逆伝播学習法の学習法導出になります。

お疲れ様でした!

「誤差逆伝播学習法」を実装する!

それでは、誤差逆伝播学習法を実際に実装していきたいと思います。

誤差逆伝播学習法は、導出は複雑なものの、アルゴリズム自体はいたって簡単です。

アルゴリズム

1. 入力パターンをネットワークに順伝播して出力値を得る
   ↓
2. 出力値と教師信号を用いて、 \(\delta\) を計算
   ↓
3.  \(\delta\) を用いて重み更新量を出力層側から計算
   ↓
4. 重みを更新
   ↓
5. 学習終了基準を満たしていなければ、1. に戻る

実装例

今回は、学習率減衰というものを導入して、学習が進むごとに減衰させることにしてみます。

学習率減衰は、適切に設定をすれば収束を早める効果があります。

また、最後に、学習途中の誤答率をプロットするようにしています。

ちなみに、 \(\delta\) を計算する関数は calc_delta() で、誤差逆伝播の関数は backward() としています。

教師信号は、以前は0や1を使っていましたが、シグモイド関数では重みが発散しないように0.1や0.9を代わりに用いることが多いです。

いざ、実行!

それでは、実際に上記コードを実行してみましょう。

 

【図. 誤答率の遷移】

見事学習がうまくいっていることが分かります!

また安定感もよく、高確率で高精度を得られることも実際に動かしてみるとわかります。

実際に10試行やってみると平均精度97.4%でした。

もう少しパラメータなどを工夫してみると良い結果が得られるかもしれません。

また、Irisデータセットは普通98%あたりが限度なので、別のデータセットを使ってみるのも良いですね。

ライトコードよりお知らせ

にゃんこ師匠にゃんこ師匠
システム開発のご相談やご依頼はこちら
ミツオカミツオカ
ライトコードの採用募集はこちら
にゃんこ師匠にゃんこ師匠
社長と一杯飲みながらお話してみたい方はこちら
ミツオカミツオカ
フリーランスエンジニア様の募集はこちら
にゃんこ師匠にゃんこ師匠
その他、お問い合わせはこちら
ミツオカミツオカ
   
お気軽にお問い合わせください!せっかくなので、別の記事もぜひ読んでいって下さいね!

一緒に働いてくれる仲間を募集しております!

ライトコードでは、仲間を募集しております!

当社のモットーは「好きなことを仕事にするエンジニア集団」「エンジニアによるエンジニアのための会社」。エンジニアであるあなたの「やってみたいこと」を全力で応援する会社です。

また、ライトコードは現在、急成長中!だからこそ、あなたにお任せしたいやりがいのあるお仕事は沢山あります。「コアメンバー」として活躍してくれる、あなたからのご応募をお待ちしております!

なお、ご応募の前に、「話しだけ聞いてみたい」「社内の雰囲気を知りたい」という方はこちらをご覧ください。

ライトコードでは一緒に働いていただける方を募集しております!

採用情報はこちら

関連記事