社畜エンジニア発掘戦線

駆けだしAIエンジニア

②線形回帰問題(バイアス付加)

第二問


前回のに引き続き線形回帰問題にチャレンジします。
前回の問題 -> ①線形回帰問題(データフィッティング) - 社畜エンジニア発掘戦線

今回もPYTHONISTA3でコードを書きながら解いていってみましょう。

設問1.ガウシアンノイズを付加したy = 5x+2に準ずるデータセット(インプット:x、正解ラベル:t)を作成せよ

前回とほとんど同じですが、直線に切片が追加されています。まず、numpyとmatplotlibをインポートします。

import numpy as np
import matplotlib.pyplot as plt

次に、y = 2x+5を描くためのxをセットします。今回はランダムに0から4の範囲でサンプル数は30にしましょう。(前回と数字を変えたことにあんまり意味はありません、最後グラフ化したとき極力分かりやすいように〜ぐらいです)

num_of_sam = 30
x = np.random.uniform(0, 4, num_of_sam)

最後に、y = 2x+5を書いて終わりです。今回も正解ラベルをtとしているのでt = 2x+5としましょう。

t = 2*x+5

前回と同じく、この正解ラベルにガウシアンノイズを乗せます。正規(ガウス)分布に沿った「平均0、標準偏差0.5」のノイズをサンプル数だけ生成します。このノイズをt = 2x+5に足しましょう。

noise = np.random.normal(0, 0.5, num_of_sam) 
t = 2*x+5+noise

これで設問1は終了です、グラフ化してみましょう。

plt.plot(x, t, 'o')
plt.show()

f:id:sutokun:20190204015905p:plain
ちょっと分かりにかもくいですが、切片がプラス5されています。

設問2.初期パラメータ:重み(w)を0.2、バイアス(b)を0として、出力を計算せよ

ではニューラルネットワークを設計します。今回も線形問題であることに変わりはないので「入力層」と「出力層」のみで構築できます。しかし、ここにバイアスを付加しなければいけません。なので設計するニューラルネットワークはこんな感じ。
f:id:sutokun:20190127090406j:plain

切片としてバイアス(b)が付加されています、大きく変わってないですね。ではコードに落としてみます。

w = 0.2
b = 0
y = w*x+b

設問2は完了です。

設問3.二乗和誤差関数を損失関数(E)として、損失量の値を求めよ

今回も損失関数に二乗和誤差関数を用いて損失量を計算します。前回も登場しましたが、二乗和誤差関数は次のような式で表されます。

二乗和誤差関数:
\displaystyle E = \sum_{i} (y_{i} -t_{i} )^2

E = np.sum((y-t)**2)

ここの記述は前回と全く同じです、バイアスが付加されても損失関数の式は変わりません。

設問4.勾配降下法を用いて、パラメータ更新式を導出せよ

ここから少し計算が必要です。前回は損失関数をパラメータwで微分してwの更新式を求めました。バイアスも同様のプロセスで更新式を求めにいきます。まずは損失関数のパラメータ微分から。

{\displaystyle
\frac{\partial E}{\partial b}\ = \frac{\partial }{\partial b}\  \sum_{i} (t_{i} - y_{i})^2
}
「連鎖律」を使います。
{\displaystyle
\frac{\partial E}{\partial b}\ = \frac{\partial y}{\partial b}\   \frac{\partial E}{\partial y}\  = \frac{\partial }{\partial b}(x_{i}w+b)\   \frac{\partial }{\partial y}\  \sum_{i} (t_{i} - y_{i})^2
}
微分を進めましょう。
{\displaystyle
\frac{\partial E}{\partial b}\ = 1 \cdot \Bigl(-2 \sum_{i} (t_{i} - y_{i})\Bigr)  = 2 \sum_{i} (y_{i} -t_{i} )
}
wx+bのbでの微分は1なので、wみたいにややこしいこと考えなくていいですね。

では、パラメータの更新式を求めます。更新方法は今回も降下法です。

{\displaystyle
b_{new} = b_{old} -  \mu \frac{\partial E}{\partial b}\ 
}

損失関数をbで微分した式を代入します。

{\displaystyle
b_{new} = b_{old} -  \mu \Bigl(2 \sum_{i} (y_{i} -t_{i} )\Bigr)
}

ちなみに、前回導出したwの更新式はこちら。

