Python3 機器學習實戰自我講解(二) K-近鄰法-海倫約會-手寫字型識別
第二章 k近鄰法
2.1 概念
2.1.1 k近鄰法簡介
k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。它的工作原理是:存在一個樣本資料集合,也稱作為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一個數據與所屬分類的對應關係。輸入沒有標籤的新資料後,將新的資料的每個特徵與樣本集中資料對應的特徵進行比較,然後演算法提取樣本最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料,這就是k-近鄰演算法中k的出處,通常k是不大於20的整數。最後,選擇k個最相似資料中出現次數最多的分類,作為新資料的分類。
K近鄰演算法
優 點 :精度高、對異常值不敏感、無資料輸入假定。
缺點:計算複雜度高、空間複雜度高。
適用資料範圍:數值型和標稱型
舉個例子,我們來使用k近鄰法來分類愛情片和動作片。有人曾統計過很多電影的打鬥鏡頭和接吻鏡頭,那麼如何確定一部沒看過的電影是愛情片還是動作片呢?我們可以使用k-nn來解決這個問題。
電影名稱 | 打鬥鏡頭 | 接吻鏡頭 | 電影型別 |
---|---|---|---|
California Man | 3 | 104 | 愛情片 |
He’s Not Really into Dudes | 2 | 100 | 愛情片 |
Robo Slayer 3000 | 99 | 5 | 動作片 |
Amped ll | 98 | 2 | 動作片 |
? | 18 | 90 | 未知 |
每部電影的打鬥鏡頭數、接吻鏡頭數以及電影評估型別
現在我們如何判斷該電影的型別呢?當然是通過最近鄰居來判斷啦~如何判斷鄰居的距離呢,首先我們需要定義距離。
2.1.2 距離度量
我們一般運用歐氏距離:
這樣我們可以得到
電影名稱 | 與未知電影的距離 |
---|---|
California Man | 20.5 |
He’s Not Really into Dudes | 18.7 |
Robo Slayer 3000 | 117.4 |
Amped ll | 118.9 |
由此可知最近的鄰居為《California Man》,所以我們未知電影的型別可以判定為愛情片。
k-近鄰演算法的一般流程:
(1)收集資料:可以使用任何方法。
(2)準備資料:距離計算所需要的數值,最好是結構化的資料格式。
(3)分析資料:可以使用任何方法。
(4)訓練演算法:此步驟不適用於k近鄰演算法。
(5)測試演算法:計算錯誤率。
(6)使用演算法:首先需要輸入樣本資料和結構化的輸出結果,然後執行k -近鄰演算法判定輸
入資料分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。
2.1.3 實戰環節
python3實現:
# -*- coding: UTF-8 -*-
import numpy as np
#資料集,標籤
dataSet=np.array([[3,104],[2,100],[99,5],[98,2]])
labels=['愛情片','愛情片','動作片','動作片']
def classy(inx,dataSet,labels):
diffMat=inx-dataSet
distanceMat=np.sqrt(np.sum(diffMat**2,axis=1))
sorted_distanceMat=np.argsort(distanceMat)
classylabel=labels[sorted_distanceMat[0]]
return classylabel
label=classy([18,90],dataSet,labels)
print("電影型別為:{0:s}".format(label))
可以看到,分類結果根據我們的”經驗”,是正確的
接下來我們來挑戰更有難度的。
def classify0(inX, dataSet, labels, k):
diffMat = inX- dataSet #python的廣播機制
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**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) #python2 使用classCount.iteritems()
return sortedClassCount[0][0] #選擇k個距離最小的中票數最多的
2.2 海倫約會
2.2.1 k-近鄰演算法實戰之約會網站配對效果判定
上一小結學習了簡單的k-近鄰演算法的實現方法,但是這並不是完整的k-近鄰演算法流程,k-近鄰演算法的一般流程:
- 收集資料:可以使用爬蟲進行資料的收集,也可以使用第三方提供的免費或收費的資料。一般來講,資料放在txt文字檔案中,按照一定的格式進行儲存,便於解析及處理。
- 準備資料:使用Python解析、預處理資料。
- 分析資料:可以使用很多方法對資料進行分析,例如使用Matplotlib將資料視覺化。
- 測試演算法:計算錯誤率。
- 使用演算法:錯誤率在可接受範圍內,就可以執行k-近鄰演算法進行分類。
已經瞭解了k-近鄰演算法的一般流程,下面開始進入實戰內容。
2.2.2 實戰背景
海倫女士一直使用線上約會網站尋找適合自己的約會物件。儘管約會網站會推薦不同的任選,但她並不是喜歡每一個人。經過一番總結,她發現自己交往過的人可以進行如下分類:
- 不喜歡的人
- 魅力一般的人
- 極具魅力的人
海倫收集約會資料已經有了一段時間,她把這些資料存放在文字檔案datingTestSet.txt中,每個樣本資料佔據一行,總共有1000行。
資料集下方下載:
海倫收集的樣本資料主要包含以下3種特徵:
- 每年獲得的飛行常客里程數
- 玩視訊遊戲所消耗時間百分比
- 每週消費的冰淇淋公升數
開啟txt文字檔案,資料格式如圖2.1所示。
2.2.3 準備資料:從文字檔案中解析資料
於是我們要寫一個函式來將文字檔案裡的資訊轉化為矩陣資訊,話不多說動手!!
#得到檔案行數,建立矩陣,建立標籤向量
def file2matrix(filename):
f = open(filename)
arrayOfLines=f.readlines()
numberOfLines = len(arrayOfLines) #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 arrayOfLines:
listFromLine = line.split('\t')
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
我們檢查一下資料內容,沒有問題。
2.2.4 歸一化特徵
如果我們不歸一化特徵那麼就會使結果偏向數值較大的特徵,顯然是不正確的。
樣本 | 玩遊戲所耗時間百分比 | 每年獲得的飛行常用里程數 | 每週消費的冰淇淋公升數 | 樣本分類 |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 0.1 | 2 |
我們很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大,也就是說,每年獲取的飛行常客里程數對於計算結果的影響將遠遠大於表2.1中其他兩個特徵-玩視訊遊戲所耗時間佔比和每週消費冰淇淋公斤數的影響。而產生這種現象的唯一原因,僅僅是因為飛行常客里程數遠大於其他特徵值。但海倫認為這三種特徵是同等重要的,因此作為三個等權重的特徵之一,飛行常客里程數並不應該如此嚴重地影響到計算結果。
在處理這種不同取值範圍的特徵值時,我們通常採用的方法是將數值歸一化,如將取值範圍處理為0到1或者-1到1之間。下面的公式可以將任意取值範圍的特徵值轉化為0到1區間內的值
歸一化特徵的公式如下:
def autoNorm(dataSet):
minValues=dataSet.min(axis=0) #按行求最小值
maxValues=dataSet.max(axis=0) #按行求最大值
ranges=maxValues-minValues
normDataSet=(dataSet-minValues)/ranges #python的廣播機制
return normDataSet,ranges,minValues
dataMat,labels=file2matrix("datingTestSet2.txt")
檢查一下,沒有問題,都在0-1之間。
2.2.5 分類器針對約會網站的測試程式碼
def datingClassTest():
hoRatio=0.1 #留百分之10作為測試資料
datingDataMat,datingLabels=file2matrix("datingTestSet2.txt")
normMat,ranges,minValues=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("預測結果:{0},實際結果:{1}".format(classifierResult,datingLabels[i]))
if(classifierResult!=datingLabels[i]):
errorCount+=1.0
print("正確率為:{0:f}".format(1.0-errorCount/float(numTestVecs)))
正確率高達百分之95!
2.2.6 最後一步使用演算法
def classifyPerson():
resultList=['不感興趣','有那麼一丟丟意思','很感興趣']
percentTats=float(input("每天多少小時玩遊戲?")) #如果是python2請使用raw_input
ffMiles=float(input("每年飛多少公里數?"))
iceCream=float(input("每年吃多少公升冰激凌?"))
datingDataMat,datingLabels=file2matrix('datingTestSet2.txt')
normMat,ranges,minValues=autoNorm(datingDataMat)
inArr=array([percentTats,ffMiles,iceCream])
classifierResult=classify0(inArr,datingDataMat,datingLabels,3)
print("海倫對他:{}".format(resultList[classifierResult-1]))
2.3 手寫體識別實戰
思考
機器學習六步走:
- 收集資料:下載
- 準備資料:編寫函式classify0()
- 分析資料:檢視是否有缺漏
- 訓練演算法:KNN不需要
- 測試演算法:提取部分作為測試樣本
- 使用演算法:有興趣有空可以嘗試
2.3.1 準備資料:將影象轉化為向量
下載資料
目錄trainingDigits中包含了大約2000個例子,每個例子如下圖所示,每個數字大約有200個樣本,目錄testDigits中包含大約900個測試資料。我們使用目錄trainingDigits中的資料訓練分類器,使用目錄testDigits中的資料測試分類器效果。
首先我們要將影象格式化處理為一個向量。程式碼如下:
def img2vector(filename):
""" 將影象轉為向量
影象32*32轉為向量1*1024
輸入檔名
輸出向量 """
returnMat=zeros((1,1024))
f=open(filename)
for i in range(32):
line_str=f.readline()
for j in range(32):
returnMat[0,32*i+j]=int(line_str[i])
return returnMat
有了特徵向量後,我們就能夠進行預測啦!
類似之前的約會預測,話不多說直接上程式碼:
正確率高達98.9%
2.4 總結
KNN演算法
給一個訓練資料集和一個新的例項,在訓練資料集中找出與這個新例項最近的k個訓練例項,然後統計最近的k個訓練例項中所屬類別計數最多的那個類,就是新例項的類
三要素:
- k值的選擇
- 距離的度量(常見的距離度量有歐式距離,馬氏距離等)
- 分類決策規則 (多數表決規則)
k值的選擇
- k值越小表明模型越複雜,更加容易過擬合
- 但是k值越大,模型越簡單,如果k=N的時候就表明無論什麼點都是訓練集中類別最多的那個類
所以一般k會取一個較小的值,然後用過交叉驗證來確定
這裡所謂的交叉驗證就是將樣本劃分一部分出來為預測樣本,比如95%訓練,5%預測,然後k分別取1,2,3,4,5之類的,進行預測,計算最後的分類誤差,選擇誤差最小的k
KNN的迴歸
在找到最近的k個例項之後,可以計算這k個例項的平均值作為預測值。或者還可以給這k個例項新增一個權重再求平均值,這個權重與度量距離成反比(越近權重越大)。
優缺點:
KNN演算法的優點:
- 思想簡單,理論成熟,既可以用來做分類也可以用來做迴歸;
- 可用於非線性分類;
- 訓練時間複雜度為O(n);
- 準確度高,對資料沒有假設,對outlier不敏感;
缺點:
- 計算量大;
- 樣本不平衡問題(即有些類別的樣本數量很多,而其它樣本的數量很少);
- 需要大量的記憶體;
有空再補充。。。