目次
はじめに
現在ニューラルネットワークは多くの分野で使われています。私の場合、セキュリティ上の課題を解決する手段としてニューラルネットワークを用いています。
このニューラルネットワークですが、モデルの表現性を高めるために層を重ねることが必要不可欠となっています。層を重ねたニューラルネットワークは、推定対象のパラメータが多く、前回記事で用いた方法でパラメータを推定することができません。
以上のような背景のもとで生まれた技術が誤差逆伝播法です。誤差逆伝播法は合成関数の微分の公式を用いることで、全ての層のパラメータの推定を可能にします。
本記事では、3層構造のニューラルネットワークを題材に、誤差逆伝播法がどのような仕組みになっているのか、数式とJuliaのコードを示すことで説明しようと思います。
なお、本記事はなっとく!ディープラーニングの第6章に沿って書いております。問題設定等をより詳しく知りたい方は本書をご覧ください。
勾配降下法のおさらい
勾配降下法の基本式は、
です。ただし、は更新後のパラメータ、は更新前のパラメータ、は学習率、は損失関数です。 によって更新の方向が、によって更新の大きさが決まります。なお、損失関数は二乗誤差とします。
複数層のニューラルネットワーク
図1のような3層構造のニューラルネットワークを考えます。
このとき、入力層のベクトルを横ベクトルとします。また、入力層から中間層の変換を
とします。はのベクトル、はの行列です。
次に、の各成分を活性化関数Reluに入力し、
と変換します。ただし、
とします。
最後に、中間層から出力層への変換を
とします。はスカラー、はの行列です。
誤差逆伝播
ここでは、勾配降下法を使いを推定する方法を紹介します。を推定する方法については、過去の記事 aisinkakura-datascientist.hatenablog.com で紹介しているので、興味があればご覧ください。
一つの成分に対する更新式
「勾配降下法のおさらい」に書いた通り、パラメータを推定するためには損失関数をパラメータで微分した式が必要となります。今回はわかりやすいように、
とし、代表としてを推定するための更新式を導こうと思います。
更新式のポイントは、を計算することです。この式は、合成関数の微分の公式を使うことで次の様に分解できます。
分解した式を一つずつ計算していきます。まず、を計算します。より、
となることから、
となります。次にについて、より、
となります。なお、この右辺の式を今後はと呼びます。
さらに、を計算します。
とすると、より、
となります。したがって、
となります。
最後に、を計算します。なので、
となります。
以上をまとめると、
となります。なお、の係数の2は学習率に吸収すれば良いです。そのため、係数の2は消して等号を比例記号に置き換えています。
上記を踏まえ、一般のによるの微分を計算すると下記のようになります。
全ての成分に対する更新式
前節で
となると書きました。これを基にを計算すると、下記のようになります。
なお、RDとはReluDerivのこと、行列間のは各成分の積を表します。
コードに落とし込む
以上のことをJuliaで書いたコードが下記です。W_0_1_delta
がです。
using LinearAlgebra, Statistics, Random """ データ """ # 特徴量 streetlights = [ 1 0 1 0 1 1 0 0 1 1 1 1 ] # 目的変数 walk_vs_stop = [ 1 1 0 0 ] """ パラメータ """ # 学習率 α = 0.2 # 入力層のサイズ input_size = size(streetlights[1, :])[1] # 隠れ層のサイズ hidden_size = 4 # 出力層のサイズ output_size = 1 # ランダムに初期化された3つの層を結合するパラメータの初期値 #W_0_1 = 2*rand(input_size, hidden_size).-1 #W_1_2 = 2*rand(hidden_size, output_size).-1 Random.seed!(1) W_0_1 = 2*rand(input_size, hidden_size).-1 W_1_2 = 2*rand(hidden_size).-1 """ 関数 """ relu(x) = (x>0)*x relu2deriv(output) = output>0 """ 誤差逆伝播法 """ # MSEを保存するためのリストを作成 mse_list = [] for iteration=1:60 for index=1:size(walk_vs_stop)[1] # 順伝播 layer_0 = streetlights[index, :]' layer_1_1 = layer_0*W_0_1 # 中間層の活性化関数に入れる前 layer_1_2 = relu.(layer_1_1) # 中間層の活性化関数に入れた後 layer_2 = layer_1_2*W_1_2 # 更新式の差分 ## layer_2のパラメータの更新式の差分 ### layer_2ではなく、layer_2[1]としているのは、layer2が(1, 1)の行列になっているから layer_2_delta = layer_2[1]-walk_vs_stop[index] W_1_2_delta = α*layer_2_delta*layer_1_2' ## layer_1のパラメータの更新式の差分 W_0_1_delta = α*layer_2_delta*layer_0'*(W_1_2'.*relu2deriv.(layer_1_1)) # 更新 W_1_2 = W_1_2-W_1_2_delta W_0_1 = W_0_1-W_0_1_delta end # MSE(Mean Square Error)の計算 ## 全てのデータに対して順伝播を行う Layer_1_1 = streetlights*W_0_1 # 中間層の活性化関数に入れる前 Layer_1_2 = relu.(Layer_1_1) # 中間層の活性化関数に入れた後 pred_list = Layer_1_2*W_1_2 mse = mean((pred_list-walk_vs_stop).^2) push!(mse_list, mse) println("Error:", mse) println("pred_list:", pred_list) end
図2は学習が進むにつれ平均二乗誤差(Mean Squared Error:MSE)がどのように減少するのかを示した図です。学習が進むにつれ、MSEが減少していることがわかります。このように、誤差逆伝播法によって正しい予測ができるニューラルネットワークを構築できることがわかります。
なお、図2を描画するのに用いたコードは下記の通りです。
import Pkg Pkg.add("PyPlot") using PyPlot # 折れ線グラフを描画 plot(1:size(mse_list)[1], mse_list) xlabel("iteration") ylabel("MSE")
まとめ
本記事では、3層構造のニューラルネットワークを題材にして誤差逆伝播法について説明しました。また、Juliaを用いて誤差逆伝播法を実装し、ニューラルネットワークに含まれるパラメータを推定しました。
本記事が誤差逆伝播法の理解促進につながれば幸いです。