kmeans聚类的实现

Kmeans算法流程

从数据中随机抽取k个点作为初始聚类的中心,由这个中心代表各个聚类
计算数据中所有的点到这k个点的距离,将点归到离其最近的聚类里
调整聚类中心,即将聚类的中心移动到聚类的几何中心(即平均值)处,也就是k-means中的mean的含义
重复第2步直到聚类的中心不再移动,此时算法收敛
最后kmeans算法时间、空间复杂度是:
时间复杂度:上限为O(tKmn),下限为Ω(Kmn)其中,t为迭代次数,K为簇的数目,m为记录数,n为维数 
空间复杂度:O((m+K)n),其中,K为簇的数目,m为记录数,n为维数


影响算法准确度的因素

数据的采集和抽象

初始的中心选择

k值的选定

最大迭代次数

收敛值

度量距离的手段


在进一步阐述初始中心点选择之前,我们应该先确定度量kmeans的算法精确度的方法。一种度量聚类效果的标准是:SSE(Sum of Square Error,误差平方和)
SSE越小表示数据点越接近于它们的质心,聚类效果也就越好。因为对误差取了平方所以更重视那些远离中心的点。
一种可以肯定降低SSE的方法是增加簇的个数。但这违背了聚类的目标。因为聚类是在保持目标簇不变的情况下提高聚类的质量。
现在思路明了了我们首先以缩小SSE为目标改进算法。


二分K均值

为了克服k均值算法收敛于局部的问题,提出了二分k均值算法。该算法首先将所有的点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续划分,选择哪个簇进行划分取决于对其划分是否可以最大程度降低SSE值。
伪代码如下:
将所有的点看成一个簇
当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上面进行K均值聚类(K=2)
计算将该簇一分为二后的总误差
选择使得误差最小的那个簇进行划分操作



// spectral-cluster.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream> 
#include<vector>
#include<time.h>
#include<cstdlib>
#include<set>

using namespace std;

//template <typename T>
class kmeans
{
private:
	vector<vector<int>>dataset;
	unsigned int k;
	unsigned int dim;
	typedef vector<double> Centroid;
	vector<Centroid> center;
	vector<set<int>>cluster_ID;
	vector<Centroid>new_center;
	vector<set<int>>new_cluster_ID;
	double threshold;
	int iter;
private:
	void init();
	void assign();
	double distance(Centroid cen, int k2);
	void split(vector<set<int>>&clusters, int kk);
	void update_centers();
	bool isfinish();
	void show_result();
	void generate_data()
	{
		dim = 2;
		threshold = 0.001;
		k = 4;
		for (int i = 0; i < 300; i++)
		{
			vector<int>data;
			data.resize(dim);
			for (int j = 0; j < dim; j++)
				data[j] = double(rand()) / double(RAND_MAX + 1.0) * 500;
			dataset.push_back(data);
		}
	}
public:
	kmeans()
	{
		time_t t;
		srand(time(&t));
	}
	void apply();
};

//template <typename T>
void kmeans::init()
{
	center.resize(k);

	set<int>bb;
	for (int i = 0; i < k; i++)
	{

		int id = double(rand()) / double(RAND_MAX + 1.0)*dataset.size();
		while (bb.find(id) != bb.end())
		{
			id = double(rand()) / double(RAND_MAX + 1.0)*dataset.size();
		}
		bb.insert(id);
		center[i].resize(dim);
		for (int j = 0; j < dim; j++)
			center[i][j] = dataset[id][j];

	}
}
bool kmeans::isfinish()
{
	double error = 0;
	for (int i = 0; i < k; i++)
	{
		for (int j = 0; j < dim; j++)
			error += pow(center[i][j] - new_center[i][j], 2);
	}
	return error < threshold ? true : false;
}
void kmeans::assign()
{

	for (int j = 0; j < dataset.size(); j++)
	{
		double mindis = 10000000;
		int belongto = -1;
		for (int i = 0; i < k; i++)
		{
			double dis = distance(center[i], j);
			if (dis < mindis)
			{
				mindis = dis;
				belongto = i;
			}
		}
		new_cluster_ID[belongto].insert(j);
	}
	for (int i = 0; i < k; i++)
	{
		if (new_cluster_ID[i].empty())
		{
			split(new_cluster_ID, i);
		}
	}
}

double kmeans::distance(Centroid cen, int k2)
{
	double dis = 0;
	for (int i = 0; i < dim; i++)
		dis += pow(cen[i] - dataset[k2][i], 2);
	return sqrt(dis);
}

void kmeans::split(vector<set<int>>&clusters, int kk)
{
	int maxsize = 0;
	int th = -1;
	for (int i = 0; i < k; i++)
	{
		if (clusters[i].size() > maxsize)
		{
			maxsize = clusters[i].size();
			th = i;
		}
	}
#define DELTA 1
	vector<double>tpc1, tpc2;
	tpc1.resize(dim);
	tpc2.resize(dim);
	for (int i = 0; i < dim; i++)
	{
		tpc2[i] = center[th][i] - DELTA;
		tpc1[i] = center[th][i] + DELTA;
	}
	for (set<int>::iterator it = clusters[th].begin(); it != clusters[th].end(); it++)
	{
		double d1 = distance(tpc1, *it);
		double d2 = distance(tpc2, *it);
		if (d2 < d1)
		{
			clusters[kk].insert(*it);
		}
	}
	_ASSERTE(!clusters[kk].empty());
	for (set<int>::iterator it = clusters[kk].begin(); it != clusters[kk].end(); it++)
		clusters[th].erase(*it);

}

