一、算法原理
算法原理是什么?允許我不嚴謹的說一下:首先有一堆有標簽的樣本,比如有一堆各種各樣的鳥(樣本集),我知道各種鳥的不同外貌(特征),比如羽毛顏色、有無腳蹼、身體重量、身體長度以及最重要的它屬于哪一種鳥(類別/標簽);然后給我一只不是這堆鳥中的一只鳥(測試樣本),讓我觀察了它的羽毛顏色等后,讓我說出它屬于哪一種鳥?我的做法是:遍歷之前的一堆鳥,分別比較每一只鳥的羽毛顏色、身體重量等特征與給定鳥的相應特征,并給出這兩只鳥的相似度。最終,從那一堆鳥中找出相似度最大的前k只,然后統計這k只鳥的分類,最后把分類數量最多的那只鳥的類別作為給定鳥的類別。雖然結果不一定準確,但是是有理論支持的,那就是概率論,哈哈。
下面來看一下書上對這個算法的原理介紹:存在一個訓練樣本集,并且每個樣本都存在標簽(有監督學習)。輸入沒有標簽的新樣本數據后,將新數據的每個特征與樣本集中數據對應的特征進行比較,然后算法提取出與樣本集中特征最相似的數據(最近鄰)的分類標簽。一般來說,我們只選擇樣本數據集中前k個最相似的數據,這就是k-近鄰算法中k的出處,而且k通常不大于20。最后選擇k個最相似數據中出現次數最多的分類,作為新數據的分類。
二、如何解決問題
沒接觸過的同學應該能懂了吧。書中的舉例是對電影的題材進行分類:愛情片or動作片。依據電影中打斗鏡頭和接吻鏡頭的數量。下面來看一下如何用kNN來解決這個問題。
要解決給定一部電影,判斷其屬于哪一種電影這個問題,就需要知道這個未知電影存在多少個打斗鏡頭和接吻鏡頭,如上圖所示,問號位置所代表的兩種鏡頭次數分別是多少?
下面我們來看一下圖中電影的特征值,如下表:
相信看過數據以后,即使不知道未知電影(?)屬于哪一種類型,但是可以通過某個計算方法計算出來。
第一步:首先計算未知電影與已知電影的相似度(抽象距離--相似度越小,距離越遠)。具體如何計算暫且不考慮。下面看一下相似度列表:
第二步:再將相似度列表排序,選出前k個最相似的樣本。此處我們假設k=3,將上表中的相似度進行排序后前3分別是:He’s Not Really into Dudes,Beautiful Woman,California Man。
第三步:統計最相似樣本的分類。此處很容易知道這3個樣本均為愛情片。
第四步:將分類最多的類別作為未知電影的分類。那么我們就得出結論,未知電影屬于愛情片。
下面貼一下書上總結的k近鄰算法的一般流程:
#coding=UTF8
from numpy import *
import operator
def createDataSet():
"""
函數作用:構建一組訓練數據(訓練樣本),共4個樣本
同時給出了這4個樣本的標簽,及labels
"""
group = array([
[1.0, 1.1],
[1.0, 1.0],
[0. , 0. ],
[0. , 0.1]
])
labels = ['A', 'A', 'B', 'B']
return group, labels
def classify0(inX, dataset, labels, k):
"""
inX 是輸入的測試樣本,是一個[x, y]樣式的
dataset 是訓練樣本集
labels 是訓練樣本標簽
k 是top k最相近的
"""
# shape返回矩陣的[行數,列數],
# 那么shape[0]獲取數據集的行數,
# 行數就是樣本的數量
dataSetSize = dataset.shape[0]
"""
下面的求距離過程就是按照歐氏距離的公式計算的。
即 根號(x^2+y^2)
"""
# tile屬于numpy模塊下邊的函數
# tile(A, reps)返回一個shape=reps的矩陣,矩陣的每個元素是A
# 比如 A=[0,1,2] 那么,tile(A, 2)= [0, 1, 2, 0, 1, 2]
# tile(A,(2,2)) = [[0, 1, 2, 0, 1, 2],
# [0, 1, 2, 0, 1, 2]]
# tile(A,(2,1,2)) = [[[0, 1, 2, 0, 1, 2]],
# [[0, 1, 2, 0, 1, 2]]]
# 上邊那個結果的分開理解就是:
# 最外層是2個元素,即最外邊的[]中包含2個元素,類似于[C,D],而此處的C=D,因為是復制出來的
# 然后C包含1個元素,即C=[E],同理D=[E]
# 最后E包含2個元素,即E=[F,G],此處F=G,因為是復制出來的
# F就是A了,基礎元素
# 綜合起來就是(2,1,2)= [C, C] = [[E], [E]] = [[[F, F]], [[F, F]]] = [[[A, A]], [[A, A]]]
# 這個地方就是為了把輸入的測試樣本擴展為和dataset的shape一樣,然后就可以直接做矩陣減法了。
# 比如,dataset有4個樣本,就是4*2的矩陣,輸入測試樣本肯定是一個了,就是1*2,為了計算輸入樣本與訓練樣本的距離
# 那么,需要對這個數據進行作差。這是一次比較,因為訓練樣本有n個,那么就要進行n次比較;
# 為了方便計算,把輸入樣本復制n次,然后直接與訓練樣本作矩陣差運算,就可以一次性比較了n個樣本。
# 比如inX = [0,1],dataset就用函數返回的結果,那么
# tile(inX, (4,1))= [[ 0.0, 1.0],
# [ 0.0, 1.0],
# [ 0.0, 1.0],
# [ 0.0, 1.0]]
# 作差之后
# diffMat = [[-1.0,-0.1],
# [-1.0, 0.0],
# [ 0.0, 1.0],
# [ 0.0, 0.9]]
diffMat = tile(inX, (dataSetSize, 1)) - dataset
# diffMat就是輸入樣本與每個訓練樣本的差值,然后對其每個x和y的差值進行平方運算。
# diffMat是一個矩陣,矩陣**2表示對矩陣中的每個元素進行**2操作,即平方。
# sqDiffMat = [[1.0, 0.01],
# [1.0, 0.0 ],
# [0.0, 1.0 ],
# [0.0, 0.81]]
sqDiffMat = diffMat ** 2
# axis=1表示按照橫軸,sum表示累加,即按照行進行累加。
# sqDistance = [[1.01],
# [1.0 ],
# [1.0 ],
# [0.81]]
sqDistance = sqDiffMat.sum(axis=1)
# 對平方和進行開根號
distance = sqDistance ** 0.5
# 按照升序進行快速排序,返回的是原數組的下標。
# 比如,x = [30, 10, 20, 40]
# 升序排序后應該是[10,20,30,40],他們的原下標是[1,2,0,3]
# 那么,numpy.argsort(x) = [1, 2, 0, 3]
sortedDistIndicies = distance.argsort()
# 存放最終的分類結果及相應的結果投票數
classCount = {}
# 投票過程,就是統計前k個最近的樣本所屬類別包含的樣本個數
for i in range(k):
# index = sortedDistIndicies[i]是第i個最相近的樣本下標
# voteIlabel = labels[index]是樣本index對應的分類結果('A' or 'B')
voteIlabel = labels[sortedDistIndicies[i]]
# classCount.get(voteIlabel, 0)返回voteIlabel的值,如果不存在,則返回0
# 然后將票數增1
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 把分類結果進行排序,然后返回得票數最多的分類結果
# classCount.items():迭代器,獲得每一個對象
# operator.itemgetter(1):表示獲取函數一維數據進行排序,即按照其投票數進行排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]