機器學習之感知機

感知機模型是二類分類的線性分類模型,其輸入爲實例的特徵向量,輸出爲實例的類別,取+1和-1二值。感知機對應於輸入空間(特徵空間)中將實例劃分爲正負兩類的分離超平面,屬於判別模型。感知機學習旨在求出將訓練數據進行線性劃分的分離超平面,爲此,導入誤分類的損失函數,利用梯度下降法對損失函數進行極小化,求得感知機模型。感知機學習算法,具有簡單而易於實現的優點,分爲原始形式與對偶形式。感知機預測是用學習得到的感知機模型對新的輸入實例進行分類。

 1.首先,我們假定線性方程 wx+b=0 是一個超平面,令 g(x)=wx+b,也就是超平面上的點x都滿足g(x)=0。對於超平面的一側的點滿足:g(x)>0; 同樣的,對於超平面另一側的點滿足:g(x)<0.

       結論一:對於不在超平面上的點x,它到超平面的距離:

                                                                    

      證明:如下圖所示,O表示原點,Xp表示超平面上的一點,X是超平面外的一點,w是超平面的法向量。

                                                                                            

         

           

         

        等式1說明:向量的基本運算法則,OX=OXp+XpX. 因爲w是法向量,所以w/||w||是垂直於超平面的單位向量。

       等式2說明:將等式1帶入g(x)=wx+b;由於Xp在超平面上,所以g(Xp)=w^T*Xp+w0 = 0

       以上得證。

  

       2.下面區分一下易混淆的兩個概念,梯度下降和隨機梯度下降:

        梯度下降:一次將誤分類集合中所有誤分類點的梯度下降;

        隨機梯度下降:隨機選取一個誤分類點使其梯度下降。


       3.對於誤分類的數據來說,當w*xi + b>0時,yi = -1,也就是,明明是正例,預測成負例。因此,誤分類點到超平面的距離爲:

                                                                                                                                                                   

        因此所有誤分類點到超平面的總距離爲:

                                                      

       忽略1/||w||,我們就可以得到感知機學習的損失函數。

       損失函數:

                                              

       這個損失函數就是感知機學習的經驗風險函數。

感知機學習算法


從上面可以,感知機學習問題轉化爲求解損失函數最優化問題,最優化的方法是隨機梯度下降法。感知機學習算法有兩種形式:原始形式和對偶形式。在訓練數據線性可分的條件下,感知機學習算法是收斂的。

原始形式

在給定訓練數據集T={(x1,y1), {x2,y2},…..,{xN,yN}},我們可以通過求參數w和b使得: 

 
其中M是誤分類點。

感知機學習算法是誤分類數據驅動的,採用隨機梯度下降法,即隨機選取一個超平面w0和b0,使用梯度下降法對損失函數進行極小化。極小化不是一次使得所有M集合誤分類點梯度下降,而是一次隨機選取一個點使其梯度下降。 
假設M集合是固定,那麼損失函數的梯度爲: 
這裏寫圖片描述 
隨機一個選取一個誤分類點(xi, yi)對w和b進行更新。

這裏寫圖片描述 
η(0<=η<=1)是步長,統計學習中稱爲學習率。通過不斷迭代,損失函數不斷減小,直到爲0。 
具體步驟: 
1、 隨機選取w0和b0 
2、 在訓練數據中選取(xi,yi) 
3、 如果yi(w*xi+b) <= 0 
這裏寫圖片描述 
4、 轉2,直到訓練數據中,沒有誤分類點。 
感知機學習算法直觀的解釋如下: 

當一個實例點被誤分類時,即位於分離超平面錯誤的一邊,則調整w和b使得超平面想誤分類點一側移動,減少誤分類點到超平面的距離。直到超平面越過該誤分類點,被正確分類。

以上即爲感知機算法的原始形式,理解起來比較簡單,也較容易實現。下面給出其的Python實現

'''感知機的原始形式'''
import numpy as np
import matplotlib.pyplot as plt
import random

def sign(v):
    if v >= 0:
        return 1
    else:
        return -1

def train(train_num,train_datas,lr):
    w = [0,0]
    b = 0
    for i in range(train_num):
        x = random.choice(train_datas)
        x1,x2,y = x
        if (y*sign(w[0]*x1 + w[1]*x2 + b)) < 0:
            w[0] += lr*x1*y
            w[1] += lr*x2*y
            b += lr*y
    return w,b

def plot_points(train_datas,w,b):
    plt.figure()
    x1 = np.linspace(0,8,100)
    x2 = (-b - w[0]*x1)/w[1]
    plt.plot(x1,x2,color='r',label = 'y_label')
    for i in range(len(train_datas)):
        if (train_datas[i][-1] == 1):
            plt.scatter(train_datas[i][0],train_datas[i][1],s=50)
        else:
            plt.scatter(train_datas[i][0],train_datas[i][1],marker='x',s=50)
    plt.show()

