1. 程式人生 > >Knn演算法實現

Knn演算法實現

#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;
}