{\displaystyle
w_{new} = w_{old} -  \mu \Bigl(2 \sum_{i} x_{i}(y_{i} -t_{i} )\Bigr)
}

これで設問4は終了です。

設問5.学習率0.0018でイテレーションを30回転させ、その後の出力を計算せよ

今回の学習率は0.0018です。バイアスを付加するとなかなか厄介で、損失関数が発散(過学習)しやすくなります。そのため学習率を極力下げる分、ちょっと時間がかかりそうなのでイテレーション回数を増やしました。wも更新しなければいけないのでお忘れなく。

learning_rate = 0.0018
w = w - learning_rate*2*np.sum(x*(y-t))
b = b - learning_rate*2*np.sum(y-t)

これでパラメータの更新が完了しました。順伝搬から逆伝搬までの部分をインテンドさせて、forの中に入れてしまいます。learning_rateの定義は外に出しましょう。

w = 0.2
b = 0
learning_rate = 0.0018

num_of_itr = 30
for i in range(num_of_itr):
    y = w*x+b
    E = np.sum((y-t)**2)
    w = w - learning_rate*2*np.sum(x*(y-t))
    b = b - learning_rate*2*np.sum(y-t)

これでニューラルネットワークの構築が完成しました。うまくいっていれば最初に設定したプロットデータに沿っていい感じに直線がフィッティングされているはずです。

X_line = np.linspace(0, 2, 5)
Y_line = w*X_line+b

plt.plot(x, t, 'o')
plt.plot(X_line, Y_line)
plt.show()

f:id:sutokun:20190204015955p:plain
概ね合ってそうですがもうちょっと頑張る余地はあるかな、イテレーション回数を増やせば微修正が進んでいきます。

これで設問6は終了です。

設問6.損失量のイテレーション回数による推移をグラフ化せよ

前回と同様、各イテレーションで損失量を保存するコードを追記します。

w = 0.2
b= 0
learning_rate = 0.0018
E_save = [] #空のリストを用意

num_of_itr = 30
for i in range(num_of_itr):
    y = w*x+b
    E = np.sum((y-t)**2)
    E_save = np.append(E_save, E) #E_saveにEの値を保存
    w = w - learning_rate*2*np.sum(x*(y-t))
    b = b - learning_rate*2*np.sum(y-t)

では、最後に損失量を保存したリストをグラフ化します。

plt.plot(E_save)
plt.show()

f:id:sutokun:20190204020026p:plain
バイアスを追加したことで1回目の損失量は大きな値になっています。最初の値がでかすぎて30回目がどうなってるか見えにくいですね、拡大してみます。
f:id:sutokun:20190204020049p:plain
イテレーションをもう少し回すといいフィッティングになりそうですね。こんな感じでイテレーション回数が不足しているかどうかもこのグラフから確認できます。とは言え、入力数も増やしてノイズ幅も広げたので収束してもそこそこの損失量は残りそうですが。

まとめ

これで第二問が完了です。直線がプロットにフィットしていく様子をアニメ化しました。(1枚目の画像を出力し忘れてダイナミックな動きがなくなってしまいました…)
f:id:sutokun:20190204020826g:plain

前回と同じような問題をベースにバイアスを付加しました。バイアスを付加することで直線に自由度が生まれ、原点束縛から開放されました。まだパラメータも少なく、十分グラフ化できるレベルの次元ですが、ニューラルネットワークが複雑化すると重みやバイアスも行列化され、可視化できなくなります。そうなるとこれらパラメータのもともとの役割が何だったのか見えにくくなるので、簡単なモデルできちんと確認しておくのは良いことかと思います。

[ note ]
ここまでGIFアニメ用の画像出力もPYTHONISTA3でやってたんですが、処理落ちが激しくなってきました。これ以上iPadにムチを打つのも非効率なんで、次から画像出力はPC(Mac)でやろうと思います、ちょっとグラフの雰囲気が変わります。画像出力以外の計算はPYTHONISTA3でまだ問題なくできそうです。

全体コード
github.com

次の問題
③線形回帰問題(損失関数の可視化) - 社畜エンジニア発掘戦線

元の記事
PYTHONISTA3を使って機械学習(ディープラーニング) - 社畜エンジニア発掘戦線

Twitter
世界の社畜 (@sekai_syachiku) | Twitter