【ML學習筆記】18:原始的Perceptron(感知機)
感知機的決策面
感知機用來獲取樣本特徵空間中的一個超平面,以對樣本進行分類,屬於線性分類器。
這樣的分類問題比較經典,如某一個參加非誠入擾的女士(分類器)評判自己會不會給非誠勿擾的各個男嘉賓(樣本)留燈(1或者-1),男嘉賓作為樣本,有多個特徵,如身高、月收入、長相得分等。
女嘉賓的內心對這些特徵會有一個權重,她想的是:把這些特徵乘以權重(當然對某些特徵的權重是負的,比如每週抽菸的數目)加起來,得到總得分:
如果得分超過某個數字,那麼就留燈(判為1);如果得分小於這個數字,那麼就滅燈(判為-1);如果得分恰好等於這個數字,那麼女嘉賓十分糾結(拒判):
把這個得分M移到左邊,取:
即為超平面的偏置 bias。
則要判定的是:
取x0=1,則判別函式可以表示成兩個向量的內積:
這兩個列向量都新增過第0號項,分別稱為增廣權重向量和增廣樣本特徵向量。
在特徵空間中,權重向量內積樣本向量則是用過原點的超平面作判決,兩個向量增廣以後,因為加了常數項,所以是用任意位置的超平面作判決:
對於投入的樣本特徵向量y,用感知機算出一個數,如果大於0就判為1,小於0就判為-1,在0處是跳變的(不同於sigmoid函式):
感知機的訓練
感知機的訓練目標就是得到增廣的權向量,對於訓練集中的每個樣本,需要判定它是否符合當前增廣權重向量的分類,如果不符合,就需要對增廣權重向量進行調整,直到
這是最原始的感知機,因為是”直到”,所以要求樣本是線性可分的,也就是對於所有的樣本,能夠找到這樣的增廣權向量,使所有樣本都被正確分類。
為了方便”判定它是否符合當前增廣權重向量的分類”,可以讓本來需要判別函式<0的那些樣本增廣樣本向量取反,取反也就是乘以-1,這樣只需要判定:
是否成立即可。這樣定義的y’是規範化增廣樣本特徵向量。
對於每一個樣本y’,滿足上式的增廣權重向量只需要和它的夾角<90度,就可以滿足內積>0,這樣所有的符合條件的增廣權重向量完全等同於垂直於樣本y’向量的超平面正側的所有向量
對於訓練集中的所有樣本,每個樣本都能找到這樣的超平面,它們的正側的交集即成為一個比較窄的區域,稱為解區:
在解區內的向量即是訓練演算法要尋找的解向量,都是能把所有訓練集樣本正確分類的增廣的權向量。
當然,靠近解區邊緣的向量可能不太具好的泛化能力,可以使用餘量 b來讓手動讓解區更狹窄,從而找到落在原來解區中央的解向量:
引入餘量時的判別條件:
這次的程式碼裡不使用餘量。
要尋找解區裡的解向量作為增廣權重向量,可以先設定一個向量。
使用這個任定的向量做判別時,如果樣本yk被錯誤分類,即:
顯然這個值越小,錯誤的程度也就越強。因此感知機準則函式可以取負加和:
作為用當前這個增廣權重向量作判別的損失。
所以目標就是求使得這個損失最小(線上性可分的情況下必須是0)的增廣權重向量:
梯度下降法求損失函式最小的解向量
從任給定的一個增廣權重向量開始,沿著損失函式梯度方向的反方向,按照一個步長來求極小值(如果它就是最小值):
這裡的步長yita也稱學習率,如果學習率太大,會導致不停的波動,總是沒法落入解區;如果學習率太小,那麼可能需要較多的迭代次數。
在這次的程式碼裡,沒有改變學習率,實際上學習率應該隨著迭代的推進變小,這樣才能較快接近解區然後較緩慢地進入解區。
梯度求出來是:
所以迭代公式變為:
相當於把分錯的樣本增廣特徵向量以學習率為係數加到增廣權重向量上。
注意
①在實際使用時,如果把每次迭代的所有錯分樣本加起來一起修正增廣權重向量,效率不高。應該在迭代中每次發現錯分樣本即刻修正增廣權重向量。
②樣本不是線性可分時,可以讓學習率在梯度下降過程中逐步減少,強制收斂,獲得一個可用的解。
程式碼實現
使用的線性可分的樣本:
#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
#獲取資料
def getData():
fr1=open(r'myboy.txt')
fr0=open(r'mygirl.txt')
arrayOLines1=fr1.readlines() #讀取檔案
arrayOLines0=fr0.readlines()
#特徵矩陣
dataMat1=[]
dataMat0=[]
#男同學
for line in arrayOLines1:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字元分割成列表
#string變float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特徵矩陣
dataMat1.append(listFromLine)
#女同學
for line in arrayOLines0:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字元分割成列表
#string變float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特徵矩陣
dataMat0.append(listFromLine)
return dataMat1,dataMat0
#傳入迭代次數和訓練集,梯度上升訓練感知機
def TraPCPT(xMat,yLab):
omg=mat([1,1,1]) #初始w0(偏置),w1=,w2
n=0 #統計迴圈次數
yita=0.05 #學習率
lnth=len(xMat) #總的樣本數
xMat=mat(xMat) #二維列表矩陣化
yLab=mat(yLab) #一維列表向量化
#梯度上升直到進入解區
while(True):
n+=1
ok=True #用ok來判定收斂是否結束
#smy=mat([0.0,0.0,0.0]) #把錯分的規範化樣本yi加起來
#N-=1
#每次求得真的realyLab=f(x)的列向量
realyLab=xMat*omg.T
#對每個樣本求出來的f(x)值
for i in range(lnth):
#若點此時的分類位置不對
if yLab[0,i]*realyLab[i,0]<=0:
ok=False
'''
#規範化後加到smy向量裡
smy=smy+yLab[0,i]*xMat[i]
'''
#在學習率yita下修正權值向量
omg=omg+yita*yLab[0,i]*xMat[i]
if ok==True: #如果已經收斂
break
print '迴圈了',n,'次'
return omg #返回權值向量
#演示
def Go(i,j):
dataMat1,dataMat0=getData()
#i,j號特徵在男女類各自的列表
Ftr1i=[line[i] for line in dataMat1]
Ftr1j=[line[j] for line in dataMat1]
Ftr0i=[line[i] for line in dataMat0]
Ftr0j=[line[j] for line in dataMat0]
#繪製男女離散的點
plt.scatter(Ftr1i,Ftr1j,c=u'b',label='male')
plt.scatter(Ftr0i,Ftr0j,c=u'g',label='female')
#xMat每行存[x0=1,x1=i特徵值,x2=j特徵值]
#yLab[k]存xMat中第k行對應的1(男),或-1(女)
'''
xMat=[]
yLab=[]
#交叉放入的方式(扔掉了不一樣多的那塊資料)
lnth=min(len(Ftr1i),len(Ftr0i)) #取比較小的
for k in range(lnth):
xMat.append([1,Ftr1i[k],Ftr1j[k]])
yLab.append(1)
xMat.append([1,Ftr0i[k],Ftr0j[k]])
yLab.append(-1)
'''
#先放男的
xMat=[[1,Ftr1i[k],Ftr1j[k]] for k in range(len(Ftr1i))]
yLab=[1 for k in range(len(Ftr1i))]
#再臨時放女的
xMat0=[[1,Ftr0i[k],Ftr0j[k]] for k in range(len(Ftr0i))]
yLab0=[-1 for k in range(len(Ftr0i))]
#把女的擴充套件進來
xMat.extend(xMat0)
yLab.extend(yLab0)
'''
xMat=[[1,Ftr0i[k],Ftr0j[k]] for k in range(len(Ftr0i))]
yLab=[-1 for k in range(len(Ftr0i))]
xMat1=[[1,Ftr1i[k],Ftr1j[k]] for k in range(len(Ftr1i))]
yLab1=[1 for k in range(len(Ftr1i))]
xMat.extend(xMat1)
yLab.extend(yLab1)
'''
#訓練以獲得引數
omg=TraPCPT(xMat,yLab)
#omg=mat([-500,3,8])
print 'new omg',omg
#繪製曲線用的橫座標(i特徵)
if i==0:
lft=140
rgt=210
elif i==1:
lft=40
rgt=90
else:
lft=30
rgt=47
x1=linspace(lft,rgt,10) #反正是直線,不用取樣太多
#繪製得到的直線
o1=float(omg[0,1])
o0=float(omg[0,0])
o2=float(omg[0,2])
plt.plot(x1,-(o1*x1+o0)/o2,c=u'r')
plt.show() #顯示
測試
這條決策線是梯度下降剛剛進入解區時取得的,有些過於貼近某些樣本點,如果用SVM,得到的決策線泛化能力或許好得多。