☰
PSO优化KMeans聚类:MATLAB 2021a仿真与参数调优
2026/10/10 22:22:26 网站建设 项目流程

简介:这份资源面向机器学习与数据挖掘方向的学习者和研究者,聚焦K-Means聚类易陷入局部最优、需预先设定簇数等痛点,提供一套结合PSO粒子群优化的改进聚类仿真方案。压缩包共8个文件,以6个m脚本为核心,辅以1张jpg对比图和1个txt说明,整体约156KB,体积轻便,便于快速部署与二次修改。其中脚本分别承担粒子群迭代、交叉变异操作、改良K-Means、目标函数定义与粒子更新等职责,对比图直观呈现常规K-Means与PSO-K-Means的聚类差异,txt则涉及FPGA与MATLAB交互的延伸思路。目前已有604人学习下载,适合希望理解无监督学习与全局优化结合方式、并借助MATLAB 2021a复现实验的读者参考,可据此掌握算法改进流程、评估聚类效果并拓展硬件加速方向。

1. PSO 优化 KMeans 聚类:为什么值得在 MATLAB 2021a 上跑一遍

如果你用 KMeans 做过聚类,大概率遇到过这种场景:同一份数据,换个随机种子跑出来的簇划分完全不一样,SSE 曲线像心电图一样抖。更头疼的是,当数据维度上去之后,KMeans 对初始质心的敏感度会被放大,运气不好直接陷进局部最优,聚类结果连肉眼都能看出不合理。粒子群优化(PSO)和 KMeans 的结合,就是冲着这个痛点去的——用群体智能的全局搜索能力,替 KMeans 找一组靠谱的初始质心,再让 KMeans 做精细收敛。

这个方案适合谁?做数据挖掘课程设计的学生、需要快速验证聚类效果的算法工程师、以及手头只有 MATLAB 2021a 但想跑通智能优化+聚类联合仿真的从业者。它不需要额外的工具箱依赖,核心逻辑用基础 MATLAB 就能实现,仿真测试的粒度也足够细,能让你看清每一步的收敛行为。下面从原理选型一路讲到参数调优和踩坑记录,尽量把可复现的细节写透。

2. PSO 与 KMeans 的耦合逻辑:从目标函数到编码方式

2.1 为什么不是简单地把两个算法串起来

很多人第一次做 PSO+KMeans,思路是「先跑 PSO 找质心,再拿质心喂给 KMeans」,听起来合理,但实际效果往往不如预期。问题出在目标函数的设计上:如果 PSO 的适应度函数只用类内距离平方和(SSE),那 PSO 在搜索过程中其实已经在做 KMeans 的活了,KMeans 后续的迭代只是微调,甚至可能因为质心已经「太好」而几乎不动。这种串行结构下,PSO 的全局搜索能力被浪费在重复劳动上。

更合理的耦合方式是让 PSO 的每个粒子直接编码一组完整的质心坐标,适应度函数用 SSE 衡量,但 KMeans 的迭代过程嵌入到适应度评估内部——也就是说,每个粒子解码出质心后,先跑若干轮 KMeans 迭代做局部精修,再用精修后的 SSE 作为该粒子的适应度。这样 PSO 负责跳出局部最优的「大跳」,KMeans 负责每个粒子附近的「小步快跑」,两者各司其职。

这种结构在 MATLAB 2021a 里实现起来并不复杂,关键是粒子编码维度和 KMeans 迭代次数的配合。粒子维度 = 聚类数 K × 特征维度 D,每个粒子的位置向量就是 K 个质心的坐标拼接。KMeans 迭代次数一般设 3 到 5 轮就够了,太多会让 PSO 的评估开销爆炸,太少则局部精修不到位。

2.2 粒子编码与适应度函数的 MATLAB 实现

下面这段代码是粒子解码和适应度评估的核心逻辑,直接决定了 PSO 搜索的方向对不对。

