
python機(jī)器學(xué)習(xí)案例教程—K最近鄰算法的實(shí)現(xiàn)
K最近鄰屬于一種分類算法,他的解釋最容易,近朱者赤,近墨者黑,我們想看一個人是什么樣的,看他的朋友是什么樣的就可以了。當(dāng)然其他還牽著到,看哪方面和朋友比較接近(對象特征),怎樣才算是跟朋友親近,一起吃飯還是一起逛街算是親近(距離函數(shù)),根據(jù)朋友的優(yōu)秀不優(yōu)秀如何評判目標(biāo)任務(wù)優(yōu)秀不優(yōu)秀(分類算法),是否不同優(yōu)秀程度的朋友和不同的接近程度要考慮一下(距離權(quán)重),看幾個朋友合適(k值),能否以分?jǐn)?shù)的形式表示優(yōu)秀度(概率分布)。
K最近鄰概念:
它的工作原理是:存在一個樣本數(shù)據(jù)集合,也稱作為訓(xùn)練樣本集,并且樣本集中每個數(shù)據(jù)都存在標(biāo)簽,即我們知道樣本集中每一個數(shù)據(jù)與所屬分類的對應(yīng)關(guān)系。輸入沒有標(biāo)簽的新數(shù)據(jù)后,將新的數(shù)據(jù)的每個特征與樣本集中數(shù)據(jù)對應(yīng)的特征進(jìn)行比較,然后算法提取樣本最相似數(shù)據(jù)(最近鄰)的分類標(biāo)簽。一般來說,我們只選擇樣本數(shù)據(jù)集中前k個最相似的數(shù)據(jù),這就是k-近鄰算法中k的出處,通常k是不大于20的整數(shù)。最后,選擇k個最相似數(shù)據(jù)中出現(xiàn)次數(shù)最多的分類,作為新數(shù)據(jù)的分類。
今天我們使用k最近鄰算法來構(gòu)建白酒的價格模型。
構(gòu)造數(shù)據(jù)集
構(gòu)建一個葡萄酒樣本數(shù)據(jù)集。白酒的價格跟等級、年代有很大的關(guān)系。
from random import random,randint
import math
# 根據(jù)等級和年代對價格進(jìn)行模擬
def wineprice(rating,age):
peak_age=rating-50
# 根據(jù)等級計算價格
price=rating/2
if age>peak_age:
# 經(jīng)過“峰值年”,后續(xù)5年里其品質(zhì)將會變差
price=price*(5-(age-peak_age)/2)
else:
# 價格在接近“峰值年”時會增加到原值的5倍
price=price*(5*((age+1)/peak_age))
if price<0: price=0
return price
# 生成一批模式數(shù)據(jù)代表樣本數(shù)據(jù)集
def wineset1():
rows=[]
for i in range(300):
# 隨機(jī)生成年代和等級
rating=random()*50+50
age=random()*50
# 得到一個參考價格
price=wineprice(rating,age)
# 添加一些噪音
price*=(random()*0.2+0.9)
# 加入數(shù)據(jù)集
rows.append({'input':(rating,age),'result':price})
return rows
數(shù)據(jù)間的距離
使用k最近鄰,首先要知道那些最近鄰,也就要求知道數(shù)據(jù)間的距離。我們使用歐幾里得距離作為數(shù)據(jù)間的距離。
# 使用歐幾里得距離,定義距離
def euclidean(v1,v2):
d=0.0
for i in range(len(v1)):
d+=(v1[i]-v2[i])**2
return math.sqrt(d)
獲取與新數(shù)據(jù)距離最近的k個樣本數(shù)據(jù)
# 計算給預(yù)測商品和原數(shù)據(jù)集中任一其他商品間的距離。data原數(shù)據(jù)集,vec1預(yù)測商品
def getdistances(data,vec1):
distancelist=[]
# 遍歷數(shù)據(jù)集中的每一項(xiàng)
for i in range(len(data)):
vec2=data[i]['input']
# 添加距離到距離列表
distancelist.append((euclidean(vec1,vec2),i))
# 距離排序
distancelist.sort()
return distancelist #返回距離列表
根據(jù)距離最近的k個樣本數(shù)據(jù)預(yù)測新數(shù)據(jù)的屬性
1、簡單求均值
# 對距離值最小的前k個結(jié)果求平均
def knnestimate(data,vec1,k=5):
# 得到經(jīng)過排序的距離值
dlist=getdistances(data,vec1)
avg=0.0
# 對前k項(xiàng)結(jié)果求平均
for i in range(k):
idx=dlist[i][1]
avg+=data[idx]['result']
avg=avg/k
return avg
2、求加權(quán)平均
如果使用直接求均值,有可能存在前k個最近鄰中,可能會存在距離很遠(yuǎn)的數(shù)據(jù),但是他仍然屬于最近的前K個數(shù)據(jù)。當(dāng)存在這種情況時,對前k個樣本數(shù)據(jù)直接求均值會有偏差,所以使用加權(quán)平均,為較遠(yuǎn)的節(jié)點(diǎn)賦予較小的權(quán)值,對較近的節(jié)點(diǎn)賦予較大的權(quán)值。
那么具體該怎么根據(jù)數(shù)據(jù)間距離分配權(quán)值呢?這里使用三種遞減函數(shù)作為權(quán)值分配方法。
2.1、使用反函數(shù)為近鄰分配權(quán)重。
def inverseweight(dist,num=1.0,const=0.1):
return num/(dist+const)
2.2、使用減法函數(shù)為近鄰分配權(quán)重。當(dāng)最近距離都大于const時不可用。
def subtractweight(dist,const=1.0):
if dist>const:
return 0
else:
return const-dist
2.3、使用高斯函數(shù)為距離分配權(quán)重。
def gaussian(dist,sigma=5.0):
return math.e**(-dist**2/(2*sigma**2))
有了權(quán)值分配方法,下面就可以計算加權(quán)平均了。
# 對距離值最小的前k個結(jié)果求加權(quán)平均
def weightedknn(data,vec1,k=5,weightf=gaussian):
# 得到距離值
dlist=getdistances(data,vec1)
avg=0.0
totalweight=0.0
# 得到加權(quán)平均
for i in range(k):
dist=dlist[i][0]
idx=dlist[i][1]
weight=weightf(dist)
avg+=weight*data[idx]['result']
totalweight+=weight
if totalweight==0: return 0
avg=avg/totalweight
return avg
第一次測試
上面完成了使用k最近鄰進(jìn)行新數(shù)據(jù)預(yù)測的功能,下面我們進(jìn)行測試。
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時才會運(yùn)行此函數(shù)
data = wineset1() #創(chuàng)建第一批數(shù)據(jù)集
result=knnestimate(data,(95.0,3.0)) #根據(jù)最近鄰直接求平均進(jìn)行預(yù)測
print(result)
result=weightedknn(data,(95.0,3.0),weightf=inverseweight) #使用反函數(shù)做權(quán)值分配方法,進(jìn)行加權(quán)平均
print(result)
result = weightedknn(data, (95.0, 3.0), weightf=subtractweight) # 使用減法函數(shù)做權(quán)值分配方法,進(jìn)行加權(quán)平均
print(result)
result = weightedknn(data, (95.0, 3.0), weightf=gaussian) # 使用高斯函數(shù)做權(quán)值分配方法,進(jìn)行加權(quán)平均
print(result)
交叉驗(yàn)證
交叉驗(yàn)證是用來驗(yàn)證你的算法或算法參數(shù)的好壞,比如上面的加權(quán)分配算法我們有三種方式,究竟哪個更好呢?我們可以使用交叉驗(yàn)證進(jìn)行查看。
隨機(jī)選擇樣本數(shù)據(jù)集中95%作為訓(xùn)練集,5%作為新數(shù)據(jù),對新數(shù)據(jù)進(jìn)行預(yù)測并與已知結(jié)果進(jìn)行比較,查看算法效果。
要實(shí)現(xiàn)交叉驗(yàn)證,要實(shí)現(xiàn)將樣本數(shù)據(jù)集劃分為訓(xùn)練集和新數(shù)據(jù)兩個子集的功能。
# 劃分?jǐn)?shù)據(jù)。test測試數(shù)據(jù)集占的比例。其他數(shù)據(jù)集為訓(xùn)練數(shù)據(jù)
def dividedata(data,test=0.05):
trainset=[]
testset=[]
for row in data:
if random()
else:
trainset.append(row)
return trainset,testset
還要能應(yīng)用算法,計算預(yù)測結(jié)果與真實(shí)結(jié)果之間的誤差度。
# 使用數(shù)據(jù)集對使用算法進(jìn)行預(yù)測的結(jié)果的誤差進(jìn)行統(tǒng)計,一次判斷算法好壞。algf為算法函數(shù),trainset為訓(xùn)練數(shù)據(jù)集,testset為預(yù)測數(shù)據(jù)集
def testalgorithm(algf,trainset,testset):
error=0.0
for row in testset:
guess=algf(trainset,row['input']) #這一步要和樣本數(shù)據(jù)的格式保持一致,列表內(nèi)個元素為一個字典,每個字典包含input和result兩個屬性。而且函數(shù)參數(shù)是列表和元組
error+=(row['result']-guess)**2
#print row['result'],guess
#print error/len(testset)
return error/len(testset)
有了數(shù)據(jù)拆分和算法性能誤差統(tǒng)計函數(shù)。我們就可以在原始數(shù)據(jù)集上進(jìn)行多次這樣的實(shí)驗(yàn),統(tǒng)計平均誤差。
# 將數(shù)據(jù)拆分和誤差統(tǒng)計合并在一起。對數(shù)據(jù)集進(jìn)行多次劃分,并驗(yàn)證算法好壞
def crossvalidate(algf,data,trials=100,test=0.1):
error=0.0
for i in range(trials):
trainset,testset=dividedata(data,test)
error+=testalgorithm(algf,trainset,testset)
return error/trials
交叉驗(yàn)證測試
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時才會運(yùn)行此函數(shù)
data = wineset1() #創(chuàng)建第一批數(shù)據(jù)集
print(data)
error = crossvalidate(knnestimate,data) #對直接求均值算法進(jìn)行評估
print('平均誤差:'+str(error))
def knn3(d,v): return knnestimate(d,v,k=3) #定義一個函數(shù)指針。參數(shù)為d列表,v元組
error = crossvalidate(knn3, data) #對直接求均值算法進(jìn)行評估
print('平均誤差:' + str(error))
def knninverse(d,v): return weightedknn(d,v,weightf=inverseweight) #定義一個函數(shù)指針。參數(shù)為d列表,v元組
error = crossvalidate(knninverse, data) #對使用反函數(shù)做權(quán)值分配方法進(jìn)行評估
print('平均誤差:' + str(error))
不同類型、值域的變量、無用變量
在樣本數(shù)據(jù)的各個屬性中可能并不是取值范圍相同的同類型的數(shù)據(jù),比如上面的酒的屬性可能包含檔次(0-100),酒的年限(0-50),酒的容量(三種容量375.0ml、750.0ml、1500.0ml),甚至在我們獲取的樣本數(shù)據(jù)中還有可能包含無用的數(shù)據(jù),比如酒生產(chǎn)的流水線號(1-20之間的整數(shù))。在計算樣本距離時,取值范圍大的屬性的變化會嚴(yán)重影響取值范圍小的屬性的變化,以至于結(jié)果會忽略取值范圍小的屬性。而且無用屬性的變化也會增加數(shù)據(jù)之間的距離。
所以就要對樣本數(shù)據(jù)的屬性進(jìn)行縮放到合適的范圍,并要能刪除無效屬性。
構(gòu)造新的數(shù)據(jù)集
# 構(gòu)建新數(shù)據(jù)集,模擬不同類型變量的問題
def wineset2():
rows=[]
for i in range(300):
rating=random()*50+50 #酒的檔次
age=random()*50 #酒的年限
aisle=float(randint(1,20)) #酒的通道號(無關(guān)屬性)
bottlesize=[375.0,750.0,1500.0][randint(0,2)] #酒的容量
price=wineprice(rating,age) #酒的價格
price*=(bottlesize/750)
price*=(random()*0.2+0.9)
rows.append({'input':(rating,age,aisle,bottlesize),'result':price})
return rows
實(shí)現(xiàn)按比例對屬性的取值進(jìn)行縮放的功能
# 按比例對屬性進(jìn)行縮放,scale為各屬性的值的縮放比例。
def rescale(data,scale):
scaleddata=[]
for row in data:
scaled=[scale[i]*row['input'][i] for i in range(len(scale))]
scaleddata.append({'input':scaled,'result':row['result']})
return scaleddata
那就剩下最后最后一個問題,究竟各個屬性縮放多少呢。這是一個優(yōu)化問題,我們可以通過優(yōu)化技術(shù)尋找最優(yōu)化解。而需要優(yōu)化的成本函數(shù),就是通過縮放以后進(jìn)行預(yù)測的結(jié)果與真實(shí)結(jié)果之間的誤差值。誤差值越小越好。誤差值的計算同前面交叉驗(yàn)證時使用的相同crossvalidate函數(shù)
下面構(gòu)建成本函數(shù)
# 生成成本函數(shù)。閉包
def createcostfunction(algf,data):
def costf(scale):
sdata=rescale(data,scale)
return crossvalidate(algf,sdata,trials=10)
return costf
weightdomain=[(0,10)]*4 #將縮放比例這個題解的取值范圍設(shè)置為0-10,可以自己設(shè)定,用于優(yōu)化算法
優(yōu)化技術(shù)的可以參看http://www.jb51.net/article/131719.htm
測試代碼
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時才會運(yùn)行此函數(shù)
#========縮放比例優(yōu)化===
data = wineset2() # 創(chuàng)建第2批數(shù)據(jù)集
print(data)
import optimization
costf=createcostfunction(knnestimate,data) #創(chuàng)建成本函數(shù)
result = optimization.annealingoptimize(weightdomain,costf,step=2) #使用退火算法尋找最優(yōu)解
print(result)
不對稱分布
對于樣本數(shù)據(jù)集包含多種分布情況時,輸出結(jié)果我們也希望不唯一。我們可以使用概率結(jié)果進(jìn)行表示,輸出每種結(jié)果的值和出現(xiàn)的概率。
比如葡萄酒有可能是從折扣店購買的,而樣本數(shù)據(jù)集中沒有記錄這一特性。所以樣本數(shù)據(jù)中價格存在兩種形式的分布。
構(gòu)造數(shù)據(jù)集
def wineset3():
rows=wineset1()
for row in rows:
if random()<0.5:
# 葡萄酒是從折扣店購買的
row['result']*=0.6
return rows
如果以結(jié)果概率的形式存在,我們要能夠計算指定范圍的概率值
# 計算概率。data樣本數(shù)據(jù)集,vec1預(yù)測數(shù)據(jù),low,high結(jié)果范圍,weightf為根據(jù)距離進(jìn)行權(quán)值分配的函數(shù)
def probguess(data,vec1,low,high,k=5,weightf=gaussian):
dlist=getdistances(data,vec1) #獲取距離列表
nweight=0.0
tweight=0.0
for i in range(k):
dist=dlist[i][0] #距離
idx=dlist[i][1] #索引號
weight=weightf(dist) #權(quán)值
v=data[idx]['result'] #真實(shí)結(jié)果
# 當(dāng)前數(shù)據(jù)點(diǎn)位于指定范圍內(nèi)么?
if v>=low and v<=high:
nweight+=weight #指定范圍的權(quán)值之和
tweight+=weight #總的權(quán)值之和
if tweight==0: return 0
# 概率等于位于指定范圍內(nèi)的權(quán)重值除以所有權(quán)重值
return nweight/tweight
對于多種輸出、以概率和值的形式表示的結(jié)果,我們可以使用累積概率分布圖或概率密度圖的形式表現(xiàn)。
繪制累積概率分布圖
from pylab import *
# 繪制累積概率分布圖。data樣本數(shù)據(jù)集,vec1預(yù)測數(shù)據(jù),high取值最高點(diǎn),k近鄰范圍,weightf權(quán)值分配
def cumulativegraph(data,vec1,high,k=5,weightf=gaussian):
t1=arange(0.0,high,0.1)
cprob=array([probguess(data,vec1,0,v,k,weightf) for v in t1]) #預(yù)測產(chǎn)生的不同結(jié)果的概率
plot(t1,cprob)
show()
繪制概率密度圖
# 繪制概率密度圖
def probabilitygraph(data,vec1,high,k=5,weightf=gaussian,ss=5.0):
# 建立一個代表價格的值域范圍
t1=arange(0.0,high,0.1)
# 得到整個值域范圍內(nèi)的所有概率
probs=[probguess(data,vec1,v,v+0.1,k,weightf) for v in t1]
# 通過加上近鄰概率的高斯計算結(jié)果,對概率值做平滑處理
smoothed=[]
for i in range(len(probs)):
sv=0.0
for j in range(0,len(probs)):
dist=abs(i-j)*0.1
weight=gaussian(dist,sigma=ss)
sv+=weight*probs[j]
smoothed.append(sv)
smoothed=array(smoothed)
plot(t1,smoothed)
show()
測試代碼
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時才會運(yùn)行此函數(shù)
data = wineset3() # 創(chuàng)建第3批數(shù)據(jù)集
print(data)
cumulativegraph(data,(1,1),120) #繪制累積概率密度
probabilitygraph(data,(1,1),6) #繪制概率密度圖
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助。
數(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ù)分析師認(rèn)證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計的實(shí)用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強(qiáng)大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實(shí)施重大更新。 此次更新旨在確保認(rèn) ...
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é)束)并非工作的終點(diǎn),而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報考到取證的全攻略? 在數(shù)字經(jīng)濟(jì)蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢性檢驗(yàn):捕捉數(shù)據(jù)背后的時間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢性檢驗(yàn)如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時間維度的精準(zhǔn)切片? ? 在數(shù)據(jù)的世界里,時間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準(zhǔn) ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應(yīng)用與實(shí)戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認(rèn)證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗(yàn):數(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ù)分析師認(rèn)證作為國內(nèi)權(quán)威的數(shù)據(jù)分析能力認(rèn)證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應(yīng)對策略? 長短期記憶網(wǎng)絡(luò)(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)的一種變體,憑借獨(dú)特的門控機(jī)制,在 ...
2025-07-07統(tǒng)計學(xué)方法在市場調(diào)研數(shù)據(jù)中的深度應(yīng)用? 市場調(diào)研是企業(yè)洞察市場動態(tài)、了解消費(fè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