k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
K-Means 聚类算法的流程
以一个无标签数据集作为例子:
假如要把这堆数据分成 2 类,以我们的肉眼可以很明显地做出判断,因为这堆数据被一大段空白区域分隔开了
若是用 K-Means 的方法来进行操作,也可以很简单地把它们分类,并得出每类数据的聚类中心,步骤如下:
-
首先随机生成两个点,这两点叫做聚类中心:
-
簇分配:K-Means 算法中每次内循环的第一步要进行簇分配,遍历每个样本点,根据每个样本与红色聚类中心更近还是蓝色中心更近来将每个样本点分配个两个聚类中心之一:
简单来说,就是将靠近蓝色聚类中心的点染成蓝色,靠近红色聚类中心的点染成红色。因为聚类中心是随机生成的,所以这样分配肯定是不合理的,这一步只是为了确定分配的类数 -
移动聚类中心:K-Means 算法中每次内循环的第二步要移动聚类中心,要做是找到所有蓝点并计算出它们的均值,然后把蓝色聚类中心移到那里,红色聚类中心也是一样的操作。再把所有点重新染色:
这样移动聚类中心后,看起来似乎比上一步分配得更合理了,但是还没有达到理想中的情况,不过只要朝着正确的方向一步步走,肯定能成功 -
然后接着继续做簇分配和移动聚类中心,迭代多次,直到聚类中心的位置不再改变,就完成了最终两个点集的聚类,这就可以说 K-Means 算法已经聚合了:
以经验来看,这堆数据很适合分成 2 类,如果硬要分成 3 或更多类则很难凭人工做出选择,而且凭主管印象来看这也并不合理,但是用 K-Means 算法也可以进行分类
当然 K-Means 算法一般情况下多用于处理上面的情况,数据可以明显地被分成多个部分,而且它的优势是可以把每一个数据都明确地进行划分。但有时候一些数据集看起来并不能很好地分成几个簇,但依旧可以使用 K-Means 算法进行更精细的划分
案例解读
使用 MatLab 语言
K-means 分类函数
function [ idx, ctr ] = K_means( data, k, iterations )
% 3 个参数分别代表:待聚类的数据、类别数目、迭代次数
% m表示数据的规模,n表示数据的维度
[m, n] = size(data);
if k > m
disp('你需要聚类的数目已经大于数据的数目,无法聚类!');
return;
end
idx = zeros(m, 1);
ctr = zeros(k, n);
% nargin是用来判断输入变量个数的函数,这样就可以针对不同的情况执行不同的功能。
if nargin == 2
iterations = 0;
end
u = zeros(k, n); % 保存上一次的聚类中心
c = zeros(k, n); % 保存更新后的聚类中心
% 选定初始质心
t = 1;
for i=1:k
% 从第一个数据开始,每隔 m/k 间隔选取一个数据点,直至得到k个类别中心
u(i, :) = data(t, :);
t = t + m/k;
end
iteration = 1;
while true
% 计算每个数据点到类别中心的距离,把数据点归入到与之最近的类别中
for i=1:m
dis = zeros(k, 1); % dis保存每个数据点到k个类别中心的距离
for j=1:k
sum_dis = 0;
for t=1:n
sum_dis = sum_dis + (u(j, t) - data(i, t))^2;
end
dis(j) = sqrt(sum_dis);
end
% 找出数据点与k个类别中心中,距离最小的一个,该数据点归入到这一类中
[~, index] = sort(dis);
idx(i, 1:2) = data(i, :);
idx(i, 3) = index(1);
end
% 每一次聚类之后计算中心值将其作为新类别中心
for i=1:k
total_dis = zeros(1, n);
num_i = 0;
for j=1:m
if idx(j, 3) == i
for t=1:n
total_dis(1, t) = total_dis(1, t) + data(j, t);
end
num_i = num_i + 1;
end
end
c(i, :) = total_dis(1, :)/num_i;
end
% 给定了迭代次数并且已经迭代了iterations次,退出算法
if iterations ~= 0 && iteration == iterations
ctr = c;
break;
elseif iterations == 0 && norm(c-u) < 0.01
ctr = c;
break;
end
iteration = iteration + 1;
u = c;
end
构造数据验证
生成 4 类数据:
clc;
clear;
% 第一类数据
mu1 = [-2 -2]; % 均值
S1 = [0.5 0; 0 0.5]; % 协方差
data1 = mvnrnd(mu1, S1, 100);% 产生高斯分布数据
mu2 = [2 -2];
S2 = [0.5 0; 0 0.5];
data2 = mvnrnd(mu2, S2, 100);
mu3 = [-2 2];
S3 = [0.5 0; 0 0.5];
data3 = mvnrnd(mu3, S3, 100);
mu4 = [2 2];
S4 = [0.5 0; 0 0.5];
data4 = mvnrnd(mu4, S4, 100);
% 显示数据
figure();
hold on;
plot(data1(:,1), data1(:,2), 'k+');
plot(data2(:,1), data2(:,2), 'k+');
plot(data3(:,1), data3(:,2), 'k+');
plot(data4(:,1), data4(:,2), 'k+');
grid on;
图像如下:
可以看到数据被明显地分为 4 个部分,但它们之间的一些点不太好分类
使用 K-Means 聚类算法后:
data = [data1; data2; data3; data4];
% 数据聚类
[idx, ctr] = K_means(data, 4, 1000);
[m, n] = size(idx);
% 显示聚类后的结果
figure();
hold on;
for i=1:m
if idx(i, 3) == 1
plot(idx(i, 1), idx(i, 2), 'r.', 'MarkerSize', 12);
elseif idx(i, 3) == 2
plot(idx(i, 1), idx(i, 2), 'b.', 'MarkerSize', 12);
elseif idx(i, 3) == 3
plot(idx(i, 1), idx(i, 2), 'g.', 'MarkerSize', 12);
else
plot(idx(i, 1), idx(i, 2), 'y.', 'MarkerSize', 12);
end
end
grid on;
% 绘出聚类中心点,kx表示是交叉符
plot(ctr(:,1), ctr(:,2), 'kx', 'MarkerSize', 12, 'LineWidth', 2);
图像如下:
可以看到结果还是相当精确的,每个点都被明确划分