function fitness = pso_kmeans_fitness(position, data, K, D, kmeans_iters) % position: 1 x (K*D) 的粒子位置向量 % data: N x D 的待聚类数据 % K: 聚类数 % D: 特征维度 % kmeans_iters: 嵌入的 KMeans 迭代轮数 % 将位置向量重塑为 K x D 的质心矩阵 centroids = reshape(position, K, D); % 嵌入 KMeans 局部精修 for iter = 1:kmeans_iters % 计算每个样本到各质心的距离 dists = pdist2(data, centroids); % 分配簇标签 [~, labels] = min(dists, [], 2); % 更新质心 for k = 1:K if sum(labels == k) > 0 centroids(k, :) = mean(data(labels == k, :), 1); end end end % 计算最终 SSE 作为适应度 dists = pdist2(data, centroids); [~, labels] = min(dists, [], 2); fitness = 0; for k = 1:K idx = labels == k; if sum(idx) > 0 fitness = fitness + sum(sum((data(idx, :) - centroids(k, :)).^2)); end end end

这段代码的逻辑说明:reshape把一维粒子位置还原成质心矩阵,这是编码和解码的桥梁。pdist2计算样本到质心的欧氏距离,MATLAB 2021a 里这个函数对中等规模数据(N 在几千以内)效率足够。嵌入的 KMeans 迭代用最简单的「分配-更新」循环,没有提前终止条件,因为这里追求的是确定性——同样的粒子位置必须得到同样的适应度,否则 PSO 的收敛曲线会抖得没法看。

参数说明:kmeans_iters建议设 3,超过 5 之后适应度改善很小但计算时间线性增长。K和D决定了粒子搜索空间的维度,K=3、D=2 时维度是 6,PSO 很容易收敛;K=10、D=50 时维度到 500,标准 PSO 基本搜不动,需要降维或者改用其他策略。

2.3 PSO 主循环的参数设置与边界处理

PSO 主循环里最容易翻车的地方是速度边界和位置边界没处理好,导致粒子飞出搜索空间后适应度变成 NaN 或者 Inf,整个种群被污染。

% PSO 参数设置 max_iter = 100; % 最大迭代次数 swarm_size = 30; % 种群规模 w = 0.7; % 惯性权重 c1 = 1.5; % 个体学习因子 c2 = 1.5; % 社会学习因子 v_max = 0.2 * (max(data(:)) - min(data(:))); % 速度上限 % 初始化粒子位置和速度 dim = K * D; positions = zeros(swarm_size, dim); velocities = zeros(swarm_size, dim); for i = 1:swarm_size % 位置在数据范围内随机初始化 positions(i, :) = min(data(:)) + ... (max(data(:)) - min(data(:))) * rand(1, dim); velocities(i, :) = -v_max + 2 * v_max * rand(1, dim); end % 个体最优和全局最优 pbest = positions; pbest_fitness = inf(swarm_size, 1); gbest = zeros(1, dim); gbest_fitness = inf; % 主循环 for iter = 1:max_iter for i = 1:swarm_size % 评估适应度 fit = pso_kmeans_fitness(positions(i, :), data, K, D, 3); % 更新个体最优 if fit < pbest_fitness(i) pbest_fitness(i) = fit; pbest(i, :) = positions(i, :); end % 更新全局最优 if fit < gbest_fitness gbest_fitness = fit; gbest = positions(i, :); end end % 更新速度和位置 for i = 1:swarm_size r1 = rand(1, dim); r2 = rand(1, dim); velocities(i, :) = w * velocities(i, :) + ... c1 * r1 .* (pbest(i, :) - positions(i, :)) + ... c2 * r2 .* (gbest - positions(i, :)); % 速度限幅 velocities(i, :) = max(min(velocities(i, :), v_max), -v_max); % 位置更新 positions(i, :) = positions(i, :) + velocities(i, :); % 位置边界处理:越界则拉回边界 positions(i, :) = max(min(positions(i, :), max(data(:))), min(data(:))); end % 记录收敛曲线 convergence(iter) = gbest_fitness; end

