一.总结
1.今天小组进行了初步分工,我和另一个同学负责 k-means 聚类及其并行优化这一部分
2.在网上找了一份 k-means 聚类算法实现的c代码进行学习,以加深对其的理解
二.学习笔记
1.头文件及类定义
#include<iostream>
#include<vector>
#include<map>
#include<cstdlib>
#include<algorithm>
#include<fstream>
#include<stdio.h>
#include<string.h>
#include<string>
#include<time.h> //for srand
#include<limits.h> //for INT_MIN INT_MAX
using namespace std;
template<typename T> //类定义
class KMEANS
{
private:
vector< vector<T> > dataSet; //数据集,每一个数据点是一个有x和y坐标的二维点
vector< T > mmin,mmax;
int colLen,rowLen; //colLen:数据点维度;rowLen:数据点个数
int k; //聚成k个簇
vector< vector<T> > centroids; //k个簇的质心集合
typedef struct MinMax //存有最大最小值的结构体
{
T Min;
T Max;
MinMax(T min , T max):Min(min),Max(max) {}
}tMinMax;
typedef struct Node //标识了每个数据点所属的簇类别,以及到质心的距离值
{
int minIndex; //the index of each node
double minDist;
Node(int idx,double dist):minIndex(idx),minDist(dist) {}
}tNode;
vector<tNode> clusterAssment; //存储了所有数据点的所属簇和距离信息的数组
/*split line into numbers*/
void split(char *buffer , vector<T> &vec); //将一行切分成多个数值放入容器
tMinMax getMinMax(int idx); //获取最大最小值
void setCentroids(tMinMax &tminmax , int idx); //设置各个簇的质心
void initClusterAssment(); //初始化每个数据点的所属簇和距离信息
double distEclud(vector<T> &v1 , vector<T> &v2); //计算两个数据点的欧氏距离
public:
KMEANS(int k);
void loadDataSet(char *filename); //读入文件信息,初始化dataSet,colLen,rowLen
void randCent(); //随机生成k个初始聚类中心
void print(); //打印结果
void kmeans(); //核心函数,计算k个质心以及每个数据点的分类
};
2.函数实现
(1)构造函数初始化k
template<typename T>
KMEANS<T>::KMEANS(int k)
{
this->k = k;
}
(2)获取所有数据点的idx分量的最大值和最小值
//作用是获取所有数据点的idx分量的最大值和最小值
//数据点的idx分量是指数据点的下标为idx的那个值(每个数据点是一个向量vector)
template<typename T>
//返回值是类中定义的tMinMax结构体
typename KMEANS<T>::tMinMax KMEANS<T>::getMinMax(int idx)
{
T min , max ;
dataSet[0].at(idx) > dataSet[1].at(idx) ? ( max = dataSet[0].at(idx),min =
dataSet[1].at(idx) ) : ( max = dataSet[1].at(idx),min = dataSet[0].at(idx) ) ;
for(int i=2;i<rowLen;i++)
{
if( dataSet[i].at(idx) < min ) min = dataSet[i].at(idx);
else if( dataSet[i].at(idx) > max ) max = dataSet[i].at(idx);
else continue;
}
tMinMax tminmax(min,max);
return tminmax;
}
(3)设置k个质心的初始idx分量
template<typename T>
void KMEANS<T>::setCentroids(tMinMax &tminmax,int idx)
{
T rangeIdx = tminmax.Max - tminmax.Min;
for(int i=0;i<k;i++)
{
/* generate float data between 0 and 1 */
centroids[i].at(idx) = tminmax.Min +
rangeIdx * ( rand() / (double)RAND_MAX ) ;
}
}
(4)随机生成k个初始聚类中心函数randCent
调用到了函数(2)和(4)
template<typename T>
void KMEANS<T>::randCent()
{
//先将k个质心都初始化为元素为全0的colLen维向量
vector<T> vec(colLen,0);
for(int i=0;i<k;i++)
{
centroids.push_back(vec);
}
//set values by column
srand( time(NULL) ); //根据当前时间生成随机数
/* 调用setCentroids函数初始化k个质心的所有colLen个分量 */
for(int j=0;j<colLen;j++)
{
tMinMax tminmax = getMinMax(j);
setCentroids(tminmax,j);
}
}
(5) 数据点信息数组clusterAssment初始化
template<typename T>
void KMEANS<T>::initClusterAssment()
{
tNode node(-1,-1);
for(int i=0;i<rowLen;i++)
{
clusterAssment.push_back(node);
}
}
//将所有数据点所属簇编号以及到簇的质心的距离都初始化为-1
(6)计算两个数据点的欧氏距离
template<typename T>
double KMEANS<T>::distEclud(vector<T> &v1 , vector<T> &v2)
{
T sum = 0;
int size = v1.size();
for(int i=0;i<size;i++)
{
sum += (v1[i] - v2[i])*(v1[i] - v2[i]);
}
return sum;
}
(7)打印函数print
template<typename T>
void KMEANS<T>::print()
{
ofstream fout;
fout.open("res.txt");
if(!fout)
{
cout<<"file res.txt open failed"<<endl;
exit(0);
}
typename vector< vector<T> > :: iterator it = dataSet.begin();
typename vector< tNode > :: iterator itt = clusterAssment.begin();
for(int i=0;i<rowLen;i++)
{
typename vector<T> :: iterator it2 = (*it).begin();
while( it2!=(*it).end() )
{
fout<<*it2<<"\t";
it2++;
}
fout<<(*itt).minIndex<<endl;
itt++;
it++;
}
}
(8)核心函数kmeans
调用到了函数(5)(6)(7)
template<typename T>
void KMEANS<T>::kmeans()
{
initClusterAssment(); //初始化clusterAssment数组
bool clusterChanged = true; //用于检查迭代是否继续的标志
//终止迭代的另一种方式可以是限制迭代次数
while( clusterChanged )
{
clusterChanged = false;
//第一步:遍历每一个数据点,分别找到距离它们最近的簇
cout<<"find the nearest centroid of each point : "<<endl;
for(int i=0;i<rowLen;i++)
{
int minIndex = -1;
double minDist = INT_MAX; //??
//先计算数据点dataSet[i]到当前所有簇的质心的距离
for(int j=0;j<k;j++)
{
double distJI = distEclud( centroids[j],dataSet[i] );
if( distJI < minDist ) //遍历当前所有质心
{
minDist = distJI; //将minDist置为所有距离值最小者
minIndex = j; //将minIndex置为对应质心的编号
}
}
//更新dataSet[i]的信息
if( clusterAssment[i].minIndex != minIndex )
{
clusterChanged = true;//只要有一个数据点的分类情况还在变,就需要继续迭代
clusterAssment[i].minIndex = minIndex;
clusterAssment[i].minDist = minDist ;
}
}
//第二步:更新k个簇的质心
cout<<"update the centroids:"<<endl;
for(int cent=0;cent<k;cent++) //遍历k个质心,一轮for完成更新一个质心
{
vector<T> vec(colLen,0); //vec初始化为colLen个元素0的向量
int cnt = 0;
for(int i=0;i<rowLen;i++) //对于每个质心,遍历rowLen个数据点
{
/* 如果找到数据点在当前质心所表示的簇内 */
if( clusterAssment[i].minIndex == cent )
{
++cnt;
for(int j=0;j<colLen;j++)
{
vec[j] += dataSet[i].at(j);
}
}
}
/* 对一个质心处理结束后,vec中存储的是在该质心代表的簇内
所有数据点的总和,一一对应,cnt记录了该簇内的数据点个数 */
//更新当前质心
for(int i=0;i<colLen;i++)
{
if( cnt!=0 ) vec[i] /= cnt;
centroids[cent].at(i) = vec[i];
}
}//完成一个质心的更新
print();
}
(9)将一行数据切分成多个数值放入容器
template<typename T>
void KMEANS<T>::split(char *buffer , vector<T> &vec)
{
char *p = strtok(buffer," \t");
while(p!=NULL)
{
vec.push_back( atof(p) ); //atof将字符串转换为double
p = strtok( NULL," " );
}
}
(10)加载数据集函数
调用到了函数(9)
template<typename T>
void KMEANS<T>:: loadDataSet(char *filename)
{
FILE *pFile;
pFile = fopen(filename,"r");
if( !pFile )
{
printf("open file %s failed...\n",filename);
exit(0);
}
//init dataSet
char *buffer = new char[100];
vector<T> temp;
while( fgets(buffer,100,pFile) ) //每一次循环处理一行
{
temp.clear();
split(buffer,temp);
dataSet.push_back(temp);
}
//init colLen,rowLen
colLen = dataSet[0].size();
rowLen = dataSet.size();
}
(11)main函数
int main( int argc , char *argv[])
{
if(argc!=3)
{
cout<<"Usage : ./a.out filename k"<<endl;
exit(0);
}
char *filename = argv[1];
int k = atoi(argv[2]);
KMEANS<double> kms(k);
kms.loadDataSet(filename);
kms.randCent();
kms.kmeans();
return 0;
}
三.工作记录
暂无
注:代码来源http://t.csdn.cn/yTJ5Y