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