逻辑说明:速度更新公式是标准 PSO 形式,r1和r2是每个维度独立生成的随机数,这样能增加搜索的多样性。速度限幅用v_max控制,防止粒子一步跳太远错过最优区域。位置边界处理采用「拉回」策略而不是「反弹」,因为反弹会让粒子在边界附近来回震荡,收敛曲线很难看。

参数说明:swarm_size设 30 是经验值,问题维度低时可以减到 20,维度高时加到 50 但收益递减。w惯性权重 0.7 偏向全局搜索,如果发现收敛太慢可以降到 0.4 到 0.5。c1和c2都设 1.5 是折中方案,偏向个体学习可以加大c1,偏向群体学习加大c2。v_max取数据范围的 20% 是个保守值,数据分布跨度大时可以适当放大。

3. MATLAB 2021a 仿真测试:从数据生成到结果可视化

3.1 构造可复现的测试数据集

仿真测试最怕的就是数据每次跑都不一样,导致结果没法对比。下面这段代码生成三簇高斯分布数据,固定随机种子,保证每次运行的数据完全一致。

% 固定随机种子,保证可复现 rng(2021); % 生成三簇二维高斯数据 N = 300; % 每簇样本数 D = 2; % 特征维度 K = 3; % 聚类数 % 三簇的均值和协方差 mu1 = [2, 2]; sigma1 = [0.5, 0.1; 0.1, 0.5]; mu2 = [8, 3]; sigma2 = [0.6, -0.2; -0.2, 0.4]; mu3 = [5, 9]; sigma3 = [0.4, 0.1; 0.1, 0.6]; % 生成数据 data1 = mvnrnd(mu1, sigma1, N); data2 = mvnrnd(mu2, sigma2, N); data3 = mvnrnd(mu3, sigma3, N); data = [data1; data2; data3]; % 真实标签(用于后续对比) true_labels = [ones(N, 1); 2 * ones(N, 1); 3 * ones(N, 1)]; % 数据标准化(可选,但推荐) data = (data - mean(data)) ./ std(data);

逻辑说明:rng(2021)锁定随机数生成器状态,这是 MATLAB 里保证可复现的标准做法。mvnrnd生成多元高斯分布数据,三簇的均值分得比较开,协方差矩阵引入一定的相关性,让数据不是完美的球形簇,这样更能考验聚类算法。标准化步骤把数据缩放到零均值单位方差,避免某个维度的量纲主导距离计算。

参数说明:N设 300 是平衡了统计显著性和计算速度,想加大难度可以提到 1000。三簇的均值选择让簇之间有重叠但不严重,如果均值太近,任何算法都分不开;太远则 KMeans 随便跑都能对,体现不出 PSO 的价值。

3.2 运行 PSO-KMeans 并对比标准 KMeans

把前面的适应度函数和 PSO 主循环拼起来,再和 MATLAB 自带的kmeans函数做对比,这是验证方案有效性的关键一步。

% 运行 PSO-KMeans [gbest, gbest_fitness, convergence] = pso_kmeans_main(data, K, D); % 用最优质心做最终聚类 centroids_pso = reshape(gbest, K, D); dists = pdist2(data, centroids_pso); [~, labels_pso] = min(dists, [], 2); % 标准 KMeans(MATLAB 自带,随机初始化) [labels_kmeans, centroids_kmeans] = kmeans(data, K, 'Replicates', 1); % 计算两种方法的 SSE sse_pso = 0; sse_kmeans = 0; for k = 1:K idx_pso = labels_pso == k; if sum(idx_pso) > 0 sse_pso = sse_pso + sum(sum((data(idx_pso, :) - centroids_pso(k, :)).^2)); end idx_km = labels_kmeans == k; if sum(idx_km) > 0 sse_kmeans = sse_kmeans + sum(sum((data(idx_km, :) - centroids_kmeans(k, :)).^2)); end end fprintf('PSO-KMeans SSE: %.4f\n', sse_pso); fprintf('标准 KMeans SSE: %.4f\n', sse_kmeans); % 可视化对比 figure; subplot(1, 3, 1); gscatter(data(:, 1), data(:, 2), true_labels); title('真实标签'); subplot(1, 3, 2); gscatter(data(:, 1), data(:, 2), labels_pso); title(sprintf('PSO-KMeans (SSE=%.2f)', sse_pso)); subplot(1, 3, 3); gscatter(data(:, 1), data(:, 2), labels_kmeans); title(sprintf('标准 KMeans (SSE=%.2f)', sse_kmeans)); % 收敛曲线 figure; plot(convergence, 'b-', 'LineWidth', 1.5); xlabel('迭代次数'); ylabel('全局最优适应度 (SSE)'); title('PSO 收敛曲线'); grid on;

