简单实现k-means算法

  • 前言
    最近老师布置的作业中含有Borderline-SMOTE算法,而k-means算法是其基础,我需要将论文中的模型改造为自己的模型,从论文中的伪代码写出自己的框架,下面是自己动手热身实现的第一版k-means算法,很简单,如有错误请多多指教。

  • k-means算法
    该算法很简单,开始时随机选取k个核心,利用欧式距离不断将数据集的样本进行分类,直至核心不在变化或者收敛为止。本篇博客重点在于算法实现,如果不明白算法实现可以实际计算一遍,就会恍然大悟,没有比这更简单的算法了。

  • 函数运行流程

简单实现k-means算法_第1张图片

  • 数据格式

文本的第一行分别为数据的行数和列数,从第二行起为数据集。

简单实现k-means算法_第2张图片

  • 函数定义

根据面向对象的编程方式,将k-means函数的过程拆分为5个函数,方便调用。

#include 
#include 
#include 
#include 
#include 
#define ROW 10000
#define COLUMN 10
#define MAX 10
double data[ROW][COLUMN];     //支持的最大数据量
double core[MAX][COLUMN];     //最大支持K 10
double old_core[MAX][COLUMN];  //用于判断程序结束
double threshold = 0.0001 ; // 阈值
int  _row = 0;   //记录真实数据的行数
int _column = 0 ;   //记录真实数据的列数
int  k = 0 ;           
using namespace std;

void readFile(string );         //进行读取数据
void k_means();            //算法函数
void initCore();            //初始化k个核心
double getDistance(int,int);   //计算样本与核心之间的距离
void classify();                //对数据集进行归类
void adjustCore();               //调整核心
bool isOver();            //判断核心是否收敛
void saveFile();           //保存数据
  • readFile和saveFile函数

readFile函数,代码中的_row和_column变量在读取文件时进行赋值,这样做是为了编程方便。

void readFile(string  path)
{
	ifstream input ;
	input.open(path,iostream::in);
	if(!input.is_open())
	{
		cout << "文件读取失败"  << endl;
		exit(-1);
	}
	input >> _row ;
	input >> _column ;
	for(int i=0;i < _row ;i++)
	{
		for(int j=0; j < _column ; j++)
		{
			input >> data[i][j];
		}
	}
	input.close();
}
void saveFile(string path)
{
	ofstream output;
	output.open(path);
	if(!output.is_open())
	{
		cout << "文件保存失败" << endl;
		exit(-1);
	}
	for(int i = 0 ; i < _row ; i ++)
	{
		for(int  j = 0 ; j <= _column ; j++)
		{
			output << data[i][j]  ;
			output << " ";
		}
		output << endl;
	}
	output.close();
}
  • k-means函数

该函数主要是编写逻辑,然后在具体实现逻辑函数。

void  k_means()
{
	initCore();   //初始化核心
	classify();   //对样本集进行分类
	adjustCore();   //调整核心
	while(!isOver())   //判断核心是否收敛
	{
		classify();   //重新进行分类
		adjustCore();   //调整核心
	}

}
  • initCore函数

该函数使用rand()生成随机数随机选择k个核心,支持多维数据。

void initCore()
{
	//随机选取k个中心
	for(int i = 0 ; i < k ; i++)
	{
		int temp  = rand() % _row ;
		for(int j = 0 ; j < _column ; j++)
		{
			core[i][j] = data[temp][j] ;
		}
	}
}
  • classify函数

该函数将每一行数据的最后一位后的一位作为改行所属类别,当然也可以用其他形势的编写方法。

void classify()
{
	for(int i = 0 ; i < _row ; i++)
	{
		double minDistance = INT_MAX;
		for(int j = 0 ; j  < k ; j++)
		{
			double Distance = getDistance(i,j);
			if(Distance < minDistance)
			{
				minDistance = Distance;
				data[i][_column] = j;    //标记改行所属类别
			}
		}
	}
}
  • adjustCore函数

