這個演算法真是不太好懂,看了好幾遍終於有點入門的感覺,就趕緊記錄下這點感覺。我從複習線性分類開始,然後複習點積的含義,再引出核方法。
線性分類是最容易理解的分類方法,兩組資料A和B,分別求出A和B的平均值,比如M和N,當判斷新資料X是屬於A還是屬於B呢,就看新資料X到M近還是N近,X屬於距離近的那個。為了實現這個演算法,我們需要計算出各分類的均值點:
def lineartrain(rows): averages={} counts={} for row in rows: # Get the class of this point cl=row.match averages.setdefault(cl,[0.0]*(len(row.data))) counts.setdefault(cl,0) # Add this point to the averages for i in range(len(row.data)): averages[cl][i]+=float(row.data[i]) # Keep track of how many points in each class counts[cl]+=1 # Divide sums by counts to get the averages for cl,avg in averages.items(): for i in range(len(avg)): avg[i]/=counts[cl] return averages
下來就可以給分類了,計算要分類的座標點到各個分類的均值點的距離,然後從中選擇距離最近者。
這裡我們採用另一種方法,使用向量和點積。向量具有大小和方向,我們通常將其表示成平面的一個箭頭。所謂點積,針對兩個向量,將第一個向量中的每個值雨第二個向量中的對應值相乘,然後再將所得的每個乘積相加,最後得到一個總的結果。
def dotproduct(v1,v2): return sum([v1[i]*v2[i] for i in range(len(v1))])
點積也可以利用兩個向量的長度乘積,再乘以兩者夾角的餘弦求得。最重要的是,如果夾角的度數大於90度,夾角餘弦值為負,這樣點積結果也就是負了。
M0和M1分別是分類0和分類1的均值點,C為M0和M1的中心點,還有兩個點即將分類的點X1和點X2。很明顯X1離M0近一點,應該劃歸為分類0,X2離M1近一些,應該劃歸為分類1。同時也注意到,向量X1--->C的向量M0--->M1的夾角小於90度,因此X1--->C和M0--->M1的點積結果為正數;向量X2--->C的向量M0--->M1的夾角大於90度,因此X2--->C和M0--->M1的點積結果為負數。由此,只需通過觀察點積結果的正負,就可以判斷出新座標的類屬。
點C為M0和M1的均值點,亦即(M0+M1)/2,所以尋找分類的公式如下:
class=sign((X-(M0+M1)/2) . (M0-M1))
相乘後的結果為:
class=sign(X.M0 - X.M1 + (M1.M1 - M0.M0)/2)
我們可以利用上面的公式來確定分類了:
def dpclassify(point,avgs): b=(dotproduct(avgs[1],avgs[1])-dotproduct(avgs[0],avgs[0]))/2 y=dotproduct(point,avgs[0])-dotproduct(point,avgs[1])+b if y>0: return 0 else: return 1
到這裡,一切都很正常。正常的事情往往又不正常,目前正常的局面是,可以用到兩個點的距離,也就是一個超平面來分割開分類0和分類1。不正常的局面是,面對著這種情況好像又鞭長莫及了,分類的均值點重合,儘管大家很清楚,任何位於圈圈內的都是X,圈圈外的都是O,但是線性分類器卻無法識別這兩個分類。
可是我們又發現,如果對每個點的x和y求平方,會是什麼樣呢?這樣所有X都位移到了圖上的角落處,所有的O位於角落以外的地區,這樣我們就可以用線性分類器來處理了。
上面的兩站圖說明:通過對座標點的變換,構造出一個只用一條直線進行劃分新資料是有可能的。有時我們需要升級維度,比如將一個x和y座標的資料集,變換成一個由a,b,c三個座標構成的新資料集,其中a=x*x,b=x*y,c=y*y。
儘管我們可以把資料依照上面的方式變換到新的座標系中,但通常我們不會這麼去做,下面來聊聊核技法。核技法的思路是用一個新的函數取代上面的點積函數,而不是將資料變換奧新座標系中。一種備受推崇的方法被成為徑向基函數,這個函數於點積類似,接受兩個向量作為輸入參數,並返回一個標量,與點積不同的是,徑向基函數是非線性,因而它能夠將資料對應到更為複雜的空間中。
def rbf(v1,v2,gamma=10): dv=[v1[i]-v2[i] for i in range(len(v1))] l=veclength(dv) return math.e**(-gamma*l)
現在我們需要一個新函數,來計算座標點在變換後的空間中與均值點的距離。遺憾的是,目前的均值是在原始空間中計算得到的,因為此處無法直接使用他們。所幸的是,先對一組向量求均值,然後再計算均值與向量A的點積結果,與先對向量A與該組向量中的每一個向量求點積,然後再計算均值,在效果上是等價的。因此,我們不再嘗試對分類的兩個座標點求點積,也不在計算某個分類的均值點,取而代之的是,計算出某個座標點與分類中其餘每個座標點之間的點積或徑向基函數的結果,然後在對他們求均值。再來看一遍這個函數 class=sign(X.M0
- X.M1 + (M1.M1 - M0.M0)/2)。我們不直接去求M0和M1了,但心裡必須清楚M0,M1是中心點,是徑向基函數結果的平均值。
def nlclassify(point,rows,offset,gamma=10): sum0=0.0 sum1=0.0 count0=0 count1=0 for row in rows: if row.match==0: sum0+=rbf(point,row.data,gamma) count0+=1 else: sum1+=rbf(point,row.data,gamma) count1+=1 y=(1.0/count0)*sum0-(1.0/count1)*sum1+offset if y>0: return 0 else: return 1def getoffset(rows,gamma=10): l0=[] l1=[] for row in rows: if row.match==0: l0.append(row.data) else: l1.append(row.data) sum0=sum(sum([rbf(v1,v2,gamma) for v1 in l0]) for v2 in l0) sum1=sum(sum([rbf(v1,v2,gamma) for v1 in l1]) for v2 in l1) return ((1.0/(len(l1)**2))*sum1-(1.0/(len(l0)**2))*sum0)/2
注意函數getoffset函數就是求(M1.M1 - M0.M1)/2,nlclassify函數就是具體的分類了。