K近鄰(KNN)算法詳解及Python實現(xiàn)

K近鄰(KNN)算法詳解及Python實現(xiàn)

今天瀏覽網(wǎng)頁看到一篇用Python實現(xiàn)K近鄰(KNN)算法的詳解教程佑惠,果斷收藏下來,雖然是五年前的文章,可能有些語法已經(jīng)不適合循狰,但文章語法思路還是可以值得借鑒的咧擂,收藏之后以后慢慢研究逞盆。

KNN依然是一種監(jiān)督學(xué)習(xí)算法

KNN(K Nearest Neighbors,K近鄰 )算法是機(jī)器學(xué)習(xí)所有算法中理論最簡單,最好理解的松申。KNN是一種基于實例的學(xué)習(xí)云芦,通過計算新數(shù)據(jù)與訓(xùn)練數(shù)據(jù)特征值之間的距離,然后選取 K(K>=1)個距離最近的鄰居進(jìn)行分類判斷(投票法)或者回歸贸桶。如果K=1舅逸,那么新數(shù)據(jù)被簡單分配給其近鄰的類。KNN算法算是監(jiān)督學(xué)習(xí)還是無監(jiān) 督學(xué)習(xí)呢皇筛?首先來看一下監(jiān)督學(xué)習(xí)和無監(jiān)督學(xué)習(xí)的定義琉历。對于監(jiān)督學(xué)習(xí),數(shù)據(jù)都有明確的label(分類針對離散分布水醋,回歸針對連續(xù)分布)旗笔,根據(jù)機(jī)器學(xué)習(xí)產(chǎn)生 的模型可以將新數(shù)據(jù)分到一個明確的類或得到一個預(yù)測值。對于非監(jiān)督學(xué)習(xí)拄踪,數(shù)據(jù)沒有l(wèi)abel蝇恶,機(jī)器學(xué)習(xí)出的模型是從數(shù)據(jù)中提取出來的pattern(提取 決定性特征或者聚類等)。例如聚類是機(jī)器根據(jù)學(xué)習(xí)得到的模型來判斷新數(shù)據(jù)“更像”哪些原數(shù)據(jù)集合惶桐。KNN算法用于分類時撮弧,每個訓(xùn)練數(shù)據(jù)都有明確的 label,也可以明確的判斷出新數(shù)據(jù)的label姚糊,KNN用于回歸時也會根據(jù)鄰居的值預(yù)測出一個明確的值贿衍,因此KNN屬于監(jiān)督學(xué)習(xí)。

KNN算法的過程為:

  • 選擇一種距離計算方式, 通過數(shù)據(jù)所有的特征計算新數(shù)據(jù)與已知類別數(shù)據(jù)集中的數(shù)據(jù)點(diǎn)的距離
  • 按照距離遞增次序進(jìn)行排序叛拷,選取與當(dāng)前距離最小的k個點(diǎn)
  • 對于離散分類舌厨,返回k個點(diǎn)出現(xiàn)頻率最多的類別作預(yù)測分類;對于回歸則返回k個點(diǎn)的加權(quán)值作為預(yù)測值

KNN算法關(guān)鍵