该函数将每个类别的所有样本相加求平均值来作为调整后的核心。

void adjustCore()
{
	double temp[MAX][MAX]={0};
	int count[MAX]={0};
	for(int i = 0 ; i < _row; i++)
	{
		int index = data[i][_column];
		for(int j = 0 ;j < _column ; j++)
		{
			temp[index][j] += data[i][j];
		}
		count[index]++;
	}
	for(int i=0 ; i< k ; i++)
	{
		for(int j = 0 ; j < _column; j++)
		{
			old_core[i][j] = core[i][j] ;   //保存旧核
			core[i][j] = temp[i][j] / count[i];      //生成新核
		}
	}
}
  • isOver函数

该函数通过比较新旧核心的距离是否小于阈值来判断是否收敛。

bool isOver()
{
	
	for(int i=0; i < k ;i ++)
	{
		double sum = 0.0 ;
		for(int j = 0 ; j < _column ; j++)
		{
			sum += pow(core[i][j] - old_core[i][j],2);
		}
		if(sum > threshold)             //有大于阈值数据进行下一轮
		{
			return false;
		}
	}
	return true;
}
  • getDistance函数

该函数使用欧式距离来进行技术。

double getDistance(int i,int j)
{
	double sum = 0.0 ;
	for(k=0; k < _column ; k++)
	  sum += pow(data[i][k]-core[j][k],2);
	return sum ;
}
  • 测试样本集

Iris数据集:http://archive.ics.uci.edu/ml/machine-learning-databases/iris/
测试结果

简单实现k-means算法_第3张图片

	真实值	分类值
1	0	0
2	0	0
3	0	0
4	0	0
5	0	0
6	0	0
7	0	0
8	0	0
9	0	0
10	0	0
11	0	0
12	0	0
13	0	0
14	0	0
15	0	0
16	0	0
17	0	0
18	0	0
19	0	0
20	0	0
21	0	0
22	0	0
23	0	0
24	0	0
25	0	0
26	0	0
27	0	0
28	0	0
29	0	0
30	0	0
31	0	0
32	0	0
33	0	0
34	0	0
35	0	0
36	0	0
37	0	0
38	0	0
39	0	0
40	0	0
41	0	0
42	0	0
43	0	0
44	0	0
45	0	0
46	0	0
47	0	0
48	0	0
49	0	0
50	0	0
51	1	2
52	2	2
53	1	2
54	2	2
55	2	2
56	2	2
57	2	2
58	2	2
59	2	2
60	2	2
61	2	2
62	2	2
63	2	2
64	2	2
65	2	2
66	2	2
67	2	2
68	2	2
69	2	2
70	2	2
71	2	2
72	2	2
73	2	2
74	2	2
75	2	2
76	2	2
77	2	2
78	1	2
79	2	2
80	2	2
81	2	2
82	2	2
83	2	2
84	2	2
85	2	2
86	2	2
87	2	2
88	2	2
89	2	2
90	2	2
91	2	2
92	2	2
93	2	2
94	2	2
95	2	2
96	2	2
97	2	2
98	2	2
99	2	2
100	2	2
101	1	1
102	2	1
103	1	1
104	1	1
105	1	1
106	1	1
107	2	1
108	1	1
109	1	1
110	1	1
111	1	1
112	1	1
113	1	1
114	2	1
115	2	1
116	1	1
117	1	1
118	1	1
119	1	1
120	2	1
121	1	1
122	2	1
123	1	1
124	2	1
125	1	1
126	1	1
127	2	1
128	2	1
129	1	1
130	1	1
131	1	1
132	1	1
133	1	1
134	2	1
135	1	1
136	1	1
137	1	1
138	1	1
139	2	1
140	1	1
141	1	1
142	1	1
143	2	1
144	1	1
145	1	1
146	1	1
147	2	1
148	1	1
149	1	1
150	2	1
  • 源代码