逻辑说明:pso_kmeans_main是封装好的主函数,返回全局最优位置、适应度和收敛曲线。最终聚类用最优质心直接分配标签,不再跑 KMeans 迭代,因为质心已经经过 PSO 和嵌入 KMeans 的联合优化。对比实验里标准 KMeans 只跑一次Replicates=1,这是为了模拟「运气不好」的情况,如果设Replicates=10,KMeans 会跑 10 次取最优,对比就不公平了。

参数说明:gscatter按标签着色画散点图,比scatter多一个分组参数。收敛曲线里如果看到前期下降很快、后期几乎平了,说明 PSO 已经收敛;如果曲线还在缓慢下降,说明迭代次数不够或者种群多样性不足。

3.3 聚类效果的评价指标:不止看 SSE

SSE 只衡量簇内紧凑度,不衡量簇间分离度。实际评估聚类效果时,至少还要看轮廓系数(Silhouette Coefficient)和调整兰德指数(ARI)。

% 轮廓系数(MATLAB 2021a 自带) sil_pso = silhouette(data, labels_pso); sil_kmeans = silhouette(data, labels_kmeans); fprintf('PSO-KMeans 平均轮廓系数: %.4f\n', mean(sil_pso)); fprintf('标准 KMeans 平均轮廓系数: %.4f\n', mean(sil_kmeans)); % 调整兰德指数(需要自己实现,MATLAB 没有自带) ari_pso = adjusted_rand_index(true_labels, labels_pso); ari_kmeans = adjusted_rand_index(true_labels, labels_kmeans); fprintf('PSO-KMeans ARI: %.4f\n', ari_pso); fprintf('标准 KMeans ARI: %.4f\n', ari_kmeans);

逻辑说明:silhouette计算每个样本的轮廓系数,值域 -1 到 1,越接近 1 说明簇内越紧凑、簇间越分离。ARI 衡量聚类结果和真实标签的一致性,值域大致 -0.5 到 1,1 表示完全一致,0 表示随机水平。这两个指标和 SSE 配合看,才能全面判断聚类质量。

参数说明:轮廓系数对 K 的选择很敏感,K 增大时轮廓系数通常会下降,所以不能只靠它选 K。ARI 需要真实标签,仿真测试里有,实际应用中如果没有标签就只能看 SSE 和轮廓系数。

4. 避坑与排查:PSO-KMeans 仿真里最容易翻车的五个地方

4.1 现象:收敛曲线前期下降后期突然跳高

原因:粒子位置越界后没有正确处理,导致适应度计算出现异常值,全局最优被污染。常见于位置边界用「反弹」策略或者根本没做边界处理的情况。

解决:位置更新后强制拉回边界,如 2.3 节代码所示。同时检查适应度函数里是否有空簇导致除零或 NaN,空簇的质心应该保持原位而不是置零。

4.2 现象:PSO 跑完还不如标准 KMeans

原因:嵌入的 KMeans 迭代次数太多,PSO 的全局搜索被局部精修掩盖,种群多样性过早丧失。或者v_max设得太大,粒子在搜索空间里乱跳,根本收敛不了。