void kmeans::update_centers()
{
	for (int i = 0; i < k; i++)
	{
		Centroid temp;
		temp.resize(dim);
		for (set<int>::iterator j = new_cluster_ID[i].begin(); j != new_cluster_ID[i].end(); j++)
		{
			for (int m = 0; m < dim; m++)
				temp[m] += dataset[*j][m];
		}
		for (int m = 0; m < dim; m++)
			temp[m] /= new_cluster_ID[i].size();
		new_center[i] = temp;
	}
}

void kmeans::apply()
{
	generate_data();
	init();
	new_center.resize(k);
	new_cluster_ID.resize(k);
	assign();
	update_centers();
	iter = 0;
	while (!isfinish())
	{
		center = new_center;
		cluster_ID = new_cluster_ID;
		new_center.clear();
		new_center.resize(k);
		new_cluster_ID.clear();
		new_cluster_ID.resize(k);
		assign();
		update_centers();
		iter++;
	}
	show_result();
}

void kmeans::show_result()
{
	int num = 0;
	for (int i = 0; i < k; i++)
	{
		char string[100];
		sprintf(string, "第个%d簇:", i);
		cout << string << endl;
		cout << "中心为 (" << center[i][0] << "," << center[i][1] << ")" << endl;
		for (set<int>::iterator it = cluster_ID[i].begin(); it != cluster_ID[i].end(); it++)
		{
			sprintf(string, "编号%d   ", *it);
			cout << string << "(" << dataset[*(it)][0] << "," << dataset[*(it)][1] << ")" << endl;
			num++;
		}

		cout << endl << endl;
	}

	_ASSERTE(num == dataset.size());
}

int _tmain(int argc, _TCHAR* argv[])
{
	
	kmeans km;
	km.apply();

	system("pause");


	return 0;
}


K均值的时间复杂度为NKTD,其中,N代表样本个数,K代表k值,即聚类中心点个数,T代表循环次数,D代表样本数据的维度。 本算法的改进主要在以下方面: 一, 初始聚类中心点,传统的初始中心点是随机选择,由于K均值算法受初始中心点影响较大,为获得更好的效果,在本方法中,先将数据采用层次聚类的方法预处理,得到的k个中心点作为K均值算法的中心点。 二, 传统的聚类中心点更新是在结束一次循环后,本方法的聚类中心采用实时更新策略,即每次将一个模式归于一个新的聚类中心时,即立刻更新新的所属中心和原属聚类中心的中心值,增强算法的收敛性。 三, 为达到类内方差最小化,类类方差最大化这一原则,考虑到往往设定的K值不一定能很好实现聚类效果,故将以往的固定聚类中心改为一浮动区间。原有K为最小聚类中心个数,另设一聚类中心个数上限maxK。其具体实现如下: 1) 当一待聚类的模式得到其最近中心时,计算该聚类中心类内方差和将此模式归于该中心之后的类内方差,如果两者差别大于某设定阈值,则以该模式数据为基础,得到一新的聚类中心。 2) 当当前聚类中心个数等于设定的最大聚类中心时,合并最相邻的两个聚类。为使得到的聚类效果更为均衡,应该优先合并维度较小的聚类类别。
以下是使用对比学习和 Kmeans 聚类实现的一个简单示例,这里以图像数据为例,使用 PyTorch 实现对比学习,再使用 `scikit-learn` 进行 Kmeans 聚类。 ```python import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from sklearn.cluster import KMeans import numpy as np # 定义简单的编码器网络 class Encoder(nn.Module): def __init__(self): super(Encoder, self).__init__() self.fc1 = nn.Linear(784, 128) self.fc2 = nn.Linear(128, 64) def forward(self, x): x = x.view(-1, 784) x = torch.relu(self.fc1(x)) x = self.fc2(x) return x # 数据预处理 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) # 加载 MNIST 数据集 train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True) # 初始化编码器和优化器 encoder = Encoder() optimizer = optim.Adam(encoder.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() # 对比学习训练过程(简单模拟) num_epochs = 5 for epoch in range(num_epochs): for data, _ in train_loader: optimizer.zero_grad() features = encoder(data) # 这里省略了正负样本对的构建和损失计算细节 loss = torch.tensor(0.0) # 模拟损失 loss.backward() optimizer.step() # 提取特征 all_features = [] with torch.no_grad(): for data, _ in train_loader: features = encoder(data) all_features.extend(features.numpy()) all_features = np.array(all_features) # 使用 Kmeans 进行聚类 kmeans = KMeans(n_clusters=10) kmeans.fit(all_features) # 输出聚类标签 labels = kmeans.labels_ print("聚类标签:", labels) ``` 这个示例首先定义了一个简单的编码器网络,使用 MNIST 数据集进行对比学习的训练(这里只是简单模拟了训练过程)。然后提取所有样本的特征,最后使用 Kmeans 对这些特征进行聚类,并输出聚类标签。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值