【機器學習實戰之一】:C++實現K-近鄰演算法KNN

來源:互聯網
上載者:User

本文不對KNN演算法做過多的理論上的解釋,主要是針對問題,進行演算法的設計和代碼的註解。

KNN演算法:

優點:精度高、對異常值不敏感、無資料輸入假定。

缺點:計算複雜度高、空間複雜度高。

適用資料範圍:數值型和標稱性。

工作原理:存在一個樣本資料集合,也稱作訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一個資料與所屬分類的對應關係。輸入沒有標籤的新資料後,將新資料的每個特徵與樣本集中資料對應的特徵進行比較,然後演算法提取樣本集中特徵最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料及中前k個最相似的資料,這就是k-近鄰演算法中k的出處,通常k選擇不大於20的整數。最後,選擇k個最相似資料中出現次數最多的分類,作為新資料的分類。

K-近鄰演算法的一般流程:

(1)收集資料:可以使用任何方法

(2)準備資料:距離計算所需要的數值,最好是結構化的資料格式

(3)分析資料:可以使用任何方法

(4)訓練演算法:此步驟不適用k-鄰近演算法

(5)測試演算法:計算錯誤率

(6)使用演算法:首先需要輸入樣本資料和結構化的輸出結果,然後運行k-近鄰演算法判定輸入資料分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。


問題一:現在我們假設一個情境,就是要為座標上的點進行分類,如下圖所示:



上圖一共12個左邊點,每個座標點都有相應的座標(x,y)以及它所屬的類別A/B,那麼現在需要做的就是給定一個點座標(x1,y1),判斷它屬於的類別A或者B。

所有的座標點在data.txt檔案中:
0.01.1A1.01.0A2.01.0B0.50.5A2.50.5B0.00.0A1.00.0A2.00.0B3.00.0B0.0-1.0A1.0-1.0A2.0-1.0B


step1:通過類的預設建構函式去初始化訓練資料集dataSet和測試資料testData。

step2:用get_distance()來計算測試資料testData和每一個訓練資料dataSet[index]的距離,用map_index_dis來儲存索引值對<index,distance>,其中index代表第幾個訓練資料,distance代表第index個訓練資料和測試資料的距離。

step3:將map_index_dis按照value值(即distance值)從小到大的順序排序,然後取前k個最小的value值,用map_label_freq來記錄每一個類標籤出現的頻率。

step4:遍曆map_label_freq中的value值,返回value最大的那個key值,就是測試資料屬於的類。