KNN算法的理論和過程就是那么簡單忿薇,為了使其獲得更好的學(xué)習(xí)效果裙椭,有下面幾個需要注意的地方躏哩。

  • 數(shù)據(jù)的所有特征都要做可比較的量化。
    若是數(shù)據(jù)特征中存在非數(shù)值的類型揉燃,必須采取手段將其量化為數(shù)值扫尺。舉個例子,若樣本特征中包含顏色(紅 黑藍(lán))一項炊汤,顏色之間是沒有距離可言的正驻,可通過將顏色轉(zhuǎn)換為灰度值來實現(xiàn)距離計算。另外抢腐,樣本有多個參數(shù)姑曙,每一個參數(shù)都有自己的定義域和取值范圍,他們對 distance計算的影響也就不一樣迈倍,如取值較大的影響力會蓋過取值較小的參數(shù)伤靠。為了公平,樣本參數(shù)必須做一些scale處理啼染,最簡單的方式就是所有特 征的數(shù)值都采取歸一化處置宴合。
  • 需要一個distance函數(shù)以計算兩個樣本之間的距離。
    距離的定義有很多迹鹅,如歐氏距離卦洽、余弦距離、漢明距離斜棚、曼哈頓距離等等阀蒂,關(guān)于相似性度量的方法可參考:機(jī)器學(xué)習(xí)中距離和相似性度量方法 。 一般情況下打肝,選歐氏距離作為距離度量脂新,但是這是只適用于連續(xù)變量挪捕。在文本分類這種非連續(xù)變量情況下粗梭,漢明距離可以用來作為度量。通常情況下级零,如果運(yùn)用一些特殊的算法來計算度量的話,K近鄰分類精度可顯著提高,如運(yùn)用大邊緣最近鄰法或者近鄰成分分析法耗美。
  • 確定K的值
    K是一個自定義的常數(shù)盛龄,K的值也直接影響最后的估計,一種選擇K值得方法是使用 cross-validate(交叉驗證)誤差統(tǒng)計選擇法序调。交叉驗證的概念之前提過醉锅,就是數(shù)據(jù)樣本的一部分作為訓(xùn)練樣本,一部分作為測試樣本发绢,比如選擇 95%作為訓(xùn)練樣本硬耍,剩下的用作測試樣本垄琐。通過訓(xùn)練數(shù)據(jù)訓(xùn)練一個機(jī)器學(xué)習(xí)模型,然后利用測試數(shù)據(jù)測試其誤差率经柴。 cross-validate(交叉驗證)誤差統(tǒng)計選擇法就是比較不同K值時的交叉驗證平均誤差率狸窘,選擇誤差率最小的那個K值。例如選擇 K=1,2,3,... 坯认, 對每個K=i做100次交叉驗證翻擒,計算出平均誤差,然后比較牛哺、選出最小的那個陋气。

KNN分類

訓(xùn)練樣本是多維特征空間向量,其中每個訓(xùn)練樣本帶有一個類別標(biāo)簽(喜歡或者不喜歡引润、保留或者刪除)恩伺。分類算法常采用“多數(shù)表決”決定,即k個鄰居中 出現(xiàn)次數(shù)最多的那個類作為預(yù)測類椰拒【“多數(shù)表決”分類的一個缺點(diǎn)是出現(xiàn)頻率較多的樣本將會主導(dǎo)測試點(diǎn)的預(yù)測結(jié)果,那是因為他們比較大可能出現(xiàn)在測試點(diǎn)的K鄰 域而測試點(diǎn)的屬性又是通過K領(lǐng)域內(nèi)的樣本計算出來的燃观。解決這個缺點(diǎn)的方法之一是在進(jìn)行分類時將K個鄰居到測試點(diǎn)的距離考慮進(jìn)去褒脯。例如,若樣本到測試點(diǎn)距離 為d缆毁,則選1/d為該鄰居的權(quán)重(也就是得到了該鄰居所屬類的權(quán)重)番川,接下來統(tǒng)計統(tǒng)計k個鄰居所有類標(biāo)簽的權(quán)重和,值最大的那個就是新數(shù)據(jù)點(diǎn)的預(yù)測類標(biāo) 簽脊框。

舉例颁督,K=5,計算出新數(shù)據(jù)點(diǎn)到最近的五個鄰居的舉例是(1,3,3,4,5)浇雹,五個鄰居的類標(biāo)簽是(yes沉御,no,no昭灵,yes吠裆,no)

若是按照多數(shù)表決法,則新數(shù)據(jù)點(diǎn)類別為no(3個no烂完,2個yes)试疙;若考慮距離權(quán)重類別則為yes(no:2/3+1/5,yes:1+1/4)。

下面的Python程序是采用KNN算法的實例(計算歐氏距離抠蚣,多數(shù)表決法決斷):一個是采用KNN算法改進(jìn)約會網(wǎng)站配對效果祝旷,另一個是采用KNN算法進(jìn)行手寫識別。

