无监督学习算法K-means算法小结与c++编程实现

无监督学习算法K-means算法总结与c++编程实现

无监督学习算法K-means算法小结与c++编程实现

无监督学习算法K-means算法小结与c++编程实现

无监督学习算法K-means算法小结与c++编程实现

无监督学习算法K-means算法小结与c++编程实现

Figure 1: K-means algorithm. Training examples are shown as dots, and cluster centroids are shown as crosses. (a) Original dataset. (b) Random initial cluster centroids (in this instance, not chosen to be equal to twotrainingexamples). (c-f) Illustration of running two iterations of k-means. In each iteration, we assign each training example to the closest cluster centroid(shown by “painting” the training examples the same color as the cluster centroid to which is assigned); then we move each cluster centroid to the mean of the points assigned to it. (Best viewed in color.) Images courtesy Michael Jordan.

注意:

问题1:K值选取问题

K的选取通常是我们的目标,也就是说,我们要将这队数据分为几类。因此,是相对明确的。

问题2:初始值的选取问题

初始值的选取对于迭代的结果有较大的影响,选取不当,会出现所有点都归为一类的情况。一个通常的解决方案是:随机选取多组初始值进行分类,选取损失函数最小的分类结果。

编程举例:

将如下三维空间的点进行k-means分类:

[input.txt]

1.0 , 5.7 , 2.8

4.5 , 5.2 , -0.3

-0.9 , 8.1 , 1.4

0.5 , 6.6 , 2.3

3.5 , 4.7 , 0.2

4.7 , 5.9 , -1

5.1 , 8.2 , 0.9

2.1 , 7.4 , 3.0

0.6 , 6.5 , 3.8

在三维空间的图及k-means分类的结果

无监督学习算法K-means算法小结与c++编程实现

K-means算法的c++实现代码:

#include <iostream>
#include <fstream>
#include <cmath>
#include <vector>
#define dim  3//输入特征x的维数
#define K    2//分类的个数
#define MAX_NUM 100//读取输入数据的最大数目
using namespace std;
//定义类,封装输入向量x,及它要分到哪个类别
class InputFeature
{
public:
	double x[dim];//保存输入数据的向量坐标
	int cluster;//标志分类的哪一类(0,1,...,K)
	InputFeature(double x[dim])//构造函数
	{
       memcpy(this->x,x,sizeof(double)*dim);
	   cluster=-1;
	}
	//输入函数
	void print()
	{
		cout<<"cluster="<<cluster<<"   x=[";
		for (int i=0;i<dim;i++)
		{
			cout<<x[i]<<" ";
		}
		cout<<"]"<<endl;
	}
	//计算该特征向量与输入向量的2范数(欧式距离)
	double distanceOf(double u[dim])
	{
        double distance=0;
		for (int i=0;i<dim;i++)
		{
			distance+=(u[i]-x[i])*(u[i]-x[i]);
		}
		distance=sqrt(distance);
		return distance;
	}
};
vector<InputFeature> InputVector;//保存输入特征向量
void inputData()//从文件中读取数据
{
	ifstream ifile("input.txt");  
    if(!ifile)  
    {  
        cout<<"input.txt cannot be opened!"<<endl;  
        return;  
    }
    char ch;  
    int i;
    for (i=0;i<MAX_NUM;i++)//读取数目  
    {  
		string s_X1,s_X2,s_X3;
        if(!ifile.get(ch))  
        {
            return;  
        }  
        while (ch!=',')//读取第一个数据
        {  
            if (ch==' ')//跳过空格  
            {  
				if(!ifile.get(ch))  
				{
					return;  
				}
                continue;  
            }
            s_X1+=ch; 
			if(!ifile.get(ch))  
			{
				return;  
			}  
        }  
		if(!ifile.get(ch))  
        {
            return;  
        }
        while (ch!=',')//读取第二个数据 
        {  
            if (ch==' ')  
            {  
				if(!ifile.get(ch))  
				{
					return;  
				}  //跳过空格  
                continue;  
            }  
            s_X2+=ch;  
			if(!ifile.get(ch))  
			{
				return;  
			}   
        }  
		if(!ifile.get(ch))  
        {
            return;  
        } 
        while(ch!='\n')//读取第三个数据 
        {  
            if (ch==' ')  
            {  
				if(!ifile.get(ch))  
				{
					return;  
				}    
                continue;  
            }  
            s_X3+=ch;  
			if(!ifile.get(ch))  
			{
				cout<<"文件已经读完!"<<endl;  
				return;  
			}   
        }
		double xt[dim];//将读入的字符串转化为小数
		xt[0]=atof(s_X1.c_str());
		xt[1]=atof(s_X2.c_str());
		xt[2]=atof(s_X3.c_str());
		InputFeature t1(xt);//生成特征向量
	    InputVector.push_back(t1);//保存到数组
    }  
    ifile.close();
}
//计算在误差err内,2次迭代的结果是否一样
bool CompareU(double U0[K][dim],double U[K][dim],double err)
{
	for (int k=0;k<K;k++)
	{
		for (int d=0;d<dim;d++)
		{
			if(abs(U0[k][d]-U[k][d])>err)
			{
				return false;
			}
		}
   }
	return true;
}
//k-means算法核心
void k_means(double U[K][dim])
{
   double U0[K][dim];
   memcpy(U0,U,sizeof(double)*K*dim);
   while (true)
   {
	   //第一步 标定集合中的点,离哪个U点最近,即将其cluster修改为对应的分类
	   int j;
	   vector<InputFeature>::iterator it;
	   for(it = InputVector.begin(); it != InputVector.end(); ++it)
	   {
		   double dist[K];
		   for (j=0;j<K;j++)//计算该向量到各个标定向量的欧式距离
		   {
			   dist[j]=it->distanceOf(U[j]);
		   }
		   double minDist=dist[0];//初始化最小距离
		   it->cluster=0;//初始化分类
		   for (j=1;j<K;j++)
		   {
			   if (dist[j]<minDist)//如果发现离第j个更近,则更新分类
			   {
				   minDist=dist[j];
				   it->cluster=j;
			   }
		   }
	   }
	   //第二步 更新重心U
	   double sum[K][dim],num[K];
	   memset(&sum,0,sizeof(double)*K*dim);
	   memset(&num,0,sizeof(double)*K);
       for(it = InputVector.begin(); it != InputVector.end(); ++it)
	   {
		   for (int d=0;d<dim;d++)//计算所有相同分类的坐标重心
		   {
			   sum[it->cluster][d]+=it->x[d];
		   }
		   num[it->cluster]++;//计算相同分类的数目
	   }
	   for (j=0;j<K;j++)//更新标定向量的重心
	   {
		   for (int d=0;d<dim;d++)
		   {
			   if (num[j]!=0)
			     U[j][d]=(sum[j][d])/(num[j]);
		   }
	   }
	   //判断是否收敛, U==U0时收敛
	   if (CompareU(U0,U,1.0e-5))
	   {
		   break;
	   }
	   memcpy(U0,U,sizeof(double)*K*dim);//将本次迭代的结果保存
   }
   //输出聚类的结果
   cout<<"k-means聚类的中心点坐标为:"<<endl;
   for (int k=0;k<K;k++)
   {
	   cout<<"U"<<k<<"=";
	   for (int d=0;d<dim;d++)
	   {
		   cout<<U[k][d]<<" ";
	   }
	   cout<<endl;
   }
   //此时每个元素的分类情况为:
   cout<<"输入向量的聚类情况及坐标点:"<<endl;
   vector<InputFeature>::iterator it;
   for(it = InputVector.begin(); it != InputVector.end(); ++it)
   {
	   it->print();
   }
   //计算误差函数
   double J=0;
   for(it = InputVector.begin(); it != InputVector.end(); ++it)
   {
	   J+=it->distanceOf(U[it->cluster]);
   }
   cout<<"误差函数J(c,u)="<<J<<endl;
}
/*
 *@author:郑海波 zhb931706659@126.com
 *http://blog.csdn.net/nuptboyzhb/
 */
