
機器學(xué)習(xí)入門:K-近鄰算法
先來一個簡單的例子,我們?nèi)绾蝸韰^(qū)分動作類電影與愛情類電影呢?動作片中存在很多的打斗鏡頭,愛情片中可能更多的是親吻鏡頭,所以我們姑且通過這兩種鏡頭的數(shù)量來預(yù)測這部電影的主題。簡單的說, k-近鄰算法 采用了測量不同特征值之間的距離方法進行分類。
優(yōu)點:精度高、對異常值不敏感、無數(shù)據(jù)輸入假定 缺點:計算復(fù)雜度高、控件復(fù)雜度高 適用數(shù)據(jù)范圍:數(shù)值型和標(biāo)稱型
首先我們來理解它的工作原理:
存在一個樣本數(shù)據(jù)集(訓(xùn)練集),并且我們知道每一數(shù)據(jù)與目標(biāo)變量的對應(yīng)關(guān)系,輸入沒有標(biāo)簽的新數(shù)據(jù)后,將新數(shù)據(jù)的每個特征與樣本集中數(shù)據(jù)對應(yīng)的特征進行比較,然后算法提取樣本集中最相近的分類標(biāo)簽,一般來說,我們只選擇樣本集中前k個最相似的數(shù)據(jù),通常k為不大于20的整數(shù),最后,選擇k個最相似數(shù)據(jù)中出現(xiàn)次數(shù)最多的分類,作為新數(shù)據(jù)的分類。
現(xiàn)在我們回到文章開頭所提到的電影的例子,根據(jù)下表如何確定未知的電影類型呢?
電影名稱 打斗鏡頭 接吻鏡頭 電影類型
電影1 3 104 愛情
電影2 2 100 愛情
電影3 1 81 愛情
電影4 101 10 動作
電影5 99 5 動作
電影6 98 2 動作
電影7 18 90 未知?
該如何計算電影7的電影類型呢?計算電影7與樣本集中其他電影的距離,然后我們假定k=3,看一下最近的3部電影是什么類型即可預(yù)測出電影7的電影類型。
流程介紹
收集數(shù)據(jù)
準(zhǔn)備數(shù)據(jù):距離計算所需要的值,最好是結(jié)構(gòu)化的數(shù)據(jù)格式
分析數(shù)據(jù)
測試算法:計算錯誤率
使用算法
開始工作
使用python導(dǎo)入數(shù)據(jù)
首先,創(chuàng)建名為kNN.py的Python模塊,在構(gòu)造算法之前,我們還需要編寫一些通用的函數(shù)等,我們先寫入一些簡單的代碼:
from numpy import *
import operator
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
然后將終端改變到代碼文件目錄,輸入命令python進入到交互界面:
>>> import kNN
>>> group, labels = kNN.createDataSet()
>>> group
array([[ 1. , 1.1],
[ 1. , 1. ],
[ 0. , 0. ],
[ 0. , 0.1]])
>>> labels
['A', 'A', 'B', 'B']
這里有4組數(shù)據(jù),每組數(shù)據(jù)有2個我們已知的特征值,上面的group矩陣每行為一條數(shù)據(jù),對于每個數(shù)據(jù)點我們通常使用2個特征(所以特征的選擇很重要),向量labels包含了每個數(shù)據(jù)點的標(biāo)簽信息,labels的維度等于矩陣的行數(shù),這里我們將 [1, 1,1] 定為類A, [0, 0.1] 定為類B,接下來我們進行算法的編寫:
計算已知數(shù)據(jù)集中點到當(dāng)前點之間的距離
按照距離遞增次序排序
選取與當(dāng)前點距離最小的k個點
確定前k個點所在類別的出現(xiàn)頻率
返回前k個點中頻次最高的類別作為預(yù)測類別
接著定義函數(shù):
# inX: 用于分類的輸入向量
# dataSet:輸入的訓(xùn)練集
# labels:標(biāo)簽向量
# k:選擇近鄰項目的個數(shù)
def classify0(inX, dataSet, labels, k) :
dataSetSize = dataSet.shape[0]
# 距離計算
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2 # python中, **2 代表2平方,**0.5代表開方
sqDistances = sqDiffMat.sum(axis=1) # 加入axis=1以后就是將一個矩陣的每一行向量相加
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort()
classCount = {}
# 選擇距離最小的k個點
for i in range(k) :
voteILabel = labels[sortedDistIndicies[i]]
classCount[voteILabel] = classCount.get(voteILabel, 0) + 1
# 排序
sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
然后我們進行測試,重新打開python編譯環(huán)境:
>>> import kNN
>>> group, labels = kNN.createDataSet()
>>> kNN.classify0([0, 0], group, labels, 3)
'B'
>>> kNN.classify0([0.3, 0], group, labels, 3)
'B'
>>> kNN.classify0([0.8, 0.9], group, labels, 3)
'A'
我們看到,一個簡單的分類器就這樣搞定了。這時,我們來將電影數(shù)據(jù)進行樣本寫入:
def createDataSet():
group = array([
[3, 104],
[2, 100],
[1, 81],
[101, 10],
[99, 5],
[98, 2]
])
labels = ["love", "love", "love", "action", "action", "action"]
return group, labels
>>> import kNN
>>> group, labels = kNN.createDataSet()
>>> kNN.classify0([18, 90], group, labels, 3)
'love'
我們看到預(yù)測結(jié)果為愛情片。這是一個簡單的分類器,當(dāng)然,我們應(yīng)該通過大量的測試,看預(yù)測結(jié)果與預(yù)期結(jié)果是否相同,進而得出錯誤率,完美的分類器錯誤率為0,最差的錯誤率為1,上邊電影分類的例子已經(jīng)可以使用了,但是貌似沒有太大的作用,下邊我們來看一個生活中的實例,使用k-近鄰算法改進約會網(wǎng)站的效果,然后使用k-近鄰算法改進手寫識別系統(tǒng)。
改進約會網(wǎng)站的配對效果
有個姑娘,一直在使用某交友網(wǎng)站,但是推薦來的人總是不盡人意,她總結(jié)了一番,曾交往過3中類型的人:
不喜歡的人
魅力一般的人
極具魅力的人
她覺得自己可以在周一~周五約會那些魅力一般的人,周末與那些極具魅力的人約會,因為她希望我們可以更好的幫助她將匹配的對象劃分到確切的分類中,她還收集了一些約會網(wǎng)站未曾記錄過的數(shù)據(jù)信息,她認為這些數(shù)據(jù)信息會有幫助。這些數(shù)據(jù)存放在代碼文件夾下 datingTestSet2.txt 中,總共有1000行數(shù)據(jù)(妹的約了1000個人),主要包含以下特征:
每年獲得的飛行??屠锍虜?shù)
玩視頻游戲所消耗的時間百分比
每周消費的冰激凌公升數(shù)
我們看到,統(tǒng)計的東西都比較奇葩,我們先從文件中把這些數(shù)值解析出來,輸出訓(xùn)練樣本矩陣和分類標(biāo)簽向量。在kNN.py中繼續(xù)書寫代碼:
從文本中讀入數(shù)據(jù)
def file2matrix(filename) :
fr = open(filename)
arrayOfLines = fr.readlines()
numberOfLines = len(arrayOfLines) # 得到文件行數(shù)
returnMat = zeros((numberOfLines, 3)) # 創(chuàng)建用0填充的矩陣,這里為了簡化處理,我們將矩陣的另一個緯度設(shè)置為3,可以按照自己的需求增加數(shù)量。
classLabelVector = []
index = 0
# 解析文件
for line in arrayOfLines :
line = line.strip() # 截取掉所有的回車字符
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0: 3] # range
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
>>> import kNN
>>> mat, vector = kNN.file2matrix('datingTestSet2.txt')
>>> mat, vector = kNN.file2matrix('datingTestSet2.txt')
>>> mat
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],
...,
[ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01],
[ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01],
[ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]])
>>> vec
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'vec' is not defined
>>> vector
[3 ... 2]
現(xiàn)在我們已經(jīng)從文文件中導(dǎo)入了數(shù)據(jù),并將其格式化為想要的格式,接著我們需要了解數(shù)據(jù)的真是含義,我們可以直觀的瀏覽文本文件,但是這種方法非常不友好,一般來說,我們會采用圖形化的方式直觀的展示數(shù)據(jù)。下面我們就用Python圖形化工具來圖形化展示數(shù)據(jù)內(nèi)容,以便辨識出一些數(shù)據(jù)模式。
分析數(shù)據(jù),使用 Matplotlib 創(chuàng)建散點圖
pip install matplotlib
接下來打開python命令行,我們對剛才讀入的內(nèi)容進行測試的展示
>>> from matplotlib import *
>>> import matplotlib.pyplot as plt
>>> import kNN
>>> import numpy as np
>>> mat, vec = kNN.file2matrix('datingTestSet2.txt')
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(mat[:, 1], mat[:, 2], 15.0*np.array(vec), 15.0*np.array(vec))
<matplotlib.collections.PathCollection object at 0x1087cf0d0>
>>> plt.show()
這個時候,我們展示的是數(shù)據(jù)集的第一列與第二列所繪制的圖,這里我們很難看出來什么端倪,所以我們嘗試使用第一列和第二列作為特征來繪圖,重新書寫上邊代碼:
ax.scatter(mat[:, 0], mat[:, 1], 15.0*np.array(vec), 15.0*np.array(vec))
然后我們得到了以下數(shù)據(jù)圖:
這次,我們可以看到,圖中清晰的標(biāo)識了3個不同的樣本分類區(qū)域。
準(zhǔn)備數(shù)據(jù),歸一化數(shù)值
我們隨便的抽取了4組差異比較大的數(shù)據(jù)
玩游戲所消耗時間 里程數(shù) 冰激凌公升數(shù) 樣本分類
1 0.8 400 0.5 1
2 12 13400 0.9 3
3 0 20000 1.1 2
4 67 32000 0.1 2
我們很容易發(fā)現(xiàn),如果我們計算樣本3和樣本4之間的距離,可以使用下邊的方法
$\sqrt{(0-67)^2 + (20000 + 32000)^2 + (1.1-0.1)^2}$
但是這些大的值堆結(jié)果的影響比較大,因此,作為比較重要的特征屬性,不應(yīng)該如此的影響計算結(jié)果,所以在處理數(shù)據(jù)的時候,我們對數(shù)據(jù)進行歸一化處理,將取值的范圍處理到0 - 1或者-1 ~ -1之間,下面的公事,可以將任意范圍內(nèi)的特征值轉(zhuǎn)換為0-1區(qū)間內(nèi)的值:
$newValue = (oldValue - min)/(max - min)$
其中,min和max分別為特征值中最大和最小值,雖然改變數(shù)值范圍增加了分類器的復(fù)雜度,但是為了得到準(zhǔn)確的結(jié)果,必須這樣做,所以我們在kNN.py中新增一個函數(shù) autoNorm() ,用來將數(shù)字特征值轉(zhuǎn)化為0-1的區(qū)間:
def autoNorm(dataSet) :
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))
return normDataSet, ranges, minvals
運行結(jié)果:
>>> import kNN
>>> mat, vec = kNN.file2matrix('datingTestSet2.txt')
>>> a, b, c = kNN.autoNorm(mat)
>>> a
array([[ 0.44832535, 0.39805139, 0.56233353],
[ 0.15873259, 0.34195467, 0.98724416],
[ 0.28542943, 0.06892523, 0.47449629],
...,
[ 0.29115949, 0.50910294, 0.51079493],
[ 0.52711097, 0.43665451, 0.4290048 ],
[ 0.47940793, 0.3768091 , 0.78571804]])
這樣一來,我們把值處理成了我們預(yù)期的范偉內(nèi)的值。
測試算法
通常我們把數(shù)據(jù)集的90%的數(shù)據(jù)當(dāng)做訓(xùn)練集,余下的10%作為測試集,著10%的數(shù)據(jù)是隨機選擇的。 下面,我們來書寫測試程序,并通過 datingTestSet.txt 來測試程序:
def datingClassTest() :
hoRatio = 0.10 # 設(shè)置抽取多少數(shù)據(jù)進行測試集
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # 讀入數(shù)據(jù)集
normMat, ranges, minVals = autoNorm(datingDataMat) # 轉(zhuǎn)化特征值至 0 - 1 區(qū)間內(nèi)
m = normMat.shape[0]
numTestVecs = int( m * hoRatio ) # 計算測試向量的數(shù)量
errorCount = 0.0
for i in range(numTestVecs) :
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs: m], 3) # 使用近鄰算法得出結(jié)果
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))
然后我們在python環(huán)境中通過
reload(kNN)
來重新加載kNN.py模塊,然后調(diào)用
kNN.datingClassTest()
得到結(jié)果:
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: 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
所以我們看到,數(shù)據(jù)集的錯誤率是5%,這里會有一定的偏差,因為我們隨機選取的數(shù)據(jù)可能會不同。
使用算法
我們使用上面建立好的分類器構(gòu)建一個可用的系統(tǒng),通過輸入這些特征值幫她預(yù)測喜歡程度。我們來編寫代碼:
def classifyPerson() :
resultList = ['not', 'small doses', 'large does']
percentTats = float(raw_input("percent of time spent on video games?"))
miles = float(raw_input("flier miles per year?"))
ice = float(raw_input("liters of ice-cream?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([miles, percentTats, ice])
classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
print "you will like this person: ", resultList[classifierResult - 1]
這里的代碼大家比較熟悉了,就是加入了raw_input用于輸入,我們來看結(jié)果:
>>> reload(kNN)
<module 'kNN' from 'kNN.py'>
>>> kNN.classifyPerson()
percent of time spent on video games?10
flier miles per year?10000
liters of ice-cream?0.5
you will like this person: small doses
我們在做近鄰算法的時候也發(fā)現(xiàn),并沒有做 訓(xùn)練算法 這一環(huán)節(jié),因為我們不需要訓(xùn)練,直接計算就好了。
同時我們也發(fā)現(xiàn)一個問題,k-近鄰算法特別慢,它需要對每一個向量進行距離的計算,這該怎么優(yōu)化呢?
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
LSTM 模型輸入長度選擇技巧:提升序列建模效能的關(guān)鍵? 在循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)家族中,長短期記憶網(wǎng)絡(luò)(LSTM)憑借其解決長序列 ...
2025-07-11CDA 數(shù)據(jù)分析師報考條件詳解與準(zhǔn)備指南? ? 在數(shù)據(jù)驅(qū)動決策的時代浪潮下,CDA 數(shù)據(jù)分析師認證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計的實用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實施重大更新。 此次更新旨在確保認 ...
2025-07-10BI 大數(shù)據(jù)分析師:連接數(shù)據(jù)與業(yè)務(wù)的價值轉(zhuǎn)化者? ? 在大數(shù)據(jù)與商業(yè)智能(Business Intelligence,簡稱 BI)深度融合的時代,BI ...
2025-07-10SQL 在預(yù)測分析中的應(yīng)用:從數(shù)據(jù)查詢到趨勢預(yù)判? ? 在數(shù)據(jù)驅(qū)動決策的時代,預(yù)測分析作為挖掘數(shù)據(jù)潛在價值的核心手段,正被廣泛 ...
2025-07-10數(shù)據(jù)查詢結(jié)束后:分析師的收尾工作與價值深化? ? 在數(shù)據(jù)分析的全流程中,“query end”(查詢結(jié)束)并非工作的終點,而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報考到取證的全攻略? 在數(shù)字經(jīng)濟蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢性檢驗:捕捉數(shù)據(jù)背后的時間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢性檢驗如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時間維度的精準(zhǔn)切片? ? 在數(shù)據(jù)的世界里,時間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準(zhǔn) ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應(yīng)用與實戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗:數(shù)據(jù)趨勢與突變分析的有力工具? ? ? 在數(shù)據(jù)分析的廣袤領(lǐng)域中,準(zhǔn)確捕捉數(shù)據(jù)的趨勢變化以及識別 ...
2025-07-08備戰(zhàn) CDA 數(shù)據(jù)分析師考試:需要多久?如何規(guī)劃? CDA(Certified Data Analyst)數(shù)據(jù)分析師認證作為國內(nèi)權(quán)威的數(shù)據(jù)分析能力認證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應(yīng)對策略? 長短期記憶網(wǎng)絡(luò)(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)的一種變體,憑借獨特的門控機制,在 ...
2025-07-07統(tǒng)計學(xué)方法在市場調(diào)研數(shù)據(jù)中的深度應(yīng)用? 市場調(diào)研是企業(yè)洞察市場動態(tài)、了解消費者需求的重要途徑,而統(tǒng)計學(xué)方法則是市場調(diào)研數(shù) ...
2025-07-07CDA數(shù)據(jù)分析師證書考試全攻略? 在數(shù)字化浪潮席卷全球的當(dāng)下,數(shù)據(jù)已成為企業(yè)決策、行業(yè)發(fā)展的核心驅(qū)動力,數(shù)據(jù)分析師也因此成為 ...
2025-07-07剖析 CDA 數(shù)據(jù)分析師考試題型:解鎖高效備考與答題策略? CDA(Certified Data Analyst)數(shù)據(jù)分析師考試作為衡量數(shù)據(jù)專業(yè)能力的 ...
2025-07-04SQL Server 字符串截取轉(zhuǎn)日期:解鎖數(shù)據(jù)處理的關(guān)鍵技能? 在數(shù)據(jù)處理與分析工作中,數(shù)據(jù)格式的規(guī)范性是保證后續(xù)分析準(zhǔn)確性的基礎(chǔ) ...
2025-07-04CDA 數(shù)據(jù)分析師視角:從數(shù)據(jù)迷霧中探尋商業(yè)真相? 在數(shù)字化浪潮席卷全球的今天,數(shù)據(jù)已成為企業(yè)決策的核心驅(qū)動力,CDA(Certifie ...
2025-07-04CDA 數(shù)據(jù)分析師:開啟數(shù)據(jù)職業(yè)發(fā)展新征程? ? 在數(shù)據(jù)成為核心生產(chǎn)要素的今天,數(shù)據(jù)分析師的職業(yè)價值愈發(fā)凸顯。CDA(Certified D ...
2025-07-03