パーセプトロンの学習規則をPythonで実装

#この投稿について

線形分離可能なデータ群に対する識別境界の決定方法の1つであるパーセプトロンの学習規則をライブラリなどを使わずにPythonで実装してみました。
Python、機械学習ともに初心者なので、良くないポイントはご指摘お願いします。


「パーセプトロンの学習規則」と並んで比較される「Widrow-Hoff（ウィドロウ・ホフ）の学習規則」については[「Widrow-Hoff（ウィドロウ・ホフ）の学習規則をPythonで実装」](http://qiita.com/s-kiriki/items/6a90beede4c139558bcc)でまとめてあります。

##パーセプトロンの学習規則の理論

パーセプトロンの学習規則の概要や数式については以下のスライドにざっくりとまとめてあります（スライド途中からです）。

https://speakerdeck.com/kirikisinya/xin-zhe-renaiprmlmian-qiang-hui-at-ban-zang-men-number-2

##実装
## 1次元の場合

下図のような1次元上に存在し、2クラスのいずれかにに属する線形分離可能な学習データの分離境界線を求める。

![スクリーンショット 2014-12-04 16.11.00.png](https://qiita-image-store.s3.amazonaws.com/0/47881/30068c50-9a6e-d265-8ccc-b89141898f68.png)

実装のポイントとしては、

* 初期の重みベクトルは`w=(0.2,0.3)`とし、学習係数は`ρ=0.5`とした
* 分離境界の収束判定は行わず、重みベクトルの補正（学習）は十分な回数（100回）繰り返した（本当はよくないんだろうけど、まあ機械にいっぱい仕事させるのはいいかなーと思いまして。）

実際のコードは以下のようになりました。

```py
# coding: UTF-8
# 1次元のパーセプトロンの学習規則の実装例
import numpy as np
import matplotlib.pyplot as plt

def train(wvec, xvec, is_c1):
    low = 0.5#学習係数
    if (np.dot(wvec,xvec) > 0) != is_c1:
        if is_c1:
            wvec_new = wvec + low*xvec
        else:
            wvec_new = wvec - low*xvec
        return wvec_new
    else:
        return wvec

if __name__ == '__main__':
    
    data = np.array([[1.0, 1],[0.5, 1],[-0.2, 2],[-1.3, 2]])# データ群
    
    features = data[:,0].reshape(data[:,0].size,1)# 特徴ベクトル
    labels = data[:,1]# クラス（今回はc1=1,c2=2）
    wvec = np.array([0.2, 0.3])# 初期の重みベクトル
    is_c1s = (labels == 1)# c1かどうか booleanの配列
    
    xvecs = np.c_[np.ones(features.size), features]#xvec[0] = 1
    
    loop = 100
    for j in range(loop):
        for xvec, is_c1 in zip(xvecs, is_c1s):
            wvec = train(wvec, xvec, is_c1)
    
    print wvec
    print -(wvec[0]/wvec[1])
    
    # グラフ描写
    plt.axhline(y=0, c='gray')
    plt.scatter(features[is_c1s], np.zeros(features[is_c1s].size), c='red', marker="o")
    plt.scatter(features[~is_c1s], np.zeros(features[~is_c1s].size), c='yellow', marker="o")
    # 分離境界線
    plt.axvline(x=-(wvec[0]/wvec[1]), c='green')    
    plt.show()

```

学習後の重みベクトルは`w=(-0.3,0.75)`となる。
これを`wx=0`の式に代入すると識別関数は`x=0.4`となり、下図のように学習によってうまく線形分離できていることがわかる。

![figure_1.png](https://qiita-image-store.s3.amazonaws.com/0/47881/71f59fa8-6c92-e25c-036b-c7bd3c4385e9.png)


##2次元の場合

下図（イメージ）のように2次元上に存在し、2クラスのいずれかに属する線形分離可能な学習データの分離境界線を求める。

![スクリーンショット 2014-12-04 16.48.41.png](https://qiita-image-store.s3.amazonaws.com/0/47881/2fa0a1eb-0bbd-e057-5e74-c3807aeb9caa.png)

実装のポイントとしては、

* `np.random.rand`を用いて、線形分離可能な2クラスのデータ群を生成
* 初期の重みベクトルは`w=(2,-1,3)`とし、学習係数は`ρ=0.5`とした
* 1次元の場合と同じく、分離境界の収束判定は行わず、重みベクトルの補正（学習）は十分な回数（100回）繰り返した。

実際のコードは以下のようになりました。

```py
# coding: UTF-8
# 2次元のパーセプトロンの学習規則の実装例
import numpy as np
import matplotlib.pyplot as plt
import sys

def train(wvec, xvec, label):
    low = 0.5#学習係数
    if (np.dot(wvec,xvec) * label < 0):
        wvec_new = wvec + label*low*xvec
        return wvec_new
    else:
        return wvec

if __name__ == '__main__':
    
    train_num = 100#学習データ数
    
    #class1の学習データ
    x1_1=np.random.rand(train_num/2) * 5 + 1 #x成分
    x1_2=np.random.rand(int(train_num/2)) * 5 + 1 #y成分
    label_x1 = np.ones(train_num/2) #ラベル（すべて1）
    
    #class2の学習データ
    x2_1=(np.random.rand(train_num/2) * 5 + 1) * -1 #x成分
    x2_2=(np.random.rand(train_num/2) * 5 + 1) * -1 #y成分
    label_x2 = np.ones(train_num/2) * -1 #ラベル（すべて-1）
    
    x0=np.ones(train_num/2) # x0は常に1
    x1=np.c_[x0, x1_1, x1_2]
    x2=np.c_[x0, x2_1, x2_2]
    
    xvecs=np.r_[x1, x2]
    labels = np.r_[label_x1, label_x2]
    
    wvec = np.array([2,-1,3])#初期の重みベクトル 適当に決める
    
    loop = 100
    for j in range(loop):
        for xvec, label in zip(xvecs, labels):
            wvec = train(wvec, xvec, label)
    
    print wvec
    
    plt.scatter(x1[:,1], x1[:,2], c='red', marker="o")
    plt.scatter(x2[:,1], x2[:,2], c='yellow', marker="o")
    #分離境界線
    x_fig = np.array(range(-8,8))
    y_fig = -(wvec[1]/wvec[2])*x_fig - (wvec[0]/wvec[2])
    
    plt.plot(x_fig,y_fig)
    plt.show()
```

学習データをランダムで生成しているため、学習後の重みベクトルや識別関数は毎回異なるが、
実際に実行結果の一例としては下図のようになり、学習によって上手く線形分離できていることがわかる。

![figure_2.png](https://qiita-image-store.s3.amazonaws.com/0/47881/3355eefb-09d8-f9b3-e8e6-84c5c6bf2e36.png)