DM&ML_note.5-K-means聚类算法
来源:互联网 发布:js中的层级选择器 编辑:程序博客网 时间:2024/06/05 02:27
这个学期要学DM&ML,用的是《数据挖掘算法原理与实现》王振武 本着造福同学的思想,开一个DM&ML的笔记系列,打算给书上的源代码添加一点注释,方便阅读和理解
- 前置知识要求
- 具体实现
- 感想
前置知识要求
C++构造函数的十种姿势【口胡】
具体实现
#include <iostream>#include <fstream>#include <cmath>#include <cstdlib>#include <ctime>using namespace std;/*hiro:注意这里自定义了一个vector的类,所以下文用的vector都与STL的无任何关系。*/// 数据对象,size为维度struct Vector { double* coords; // 所有维度的数值 int size; /*hiro:↓构造函数+实现*/ Vector() : coords(0), size(0) {} Vector(int d) { create(d); } // 创建维度为d的数据,并将各维度初始化为0 void create(int d) { size = d; coords = new double[size]; for (int i=0; i<size; i++) coords[i] = 0.0; } // 复制一个数据 void copy(const Vector& other) { if (size == 0) // 如果原来没有数据,创建之 create(other.size); for (int i=0; i<size; i++) coords[i] = other.coords[i]; } // 将另一个数据的各个维度加在自身的维度上 void add(const Vector& other) { for (int i=0; i<size; i++) coords[i] += other.coords[i]; } // 释放数值的空间 ~Vector() { if (coords)/*hiro:是不是显式的写!=NULL好一点,,,*/ delete[] coords; size = 0; }};// 聚类结构struct Cluster { Vector center; // 中心/引力数据对象 int* member; // 该聚类中各个数据的索引 int memberNum; // 数据的数量};// KMeans算法类class KMeans{private: int num; // 输入数据的数量 int dimen; // 数据的维数 int clusterNum; // 数据的聚类数 Vector* observations; // 所有数据存放在这个数组中 Cluster* clusters; // 聚类数组 int passNum; // 迭代的趟数public: // 初始化参数和动态分配内存 KMeans(int n, int d, int k, Vector* ob) : num(n) , dimen(d) , clusterNum(k) , observations(ob) , clusters(new Cluster[k]) { for (int x=0; x<clusterNum; x++) clusters[x].member = new int[n]; } // 释放内存 ~KMeans() { for (int k=0; k<clusterNum; k++) delete [] clusters[k].member; delete [] clusters; } void initClusters() { // 由于初始数据中心是任意的, // 所以直接把前clusterNum个数据作为NumClusters个聚类的数据中心 for (int i=0; i<clusterNum; i++) { clusters[i].member[0] = i; // 记录这个数据的索引到第i个聚类中 clusters[i].center.copy(observations[i]); // 把这个数据作为数据中心 } } void run() { bool converged = false; // 是否收敛 passNum = 0; /*hiro:的确我在看算法描述的时候就有这个疑惑 不排除会有不收敛的情况,不管怎么说都是逼近的一种算法嘛*/ while (!converged && passNum < 999) // 如果没有收敛,则再次迭代 // 正常情况下总是会收敛,passNum < 999是防万一 { distribute(); // 将数据分配到聚中心最近的聚类 converged = recalculateCenters(); // 计算新的聚类中心,如果计算结果和上次相同,认为已经收敛 passNum++; } } void distribute() { // 将上次的记录的该聚类中的数据数量清0,重新开始分配数据 for(int k=0; k<clusterNum; k++) getCluster(k).memberNum = 0; // 找出每个数据的最近聚类数据中心,并将该数据分配到该聚类 for(int i=0; i<num; i++) { Cluster& cluster = getCluster(closestCluster(i)); // 找出最接近的其中心的聚类 int memID = cluster.memberNum; // memberNum是当前记录的数据数量,也是新加入数据在member数组中的位置 cluster.member[memID] = i; // 将数据索引加入Member数组 cluster.memberNum++; // 聚类中的数据数量加1 } } int closestCluster(int id) { int clusterID = 0; // 暂时假定索引为id的数据最接近第一个聚类 double minDist = eucNorm(id, 0); // 计算到第一个聚类中心的误差(本程序中用距离的平方和作为误差) // 计算其它聚类中心到数据的误差,找出其中最小的一个 for (int k=1; k<clusterNum; k++) { double d = eucNorm(id, k); if(d < minDist) // 如果小于前最小值,将改值作为当前最小值 { minDist = d; clusterID = k; } } return clusterID; } // 索引为id的数据到第k个聚类中心的误差(距离的平方) double eucNorm(int id, int k) { Vector& observ = observations[id]; Vector& center = clusters[k].center; double sumOfSquare = 0; // 将每个维度的差的平方相加,得到距离的平方 for (int d=0; d<dimen; d++) { double dist = observ.coords[d] - center.coords[d]; // 在一个维度上中心到数据的距离 sumOfSquare += dist*dist; } return sumOfSquare; } // 重新计算聚类中心 bool recalculateCenters() { bool converged = true; for (int k=0; k<clusterNum; k++) { Cluster& cluster = getCluster(k); Vector average(dimen); // 初始的数据平均值 // 统计这个聚类中数据的总和(因为在构造函数中会将各维数值清0,所以可以直接加) for (int m=0; m<cluster.memberNum; m++) average.add(observations[cluster.member[m]]); // 计算各个维度的评价值 for(int d=0; d<dimen; d++) { average.coords[d] /= cluster.memberNum; /*hiro:依然是浮点数判断的问题,,, 我随便取个精度值*/ /*原代码: if(average.coords[d] != cluster.center.coords[d])*/ if(fabs(average.coords[d] -cluster.center.coords[d])<0.0000001) // 如果和原来的聚类中心不同 // 表示没有收敛 { converged = false; cluster.center.coords[d] = average.coords[d]; // 用这次的平均值作为新的聚类中心 } } } return converged; } // 获得第id个聚类 Cluster& getCluster(int id) { return clusters[id]; }};// 打印一个数据void printVector(ostream& output, const Vector& v){ for (int i=0; i<v.size; i++) { if(i != 0) output << ","; output << v.coords[i]; }}void partitionObservations(istream& input){ // 从input输入中获取数据 int n, dimen, k; // 文本文件中头三个数据分别是数据数量(n)、数据维度(dimen)和聚类数量(k) input >> n >> dimen >> k; // 创建存储数据的数值 Vector* obs = new Vector[n]; // 将数据读入数组 for (int i=0; i<n; i++) { obs[i].create(dimen); // 创建数据 // 依次读入各个维度的数值 for (int d=0; d<dimen; d++) { input >> obs[i].coords[d]; } } // 建立KMeans算法类实例 KMeans kmeans(n, dimen, k, obs); kmeans.initClusters(); // 初始化 kmeans.run(); // 执行算法 // 输出聚类数据,如果希望输出到文件中, // 将后面的output的定义改为下面的形式即可 // ofstream output("result.txt"); ostream& output = cout; for (int c=0; c<k; c++) { Cluster& cluster = kmeans.getCluster(c); output << "---- 第" << (c + 1) << "个聚类 ----\n"; // 显示第c个聚类 output << "聚类中心:"; printVector(output, cluster.center); output << '\n'; for (int m=0; m<cluster.memberNum; m++) { int id = cluster.member[m]; printVector(output, obs[id]); output << "\n"; } output << endl; } delete[] obs;}int main(){ const char* fileName = "observations.txt"; ifstream obIn(fileName); if (obIn.is_open()) partitionObservations(obIn); else cout << "open " << fileName << " is fail!" << endl; return 0;}
感想
这个算法真是简单啊。
而且代码上的注释也是很足。
好吧我拿大白话描述一下过程,大概就是给你一堆有N维数据的点,将这些点进行划分。对于一个二维平面而言,大概就是画几个没有交集的圈把不同的点区分开来。
用来衡量数据之间的“距离”在本例中恰好就是欧氏几何中经典的集合距离公式,所以理解起来十分的简单。当然我觉得这个函数也可以针对不同的场合换成其他的“评判函数”。
当然了,这份代码跑下来,对付那种极端值的处理应该不会很好,书上也提到了这点。
0 0
- DM&ML_note.5-K-means聚类算法
- DM&ML_note.6-K-中心点聚类算法
- DM&ML_note.7-神经网络聚类算法:SOM
- DM&ML_note.4-BP神经网络算法
- k-means聚类算法
- k-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-MEANS聚类算法
- k-means聚类算法
- K-means聚类算法
- 聚类算法 K-means
- K-means聚类算法
- K-means聚类算法
- C#与C++通过socket传送结构体
- 自定义View之文字游乐场(一)
- 机房收费系统(二)之下机退卡
- 弗洛伊德(Floyd)算法
- 对象导论
- DM&ML_note.5-K-means聚类算法
- Leetcode007--将字符串转换成整形
- Xamarin Android Fragment的两种加载方式
- hdu ---2005做题笔记(c++)
- 欢迎使用CSDN-markdown编辑器
- “光”的历史(一)
- 【JZOJ4814】【NOIP2016提高A组五校联考2】tree
- UVa1225 Digit Counting
- caffe-reduction layer