看一下代碼KNN_0.cc:#include<iostream>#include<map>#include<vector>#include<stdio.h>#include<cmath>#include<cstdlib>#include<algorithm>#include<fstream>usingnamespacestd;typedefchartLabel;typedefdoubletData;typedefpair<int,double>PAIR;constintcolLen=2;constintrowLen=12;ifstreamfin;ofstreamfout;classKNN{private:tDatadataSet[rowLen][colLen];tLabellabels[rowLen];tDatatestData[colLen];intk;map<int,double>map_index_dis;map<tLabel,int>map_label_freq;doubleget_distance(tData*d1,tData*d2);public:KNN(intk);voidget_all_distance();voidget_max_freq_label();structCmpByValue{booloperator()(constPAIR&lhs,constPAIR&rhs){returnlhs.second<rhs.second;}};};KNN::KNN(intk){this->k=k;fin.open("data.txt");if(!fin){cout<<"cannotopenthefiledata.txt"<<endl;exit(1);}/*inputthedataSet*/for(inti=0;i<rowLen;i++){for(intj=0;j<colLen;j++){fin>>dataSet[i][j];}fin>>labels[i];}cout<<"pleaseinputthetestdata:"<<endl;/*inuputthetestdata*/for(inti=0;i<colLen;i++)cin>>testData[i];}/**calculatethedistancebetweentestdataanddataSet[i]*/doubleKNN::get_distance(tData*d1,tData*d2){doublesum=0;for(inti=0;i<colLen;i++){sum+=pow((d1[i]-d2[i]),2);}//cout<<"thesumis="<<sum<<endl;returnsqrt(sum);}/**calculateallthedistancebetweentestdataandeachtrainingdata*/voidKNN::get_all_distance(){doubledistance;inti;for(i=0;i<rowLen;i++){distance=get_distance(dataSet[i],testData);//<key,value>=><i,distance>map_index_dis[i]=distance;}//traversethemaptoprinttheindexanddistancemap<int,double>::const_iteratorit=map_index_dis.begin();while(it!=map_index_dis.end()){cout<<"index="<<it->first<<"distance="<<it->second<<endl;it++;}}/**checkwhichlabelthetestdatabelongstotoclassifythetestdata*/voidKNN::get_max_freq_label(){//transformthemap_index_distovec_index_disvector<PAIR>vec_index_dis(map_index_dis.begin(),map_index_dis.end());//sortthevec_index_disbydistancefromlowtohightogetthenearestdatasort(vec_index_dis.begin(),vec_index_dis.end(),CmpByValue());for(inti=0;i<k;i++){cout<<"theindex="<<vec_index_dis[i].first<<"thedistance="<<vec_index_dis[i].second<<"thelabel="<<labels[vec_index_dis[i].first]<<"thecoordinate("<<dataSet[vec_index_dis[i].first][0]<<","<<dataSet[vec_index_dis[i].first][1]<<")"<<endl;//calculatethecountofeachlabelmap_label_freq[labels[vec_index_dis[i].first]]++;}map<tLabel,int>::const_iteratormap_it=map_label_freq.begin();tLabellabel;intmax_freq=0;//findthemostfrequentlabelwhile(map_it!=map_label_freq.end()){if(map_it->second>max_freq){max_freq=map_it->second;label=map_it->first;}map_it++;}cout<<"Thetestdatabelongstothe"<<label<<"label"<<endl;}intmain(){intk;cout<<"pleaseinputthekvalue:"<<endl;cin>>k;KNNknn(k);knn.get_all_distance();knn.get_max_freq_label();system("pause");return0;}


我們來測試一下這個分類器(k=5):

testData(5.0,5.0):



testData(-5.0,-5.0):



testData(1.6,0.5):



分類結果的正確性可以通過座標系來判斷,可以看出結果都是正確的。


問題二:使用k-近鄰演算法改進約會網站的匹配效果

情景如下:我的朋友海倫一直使用線上約會網站尋找合適自己的約會對象。儘管約會網站會推薦不同的人選,但她沒有從中找到喜歡的人。經過一番總結,她發現曾交往過三種類型的人:

>不喜歡的人

>魅力一般的人

>極具魅力的人

儘管發現了上述規律,但海倫依然無法將約會網站推薦的匹配對象歸入恰當的分類。她覺得可以在周一到周五約會哪些魅力一般的人,而周末則更喜歡與那些極具魅力的人為伴。海倫希望我們的分類軟體可以更好的協助她將匹配對象劃分到確切的分類中。此外海倫還收集了一些約會網站未曾記錄的資料資訊,她認為這些資料更有助於匹配對象的歸類。

