感知機模型是二類分類的線性分類模型,其輸入爲實例的特徵向量,輸出爲實例的類別,取+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一定有如下的形式:
(5)
其中αi=niηαi=niη中nini代表對第i個樣本的學習次數,感知機對偶形式的完整形式即爲(6)式:
- 初始化α=0α=0,b=0b=0.
- 任意選取(xi,yi)
- 如果yi(∑j=1Nαjyjxj⋅xi+b)≤0yi(∑j=1Nαjyjxj⋅xi+b)≤0,即發生誤判,則對αi,bαi,b進行更新: αi←αi+ηbi←bi+ηyiαi←αi+ηbi←bi+ηyi
- 重複2直到所有點都被正確分類
簡而言之,感知機的對偶形式就是把對w,bw,b的學習變成了對α,bα,b的學習,原始形式中,ww在每一輪迭代錯分時都需要更新,而採用對偶形式時,對於某一點(xi,yi)發生錯分時,我們只需要更新其對應的αiαi即可,最後按照(5)式即可一次計算出ww.
同時我們上述步驟3中的yi(∑j=1Nαjyjxj⋅xi+b)≤0yi(∑j=1Nαjyjxj⋅xi+b)≤0可以看出,xj⋅xixj⋅xi僅以內積的形式出現,因此我們可以是先計算出x的gram矩陣存儲起來,這樣正式訓練時只需要查表就可以得到xj⋅xixj⋅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)