1. 程式人生 > >C++單刷《機器學習實戰》之一——k-近鄰演算法

C++單刷《機器學習實戰》之一——k-近鄰演算法

    數學系研二渣碩一枚,最早接觸機器學習還是在研究生一年級的模式識別課程上,發現大部分機器學習的書籍都是採用Python語言,當然Python在資料分析和矩陣計算方面確實會有很大的優勢,對於缺乏程式設計基礎又想要快速入門的同學,Python確實是首選。而從本系列開始,我將主要用C++將《機器學習實戰》這本書刷一遍,旨在加深對演算法理解的同時提高程式設計能力,也希望能夠為想入坑機器學習,同時又熱愛C++的人提供一些參考。

    每一篇的開始會有一個簡單的演算法描述,不會將原書的Python的程式碼照搬過來,想了解具體的可以參考《機器學習實戰》原著,

CSDN上也有電子版可供下載。

        現學現賣,希望我能夠堅持得下去。



演算法概述:kNN演算法採用測量不同特徵值之間的距離方法進行分類。kNN演算法在能夠工作之前需要先獲取大量樣本資料進行訓練,每一個樣本資料事先都存在標籤。輸入沒有標籤的資料後,將新資料的每個特徵與樣本集中的特徵進行比較,然後提取樣本集中最相似資料的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料標籤,這就是kNN演算法中k的出處,通常k是不大於20的整數。最後,我們選擇k個最相似資料中出現次數最多的分類,作為新資料的分類。

 

現在我們嘗試一個簡單的範例,對二維座標系中的點進行分類,定義一個4×2二維陣列group代表座標系中4個點的座標,相應的標籤labels為一個字串陣列,labels索引對應group中每一行的索引。

 

double group[4][2] = { { 1.0, 1.1 }, { 1.0, 1.0 }, { 0, 0 }, { 0, 0.1 } };
string labels[4] = { "A", "A", "B", "B" };

 

C++中,將樣本集定義為一個結構體似乎是更為合理的選擇,不過原書中都是矩陣計算,為描述方便,還是採用類似矩陣的陣列。

在獲得樣本集後,需要採用kNN對無標籤的資料進行分類,具體步驟如下:

(1)計算樣本集中的點與當前點的距離;

(2)按照距離遞增次序排序;

(3)選取與當前點距離最小k個點;

(4)確定前k個點所在類別的出現頻率;

(5)返回前k個點出現頻率最高的類別作為當前點的分類。

     相應的程式碼清單如下:

string classify(double* inX,double* dataSet,string labels[],int k,int size,int dataSetSize)
//kNN分類演算法
//inX:未分類的輸入資料,dataSet:樣本集,labels:標籤,k:k值,size:資料的特徵數量,dataSetSize:樣本集數量 
{
double sum = 0;
double* diff_array = new double[size];
double* diff_all = new double[dataSetSize];
int* sorted_index = new int[dataSetSize];
string label;
map<string, int> label_count;
for (int i = 0; i < dataSetSize; i++)
//計算當前點與各樣本點的歐式距離,並存入陣列diff_array
{
sum = 0;
for (int j = 0; j < size; j++)
{
diff_array[j] = *(inX + j) - *(dataSet + i*size + j);
sum += (diff_array[j] * diff_array[j]);
}
diff_all[i] = sqrt(sum);
}
//排序,並返回排序後的原陣列索引
sortIndex(diff_all, sorted_index, dataSetSize);
for (int i = 0; i < k; i++)
//計算前k個索引對應標籤的出現次數,存入關聯容器label_count
{
label = labels[sorted_index[i]];
++label_count[label];
}
 
//找出出現次數最多的標籤,返回
auto map_it = label_count.begin();
label = map_it->first;
int max_count = map_it->second;
for (; map_it != label_count.end(); map_it++)
{
if (max_count < map_it->second)
{
max_count = map_it->second;
label = map_it->first;
}
}
 
delete diff_array;
delete diff_all;
delete sorted_index;
 
return label;
}


以上程式使用歐氏距離公式,計算兩個點xAxB之間的距離:

 

計算當前點與所有樣本點的距離後,將距離由小到大進行排序,找到最近的k個點,然後返回k個點中出現頻率最高的標籤,這是基本思路。

在對距離進行排序並按原陣列的索引返回的時候出現了一些小麻煩,在python中只要一個命令就能解決的問題,我不知道在C++中是否有同樣方便好用的排序函式,不過以提高程式設計能力為目標,我本人倒不介意重複造車輪子的過程,排序程式碼如下:

void sortIndex(double* data, int* sorted_index2, int n)
//排序,並返回排序後的原陣列索引
//data:原始陣列,sorted_index2:排序後的原陣列索引,n:陣列大小
{
int index = 0;
int* sorted_index = new int[n];
for (int i = 0; i < n; i++)
{
index = 0;
for (int j = 0; j < n; j++)
{
if (data[i] > data[j])
index++;
else if (data[i] == data[j] && i > j)
index++;
}
sorted_index[i] = index;
}
 
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (i == sorted_index[j])
sorted_index2[i] = j;
}
}
 
delete sorted_index;
}
 


程式碼完成後,在main函式中進行呼叫測試:

int main()
{
string result;
string line;
double point[2];
cout << "Please input the coodinate of the pixel" << endl;
while (getline(cin, line))
{
istringstream record(line);
record >> point[0];
record >> point[1];
result = classify(point, &group[0][0], labels, 3, 2, 4);
cout << "The result is: " << result << endl;
cout << "Please input the coodinate of the pixel" << endl;
}
return 0;
}


執行結果:

 

 

使用kNN演算法改進約會網站的配對效果

海倫在使用約會網站尋找約會物件的過程中,經過總結可以把交往過的人分為三種類型:

不喜歡的人

魅力一般的人

極具魅力的人

海倫希望我們的演算法可以幫助她將匹配物件分到確切的分類中,為此她收集了一批約會物件的使用者資料,每一個約會物件包含3個特徵:
每年的飛行里程數

玩遊戲所耗時間百分比

每週消費的冰淇淋公升數

每一條樣本資料都已經做好了標籤,樣本資料如下:

 

為了應用已經實現的kNN演算法幫助海倫進行分類,我們先要讀取文字資料並轉化為程式所需要的型別。然後應用以上的classify函式進行分類。

資料檔案裡共有1000條資料,選取前100條資料為測試集對演算法進行測試,發現當k=3時,錯誤率為5%

 

利用全體資料作為樣本集對當個數據進行分類:


    

檢視完整程式碼:

http://blog.csdn.net/u014080185/article/details/60467308