スポンサードリンク
前回までで、パーセプトロンの理論的な背景と、そのデータセットの用意のところまではすでに説明し終えたので、その続きから説明していきます。
>>>全体のソースコードがみたい方は前回の記事をみてください。
パーセプトロンのアルゴリズム(確率的勾配降下法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
def run_simulation(variance, data_graph, param_graph): train_set = prepare_dataset(variance) train_set1 = train_set[train_set['type']==1] train_set2 = train_set[train_set['type']==-1] ymin, ymax = train_set.y.min()-5, train_set.y.max()+10 xmin, xmax = train_set.x.min()-5, train_set.x.max()+10 data_graph.set_ylim([ymin-1, ymax+1]) data_graph.set_xlim([xmin-1, xmax+1]) data_graph.scatter(train_set1.x, train_set1.y, marker='o', label=None) data_graph.scatter(train_set2.x, train_set2.y, marker='x', label=None) # パラメータの初期値とbias項の設定 w0 = w1 = w2 = 0.0 bias = 0.5 * (train_set.x.mean() + train_set.y.mean()) # Iterationを30回実施 paramhist = DataFrame([[w0,w1,w2]], columns=['w0','w1','w2']) for i in range(30): for index, point in train_set.iterrows(): x, y, type = point.x, point.y, point.type if type * (w0*bias + w1*x + w2*y) <= 0: w0 += type * bias w1 += type * x w2 += type * y paramhist = paramhist.append( Series([w0,w1,w2], ['w0','w1','w2']), ignore_index=True) # 判定誤差の計算 err = 0 for index, point in train_set.iterrows(): x, y, type = point.x, point.y, point.type if type * (w0*bias + w1*x + w2*y) <= 0: err += 1 err_rate = err * 100 / len(train_set) # 結果の表示 linex = np.arange(xmin-5, xmax+5) liney = - linex * w1 / w2 - bias * w0 / w2 label = "ERR %.2f%%" % err_rate data_graph.plot(linex, liney, label=label, color='red') data_graph.legend(loc=1) paramhist.plot(ax=param_graph) param_graph.legend(loc=1) |
w0,w1,w2の初期値は全て0で始めていますね。
biasの部分は少し説明する必要があります。
スポンサードリンク
バイアス項とは何か?
バイアス項とはベクトル\(\phi_n\)の第一成分です。
ベクトル\(\phi_n\)の説明については以下の記事を読んでください
求める直線の方程式を
\begin{eqnarray}
f(x,y) = w_{0}+w_{1}x+w_{2}y
\end{eqnarray}
とおきましたが、
\begin{eqnarray}
f(x,y) = cw_{0}+w_{1}x+w_{2}y
\end{eqnarray}
と仮定することもできます。この場合、ベクトル\(\phi_{n}\)のバイアス項は「c(任意定数)」になります。
ベクトルwの更新には\(t_{n}\phi_{n}\)が用いられますので、w0は±cだけ更新されることになります。
なので、このcを適切に選択すれば、アルゴリズムの収束速度を改善できるということになります。
上のコードではこのcの値(biasの部分)をデータに含まれる全てのxn,ynの平均値を使っています。
1 |
bias = 0.5 * (train_set.x.mean() + train_set.y.mean()) |
なぜ平均値をとるのか
データの値(xn,yn)がとてつもなく大きい数(1000とか10000)で、平均的に10000とかの場合、勾配降下法のアルゴリズムより、w1,w2も10000程度の大きさで一気に増減します。
もしバイアス項が”1″ならw0の値は更新されても±1でしか変化しないことになります。
w0の変化がw1,w2の変化に比べて小さすぎるので、正しいwが得られるのに時間がかかります。
そこでバイアス項もデータの平均的な値にすることで、w0の変化率もw1,w2と同等にしてやろうという意味です。
Iteration
Iterationはベクトルwの更新回数です。今回は30回です。
ここはそんなに説明する必要はないと思います。
前々回の記事で説明した理論的な計算をおこなっている部分です。要は「更新」作業の一番の根幹の部分です。
判定誤差の計算
分散が大きい場合と小さい場合での比較として、エラー率を計算しています。
前にも述べましたが、分散が小さい場合は綺麗に直線で二つのデータ群を分割できますが、分散が大きいと二つのデータ群に重なる領域が生まれますので、直線で分割することができなくなります。
30回更新し直しても、直線で分割したエリアとは違うエリアにデータが含まれている場合、それをエラー率として表示するようにしています。
結果の表示、2種類の分散での実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 結果の表示 linex = np.arange(xmin-5, xmax+5) liney = - linex * w1 / w2 - bias * w0 / w2 label = "ERR %.2f%%" % err_rate data_graph.plot(linex, liney, label=label, color='red') data_graph.legend(loc=1) paramhist.plot(ax=param_graph) param_graph.legend(loc=1) # Main if __name__ == '__main__': fig = plt.figure() # 2種類の分散で実行 for c, variance in enumerate(Variances): subplots1 = fig.add_subplot(2,2,c+1) subplots2 = fig.add_subplot(2,2,c+2+1) run_simulation(variance, subplots1, subplots2) fig.show() |
ここはグラフを作るだけですので、特に問題ないと思います。
最後のfor文以降で、”2種類の分散”でこれまでの全ての計算を実行するようにしています。
忘れたかもしれませんがVariance(分散)は最初に配列で設定していますね。
スポンサードリンク
計算結果
上が計算結果です。
分散が小さい方(左上)は綺麗に直線で分割できます。その証拠にw0,w1,w2の更新も7回目くらいで収束しているのがわかります(左上)。
分散が大きい方(右上)は更新を30回やっても直線で綺麗に分割することはできず、エラー率6パーセントになっています。
実際、●のある領域に×のデータが入り混んでいます。エラーが出ているということは、30回更新してもw0,w1,w2は収束していないことが右下のグラフからわかります。
以上、パーセプトロンのアルゴリズムについて理論と実際にコードを用いてどのように計算されているのか述べてきました。
データセットの用意は乱数を用いているので、計算するたびに結果が異なります。更新回数や、データ数等変えて遊んでみてください。