Knn演算法實現
阿新 • • 發佈:2019-02-15
#include <iostream> #include <string> #include <vector> #include <set> #include <map> #include <fstream> #include <sstream> #include <cassert> #include <cmath> using namespace std; //樣例結構體,所屬型別和特徵向量 struct sample { string type; vector<double> features; }; // 型別和距離結構體,未用到 struct typeDistance { string type; double distance; }; bool operator < (const typeDistance& lhs, const typeDistance& rhs) { return lhs.distance < rhs.distance; } // 讀取訓練樣本 // 訓練樣本的格式是:每行代表一個樣例 // 每行的第一個元素是型別名,後面的是樣例的特徵向量 // 例如: /* a 1 2 3 4 5 b 5 4 3 2 1 c 3 3 3 3 3 d -3 -3 -3 -3 -3 a 1 2 3 4 4 b 4 4 3 2 1 c 3 3 3 2 4 d 0 0 1 1 -2 */ void readTrain(vector<sample>& train, const string& file) { ifstream fin(file.c_str()); if (!fin) { cerr << "File error!" << endl; exit(1); } string line; double d = 0.0; while (getline(fin, line)) { istringstream sin(line); sample ts; sin >> ts.type; while (sin >> d) { ts.features.push_back(d); } train.push_back(ts); } fin.close(); } // 讀取測試樣本 // 每行代表一個樣例 // 每一行是一個樣例的特徵向量 // 例如: /* 1 2 3 2 4 2 3 4 2 1 8 7 2 3 5 -3 -2 2 4 0 -4 -4 -4 -4 -4 1 2 3 4 4 4 4 3 2 1 3 3 3 2 4 0 0 1 1 -2 */ void readTest(vector<sample>& test, const string& file) { ifstream fin(file.c_str()); if (!fin) { cerr << "File error!" << endl; exit(1); } double d = 0.0; string line; while (getline(fin, line)) { istringstream sin(line); sample ts; while (sin >> d) { ts.features.push_back(d); } test.push_back(ts); } fin.close(); } // 計算歐氏距離 double euclideanDistance(const vector<double>& v1, const vector<double>& v2) { assert(v1.size() == v2.size()); double ret = 0.0; /* size_type由string類型別和vector類型別定義的型別,用以儲存任意string物件或vector物件的長度,標準庫型別將size_type定義為unsigned型別 */ for (vector<double>::size_type i = 0; i != v1.size(); ++i) { ret += (v1[i] - v2[i]) * (v1[i] - v2[i]); } return sqrt(ret); } // 初始化距離矩陣 // 該矩陣是根據訓練樣本和測試樣本而得 // 矩陣的行數為測試樣本的數目,列數為訓練樣本的數目 // 每一行為一個測試樣本到各個訓練樣本之間的歐式距離組成的陣列 void initDistanceMatrix(vector<vector<double> >& dm, const vector<sample>& train, const vector<sample>& test) { for (vector<sample>::size_type i = 0; i != test.size(); ++i) { vector<double> vd; for (vector<sample>::size_type j = 0; j != train.size(); ++j) { vd.push_back(euclideanDistance(test[i].features, train[j].features)); } dm.push_back(vd); } } // K-近鄰法的實現 // 設定不同的 k 值,給每個測試樣例予以一個型別 // 距離和權重成反比 void knnProcess(vector<sample>& test, const vector<sample>& train, const vector<vector<double> >& dm, unsigned int k) { for (vector<sample>::size_type i = 0; i != test.size(); ++i) { multimap<double, string> dts; //儲存與測試樣本i距離最近的k個點 for (vector<double>::size_type j = 0; j != dm[i].size(); ++j) { if (dts.size() < k) //把前面k個插入dts中 { dts.insert(make_pair(dm[i][j], train[j].type)); //插入時會自動排序,按dts中的double排序,最小的排在最後 } else { multimap<double, string>::iterator it = dts.end(); --it; if (dm[i][j] < it->first) //把當前測試樣本i到當前訓練樣本之間的歐氏距離與dts中最小距離比較,若更小就更新dts { dts.erase(it); dts.insert(make_pair(dm[i][j], train[j].type)); } } } map<string, double> tds; string type = ""; double weight = 0.0; //下面for迴圈主要是求出與測試樣本i最鄰近的k個樣本點中大多數屬於的類別,即將其作為測試樣本點i的類別 for (multimap<double, string>::const_iterator cit = dts.begin(); cit != dts.end(); ++cit) { // 不考慮權重的情況,在 k 個樣例中只要出現就加 1 // ++tds[cit->second]; // 這裡是考慮距離與權重的關係,距離越大權重越小 tds[cit->second] += 1.0 / cit->first; if (tds[cit->second] > weight) { weight = tds[cit->second]; type = cit->second; //儲存一下類別 } } test[i].type = type; } } // 輸出結果 // 輸出的格式和訓練樣本的格式一樣 // 每行表示一個樣例,第一個元素是該樣例的型別,後面是該樣例的特徵向量 // 例如: /* a 1 2 3 2 4 b 2 3 4 2 1 b 8 7 2 3 5 a -3 -2 2 4 0 d -4 -4 -4 -4 -4 a 1 2 3 4 4 b 4 4 3 2 1 c 3 3 3 2 4 d 0 0 1 1 -2 */ void writeTest(const vector<sample>& test, const string& file) { ofstream fout(file.c_str()); if (!fout) { cerr << "File error!" << endl; exit(1); } for (vector<sample>::size_type i = 0; i != test.size(); ++i) { fout << test[i].type << '\t'; for (vector<double>::size_type j = 0; j != test[i].features.size(); ++j) { fout << test[i].features[j] << ' '; } fout << endl; } } // 封裝 void knn(const string& file1, const string& file2, const string& file3, int k) { vector<sample> train, test; readTrain(train, file1.c_str()); readTest(test, file2.c_str()); vector<vector<double> > dm; initDistanceMatrix(dm, train, test); knnProcess(test, train, dm, k); writeTest(test, file3.c_str()); } // 測試 int main() { knn("train.txt", "test.txt", "result.txt", 5); return 0; }