【機器學習】k-近鄰算法以及算法實例


機器學習中常常要用到分類算法,在諸多的分類算法中有一種算法名為k-近鄰算法,也稱為kNN算法。

一、kNN算法的工作原理

二、適用情況

三、算法實例及講解

  ---1.收集數據

  ---2.准備數據

  ---3.設計算法分析數據

  ---4.測試算法

 

一、kNN算法的工作原理

官方解釋:存在一個樣本數據集,也稱作訓練樣本集,並且樣本中每個數據都存在標簽,即我們知道樣本集中每一數據與所屬分類的對應關系,輸入沒有標簽的新數據后,將新數據的每個特征與樣本集中的數據對應的特征進行比較,然后算法提取樣本集中特征最相似的數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大於20的整數,最后,選擇k個最相似的數據中出現次數最多的分類,作為新數據的分類。

我的理解:k-近鄰算法就是根據“新數據的分類取決於它的鄰居”進行的,比如鄰居中大多數都是退伍軍人,那么這個人也極有可能是退伍軍人。而算法的目的就是先找出它的鄰居,然后分析這幾位鄰居大多數的分類,極有可能就是它本省的分類。

 

二、適用情況

優點:精度高,對異常數據不敏感(你的類別是由鄰居中的大多數決定的,一個異常鄰居並不能影響太大),無數據輸入假定;

缺點:計算發雜度高(需要計算新的數據點與樣本集中每個數據的“距離”,以判斷是否是前k個鄰居),空間復雜度高(巨大的矩陣);

適用數據范圍:數值型(目標變量可以從無限的數值集合中取值)和標稱型(目標變量只有在有限目標集中取值)。

 

三、算法實例及講解

例子中的案例摘《機器學習實戰》一書中的,代碼例子是用python編寫的(需要matplotlib和numpy庫),不過重在算法,只要算法明白了,用其他語言都是可以寫出來的:

海倫一直使用在線約會網站尋找合適自己的約會對象。盡管約會網站會推薦不同的人選,但她沒有從中找到喜歡的人。經過一番總結,她發現曾交往過三種類型的人:1.不喜歡的人(以下簡稱1);2.魅力一般的人(以下簡稱2) ;3.極具魅力的人(以下簡稱3
盡管發現了上述規律,但海倫依然無法將約會網站推薦的匹配對象歸入恰當的分類。她覺得可以在周一到周五約會哪些魅力一般的人,而周末則更喜歡與那些極具魅力的人為伴。海倫希望我們的分類軟件可以更好的幫助她將匹配對象划分到確切的分類中。此外海倫還收集了一些約會網站未曾記錄的數據信息,她認為這些數據更有助於匹配對象的歸類。

我們先提取一下這個案例的目標:根據一些數據信息,對指定人選進行分類(1或2或3)。為了使用kNN算法達到這個目標,我們需要哪些信息?前面提到過,就是需要樣本數據,仔細閱讀我們發現,這些樣本數據就是“海倫還收集了一些約會網站未曾記錄的數據信息”。好的,下面我們就開始吧!

----1.收集數據

海倫收集的數據是記錄一個人的三個特征:每年獲得的飛行常客里程數;玩視頻游戲所消耗的時間百分比;每周消費的冰淇淋公升數。數據是txt格式文件,如下圖,前三列依次是三個特征,第四列是分類(1:不喜歡的人,2:魅力一般的人,3:極具魅力的人),每一行代表一個人。

數據文檔的下載鏈接是:http://pan.baidu.com/s/1jG7n4hS

----2.准備數據

何為准備數據?之前收集到了數據,放到了txt格式的文檔中了,看起來也比較規整,但是計算機並不認識啊。計算機需要從txt文檔中讀取數據,並把數據進行格式化,也就是說存到矩陣中,用矩陣來承裝這些數據,這樣才能使用計算機處理。

需要兩個矩陣:一個承裝三個特征數據,一個承裝對應的分類。於是,我們定義一個函數,函數的輸入時數據文檔(txt格式),輸出為兩個矩陣。

代碼如下:

 1 def file2matrix(filename):
 2     fr = open(filename)
 3     numberOfLines = len(fr.readlines())
 4     returnMat = zeros((numberOfLines, 3))
 5     classLabelVector = []
 6     fr = open(filename)
 7     index = 0
 8     for line in fr.readlines():
 9         line = line.strip()
10         listFromLine = line.split('\t')
11         returnMat[index, :] = listFromLine[0:3]
12         classLabelVector.append(int(listFromLine[-1]))
13         index += 1
14     return returnMat, classLabelVector  

簡要解讀代碼:首先打開文件,讀取文件的行數,然后初始化之后要返回的兩個矩陣(returnMat、classLabelsVector),然后進入循環,將每行的數據各就各位分配給returnMat和classLabelsVector。

----3.設計算法分析數據

k-近鄰算法的目的就是找到新數據的前k個鄰居,然后根據鄰居的分類來確定該數據的分類。

首先要解決的問題,就是什么是鄰居?當然就是“距離”近的了,不同人的距離怎么確定?這個有點抽象,不過我們有每個人的3個特征數據。每個人可以使用這三個特征數據來代替這個人——三維點。比如樣本的第一個人就可以用(40920, 8.326976, 0.953952)來代替,並且他的分類是3。那么此時的距離就是點的距離:

A點(x1, x2, x3),B點(y1, y2, y3),這兩個點的距離就是:(x1-y1)^2+(x2-y2)^2+(x3-y3)^2的平方根。求出新數據與樣本中每個點的距離,然后進行從小到大排序,前k位的就是k-近鄰,然后看看這k位近鄰中占得最多的分類是什么,也就獲得了最終的答案。

這個處理過程也是放到一個函數里的,代碼如下:

 

 1 def classify0(inX, dataSet, labels, k):
 2     dataSetSize = dataSet.shape[0]
 3     diffMat = tile(inX, (dataSetSize,1)) - dataSet
 4     sqDiffMat = diffMat**2
 5     sqDistances = sqDiffMat.sum(axis=1)
 6     distances = sqDistances**0.5
 7     sortedDistIndicies = distances.argsort()
 8     classCount={}
 9     for i in range(k):
10         voteIlabel = labels[sortedDistIndicies[i]]
11         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
12     sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True)
13     return sortedClassCount[0][0]

 