int main()
{
	cout<<"@author:郑海波 zhb931706659@126.com"<<endl;
	inputData();//读入输入的数据
	//初始化K=2个标定向量
	double U[K][dim]={{4.5,8.1,2.8},{-0.9,4.7,-0.3}};
	k_means(U);//进行k-means聚类
	return 0;
}

实验结果:

无监督学习算法K-means算法小结与c++编程实现

matlab plot code

% *@author:郑海波 zhb931706659@126.com
% *http://blog.csdn.net/nuptboyzhb/
clear all;
clc;
close all;
x=[1.0 , 5.7 , 2.8
4.5 , 5.2 , -0.3
-0.9 , 8.1 , 1.4
0.5 , 6.6 , 2.3
3.5 , 4.7 , 0.2
4.7 , 5.9 , -1
5.1 , 6.2 , 0.9
2.1 , 7.4 , 3.0];
subplot(1,2,1);
plot3(x(:,1),x(:,2),x(:,3),'o');
hold on;
U1=[4.5,8.1,2.8];
U2=[-0.9,4.7,-0.3];
plot3(U1(1),U1(2),U1(3),'+');
hold on;
plot3(U2(1),U2(2),U2(3),'r+');
hold on;
title('o--原始数据,+初始的迭代点');
x1=[1.0 , 5.7 , 2.8
-0.9 , 8.1 , 1.4
0.5 , 6.6 , 2.3
2.1 , 7.4 , 3.0];
x2=[4.5 , 5.2 , -0.3
3.5 , 4.7 , 0.2
4.7 , 5.9 , -1
5.1 , 6.2 , 0.9];
subplot(1,2,2);
plot3(x1(:,1),x1(:,2),x1(:,3),'o');
hold on;
plot3(x2(:,1),x2(:,2),x2(:,3),'ro');
hold on;
U1=[4.45 5.5 -0.05];
U2=[0.675 6.95 2.125];
plot3(U1(1),U1(2),U1(3),'r*');
hold on;
plot3(U2(1),U2(2),U2(3),'*');
hold on;
title('迭代结果,*为聚类点的中心');

未经允许不得用于商业目的