約會網(wǎng)站配對效果改進(jìn)的例子是根據(jù)男子的每年的飛行里程、視頻游戲時間比和每周冰激凌耗量三個特征來判斷其是否是海倫姑娘喜歡的類型(類別為很喜歡怀跛、一般和討厭)奇昙,決策采用多數(shù)表決法。由于三個特征的取值范圍不同敌完,這里采用的scale策略為歸一化储耐。

使用KNN分類器的手寫識別系統(tǒng) 只能識別數(shù)字0到9。需要識別的數(shù)字使用圖形處理軟件滨溉,處理成具有相同的色 彩和大小 :寬髙是32像素X32像素的黑白圖像什湘。盡管采用文本格式存儲圖像不能有效地利用內(nèi)存空間,為了方便理解,這里已經(jīng)將將圖像轉(zhuǎn)換為文本格式晦攒。訓(xùn)練數(shù)據(jù)中每 個數(shù)字大概有200個樣本闽撤,程序中將圖像樣本格式化處理為向量,即一個把一個32x32的二進(jìn)制圖像矩陣轉(zhuǎn)換為一個1x1024的向量。

from numpy import *
import operator
from os import listdir
import matplotlib
import matplotlib.pyplot as plt
import pdb

def classify0(inX, dataSet, labels, k=3):

#pdb.set_trace()
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() #ascend sorted,
#return the index of unsorted, that is to choose the least 3 item    
classCount={}          
for i in range(k):
    voteIlabel = labels[sortedDistIndicies[i]]
    classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1# a dict with label as key and occurrence number as value
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
'''descend sorted according to value, '''
return sortedClassCount[0][0]

def file2matrix(filename):
fr = open(filename)

#pdb.set_trace()
L = fr.readlines()
numberOfLines = len(L)         #get the number of lines in the file
returnMat = zeros((numberOfLines,3))        #prepare matrix to return
classLabelVector = []                       #prepare labels return       
index = 0
for line in L:
    line = line.strip()
    listFromLine = line.split('\t')
    returnMat[index,:] = listFromLine[0:3]
    classLabelVector.append(int(listFromLine[-1]))
    #classLabelVector.append((listFromLine[-1]))
    index += 1
fr.close()
return returnMat,classLabelVector

def plotscattter():
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') #load data setfrom file
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = fig.add_subplot(111)
ax3 = fig.add_subplot(111)
ax1.scatter(datingDataMat[:,0],datingDataMat[:,1],15.0array(datingLabels),15.0array(datingLabels))

#ax2.scatter(datingDataMat[:,0],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
#ax2.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()

def autoNorm(dataSet):
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)) #element wise divide
return normDataSet, ranges, minVals

def datingClassTest(hoRatio = 0.20):

#hold out 10%
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')       #load data setfrom file
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 "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
    if (classifierResult != datingLabels[i]): errorCount += 1.0
print "the total error rate is: %.2f%%" % (100*errorCount/float(numTestVecs))
print 'testcount is %s, errorCount is %s' %(numTestVecs,errorCount)