簡要解讀代碼:該函數的4個參數分別為新數據的三個特征inX、樣本數據特征集(上一個函數的返回值)、樣本數據分類(上一個函數的返回值)、k,函數返回位新數據的分類。第二行dataSetSize獲取特征集矩陣的行數,第三行為新數據與樣本各個數據的差值,第四行取差值去平方,之后就是再取和,然后平方根。代碼中使用的排序函數都是python自帶的。

好了,現在我們可以分析數據了,不過,有一點不知道大家有沒有注意,我們回到那個數據集,第一列代表的特征數值遠遠大於其他兩項特征,這樣在求距離的公式中就會占很大的比重,致使兩點的距離很大程度上取決於這個特征,這當然是不公平的,我們需要的三個特征都均平地決定距離,所以我們要對數據進行處理,希望處理之后既不影響相對大小又可以不失公平

 

這種方法叫做,歸一化數值,通過這種方法可以把每一列的取值范圍划到0~1或-1~1:,處理的公式為:

newValue = (oldValue - min)/(max - min)

歸一化數值的函數代碼為:

1 def autoNorm(dataSet):
2     minVals = dataSet.min(0)
3     maxVals = dataSet.max(0)
4     ranges = maxVals - minVals
5     normDataSet = zeros(shape(dataSet))
6     m = dataSet.shape[0]
7     normDataSet = dataSet - tile(minVals, (m, 1))
8     normDataSet = normDataSet / tile(ranges, (m, 1))
9     return normDataSet, ranges, minVals

---4.測試算法

經過了格式化數據、歸一化數值,同時我們也已經完成kNN核心算法的函數,現在可以測試了,測試代碼為:

 1 def datingClassTest():
 2     hoRatio = 0.10
 3     datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
 4     normMat, ranges, minVals = autoNorm(datingDataMat)
 5     m = normMat.shape[0]
 6     numTestVecs = int(m * hoRatio)
 7     errorCount = 0.0
 8     for i in range(numTestVecs):
 9         classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
10         print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
11         if (classifierResult != datingLabels[i]): errorCount += 1.0
12     print "the total error rate is: %f" % (errorCount / float(numTestVecs))

通過測試代碼我們可以在回憶一下這個例子的整體過程:

  • 讀取txt文件,提取里面的數據到datingDataMat、datingLabels;
  • 歸一化數據,得到歸一化的數據矩陣;
  • 測試數據不止一個,這里需要一個循環,依次對每個測試數據進行分類。

代碼中大家可能不太明白hoRatio是什么。注意,這里的測試數據並不是另外一批數據而是之前的數據集里的一部分,這樣我們可以把算法得到的結果和原本的分類進行對比,查看算法的准確度。在這里,海倫提供的數據集又1000行,我們把前100行作為測試數據,后900行作為樣本數據集,現在大家應該可以明白hoRatio是什么了吧。

整體的代碼:

 1 from numpy import *
 2 import operator
 3 
 4 def classify0(inX, dataSet, labels, k):
 5     dataSetSize = dataSet.shape[0]
 6     diffMat = tile(inX, (dataSetSize,1)) - dataSet
 7     sqDiffMat = diffMat**2
 8     sqDistances = sqDiffMat.sum(axis=1)
 9     distances = sqDistances**0.5
10     sortedDistIndicies = distances.argsort()
11     classCount={}
12     for i in range(k):
13         voteIlabel = labels[sortedDistIndicies[i]]
14         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
15     sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1), reverse=True)
16     return sortedClassCount[0][0]
17 
18 def file2matrix(filename):
19     fr = open(filename)
20     numberOfLines = len(fr.readlines())
21     returnMat = zeros((numberOfLines, 3))
22     classLabelVector = []
23     fr = open(filename)
24     index = 0
25     for line in fr.readlines():
26         line = line.strip()
27         listFromLine = line.split('\t')
28         returnMat[index, :] = listFromLine[0:3]
29         classLabelVector.append(int(listFromLine[-1]))
30         index += 1
31     return returnMat, classLabelVector  
32 
33 def autoNorm(dataSet):
34     minVals = dataSet.min(0)
35     maxVals = dataSet.max(0)
36     ranges = maxVals - minVals
37     normDataSet = zeros(shape(dataSet))
38     m = dataSet.shape[0]
39     normDataSet = dataSet - tile(minVals, (m, 1))
40     normDataSet = normDataSet / tile(ranges, (m, 1))
41     return normDataSet, ranges, minVals
42 
43 def datingClassTest():
44     hoRatio = 0.10
45     datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
46     normMat, ranges, minVals = autoNorm(datingDataMat)
47     m = normMat.shape[0]
48     numTestVecs = int(m * hoRatio)
49     errorCount = 0.0
50     for i in range(numTestVecs):
51         classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
52         print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
53         if (classifierResult != datingLabels[i]): errorCount += 1.0
54     print "the total error rate is: %f" % (errorCount / float(numTestVecs))

運行一下代碼,這里我使用的是ipython:

最后的錯誤率為0.05。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM