
简介这份资源是面向机器学习初学者与数据挖掘工程师的SMOTE算法MATLAB实现包用于解决训练集中少数类样本过少导致的模型偏置问题。压缩包共5个文件以2个.m脚本为核心分别承载SMOTE主算法与测试调用逻辑另附LICENCE.txt、.gitignore、.gitattributes等辅助配置整体仅3KB轻量易读适合直接嵌入现有MATLAB工程。代码围绕K近邻与线性插值合成新样本展开可配合统计与机器学习工具箱完成数据加载、标准化、过采样、模型训练与AUC-ROC、F1等指标评估的完整链路。已有1324人学习下载读者可借此理解SMOTE原理、K值选择与过拟合防范思路并快速将平衡后的数据集用于SVM、决策树或随机森林实验。1. SMOTE 算法为什么“造样本”能救活一个分类器少数类样本太少分类器就会学会“全猜多数类”这种偷懒策略——准确率看着挺高召回率却惨不忍睹。SMOTE 要解决的就是这件事它不复制少数类样本而是在少数类样本之间“插值”造出新样本。听起来像玄学但背后有明确的几何直觉——两个真实少数类样本连线上的点大概率仍属于少数类。matlab 版本的 SMOTE 实现把这个直觉拆成了可复现的几步找近邻、算差值、乘随机因子、加回原点。搞懂这几步你就能明白为什么它比简单过采样更抗过拟合也能明白它什么时候会翻车。这份资源把整个流程落成了可读的 matlab 代码适合想从底层理解“样本合成”到底在合成什么的人。2. 从过采样到插值SMOTE 到底在合成什么2.1 随机过采样的致命缺陷最朴素的类别不平衡处理是随机过采样把少数类样本复制若干份直到两类数量接近。这个做法在代码上就是一行repmat或者索引重复但它的副作用很隐蔽——复制出来的样本和原样本完全重合分类器在训练时反复看到同一个点决策边界会向这些重复点严重倾斜。用血泪经验来说就是模型在训练集上对少数类“背答案”一到测试集就露馅。从机制上看随机过采样没有增加任何新的信息量。它只是改变了损失函数里各类样本的权重分布等价于给少数类样本加了更高的惩罚系数。但分类器看到的特征空间几何结构没变少数类依然挤在一小块区域里。SMOTE 的出发点就是与其复制不如在少数类样本的邻域里“造”出新的特征向量让少数类在特征空间里占据更大的体积。2.2 插值公式的几何直觉SMOTE 的核心公式只有一行x_new x_i rand(0,1) * (x_hat_i - x_i)其中x_i是某个少数类样本x_hat_i是它某个近邻也是少数类rand(0,1)是 0 到 1 之间的随机数。这个公式做的事就是在x_i和x_hat_i的连线上随机取一个点。当随机数接近 0 时新样本靠近x_i接近 1 时靠近x_hat_i取 0.5 时就是两点中点。为什么这样做合理因为如果两个样本都属于少数类且它们在特征空间里距离较近那么它们之间的区域大概率也属于少数类的决策区域。这不是数学定理而是一个基于“局部平滑性”的假设同类样本在特征空间里倾向于聚集聚集区域内部没有剧烈的类别突变。常见的实现思路是这个假设在低维、连续特征上比较成立但在高维、稀疏或类别边界复杂的场景下会失效。2.3 近邻选择k 值决定了什么SMOTE 在造样本前要先为每个少数类样本找 k 个最近邻。这个 k 不是随便定的它直接决定了合成样本的“扩散范围”。k 太小比如 k1每个样本只能和最近的那一个邻居插值合成样本会集中在少数几个连线上多样性不足。k 太大比如 k15近邻可能已经跨到了少数类簇的边缘甚至另一个簇插值出来的点可能落在多数类区域里相当于给分类器喂了噪声。matlab 实现里通常用knnsearch或者自己算欧氏距离矩阵来选近邻。这里有个容易忽略的细节近邻搜索必须在少数类样本内部进行不能把多数类样本也算进去。否则你可能会选到一个多数类邻居插值出来的点直接落在多数类地盘上。这个坑在第一次手写 SMOTE 时几乎必踩。3. matlab 实现拆解从距离矩阵到合成样本3.1 数据准备与少数类索引提取在 matlab 里处理类别不平衡第一步是把少数类样本单独拎出来。假设数据矩阵X是n x d标签向量Y是n x 1少数类标签为1多数类为0% 假设 X 是 n x d 特征矩阵Y 是 n x 1 标签向量 % 少数类标签为 1多数类标签为 0 minority_idx find(Y 1); X_minority X(minority_idx, :); n_minority size(X_minority, 1); % 计算需要合成的新样本数量 n_majority sum(Y 0); n_synthetic n_majority - n_minority; % 让两类数量持平这段代码的关键在于n_synthetic的计算。它决定了要造多少个新样本。如果想让少数类超过多数类可以乘一个比例系数比如n_synthetic round((n_majority - n_minority) * 1.5)。但要注意合成样本不是越多越好过量合成会让少数类区域过度膨胀反而侵入多数类的地盘。3.2 距离矩阵与近邻索引matlab 没有内置的 SMOTE 函数所以近邻搜索要自己写。最直接的方式是算少数类样本两两之间的欧氏距离% 计算少数类样本之间的欧氏距离矩阵 % 利用矩阵运算避免循环提升速度 D pdist2(X_minority, X_minority, euclidean); % 对每一行排序找到 k 个最近邻的索引 k 5; % 近邻数通常取 5 到 10 [~, sorted_idx] sort(D, 2); % 第一列是样本自身距离为 0从第二列开始取 k 个 neighbor_idx sorted_idx(:, 2:k1);pdist2返回的是n_minority x n_minority的对称矩阵对角线为 0。sort(D, 2)对每一行升序排列sorted_idx的每一行是距离从小到大的样本索引。跳过第一列自身取接下来的 k 列就得到了每个少数类样本的 k 个近邻索引。这里有个性能上的坑如果少数类样本有几千个pdist2会生成一个几千乘几千的矩阵内存占用是平方级增长。常见的优化思路是先用knnsearch配合 KD-Tree或者对数据进行分块计算。但在样本量不大时pdist2的写法最直观也最容易调试。3.3 合成样本的生成循环有了近邻索引接下来就是逐样本生成新数据。核心循环如下% 初始化合成样本矩阵 X_synthetic zeros(n_synthetic, size(X_minority, 2)); for i 1:n_synthetic % 随机选一个少数类样本作为起点 base_idx randi(n_minority); base_sample X_minority(base_idx, :); % 从它的 k 个近邻中随机选一个 neighbor_pick randi(k); neighbor_sample X_minority(neighbor_idx(base_idx, neighbor_pick), :); % 生成 0 到 1 之间的随机数 lambda rand(); % 插值公式 X_synthetic(i, :) base_sample lambda * (neighbor_sample - base_sample); end % 合并原始少数类与合成样本 X_balanced [X; X_synthetic]; Y_balanced [Y; ones(n_synthetic, 1)];这个循环里randi(n_minority)随机选起点randi(k)随机选近邻rand()随机选插值位置。三个随机源共同保证了合成样本的多样性。如果固定其中任何一个比如总是选第一个近邻合成样本就会退化成沿固定方向的线性延伸多样性大打折扣。lambda的取值范围是[0, 1]这意味着新样本一定落在两个真实少数类样本的连线上不会超出这个线段。有些变体允许lambda取[-0.5, 1.5]让新样本可以稍微超出连线两端但这会引入外推风险可能造出不属于少数类的点。标准 SMOTE 保持[0, 1]是更稳妥的选择。3.4 合成后的标签拼接与验证合成样本的标签直接设为少数类标签因为插值假设保证了连线上的点仍属于少数类。拼接时要注意维度对齐X_synthetic的列数必须和X一致Y_balanced的长度必须等于X_balanced的行数。验证环节可以做一个简单的可视化如果特征是二维的用scatter画出原始多数类、原始少数类和合成样本观察合成样本是否落在少数类簇的合理范围内。如果合成样本大量出现在多数类区域说明 k 值太大或者少数类本身分布太散需要调整参数。4. 避坑与排查SMOTE 翻车的五种典型场景4.1 近邻里混入了多数类现象合成样本的标签是少数类但特征值明显偏向多数类区域训练出的分类器在少数类上召回率不升反降。原因近邻搜索时没有限制在少数类样本内部或者数据预处理时把两类样本混在一起算距离。有些实现为了省事直接在全体数据上算knnsearch然后取前 k 个邻居结果邻居里混进了多数类。解决近邻搜索必须严格在少数类样本子集上进行。在 matlab 里先把X_minority单独提取出来再算pdist2(X_minority, X_minority)。如果非要在全体数据上算至少要在选近邻时过滤掉多数类索引。4.2 k 值大于少数类样本数现象代码报错“索引超出矩阵维度”或者近邻索引里出现重复或无效值。原因k设成了 10但少数类只有 5 个样本。sort之后取2:k1列超出了矩阵的实际列数。解决在设 k 之前先检查n_minority令k min(k, n_minority - 1)。少数类样本极少时k 只能取到n_minority - 1否则没有足够的近邻可选。如果少数类只有 1 个样本SMOTE 直接失效只能考虑其他方法。4.3 特征量纲不统一导致距离失真现象合成样本在某些特征上变化巨大在另一些特征上几乎不变看起来像是只沿着一两个维度在插值。原因欧氏距离对量纲敏感。如果一个特征取值范围是 0 到 1另一个是 0 到 10000距离计算会被大量纲特征主导近邻选择严重偏向那些特征。解决在算距离之前先做标准化比如 z-score 标准化或 min-max 归一化。matlab 里可以用zscore(X_minority)或(X - min(X)) ./ (max(X) - min(X))。标准化后记得把合成样本反变换回原始量纲否则后续模型训练会出问题。4.4 合成样本导致类别重叠加剧现象少数类召回率提升了但精确率暴跌分类器把大量多数类样本误判为少数类。原因少数类本身就和多数类有重叠区域SMOTE 在重叠区域插值造出的新样本直接落在多数类地盘上相当于给分类器灌入了错误标签的噪声。解决这种情况说明 SMOTE 不适合当前数据分布。可以考虑先用 Tomek Links 或 Edited Nearest Neighbors 清理重叠区域的多数类样本再跑 SMOTE。或者改用 Borderline-SMOTE只对边界附近的少数类样本做合成避免在安全区域过度生成。4.5 高维稀疏数据下近邻失效现象合成样本和原始少数类样本几乎一模一样多样性没有提升模型表现和随机过采样差不多。原因在高维空间里所有样本对之间的距离都趋于相近近邻的概念变得模糊。选出来的“近邻”可能并不近插值出来的点要么和原点重合要么落在无意义的区域。解决高维数据先降维再 SMOTE比如用 PCA 降到 10 到 20 维在低维空间做合成再映射回原空间。或者改用基于聚类的过采样方法先在少数类内部聚类再在簇内插值避免全局近邻搜索的失效。5. 设计取舍SMOTE 的边界在哪里SMOTE 的插值假设建立在“同类样本在特征空间局部连续”之上。这个假设在图像、传感器信号等连续特征上通常成立但在类别特征、文本词袋、稀疏编码等场景下会直接崩掉。因为两个类别样本的“中间点”在语义上可能没有任何意义——比如“猫”和“狗”的词向量中点既不是猫也不是狗。另一个取舍是合成数量。让两类数量持平是最常见的做法但这不是金科玉律。如果少数类本身噪声很大合成到持平反而会放大噪声。我一般会先跑一版不采样的基线看召回率和精确率的 trade-off再决定合成比例。通常合成到多数类的 50% 到 100% 之间配合阈值调整效果比硬性持平更稳。matlab 版本的 SMOTE 还有一个工程上的取舍用pdist2算全距离矩阵代码短、可读性好但内存是 O(n²)。如果少数类样本超过五千建议改用knnsearch配合ExhaustiveSearcher或KDTreeSearcher把内存降到 O(n log n)。不过 KD-Tree 在维度超过 10 到 15 维后退化严重这时候又得回到暴力搜索或者近似近邻。从那以后我每次跑 SMOTE 之前都强制走一遍检查先看少数类样本量够不够 k再看特征有没有标准化最后画一张二维投影图确认合成样本没跑到多数类区域。这三步花不了几分钟但能省掉后面调参时的大量返工。希望帮到你。本文还有配套的精品资源点击获取