def classifyPerson():
'''
input a person , decide like or not, then update the DB
'''
resultlist = ['not at all','little doses','large doses']
percentTats = float(raw_input('input the person' percentage of time playing video games:'))
ffMiles = float(raw_input('flier miles in a year:'))
iceCream = float(raw_input('amount of iceCream consumed per year:'))
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
normPerson = (array([ffMiles,percentTats,iceCream])-minVals)/ranges
result = classify0(normPerson, normMat, datingLabels, 3)
print 'you will probably like this guy in:', resultlist[result -1]

#update the datingTestSet
print 'update dating DB'
tmp = '\t'.join([repr(ffMiles),repr(percentTats),repr(iceCream),repr(result)])+'\n'

with open('datingTestSet2.txt','a') as fr:
    fr.write(tmp)

def img2file(filename):

#vector = zeros(1,1024)
with open(filename) as fr:
    L=fr.readlines()
vector =[int(L[i][j]) for i in range(32) for j in range(32)]
return array(vector,dtype = float)

def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits') #load the training set
m = len(trainingFileList)
trainingMat = zeros((m,1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] #take off .txt
classNumStr = int(fileStr.split('')[0])
hwLabels.append(classNumStr)
trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = listdir('testDigits') #iterate through the test set
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0] #take off .txt
classNumStr = int(fileStr.split('
')[0])
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)
if (classifierResult != classNumStr): errorCount += 1.0
print "\nthe total number of errors is: %d" % errorCount
print "\nthe total error rate is: %f" % (errorCount/float(mTest))

if name == 'main':
datingClassTest()

#handwritingClassTest()</pre> <h3>
KNN回歸</h3>

數(shù)據(jù)點(diǎn)的類別標(biāo)簽是連續(xù)值時應(yīng)用KNN算法就是回歸脯颜,與KNN分類算法過程相同哟旗,區(qū)別在于對K個鄰居的處理上。KNN回歸是取K個鄰居類標(biāo)簽值得加
權(quán)作為新數(shù)據(jù)點(diǎn)的預(yù)測值栋操。加權(quán)方法有:K個近鄰的屬性值的平均值(最差)闸餐、1/d為權(quán)重(有效的衡量鄰居的權(quán)重,使較近鄰居的權(quán)重比較遠(yuǎn)鄰居的權(quán)重大)矾芙、
高斯函數(shù)(或者其他適當(dāng)?shù)臏p函數(shù))計算權(quán)重= gaussian(distance) (距離越遠(yuǎn)得到的值就越小舍沙,加權(quán)得到更為準(zhǔn)確的估計。

總結(jié)

K-近鄰算法是分類數(shù)據(jù)最簡單最有效的算法剔宪,其學(xué)習(xí)基于實例拂铡,使用算法時我們必須有接近實際數(shù)據(jù)的訓(xùn)練樣本數(shù)據(jù)。K-近鄰算法必須保存全部數(shù)據(jù)集葱绒,
如果訓(xùn)練數(shù)據(jù)集的很大感帅,必須使用大量的存儲空間。此外,由于必須對數(shù)據(jù)集中的每個數(shù)據(jù)計算距離值地淀,實際使用時可能非常耗時失球。k-近鄰算法的另一個缺陷是它
無法給出任何數(shù)據(jù)的基礎(chǔ)結(jié)構(gòu)信息,因此我們也無法知曉平均實例樣本和典型實例樣本具有什么特征骚秦。

原文來自:K近鄰(KNN)算法詳解及Python實現(xiàn)
)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末她倘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子作箍,更是在濱河造成了極大的恐慌,老刑警劉巖前硫,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胞得,死亡現(xiàn)場離奇詭異,居然都是意外死亡屹电,警方通過查閱死者的電腦和手機(jī)阶剑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門跃巡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牧愁,你說我怎么就攤上這事素邪。” “怎么了猪半?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵兔朦,是天一觀的道長。 經(jīng)常有香客問我磨确,道長沽甥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任乏奥,我火速辦了婚禮摆舟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邓了。我一直安慰自己恨诱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布骗炉。 她就那樣靜靜地躺著胡野,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痕鳍。 梳的紋絲不亂的頭發(fā)上硫豆,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天,我揣著相機(jī)與錄音笼呆,去河邊找鬼熊响。 笑死,一個胖子當(dāng)著我的面吹牛诗赌,可吹牛的內(nèi)容都是我干的汗茄。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼铭若,長吁一口氣:“原來是場噩夢啊……” “哼洪碳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叼屠,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤瞳腌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后镜雨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫂侍,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了挑宠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菲盾。...
    茶點(diǎn)故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖各淀,靈堂內(nèi)的尸體忽然破棺而出懒鉴,到底是詐尸還是另有隱情,我是刑警寧澤碎浇,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布临谱,位于F島的核電站,受9級特大地震影響南捂,放射性物質(zhì)發(fā)生泄漏吴裤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一溺健、第九天 我趴在偏房一處隱蔽的房頂上張望麦牺。 院中可真熱鬧,春花似錦鞭缭、人聲如沸剖膳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吱晒。三九已至,卻和暖如春沦童,著一層夾襖步出監(jiān)牢的瞬間仑濒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工偷遗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墩瞳,地道東北人。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓氏豌,卻偏偏與公主長得像喉酌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子泵喘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容