SMOTE 算法
一、直观理解:在少数类邻居之间“连线造点”
类别不平衡时,模型会偷懒:全猜多数类也能拿到高准确率。朴素做法是复制少数类(随机过采样),但容易过拟合重复点。
SMOTE(Synthetic Minority Over-sampling Technique)在特征空间里,对每个少数类样本找其少数类近邻,在连线上随机插值,生成新的合成样本:
像在少数类居民区的道路上新建房屋,而不是把同一栋楼复制粘贴。这样决策边界会向多数类方向适度推移,提升对少数类的召回。
SMOTE 只是训练阶段的重采样;测试集必须保持真实分布,绝不能先造点再划分。
二、适用场景与优缺点
| 类型 | 说明 |
|---|---|
| 适用场景 | 少数类样本过少导致召回低;特征为连续数值;需要加强树模型/线性模型对少数类的学习;与代价敏感学习对照使用。 |
| 核心优点 | 缓解简单复制的过拟合;实现直观;有 Borderline-SMOTE、ADASYN 等改进;常提升少数类 F1。 |
| 局限与缺点 | 在重叠区域造点可能加重噪声;高维距离不可靠;类别特征需特殊处理;可能改变校准;不是因果增广。 |
使用前:确认评价用 PR-AUC/F1/召回而非准确率;只在训练折内 SMOTE;连续特征已处理缺失;考虑是否改用类权重更简单。
三、核心原理
1. 基本流程
对每个少数类样本 :
- 在少数类集合中找 近邻;
- 按过采样倍率,随机选近邻 ;
- 线性插值生成 ;
- 将合成样本并入训练集后拟合分类器。
2. 常见变体
| 变体 | 思想 |
|---|---|
| Borderline-SMOTE | 只对边界附近少数类造点 |
| ADASYN | 按局部难度自适应造点数量 |
| SMOTE-NC | 处理混合类别特征 |
| SMOTE + Tomek | 造点后再清边界噪声 |
3. 与其他策略关系
- 欠采样:删多数类,简单但丢信息;
- 类权重/Cost:不改数据,改损失;
- 阈值移动:先按原分布训练再调阈值;
- 组合:SMOTE 只在训练折,验证用真实比例。
四、关键决策
| 决策点 | 建议 |
|---|---|
| 目标比例 | 不必 1:1,可升到 1:2 或按验证 F1 选 |
| 5 常用;少数类极少时减小 | |
| 使用位置 | 仅训练集;CV 时每折内独立 SMOTE |
| 替代方案 | 先试 Cost/Prior/阈值,再考虑 SMOTE |
| 高维 | 先降维或特征选择 |
| 评价 | 宏 F1、少数类召回、PR 曲线 |
五、MATLAB 示例
MATLAB 没有永远稳定的同名 smote 函数于所有版本,赛题中可手写插值,或用 randsample 结合近邻。下面给出完整可运行的手写 SMOTE + 分类示例。
function [X2, y2] = smote(X, y, minorityLabel, targetCount, K)
% 对 minorityLabel 过采样至 targetCount
Xmin = X(y == minorityLabel, :);
nMin = size(Xmin, 1);
if nMin < 2
error("少数类样本不足以 SMOTE");
end
K = min(K, nMin - 1);
need = max(0, targetCount - nMin);
syn = zeros(need, size(X,2));
% 少数类内部两两距离
D = pdist2(Xmin, Xmin);
for i = 1:nMin
D(i,i) = inf;
end
for t = 1:need
i = randi(nMin);
[~, ord] = sort(D(i, :), "ascend");
nnIdx = ord(1:K);
j = nnIdx(randi(K));
lambda = rand;
syn(t, :) = Xmin(i, :) + lambda * (Xmin(j, :) - Xmin(i, :));
end
X2 = [X; syn];
y2 = [y; repmat(minorityLabel, need, 1)];
end
% ---- 演示 ----
rng(0);
Xmaj = randn(40, 2) + [0 0];
Xmin = randn(6, 2) + [2.0 2.0];
X = [Xmaj; Xmin];
y = [zeros(40,1); ones(6,1)];
% 注意:真实流程应先划分 train/test,再仅对 train SMOTE
cvp = cvpartition(y, "HoldOut", 0.3);
Xtr = X(training(cvp), :); ytr = y(training(cvp));
Xte = X(test(cvp), :); yte = y(test(cvp));
nMaj = sum(ytr == 0);
nMin = sum(ytr == 1);
fprintf("SMOTE 前:多数 %d, 少数 %d\n", nMaj, nMin);
[Xtr2, ytr2] = smote(Xtr, ytr, 1, nMaj, 3); % 少数类补到与多数相同
fprintf("SMOTE 后:多数 %d, 少数 %d\n", sum(ytr2==0), sum(ytr2==1));
% 对比:不处理 vs SMOTE
mdl0 = fitctree(Xtr, ytr, "MinLeafSize", 2);
mdl1 = fitctree(Xtr2, ytr2, "MinLeafSize", 2);
y0 = predict(mdl0, Xte);
y1 = predict(mdl1, Xte);
disp("原始训练集混淆矩阵:"); disp(confusionmat(yte, y0));
disp("SMOTE 后混淆矩阵:"); disp(confusionmat(yte, y1));
% 少数类召回
rec = @(yt,yp) sum(yt==1 & yp==1) / max(1,sum(yt==1));
fprintf("少数类召回:原始=%.2f, SMOTE=%.2f\n", rec(yte,y0), rec(yte,y1));
也可用 fitcensemble(...,"Method","RUSBoost") 等从算法侧处理不平衡,作为 SMOTE 的对照。
六、如何解释结果
- 前后类分布:造了多少点、目标比例;
- 指标变化:准确率可能下降,但少数类召回/F1 上升才是目标;
- 可视化:二维时可画合成点是否落入合理区域;
- 边界是否更噪:若精度全面下降,可能 K 过大或重叠严重;
- 报告:仅训练折使用、K、倍率、与 Cost-sensitive 对照。
七、常见误区
1. 先 SMOTE 整个数据集再 train/test split
严重泄漏:测试集含合成点或同源插值信息。
2. 用准确率证明 SMOTE “有效”
不平衡题准确率会骗人。
3. 在类别高度重叠处猛造点
等于在噪声区增稠,损害边界。
4. 对 one-hot 类别特征直接欧氏插值
可能产生无意义的小数类别编码,应使用 SMOTE-NC 或分开处理。
5. 忽略“阈值/代价”更简单的解
有时只调阈值就够,不必上采样。
八、小结
SMOTE 在少数类近邻连线上合成新样本,缓解不平衡学习中的信息稀释与简单复制过拟合,但必须严格限制在训练协议之内。
它是手段不是目的:最终要用少数类召回、F1、PR-AUC 与业务代价说话,并与类权重、欠采样、RUSBoost 等方案并列比较。