
简介面向机器学习与数据挖掘场景这份压缩包针对类别不平衡问题提供MATLAB环境下的SMOTE插值实现适合需要处理数据不平衡的算法工程师、数据科学家及科研人员。在医疗诊断、信用欺诈等任务中少数类样本往往承载关键价值SMOTE通过挑选少数类样本、计算k近邻并沿连线线性插值合成新样本从而改善分类模型对少数类的识别能力。资源共2个m文件大小约1KB一个为核心算法代码另一个为可直接运行的示例脚本便于在自带数据上快速验证效果。已有340人学习下载。代码同样适用于数值分析中的缺失值补全可在保持原始分布特征的前提下填充空缺避免简单删除带来的信息损失。读者可对照代码理解过采样原理并根据自身任务调整近邻数、采样倍率等参数灵活运用于不平衡分类、数据增强或数据清洗流程。合成样本与原始数据可直接合并构成平衡数据集用于后续模型训练与评估提升整体预测性能。1. 从类别不平衡到 SMOTE 插值一张混淆矩阵引发的数据补全需求第一次面对混淆矩阵里几乎被吞掉的少数类时第一反应通常是复制少数样本。复制不产生新信息模型只是把同样的点学得更重真正该做的是在特征空间中“补”出未被观测到的合理位置。SMOTE 干的正是这件事对某个少数类样本找出同类的 k 个近邻再在样本与近邻的连线上按比例取点合成新样本。从数值分析角度看这是标准线性插值从机器学习角度看这是目前最常用的过采样方法。下面用 MATLAB 实现一套可运行的 SMOTE 流程把插值参数、数据补全边界和验证方式一次讲清。适合在做分类模型、处理不平衡数据集或补全稀疏样本的工程师阅读。2. 插值视角下的 SMOTE 算法机制与参数选择2.1 SMOTE 的插值本质和数学表达SMOTE 全称是 Synthetic Minority Over-sampling Technique。设少数类样本集合为 X_min {x_1, x_2, ..., x_m}其中每个 x_i 是一个 d 维特征向量。对每个 x_i先计算它与集合内其他样本的欧氏距离取最小的 k 个作为近邻随后从 k 个近邻里等概率挑一个 x_zi沿两点连线生成新样本x_new x_i λ(x_zi − x_i)λ ∈ [0, 1]代码实现里 λ 用 rand 生成服从均匀分布。这个式子和数值分析里线性插值的标准写法只差一个名字线性插值是在已知函数值的两点之间估计中间位置的函数值SMOTE 则是在两个已知样本坐标之间生成一个新的样本坐标。差别在于 SMOTE 生成的坐标没有“真实值”可以做误差检验所以它不能被当作函数插值器而是样本分布插值器。还需要注意 λ 的边界。λ 0 时新样本就是 x_i 本身等价于复制少数类样本λ 1 时新样本等于近邻样本本身。SMOTE 的原版做法把 λ 限制在 [0, 1] 内不做外推。一旦让 λ 大于 1合成样本会跑到两点连线的延长线上那个位置完全依赖两个样本张成的方向推断分类器会把这种合成点当成强判决区域直接放大过拟合。在数据补全场景里外推点尤其危险因为补全的目的是让缺失区域有合理覆盖而不是制造新边界。所以参数设置的第一原则是λ 保持均匀分布随手写合理但永远不要让它越界。2.2 三个必须调的核心参数参数设置位置典型值对插值结果的影响k近邻数算法入参5决定插值线段来自多大局部范围k 太小方向过窄k 太大跨过簇结构N过采样倍率算法入参多数类数量 / 少数类数量控制合成样本总量决定少数类在训练集里的权重λ 分布随机数生成处U[0,1]控制新样本在线段上的位置影响合成样本与原样本的相似度k 5 是原版论文的默认值也是绝大多数工程场景的首选。当少数类样本非常少比如只有 15 条k 最大值不能超过 14否则近邻计算会拿到重复或无效索引。特征维度较高时欧氏距离会出现“球面集中”现象任何两点之间的距离都趋于相等近邻失去区分度。这时候应该先生成距离矩阵看分布发现区分度不行就先用 PCA 降维到 20 维左右再做 SMOTE而不是硬调 k。N 的取值要分场景讨论。多数类 800 条、少数类 40 条时N 19 可以把少数类补到 800 条与多数类完全平衡。但一次补到位会让合成样本主导少数类区域尤其是原始少数类本身就分布不均匀的时候。常见的做法是分轮补第一轮按 N 5 补训练模型看少数类召回率不足再补第二轮。对数据补全来说目标是让模型对少数类有稳定识别能力不是强制数学意义上的数量完全相等。λ 的采样方式在 MATLAB 里常见两个变体rand 均匀采样和 randn 带噪声偏移。后者生成的新样本会偏离线段本身适合特征带噪的数据但偏离程度不可控交叉验证里方差会变大。稳健做法是平时用 rand只在特征噪声明显时才引入高斯偏移。2.3 和经典数值插值方法的对照数值分析里最常听到的插值应用是地面站点中的风场数据插值到格点这是典型空间插值站点观测值已知格点坐标已知用线性插值或克里金估计格点处的风速。这种插值有明确的空间邻居关系和物理约束误差可以用留出站点验证。SMOTE 插值不是这种用途。它的输入没有“坐标-数值”二元结构只有样本在特征空间里的位置输出也不是某个物理量的估计值而是一组新的样本点。下表给出四类插值器的差异插值器已知量输出误差可校验性线性插值两点及函数值两点间函数估计有真实函数可对比多项式插值n1 个节点高次多项式全覆盖有龙格振荡风险样条插值节点与端部条件分段多项式逐段误差可控SMOTE 插值少数类样本坐标线段上的新样本坐标只有模型指标间接验证这个差异直接决定了后续的做法对 SMOTE 生成的坐标无法像数值分析那样计算绝对误差更不能用某个经典插值公式去“验证”它。评估只能落在两条路上合成样本与真实样本的距离分布是否一致以及训练出来的模型在测试集上的指标是否稳定。这两个方向在第四章和第五章会分别展开。3. 在 MATLAB 里从零实现 SMOTE 插值3.1 构造一份可复现的不平衡数据先用 randn 生成一份二维数据多数类 200 条、少数类 40 条。为了模拟真实场景少数类里混入一部分与多数类重叠的样本这些边界样本最容易在插值后把新样本送到多数类区域。% 固定随机种子保证每次运行得到相同数据 rng(42); % 多数类以 (3,3) 为中心的 200 条二维正态样本 X_major randn(200, 2) [3, 3]; % 少数类以 (1,1) 为中心的 30 条二维正态样本 X_minor randn(30, 2) [1, 1]; % 再补 10 条靠近多数类区域的边界样本模拟真实场景里的重叠 X_minor [X_minor; X_major(1:10, :) 1.2 * randn(10, 2)]; X [X_major; X_minor]; y [zeros(200, 1); ones(40, 1)]; % 看一眼类别分布 tabulate(y);代码里 rng(42) 是关键数值分析性质的代码必须可复现后续调参时如果每次数据都变没法判断指标变化来自 SMOTE 还是来自随机波动。tabulate 需要统计工具箱不想依赖时可以直接用 histcounts(y, [0, 1, 2])。少数类里那 10 条重叠样本很值得保留后续验证时你会发现SMOTE 的插值线一旦跨越这些点合成样本就会大量落在多数类一侧。3.2 SMOTE 主函数距离矩阵、近邻搜索与插值生成以下函数是完整的 SMOTE 实现不算工具箱内置函数直接用 pdist2 和 mink 组合完成。function [X_aug, y_aug] my_smote(X_minor, N, k) % my_smote: 对少数类样本做线性插值过采样 % 输入: % X_minor : m×d 矩阵少数类特征 % N : 正整数每条原样本生成 N 条新样本 % k : 正整数近邻个数 % 输出: % X_aug : (m m*N)×d 矩阵原始少数类 合成样本 % y_aug : 全 1 标签 m size(X_minor, 1); if m k 1 error(样本量不足至少需要 %d 条少数类样本才能计算 k%d 个近邻, k 1, k); end % 1. 全距离矩阵 D pdist2(X_minor, X_minor, euclidean); % 2. 主对角线置为 inf避免样本把自己当成邻居 D(1:m1:m*m) inf; % 3. 每行取 k 个最小距离mink 返回升序排列的索引 [~, neighIdx] mink(D, k, 2); % 4. 逐样本生成 N 条插值样本 X_new zeros(m * N, size(X_minor, 2)); row 0; for i 1:m for j 1:N n randi(k); % 随机挑一个近邻 neighbor neighIdx(i, n); lambda rand(); % 插值比例 U[0,1] row row 1; X_new(row, :) X_minor(i, :) lambda * (X_minor(neighbor, :) - X_minor(i, :)); end end % 5. 组装结果 X_aug [X_minor; X_new]; y_aug ones(size(X_aug, 1), 1); end逻辑上最重要的三处距离矩阵预计算、主对角线处理、λ 的位置。距离矩阵一次性算完避免内层循环里反复调用 pdist2数据量到几千条时性能差异非常明显。D(1:m1:m*m) inf 这行写成线性索引等价于把每个样本到自己的距离设为无穷大确保近邻列表里不混入自己。λ 直接乘在 (邻居 − 原样本) 方向上等价于线段上的线性插值。如果你的 MATLAB 没有统计工具箱pdist2 和 mink 会报错。可以用两层 for 循环加 sort 替换先算 D再用 sort(D, 2) 得到索引取每行第 2 到第 k1 列。速度慢一些但结果一致。生成新样本的循环里randi(k) 可能多次选中同一个邻居这是允许的因为 λ 不同生成的样本位置仍然不同但如果少数类只有很少几条同一个邻居被反复选中会让合成样本集中在那几个方向上此时建议把 k 调小。3.3 调用函数后先确认合成样本的位置在二维数据上画图是最直接的数值分析式验证。% 调用 my_smote原始 40 条少数类每条生成 3 条新样本 [X_aug, ~] my_smote(X_minor, 3, 5); X_syn X_aug(41:end, :); % 前 40 行是原始少数类 figure; plot(X_major(:, 1), X_major(:, 2), k., MarkerSize, 6); hold on; plot(X_minor(:, 1), X_minor(:, 2), bo, MarkerSize, 8); plot(X_syn(:, 1), X_syn(:, 2), r, MarkerSize, 6); legend(多数类, 原始少数类, SMOTE 新样本);画完图之后重点看两类位置红点是否大致落在蓝色圆点连成的狭窄区域内以及有没有大量红点越过黑色点群。如果红点大规模混入多数类区域说明少数类样本本身重叠度过高需要减少边界样本的权重或者改用过滤边界样本的策略。如果红点全部集中在少数几个方向说明某个邻居被反复选中这时把 k 调大能增加方向多样性。4. 把 SMOTE 与数据补全接进不平衡分类建模流程4.1 数据补全前先做缺失值填补和标准化SMOTE 用的距离是欧氏距离对特征尺度极其敏感。特征 A 取值范围 0 到 1特征 B 取值范围 0 到 1000 时近邻选择几乎完全由特征 B 主导插值出来的新样本在特征 B 上的差异会被放大在特征 A 上的差异几乎为零。所以 SMOTE 必须放在缺失值填补和标准化之后。一个通用的数据补全流程是读取数据 → fillmissing 填补缺失值 → zscore 标准化 → SMOTE → 训练模型 → 交叉验证。缺失值落在少数类样本上时尤其麻烦因为欧氏距离无法处理 NaN距离矩阵会出现整行 Inf。用中位数填补是最稳妥的做法对边界样本的扰动最小。% 替换每一列中的 NaN 为该列中位数 for j 1:size(Xraw, 2) col Xraw(:, j); idxNaN isnan(col); if any(idxNaN) col(idxNaN) median(col(~idxNaN), omitnan); Xraw(:, j) col; end end % 标准化零均值单位方差 mu mean(Xraw); sd std(Xraw); sd(sd 0) 1; % 零方差特征直接置为 1避免产生 NaN X_std (Xraw - mu) ./ sd; % 提取少数类并设置倍率 minor_idx find(y 1); N floor(sum(y 0) / sum(y 1)) - 1; if N 1, N 1; end [X_aug, y_aug] my_smote(X_std(minor_idx, :), N, 5);填补缺失值时优先中位数而不是均值因为少数类样本的分布往往有偏均值会拉低边界样本的位置。标准化之后SMOTE 生成的新样本落在标准化空间里训练出的模型参数也是在标准化空间里的预测新数据时要用同一组 mu 和 sd 做变换。这是最容易被忽略的步骤预测阶段忘掉标准化的一致处理等效于把插值结果直接扔进一个错误坐标系里。4.2 用交叉验证评估 SMOTE 的真实增益SMOTE 是数据补全过程评估它是否有效必须做交叉验证。这里有一个关键禁忌SMOTE 绝对不能放在划分数据集之前执行。如果把全量数据做了 SMOTE 再划分训练集和测试集测试集会被合成样本污染模型是在包含测试集分布信息的数据上训练出来的指标虚高且无法复现。正确的做法是在每一折的训练子集内部执行 SMOTE测试子集保持原始样本不动。rng(7); cv cvpartition(y, KFold, 10); auc_org zeros(cv.NumTestSets, 1); auc_smt zeros(cv.NumTestSets, 1); % 用 AdaBoostM2 做基分类器弱学习器为深度 4 的决策树模板 mdlSpec (Xt, yt) fitcensemble(Xt, yt, Method, AdaBoostM2, ... Learners, templateTree(MaxNumSplits, 4)); for f 1:cv.NumTestSets tr training(cv, f); te test(cv, f); % 对照组原始训练子集 mdl1 mdlSpec(X_std(tr, :), y(tr)); [~, s1] predict(mdl1, X_std(te, :)); [~, ~, ~, auc_org(f)] perfcurve(y(te), s1(:, 2), 1); % 实验组对训练子集内的少数类做 SMOTE cnt1 sum(tr y 1); cnt0 sum(tr y 0); X_aug X_std(tr, :); y_aug y(tr); if cnt0 cnt1 N floor(cnt0 / cnt1) - 1; if N 1, N 1; end [X_b, ~] my_smote(X_std(tr y 1, :), N, 5); X_syn X_b(cnt1 1:end, :); % 只取合成样本跳过原始少数类 y_syn ones(size(X_syn, 1), 1); X_aug [X_aug; X_syn]; y_aug [y_aug; y_syn]; end mdl2 mdlSpec(X_aug, y_aug); [~, s2] predict(mdl2, X_std(te, :)); [~, ~, ~, auc_smt(f)] perfcurve(y(te), s2(:, 2), 1); end fprintf(原始 AUC: %.4f ± %.4f\n, mean(auc_org), std(auc_org)); fprintf(SMOTE AUC: %.4f ± %.4f\n, mean(auc_smt), std(auc_smt));代码里的核心点在X_b(cnt1 1:end, :)这一行。my_smote 返回的前 cnt1 行是原始少数类样本如果把这部分再追加进 X_aug少数类会被重复一遍指标变好不能说明 SMOTE 有效只能说明重复数据对模型的加权有效。这里只追加合成部分训练集保证每条真实样本只出现一次。实际工程里 AUC 提升一般不会太大尤其是边界样本本来就多的数据集。更应该关注的是少数类召回率。把 perfcurve 的输出改成 [~, ~, ~, tpr, fpr] 可以单独看召回率如果 SMOTE 后召回率上升但精度明显下降说明合成样本把多数类区域吞掉了这时要做的是调小 N 或者过滤边界样本。4.3 数据补全策略选择什么时候用 SMOTE什么时候改用类别权重SMOTE 不是所有不平衡场景的最优解。补全数据的代价是额外引入合成样本而这些样本没有真实观测支撑。下面这组选择标准来自实际项目经验场景做法理由少数类只有 5 到 10 条先聚类确认不是离群点再做 SMOTEk 取 3样本过少时距离不稳定小 k 降低跨簇插值风险多数类 / 少数类比例超过 100分轮 SMOTE 加成本敏感学习一次拉到平衡会让合成样本主导决策面缺失值集中在少数类特征列先 fillmissing 再 SMOTESMOTE 的线路上存在 NaN 时距离矩阵直接失效特征全部是类别型改用 SMOTE-NC 或类别权重连续距离不适合类别特征直接插值会产生不存在的组合类别权重方案比 SMOTE 省事做法是给少数类样本更高的误分类惩罚fitcsvm 可以传 Cost 参数fitcensemble 可以调 Prior。SMOTE 的优势在于生成的新样本让模型的局部几何信息更丰富对树模型和线性模型都有帮助代价是合成样本可能泄漏到测试集区域导致验证指标失真。用第四章开头提到的数据补全流程再配合交叉验证是判断该不该用的唯一标准。5. 验证 SMOTE 插值效果的三种边界测试5.1 合成样本距真实样本的最近距离检验对每个合成样本计算到最近真实少数类样本的欧氏距离再计算真实少数类样本之间的最近邻距离比较两个分布的 90 分位数。若合成样本的 90 分位数明显偏高说明插值线段跨越了簇边界合成样本进入了多数类地域。D_real pdist2(X_minor, X_minor); D_real(1:size(D_real,1)1:end) inf; real_nn min(D_real, [], 2); D_syn pdist2(X_syn, X_minor); syn_nn min(D_syn, [], 2); disp(prctile(real_nn, [10 50 90])); disp(prctile(syn_nn, [10 50 90]));经验判断是合成样本的 90 分位数超过真实样本的 1.5 倍时优先调小 k再看是否需要做边界采样。5.2 过采样倍率 N 的敏感性测试对 N 做网格扫描N 分别取 1、2、3、5、8在固定交叉验证折上记录 AUC、少数类召回率和精度。N 增大到某个临界点后召回率可能不再上升精度反而快速下跌这个临界点就是这个数据集的合理补全上限。5.3 边界样本过滤的快速实验提前计算每个少数类样本的 k 近邻中多数类占比占比超过一半的样本标记为边界样本生成合成样本时跳过它们。这个过滤法不需要额外工具箱直接在近邻索引上统计即可。边界占比0 到 0.30.3 到 0.5大于 0.5处理方式正常插值插值但不作为线段端点跳过不做插值最终判断不看单一指标而是看少数类召回率与精度的平衡是否比原始数据更好如果 SMOTE 后召回率提升的同时精度没有塌缩说明插值确实补上了有效区域。本文还有配套的精品资源点击获取