
简介这份资源是面向机器学习初学者与数据挖掘工程师的SMOTE算法MATLAB实现包用于解决分类任务中少数类样本不足导致的模型偏置问题。压缩包共5个文件以2个.m脚本为核心分别承载SMOTE主算法与测试调用逻辑另附LICENCE.txt、.gitignore、.gitattributes等辅助配置整体仅3KB轻量易读便于直接嵌入现有工程。代码围绕K近邻与线性插值合成少数类样本展开涵盖数据加载、预处理、过采样、数据融合到模型训练评估的完整链路并提示K值选择、过采样与欠采样结合、防止过拟合等注意事项。已有1324人学习下载适合希望快速理解SMOTE原理、在MATLAB环境中动手复现并调参的读者参考。1. 当类别样本只有几十条时我靠这份 MATLAB 版 SMOTE 把模型从“全猜多数类”拉了回来前阵子帮某实验室处理一批工业传感器故障数据正负样本比例接近 1:40模型训练完混淆矩阵里少数类召回率直接是 0——它学聪明了全猜多数类也能拿 97% 的准确率。这种场景下过采样几乎是绕不开的一步而 SMOTE合成少数类过采样技术又是最常被拿来当 baseline 的算法。这份 MATLAB 版本的 SMOTE 实现就是我在那次踩坑之后翻出来重新拆解的一份代码它不依赖任何工具箱纯矩阵运算适合需要在 MATLAB 环境里快速做数据平衡、又不想装一堆 Python 依赖的开发者。如果你手头正好有类别极不平衡的分类任务或者想搞懂 SMOTE 到底在特征空间里做了什么这份代码值得跟着跑一遍。2. 算法骨架拆解从 K 近邻到合成样本的完整链路2.1 核心思路为什么不是简单复制少数类很多人第一次接触过采样直觉就是“把少数类样本复制几份”。但随机复制有个致命问题它不增加任何新的特征空间信息只是让决策边界更偏向少数类模型容易过拟合到那几个重复点。SMOTE 的做法是在两个少数类近邻之间做线性插值生成“看起来合理”的新样本。具体流程分三步先对每个少数类样本在同类样本里找 K 个最近邻然后随机挑一个近邻在两者连线上随机取一点作为新样本重复这个过程直到少数类数量达到目标比例。这份 MATLAB 实现把这三步拆得很清楚没有用任何黑箱函数全部是显式的距离计算和矩阵索引。2.2 距离矩阵计算MATLAB 向量化的关键几行整个算法最耗时的部分就是找 K 近邻。如果写双重循环逐个算欧氏距离样本量一上来就卡死。这份代码用了矩阵化写法核心逻辑如下function D pairwise_dist(X) % X: n x d 矩阵n 个样本d 维特征 % 利用 (a-b)^2 a^2 b^2 - 2ab 展开避免循环 n size(X, 1); sq sum(X.^2, 2); % 每行的平方和n x 1 D2 sq sq - 2 * (X * X); % 平方欧氏距离矩阵 D2(D2 0) 0; % 数值误差可能导致微小负值 D sqrt(D2); % 开方得到真实距离 D(1:n1:end) inf; % 对角线置 inf排除自身 end这段代码有几个细节值得说。sq sq是广播加法得到 n×n 矩阵X * X是 Gram 矩阵一次矩阵乘法搞定所有内积。对角线置inf是为了后面排序时把自己排除掉不然每个样本的最近邻永远是它自己。数值误差那行也不能省浮点运算下D2可能出现 -1e-12 这种值开方直接变复数后面全乱。参数上X的每一行是一个样本列是特征维度。如果你的数据是特征在行、样本在列记得先转置。这个函数返回的是完整距离矩阵内存占用是 O(n²)样本量超过两万就要考虑分块或者换 KD 树了。2.3 合成样本生成随机插值的边界控制拿到距离矩阵后对每个少数类样本找 K 个近邻然后随机选一个做插值function X_new generate_samples(X_min, k, N) % X_min: 少数类样本m x d % k: 近邻数 % N: 需要生成的样本总数 m size(X_min, 1); D pairwise_dist(X_min); [~, idx] sort(D, 2); % 每行按距离升序排列 knn idx(:, 2:k1); % 取前 k 个近邻跳过自身 X_new zeros(N, size(X_min, 2)); for i 1:N base randi(m); % 随机选一个少数类样本作为基准 nb knn(base, randi(k)); % 从它的 k 个近邻里随机挑一个 gap rand(); % 0 到 1 之间的随机插值系数 X_new(i, :) X_min(base, :) gap * (X_min(nb, :) - X_min(base, :)); end endsort(D, 2)对每一行独立排序idx(:, 2:k1)跳过第一列自身距离为 inf 排最后这里要注意因为对角线置了 inf排序后 inf 会在最后所以前 k 个就是真正的近邻。插值系数gap用rand()生成范围是 [0,1)新样本落在两个少数类样本的连线上。如果gap取 0 或 1新样本就和原样本重合所以有些改进版会用rand()加一个小偏移避免边界值这份代码没做这个处理后面改造章节我会提怎么加。N是你需要生成的数量通常由目标比例决定。比如少数类原有 50 条多数类 2000 条你想让少数类达到多数类的 50%就需要生成 950 条。3. 跑通这份代码数据准备、参数设置与结果验证3.1 数据格式与目录组织这份代码对输入数据的要求很宽松核心就是一个特征矩阵和一个标签向量。我一般会这样组织工作目录smote_project/ ├── main.m % 入口脚本 ├── smote.m % 核心函数 ├── pairwise_dist.m % 距离计算 ├── data/ │ └── dataset.mat % 你的数据 └── output/ └── balanced.mat % 平衡后的数据数据文件里至少要有两个变量X是 n×d 的特征矩阵Y是 n×1 的标签向量假设少数类标签为 1多数类为 0。如果你的原始数据是 CSV用readmatrix读进来后手动拆一下就行。3.2 主脚本调用示例入口脚本的逻辑很直白读数据、分少数类多数类、调 SMOTE、合并、保存。% main.m load(data/dataset.mat); % 载入 X 和 Y minority_idx find(Y 1); % 少数类索引 majority_idx find(Y 0); % 多数类索引 X_min X(minority_idx, :); X_maj X(majority_idx, :); k 5; % 近邻数常用 5 或 3 ratio 0.5; % 目标少数类达到多数类的 50% N round(ratio * size(X_maj, 1)) - size(X_min, 1); X_syn generate_samples(X_min, k, N); X_balanced [X_maj; X_min; X_syn]; Y_balanced [zeros(size(X_maj,1),1); ones(size(X_min,1),1); ones(N,1)]; save(output/balanced.mat, X_balanced, Y_balanced);k的选择有个经验范围太小比如 1生成的样本多样性不够太大比如 20可能跨越类别边界引入噪声。我一般从 5 开始试看后续分类器的召回率和精确率平衡点。ratio不是越高越好把少数类硬拉到和多数类 1:1 有时反而伤精确率0.3 到 0.5 之间往往更稳。3.3 怎么验证生成结果靠不靠谱跑完之后别急着训模型先做两件事。第一画一下生成前后少数类在二维 PCA 投影下的分布看看新样本是不是落在原有少数类簇的合理范围内有没有跑到多数类堆里去。第二用生成前后的数据分别训一个简单的分类器比如 MATLAB 自带的fitcsvm或fitctree对比少数类召回率的变化。% 快速验证用决策树对比 tree_before fitctree(X, Y); tree_after fitctree(X_balanced, Y_balanced); % 在留出测试集上算混淆矩阵 pred_before predict(tree_before, X_test); pred_after predict(tree_after, X_test); % 重点看少数类的 recall 和 precision如果召回率上去了但精确率崩了说明k或ratio设得太激进生成的样本侵入了多数类区域。这时候把k降到 3或者把ratio降到 0.3重新跑一遍。4. 避坑与排查SMOTE 在 MATLAB 里最容易翻车的几个点4.1 距离矩阵内存爆炸现象样本量到一万以上时pairwise_dist直接报Out of memory。原因很简单n×n 的 double 矩阵一万条就是 800MB两万条直接 3.2GB。解决办法有两个一是对多数类先做随机下采样把总样本量压到五千以内再跑 SMOTE二是改成分块计算每次只算一批样本的距离用pdist2配合循环。我一般选第一种因为多数类下采样对最终效果的影响通常比少数类过采样小。4.2 近邻数 k 大于少数类样本数现象少数类只有 3 条样本你设k5代码在knn idx(:, 2:k1)这里直接索引越界。原因就是近邻数不能超过同类样本总数减一。解决在调用前加一行k min(k, size(X_min,1)-1)保证不会越界。如果少数类少到只有一两条SMOTE 本身就不适用了得换 ADASYN 或者直接收集更多数据。4.3 特征量纲不统一导致距离失真现象生成的新样本全挤在某个特征维度附近分类器效果没提升。原因是欧氏距离对量纲敏感如果一个特征范围是 0-1另一个是 0-10000距离几乎完全由大范围特征主导。解决在跑 SMOTE 之前对所有特征做标准化z-score 或 min-max生成完再反标准化回去。这一步很多人会忘但影响很大。4.4 类别标签不是 0/1 导致索引错乱现象find(Y 1)返回空值后面全报错。原因是你的标签可能是 1/2、yes/no 或者字符串。解决先看一眼unique(Y)把少数类标签映射成 1多数类映射成 0再往下走。这个坑在新手手里出现频率极高因为大家拿到数据就急着跑算法忽略了标签编码。4.5 生成样本与原始样本完全重合现象检查生成结果时发现有些新样本和原样本一模一样。原因是插值系数rand()理论上可以取到 0虽然概率极低但在某些随机种子下确实会出现。解决把gap rand()改成gap 0.1 0.8 * rand()强制插值系数落在 [0.1, 0.9] 之间保证新样本和两个父样本都有区分度。5. 二次开发改造从能跑到好用我习惯加的三个扩展这份代码本身是个干净的 baseline但直接拿去处理真实项目我一般会做三处改造。第一处是加自适应近邻数不再对所有少数类样本用同一个k而是根据每个样本周围多数类的密度动态调整——密度高的区域用更小的k避免生成样本侵入多数类。实现上就是在generate_samples里对每个基准样本算一下它到最近多数类样本的距离如果这个距离小于到第 k 个少数类近邻的距离就把k降到 3 或者 2。第二处是加边界样本过滤。SMOTE 有个已知问题如果少数类样本本身就落在多数类区域里噪声点以它为基准生成的样本也是噪声。我习惯在生成前先跑一遍 Tomek links 或者简单的近邻清洗把那些近邻全是多数类的少数类样本剔掉再拿剩下的干净样本去生成。代码改动不大在main.m里加一个清洗步骤就行。第三处是加生成质量评估。每次跑完 SMOTE自动算一下生成样本到最近多数类样本的平均距离以及生成样本之间的平均距离。如果前者太小说明生成样本太靠近边界如果后者太小说明生成样本多样性不够。这两个指标可以做成一个简单的报告输出帮你快速判断参数是否合理。% 生成质量快速评估 dist_to_maj min(pdist2(X_syn, X_maj), [], 2); dist_within pdist(X_syn); fprintf(生成样本到多数类平均距离: %.4f\n, mean(dist_to_maj)); fprintf(生成样本内部平均距离: %.4f\n, mean(dist_within));从那以后我每次跑 SMOTE 都强制走一遍“标准化 → 清洗 → 生成 → 质量评估”这四步再也没出现过生成完效果反而变差的情况。希望这份拆解能帮到你代码跑起来之后先从k5、ratio0.3这组保守参数开始试稳了再往上调。本文还有配套的精品资源点击获取