if __name__=='__main__':
    train_data1 = [[1, 3, 1], [2, 2, 1], [3, 8, 1], [2, 6, 1]]  # 正樣本
    train_data2 = [[2, 1, -1], [4, 1, -1], [6, 2, -1], [7, 3, -1]]  # 負樣本
    train_datas = train_data1 + train_data2  # 樣本集
    w,b = train(train_num=50,train_datas=train_datas,lr=0.01)
    plot_points(train_datas,w,b)

感知機學習算法的對偶形式

相比於原始形式,其對偶形式在理解上沒有前者那麼直觀,網上關於其實現代碼的例子也比較少。 
在《統計學習方法》一書中,關於對偶形式有如下的描述:

對偶形式的基本想法是,將w和b表示爲實例xixi 和標記 yiyi 的線性組合的形式,通過求解其係數而求得w和b.

假設w0=0,b=0,那麼從(4)式可以看出,當所有的點均不發生誤判時,最後的w,b一定有如下的形式: 

w=i=1Nniηyixi=i=1Nαiyixib=i=1Nniηyi=i=1Nαiyiw=∑i=1Nniηyixi=∑i=1Nαiyixib=∑i=1Nniηyi=∑i=1Nαiyi

(5) 
其中αi=niηαi=niηnini代表對第i個樣本的學習次數,感知機對偶形式的完整形式即爲(6)式: 
f(x)=sign(j=1Nαjyjxjx+b)f(x)=sign(∑j=1Nαjyjxj⋅x+b)
(6)

  1. 初始化α=0α=0,b=0b=0.
  2. 任意選取(xi,yi)
  3. 如果yi(j=1Nαjyjxjxi+b)0yi(∑j=1Nαjyjxj⋅xi+b)≤0,即發生誤判,則對αi,bαi,b進行更新: 
    αiαi+ηbibi+ηyiαi←αi+ηbi←bi+ηyi
  4. 重複2直到所有點都被正確分類

簡而言之,感知機的對偶形式就是把對w,bw,b的學習變成了對α,bα,b的學習,原始形式中,ww在每一輪迭代錯分時都需要更新,而採用對偶形式時,對於某一點(xi,yi)發生錯分時,我們只需要更新其對應的αiαi即可,最後按照(5)式即可一次計算出ww
同時我們上述步驟3中的yi(j=1Nαjyjxjxi+b)0yi(∑j=1Nαjyjxj⋅xi+b)≤0可以看出,xjxixj⋅xi僅以內積的形式出現,因此我們可以是先計算出x的gram矩陣存儲起來,這樣正式訓練時只需要查表就可以得到xjxixj⋅xi的值,這樣做可以方便程序的優化,提高運算的速度。 
原始形式和對偶形式對參數b的處理是相同的。 
以下是感知機對偶形式的Python實現

'''感知機的對偶形式'''
import numpy as np
import matplotlib.pyplot as plt
import random

def sign(v):
    if v >= 0:
        return 1
    else:
        return -1

def train(train_num,train_datas,lr):
    w=0.0
    b=0
    datas_len = len(train_datas)
    alpha = [0 for i in range(datas_len)]
    train_array = np.array(train_datas)
    gram = np.matmul(train_array[:,0:-1] , train_array[:,0:-1].T)
    for idx in range(train_num):
        tmp=0
        i = random.randint(0,datas_len-1)
        yi=train_array[i,-1]
        for j in range(datas_len):
            tmp+=alpha[j]*train_array[j,-1]*gram[i,j]
        tmp+=b
        if(yi*tmp<=0):
            alpha[i]=alpha[i]+lr
            b=b+lr*yi
    for i in range(datas_len):
        w+=alpha[i]*train_array[i,0:-1]*train_array[i,-1]
    return w,b,alpha,gram

def plot_points(train_datas,w,b):
    plt.figure()
    x1 = np.linspace(0,8,100)
    x2 = (-b - w[0]*x1)/w[1]
    plt.plot(x1,x2,color='r',label = 'y_label')
    for i in range(len(train_datas)):
        if (train_datas[i][-1] == 1):
            plt.scatter(train_datas[i][0],train_datas[i][1],s=50)
        else:
            plt.scatter(train_datas[i][0],train_datas[i][1],marker='x',s=50)
    plt.show()
    
if __name__=='__main__':
    train_data1 = [[1, 3, 1], [2, 2, 1], [3, 8, 1], [2, 6, 1]]  # 正樣本
    train_data2 = [[2, 1, -1], [4, 1, -1], [6, 2, -1], [7, 3, -1]]  # 負樣本
    train_datas = train_data1 + train_data2  # 樣本集
    w,b ,alpha,gram = train(train_num=50,train_datas=train_datas,lr=0.01)
    plot_points(train_datas,w,b)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章