#include 
#include 
#include 
#include 
#include 
#define ROW 10000
#define COLUMN 10
#define MAX 10
double data[ROW][COLUMN];     //支持的最大数据量
double core[MAX][COLUMN];     //最大支持K 10
double old_core[MAX][COLUMN];  //用于判断程序结束
double threshold = 0.0001 ; // 阈值
int  _row = 0;   //记录真实数据的行数
int _column = 0 ;   //记录真实数据的列数
int  k = 0 ;           
using namespace std;

void readFile(string );         //进行读取数据
void k_means();            //算法函数
void initCore();            //初始化k个核心
double getDistance(int,int);   //计算样本与核心之间的距离
void classify();                //对数据集进行归类
void adjustCore();               //调整核心
bool isOver();            //判断核心是否收敛
void saveFile();           //保存数据

void saveFile(string path)
{
	ofstream output;
	output.open(path);
	if(!output.is_open())
	{
		cout << "文件保存失败" << endl;
		exit(-1);
	}
	for(int i = 0 ; i < _row ; i ++)
	{
		for(int  j = 0 ; j <= _column ; j++)
		{
			output << data[i][j]  ;
			output << " ";
		}
		output << endl;
	}
	output.close();
}

void readFile(string  path)
{
	ifstream input ;
	input.open(path,iostream::in);
	if(!input.is_open())
	{
		cout << "文件读取失败"  << endl;
		exit(-1);
	}
	input >> _row ;
	input >> _column ;
	for(int i=0;i < _row ;i++)
	{
		for(int j=0; j < _column ; j++)
		{
			input >> data[i][j];
		}
	}
	input.close();
}
//初始化核
void initCore()
{
	//随机选取k个中心
	for(int i = 0 ; i < k ; i++)
	{
		int temp  = rand() % _row ;
		for(int j = 0 ; j < _column ; j++)
		{
			core[i][j] = data[temp][j] ;
		}
	}
}

//生成新核
void adjustCore()
{
	double temp[MAX][MAX]={0};
	int count[MAX]={0};
	for(int i = 0 ; i < _row; i++)
	{
		int index = data[i][_column];
		for(int j = 0 ;j < _column ; j++)
		{
			temp[index][j] += data[i][j];
		}
		count[index]++;
	}
	for(int i=0 ; i< k ; i++)
	{
		for(int j = 0 ; j < _column; j++)
		{
			old_core[i][j] = core[i][j] ;   //保存旧核
			core[i][j] = temp[i][j] / count[i];      //生成新核
		}
	}
}

//获取各点到核的距离
double getDistance(int i,int j)
{
	double sum = 0.0 ;
	for(k=0; k < _column ; k++)
	  sum += pow(data[i][k]-core[j][k],2);
	return sum ;
}


//进行聚类
void classify()
{
	for(int i = 0 ; i < _row ; i++)
	{
		double minDistance = INT_MAX;
		for(int j = 0 ; j  < k ; j++)
		{
			double Distance = getDistance(i,j);
			if(Distance < minDistance)
			{
				minDistance = Distance;
				data[i][_column] = j;
			}
		}
	}
}

bool isOver()
{
	
	for(int i=0; i < k ;i ++)
	{
		double sum = 0.0 ;
		for(int j = 0 ; j < _column ; j++)
		{
			sum += pow(core[i][j] - old_core[i][j],2);
		}
		if(sum > threshold)             //有大于阈值数据进行下一轮
		{
			return false;
		}
	}
	return true;
}
//采用欧式距离
void  k_means()
{
	initCore();
	classify();
	adjustCore();
	while(!isOver())
	{
		classify();
		adjustCore();
	}

}

int  main()
{
	cin >> k;
	readFile("C:\\Users\\Administrator\\Desktop\\1.txt");
	k_means();
	saveFile("C:\\Users\\Administrator\\Desktop\\2.txt");
	getchar();
	getchar();
	return 0;
}

文件:链接: https://pan.baidu.com/s/1zpolypOUXSERBSuG98oHzw 提取码: 9v2s

你可能感兴趣的:(数据结构及算法)