機器學習之k-近鄰算法實踐學習
關於本文說明,筆者原博客地址位於http://blog.csdn.net/qq_37608890,本文來自筆者於2017年12月04日 22:54:26所撰寫內容(http://blog.csdn.net/qq_37608890/article/details/78714664)。
本文根據最近學習機器學習書籍 網絡文章的情況,特將一些學習思路做了歸納整理,詳情如下.如有不當之處,請各位大拿多多指點,在此謝過.
一、k-近鄰算法(k-Nearest Neighbor,KNN)概述
1、簡言之,k-近鄰算法采用測量不同特征值之間的距離方法進行分類。
2、工作原理
存在一個樣本數據集合,也稱為訓練樣本集,且樣本集中每個數據都存在標簽,也就是眾所周知樣本集中每一數據與所屬分類的對應關系。輸入沒有標簽的新數據以後,將新數據的每個特征與樣本集中數據對應的特征進行比較,然後算法提取樣本集中特征最相似數據(最近鄰)的分類標簽。一般情況下,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大於20的整數。最終,選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。
3、k-近鄰算法的一般流程
(1) 收集數據:可以使用任何方法。
(2) 準備數據: 距離計算所需要的數值,最好是結構化數據格式。
(3) 分析數據: 可以使用任何方法。
(4) 訓練算法: 此步驟不適用K-近鄰算法。
(5) 測試算法: 計算錯誤率。
(6) 使用算法: 首先需要輸入樣本數據和結構化的輸出結果,然後運行k-近鄰算法判定輸入數據分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。
4、相關特性
(1)優點: 精度高,對異常值不敏感、無輸入假定。
(2)缺點: 計算復雜度高、空間復雜度高。
(3)適用數據範圍: 數值型和標稱型。
kNN是non-parametric分類器(不做分布形式的假設,直接從數據估計概率密度),是memory-based learning,不適用於高維數據(curse of dimension),算法復雜度高(可用KD樹優化)。另外,k越小越容易過擬合,但是k很大會將分類精度(設想極限情況:k=1和k=N(樣本數))。
二、k-近鄰場景
我們知道,電影可以按題材分類,但題材本身是如何定義的?由誰來判定某部電影屬於哪個題材?即同一題材的電影會具有哪些公共特征?這些都是在做電影分類時一定要搞清楚的問題。這裏以動作片和愛情片為例做簡要說明。動作片具有哪些公共特征,使得動作片之間非常相似,卻明顯有別於愛情片?動作片中也可能有接吻鏡頭,愛情片中也可能存在打鬥鏡頭,所以,不能簡單地依靠是否存在打鬥或者接吻來判斷一部電影的類型。但很明顯的是,動作片中的打鬥鏡頭更多、愛情片中的接吻次數更頻繁,基於此類場景在一部電影中出現的次數可用來進行電影分類。
有人曾經統計過很多電影的打鬥鏡頭和接吻鏡頭, 下方圖1-1 給出了6部電影的打鬥和接吻鏡頭。假如現在有一部從未看過的電影,你如何判斷它屬於動作片還是愛情片呢?
圖1-1
首先,我們弄清楚這部未知電影中存在多少打鬥鏡頭、多少接吻鏡頭,圖1-1中問號位置是該未知電影出現的鏡頭數圖示,具體見下方表1-1。
表1-1每部電影的打鬥鏡頭數、接吻鏡頭數及電影評估類型
由圖1-1和表1-1,可用將未知電影在圖1-1的具體位置標出,利用歐式距離公式,計算出未知電影與樣本集中其他電影之間的距離,相見下方表2-2所示。
表2-2 已知電影與未知電影之間的距離
由表2-2所示,顯然,如果樣本集中所有電影與未知電影之間的距離按照遞增排序的話,可以得到k個距離最近的電影,這裏假設k=2的話,則未知電影與電影He’s Not Really into Dudes,Beautiful Woman影片類型最為相似,判定未知電影屬於愛情片。
三、示例:使用kNN算法優化約會網站的配對效果
特別提醒:有些教材或博客,在代碼實現過程中,由於python2.x和python3.x的不同,在實際執行過程中出現語法錯誤,這裏特做提醒,print語句將輸出內容一律加上();另外,python3.0版本後用input替換了raw_input,請讀者註意。
1、項目概述
海倫在使用約會網站尋找自己的約會對象。總結經驗之後,她發現曾交往過的人分三種類型:
- 不喜歡的人
- 魅力一般的人
- 極具魅力的人
她期待:
- 工作日與魅力一般的人約會
- 周末與極具魅力的人約會
- 不喜歡的人則直接排除掉
2、 k-近鄰算法開發實現流程
收集數據:提供文本文件
-
準備數據:使用 Python 解析文本文件
-
分析數據:使用 Matplotlib 畫二維散點圖
-
訓練算法:此步驟不適用於 k-近鄰算法
-
測試算法:使用海倫提供的部分數據作為測試樣本。 測試樣本和非測試樣本的區別在於: 測試樣本是已經完成分類的數據,如果預測分類與實際類別不同,則標記為一個錯誤。
-
使用算法:產生簡單的命令行程序,然後海倫可以輸入一些特征數據以判斷對方是否為自己喜歡的類型。
3、 準備數據:從文本文件中解析數據
目前,海倫搜集來一些約會對象的數據,並把這些數據存放在文本文件 datingTestSet2.txt 中,每個樣本數據占據一行,總共有 1000 行。這些樣本數據主要包含以下 3 種特征:
- 每年獲得的飛行常客裏程數
- 玩視頻遊戲所耗時間百分比
- 每周消費的冰淇淋公升數
而文本文件datingTestSet2.txt的數據格式如下:
40999 9.161978 1.110180 3 15823 0.991725 0.730979 2 35432 7.398380 0.684218 3 53711 12.149747 1.389088 3 64371 9.149678 0.874905 1 9289 9.666576 1.370330 2
準備數據:使用 Python 解析文本文件
將文本記錄轉換為 NumPy 的解析程序
def file2matrix(filename): """ Desc: 導入訓練數據 parameters: filename: 數據文件路徑 return: 數據矩陣 returnMat 和對應的類別 classLabelVector """ fr = open(filename) # 獲得文件中的數據行的行數 numberOfLines = len(fr.readlines()) # 生成對應的空矩陣 # 例如:zeros(2,3)就是生成一個 2*3的矩陣,各個位置上全是 0 returnMat = zeros((numberOfLines, 3)) # prepare matrix to return classLabelVector = [] # prepare labels return fr = open(filename) index = 0 for line in fr.readlines(): # str.strip([chars]) --返回移除字符串頭尾指定的字符生成的新字符串 line = line.strip() # 以 ‘\t‘ 切割字符串 listFromLine = line.split(‘\t‘) # 每列的屬性數據 returnMat[index, :] = listFromLine[0:3] # 每列的類別數據,就是 label 標簽數據 classLabelVector.append(int(listFromLine[-1])) index += 1 # 返回數據矩陣returnMat和對應的類別classLabelVector return returnMat, classLabelVector
執行如下命令
datingDataMat,datingLabels=file2matrix(‘datingTestSet2.txt‘) datingDataMat
得到
array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01], [ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00], [ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01], ..., [ 6.88460000e+04, 9.97471500e+00, 6.69787000e-01], [ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01], [ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01]])
執行 datingLabels[0:20],得到
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]
4、 分析數據:使用 Matplotlib 畫二維散點圖
執行如下代碼
import matplotlib import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0*array(datingLabels), 15.0*array(datingLabels)) plt.show()
得到如下圖
而下圖中采用矩陣的第一和第三列屬性得到很好的展示效果,清晰地標識了三個不同的樣本分類區域,具有不同愛好的人其類別區域也不同。
5、 準備數據:歸一化數值
歸一化數據 (歸一化是一個讓權重變為統一的過程)
序號 | 玩視頻遊戲所耗時間百分比 | 每年獲得的飛行常客裏程數 | 每周消費的冰淇淋公升數 | 樣本分類 |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134 000 | 0.9 | 3 |
3 | 0 | 20 000 | 1.1 | 2 |
4 | 67 | 32 000 | 0.1 | 2 |
樣本3和樣本4的距離:
(0?67)2+(20000?32000)2+(1.1?0.1)2?????????????????????????????????√歸一化特征值,消除特征之間量級不同導致的影響
歸一化定義: 我是這樣認為的,歸一化就是要把你需要處理的數據經過處理後(通過某種算法)限制在你需要的一定範圍內。首先歸一化是為了後面數據處理的方便,其次是保正程序運行時收斂加快。 方法有如下:
-
線性函數轉換,表達式如下:
y=(x-MinValue)/(MaxValue-MinValue)
說明:x、y分別為轉換前、後的值,MaxValue、MinValue分別為樣本的最大值和最小值。
-
對數函數轉換,表達式如下:
y=log10(x)
說明:以10為底的對數函數轉換。
如圖:
-
反余切函數轉換,表達式如下:
y=atan(x)*2/PI
如圖:
-
式(1)將輸入值換算為[-1,1]區間的值,在輸出層用式(2)換算回初始值,其中和分別表示訓練樣本集中負荷的最大值和最小值。
在統計學中,歸一化的具體作用是歸納統一樣本的統計分布性。歸一化在0-1之間是統計的概率分布,歸一化在-1--+1之間是統計的坐標分布。
def autoNorm(dataSet): """ Desc: 歸一化特征值,消除特征之間量級不同導致的影響 parameter: dataSet: 數據集 return: 歸一化後的數據集 normDataSet. ranges和minVals即最小值與範圍,並沒有用到 歸一化公式: Y = (X-Xmin)/(Xmax-Xmin) 其中的 min 和 max 分別是數據集中的最小特征值和最大特征值。該函數可以自動將數字特征值轉化為0到1的區間。 """ # 計算每種屬性的最大值、最小值、範圍 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
訓練算法:此步驟不適用於 k-近鄰算法 因為測試數據每一次都要與全量的訓練數據進行比較,所以這個過程是沒有必要的。
6 、 測試算法
作為完整程序驗證分類器
使用海倫提供的部分數據作為測試樣本。如果預測分類與實際類別不同,則標記為一個錯誤。
kNN 分類器針對約會網站的測試代碼
def datingClassTest(): """ Desc: 對約會網站的測試方法 parameters: none return: 錯誤數 """ # 設置測試數據的的一個比例(訓練數據集比例=1-hoRatio) hoRatio = 0.1 # 測試範圍,一部分測試一部分作為樣本 # 從文件中加載數據 datingDataMat, datingLabels = file2matrix(‘datingTestSet2.txt‘) # load data setfrom file # 歸一化數據 normMat, ranges, minVals = autoNorm(datingDataMat) # m 表示數據的行數,即矩陣的第一維 m = normMat.shape[0] # 設置測試的樣本數量, numTestVecs:m表示訓練樣本的數量 numTestVecs = int(m * hoRatio) print ‘numTestVecs=‘, numTestVecs 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: %f" % (errorCount / float(numTestVecs))) print (errorCount)
7、 使用算法:構建完整可用的系統
上面已經在數據上對分類器進行了測試,現在則可以使用這個分類器幫助海倫對於約會對象進行分類,即海倫在網站上找到某人並輸入他的信息,該程序會給出她對對方喜歡程度的預測值.
def clasdifyPerson(): resultList = [‘not at all‘, ‘in small doses‘, ‘in large doses‘] percentTats = float(raw_input("percentage of time spent playing video games ?")) ffMiles = float(raw_input("frequent filer miles earned per year?")) iceCream = float(raw_input("liters of ice cream consumed per year?")) datingDataMat, datingLabels = file2matrix(‘datingTestSet2.txt‘) normMat, ranges, minVals = autoNorm(datingDataMat) inArr = array([ffMils, percentTats, iceCream]) classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels, 3) print ("You will probably like this person: ", resultList[classifierResult - 1])
實際運行效果如下:
>>> kNN.classifyPerson() percentage of time spent playing video games?10 frequent flier miles earned per year?10000 liters of ice cream consumed per year?0.5 You will probably like this person: in small doses
四、 項目案例2: 手寫數字識別系統
1、 項目概述
構造一個能識別數字 0 到 9 的基於 KNN 分類器的手寫數字識別系統。
需要識別的數字是存儲在文本文件中的具有相同的色彩和大小:寬高是 32 像素 * 32 像素的黑白圖像。
2、 開發流程
收集數據:提供文本文件。 準備數據:編寫函數 img2vector(), 將圖像格式轉換為分類器使用的向量格式 分析數據:在 Python 命令提示符中檢查數據,確保它符合要求 訓練算法:此步驟不適用於 KNN 測試算法:編寫函數使用提供的部分數據集作為測試樣本,測試樣本與非測試樣本的 區別在於測試樣本是已經完成分類的數據,如果預測分類與實際類別不同, 則標記為一個錯誤 使用算法:本例沒有完成此步驟,若你感興趣可以構建完整的應用程序,從圖像中提取 數字,並完成數字識別,美國的郵件分揀系統就是一個實際運行的類似系統
收集數據: 提供文本文件
目錄 trainingDigits中包含了大約 2000 個例子,每個例子內容如下圖所示,每個數字大約有 200 個樣本;目錄 testDigits中包含了大約 900 個測試數據。
準備數據: 編寫函數 img2vector(), 將圖像文本數據轉換為分類器使用的向量
將圖像文本數據轉換為向量
def img2vector(filename): returnVect = zeros((1,1024)) fr = open(filename) for i in range(32): lineStr = fr.readLine() for j in range(32): returnVect[0,32*i+j] = int(lineStr[j]) return returnVect
分析數據:在 Python 命令提示符中檢查數據,確保它符合要求
在 Python 命令行中輸入下列命令測試 img2vector 函數,然後與文本編輯器打開的文件進行比較:
>>> testVector = kNN.img2vector(‘testDigits/0_13.txt‘) >>> testVector[0,0:31] array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) >>> testVector[0,31:63] array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
訓練算法:此步驟不適用於 KNN
因為測試數據每一次都要與全量的訓練數據進行比較,所以這個過程是沒有必要的。
測試算法:編寫函數使用提供的部分數據集作為測試樣本,如果預測分類與實際類別不同,則標記為一個錯誤def handwritingClassTest(): # 1. 導入訓練數據 hwLabels = [] trainingFileList = listdir(‘trainingDigits‘) # load the training set m = len(trainingFileList) trainingMat = zeros((m, 1024)) # hwLabels存儲0~9對應的index位置, trainingMat存放的每個位置對應的圖片向量 for i in range(m): fileNameStr = trainingFileList[i] fileStr = fileNameStr.split(‘.‘)[0] # take off .txt classNumStr = int(fileStr.split(‘_‘)[0]) hwLabels.append(classNumStr) # 將 32*32的矩陣->1*1024的矩陣 trainingMat[i, :] = img2vector(‘trainingDigits/%s‘ % fileNameStr) # 2. 導入測試數據 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)))
使用算法:本例沒有完成此步驟,若你感興趣可以構建完整的應用程序,從圖像中提取數字,並完成數字識別,美國的郵件分揀系統就是一個實際運行的類似系統
五、 K-近鄰算法小結
截至目前,我們做一總結,k-近鄰算法必須保存全部數據集,如果訓練數據集很大,必須使用大量的存儲空間.由於必須對數據集中的每個數據計算距離值,實際使用時可能非常耗時.此外,k-近鄰還有以缺陷是它沒辦法給出任何數據的基礎結構信息,也就無法得知平均實例樣本和典型實例樣本有哪些特征.而要解決這個問題,需要使用概率測量方法來處理.
k 近鄰算法有 三個基本的要素:
-
k 值的選擇
- k 值的選擇會對 k 近鄰算法的結果產生重大的影響。
- 如果選擇較小的 k 值,就相當於用較小的鄰域中的訓練實例進行預測,“學習”的近似誤差(approximation error)會減小,只有與輸入實例較近的(相似的)訓練實例才會對預測結果起作用。但缺點是“學習”的估計誤差(estimation error)會增大,預測結果會對近鄰的實例點非常敏感。如果鄰近的實例點恰巧是噪聲,預測就會出錯。換句話說,k 值的減小就意味著整體模型變得復雜,容易發生過擬合。
- 如果選擇較大的 k 值,就相當於用較大的鄰域中的訓練實例進行預測。其優點是可以減少學習的估計誤差。但缺點是學習的近似誤差會增大。這時與輸入實例較遠的(不相似的)訓練實例也會對預測起作用,使預測發生錯誤。 k 值的增大就意味著整體的模型變得簡單。
- 近似誤差和估計誤差,請看這裏:https://www.zhihu.com/question/60793482
-
距離度量
- 特征空間中兩個實例點的距離是兩個實例點相似程度的反映。
- k 近鄰模型的特征空間一般是 n 維實數向量空間 。使用的距離是歐氏距離,但也可以是其他距離,如更一般的 距離,或者 Minkowski 距離。
-
分類決策規則
- k 近鄰算法中的分類決策規則往往是多數表決,即由輸入實例的 k 個鄰近的訓練實例中的多數類決定輸入實例的類。
機器學習之k-近鄰算法實踐學習