python機(jī)器學(xué)習(xí)案例教程——K最近鄰算法的實(shí)現(xiàn)
K最近鄰屬于一種分類算法,他的解釋最容易,近朱者赤,近墨者黑,我們想看一個(gè)人是什么樣的,看他的朋友是什么樣的就可以了。當(dāng)然其他還牽著到,看哪方面和朋友比較接近(對(duì)象特征),怎樣才算是跟朋友親近,一起吃飯還是一起逛街算是親近(距離函數(shù)),根據(jù)朋友的優(yōu)秀不優(yōu)秀如何評(píng)判目標(biāo)任務(wù)優(yōu)秀不優(yōu)秀(分類算法),是否不同優(yōu)秀程度的朋友和不同的接近程度要考慮一下(距離權(quán)重),看幾個(gè)朋友合適(k值),能否以分?jǐn)?shù)的形式表示優(yōu)秀度(概率分布)。
K最近鄰概念:
它的工作原理是:存在一個(gè)樣本數(shù)據(jù)集合,也稱作為訓(xùn)練樣本集,并且樣本集中每個(gè)數(shù)據(jù)都存在標(biāo)簽,即我們知道樣本集中每一個(gè)數(shù)據(jù)與所屬分類的對(duì)應(yīng)關(guān)系。輸入沒(méi)有標(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的整數(shù)。最后,選擇k個(gè)最相似數(shù)據(jù)中出現(xiàn)次數(shù)最多的分類,作為新數(shù)據(jù)的分類。
今天我們使用k最近鄰算法來(lái)構(gòu)建白酒的價(jià)格模型。
構(gòu)造數(shù)據(jù)集
構(gòu)建一個(gè)葡萄酒樣本數(shù)據(jù)集。白酒的價(jià)格跟等級(jí)、年代有很大的關(guān)系。
from random import random,randint import math # 根據(jù)等級(jí)和年代對(duì)價(jià)格進(jìn)行模擬 def wineprice(rating,age): peak_age=rating-50 # 根據(jù)等級(jí)計(jì)算價(jià)格 price=rating/2 if age>peak_age: # 經(jīng)過(guò)“峰值年”,后續(xù)5年里其品質(zhì)將會(huì)變差 price=price*(5-(age-peak_age)/2) else: # 價(jià)格在接近“峰值年”時(shí)會(huì)增加到原值的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ī)生成年代和等級(jí) rating=random()*50+50 age=random()*50 # 得到一個(gè)參考價(jià)格 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個(gè)樣本數(shù)據(jù)
# 計(jì)算給預(yù)測(cè)商品和原數(shù)據(jù)集中任一其他商品間的距離。data原數(shù)據(jù)集,vec1預(yù)測(cè)商品 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個(gè)樣本數(shù)據(jù)預(yù)測(cè)新數(shù)據(jù)的屬性
1、簡(jiǎn)單求均值
# 對(duì)距離值最小的前k個(gè)結(jié)果求平均 def knnestimate(data,vec1,k=5): # 得到經(jīng)過(guò)排序的距離值 dlist=getdistances(data,vec1) avg=0.0 # 對(duì)前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個(gè)最近鄰中,可能會(huì)存在距離很遠(yuǎn)的數(shù)據(jù),但是他仍然屬于最近的前K個(gè)數(shù)據(jù)。當(dāng)存在這種情況時(shí),對(duì)前k個(gè)樣本數(shù)據(jù)直接求均值會(huì)有偏差,所以使用加權(quán)平均,為較遠(yuǎn)的節(jié)點(diǎn)賦予較小的權(quán)值,對(duì)較近的節(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時(shí)不可用。
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)值分配方法,下面就可以計(jì)算加權(quán)平均了。
# 對(duì)距離值最小的前k個(gè)結(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
第一次測(cè)試
上面完成了使用k最近鄰進(jìn)行新數(shù)據(jù)預(yù)測(cè)的功能,下面我們進(jìn)行測(cè)試。
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時(shí)才會(huì)運(yùn)行此函數(shù) data = wineset1() #創(chuàng)建第一批數(shù)據(jù)集 result=knnestimate(data,(95.0,3.0)) #根據(jù)最近鄰直接求平均進(jìn)行預(yù)測(cè) 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)證是用來(lái)驗(yàn)證你的算法或算法參數(shù)的好壞,比如上面的加權(quán)分配算法我們有三種方式,究竟哪個(gè)更好呢?我們可以使用交叉驗(yàn)證進(jìn)行查看。
隨機(jī)選擇樣本數(shù)據(jù)集中95%作為訓(xùn)練集,5%作為新數(shù)據(jù),對(duì)新數(shù)據(jù)進(jìn)行預(yù)測(cè)并與已知結(jié)果進(jìn)行比較,查看算法效果。
要實(shí)現(xiàn)交叉驗(yàn)證,要實(shí)現(xiàn)將樣本數(shù)據(jù)集劃分為訓(xùn)練集和新數(shù)據(jù)兩個(gè)子集的功能。
# 劃分?jǐn)?shù)據(jù)。test測(cè)試數(shù)據(jù)集占的比例。其他數(shù)據(jù)集為訓(xùn)練數(shù)據(jù) def dividedata(data,test=0.05): trainset=[] testset=[] for row in data: if random()<test: testset.append(row) else: trainset.append(row) return trainset,testset
還要能應(yīng)用算法,計(jì)算預(yù)測(cè)結(jié)果與真實(shí)結(jié)果之間的誤差度。
# 使用數(shù)據(jù)集對(duì)使用算法進(jìn)行預(yù)測(cè)的結(jié)果的誤差進(jìn)行統(tǒng)計(jì),一次判斷算法好壞。algf為算法函數(shù),trainset為訓(xùn)練數(shù)據(jù)集,testset為預(yù)測(cè)數(shù)據(jù)集 def testalgorithm(algf,trainset,testset): error=0.0 for row in testset: guess=algf(trainset,row['input']) #這一步要和樣本數(shù)據(jù)的格式保持一致,列表內(nèi)個(gè)元素為一個(gè)字典,每個(gè)字典包含input和result兩個(gè)屬性。而且函數(shù)參數(shù)是列表和元組 error+=(row['result']-guess)**2 #print row['result'],guess #print error/len(testset) return error/len(testset)
有了數(shù)據(jù)拆分和算法性能誤差統(tǒng)計(jì)函數(shù)。我們就可以在原始數(shù)據(jù)集上進(jìn)行多次這樣的實(shí)驗(yàn),統(tǒng)計(jì)平均誤差。
# 將數(shù)據(jù)拆分和誤差統(tǒng)計(jì)合并在一起。對(duì)數(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)證測(cè)試
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時(shí)才會(huì)運(yùn)行此函數(shù) data = wineset1() #創(chuàng)建第一批數(shù)據(jù)集 print(data) error = crossvalidate(knnestimate,data) #對(duì)直接求均值算法進(jìn)行評(píng)估 print('平均誤差:'+str(error)) def knn3(d,v): return knnestimate(d,v,k=3) #定義一個(gè)函數(shù)指針。參數(shù)為d列表,v元組 error = crossvalidate(knn3, data) #對(duì)直接求均值算法進(jìn)行評(píng)估 print('平均誤差:' + str(error)) def knninverse(d,v): return weightedknn(d,v,weightf=inverseweight) #定義一個(gè)函數(shù)指針。參數(shù)為d列表,v元組 error = crossvalidate(knninverse, data) #對(duì)使用反函數(shù)做權(quán)值分配方法進(jìn)行評(píng)估 print('平均誤差:' + str(error))
不同類型、值域的變量、無(wú)用變量
在樣本數(shù)據(jù)的各個(gè)屬性中可能并不是取值范圍相同的同類型的數(shù)據(jù),比如上面的酒的屬性可能包含檔次(0-100),酒的年限(0-50),酒的容量(三種容量375.0ml、750.0ml、1500.0ml),甚至在我們獲取的樣本數(shù)據(jù)中還有可能包含無(wú)用的數(shù)據(jù),比如酒生產(chǎn)的流水線號(hào)(1-20之間的整數(shù))。在計(jì)算樣本距離時(shí),取值范圍大的屬性的變化會(huì)嚴(yán)重影響取值范圍小的屬性的變化,以至于結(jié)果會(huì)忽略取值范圍小的屬性。而且無(wú)用屬性的變化也會(huì)增加數(shù)據(jù)之間的距離。
所以就要對(duì)樣本數(shù)據(jù)的屬性進(jìn)行縮放到合適的范圍,并要能刪除無(wú)效屬性。
構(gòu)造新的數(shù)據(jù)集
# 構(gòu)建新數(shù)據(jù)集,模擬不同類型變量的問(wèn)題 def wineset2(): rows=[] for i in range(300): rating=random()*50+50 #酒的檔次 age=random()*50 #酒的年限 aisle=float(randint(1,20)) #酒的通道號(hào)(無(wú)關(guān)屬性) bottlesize=[375.0,750.0,1500.0][randint(0,2)] #酒的容量 price=wineprice(rating,age) #酒的價(jià)格 price*=(bottlesize/750) price*=(random()*0.2+0.9) rows.append({'input':(rating,age,aisle,bottlesize),'result':price}) return rows
實(shí)現(xiàn)按比例對(duì)屬性的取值進(jìn)行縮放的功能
# 按比例對(duì)屬性進(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
那就剩下最后最后一個(gè)問(wèn)題,究竟各個(gè)屬性縮放多少呢。這是一個(gè)優(yōu)化問(wèn)題,我們可以通過(guò)優(yōu)化技術(shù)尋找最優(yōu)化解。而需要優(yōu)化的成本函數(shù),就是通過(guò)縮放以后進(jìn)行預(yù)測(cè)的結(jié)果與真實(shí)結(jié)果之間的誤差值。誤差值越小越好。誤差值的計(jì)算同前面交叉驗(yàn)證時(shí)使用的相同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 #將縮放比例這個(gè)題解的取值范圍設(shè)置為0-10,可以自己設(shè)定,用于優(yōu)化算法
優(yōu)化技術(shù)的可以參看http://www.dbjr.com.cn/article/131719.htm
測(cè)試代碼
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時(shí)才會(huì)運(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)
不對(duì)稱分布
對(duì)于樣本數(shù)據(jù)集包含多種分布情況時(shí),輸出結(jié)果我們也希望不唯一。我們可以使用概率結(jié)果進(jìn)行表示,輸出每種結(jié)果的值和出現(xiàn)的概率。
比如葡萄酒有可能是從折扣店購(gòu)買的,而樣本數(shù)據(jù)集中沒(méi)有記錄這一特性。所以樣本數(shù)據(jù)中價(jià)格存在兩種形式的分布。
構(gòu)造數(shù)據(jù)集
def wineset3(): rows=wineset1() for row in rows: if random()<0.5: # 葡萄酒是從折扣店購(gòu)買的 row['result']*=0.6 return rows
如果以結(jié)果概率的形式存在,我們要能夠計(jì)算指定范圍的概率值
# 計(jì)算概率。data樣本數(shù)據(jù)集,vec1預(yù)測(cè)數(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] #索引號(hào) 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
對(duì)于多種輸出、以概率和值的形式表示的結(jié)果,我們可以使用累積概率分布圖或概率密度圖的形式表現(xiàn)。
繪制累積概率分布圖
from pylab import * # 繪制累積概率分布圖。data樣本數(shù)據(jù)集,vec1預(yù)測(cè)數(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ù)測(cè)產(chǎn)生的不同結(jié)果的概率 plot(t1,cprob) show()
繪制概率密度圖
# 繪制概率密度圖 def probabilitygraph(data,vec1,high,k=5,weightf=gaussian,ss=5.0): # 建立一個(gè)代表價(jià)格的值域范圍 t1=arange(0.0,high,0.1) # 得到整個(gè)值域范圍內(nèi)的所有概率 probs=[probguess(data,vec1,v,v+0.1,k,weightf) for v in t1] # 通過(guò)加上近鄰概率的高斯計(jì)算結(jié)果,對(duì)概率值做平滑處理 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()
測(cè)試代碼
if __name__=='__main__': #只有在執(zhí)行當(dāng)前模塊時(shí)才會(huì)運(yùn)行此函數(shù) data = wineset3() # 創(chuàng)建第3批數(shù)據(jù)集 print(data) cumulativegraph(data,(1,1),120) #繪制累積概率密度 probabilitygraph(data,(1,1),6) #繪制概率密度圖
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python字典嵌套字典的情況下找到某個(gè)key的value詳解
這篇文章主要介紹了python字典嵌套字典的情況下找到某個(gè)key的value詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07Python學(xué)習(xí)筆記之解析json的方法分析
這篇文章主要介紹了Python解析json的方法,結(jié)合實(shí)例形式分析了常見的Python解析與轉(zhuǎn)換json格式數(shù)據(jù)相關(guān)操作技巧,需要的朋友可以參考下2017-04-04python為什么會(huì)環(huán)境變量設(shè)置不成功
在本篇文章里小編給大家分享的是一篇關(guān)于python環(huán)境變量設(shè)置不成功怎么辦的解決方法內(nèi)容,有興趣的朋友們可以跟著學(xué)習(xí)下。2020-06-06python ChainMap 合并字典的實(shí)現(xiàn)步驟
這篇文章主要介紹了python ChainMap 合并字典的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06使用python的pyplot繪制函數(shù)實(shí)例
今天小編就為大家分享一篇使用python的pyplot繪制函數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02