C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)

C++实现的简单的K近邻算法(K-Nearest Neighbor,K-NN)

前一段时间学习了K近邻算法,对K近邻算法有了一个初步的了解,也存在一定的问题,下面我来简单介绍一下K近邻算法。本博客将从以下几个方面开始介绍K近邻算法:
1、K近邻算法的介绍
2、K近邻算法的模型及其三要素
3、C++实现的简单KNN算法
4、KNN算法的优缺点
5、遇到的问题

一、K近邻算法的介绍
K近邻算法(K-Nearest Neighbour,K-NN)是一种基本分类与回归方法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路是:如果一个样本在特征空间中的K个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
K近邻算法简单,直观。给定一个训练数据集,对于新的输入实例,在训练数据集中找到与该实例最邻近的K个实例,这K个实例的多数某个类,就把该输入实例归入这个类。K近邻法没有显示的学习过程。
1.1 算法(K近邻法)
C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)_第1张图片

1.2 KNN算法的实现步骤:
step.1---初始化距离为最大值
step.2---计算未知样本和每个训练样本的距离dist
step.3---得到目前K个最临近样本中的最大距离maxdist
step.4---如果dist小于maxdist,则将该训练样本作为K-最近邻样本
step.5---重复步骤2、3、4,直到未知样本和所有训练样本的距离都算完
step.6---统计K个最近邻样本中每个类别出现的次数
step.7---选择出现频率最大的类别作为未知样本的类别

空洞的描述不易理解,生动的例子会方便我们进行理解,下面将举例帮助我们理解。
C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)_第2张图片 如图所示,有两类不同的数据样本,分别用蓝色的小正方形和红色的小三角形表示,而图正中间的那个绿色的圆所标示的数据则是待分类的数据。也就是说,现在,我们不知道中间那个绿色的数据是从属于哪一个类(蓝色小正方形or红色小三角形),那么这个数据到底是哪个类呢?
1、如果K=3,绿色圆点的最近的三个邻居是2个红色小三角形和1个蓝色小正方形,那么就可以判定这个绿色的待分类的圆形点属于红色小三角形类。
2、如果K=5,绿色圆点的最近的五个邻居是2个红色小三角形和3个蓝色小正方形,那么就可以判定这个绿色的待分类的圆形点属于蓝色小正方形类。
我们看到,当无法判定当前待分类点是从属于已知分类中的哪一类时,我们可以依据统计学的理论看它所处的位置特征,衡量它周围邻居的权重,而把它归为(或分配)到权重更大的那一类。这就是K近邻算法的核心思想。

二、K近邻的模型及其三要素

2.1 K近邻模型
K近邻法中,当训练集、距离度量(如欧氏距离)、K值以及分类决策规则(如多数表决)确定后,对于任何一个新的输入实例,它所属的类唯一地确定,这相当于根据上述要素将特征空间划分成一个个子空间,确定子空间里的每个点所属的类。
K近邻算法使用的模型实际上对应于对特征空间的划分,模型由三个基本要素——距离度量、K值的选择和分类决策规则决定。

2.2 距离度量
特征空间两个实例点的距离是两个实例点相似程度的反映,K近邻模型的特征空间一般是n维实数向量空间Rn ,使用的距离一般是欧氏距离,但也可以是其他距离,如更一般的LP距离(LPdistance)或曼哈顿距离(Manhanttan distance)。
C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)_第3张图片
2.2 欧氏距离
欧氏距离是最常见的两点之间或多点之间的距离表示法,又称之为欧几里得度量,它定义于欧几里得空间中,如点x = (x1,...,xn) 和y = (y1,...,yn) 之间的距离为:

(1)二维平面上两点a(x1,y1)b(x2,y2)间的欧氏距离:
    
 (2)三维空间两点a(x1,y1,z1)与b(x2,y2,z2)间的欧氏距离:
            
 (3)两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的欧氏距离:
          
   也可以用表示成向量运算的形式:
            
2.3 K值的选择
KNN算法的分类效果很大程度上依赖于K值的选取,以往人们都是根据个人经验来定。K值选择过小,就相当于用较小的邻域中的训练实例进行预测,只有与输入示例较近的(相似的)训练实例才会对预测起作用,得到的近邻数目过少,预测结果会对近邻的实例点非常敏感,会降低分类精度,如果邻近的实例点恰好是噪声,预测就会出错;而如果K值过大,就相当于用较大邻域中的训练实例作为预测,与输入示例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误。
两个极端情况:
1、当K=1时,K近邻问题就转化成了K最近邻问题,即无论新的输入实例是什么,都只选择距离它最近的样本类别归属。
2、当K=N时,无论输入实例是什么,都将简单的预测它属于在训练实例中最多的类,这时候,模型过于简单,完全忽略训练实例中的大量有用信息。所以当K=N时,是不可取的。

2.4 分类决策规则
K近邻算法中的分类决策规则往往是多数表决,即由输入示例的K个邻近的训练实例中的多数类决定输入实例的类。

