K-Means:
聚类算法有很多种(几十种),K-Means是聚类算法中的最常用的一种,算法最大的特点是简单,好理解,运算速度快,但是只能应用于连续型的数据,并且一定要在聚类前需要手工指定要分成几类!
连续性数据:
在统计学中,数据按变量值是否连续可分为连续数据与离散数据两种。
离散数据是指数值职能用自然数或整数单位计算,例如,企业职工人数,设备台数等,只能按计算量单位数计数,这种数据的数值一般用技术方法取得。
反之,在一定区间内可以任意取值的数据叫连续数据,其数值是连续不断的,相邻两个数值可作无限分割,即可取无限个数值。例如,生产零件的规格尺寸,人体测量的身高,体重,胸围等为连续数据,其数值只能用测量或计量的方法取得。
K-Means的算法如下:
1.随机在图中取K(这里K=2)个种子点。
2.然后对图中的所有点求到这K个种子点的距离,假如点Pi离种子点Si最近,那么Pi属于Si点群。
3.接下来,我们要移动种子点到属于他的“点群”的中心。
4.然后重复第2)和第3)步,直到,种子点没有移动
但是K-Means算法有两大缺陷:(都是和初始值有关的)
(1)K是事先给定的,这个K值的选定是非常难以估计的。很多时候,事先并不知道给定的数据集应该分成多少个类别才最合适。(ISODATA算法通过类的自动合并和分裂,得到较为合理的类型数目K)
(2)K-Means算法需要用初始随机种子点来搞,这个随机种子点太重要,不同的随机种子点会有得到完全不同的结果。(K-Means++算法可以用来解决这个问题,其可以有效地选择初始点)
K-Means++算法步骤:
(1)先从我们的数据库随机挑个随机点当“种子点”。
(2)对于每个点,我们都计算其和最近的一个“种子点”的距离D(x)并保存在一个数组里,然后把这些距离加起来得到Sum(D(x))。
(3)然后,再取一个随机值,用权重的方式来取计算下一个“种子点”。这个算法的实现是,先取一个能落在Sum(D(x))中的随机值Random,然后用Random -= D(x),直到其<=0,此时的点就是下一个“种子点”。
(4)重复第(2)和第(3)步直到所有的K个种子点都被选出来。
(5)进行K-Means算法。
这里需要说明的是在计算机中用随机数来代表概率事件。上面的三步就用了这一原理。随机抽取一个0-Sum(D(x))中的数,每次都减去相应的距离D(x),当打破Sum(D(x))>0,此时的点x就是下一个中心点。
首先定义一个类,来保存文本文件中的每条数据(KmPoint.h 代码如下)
#ifndef KmPoint_h
#define KmPoint_h
#include
class KmPoint
{
// friend std::ostream &operator<<(std::ostream &out, const KmPoint &item);
public:
KmPoint():id(-1)
{
}
KmPoint(const std::vector<double> &other, int other_id):data(other), id(other_id)
{}
const std::vector<double> &getData() const { return data; }
std::vector<double> &getData() {return data; }
int getId() { return id; }
private:
std::vector<double> data; //用来存储一行中的数据
int id; //在文本文件中是第几行数据
};
//std::ostream &operator<<(std::ostream &out, const KmPoint &item)
//{
// for (int i = 0; i < item.data.size(); i++) {
// out<
// }
// return out;
//}
#endif /* KmPoint_h */
定义K-Means类,进行聚类(Kmeans.h 代码如下)
#ifndef Kmeans_h
#define Kmeans_h
#include "KmPoint.h"
const int k = 2; //簇的个数
/*
这里我们采用的是K-Means++算法,即在初始化中心点时比K-Means算法有改进(自动生成中心点)
*/
class Kmeans
{
public:
Kmeans(const char *file);
void improvementKmeans();
void print(std::vector points);
void printClusters();
private:
std::vector points; //存储文本中存储的数据
std::vector centers; //存储K个中心点
std::vector clusters[k]; //存储k个分类
int dim; //每行数据的维度
int num; //一共有多少数据
private:
//初始化中心点
void initCenter();
double getDistance(const KmPoint &lh, const KmPoint &rh); //返回两个点之间的距离
void minDist(KmPoint point, std::vector centers, double &distance); //返回该点到各个中心的最小距离
int clusterIdOfPoint(std::vector centers, KmPoint point); //返回该point点到哪个簇距离最短
double getVar(); //得到此次聚类后误差平方和
KmPoint getNewCenter(int h); //得到第h簇的中心点
};
#endif /* Kmeans_h */
K-Means类的实现(K-Means.cpp 代码如下)
#include
#include
#include
#include
#include
#include "Kmeans.h"
using namespace std;
void Kmeans::print(vector points)
{
for (int i = 0; i < points.size(); i++) {
for (int j = 0; j < dim; j++) {
cout<<(points[i].getData())[j]<<" ";
}
cout<const char *file)
{
fstream input(file);
//1.先查看一共有多少的数据,每个数据多少维度
int lines = 0;
char c;
if (!input) {
cerr<<"cannot open the file!"<return ;
}
int sum = 0;
while (input.get(c)) {
if (c == '\n') {
lines++;
}
else if(c != ' '){
sum++;
}
}
num = lines;
dim = sum / lines;
input.close();
fstream infile(file);
for (int i = 0; i < num; i++) {
string str;
getline(infile, str);
istringstream istr(str);
vector<double> data;
data.resize(dim);
for (int j = 0; j < dim; j++) {
istr>>data[j];
}
KmPoint point(data, i+1);
points.push_back(point);
}
#if 0
print(points);
initCenter();
#endif
}
double Kmeans::getDistance(const KmPoint &lh, const KmPoint &rh)
{
double dst = 0.0;
vector<double> ldata = lh.getData();
vector<double> rdata = rh.getData();
// cout<
// cout<
for (int i = 0; i < dim; i++) {
dst += (ldata[i] - rdata[i]) * (ldata[i] - rdata[i]);
}
return sqrt(dst);
}
void Kmeans::minDist(KmPoint point, std::vector centers, double &distance)
{
distance = getDistance(point, centers[0]);
double tempDistance = 0.0;
for (int i = 1; i < centers.size(); i++) {
if (tempDistance < distance) {
distance = tempDistance;
}
}
}
void Kmeans::initCenter()
{
//1.先随机产生第一个中心点
KmPoint first_center = points[random() % num];
centers.push_back(first_center);
#if 0
cout<<"=====中心点:======"<#endif
//2.产生之后的 k-1 个中心点
vector<double> d; //用来存储每个点到中心点的最近距离
d.resize(num);
double sum = 0.0; //用来叠加各个最短距离
for (int i = 1; i < k; i++) {
for (int j = 0; j < num; j++) {
minDist(points[j], centers, d[j]);
sum += d[j];
}
sum = rand() / double(RAND_MAX) * sum;
for (int j = 0 ; j < num; j++) {
if ((sum - d[j]) > 0) {
continue;
}
centers.push_back(points[j]);
break;
}
}
#if 0
cout<<"=====中心点:======"<#endif
}
int Kmeans::clusterIdOfPoint(vector centers, KmPoint point)
{
double distance = getDistance(point, centers[0]);
int templabel = 0;
double tempDistance = 0.0;
for (int i = 1; i < k; i++)
{
tempDistance = getDistance(point, centers[i]);
if (tempDistance < distance)
{
distance = tempDistance;
templabel = i;
}
}
return templabel;
}
double Kmeans::getVar()
{
double var = 0.0;
for (int i = 0; i < k; i++) {
vector oneCluster = clusters[i];
for (int j = 0; j < oneCluster.size(); j++) {
var += getDistance(oneCluster[j], centers[i]);
}
}
return var;
}
KmPoint Kmeans::getNewCenter(int h)
{
KmPoint newcenter;
vector<double> &data = newcenter.getData();
data.resize(dim);
for (int i = 0; i < clusters[h].size(); i++) {
for (int j = 0; j < dim; j++) {
data[j] += (clusters[h][i].getData())[j];
}
}
for (int i = 0; i < dim; i++) {
data[i] /= clusters[h].size();
}
return newcenter;
}
void Kmeans::improvementKmeans()
{
initCenter();
int label = 0; //设置一个标签,表示当前的点应该数据哪个簇
for (int i = 0; i < num; i++)
{
label = clusterIdOfPoint(centers ,points[i]);
clusters[label].push_back(points[i]);
}
double oldVar = -1;
double newVar = getVar();
cout<<"初始化时的整体误差平方和为:"<int t = 0;
while (fabs(newVar - oldVar) >= 1) //当新旧函数值相差不到1,即准则函数值不发生明显变化时,算法终止
{
cout<<"第 "<<++t<<" 次迭代开始:"<//更新中心点,得到此次迭代的中心点
for (int i = 0; i < k; i++)
{
centers[i] = getNewCenter(i);
}
// cout<<"=====中心点====="<
// cout<
oldVar = newVar;
newVar = getVar();
cout<//清空每个簇(非常重要)
for (int i = 0; i < k; i++) {
clusters[i].clear();
}
//根据新的簇心获得新的簇
for (int i = 0; i < num; i++) {
label = clusterIdOfPoint(centers ,points[i]);
clusters[label].push_back(points[i]);
}
cout<<"此次迭代之后的整体误差平方和为:"<void Kmeans::printClusters()
{
for (int i = 0; i < k; i++) {
cout<<"第 "<1<<" 个簇"<
main.cpp代码:
#include
#include "Kmeans.h"
using namespace std;
int main(int argc, const char * argv[]) {
Kmeans km("/Users/fengjiakai/Desktop/Kmean-data.txt");
km.improvementKmeans();
km.printClusters();
return 0;
}
这是小弟第一次写,主要目的是为了保存一下代码,为了方便自己以后查看,也可以方便大家!(代码是已经运行成功的,前面的理解都是我看别人的博客学会的。)
参考:
http://www.csdn.net/article/2012-07-03/2807073-k-means
http://www.jianshu.com/p/fc91fed8c77b
http://blog.csdn.net/qll125596718/article/details/8243404/