海倫已經收集資料一段時間。她把這些資料存放在文字檔datingTestSet.txt(檔案連結:http://yunpan.cn/QUL6SxtiJFPfN,提取碼:f246)中,每個樣本佔據一行,總共有1000行。海倫的樣本主要包含3中特徵:

>每年獲得的飛行常客裡程數

>玩視頻遊戲所耗時間的百分比

>每周消費的冰淇淋公升數


資料預先處理:歸一化資料

我們可以看到,每年擷取的飛行常客裡程數對於計算結果的影響將遠大於其他兩個特徵。而產生這種現象的唯一原因,僅僅是因為飛行常客書遠大於其他特徵值。但是這三種特徵是同等重要的,因此作為三個等權重的特徵之一,飛行常客數不應該如此嚴重地影響到計算結果。

處理這種不同取值範圍的特徵值時,我們通常採用的方法是數值歸一化,如將取值範圍處理為0到1或者-1到1之間。

公式為:newValue=(oldValue-min)/(max-min)

其中min和max分別是資料集中的最小特徵值和最大特徵值。我們增加一個auto_norm_data函數來歸一化資料。

同事還要設計一個get_error_rate來計算分類的錯誤率,選總體資料的10%作為測試資料,90%作為訓練資料,當然也可以自己設定百分比。

其他的演算法設計都與問題一類似。


代碼如下KNN_2.cc(k=7):

/*addtheget_error_ratefunction*/#include<iostream>#include<map>#include<vector>#include<stdio.h>#include<cmath>#include<cstdlib>#include<algorithm>#include<fstream>usingnamespacestd;typedefstringtLabel;typedefdoubletData;typedefpair<int,double>PAIR;constintMaxColLen=10;constintMaxRowLen=10000;ifstreamfin;ofstreamfout;classKNN{private:tDatadataSet[MaxRowLen][MaxColLen];tLabellabels[MaxRowLen];tDatatestData[MaxColLen];introwLen;intcolLen;intk;inttest_data_num;map<int,double>map_index_dis;map<tLabel,int>map_label_freq;doubleget_distance(tData*d1,tData*d2);public:KNN(intk,introwLen,intcolLen,char*filename);voidget_all_distance();tLabelget_max_freq_label();voidauto_norm_data();voidget_error_rate();structCmpByValue{booloperator()(constPAIR&lhs,constPAIR&rhs){returnlhs.second<rhs.second;}};~KNN();};KNN::~KNN(){fin.close();fout.close();map_index_dis.clear();map_label_freq.clear();}KNN::KNN(intk,introw,intcol,char*filename){this->rowLen=row;this->colLen=col;this->k=k;test_data_num=0;fin.open(filename);fout.open("result.txt");if(!fin||!fout){cout<<"cannotopenthefile"<<endl;exit(0);}for(inti=0;i<rowLen;i++){for(intj=0;j<colLen;j++){fin>>dataSet[i][j];fout<<dataSet[i][j]<<"";}fin>>labels[i];fout<<labels[i]<<endl;}}voidKNN::get_error_rate(){inti,j,count=0;tLabellabel;cout<<"pleaseinputthenumberoftestdata:"<<endl;cin>>test_data_num;for(i=0;i<test_data_num;i++){for(j=0;j<colLen;j++){testData[j]=dataSet[i][j];}get_all_distance();label=get_max_freq_label();if(label!=labels[i])count++;map_index_dis.clear();map_label_freq.clear();}cout<<"theerrorrateis="<<(double)count/(double)test_data_num<<endl;}doubleKNN::get_distance(tData*d1,tData*d2){doublesum=0;for(inti=0;i<colLen;i++){sum+=pow((d1[i]-d2[i]),2);}//cout<<"thesumis="<<sum<<endl;returnsqrt(sum);}voidKNN::get_all_distance(){doubledistance;inti;for(i=test_data_num;i<rowLen;i++){distance=get_distance(dataSet[i],testData);map_index_dis[i]=distance;}//map<int,double>::const_iteratorit=map_index_dis.begin();//while(it!=map_index_dis.end())//{//cout<<"index="<<it->first<<"distance="<<it->second<<endl;//it++;//}}tLabelKNN::get_max_freq_label(){vector<PAIR>vec_index_dis(map_index_dis.begin(),map_index_dis.end());sort(vec_index_dis.begin(),vec_index_dis.end(),CmpByValue());for(inti=0;i<k;i++){cout<<"theindex="<<vec_index_dis[i].first<<"thedistance="<<vec_index_dis[i].second<<"thelabel="<<labels[vec_index_dis[i].first]<<"thecoordinate(";intj;for(j=0;j<colLen-1;j++){cout<<dataSet[vec_index_dis[i].first][j]<<",";}cout<<dataSet[vec_index_dis[i].first][j]<<")"<<endl;map_label_freq[labels[vec_index_dis[i].first]]++;}map<tLabel,int>::const_iteratormap_it=map_label_freq.begin();tLabellabel;intmax_freq=0;while(map_it!=map_label_freq.end()){if(map_it->second>max_freq){max_freq=map_it->second;label=map_it->first;}map_it++;}cout<<"Thetestdatabelongstothe"<<label<<"label"<<endl;returnlabel;}voidKNN::auto_norm_data(){tDatamaxa[colLen];tDatamina[colLen];tDatarange[colLen];inti,j;for(i=0;i<colLen;i++){maxa[i]=max(dataSet[0][i],dataSet[1][i]);mina[i]=min(dataSet[0][i],dataSet[1][i]);}for(i=2;i<rowLen;i++){for(j=0;j<colLen;j++){if(dataSet[i][j]>maxa[j]){maxa[j]=dataSet[i][j];}elseif(dataSet[i][j]<mina[j]){mina[j]=dataSet[i][j];}}}for(i=0;i<colLen;i++){range[i]=maxa[i]-mina[i];//normalizethetestdatasettestData[i]=(testData[i]-mina[i])/range[i];}//normalizethetrainingdatasetfor(i=0;i<rowLen;i++){for(j=0;j<colLen;j++){dataSet[i][j]=(dataSet[i][j]-mina[j])/range[j];}}}intmain(intargc,char**argv){intk,row,col;char*filename;if(argc!=5){cout<<"Theinputshouldbelikethis:./a.outkrowcolfilename"<<endl;exit(1);}k=atoi(argv[1]);row=atoi(argv[2]);col=atoi(argv[3]);filename=argv[4];KNNknn(k,row,col,filename);knn.auto_norm_data();knn.get_error_rate();//knn.get_all_distance();//knn.get_max_freq_label();return0;}
makefile:

target:g++KNN_2.cc./a.out710003datingTestSet.txt


結果:

可以看到:在測試資料為10%和訓練資料90%的比例下,可以看到錯誤率為4%,相對來講還是很準確的。


構建完整可用系統:

已經通過使用資料對分類器進行了測試,現在可以使用分類器為海倫來對人進行分類。

代碼KNN_1.cc(k=7):

/*addtheauto_norm_data*/#include<iostream>#include<map>#include<vector>#include<stdio.h>#include<cmath>#include<cstdlib>#include<algorithm>#include<fstream>usingnamespacestd;typedefstringtLabel;typedefdoubletData;typedefpair<int,double>PAIR;constintMaxColLen=10;constintMaxRowLen=10000;ifstreamfin;ofstreamfout;classKNN{private:tDatadataSet[MaxRowLen][MaxColLen];tLabellabels[MaxRowLen];tDatatestData[MaxColLen];introwLen;intcolLen;intk;map<int,double>map_index_dis;map<tLabel,int>map_label_freq;doubleget_distance(tData*d1,tData*d2);public:KNN(intk,introwLen,intcolLen,char*filename);voidget_all_distance();tLabelget_max_freq_label();voidauto_norm_data();structCmpByValue{booloperator()(constPAIR&lhs,constPAIR&rhs){returnlhs.second<rhs.second;}};~KNN();};KNN::~KNN(){fin.close();fout.close();map_index_dis.clear();map_label_freq.clear();}KNN::KNN(intk,introw,intcol,char*filename){this->rowLen=row;this->colLen=col;this->k=k;fin.open(filename);fout.open("result.txt");if(!fin||!fout){cout<<"cannotopenthefile"<<endl;exit(0);}//inputthetrainingdatasetfor(inti=0;i<rowLen;i++){for(intj=0;j<colLen;j++){fin>>dataSet[i][j];fout<<dataSet[i][j]<<"";}fin>>labels[i];fout<<labels[i]<<endl;}//inputthetestdatacout<<"frequentfliermilesearnedperyear?";cin>>testData[0];cout<<"percentageoftimespentplayingvideogames?";cin>>testData[1];cout<<"litersoficecreamconsumedperyear?";cin>>testData[2];}doubleKNN::get_distance(tData*d1,tData*d2){doublesum=0;for(inti=0;i<colLen;i++){sum+=pow((d1[i]-d2[i]),2);}returnsqrt(sum);}voidKNN::get_all_distance(){doubledistance;inti;for(i=0;i<rowLen;i++){distance=get_distance(dataSet[i],testData);map_index_dis[i]=distance;}//map<int,double>::const_iteratorit=map_index_dis.begin();//while(it!=map_index_dis.end())//{//cout<<"index="<<it->first<<"distance="<<it->second<<endl;//it++;//}}tLabelKNN::get_max_freq_label(){vector<PAIR>vec_index_dis(map_index_dis.begin(),map_index_dis.end());sort(vec_index_dis.begin(),vec_index_dis.end(),CmpByValue());for(inti=0;i<k;i++){/*cout<<"theindex="<<vec_index_dis[i].first<<"thedistance="<<vec_index_dis[i].second<<"thelabel="<<labels[vec_index_dis[i].first]<<"thecoordinate(";intj;for(j=0;j<colLen-1;j++){cout<<dataSet[vec_index_dis[i].first][j]<<",";}cout<<dataSet[vec_index_dis[i].first][j]<<")"<<endl;*/map_label_freq[labels[vec_index_dis[i].first]]++;}map<tLabel,int>::const_iteratormap_it=map_label_freq.begin();tLabellabel;intmax_freq=0;/*traversethemap_label_freqtogetthemostfrequentlabel*/while(map_it!=map_label_freq.end()){if(map_it->second>max_freq){max_freq=map_it->second;label=map_it->first;}map_it++;}returnlabel;}/**normalizethetrainingdataset*/voidKNN::auto_norm_data(){tDatamaxa[colLen];tDatamina[colLen];tDatarange[colLen];inti,j;for(i=0;i<colLen;i++){maxa[i]=max(dataSet[0][i],dataSet[1][i]);mina[i]=min(dataSet[0][i],dataSet[1][i]);}for(i=2;i<rowLen;i++){for(j=0;j<colLen;j++){if(dataSet[i][j]>maxa[j]){maxa[j]=dataSet[i][j];}elseif(dataSet[i][j]<mina[j]){mina[j]=dataSet[i][j];}}}for(i=0;i<colLen;i++){range[i]=maxa[i]-mina[i];//normalizethetestdatasettestData[i]=(testData[i]-mina[i])/range[i];}//normalizethetrainingdatasetfor(i=0;i<rowLen;i++){for(j=0;j<colLen;j++){dataSet[i][j]=(dataSet[i][j]-mina[j])/range[j];}}}intmain(intargc,char**argv){intk,row,col;char*filename;if(argc!=5){cout<<"Theinputshouldbelikethis:./a.outkrowcolfilename"<<endl;exit(1);}k=atoi(argv[1]);row=atoi(argv[2]);col=atoi(argv[3]);filename=argv[4];KNNknn(k,row,col,filename);knn.auto_norm_data();knn.get_all_distance();cout<<"Youwillprobablylikethisperson:"<<knn.get_max_freq_label()<<endl;return0;}


makefile:

target:g++KNN_1.cc./a.out710003datingTestSet.txt
結果:



KNN_1.cc和KNN_2.cc的差別就在於後者對分類器的效能(即分類錯誤率)進行分析,而前者直接對具體實際的資料進行了分類。


註明出處:http://blog.csdn.net/lavorange/article/details/16924705


相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.