尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MATLAB实现k均值聚类欠采样:解决不平衡分类问题的实战指南

MATLAB实现k均值聚类欠采样:解决不平衡分类问题的实战指南 简介在机器学习分类任务中数据不平衡是常见挑战正负样本比例失衡会导致模型偏向多数类使少数类召回率极低。重采样技术是解决该问题的核心手段其中欠采样通过减少多数类样本量来平衡类别分布。然而随机欠采样易丢失关键信息且结果不稳定而基于聚类的欠采样方法能有效保留多数类的内在结构。k均值聚类作为一种经典的无监督学习算法可将多数类划分为多个簇并以簇中心代表原始样本从而在压缩数据规模的同时维持分布形态。该技术广泛应用于欺诈检测、故障诊断、医疗筛查等场景尤其适合处理高不平衡比数据的特征工程与模型训练。本文以MATLAB为载体详细讲解k均值聚类欠采样的原理、完整实现代码、与随机欠采样的实验对比及实际踩坑经验为论文实验与工程实践提供可直接复用的解决方案。 做分类建模的朋友十有八九都被不平衡数据折磨过。正负样本比例到了几十比一甚至几百比一的时候模型基本就废了——它会把所有样本都预测成多数类准确率看着挺高实际上对少数类一个都抓不住。这个“matlab源码集锦-k均值聚类的欠采样”标题下的内容就是解决这个问题的用k均值聚类对多数类做欠采样在压缩多数类样本量的同时尽量保留原始分布结构而不是简单粗暴地随机删样本。这篇文章我会把原理、MATLAB完整实现、评估对比和踩坑经验一次讲透适合正在做不平衡分类、论文实验或者竞赛特征工程的朋友参考。生成一个现实中的不平衡数据集写完函数之后用真实分类器做对比实验最后再聊几个我实际跑代码时遇到的坑。1. 首先搞懂为什么要用聚类欠采样而不用随机欠采样1.1 不平衡数据的“坑”到底在哪先用一个具体场景来说明。假设你在做信用卡欺诈检测绝大多数交易是正常的异常交易可能只占0.1%。如果把全部数据直接丢给分类器分类器发现只要把所有样本都判定为正常准确率就是99.9%。于是它学会了“偷懒”——对所有样本输出正常类别。这时候你拿这个模型上线去做风控等于没做。不平衡数据的问题本质上是先验概率严重偏移带来的学习困难。标准的分类算法包括逻辑回归、支持向量机、神经网络本质上都在优化整体准确率或者整体损失函数。当少数类极度稀少时它们对损失函数的贡献几乎被多数类淹没模型根本学不到少数类的边界模式。这就是为什么在欺诈检测、故障诊断、医疗筛查这些领域单一准确率指标会骗人真正要关注的是少数类的召回率。解决不平衡问题通常有三条路数据层面重采样、算法层面代价敏感学习、集成学习、评价层面用PR曲线、AUC。其中数据层面的重采样最常见因为它不限制后端模型无论后面接的是决策树、SVM还是深度学习都能用。重采样又分过采样把少数类复制或者插值变多和欠采样把多数类删掉一部分。欠采样的逻辑很简单既然多数类样本太多那就砍掉一些让两类样本数量趋于平衡。但问题来了砍哪些怎么砍这里面门道很深。1.2 随机欠采样的问题丢了信息还不稳定最直觉的做法就是随机欠采样——从多数类里随机抽出一部分样本数量和少数类对齐然后把两类合在一起训练。这种方案实现起来一行代码的事情但缺点也很致命。首先是信息丢失。随机删除是盲目的它不管哪些样本承载了关键的分类边界信息。如果多数类内部本身有子结构比如不同场景下的正常交易有不同的特征模式随机删除很可能把某些子结构整体抹掉。等模型训练出来遇到一个落在被删除子结构附近的新样本就会误判。其次是不稳定性。随机欠采样每次运行删除的样本都不同训练出的模型往往差异很大。你今天实验跑出来F1值是0.72明天跑一遍变成0.65论文里数据都没法复现。我之前用随机欠采样做一组对比实验同一个数据集跑了10次AUC的方差高到离谱评审直接质疑实验的可信度。第三当数据量本身不大时随机欠采样的信息损失会被进一步放大。比如多数类只有2000个样本少数类100个随机删除1900个多数类样本意味着97%的多数类信息被扔掉了靠剩下的100个多数类样本去代表2000个样本的分布哪个模型也扛不住。所以我们需要一种更“聪明”的欠采样方法让它保留多数类的整体分布形态而不是随机抹掉。1.3 聚类欠采样的基本思想聚类欠采样的核心思路非常直观先用聚类算法把多数类划分成若干个簇然后用簇的代表点替代簇内所有样本。因为每个簇内部的样本在特征空间上高度相似用一个代表点代表它们既能大大压缩样本数量又不会把整个分布结构丢掉。打个比方随机欠采样就像你要写一份2000人的居民情况报告为了把篇幅压缩到100人随机抽了100个人来写结果整个社区的结构信息全没了。聚类欠采样则是先把2000人按“住在哪个小区、什么年龄段、职业类型”分成100个群体每个群体派一个代表通过代表来反映这个群体的整体特征。后者显然能保留更多结构信息。把聚类跟欠采样结合起来的方案在文献里有多种变体但也因此带来一个研究点多数类样本分布比较复杂时一个簇内可能同时包含了边界区域和核心区域的信息。后面我会讲到如何在MATLAB里解决这个问题以及聚类中心与最近邻原始样本之间的取舍。2. 算法原理与整体设计思路2.1 k均值聚类是怎么工作的k均值聚类k-means clustering是最经典的原型聚类算法。它的目标是把n个样本划分到k个簇中使得每个样本到其所属簇中心的距离平方和最小。目标函数是J Σ(i1 to k) Σ(x∈Si) ||x - μi||²其中μi是第i个簇的中心即簇内样本的均值向量Si是第i个簇的样本集合。算法本身是一个迭代优化过程MATLAB里调用kmeans函数时内部干的活可以简化为四步从数据中随机选k个样本作为初始簇中心。计算每个样本到每个簇中心的欧氏距离把样本分给距离最近的簇。对每个簇重新计算簇内样本的均值作为新的簇中心。重复步骤2和3直到簇中心不再变化或者达到最大迭代次数。这个算法之所以被选来做欠采样是因为它天然适合处理“用一个代表点替代一群样本”这个任务。簇中心是所有样本的均值在欧氏空间里能很好地反映这个簇的“重心”。而且k均值在MATLAB里有高度优化的实现处理几万样本也是毫秒级的事。需要补充一点k均值对初始中心敏感可能收敛到局部最优解。MATLAB的kmeans函数提供了Replicates参数可以通过多次随机初始化取最优结果这个细节后面代码里会用到。2.2 用k均值做欠采样的核心流程把k均值聚类与欠采样结合标准流程可以分成五步第一步分离多数类和少数类。根据标签把原始数据集切成两份一份是少数类样本数量记为n_min另一份是多数类样本记为n_maj。分类建模时索引要记得保留后面还要合并新训练集。第二步设定聚类簇数k。最简单的设定方式是把k设为少数类样本的数量。这样聚类完之后每个簇取一个代表点恰好能得到n_min个多数类代表样本和少数类数量对齐。如果少数类实在太少比如小于30个可以适当把k放大乘一个系数比如k 2 * n_min让采样后的多数类样本数多于少数类具体倍数视分类阈值而定。第三步对多数类样本执行k均值聚类。把多数类特征矩阵X_maj传入kmeans指定聚类数为k。这里要注意特征标准化如果不同特征的量纲差异很大比如一个特征是交易金额几千到几万另一个特征是交易次数1到10那么金额会主导距离计算聚类结果会失真。跑kmeans之前我一般会先用zscore做标准化。第四步提取代表样本。聚类完成后从k个簇中提取代表点。有两种选择取簇中心向量或者取距离簇中心最近的原始样本点。后面我会单独分析这两种方式的优缺点。第五步合并数据。把k个代表样本全部打上多数类标签和少数类样本合并组成新的平衡数据集。这个数据集可以直接用来训练分类器。整个流程用MATLAB实现核心代码不超过20行但要知道每个参数的含义才能少踩坑。2.3 两个关键选择取聚类中心还是取最邻近原始样本这一步经常被忽视但实际上对实验结果影响很大。聚类完成后每个簇的代表点有两种取法。第一种直接用聚类中心簇均值。这是最常用的方式。聚类中心是一个虚构的向量它并不存在于原始数据里而是该簇所有样本的平均。优点是计算简单而且能最大程度保证代表点在特征空间里的“中心性”缺点是它可能落在数据密度较低的区域如果特征中有语义明确的离散属性比如“卡类型信用卡”均值可能变成不存在的取值比如“卡类型0.6”导致后续模型训练出的规则没有实际解释意义。第二种取离聚类中心最近的原始样本。这种方案的动机是保留特征的原始语义每个代表点都是真实存在的样本类别特征不会被破坏。缺点是计算上要额外算一次所有样本到簇中心的距离并取最小另外这个最近邻点未必能代表整个簇的多样性。我在实际项目中用的策略是如果特征全是连续数值型优先用聚类中心如果包含较多的类别型或有序型特征就选最近邻原始样本。为了兼顾两种需求我写的函数里加了一个centroid_mode参数用逻辑值控制两种取法测试时一目了然。2.4 为什么这个方法能保留原始分布结构k均值聚类之后每个簇内部的样本距离相近簇与簇之间的中心距离相对较远。这相当于把多数类样本压缩成k个“子群落”的代表。由于聚类是自适应的如果多数类内部天然存在多个子群体聚类算法会把它们分开每个子群体都有代表点保留下来。对比随机欠采样聚类欠采样的核心优势是保证“任何子结构都不会被完全抹掉”。数据里哪怕只有一个很小的子群体只要它与其他样本特征差异足够大聚类时就会形成独立簇从而被保留。这在随机欠采样里是小概率事件一个小群体极有可能被全部删掉。不过要注意这种保留是有代价的。k均值聚类本身对离群点敏感极端离群点可能自成一个小簇或者把一个大簇的中心拉偏。所以在做欠采样之前先做一次简单的离群点筛查会比较稳妥。3. MATLAB完整实现从数据生成到函数封装3.1 构建不平衡测试数据集我们先用MATLAB生成一个带有一年金融场景特征的模拟数据集方便后续验证算法效果。假设我们有1800个多数类样本和120个少数类样本比例15比1。为了模拟真实场景让多数类内部存在两个子群一个中心在(2, 2)另一个中心在(6, 2)。少数类分布在(4, 5)附近。这样做的好处是能直观看出聚类欠采样是否保留了两个子群的结构随机欠采样则很容易丢掉其中一个子群。% 设置随机种子保证实验可复现 rng(42); % 多数类样本两个子群各900个样本标准差0.6 maj1 randn(900, 2) * 0.6 [2, 2]; maj2 randn(900, 2) * 0.6 [6, 2]; X_maj [maj1; maj2]; y_maj ones(size(X_maj, 1), 1); % 少数类样本120个样本中心在(4, 5)标准差0.4 X_min randn(120, 2) * 0.4 [4, 5]; y_min zeros(size(X_min, 1), 1); % 合并成完整数据集 X [X_maj; X_min]; y [y_maj; y_min]; % 可视化原始数据 figure; scatter(X_maj(:,1), X_maj(:,2), 12, [0.7 0.7 0.7], filled); hold on; scatter(X_min(:,1), X_min(:,2), 20, [0.85 0.33 0.1], filled); xlabel(Feature 1); ylabel(Feature 2); title(原始不平衡数据集灰色多数类橙色少数类); grid on;运行这段代码你会看到多数类分布在两个水平方向上少数类在中间偏上的位置。少数类位于两个多数类子群之间这个设置在二维平面上能让分类边界变得很有意思。后面做分类对比时重点看少数类的召回率。3.2 k均值欠采样主函数实现接下来写核心函数。我把它封装成独立的.m文件输入原始数据、标签、少数类标签值即可输出平衡后的数据。函数名定为kmeans_undersample这样以后做其他实验也能直接调用。function [X_new, y_new, cent_idx] kmeans_undersample(X, y, minority_label, k_ratio, centroid_mode) % KMEANS_UNDERSAMPLE k均值聚类的欠采样 % 输入: % X - 特征矩阵n x d % y - 标签向量n x 1 % minority_label - 少数类的标签值数值型如0 % k_ratio - 聚类簇数相对于少数类样本数的比例默认1 % centroid_mode - 逻辑值true直接用聚类中心false取最近邻原始样本 % 输出: % X_new - 采样后的特征矩阵 % y_new - 采样后的标签向量 % cent_idx - 被选中的多数类样本索引可选 % 参数处理 if nargin 4 || isempty(k_ratio) k_ratio 1; end if nargin 5 || isempty(centroid_mode) centroid_mode true; end % 分离多数类和少数类 maj_idx find(y ~ minority_label); min_idx find(y minority_label); X_maj X(maj_idx, :); X_min X(min_idx, :); n_min size(X_min, 1); n_maj size(X_maj, 1); % 确定聚类数k k max(1, round(n_min * k_ratio)); if k n_maj % 如果k已经多于多数类样本数直接返回原数据 X_new X; y_new y; cent_idx (1:n_maj); return; end % 标准化用zscore注意保存均值和标准差如果需要反变换 [X_maj_std, mu, sigma] zscore(X_maj); % 对少数类也用同样的参数标准化保持特征空间一致性分类时用原始特征这里只是聚类 % 注意这里只对多数类做标准化即可因为求的是少数类的代表点 % k均值聚类 rng(42); % 固定种子让结果可复现 opts statset(MaxIter, 500, Display, off); [idx, C] kmeans(X_maj_std, k, Replicates, 3, Options, opts, Distance, sqeuclidean); if centroid_mode % 直接用聚类中心作为代表样本 X_rep C .* sigma mu; % 反标准化回原始特征空间 cent_idx []; else % 找每个簇距离中心最近的原始样本 X_rep zeros(k, size(X_maj, 2)); cent_idx zeros(k, 1); for j 1:k cluster_points X_maj(idx j, :); cluster_points_std X_maj_std(idx j, :); dists sum((cluster_points_std - C(j, :)).^2, 2); [~, min_pos] min(dists); X_rep(j, :) cluster_points(min_pos, :); % 找到对应的全局索引 temp_idx find(idx j); cent_idx(j) maj_idx(temp_idx(min_pos)); end end % 合并代表样本和少数类样本 X_new [X_rep; X_min]; y_new [ones(k, 1) * (1 - minority_label); y_min]; end这里有几个细节要特别注意。kmeans函数的输入是标准化后的多数类样本因为如果不做标准化特征量纲不一致会让聚类结果严重偏向取值大的特征。zscore标准化之后每个特征都是均值0方差1聚类才会比较公平。另一处是用Replicates参数跑3次取最优这样能从一定程度上缓解初始中心随机性带来的不稳定性。3.3 采样效果可视化分析写一个测试脚本调用上面的函数画图对比原始数据和采样后的数据分布。这个步骤对于验证算法是否符合预期特别重要肉眼检查分布形态总比只盯着数字指标更让人放心。% 调用欠采样函数 [X_bal, y_bal] kmeans_undersample(X, y, 0, 1, true); % 分离采样后的多数类和少数类 X_bal_maj X_bal(y_bal 1, :); X_bal_min X_bal(y_bal 0, :); % 画图对比 figure; subplot(1,2,1); scatter(X_maj(:,1), X_maj(:,2), 12, [0.7 0.7 0.7], filled); hold on; scatter(X_min(:,1), X_min(:,2), 20, [0.85 0.33 0.1], filled); xlabel(Feature 1); ylabel(Feature 2); title(原始数据); axis equal; grid on; subplot(1,2,2); scatter(X_bal_maj(:,1), X_bal_maj(:,2), 30, [0.2 0.4 0.8], filled, MarkerEdgeColor, k); hold on; scatter(X_bal_min(:,1), X_bal_min(:,2), 20, [0.85 0.33 0.1], filled); xlabel(Feature 1); ylabel(Feature 2); title(k均值聚类欠采样后); axis equal; grid on;跑完这段代码你应该能在右图中看到原始数据的两个多数类子群在采样之后都还留有代表点。蓝色的代表点分布在两个子群的上方形状上大致勾勒出了原始多数类的两个簇。少数类样本保持原样。这才是理想的欠采样效果。3.4 K值选择从少数类数量出发的自适应策略有人在调用上面函数时会问k为什么非要等于少数类的样本数如果少数类只有30个那多数类就被压缩到30个代表点是不是压得太狠了这个问题很实际。当少数类样本量极小时直接让k等于少数类数量多数类会损失太多信息。解决方法是调整k_ratio参数。比如设k_ratio 3聚类簇数就是90采样后多数类有90个样本而少数类是30个比例3比1。这个比例虽然仍然不平衡但远好于原始的100比1分类器能学到一些少数类模式同时又保留了更多多数类信息。这里给一个经验性的参考范围如果少数类样本数在100到500之间k_ratio设1到1.5比较合理可以让训练集类别完全平衡如果少数类样本数小于50可以把k_ratio提高到2到5让多数类保留更多样本后续在训练时通过调整分类阈值或类别权重来进一步控制平衡度。另外还有个细节有些文章会把少数类也做聚类然后用每个簇的中心或者最近邻点来代表少数类。这种做法的意图是让少数类内部的子群结构也被保留。但稳妥起见如果少数类样本量本身就不大直接保留原始样本反而是最好的选择因为任何压缩都可能丢失潜在的重要模式。4. 实战对比聚类欠采样 vs 随机欠采样 vs 不过采样4.1 用分类器做公平对比算法写出来好不好不能靠肉眼判断需要跑分类器做对比实验。选一个对数据分布敏感的简单分类器比如k近邻KNN或者决策树。太复杂的模型会掩盖采样方法之间的差异因为强模型对不平衡的鲁棒性可能更好。这里用决策树清晰直观分类边界容易理解。实验设计是这样的三种方案——直接用原始数据训练、随机欠采样后训练、k均值聚类欠采样后训练。所有方案都用一样的决策树参数用5折交叉验证评估。评估指标不能只看准确率重点是少数类的召回率、F1值和AUC。% 实验设置 rng(42); cv cvpartition(size(X, 1), KFold, 5); % 存储结果 metrics_names {Accuracy, Recall, Precision, F1, AUC}; results_raw zeros(5, 5); results_random zeros(5, 5); results_cluster zeros(5, 5); for fold 1:cv.NumTestSets % 训练集和测试集索引 train_idx training(cv, fold); test_idx test(cv, fold); X_train X(train_idx, :); y_train y(train_idx); X_test X(test_idx, :); y_test y(test_idx); % 方案1原始数据训练不过采样 mdl_raw fitctree(X_train, y_train, MaxNumSplits, 10); [labels_raw, scores_raw] predict(mdl_raw, X_test); % 方案2随机欠采样 maj_train_idx find(y_train 1); min_train_idx find(y_train 0); n_min length(min_train_idx); rand_sel randsample(maj_train_idx, n_min, false); X_train_rand [X_train(rand_sel, :); X_train(min_train_idx, :)]; y_train_rand [ones(n_min, 1); zeros(n_min, 1)]; mdl_rand fitctree(X_train_rand, y_train_rand, MaxNumSplits, 10); [labels_rand, scores_rand] predict(mdl_rand, X_test); % 方案3k均值聚类欠采样 [X_train_clu, y_train_clu] kmeans_undersample(X_train, y_train, 0, 1, true); mdl_clu fitctree(X_train_clu, y_train_clu, MaxNumSplits, 10); [labels_clu, scores_clu] predict(mdl_clu, X_test); % 计算指标函数在下面 results_raw(fold, :) compute_metrics(y_test, labels_raw, scores_raw(:,2)); results_random(fold, :) compute_metrics(y_test, labels_rand, scores_rand(:,2)); results_cluster(fold, :) compute_metrics(y_test, labels_clu, scores_clu(:,2)); end % 输出平均结果 fprintf(方案 Accuracy Recall Precision F1 AUC\n); fprintf(原始数据 %.4f %.4f %.4f %.4f %.4f\n, mean(results_raw)); fprintf(随机欠采样 %.4f %.4f %.4f %.4f %.4f\n, mean(results_random)); fprintf(k均值聚类欠采样 %.4f %.4f %.4f %.4f %.4f\n, mean(results_cluster)); % 指标计算函数 function m compute_metrics(y_true, y_pred, score_positive) tp sum(y_true 0 y_pred 0); fp sum(y_true 1 y_pred 0); fn sum(y_true 0 y_pred 1); tn sum(y_true 1 y_pred 1); accuracy (tp tn) / length(y_true); recall tp / (tp fn); % 少数类召回率 precision tp / (tp fp); % 少数类精确率 f1 2 * precision * recall / (precision recall); [~, ~, ~, auc] perfcurve(y_true, score_positive, 0); m [accuracy, recall, precision, f1, auc]; end需要说明因为随机欠采样每次抽的样本不同带随机性的方案跑出来的结果会有波动。为了更公平可以把随机欠采样那一块循环10次取平均。上面代码为了简洁没有这么做但你在自己实验时建议加上。4.2 评估指标别只看准确率上面代码里的compute_metrics函数同时算了5个指标。尤其注意Recall它计算的是少数类标签0被正确预测的比例。在不平衡场景里这个指标才是我们真正关心的核心指标。比如欺诈检测场景召回率代表“100笔真实欺诈里我们抓到了多少笔”哪怕误报率高一点也不能让诈骗溜走。准确率在不平衡场景下基本没有参考意义。在一个99比1的数据集里模型全预测多数类准确率也有99%但这等于什么都没做。所以我在实验结果展示里把它也列出来主要是让读者看到原始数据方案的准确率可能并不低甚至可能是三个方案里最高的这是因为交叉验证每一折里测试集仍然是不平衡的大多数样本本来就是多数类。如果把准确率当成主要指标来选方案会得到完全错误的结论。4.3 结果解读与选择建议在我构造的模拟数据上跑完你会发现几件事。第一原始数据的少数类召回率通常很低可能只有0.2到0.4大量少数类样本被误判成多数类。准确率反而可能高达0.9以上但没什么意义。第二随机欠采样之后召回率会上升但同时精确率会下降因为随机删掉了很多多数类样本模型对多数类的覆盖不足会把不少多数类样本误判成少数类。F1值可能提升到0.6左右。第三聚类欠采样的F1值通常能再高一点尤其在这个模拟数据上因为多数类的两个子群都被保留了模型学到的边界更接近真实分布。召回率提升的同时精确率不会掉太多。如果你的实验结果中聚类欠采样提升不明显也不要奇怪。聚类的优势在数据量较大、子结构较多时更突出如果数据本来就比较均匀聚类欠采样和随机欠采样的差距就会缩小。但无论如何聚类欠采样由于保留了子结构模型的稳定性会好很多多次运行的结果方差小这在论文实验和工程上线中都很重要。5. 常见问题与排查技巧实录5.1 kmeans运行报错或结果异常用MATLAB的kmeans时最常见的报错是“X must have more rows than the number of clusters.”意思是样本数必须大于聚类数。如果少数类样本量特别小而k_ratio设置得又大k可能大于多数类样本数就会触发这个错误。我在函数里已经加了保护如果k大于等于多数类样本数就直接返回原始数据。但在自己写代码时这个边界判断需要留意。另外会遇到“NaN”问题。如果特征矩阵里含有NaNkmeans直接跑不动。我一般会在预处理阶段先做缺失值填充最简单的用列均值填充。如果用zscore标准化时某一列方差为0即所有样本取值一样zscore会出现除零警告产生NaN。这种情况下这一列特征本来就没有区分能力建议直接删除。5.2 聚类结果不稳定kmeans的初始中心是随机选取的不同次运行可能收敛到不同结果。这也是为什么我的函数里加了rng(42)固定种子——保证每次运行结果一致方便复现。但如果数据量较大或者数据分布复杂固定种子也可能收敛到某个局部最优不代表全局最优。解决策略有两个一是加大Replicates参数值让它每次尝试更多初始点挑目标函数最小的结果二是改用层次聚类或者DBSCAN做聚类稳定性更好但计算量更大。实际项目中我通常根据时间预算来定快速实验用Replicates3正式实验用Replicates10。还有一个容易忽略的点如果聚类之后某些簇特别大某些簇特别小说明聚类效果可能不太好多数类内部子结构的规模差异极大。这时候直接用簇中心做代表会让小簇的完整信息被保留、大簇的信息压缩过度。解决办法是分层采样如果簇的样本量超过某个阈值把簇内再做一次子聚类保证每个子簇都有代表点。5.3 数据分布极端的几个变体思路当少数类样本数量极少比如只有20个k均值聚类欠采样后只有20个多数类代表点用它训练出的模型方差会很大。这时有两个变体思路。第一个是在聚类之后做加权而不是直接删掉簇内其他样本。具体做法是对多数类做聚类每个簇保留簇中心但在训练分类器时给每个簇中心赋权重权重等于该簇原始样本数。这样保留了全部多数类信息只是对信息做了压缩。这个方案在MATLAB里可以用fitctree的Weights参数实现。第二个是混合策略多数类用聚类欠采样少数类用SMOTE过采样。这样两类同时向中间靠拢既能压缩多数类规模又能丰富少数类多样性。SMOTE在MATLAB里没有内建函数但网上有很好的开源实现也可以自己写一个简单的版本。两者结合之后数据集规模适中两类相对平衡分类效果通常比单用某一种方法更稳定。5.4 特征标准化到底该怎么做这个问题看起来基础但特别容易搞错。标准的做法是先用训练集的多数类样本拟合标准化参数均值和标准差然后用同一组参数去标准化测试集。如果直接对整个数据集做标准化再做交叉验证会造成数据泄露测试集的信息在预处理阶段就被“偷看”了得到的评估结果会偏乐观论文里这是大忌。在欠采样流程里我通常这样处理先把数据切分成训练集和测试集只在训练集上做标准化和欠采样测试集保持原始特征空间。等模型训练好之后预测测试集之前再把测试集用训练集的均值和标准差做标准化。kmeans_undersample函数内部用的是zscore(X_maj)它计算的是多数类的均值和标准差这部分只涉及训练集不会泄露测试集信息。采样后生成的少数类代表点也是基于训练集少数类样本整个过程是干净的。6. 扩展方向与个人经验6.1 可以从哪些方向继续改进标题里的方法思路本身可以继续往多个方向扩展。第一是自动确定k值不用拍脑袋定k_ratio可以用轮廓系数或者肘部法在多数类上先跑一遍聚类找到合适的k再做欠采样。第二是换成其他聚类算法比如k-medoids它对离群点更鲁棒得到的代表点一定是原始样本天然适合保留特征语义。第三是结合集成学习在每一折交叉验证里独立做一次聚类欠采样然后把多个模型的预测结果做平均这种方法可以有效抵消聚类随机性和采样随机性带来的方差。另外一个方向是从距离度量入手。kmeans默认用欧氏距离如果数据特征包含大量类别型变量欧氏距离就不合适了。可以先对类别变量做独热编码或者改用Gower距离配合PAM聚类。但在MATLAB中实现Gower距离需要自己写距离矩阵计算量会大一些样本量在几万以内还可以接受。6.2 我在实际使用中的几点体会用聚类欠采样做了不少实验之后我的感受是数据分布形态比方法本身更重要。如果多数类内部确实存在明显的子群结构聚类欠采样带来的收益是实打实的如果多数类本身分布均匀那它跟随机欠采样的差距不会很大这时不如直接随机删省事。所以在决定用什么采样方法之前先做一次简单的数据可视化或者聚类分析看看多数类内部有没有结构再做决策。这个方法本身不复杂真正讲究的是在正确的时候使用它并且把评估指标想清楚。本文还有配套的精品资源点击获取
返回列表