
python機器學(xué)習(xí)案例教程—K最近鄰算法的實現(xiàn)
K最近鄰屬于一種分類算法,他的解釋最容易,近朱者赤,近墨者黑,我們想看一個人是什么樣的,看他的朋友是什么樣的就可以了。當然其他還牽著到,看哪方面和朋友比較接近(對象特征),怎樣才算是跟朋友親近,一起吃飯還是一起逛街算是親近(距離函數(shù)),根據(jù)朋友的優(yōu)秀不優(yōu)秀如何評判目標任務(wù)優(yōu)秀不優(yōu)秀(分類算法),是否不同優(yōu)秀程度的朋友和不同的接近程度要考慮一下(距離權(quán)重),看幾個朋友合適(k值),能否以分數(shù)的形式表示優(yōu)秀度(概率分布)。
K最近鄰概念:
它的工作原理是:存在一個樣本數(shù)據(jù)集合,也稱作為訓(xùn)練樣本集,并且樣本集中每個數(shù)據(jù)都存在標簽,即我們知道樣本集中每一個數(shù)據(jù)與所屬分類的對應(yīng)關(guān)系。輸入沒有標簽的新數(shù)據(jù)后,將新的數(shù)據(jù)的每個特征與樣本集中數(shù)據(jù)對應(yīng)的特征進行比較,然后算法提取樣本最相似數(shù)據(jù)(最近鄰)的分類標簽。一般來說,我們只選擇樣本數(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ù)等級和年代對價格進行模擬
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):
# 隨機生成年代和等級
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ù)集中的每一項
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項結(jié)果求平均
for i in range(k):
idx=dlist[i][1]
avg+=data[idx]['result']
avg=avg/k
return avg
2、求加權(quán)平均
如果使用直接求均值,有可能存在前k個最近鄰中,可能會存在距離很遠的數(shù)據(jù),但是他仍然屬于最近的前K個數(shù)據(jù)。當存在這種情況時,對前k個樣本數(shù)據(jù)直接求均值會有偏差,所以使用加權(quán)平均,為較遠的節(jié)點賦予較小的權(quán)值,對較近的節(jié)點賦予較大的權(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)重。當最近距離都大于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最近鄰進行新數(shù)據(jù)預(yù)測的功能,下面我們進行測試。
if __name__=='__main__': #只有在執(zhí)行當前模塊時才會運行此函數(shù)
data = wineset1() #創(chuàng)建第一批數(shù)據(jù)集
result=knnestimate(data,(95.0,3.0)) #根據(jù)最近鄰直接求平均進行預(yù)測
print(result)
result=weightedknn(data,(95.0,3.0),weightf=inverseweight) #使用反函數(shù)做權(quán)值分配方法,進行加權(quán)平均
print(result)
result = weightedknn(data, (95.0, 3.0), weightf=subtractweight) # 使用減法函數(shù)做權(quán)值分配方法,進行加權(quán)平均
print(result)
result = weightedknn(data, (95.0, 3.0), weightf=gaussian) # 使用高斯函數(shù)做權(quán)值分配方法,進行加權(quán)平均
print(result)
交叉驗證
交叉驗證是用來驗證你的算法或算法參數(shù)的好壞,比如上面的加權(quán)分配算法我們有三種方式,究竟哪個更好呢?我們可以使用交叉驗證進行查看。
隨機選擇樣本數(shù)據(jù)集中95%作為訓(xùn)練集,5%作為新數(shù)據(jù),對新數(shù)據(jù)進行預(yù)測并與已知結(jié)果進行比較,查看算法效果。
要實現(xiàn)交叉驗證,要實現(xiàn)將樣本數(shù)據(jù)集劃分為訓(xùn)練集和新數(shù)據(jù)兩個子集的功能。
# 劃分數(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é)果與真實結(jié)果之間的誤差度。
# 使用數(shù)據(jù)集對使用算法進行預(yù)測的結(jié)果的誤差進行統(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ù)集上進行多次這樣的實驗,統(tǒng)計平均誤差。
# 將數(shù)據(jù)拆分和誤差統(tǒng)計合并在一起。對數(shù)據(jù)集進行多次劃分,并驗證算法好壞
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
交叉驗證測試
if __name__=='__main__': #只有在執(zhí)行當前模塊時才會運行此函數(shù)
data = wineset1() #創(chuàng)建第一批數(shù)據(jù)集
print(data)
error = crossvalidate(knnestimate,data) #對直接求均值算法進行評估
print('平均誤差:'+str(error))
def knn3(d,v): return knnestimate(d,v,k=3) #定義一個函數(shù)指針。參數(shù)為d列表,v元組
error = crossvalidate(knn3, data) #對直接求均值算法進行評估
print('平均誤差:' + str(error))
def knninverse(d,v): return weightedknn(d,v,weightf=inverseweight) #定義一個函數(shù)指針。參數(shù)為d列表,v元組
error = crossvalidate(knninverse, data) #對使用反函數(shù)做權(quá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ù))。在計算樣本距離時,取值范圍大的屬性的變化會嚴重影響取值范圍小的屬性的變化,以至于結(jié)果會忽略取值范圍小的屬性。而且無用屬性的變化也會增加數(shù)據(jù)之間的距離。
所以就要對樣本數(shù)據(jù)的屬性進行縮放到合適的范圍,并要能刪除無效屬性。
構(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
實現(xià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ù),就是通過縮放以后進行預(yù)測的結(jié)果與真實結(jié)果之間的誤差值。誤差值越小越好。誤差值的計算同前面交叉驗證時使用的相同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í)行當前模塊時才會運行此函數(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é)果進行表示,輸出每種結(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ù)距離進行權(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'] #真實結(jié)果
# 當前數(shù)據(jù)點位于指定范圍內(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取值最高點,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í)行當前模塊時才會運行此函數(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
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認知誤區(qū) 在 MySQL 數(shù)據(jù)庫管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動態(tài)隨機一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫)處理 Excel 數(shù)據(jù)時,“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗與 t 檢驗:差異、適用場景與實踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計學(xué)領(lǐng)域,假設(shè)檢驗是驗證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計劃中 rows 數(shù)量的準確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對象的 text 與 content:區(qū)別、場景與實踐指南 在 Python 進行 HTTP 網(wǎng)絡(luò)請求開發(fā)時(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請求工具對比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長浮點數(shù)據(jù)的科學(xué)計數(shù)法問題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長浮點數(shù)據(jù)時的科學(xué)計數(shù)法問題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運營問題、提升執(zhí)行效率的核心手段,其價值 ...
2025-09-12用 SQL 驗證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計” 與 “用戶體驗 ...
2025-09-11塔吉特百貨孕婦營銷案例:數(shù)據(jù)驅(qū)動下的精準零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當下,精準營銷成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務(wù)數(shù)據(jù)分析:概念辨析與協(xié)同價值 在數(shù)據(jù)驅(qū)動決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務(wù)數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類分析:從操作實踐到業(yè)務(wù)價值挖掘 在數(shù)據(jù)分析場景中,聚類分析作為 “無監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價值導(dǎo)向 統(tǒng)計模型作為數(shù)據(jù)分析的核心工具,并非簡單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10