好久沒更新了客蹋,最近在忙著寫論文敞咧,昨天我的新書機器實戰(zhàn)到了,于是就連夜學(xué)了第一個實例偶翅。
1他去、k-近鄰法簡介
k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與回歸方法。它的工作原理是:存在一個樣本數(shù)據(jù)集合倒堕,也稱作為訓(xùn)練樣本集,并且樣本集中每個數(shù)據(jù)都存在標(biāo)簽爆价,即我們知道樣本集中每一個數(shù)據(jù)與所屬分類的對應(yīng)關(guān)系垦巴。輸入沒有標(biāo)簽的新數(shù)據(jù)后媳搪,將新的數(shù)據(jù)的每個特征與樣本集中數(shù)據(jù)對應(yīng)的特征進(jìn)行比較,然后算法提取樣本最相似數(shù)據(jù)(最近鄰)的分類標(biāo)簽骤宣。一般來說秦爆,我們只選擇樣本數(shù)據(jù)集中前k個最相似的數(shù)據(jù),這就是k-近鄰算法中k的出處憔披,通常k是不大于20的整數(shù)等限。最后,選擇k個最相似數(shù)據(jù)中出現(xiàn)次數(shù)最多的分類芬膝,作為新數(shù)據(jù)的分類望门。
舉個簡單的例子,我們可以使用k-近鄰算法分類一個電影是愛情片還是動作片锰霜。
表1.1 每部電影的打斗鏡頭數(shù)筹误、接吻鏡頭數(shù)以及電影類型
表1.1 就是我們已有的數(shù)據(jù)集合,也就是訓(xùn)練樣本集癣缅。這個數(shù)據(jù)集有兩個特征厨剪,即打斗鏡頭數(shù)和接吻鏡頭數(shù)。除此之外友存,我們也知道每個電影的所屬類型祷膳,即分類標(biāo)簽。用肉眼粗略地觀察屡立,接吻鏡頭多的直晨,是愛情片。打斗鏡頭多的侠驯,是動作片抡秆。以我們多年的看片經(jīng)驗,這個分類還算合理吟策。如果現(xiàn)在給我一部電影儒士,你告訴我這個電影打斗鏡頭數(shù)和接吻鏡頭數(shù)。不告訴我這個電影類型檩坚,我可以根據(jù)你給我的信息進(jìn)行判斷着撩,這個電影是屬于愛情片還是動作片。而k-近鄰算法也可以像我們?nèi)艘粯幼龅竭@一點匾委,不同的地方在于拖叙,我們的經(jīng)驗更"牛逼",而k-近鄰算法是靠已有的數(shù)據(jù)赂乐。比如薯鳍,你告訴我這個電影打斗鏡頭數(shù)為2,接吻鏡頭數(shù)為102挨措,我的經(jīng)驗會告訴你這個是愛情片挖滤,k-近鄰算法也會告訴你這個是愛情片崩溪。你又告訴我另一個電影打斗鏡頭數(shù)為49,接吻鏡頭數(shù)為51斩松,我"邪惡"的經(jīng)驗可能會告訴你伶唯,這有可能是個"愛情動作片",畫面太美惧盹,我不敢想象乳幸。 (如果說,你不知道"愛情動作片"是什么钧椰?請評論留言與我聯(lián)系粹断,我需要你這樣像我一樣純潔的朋友。) 但是k-近鄰算法不會告訴你這些演侯,因為在它的眼里姿染,電影類型只有愛情片和動作片,它會提取樣本集中特征最相似數(shù)據(jù)(最鄰近)的分類標(biāo)簽秒际,得到的結(jié)果可能是愛情片悬赏,也可能是動作片,但絕不會是"愛情動作片"娄徊。當(dāng)然闽颇,這些取決于數(shù)據(jù)集的大小以及最近鄰的判斷標(biāo)準(zhǔn)等因素。
2寄锐、距離度量
我們已經(jīng)知道k-近鄰算法根據(jù)特征比較兵多,然后提取樣本集中特征最相似數(shù)據(jù)(最鄰近)的分類標(biāo)簽。那么橄仆,如何進(jìn)行比較呢剩膘?比如,我們還是以表1.1為例盆顾,怎么判斷紅色圓點標(biāo)記的電影所屬的類別呢怠褐? 如下圖所示。
我們可以從散點圖大致推斷您宪,這個紅色圓點標(biāo)記的電影可能屬于動作片奈懒,因為距離已知的那兩個動作片的圓點更近。k-近鄰算法用什么方法進(jìn)行判斷呢宪巨?沒錯磷杏,就是距離度量。這個電影分類的例子有2個特征捏卓,也就是在2維實數(shù)向量空間极祸,可以使用我們高中學(xué)過的兩點距離公式計算距離,如圖1.2所示。
通過計算遥金,我們可以得到如下結(jié)果:
- (101,20)->動作片(108,5)的距離約為16.55
- (101,20)->動作片(115,8)的距離約為18.44
- (101,20)->愛情片(5,89)的距離約為118.22
- (101,20)->愛情片(1,101)的距離約為128.69
通過計算可知峦椰,紅色圓點標(biāo)記的電影到動作片 (108,5)的距離最近,為16.55汰规。如果算法直接根據(jù)這個結(jié)果,判斷該紅色圓點標(biāo)記的電影為動作片物邑,這個算法就是最近鄰算法溜哮,而非k-近鄰算法坎匿。那么k-近鄰算法是什么呢驼唱?k-近鄰算法步驟如下:
- 計算已知類別數(shù)據(jù)集中的點與當(dāng)前點之間的距離信卡;
- 按照距離遞增次序排序休吠;
- 選取與當(dāng)前點距離最小的k個點潮梯;
- 確定前k個點所在類別的出現(xiàn)頻率碗淌;
- 返回前k個點所出現(xiàn)頻率最高的類別作為當(dāng)前點的預(yù)測分類喷橙。
比如扼劈,現(xiàn)在我這個k值取3锣笨,那么在電影例子中蝌矛,按距離依次排序的三個點分別是動作片(108,5)、動作片(115,8)错英、愛情片(5,89)入撒。在這三個點中,動作片出現(xiàn)的頻率為三分之二椭岩,愛情片出現(xiàn)的頻率為三分之一茅逮,所以該紅色圓點標(biāo)記的電影為動作片。這個判別過程就是k-近鄰算法判哥。
代碼實現(xiàn)步驟
準(zhǔn)備數(shù)據(jù):從文本文件中解析數(shù)據(jù)
分析數(shù)據(jù):使用Matplotlib創(chuàng)建散點圖
準(zhǔn)備數(shù)據(jù):歸一化數(shù)值
測試算法:作為完整程序驗證分類器
使用算法:構(gòu)建完整可用系統(tǒng)
from numpy import *
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import operator
def createDataSet():
group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels=["A","A","B","B"]
return group,labels
''''
函數(shù)說明:knn算法献雅,分類器,inx是輸入的向量塌计,dataset是數(shù)據(jù)集挺身,labels是標(biāo)簽
'''''
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0]
diffMat = tile(inX, (dataSetSize,1))-dataSet
sqDiffMat = diffMat**2
sqDistance = sqDiffMat.sum(axis=1)
distances = sqDistance**0.5
sortedDistIndicies = distances.argsort()
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
'''
函數(shù)說明:打開解析文件
filename:文件名
returnmat:特征矩陣
classlabelvector;標(biāo)簽列表
'''
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines)
returnMat = zeros((numberOfLines,3))#創(chuàng)建一個行*3的0矩陣
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip() #去除每一行的空格或換行符
listFromLine = line.split("\t") #按照回車分割每一行為每一個數(shù)組
returnMat[index, :] = listFromLine[0:3]#把每一行數(shù)據(jù)添加到之前創(chuàng)建的0矩陣之中
#print(listFromLine)
#classLabelVector.append(str(listFromLine[-1]))
""""
if listFromLine[-1] == "didntLike":
classLabelVector.append(1)
elif listFromLine[-1] == "smallDoses":
classLabelVector.append(2)
elif listFromLine[-1] == "largeDoses":
classLabelVector.append(3)
"""
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
"""
函數(shù)說明:分析數(shù)據(jù),數(shù)據(jù)可視化
"""
def showdatas(datingDataMat,datingLabels):
# 設(shè)置漢字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
# 將fig畫布分隔成1行1列,不共享x軸和y軸,fig畫布的大小為(13,8)
# 當(dāng)nrow=2,nclos=2時,代表fig畫布被分為四個區(qū)域,axs[0][0]表示第一行第一個區(qū)域
fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))
numberOfLabels = len(datingLabels)
LabelsColors = []
for i in datingLabels:
if i == 1:
LabelsColors.append('black')
if i == 2:
LabelsColors.append('orange')
if i == 3:
LabelsColors.append('red')
# 畫出散點圖,以datingDataMat矩陣的第一(飛行扯峄模客例程)瞒渠、第二列(玩游戲)數(shù)據(jù)畫散點數(shù)據(jù),散點大小為15,透明度為0.5
axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
# 設(shè)置標(biāo)題,x軸label,y軸label
axs0_title_text = axs[0][0].set_title(u'每年獲得的飛行常客里程數(shù)與玩視頻游戲所消耗時間占比', FontProperties=font)
axs0_xlabel_text = axs[0][0].set_xlabel(u'每年獲得的飛行臣级螅客里程數(shù)', FontProperties=font)
axs0_ylabel_text = axs[0][0].set_ylabel(u'玩視頻游戲所消耗時間占', FontProperties=font)
plt.setp(axs0_title_text, size=9, weight='bold', color='red')
plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
# 畫出散點圖,以datingDataMat矩陣的第一(飛行澄榫粒客例程)、第三列(冰激凌)數(shù)據(jù)畫散點數(shù)據(jù),散點大小為15,透明度為0.5
axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 設(shè)置標(biāo)題,x軸label,y軸label
axs1_title_text = axs[0][1].set_title(u'每年獲得的飛行辰宋牵客里程數(shù)與每周消費的冰激淋公升數(shù)', FontProperties=font)
axs1_xlabel_text = axs[0][1].set_xlabel(u'每年獲得的飛行城瞎浚客里程數(shù)', FontProperties=font)
axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消費的冰激淋公升數(shù)', FontProperties=font)
plt.setp(axs1_title_text, size=9, weight='bold', color='red')
plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
# 畫出散點圖,以datingDataMat矩陣的第二(玩游戲)、第三列(冰激凌)數(shù)據(jù)畫散點數(shù)據(jù),散點大小為15,透明度為0.5
axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 設(shè)置標(biāo)題,x軸label,y軸label
axs2_title_text = axs[1][0].set_title(u'玩視頻游戲所消耗時間占比與每周消費的冰激淋公升數(shù)', FontProperties=font)
axs2_xlabel_text = axs[1][0].set_xlabel(u'玩視頻游戲所消耗時間占比', FontProperties=font)
axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消費的冰激淋公升數(shù)', FontProperties=font)
plt.setp(axs2_title_text, size=9, weight='bold', color='red')
plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
# 設(shè)置圖例
didntLike = mlines.Line2D([], [], color='black', marker='.',
markersize=6, label='didntLike')
smallDoses = mlines.Line2D([], [], color='orange', marker='.',
markersize=6, label='smallDoses')
largeDoses = mlines.Line2D([], [], color='red', marker='.',
markersize=6, label='largeDoses')
# 添加圖例
axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
# 顯示圖片
plt.show()
"""""
函數(shù)說明:dataSet是數(shù)據(jù)集,將數(shù)據(jù)集轉(zhuǎn)化為0-1之間的數(shù)椰棘,公式為
newvalue = (oldvalue - min)/(max - min)
"""
def autoNorm(dataSet):
#min(0)是取當(dāng)前列最小值纺棺,min(1)是當(dāng)前列最大值,max同理
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet/tile(ranges, (m,1))
return normDataSet, ranges, minVals
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix("datingTestSet.txt")
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m],3)
print( "分類器結(jié)果:%d, 真實結(jié)果:%d" %(classifierResult,datingLabels[i]))
if(classifierResult!=datingLabels[i]) :
errorCount += 1.0
print( "分類器的錯誤率是%f" %(errorCount/float(numTestVecs)))
def classifyPerson():
resultList = ["討厭", "有點喜歡", "非常喜歡"]
percentTats = float(input("玩視頻游戲所占百分比:"))
ffMiles = float(input("每年飛行里程數(shù):"))
iceCream = float(input("每周吃的冰淇淋公升數(shù):"))
datingDataMat, datingLabels = file2matrix("datingTestSet2.txt")
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
norminArr = (inArr - minVals) / ranges
classifierResult = classify0(norminArr, normMat, datingLabels, 3)
print("這個人你應(yīng)該%s"%(resultList[classifierResult-1]))
if __name__ == '__main__':
classifyPerson()