1. K-means算法实现与评价指标全解析
在数据分析和机器学习领域,聚类算法是最基础也最常用的技术之一。今天我要分享的是一个基于MATLAB的K-means算法完整实现,不仅包含核心聚类功能,还集成了多种评价指标计算和可视化模块。这个实现特别适合需要快速验证聚类效果的研究人员和工程师使用。
1.1 为什么选择K-means算法
K-means算法因其简单高效而广受欢迎,特别适合处理数值型数据的聚类问题。它的核心思想是通过迭代优化,将数据点分配到K个簇中,使得每个数据点到其所属簇中心的距离平方和最小。在实际应用中,我经常用它来做客户分群、图像分割等任务。
这个MATLAB实现有几个亮点:
- 完整实现了K-means算法核心逻辑
- 集成了三种常用的聚类评价指标
- 提供了直观的可视化功能
- 代码结构清晰,易于扩展
2. 代码结构与核心实现
2.1 数据生成与预处理
matlab复制%% 数据生成与预处理
rng(42); % 固定随机种子
data = [mvnrnd([1,2], eye(2), 100);
mvnrnd([5,6], eye(2), 100);
mvnrnd([9,4], eye(2), 100)]; % 生成3类数据
% 数据标准化
data = zscore(data);
这段代码首先生成了三组二维高斯分布的数据,每组100个样本点,分别以(1,2)、(5,6)和(9,4)为中心。使用rng(42)固定随机种子可以确保每次运行结果一致,这在算法调试阶段非常有用。
数据标准化是聚类分析中不可忽视的一步。这里使用zscore函数将数据标准化为均值为0、标准差为1的分布。这一步很重要,因为:
- K-means算法对特征的尺度敏感
- 不同量纲的特征会影响距离计算
- 标准化后各特征对聚类结果的贡献更加均衡
提示:在实际项目中,如果数据包含异常值,建议先进行异常值处理再进行标准化,否则异常值会影响标准化结果。
2.2 K-means核心算法实现
matlab复制function [idx, centroids, sse] = run_kmeans(data, k, numRuns, distance)
[n, ~] = size(data);
bestSSE = inf;
bestIdx = [];
bestC = [];
for run = 1:numRuns
% 初始化质心
centroids = data(randperm(n,k), :);
% 迭代优化
prevC = centroids;
for iter = 1:100
% 分配样本
distances = pdist2(data, centroids, distance);
[~, idx] = min(distances, [], 2);
% 更新质心
for i = 1:k
centroids(i,:) = mean(data(idx==i,:), 1);
end
% 收敛判断
if norm(centroids - prevC) < 1e-5
break;
end
prevC = centroids;
end
% 计算SSE
sse_current = sum(sum((data - centroids(idx,:)).^2));
if sse_current < bestSSE
bestSSE = sse_current;
bestIdx = idx;
bestC = centroids;
end
end
end
这个run_kmeans函数实现了K-means算法的核心逻辑,包含以下几个关键部分:
-
质心初始化:采用随机选择的方式初始化质心。这里使用了
randperm函数从数据点中随机选择k个作为初始质心。 -
迭代优化:
- 分配阶段:计算每个数据点到各质心的距离,将其分配到最近的簇
- 更新阶段:重新计算每个簇的质心位置
- 收敛判断:当质心移动距离小于1e-5时停止迭代
-
多次运行:由于K-means对初始质心敏感,我们运行多次(numRuns)选择SSE最小的结果作为最终输出。
注意事项:迭代次数上限设为100次是个经验值,对于大多数数据集足够收敛。但如果数据量特别大或维度特别高,可能需要调整这个参数。
2.3 评价指标实现
2.3.1 轮廓系数(Silhouette Coefficient)
matlab复制function score = silhouette(data, idx)
n = size(data,1);
a = zeros(n,1);
b = zeros(n,1);
for i = 1:n
cluster = idx(i);
sameCluster = data(idx==cluster,:);
diffCluster = data(idx~=cluster,:);
a(i) = mean(pdist2(data(i,:), sameCluster, 'euclidean'));
if ~isempty(diffCluster)
b(i) = min(mean(pdist2(data(i,:), diffCluster, 'euclidean')));
else
b(i) = Inf;
end
end
score = mean((b - a)./max(a, b));
end
轮廓系数衡量的是同一簇内样本的紧密程度和不同簇间样本的分离程度。它的取值范围在[-1,1]之间:
- 接近1表示样本聚类合理
- 接近0表示样本在两个簇的边界上
- 接近-1表示样本可能被分配到了错误的簇
2.3.2 Calinski-Harabasz指数
matlab复制function score = calinski_harabasz(data, idx)
k = max(idx);
n = size(data,1);
ssb = 0;
ssw = 0;
for i = 1:k
cluster = data(idx==i,:);
mu = mean(cluster);
ssb = ssb + size(cluster,1)*(sum((mu - mean(data)).^2));
ssw = ssw + sum(sum((cluster - mu).^2));
end
score = (ssb/(k-1))/(ssw/(n-k));
end
Calinski-Harabasz指数通过计算簇间离散度(SSB)与簇内离散度(SSW)的比值来评估聚类质量。值越大表示聚类效果越好。这个指标特别适合用于确定最佳簇数K。
2.3.3 Davies-Bouldin指数
matlab复制function score = davies_bouldin(data, idx)
k = max(idx);
n = size(data,1);
distances = pdist2(data, data, 'euclidean');
maxRatio = zeros(k,1);
for i = 1:k
cluster = data(idx==i,:);
mu = mean(cluster);
for j = 1:k
if i ~= j
otherCluster = data(idx==j,:);
ratio = (mean(pdist2(cluster, mu, 'euclidean')) + ...
mean(pdist2(otherCluster, mu, 'euclidean'))) / ...
mean(pdist2(cluster, otherCluster, 'euclidean'));
maxRatio(i) = max(maxRatio(i), ratio);
end
end
end
score = mean(maxRatio);
end
Davies-Bouldin指数衡量的是簇内距离与簇间距离的比值。与前面两个指标不同,这个指数越小表示聚类效果越好。它的计算复杂度相对较高,但对簇的形状和大小不敏感。
3. 完整流程与可视化
3.1 主函数流程
matlab复制function kmeans_with_metrics()
%% 参数设置
maxK = 5; % 最大簇数
numRuns = 10; % 每个K值运行次数
distance = 'sqeuclidean'; % 距离度量
%% 运行K-means并计算指标
silhouetteScores = zeros(1, maxK-1);
chScores = zeros(1, maxK-1);
dbScores = zeros(1, maxK-1);
sse = zeros(1, maxK);
for k = 2:maxK
[bestIdx, bestC, bestSSE] = run_kmeans(data, k, numRuns, distance);
silhouetteScores(k-1) = mean(silhouette(data, bestIdx));
chScores(k-1) = calinski_harabasz(data, bestIdx);
dbScores(k-1) = davies_bouldin(data, bestIdx);
sse(k) = bestSSE;
end
%% 可视化结果
figure;
subplot(2,2,1);
gscatter(data(:,1), data(:,2), bestIdx);
hold on; plot(bestC(:,1), bestC(:,2),'kx','MarkerSize',15);
title('聚类结果可视化');
subplot(2,2,2);
plot(2:maxK, silhouetteScores,'bo-','LineWidth',2);
hold on; plot(2:maxK, sse(2:end)/max(sse),'r--');
title('轮廓系数与SSE对比');
legend('轮廓系数','SSE');
subplot(2,2,3);
bar([mean(silhouetteScores), mean(chScores), mean(dbScores)]);
set(gca,'XTickLabel',{'轮廓系数','CH指数','DB指数'});
ylabel('平均得分');
%% 输出最佳K值
[~, bestK] = max(silhouetteScores);
fprintf('推荐最佳簇数: %d\n', bestK+1);
end
主函数kmeans_with_metrics完成了以下工作:
- 设置算法参数(最大簇数、运行次数、距离度量)
- 对不同K值运行K-means算法
- 计算三种评价指标
- 可视化聚类结果和指标变化
- 根据轮廓系数推荐最佳簇数
3.2 可视化效果解读
-
聚类结果图:展示数据点在二维空间的分布和聚类结果,不同颜色代表不同簇,黑色"x"标记表示簇中心。
-
指标对比图:绘制轮廓系数和归一化SSE随K值变化的曲线。通常我们会选择轮廓系数最大且SSE下降开始平缓的K值。
-
指标评分图:用柱状图展示各指标的平均得分,方便直观比较不同评价指标的表现。
实操心得:在实际项目中,不同评价指标可能会给出不同的最佳K值建议。这时需要结合业务理解和多个指标综合判断,而不是单纯依赖某一个指标。
4. 应用场景与扩展
4.1 典型应用场景
-
客户细分:根据消费行为、人口统计等特征将客户分成不同群体,制定差异化营销策略。
-
图像处理:对图像像素进行聚类,可用于图像分割、颜色量化等任务。
-
异常检测:通过聚类识别远离所有簇中心的异常点。
-
生物信息学:分析基因表达数据,发现具有相似表达模式的基因簇。
4.2 扩展与优化方向
- 初始化方法改进:实现K-means++初始化策略,改善聚类质量和收敛速度。
matlab复制% K-means++初始化示例
centroids = zeros(k, size(data,2));
centroids(1,:) = data(randi(n),:);
for i = 2:k
D = pdist2(data, centroids(1:i-1,:)).^2;
D = min(D,[],2);
centroids(i,:) = data(find(rand < cumsum(D)/sum(D),1),:);
end
-
动态聚类:添加增量学习功能,支持新数据到来时更新聚类结果而不需要重新计算。
-
并行计算:利用MATLAB的并行计算工具箱加速大规模数据集的聚类过程。
-
核方法扩展:实现核K-means算法,使其能够处理非线性可分的数据。
-
自动化K值选择:实现更复杂的K值选择策略,如Gap统计量、肘部法则等。
5. 常见问题与解决方案
5.1 算法不收敛
问题表现:迭代次数达到上限仍未收敛。
可能原因:
- 学习率设置不当
- 数据存在大量噪声或异常值
- 初始质心选择不佳
解决方案:
- 增加最大迭代次数
- 加强数据预处理(去噪、标准化)
- 尝试K-means++初始化
- 增加运行次数(numRuns)
5.2 聚类结果不稳定
问题表现:每次运行结果差异较大。
可能原因:
- 随机初始化导致
- 数据本身簇结构不明显
- K值选择不当
解决方案:
- 增加运行次数,选择SSE最小的结果
- 尝试不同的K值
- 考虑使用层次聚类等确定性算法
5.3 高维数据聚类效果差
问题表现:在高维空间中聚类效果不理想。
可能原因:维度灾难,距离度量在高维空间失效。
解决方案:
- 先进行降维处理(PCA、t-SNE等)
- 使用更适合高维数据的距离度量(如余弦相似度)
- 考虑子空间聚类方法
5.4 评价指标矛盾
问题表现:不同评价指标给出的最佳K值建议不一致。
可能原因:不同指标关注聚类质量的不同方面。
解决方案:
- 结合多个指标综合判断
- 考虑业务实际需求
- 可视化聚类结果人工评估
6. 性能优化建议
-
向量化计算:尽量使用MATLAB的矩阵运算替代循环,特别是距离计算部分。
-
内存预分配:对于大型数组,预先分配足够内存避免动态扩展。
-
距离计算优化:对于大型数据集,可以考虑使用近似最近邻算法加速距离计算。
-
并行化:将不同K值或不同运行次数的计算分配到多个worker并行执行。
-
早期终止:实现更智能的收敛判断,在质心变化很小时提前终止迭代。
matlab复制% 示例:向量化距离计算
distances = sqrt(sum(bsxfun(@minus, reshape(data,[],1,size(data,2)), ...
reshape(centroids,1,[],size(centroids,2))).^2, 3));
7. 实际项目经验分享
在电商用户分群项目中,我们使用类似的K-means实现来分析用户行为数据。几点重要经验:
-
特征选择比算法更重要:精心选择的少量特征往往比大量原始特征效果更好。
-
标准化方式影响结果:尝试不同的标准化方法(如MinMax、RobustScaler等)可能会有意外收获。
-
可视化至关重要:即使在高维空间聚类,也尽量通过降维可视化来验证结果合理性。
-
业务理解不可或缺:最终的聚类结果需要业务专家验证,纯数据驱动的聚类可能没有实际意义。
-
迭代优化过程:聚类分析通常是一个迭代过程,需要多次尝试不同参数和预处理方法。
一个实际项目中的代码调整示例:
matlab复制% 业务特定距离度量
function d = business_distance(x, y)
% 核心行为特征使用欧氏距离
behavior_dist = norm(x(1:5)-y(1:5));
% 人口统计特征使用加权距离
demo_dist = 0.3*abs(x(6)-y(6)) + 0.7*(x(7)~=y(7));
d = behavior_dist + demo_dist;
end
这个自定义距离函数结合了业务知识,对不同类型特征赋予不同重要性,在实际项目中取得了比标准距离度量更好的效果。