什么是k-medoids
k-means受异常值或极端值的影响比较大,因此,针对k-means在离群噪声点表现出的不够鲁棒,提出了改进的k-medoids聚类方法,其核心思想在于“中心”,也就是说算法计算出的聚类中心一定是出现在样本中的点。
与k-means有何不同
不一样的地方在于中心点的选取,
- K-means中,我们将中心点取为当前cluster中所有数据点的平均值
- K-medoids中,我们将从当前cluster 中选取这样一个点——它到其他所有(当前cluster中的)点的距离之和最小——作为中心点。
步骤
这里以1维数据的聚类为例加以说明,首先介绍下数据定义:
vector<double> data; // 每个样本是一个1维数据
int clusterNum; // 要聚成几类(K)
vector<double> center; // 存放聚类中心的容器
vector<set<double> > cluster; // 存放聚类数据,cluster[i]表示第i类的数据是一个点的集合
1. 初始化聚类中心
任意选取K个对象作为medoids (O1,O2,…Oi…Ok) ,这一步和k-means是差不多的。
void medoids::initial(string fileName,int k)
{
getData(fileName);
if(data.size()<k)
clusterNum = data.size();
else
clusterNum = k;
// 应该随机初始化,这里只是为了渐变
for(int i=0;i<clusterNum;i++)
center.push_back(data[i]);
cluster.resize(clusterNum);
int s = cluster.size();
}
// 从文件中读取数据
void medoids::getData(string file)
{
ifstream in(file.c_str(),ios::in);
if(!in.is_open())
{
cout<<"open file failed!";
system("pause");
exit(1);
}
int size;
in>>size;
for(int i=0;i<size;i++)
{
double temp;
in>>temp;
data.push_back(temp);
}
in.close();
}
2. 将样本点分配到各个类中去
根据与medoid最相近的原则,这一步也可k-means是一样的。
// 对所有数据进行分配,指定聚类中心
void medoids::distribute()
{
cluster.clear();
cluster.resize(clusterNum);
int s = cluster.size();
for(int i=0;i<data.size();i++)
{
int pos = closet(data[i]);
// 向指定的中心插入该条数据
cluster[pos].insert(data[i]);
}
}
// 对于给定数据返回离自己最近的聚类中心
int medoids::closet(int num)
{
int pos = -1;
double min = 9.9e+20;
for(int i=0;i<center.size();i++)
if(min>fabs(center[i]-num))
{
min = fabs(center[i]-num); // 距离计算
pos = i;
}
return pos;
}
3. 每一类中,各自更新聚类中心
在每一类中,都尝试一下把其他的点作为聚类中心,他的消耗有多大,找一个使消耗最小的点作为新的聚类中心,有如下步骤。
1)计算cluster内所有样本点到其中一个样本点的曼哈顿距离和(绝对误差)
2)选出使cluster绝对误差最小的样本点作为质心
// 聚类中心的更新,返回是否还需更新
bool medoids::findNewCenter()
{
bool again = false;
for(int i=0;i<cluster.size();i++)
{
// 每一类单独去更新
double newCenter = calCost(cluster[i]);
if(newCenter!=center[i])
{
center[i]=newCenter;
again = true;
}
}
return again;
}
// 根据所属类点集,找到一个是成本最小的点
double medoids::calCost(set<double>& s)
{
set<double>::iterator ite = s.begin();
int minCost = 9.9e+20;
double newCenter = -1;
while(ite!=s.end())
{
set<double>::iterator ite1 = s.begin();
// 误差平方和
double total=0;
while(ite1!=s.end())
{
double delta = (*ite1)-(*ite);
total = total + delta*delta;
ite1++;
}
// 找出使得误差平方和最小的那个点作为新的中心
if(total<minCost)
{
minCost = total;
newCenter = (*ite);
}
ite++;
}
return newCenter;
}
- 4. 重复2、3步直到K个medoids固定下来
#include <iostream>
#include "medoids.h"
using namespace std;
void test(){
medoids m;
m.initial("data.txt", 3);
int max_iter = 100;
int iter = 0;
while (iter++<max_iter &&m.findNewCenter())
m.distribute();
cout << "iter : " << iter << endl;
m.print();
}
不足
k-medoids的运行速度较慢,计算质心的步骤时间复杂度是O(n^2),因为他必须计算任意两点之间的距离,而k-means只需平均即可。
注意:k-medoids的是曼哈顿距离和,k-means的则是欧式距离和