#はじめに
パーセプトロンとロジスティクス回帰を理解するために、tensorflowやkeras,pytrochなどのskit-learnのライブラリを用いずに実装した。pythonのコード付きで実装している。コードは最後に記した。データは100点で、線形分離可能なデータセットを用いた。データセットの描画もコードに記している。
パーセプトロン
学習率と初期値を表1のように設定した。初期値は左からθ0,θ1,θ2と表している。なので一番左の値がバイアス(切片)。真ん中の値が傾き。θ3は値が大きいとその値で割られるので傾きに影響してくると考えられます。そしてこの図1紫色の直線が初めの初期値で定めた識別線になる。青がだんだん学習をしていく過程の直線になる。そして赤が学習後の識別線だ。この赤の直線は2つのクラスをどれも誤りがなく分けていると言える。右側に緑のクラスがあり左側に赤のクラスがある。
ロジスティクス回帰
ロジスティクスもパーセプトロンと同様に図2に結果を示す。紫色の直線が初めの初期値で定めた直線になる。青がだんだん学習をしていく過程の直線になる。そして赤が学習後の識別線だ。この赤の直線は2つのクラスを識別線になる。ここでよく見ていただきたのが、パーセプトロンにせよ、ロジスティクスにせよどちらも識別の誤りがなくなったところで学習が止まっていることである。これは未知のデータを識別するときに曖昧なデータだと精度が落ちる可能性がある。本来であればもう少し緑と赤の真ん中が識別線になるほうが尤もらしいと言える。
初期値の検討
初期値はパーセプトロンもロジスティクス回帰も紫色の直線で示している。パーセプトロンも説明はしたが初期値のθは以下の式のような直線の式を表している。
f(x_i,y_i) = \theta_0 + \theta_1 x_i + \theta_2 y_2 = X^T_i \theta
初期値の値はそれぞれこのθ0,θ1,θ2の順になっている。ここで、θ0の切片の値を変更してみる。
θ0の値の変更(切片)
切片の値を5にしたので切片の位置がずれているのがわかる。しかし切片の値を5にしたのにグラフでは-5の位置に切片が来てしまった。また切片の位置が変わっても赤い識別線がうまく学習することができている。ロジスティクス回帰でもパーセプトロンでもうまく学習できていることがわかる。
θ1の検討(傾き)
傾きのあたいを変更したため、初めの初期値の傾きが変わった。0.5にしたため少し傾きがない状態での紫色の線から学習が始まる。パーセプトロンはゆっくり識別線に近づいていいっているのに対して、ロジスティクス回帰の方では初めから一気に近づいて言っている。これはロジスティクスはこの場合バッチ学習を採用して学習しているため、一気に解に近づいているのではないかと考える。
θ2の検討
θ2 は傾きの値の分母に付け加えるだけなのでこの値が大きければ初期値の傾きの値が小さく成る状態で始まり、小さくしていき小数などになれば、小数で割るので傾きは大きくなる状態で初期値の傾きとなる。
学習率の検討
学習率0.5
学習率を0.5に変更した。学習率はどのくらい更新を行うか。本来であれば少しずつ更新を行うものとして採用される。そして更新回数はできるだけ少ないほうがよりよいモデルだが、学習率を大きくしすぎてしまうと更新が大きくなり、更新がうまくストップしないと言う問題点がある。なので適切な学習率の設定が必要になる。今回では学習率を大きくしたため更新回数がとても少なく学習途中の青の直線の本数が少ないことが図6よりわかる。次は学習率を極端に小さくしてみる。
学習率0.01
学習率を0.01に変更してみる。
図7の(a)では学習率を0.01と更新する幅をとても小さくしたので学習途中の青の直線が今まで以上にたくさんでたことがわかる。それに対して(b)の方では青の直線はあまり出ていない。これはバッチ学習を行っているため一回での更新量がとてもおおいためと考えた。なので図7の(b)の学習率を大幅に下げ0.00001で行った結果を以下に図8として示す。
上の図8からもわかるようにバッチ学習のため一回の更新量がとてもおおいので学習率を極端に小さくするとこれだけ青い線が引かれることがわかる。
適切な学習率についてはできるだけ更新回数が少なくするとよいと言える。それの1つの方法として初めは大きめの学習率で行いだんだんエラーが少なくなってきたら、学習率を小さくする手法などが一般的によいとされている。そのような学習率を考慮した最適化としてRMSPropなどが挙げられる。しかし今回の問ではそこまで考えなくてもよい更新ができている。
学習法の検討
ロジスティクス回帰はこれまで学習データすべてを更新に用いるバッチ学習を適用していた。ここでは他にもミニバッチ学習やオンライン学習について検討したい。
ミニバッチ学習
学習データをいくつかのミニバッチに分割しそれぞれで学習することを指す。
図9の結果は学習率が0.01なのにもかからわず、青の直線がたくさん描画され少しずつ更新してることがわかる。そして今回のミニバッチ区切りは4つに分割して分けることで学習を行った。
minibatch = int(data.shape[1]/4)
while True:
yy =-(theta[0]+ xx * theta[1])/theta[2]
i+=1
plt.plot(xx,yy,color =’blue’)
diff = theta.copy()
for k in range(0,data.shape[1],minibatch):
theta = theta + eta * np.dot(Phi[:,k:k+1], label[k]-sigmoid(np.dot(Phi[:,k:k +1].T, theta)))# thetaのバッチ学習
theta = theta /np.linalg.norm(theta,ord=2)
if np.linalg.norm(diff-theta)< 10**(-4):#差をallcloseで比較
break
オンライン学習
学習データ1つ1つ毎回更新を行う。
このデータセットではあまり恩恵がもたらせないかもしれないが、更新回数はバッチ学習のときと比べて1増えた更新回数となった。やはりバッチ学習のときよりも毎回更新を行うので更新回数は多くなる。しかし今回差があまり出なかったのは学習率が小さすぎるためだと考えられる。変更部のみのソースコードを以下に示す。
1 while True:
2 yy =-(theta[0]+ xx * theta[1])/theta[2]
3 i+=1
4 plt.plot(xx,yy,color =’blue’)
5
6 diff = theta.copy()
7 for k in range(data.shape[1]):
8 theta = theta + eta * np.dot(Phi[:,k:k+1], label[k]-sigmoid(np.dot(Phi[:,k:k +1].T, theta)))# thetaのバッチ学習
9 theta = theta /np.linalg.norm(theta,ord=2)
10 if np.linalg.norm(diff-theta)< 10**(-4):#差をallcloseで比較
11 break
Fisherの線形判別分析法
図11わかるように識別線が真ん中のような場所に引かれている。これまでのパーセプトロンやロジスティクス回帰では学習終了する条件が誤認識のものがなくなれば識別が終了するため、識別の境界のどちらかのデータに識別線がよっていた。しかしFisherはそれぞれのクラスのデーたの平均がより離れた位置を識別線にするため真ん中に識別線が引かれる。
# パーセプトロン
import numpy as np
import matplotlib.pyplot as plt
data_num = 400
np.random.seed(1)
class0_data = [9,2] + [3.1, 2.3] * np.random.randn(data_num//2,2)
class1_data = [-10,-4] + [2.7, 3.3] * np.random.randn(data_num//2,2)
label = np.array([[ k//200 for k in range(data_num) ]] ).T
label[label == 0] = -1
data = np.vstack( ( class0_data, class1_data ) ).T
#t = np.concatenate((np.ones(200), -np.ones(200))) #教師データtを作成
plt.scatter( class0_data[:,0], class0_data[:,1], marker=',' )
plt.scatter( class1_data[:,0], class1_data[:,1], marker='^' )
Phi = np.vstack(( np.ones(data.shape[1]),data))
theta = np.array([[0.0,1.0,5.0]]).T
xx = np.linspace(-20.0,20.0,201)
yy = -(theta[0] + xx * theta[1])/theta[2]
plt.plot(xx,yy,color = 'purple')
epsilon = 0.01
while np.count_nonzero(np.dot(Phi.T,theta) * label <0)>0: #1誤分類されたデータがあるとき
print(np.count_nonzero(np.dot(Phi.T,theta) * label <0))
for k in range(data.shape[1]):
#print(k)
#print(np.count_nonzero(np.dot(Phi.T,theta) * label[k] <0))
if np.dot(Phi[:,k],theta) *label[k] < 0.0:
theta = theta + epsilon * Phi[:,k:k+1]*label[k]
yy= -(theta[0] + xx * theta[1])/theta[2]
plt.plot(xx,yy,color = 'blue')
print(theta)
plt.scatter( class0_data[:,0], class0_data[:,1], marker=',' )
plt.scatter( class1_data[:,0], class1_data[:,1], marker='^' )
xx = np.linspace(-10,10,21)
yy=-(theta[0] + xx * theta[1])/theta[2]
plt.ylim([-10.0,10.0])
plt.plot(xx,yy,color='red')
plt.show()
#ロジスティクス回帰
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1.0/(1.0 + np.exp(-x))
data_num = 400
np.random.seed(1)
class0_data = [9,2] + [3.1, 2.3] * np.random.randn(data_num//2,2)
class1_data = [-10,-4] + [2.7, 3.3] * np.random.randn(data_num//2,2)
label = np.array([ k//200 for k in range(data_num) ] )
data = np.vstack( ( class0_data, class1_data ) )
t = np.concatenate((np.ones(200), -np.ones(200))) #教師データtを作成
t = t.reshape(-1,1)
plt.scatter( class0_data[:,0], class0_data[:,1], marker=',' )
plt.scatter( class1_data[:,0], class1_data[:,1], marker='^' )
Phi = np.vstack(( np.ones((data.shape[0])),
data.T
))
theta = np.array([[-1.0,-1.0,-1.0]]).T
eta = 0.2
xx = np.linspace(-20.0,20.0,201)
i=0
while True:
print(i)
yy = -(theta[0] + xx * theta[1])/theta[2]
plt.plot(xx,yy,color = 'purple')
diff = theta.copy()
theta = theta + eta * np.dot(Phi, t - sigmoid(np.dot(Phi.T, theta))) # thetaのバッチ学習
theta = theta /np.linalg.norm(theta,ord=2)
i +=1
print(diff-theta)
if (diff-theta).all() < 10**(-4):
break
plt.plot(xx,yy,color = 'green')
plt.scatter( class0_data[:,0], class0_data[:,1], marker=',' )
plt.scatter( class1_data[:,0], class1_data[:,1], marker='^' )
plt.show()