三、C++实现的简单KNN算法

算法目的:根据已经给出的样本坐标点,每个坐标点都有相应的坐标(x,y)以及其所属的类别A/B,现给出待分类点,坐标为(x1,y1),判断该点所属的类别。
在测试中,设置K值为5,即选择距离待分类点最近的5个点,判断这5个点中,哪个类别的数据多,待分类点就属于这个类。源代码及测试结果如下:
源代码:

#include
#include
#include
#include
#include
#include

using namespace std;

typedef char tLabel;
typedef double tData;
typedef pair  PAIR;
const int colLen = 2;
const int rowLen = 12;
ifstream fin;
ofstream fout;

class KNN
{
private:
	tData dataSet[rowLen][colLen];
	tLabel labels[rowLen];
	tData testData[colLen];
	int k;
	map map_index_dis;
	map map_label_freq;
	double get_distance(tData *d1, tData *d2);
public:

	KNN(int k);

	void get_all_distance();

	void get_max_freq_label();

	struct CmpByValue
	{
		bool operator() (const PAIR& lhs, const PAIR& rhs)
		{
			return lhs.second < rhs.second;
		}
	};

};

KNN::KNN(int k)
{
	this->k = k;

	fin.open("C:\\Users\\zws\\Desktop\\K近邻\\data.txt");

	if (!fin)
	{
		cout << "can not open the file data.txt" << endl;
		exit(1);
	}

	/* input the dataSet */
	for (int i = 0; i> dataSet[i][j];
		}
		fin >> labels[i];
	}

	cout << "please input the test data :" << endl;
	/* inuput the test data */
	for (int i = 0; i> testData[i];

}

/*
* calculate the distance between test data and dataSet[i]
*/
double KNN::get_distance(tData *d1, tData *d2)
{
	double sum = 0;
	for (int i = 0; i::const_iterator map_it = map_label_freq.begin();
	tLabel label;
	int max_freq = 0;
	//find the most frequent label
	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 << "The test data belongs to the " << label << " label" << endl;
}

int main()
{
	int k;
	cout << "please input the k value : " << endl;
	cin >> k;
	KNN knn(k);
	knn.get_all_distance();
	knn.get_max_freq_label();
	system("pause");
	return 0;
}

测试结果:

K=5时,待分类点为(5.0 ,5.0):
C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)_第4张图片
K=5时,待分类点为(4.5,1.6):
C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)_第5张图片
K=5时,待分类点为(-2.1,3.0):
C++实现的简单k近邻算法(K-Nearest-Neighbour,K-NN)_第6张图片

四、K近邻算法的优缺点
优点:
1)原理简单,实现起来比较方便;
2)能够支持增量学习;
3)能够对超多边形的复杂决策空间建模。
使用数据类型: 数值型(目标变量可以从无限的数值集合中取值)和标称型(目标变量只有在有限目标集中取值)

缺点:
1)样本的不均衡性可能造成结果错误:如果一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新的样本时,该样本的K个邻居中大容量类的样本占多数。
2)计算量较大,需要有效的存储技术和并行硬件的支撑:因为对每一个待分类的文本都要计算它到全体已知样本的距离,才能求得他的K个最近邻点。

五、遇到的问题以及想法
Q1、K值的选择问题
K值的选择很重要,选大选小都会影响预测的结果,那么K值应当怎样选择才合适?
Idea:
1)、先定义一个很小的值,然后通过交叉验证算出合适的值。
2)、根据样本的特征来决定K的值
3)、K的值一般选取为√q(q为训练样本的数目)。
以上几种方法是查找资料以及和同学讨论出的结果,个人认为第一种方法可靠性较高,但是可能计算量相比较其他两个会稍大一些。如何快速的定义K的值,依然是一个仍需讨论和研究的问题。

Q2、假设K为偶数,训练集中包含两个类别,得出的结果两个类别包含的数据一样,那么该测试样本应该从属于哪一个类?
Idea:
1)、可以选择距离该待分类点最近的样本点作为其类别,此时问题转化为K最近邻问题。
2)、当两个类别的数目相等时,可以算各个样本类别的平均值,平均值小的作为其类别。

Q3、K近邻算法中,需要存储所有的样本数据,而且需要计算每个训练样本与测试样本之间的距离,计算量很大,有没有什么好的改进方法?
Idea:
能否设置一个阈值,将距离待分类点很远的训练样本不考虑,减少计算每个训练样本与测试样本距离的计算量?

Q4、距离度量的计算方式选择不同时,所计算出来的结果也是不同的,到底应该采取怎么样的计算方法才合理?


以上只是我对K近邻算法的初步理解,如果存在错误的地方敬原谅,关于K近邻算法的实现kd树,学习的过程中存在不懂的地方,就没在文章中提起,等到把kd树问题理解了之后再写出来吧。谢谢大家的阅读,也请大家批评指正。




你可能感兴趣的:(DataMining)