SMOTE算法
一、直观理解:在少数类邻居之间“插值造点”
分类问题里,若正类只有 2%、负类 98%,模型即使全猜负类也能拿到很高准确率,但正类(故障、欺诈、稀有病种)恰恰是真正要抓住的对象。这类数据称为类别不平衡。
最朴素的办法是把少数类样本复制多份(随机过采样)。缺点是:决策边界附近只是重复旧点,容易过拟合,模型背下这些重复坐标。
SMOTE(Synthetic Minority Over-sampling Technique)换了一种思路:
- 在少数类样本中任取一点 ;
- 找到它在少数类内部的 个近邻;
- 随机选一个近邻 ;
- 在 与 的连线上随机插值,生成新样本。
这就像在两个已知的“稀有事件”之间,沿着特征空间的线段补一些合乎局部邻域的新例子,而不是复印同一张卡片。
SMOTE 不创造标签以外的新信息奇迹,它只是按局部线性假设加密少数类分布,让学习算法有更多机会看到少数类区域。
二、适用场景与优缺点
| 类型 | 说明 |
|---|---|
| 适用场景 | 二分类/多分类中少数类样本偏少但并非极端稀缺;特征以数值型为主;关心召回率、F1、AUC 或代价敏感指标,而非笼统准确率。 |
| 核心优点 | 比简单复制更不易记忆单点;实现清晰;常能提升少数类召回;可与欠采样、类别权重联用。 |
| 局限与缺点 | 在重叠区域可能生成噪声点;对高维稀疏数据近邻意义变弱;类别特征需额外处理;若少数类本身是多个小团,参数不当会在空洞中造点;绝不能在划分测试集之前对全数据造点。 |
使用前检查:
- 先确认评价标准:准确率是否具有误导性;
- 少数类是否至少有数十个样本(过少时近邻不稳定);
- 特征是否已编码为数值,并考虑是否标准化后再算距离;
- 正负类是否严重重叠(重叠严重时,造点可能污染边界);
- 流水线是否保证:仅在训练折内拟合 SMOTE,验证/测试保持原始分布。
备选策略:类别权重(class weight)、欠采样、组合采样(SMOTE+ENN)、阈值移动、异常检测式建模。不平衡不一定非 SMOTE 不可。
三、核心原理:线段上的随机凸组合
设少数类样本集合为 。对 :
- 在 中计算 的 近邻(常用欧氏距离);
- 均匀随机选一个近邻 ;
- 生成
- 新样本标签与少数类相同;
- 重复直到少数类数量达到目标配比。
当 时回到 , 时到达 ,中间点是二者的凸组合。因此 SMOTE 默认假设:少数类在近邻连线上大致稠密、可线性过渡。
过采样倍率
若希望少数类从 增到大约 ,需要合成
个新点。常见目标是与多数类数量平衡,或达到某一比例(如 1:2),而非盲目 1:1。
边界与变体(了解)
- Borderline-SMOTE:更关注靠近分类边界的少数类点;
- ADASYN:按学习难度自适应分配合成数量;
- SMOTE-NC:可处理名义特征。
竞赛中先把经典 SMOTE 与“只调类别权重”做对照,再决定是否上更复杂变体。
四、关键决策
1. 近邻数
- 太小:合成点挤在极少邻域,多样性不足;
- 太大:可能连到另一子簇甚至噪声点,穿过多数类区域;
- 常用 ,并应随少数类样本量调整;样本很少时 必须小于 。
2. 采样比例
不必总是拉到与多数类完全相等。可先提到使训练可学的比例,再用验证集上的 F1/AUC/召回约束。过度造点会改变类别先验,带来过多假阳性。
3. 距离与缩放
欧氏近邻对量纲敏感。长度、金额、比率混用时,应先在训练折内标准化,再 SMOTE,再把同一 scaler 用于测试(测试不做 SMOTE)。
4. 只在训练折内合成
正确的交叉验证嵌套是:
for each fold:
在 train_fold 上 SMOTE
在 resampled_train 上拟合模型
在原始 valid_fold 上评估
错误做法:先对全数据 SMOTE,再划分训练测试——合成点的信息会泄漏进测试分布,分数虚高。
5. 与类别权重的取舍
逻辑回归、SVM、树模型常可直接设置少数类更高权重,无需造点。当特征空间中少数类支持不足、权重法仍召回很低时,再试 SMOTE。二者也可同时使用,但要防止过度偏向少数类。
五、MATLAB 示例
MATLAB 不同版本对现成 SMOTE 函数支持不一致。下面给出可运行的基础实现,演示核心思想;竞赛中若使用 Python,也可用 imbalanced-learn 的 SMOTE,但验证协议应相同。
function [Xaug, yaug] = smote(X, y, minorityLabel, k, targetCount)
%SMOTE 基础实现(数值特征)
% X: n×p, y: n×1, 将 minorityLabel 类过采样至 targetCount
X = double(X);
y = y(:);
idxMin = find(y == minorityLabel);
Xmin = X(idxMin, :);
nMin = size(Xmin, 1);
if nMin < 2
error("少数类样本过少,无法 SMOTE。");
end
k = min(k, nMin - 1);
nSyn = max(0, targetCount - nMin);
if nSyn == 0
Xaug = X; yaug = y; return;
end
% 少数类内部两两距离
D = squareform(pdist(Xmin, "euclidean"));
D(1:nMin+1:end) = Inf; % 排除自身
Xsyn = zeros(nSyn, size(X, 2));
for t = 1:nSyn
i = randi(nMin); % 随机选一个少数类种子
[~, order] = sort(D(i, :), "ascend");
nnIdx = order(1:k);
j = nnIdx(randi(k)); % 随机选一个近邻
u = rand; % U(0,1)
Xsyn(t, :) = Xmin(i, :) + u * (Xmin(j, :) - Xmin(i, :));
end
Xaug = [X; Xsyn];
yaug = [y; repmat(minorityLabel, nSyn, 1)];
end
% ===== 演示 =====
rng(5);
% 多数类:云团在左侧;少数类:右侧较小云团
Xmaj = [randn(80, 2) + [-2, 0]];
Xmin = [randn(15, 2) + [ 2, 0.5]];
X = [Xmaj; Xmin];
y = [zeros(80,1); ones(15,1)];
% 先划分,再仅在训练集 SMOTE
cv = cvpartition(y, "HoldOut", 0.3);
Xtrain = X(training(cv), :); ytrain = y(training(cv));
Xtest = X(test(cv), :); ytest = y(test(cv));
% 训练集内标准化(可选,但对欧氏近邻常有帮助)
mu = mean(Xtrain, 1);
sg = std(Xtrain, 0, 1); sg(sg == 0) = 1;
XtrainZ = (Xtrain - mu) ./ sg;
XtestZ = (Xtest - mu) ./ sg;
nMaj = sum(ytrain == 0);
% 将少数类补到与多数类相同数量
[Xbal, ybal] = smote(XtrainZ, ytrain, 1, 5, nMaj);
fprintf("训练原分布:多数 %d,少数 %d\n", sum(ytrain==0), sum(ytrain==1));
fprintf("SMOTE 后 :多数 %d,少数 %d\n", sum(ybal==0), sum(ybal==1));
% 用逻辑回归做个最小对照(需要 Statistics and Machine Learning Toolbox)
mdlRaw = fitclinear(XtrainZ, ytrain, "Learner", "logistic");
mdlSm = fitclinear(Xbal, ybal, "Learner", "logistic");
yhatRaw = predict(mdlRaw, XtestZ);
yhatSm = predict(mdlSm, XtestZ);
acc = @(yt, yp) mean(yt == yp);
% 少数类召回
rec = @(yt, yp) sum(yt==1 & yp==1) / max(1, sum(yt==1));
fprintf("原始训练 准确率=%.3f 少数类召回=%.3f\n", ...
acc(ytest, yhatRaw), rec(ytest, yhatRaw));
fprintf("SMOTE训练 准确率=%.3f 少数类召回=%.3f\n", ...
acc(ytest, yhatSm), rec(ytest, yhatSm));
figure;
gscatter(Xbal(:,1), Xbal(:,2), ybal);
title("训练集 SMOTE 后的二维分布(标准化空间)");
xlabel("z_1"); ylabel("z_2"); grid on;
说明:
- 若缺少
fitclinear,可只保留smote函数观察合成分布; - Python 用户可用:
from imblearn.over_sampling import SMOTE,但同样必须放在训练折内。
六、如何解释结果
SMOTE 本身不输出“机理系数”,解释重点在采样协议与指标变化。
应报告:
- 原始类别分布与不平衡比;
- 、目标数量/比例、距离度量、是否标准化;
- 合成是否仅在训练折完成;
- 合成前后训练分布;
- 测试集上的混淆矩阵、召回、精确率、F1、AUC(不要只报准确率);
- 与“不重采样 + 类别权重”基线的对比;
- 是否出现召回升、精确率显著降的代价,以及业务是否可接受。
若合成后训练集上效果很好、测试集很差,优先怀疑过拟合或泄漏,而不是继续加大过采样倍率。
七、常见误区
1. 先 SMOTE 再划分训练/测试
这是最常见也最严重的错误。测试集必须保持真实先验与原始样本,不能含合成点,也不能在造点时用到测试样本的近邻信息。
2. 只看准确率
不平衡数据上准确率可以长期虚高。应以少数类召回、F1、PR-AUC 或代价矩阵为准。
3. 少数类只有几个点仍强行 SMOTE
近邻统计毫无意义,合成点只是在重复噪声。此时更应考虑收集数据、异常检测或强先验模型。
4. 忽视类重叠
若少数类深埋在多数类中间,连线上的插值会生成大量模糊点,反而伤害模型。应先做可视化/近邻标签一致性检查。
5. 对 one-hot 后的高维稀疏向量盲目欧氏 SMOTE
类别 one-hot 与大量稀疏特征会扭曲距离。可对数值列单独 SMOTE,或使用能处理混合类型的变体,并审慎选择特征子集。
八、小结
SMOTE 的核心思想可以概括为:
在少数类样本与其少数类近邻的连线上随机插值,合成新的少数类样本,从而缓解类别不平衡,而不是简单复制旧点。
它是不平衡学习中的常用过采样基线,但效果取决于近邻是否有意义、类是否过度重叠,以及是否严格在训练折内实施。建模时应把 SMOTE 当作训练策略的一部分,与类别权重、合理评价指标和泄漏检查一起使用,而不是当成提高准确率的万能按钮。