一、 馬氏距離
我們熟悉的歐氏距離雖然很有用,但也有明顯的缺點。它將樣品的不同屬性(即各指標或各變量)之間的差別等同看待,這一點有時不能滿足實際要求。例如,在教育研究中,經常遇到對人的分析和判別,個體的不同屬性對於區分個體有着不同的重要性。因此,有時需要采用不同的距離函數。
如果用dij表示第i個樣品和第j個樣品之間的距離,那么對一切i,j和k,dij應該滿足如下四個條件:
①當且僅當i=j時,dij=0
②dij>0
③dij=dji(對稱性)
④dij≤dik+dkj(三角不等式)
顯然,歐氏距離滿足以上四個條件。滿足以上條件的函數有多種,本節將要用到的馬氏距離也是其中的一種。
第i個樣品與第j個樣品的馬氏距離dij用下式計算:
dij =(x i 一x j)'S-1(x i一xj)
其中,x i 和x j分別為第i個和第j個樣品的m個指標所組成的向量,S為樣本協方差矩陣。
馬氏距離有很多優點。它不受量綱的影響,兩點之間的馬氏距離與原始數據的測量單位無關;由標准化數據和中心化數據(即原始數據與均值之差)計算出的二點之間的馬氏距離相同。馬氏距離還可以排除變量之間的相關性的干擾。它的缺點是誇大了變化微小的變量的作用。舉例說明:
兩個樣本:
His1 = {3,4,5,6}
His2 = {2,2,8,4}
它們的均值為:
U = {2.5, 3, 6.5, 5}
協方差矩陣為:
S =
| 0.25 0.50 -0.75 0.50 |
| 0.50 1.00 -1.50 1.00 |
|-0.75 -1.50 2.25 -1.50 |
| 0.50 1.00 -1.50 1.00 |
其中S(i,j)={[His1(i)-u(i)]*[His1(j)-u(j)]+[His2(i)-u(i)]*[His2(j)-u(j)]}/2
下一步就是求出逆矩陣S^(-1)
馬氏距離 D=sqrt{[His1-His2] * S^(-1) * [(His1-His2)的轉置列向量]}
1)馬氏距離的計算是建立在總體樣本的基礎上的,這一點可以從上述協方差矩陣的解釋中可以得出,也就是說,如果拿同樣的兩個樣本,放入兩個不同的總體中,最后計算得出的兩個樣本間的馬氏距離通常是不相同的,除非這兩個總體的協方差矩陣碰巧相同;
2)在計算馬氏距離過程中,要求總體樣本數大於樣本的維數,否則得到的總體樣本協方差矩陣逆矩陣不存在,這種情況下,用歐式距離來代替馬氏距離,也可以理解為,如果樣本數小於樣本的維數,這種情況下求其中兩個樣本的距離,采用歐式距離計算即可。
3)還有一種情況,滿足了條件總體樣本數大於樣本的維數,但是協方差矩陣的逆矩陣仍然不存在,比如A(3,4),B(5,6);C(7,8),這種情況是因為這三個樣本在其所處的二維空間平面內共線(如果是大於二維的話,比較復雜)。這種情況下,也采用歐式距離計算。
4)在實際應用中“總體樣本數大於樣本的維數”這個條件是很容易滿足的,而所有樣本點出現3)中所描述的情況是很少出現的,所以在絕大多數情況下,馬氏距離是可以順利計算的,但是馬氏距離的計算是不穩定的,不穩定的來源是協方差矩陣,這也是馬氏距離與歐式距離的最大差異之處。
綜上,我們用python編寫了馬氏距離,如下:
distances=[]
for i in range(dataSetSize):
x = numpy.array(dataSet)
xt=x.T
D=numpy.cov(xt)
invD=numpy.linalg.inv(D)
tp=inX-dataSet[i]
distances.append(numpy.sqrt(dot(dot(tp,invD),tp.T)))
最后得到的distances就是測試樣本和每個訓練樣本的馬氏距離。
二、 wk_NNC算法
wk-NNC算法是對經典knn算法的改進,這種方法是對k個近鄰的樣本按照他們距離待分類樣本的遠近給一個權值w:
是第i個近鄰的權值,其中1<i<k,
是待測樣本距離第i個近鄰的距離。
用python實現這個算法比較簡單:
def wk_knn(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort()
classCount={}
w=[]
for i in range(k):
w.append((distances[sortedDistIndicies[k-1]]-distances[sortedDistIndicies[i]]\
)/(distances[sortedDistIndicies[k-1]]-distances[sortedDistIndicies[0]]))
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + w[i]
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
三、 knnm算法
knnm算法運用了訓練樣本中的每一個模式,對訓練樣本的每個類,
1 ≤ i ≤ c,在每一個類中找出距離測試樣本距離最近的k個近鄰,假設這k個近鄰的均值為
,同樣的,i從1到c變化,我們得到
,如果
是M當中距離測試樣本最近的,則測試樣本屬於
類。
如下圖所示,對於一個兩類的問題,每個類選三個近鄰,類用*表示,類
用o表示,“Y”是測試樣本,則Y屬於
類。
用python實現如下:
def knnm(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize, 1)) - dataSet #tile repeat inX to (dataSetSize,1)
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1) #sum per row
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort()
classCount={}
classNum={}
i=0
while i<dataSetSize:
voteIlabel = labels[sortedDistIndicies[i]]
if sum(classNum)==10*k:
break
elif classNum.get(voteIlabel,0)==k:
i += 1
else:
classCount[voteIlabel] = classCount.get(voteIlabel,0) \
+ distances[sortedDistIndicies[i]]
classNum[voteIlabel]=classNum.get(voteIlabel,0)+1
i += 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1))
return sortedClassCount[0][0]
四、 實驗過程
我在手寫字符和約會數集分別作了實驗,結果如下(k=7):
約會數集錯誤率 |
KNN |
WK_KNN |
KNNM |
馬氏距離 |
6% |
6.6% |
6.2% |
歐氏距離 |
5.8% |
6.2% |
6.2% |
由於手寫字符訓練樣本協方差矩陣不可逆,因此只能求歐氏距離
手寫字符錯誤率 |
KNN |
WK_KNN |
KNNM |
歐式距離 |
1.1628%(k=3最小) |
0.9514%(k=5最小) |
1.2685%(k=3最小) |
五、實驗小結
歐式距離比馬氏距離計算量小得多,速度快,而且可以看出分類的效果甚至比馬氏距離要好,,可以看到,在約會數集中,knn的表現要優於其他兩種算法,歐式距離的knn錯誤率最低,而wk_knn在手寫字符識別中有較為出色的表現,相對於其他兩種算法,knnm並沒有想象中的效果。