【机器学习算法1】——kmeans聚类的设计与实现
2016-04-10 14:55
393 查看
(本博客主要用于记录自己学习过程中的一些感悟和体会,如有错误之处希望大家帮忙指正,也希望跟大家共同学习共同进步。)
kmeans算法属于非监督学习算法,因为在原始的数据集中没有类别标号。简单回顾一下kmeans聚类算法的思想:
给定k的值,将原始的样本分为k个簇,使每个簇内的散度最小,簇与簇之间的散度最大。我们这里用J=
最小;所以具体的实现思路是:
(1)由给定的聚类数初始化一个k维的数组,作为初始的k个均值记为mean[u1,u2,u3,u4…..,uk];其中ui又是一个d维的向量(样本维数是d);
(2)将所有样本(n个) ,以x(i)为其中一个d维的样本为例,分别计算x(i)到k个不同均值(质心)即u1,u2,……,uk 的欧式距离(简单的每个维度相减然后求平方和,再做开方,因为CSDN的编辑器写公式很不方便,简单的叙述了);选择一个距离最小的质心uj作为其中心,该样本点也被划分为cluster[j]这个新的数组空间里(也可以理解为给这个样本增加了一个属性标号label,label=j);重复,直到所有样本都划分完毕。
(3)计算总的类内散步(就是每个簇里面的样本到质心的欧式距离的和)记为oldVar;根据每个cluster里面的划分的样本计算新的质心(简单的求平均),更新mean[k]数组;然后将所有样本重复第二步的过程,计算与新的质心的距离,选择新的聚类,更新cluster[j],j从1到k的元素,或者理解为更改原始样本的属性标号label。
(4)由上述计算新的方差newVar(上面提到的总的类内散度的和),与oldVar相比较。如果变化很小就表明收敛。如果变化很大就继续上述的(2)、(3)步过程。直到收敛。或者指定迭代的次数(即质心更新的次数,达到一定的次数就跳出循环)。
考虑到初试样本对收敛速度以及收敛精度的影响。可以采用不同的初试质心多做几次试验,选择newVar最小的一次作为聚类的结果;
下面是具体C++代码实现的过程(我个人编程能力很一般,这里参考的是http://blog.csdn.net/qll125596718/article/details/8243404/这篇文章,我觉得代码写的非常规范,我只是根据自己的需要改动了一小部分,但是主要思路还是源于上篇文章)
kmeans算法属于非监督学习算法,因为在原始的数据集中没有类别标号。简单回顾一下kmeans聚类算法的思想:
给定k的值,将原始的样本分为k个簇,使每个簇内的散度最小,簇与簇之间的散度最大。我们这里用J=
最小;所以具体的实现思路是:
(1)由给定的聚类数初始化一个k维的数组,作为初始的k个均值记为mean[u1,u2,u3,u4…..,uk];其中ui又是一个d维的向量(样本维数是d);
(2)将所有样本(n个) ,以x(i)为其中一个d维的样本为例,分别计算x(i)到k个不同均值(质心)即u1,u2,……,uk 的欧式距离(简单的每个维度相减然后求平方和,再做开方,因为CSDN的编辑器写公式很不方便,简单的叙述了);选择一个距离最小的质心uj作为其中心,该样本点也被划分为cluster[j]这个新的数组空间里(也可以理解为给这个样本增加了一个属性标号label,label=j);重复,直到所有样本都划分完毕。
(3)计算总的类内散步(就是每个簇里面的样本到质心的欧式距离的和)记为oldVar;根据每个cluster里面的划分的样本计算新的质心(简单的求平均),更新mean[k]数组;然后将所有样本重复第二步的过程,计算与新的质心的距离,选择新的聚类,更新cluster[j],j从1到k的元素,或者理解为更改原始样本的属性标号label。
(4)由上述计算新的方差newVar(上面提到的总的类内散度的和),与oldVar相比较。如果变化很小就表明收敛。如果变化很大就继续上述的(2)、(3)步过程。直到收敛。或者指定迭代的次数(即质心更新的次数,达到一定的次数就跳出循环)。
考虑到初试样本对收敛速度以及收敛精度的影响。可以采用不同的初试质心多做几次试验,选择newVar最小的一次作为聚类的结果;
下面是具体C++代码实现的过程(我个人编程能力很一般,这里参考的是http://blog.csdn.net/qll125596718/article/details/8243404/这篇文章,我觉得代码写的非常规范,我只是根据自己的需要改动了一小部分,但是主要思路还是源于上篇文章)
#include <iostream> #include <sstream> #include <fstream> #include <vector> #include <math.h> #include <stdlib.h> #include<time.h> #define k 3//簇的数目 using namespace std; //存放元组的属性信息 typedef vector<double> Tuple;//存储每条数据记录 int dataNum;//数据集中数据记录数目 int dimNum;//每条记录的维数 //计算两个元组间的欧几里距离 double getDistXY(const Tuple& t1, const Tuple& t2) { double sum = 0; for (int i = 1; i <= dimNum; ++i) { sum += (t1[i] - t2[i]) * (t1[i] - t2[i]); } return sqrt(sum); } //根据质心,决定当前元组属于哪个簇 int clusterOfTuple(Tuple means[], const Tuple& tuple){ //参数分别是 均值和 元组; double dist = getDistXY(means[0], tuple); double tmp; int label = 0;//标示属于哪一个簇 for (int i = 1; i<k; i++){ tmp = getDistXY(means[i], tuple); if (tmp<dist) { dist = tmp; label = i; } } return label; } //获得给定簇集的平方误差 double getVar(vector<Tuple> clusters[], Tuple means[]){ //参数分别表示簇,平均值元组; double var = 0; for (int i = 0; i < k; i++) { vector<Tuple> t = clusters[i];//将簇i的所有元组记为 vector t; for (int j = 0; j< t.size(); j++) { var += getDistXY(t[j], means[i]); } } return var; } //获得当前簇的均值(质心) Tuple getMeans(const vector<Tuple>& cluster) { int num = cluster.size(); Tuple t(dimNum + 1, 0); //定义一个dimNum+1 行 0列的元组用于记录所有样本的坐标和; for (int i = 0; i < num; i++) { for (int j = 1; j <= dimNum; ++j) { t[j] += cluster[i][j]; } } for (int j = 1; j <= dimNum; ++j) t[j] /= num; return t; } void print(const vector<Tuple> clusters[]) { for (int lable = 0; lable<k; lable++) { cout << "第" << lable + 1 << "个簇:" << endl; vector<Tuple> t = clusters[lable]; for (int i = 0; i<t.size(); i++) { cout << i + 1 << ".("; for (int j = 0; j <= dimNum; ++j) { cout << t[i][j] << ", "; } cout << ")\n"; } } } void KMeans(vector<Tuple>& tuples){ vector<Tuple> clusters[k];//k个簇 Tuple means[k];//k个中心点 int i = 0; //一开始随机选取k条记录的值作为k个簇的质心(均值) srand((unsigned int)time(NULL)); for (i = 0; i<k;){ int iToSelect = rand() % tuples.size(); if (means[iToSelect].size() == 0) { for (int j = 0; j <= dimNum; ++j) { means[i].push_back(tuples[iToSelect][j]); } ++i; } } int lable = 0; //根据默认的质心给簇赋值 for (i = 0; i != tuples.size(); ++i){ lable = clusterOfTuple(means, tuples[i]); clusters[lable].push_back(tuples[i]);//将所属的样本坐标(包括序列标签)一起存入cluster[ ]里; } double oldVar = -1; double newVar = getVar(clusters, means); cout << "初始的的整体误差平方和为:" << newVar << endl; int t = 0; while (abs(newVar - oldVar) >= 1) //当新旧函数值相差不到1即准则函数值不发生明显变化时,算法终止 { cout << "第 " << ++t << " 次迭代开始:" << endl; for (i = 0; i < k; i++) //更新每个簇的中心点 { means[i] = getMeans(clusters[i]); } oldVar = newVar; newVar = getVar(clusters, means); //计算新的准则函数值 for (i = 0; i < k; i++) //清空每个簇 { clusters[i].clear(); } //根据新的质心获得新的簇 for (i = 0; i != tuples.size(); ++i){ lable = clusterOfTuple(means, tuples[i]); clusters[lable].push_back(tuples[i]); } cout << "此次迭代之后的整体误差平方和为:" << newVar << endl; } cout << "The result is:\n"; print(clusters); } int main(){ char fname[256]; cout << "请输入存放数据的文件名: "; cin >> fname; cout << endl << " 请依次输入: 维数 样本数目" << endl; cout << endl << " 维数dimNum: "; cin >> dimNum; cout << endl << " 样本数目dataNum: "; cin >> dataNum; ifstream infile(fname);//ifstream:输入流类,用于从文件中读出内容。定义一个ifstream对象; if (!infile){ cout << "不能打开输入的文件" << fname << endl; return 0; } vector<Tuple> tuples; //从文件流中读入数据 for (int i = 0; i<dataNum && !infile.eof(); ++i)//判断是否到达文件尾部,以防止出现文件读取错误; { string str; getline(infile, str);//C++库函数。它会生成一个包含一串从输入流读入的字符的字符串; istringstream istr(str);//istringstream类用于执行C++风格的字符串流的输入操作; Tuple tuple(dimNum + 1, 0);//第一个位置存放记录编号,第2到dimNum+1个位置存放实际元素 tuple[0] = i + 1; for (int j = 1; j <= dimNum; ++j) { istr >> tuple[j]; } tuples.push_back(tuple); } cout << endl << "开始聚类" << endl; KMeans(tuples); system("pause"); return 0; }
相关文章推荐
- listview使用笔记
- HDU5661---Claris and XOR
- spoj CPTTRN3 - Character Patterns (Act 3)
- 链表c++代码的实现
- 树莓派设置静态IP地址遇到的问题
- 了解一些常用的文件系统和一些基础定义
- java io (java输入输出流)详解
- [C++ Calculator 项目] 基础运算实现
- 文库账号:xqoqk34523@163.com 文库密码:qmHr8N
- java循环练习:水仙花数
- 编码学习
- jvm可视化工具插件---Visual GC
- Fedora 22 安装无线网卡BCM43142
- 飛飛(二十一)求三角形的面积!
- DataTable 与 泛型
- eclipse的@Override快捷键
- recycleview学习03
- 运维老鸟教你安装centos6.5如何选择安装包
- Kafka源码分析之KafkaProducer发送数据send()方法
- android.os.build.clsass and class VERSION