目錄
- 工作原理
- python實現
-
算法實戰
-
約會對象好感度預測
- 故事背景
- 準備數據:從文本文件中解析數據
- 分析數據:使用Matplotlib創建散點圖
- 準備數據:歸一化數值
- 測試算法:作為完整程序驗證分類器
- 使用算法:構建完整可用的系統
-
手寫識別系統
- 準備數據:將圖像轉換為測試向量
- 測試算法:使用k-近鄰算法識別手寫數字
-
約會對象好感度預測
- 小結
- 附錄
工作原理
存在一個樣本數據集合,也稱作訓練樣本集,并且樣本集中每個數據都存在標簽,即我們知道樣本集中每一數據與所屬分類的對應關系。輸入沒有標簽的新數據后,將新數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取樣本集中特征最相似數據(最近鄰)的分類特征。一般來說,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大于20的整數。最后選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。
舉個例子,現在我們用k-近鄰算法來分類一部電影,判斷它屬于愛情片還是動作片。現在已知六部電影的打斗鏡頭、接吻鏡頭以及電影評估類型,如下圖所示。
現在我們有一部電影,它有18個打斗鏡頭、90個接吻鏡頭,想知道這部電影屬于什么類型。根據k-近鄰算法,我們可以這么算。首先計算未知電影與樣本集中其他電影的距離(先不管這個距離如何算,后面會提到)。現在我們得到了樣本集中所有電影與未知電影的距離。按照距離遞增排序,可以找到k個距離最近的電影。
現在假定
k=3
,則三個最靠近的電影依次是
He's Not Really into Dudes
、
Beautiful Woman
、
California Man
。
k-近鄰算法按照距離最近的三部電影的類型,決定未知電影的類型,而這三部電影全是愛情片,因此我們判定未知電影是愛情片。
python實現
首先編寫一個用于創建數據集和標簽的函數,要注意的是該函數在實際用途上沒有多大意義,僅用于測試代碼。
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
然后是函數
classify0()
,該函數的功能是使用k-近鄰算法將每組數據劃分到某個類中,其偽代碼如下:
對未知類別屬性的數據集中的每個點依次執行以下操作:
(1)計算已知類別數據集中的點與當前點之間的距離;
(2)按照距離遞增次序排序;
(3)選取與當前點距離最小的k個點;
(4)確定前k個點所在類別的出現頻率;
(5)返回前k個點出現頻率最高的類別作為當前點的預測分類。
Python代碼如下:
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # shape[0]表示矩陣有多少行 shape[1]表示矩陣有多少列
diffMat = tile(inX, (dataSetSize,1)) - dataSet # 計算Ai-Bi
sqDiffMat = diffMat**2 #計算(Ai-Bi)^2
sqDistances = sqDiffMat.sum(axis=1) # 計算(A0-B0)^2+...+(Ai-Bi)^2
distances = sqDistances**0.5 # 計算((A0-B0)^2+...+(Ai-Bi)^2)^0.5 也就是歐式距離
sortedDistIndicies = distances.argsort() # 得到數組的值按遞增排序的索引
classCount = {}
for i in range (k): #距離最近的k個點
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0)+1 # 如果voteIlabels的key不存在就返回0
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
該函數具有4個輸入參數,分別是 待分類的輸入向量inX 、 輸入的訓練樣本集dataSet 、 標簽向量labels 、 選擇距離最近的k個點 。其中距離使用歐式距離,計算公式如下:
如果數據集存在4個特征值,則點(1,0,0,1)與(7,6,9,4)之間的歐式距離計算為:
計算完所有點之間的距離后,可以對數據按照從小到大的次序排序。然后,確定前k個距離最小元素所在的主要分類。輸入k總是正整數;最后,將classCount字典分解為元組列表,然后按照從大到小的次序進行排序,最后返回頻率最高的元素標簽。
運行程序后得到如下結果應該是
B
算法實戰
舉兩個例子,一個是約會對象的好感度預測,一個是手寫識別系統。
約會對象好感度預測
故事背景
海倫小姐是一個大齡單身女青年,她一直通過網絡尋找適合自己的另一半。盡管網上會遇到不一樣的約會對象,但是她并不是喜歡每一個人。經過一番總結,她發現她曾和三種類型的人約會過:
- [ ] 不喜歡的人
- [ ] 魅力一般的人
- [ ] 極具魅力的人
她還發現當她歸類約會對象時主要考慮以下三個特征:
- [ ] 月收入
- [ ] 顏值
- [ ] 每周跑步的公里數
她將這些數據保存在文本文件
datingTestSet2.txt
中。
準備數據:從文本文件中解析數據
首先要將待處理數據的格式改變為分類器可以接受的格式。創建名為
file2matrix()
的函數,以此來處理輸入格式問題。該函數的輸入為文件名字符串,輸出為訓練樣本矩陣和類標簽向量。
def file2matrix(filename):
fr = open(filename,encoding = 'utf-8')
arrayOfLines = fr.readlines() #讀取文件的每一行
numberOfLines = len(arrayOfLines) #獲得文件行數
returnMat = zeros((numberOfLines,3))
classLabelVector = []
index = 0
for line in arrayOfLines:
line = line.strip() #去除首尾空格和回車
listFromLine = line.split() #按照tab鍵分割數據
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat,classLabelVector
打開文件,得到文件的行數。然后創建以零填充的矩陣。循環處理文件中的每行數據,首先使用函數
line.strip()
截取掉所有的回車字符,然后使用tab字符\t將上一步得到的整行數據分割成一個元素列表。接著,選取前3個元素,將它們存到特征矩陣中。利用負索引將列表的最后一列存儲到向量
classLabelVector
中。
分析數據:使用Matplotlib創建散點圖
這一步不過多解釋,創建可視化數據圖。
def drawFig(datingDataMat,datingLabels):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2],15.0*array(datingLabels), 15.0*array(datingLabels))
plt.show()
準備數據:歸一化數值
因為月收入的數值和其他兩個特征相比大很多,因此對于計算距離的影響遠大于其他兩個特征。但是在海倫看來這是三個等權重的特征,月收入不應該如此嚴重地影響到計算結果。
因此我們需要進行數值歸一化。采用公式
newValue = (oldValue-min)/(max-min)
可以將任意取值范圍的特征值轉化為0到1的區間。其中min和max分別是數據集中最小特征值和最大特征值。
def autoNorm(dataSet):
minVals = dataSet.min(0) #參數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
測試算法:作為完整程序驗證分類器
在數據集中選取10%的數據作為測試數據。
def datingClassTest():
hoRatio = 0.10 # 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: %f" % (errorCount / float(numTestVecs)))
得到結果如下:
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
...
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 1
the total error rate is: 0.050000
錯誤率僅為5%左右,基本上可以正確的分類。
使用算法:構建完整可用的系統
def classifyPerson():
resultList = ["not at all", "in small doses", "in large doses"]
percentTats = float(input("monthly income?"))
ffMiles = float(input("level of appearance?"))
iceCream = float(input("running miles per month?"))
datingDataMat, datingLabels = file2matrix("datingTestSet2.txt") # load data setfrom file
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0(inArr, datingDataMat, datingLabels, 3)
print("You will probably like this person:",resultList[classifierResult-1])
海倫可以將她要約會的對象信息輸入程序,程序會給出她對對方的喜歡誠度的預測值。例如輸入一個月收入為20000、顏值為5、每周運動量為1公里的數據,得到的結果是:
monthly income?20000
level of appearance?5
running miles per month?1
You will probably like this person: in small doses
手寫識別系統
為了簡單起見,這里只識別數字0-9。數據集分為訓練集和測試集分別存放在兩個文件夾下。
準備數據:將圖像轉換為測試向量
和之前一個例子不一樣的地方在于數據的處理上。我們必須將圖像格式處理為一個向量。我們將32x32的二進制圖像矩陣轉換為1x1024的向量。
編寫函數
img2vector
,將圖像轉換為向量。
def img2vector(filename):
returnVector = zeros((1,1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVector[0,32*i+j] = int(lineStr[j])
return returnVector
測試算法:使用k-近鄰算法識別手寫數字
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir("trainingDigits")
mTrain = len(trainingFileList)
trainingMat = zeros((mTrain,1024))
for i in range(mTrain):
filenameStr = trainingFileList[i]
fileStr = filenameStr.split('.')[0]
classNum = int(fileStr.split('_')[0])
hwLabels.append(classNum)
trainingMat[i,:] = img2vector("trainingDigits/%s"%filenameStr)
testFileList = listdir("testDigits")
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
filenameStr = testFileList[i]
fileStr = filenameStr.split('.')[0]
classNum = int(fileStr.split('_')[0])
testVector = img2vector("testDigits/%s"%filenameStr)
classifierResult = classify0(testVector, trainingMat, hwLabels, 4)
print("the classifier came back with: %d, the real answer is: %d" %(classifierResult, classNum))
if(classifierResult != classNum):
errorCount += 1.0
print("\nthe total number of errors is: %d" % errorCount)
print("\nthe total error rate is: %f" % (errorCount / float(mTest)))
得到結果如下:
the classifier came back with: 0, the real answer is: 0
the classifier came back with: 0, the real answer is: 0
the classifier came back with: 0, the real answer is: 0
the classifier came back with: 0, the real answer is: 0
the classifier came back with: 0, the real answer is: 0
...
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the classifier came back with: 9, the real answer is: 9
the total number of errors is: 11
the total error rate is: 0.011628
小結
k-近鄰算法是分類數據最簡單最有效的算法。k-近鄰是基于實例的學習,使用算法時必須有大量接近實際數據的訓練樣本數據。k-近鄰算法必須保存全部數據集,如果訓練的數據集很大,必須使用大量的存儲空間。此外,由于必須對數據集中的每個數據計算距離值,實際使用時可能非常耗時。
附錄
文中代碼及數據集:https://github.com/Professorchen/Machine-Learning/tree/master/kNN
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