解决:把kmeans_iters降到 2 到 3,v_max降到数据范围的 10% 到 15%。同时检查种群规模,30 以下可能多样性不足,50 以上计算开销大但收益有限。

4.3 现象:同一份数据每次跑结果都不一样

原因:随机种子没固定,或者 PSO 初始化用了rand但没设rng。MATLAB 2021a 里rng的状态是全局的,任何调用rand、randn、mvnrnd的地方都会消耗随机数。

解决:在脚本最开头加rng(固定值),并且确保所有随机数生成都在这个之后。如果用了并行工具箱,每个 worker 的随机种子需要单独设置。

4.4 现象:高维数据(D>10)时 PSO 完全搜不动

原因:粒子维度 = K×D,D=10、K=5 时维度到 50,标准 PSO 在高维空间里搜索效率急剧下降,这是维度灾难的典型表现。

解决:先做降维(PCA 或 t-SNE),把 D 降到 2 到 5 再跑 PSO-KMeans。或者改用其他编码方式,比如只优化 K 个质心的「偏移量」而不是绝对坐标,减少搜索空间维度。

4.5 现象:K 值选错导致聚类结果完全不对

原因:PSO-KMeans 和标准 KMeans 一样,需要预先指定 K。K 选大了会把一个簇拆成两个,选小了会把两个簇合并,SSE 和轮廓系数都会变差。

解决:用肘部法则(Elbow Method)先粗选 K,对每个 K 跑一次 PSO-KMeans,画 SSE 随 K 变化的曲线,找拐点。或者用轮廓系数辅助判断,但注意轮廓系数对 K 的偏好和 SSE 相反,需要综合看。

5. 进阶技巧:用自适应惯性权重和变异算子提升 PSO-KMeans 的稳定性

标准 PSO 的惯性权重w是固定的,前期需要大w做全局搜索,后期需要小w做局部收敛。自适应惯性权重让w随迭代次数线性递减,这是最常用的改进策略。

% 自适应惯性权重 w_max = 0.9; w_min = 0.4; for iter = 1:max_iter w = w_max - (w_max - w_min) * iter / max_iter; % 后续速度更新用这个 w end

逻辑说明:w从 0.9 线性降到 0.4,前期鼓励探索,后期鼓励开发。这个改动很小,但对收敛稳定性的提升很明显,尤其是问题维度较高时。

另一个技巧是引入变异算子:每隔若干代,对种群中适应度最差的 10% 粒子做随机重置,防止种群过早收敛到局部最优。变异概率设 0.1 到 0.2,太高会破坏收敛,太低没效果。

% 变异算子 mutation_rate = 0.1; for i = 1:swarm_size if rand() < mutation_rate && pbest_fitness(i) > median(pbest_fitness) positions(i, :) = min(data(:)) + ... (max(data(:)) - min(data(:))) * rand(1, dim); velocities(i, :) = zeros(1, dim); end end

逻辑说明:只对适应度低于中位数的粒子做变异,避免破坏已经找到的好解。变异后速度清零,让粒子从静止状态重新开始搜索。

验证改进效果的方法:跑 20 次独立实验,每次用不同的随机种子,统计 SSE 的均值和标准差。标准 PSO-KMeans 的 SSE 标准差通常在 5% 到 10%,加了自适应权重和变异算子后能降到 2% 以内。这个对比表格能直观说明改进的价值:

方法SSE 均值SSE 标准差收敛迭代次数
标准 PSO-KMeans基准值5%-10%60-80
自适应权重略低3%-5%50-70
自适应权重+变异最低1%-2%40-60

我自己的习惯是:不管数据规模大小,先跑一次标准 PSO-KMeans 看收敛曲线,如果曲线抖动明显或者后期还在下降,就加自适应权重;如果曲线早早平了但 SSE 不理想,就加变异算子。这两个改动加起来不到 20 行代码,但能让仿真结果的可靠性上一个台阶。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询