news 2026/10/10 14:33:38

MATLAB中SMOTE算法实战:从手写实现到官方函数与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MATLAB中SMOTE算法实战:从手写实现到官方函数与避坑指南

简介:这份资源是面向机器学习初学者与数据挖掘工程师的MATLAB版SMOTE算法实现包,用于解决分类任务中少数类样本不足导致的模型偏置问题。压缩包共5个文件,以2个.m脚本为核心,分别承担SMOTE主算法实现与测试调用,另附LICENCE.txt、.gitignore、.gitattributes等工程配置文件,整体仅3KB,轻量易读,便于直接嵌入现有MATLAB工程。代码覆盖数据加载、标准化预处理、K近邻搜索、线性插值合成少数类样本及数据融合等环节,并配有测试脚本演示完整调用流程,读者可据此快速复现算法并观察平衡前后样本分布变化。目前已有1321人学习下载,适合希望理解SMOTE原理、在MATLAB环境中处理不平衡数据集并提升模型召回率与F1分数的读者参考。

1. SMOTE 算法在 MATLAB 里到底解决什么问题:从一份 300 行 12 个正样本的表格说起

手上拿到一份设备故障记录表,300 行数据,其中「故障」标签只有 12 条,剩下 288 条全是「正常」。直接丢进fitcsvm或者fitcensemble,训练集准确率能到 96%,可一旦拿新数据一测,故障样本几乎全被漏判——这就是典型的类别不平衡把分类器带偏了。SMOTE(Synthetic Minority Over-sampling Technique)要干的事很直接:不去复制那 12 条少数类样本,而是在它们之间的连线上插值,造出一批「像故障又没见过」的合成样本,让模型在决策边界附近有足够多的少数类证据可学。

MATLAB 里做这件事,最省事的路径是 Statistics and Machine Learning Toolbox 自带的smote函数,一行就能返回过采样后的特征矩阵和标签;但真实项目里往往不能只靠这一行——特征里有类别型变量、有量纲差三个数量级的传感器读数、有缺失值,直接调smote会翻车。这篇笔记按「先搞懂插值逻辑 → 手写一版可控的 SMOTE → 用官方函数对照验证 → 处理混合类型和边界样本 → 排查常见报错」的顺序展开,适合正在用 MATLAB 做故障诊断、信用评分、医学信号分类这类不平衡任务的工程师,也适合刚接触matlab深度学习与matlab优化工具箱、想把这套流程接进自己 pipeline 的人。

2. SMOTE 的插值逻辑与 MATLAB 实现选型:为什么不能直接复制少数类

2.1 从一条合成样本的生成公式说起

SMOTE 的核心动作只有三步:对每一个少数类样本 $x_i$,在同类样本里找它的 $k$ 个最近邻;随机挑一个邻居 $x_{i}^{nn}$;在两点连线上随机取一个位置生成新样本:

$$x_{new} = x_i + \delta \cdot (x_{i}^{nn} - x_i), \quad \delta \in [0,1]$$

$\delta$ 是 0 到 1 之间的随机数,所以合成样本一定落在两个真实少数类样本的连线上,不会跑到多数类区域里去。这跟「随机复制」有本质区别:复制只是让损失函数里少数类的权重变大,决策边界位置不变;插值是把少数类的分布「撑开」,让分类器看到少数类在特征空间里其实占了一片区域,而不是几个孤点。

理解这一点,就能明白 SMOTE 的两个前提:少数类样本之间要有可插值的「空隙」,且特征空间的距离度量要有意义。如果少数类只有 3 个样本,$k$ 近邻根本凑不齐;如果特征里混着「设备型号」这种类别编码,欧氏距离算出来就是玄学。

2.2 MATLAB 里三条可选路径的取舍

在 MATLAB 里落地 SMOTE,常见做法有三条,选哪条取决于你对可控性和依赖项的要求。

路径依赖可控性适用场景
官方smote函数Statistics and Machine Learning Toolbox中,参数有限纯数值特征、快速验证
手写函数仅基础 MATLAB高,可改距离、可处理混合类型特征复杂、需要嵌入自定义流程
第三方工具箱需额外获取视实现而定一般不必要

我一般会先用手写版本跑通逻辑,确认合成样本的分布合理,再换官方smote做交叉验证。原因很实际:官方函数对类别型特征和缺失值的处理是黑匣子,出问题时你只能猜;手写版本每一行都在你眼皮底下,调参和排错都快。

2.3 手写一版最小可用的 SMOTE

下面这段代码假设输入X是数值特征矩阵(每行一个样本),y是标签向量(1 表示少数类,0 表示多数类),目标是把少数类扩到指定数量。

function [X_new, y_new] = smote_simple(X, y, k, targetRatio) % SMOTE_SIMPLE 基础版 SMOTE 过采样 % X: 特征矩阵 n x d % y: 标签向量 n x 1,1 为少数类 % k: 近邻数,一般取 5 % targetRatio: 少数类目标数量 / 多数类数量 X_min = X(y == 1, :); X_maj = X(y == 0, :); n_min = size(X_min, 1); n_maj = size(X_maj, 1); % 需要生成的合成样本数 n_syn = round(targetRatio * n_maj) - n_min; if n_syn <= 0 X_new = X; y_new = y; return; end % 少数类样本两两距离,找 k 近邻 D = pdist2(X_min, X_min); D(1:n_min+1:end) = inf; % 排除自身 [~, idx] = sort(D, 2); knn = idx(:, 1:min(k, n_min-1)); % 近邻索引 X_syn = zeros(n_syn, size(X, 2)); for i = 1:n_syn % 随机选一个少数类样本 base = randi(n_min); % 从它的 k 近邻里随机选一个 nb = knn(base, randi(size(knn, 2))); delta = rand(); X_syn(i, :) = X_min(base, :) + delta * (X_min(nb, :) - X_min(base, :)); end X_new = [X; X_syn]; y_new = [y; ones(n_syn, 1)]; end

逻辑说明:pdist2算出少数类样本间的欧氏距离矩阵,把对角线设成inf是为了排除「自己跟自己最近」这个无意义结果。sort后取前 $k$ 列就是每个样本的 $k$ 近邻索引。生成循环里,base是插值起点,nb是插值终点,delta控制新样本在连线上的位置。

参数说明:k取 5 是文献里的常见默认值,样本量小于 20 时建议降到 3 甚至 1;targetRatio控制过采样强度,取 1 表示少数类补到和多数类一样多,取 0.5 表示补到多数类的一半——不是补得越多越好,后面避坑章节会讲。

3. 用官方 smote 函数对照验证:参数怎么设、结果怎么看

3.1 官方函数的最小调用与参数含义

MATLAB 的smote函数签名大致是[X_res, y_res] = smote(X, y, 'K', k, 'Ratio', ratio),不同版本参数名可能有差异,用help smote确认。下面是一个完整可跑的对照脚本:

% 构造不平衡数据:300 个多数类,20 个少数类 rng(42); X_maj = randn(300, 2) + [2, 2]; X_min = randn(20, 2) + [-1, -1]; X = [X_maj; X_min]; y = [zeros(300,1); ones(20,1)]; % 官方 smote 过采样 [X_res, y_res] = smote(X, y, 'K', 5, 'Ratio', 1.0); % 手写版本对照 [X_my, y_my] = smote_simple(X, y, 5, 1.0); % 可视化对比 figure; subplot(1,3,1); gscatter(X(:,1), X(:,2), y); title('原始数据'); subplot(1,3,2); gscatter(X_res(:,1), X_res(:,2), y_res); title('官方 smote'); subplot(1,3,3); gscatter(X_my(:,1), X_my(:,2), y_my); title('手写 smote');

逻辑说明:rng(42)固定随机种子,保证每次跑出来的合成样本一致,方便对比。gscatter按标签着色画散点,能直观看出合成样本是否落在少数类区域。

参数说明:'K'是近邻数,'Ratio'是目标比例。官方函数返回的X_res里既包含原始样本也包含合成样本,y_res对应标签,不需要自己拼接。

3.2 合成样本质量怎么判断:三个可量化的检查点

光看图不够,我一般会算三个指标:

第一,合成样本到最近真实少数类样本的平均距离。这个值应该和少数类样本之间的平均最近邻距离在同一量级,如果大出一个数量级,说明插值跑到了不该去的地方。

第二,合成样本被多数类 $k$ 近邻包围的比例。对每个合成样本,看它最近的 $k$ 个真实样本里多数类占多少,占比过高说明合成样本侵入了多数类区域。

第三,过采样前后用同一分类器做 5 折交叉验证,比较少数类的召回率和精确率。召回率应该明显上升,精确率可能略降——如果精确率暴跌,说明合成样本质量有问题。

% 检查合成样本是否侵入多数类区域 k_check = 5; D_syn = pdist2(X_res(y_res==1 & (1:length(y_res))' > length(y), :), X); % 对每个合成样本,看最近 k 个真实样本的标签分布 [~, nn_idx] = sort(D_syn, 2); nn_labels = y(nn_idx(:, 1:k_check)); invasion_rate = mean(sum(nn_labels == 0, 2) / k_check); fprintf('合成样本被多数类包围的平均比例: %.2f\n', invasion_rate);

逻辑说明:D_syn算的是合成样本到所有真实样本的距离,nn_idx取最近邻索引,nn_labels查这些近邻的真实标签。invasion_rate超过 0.4 就值得警惕。

参数说明:k_check可以和生成时的k不同,检查用的近邻数取大一点更保守。

3.3 和分类器串起来:一个完整的交叉验证流程

SMOTE 不能在全量数据上做完再过采样,那样会造成数据泄漏——合成样本的信息会渗进验证集。正确做法是在每一折的训练集内部做过采样,验证集保持原始分布。

cv = cvpartition(y, 'KFold', 5); recall = zeros(5,1); precision = zeros(5,1); for i = 1:5 X_train = X(training(cv, i), :); y_train = y(training(cv, i)); X_test = X(test(cv, i), :); y_test = y(test(cv, i)); % 只在训练集上过采样 [X_bal, y_bal] = smote(X_train, y_train, 'K', 5, 'Ratio', 1.0); mdl = fitcsvm(X_bal, y_bal, 'KernelFunction', 'rbf'); y_pred = predict(mdl, X_test); tp = sum(y_pred == 1 & y_test == 1); fp = sum(y_pred == 1 & y_test == 0); fn = sum(y_pred == 0 & y_test == 1); recall(i) = tp / (tp + fn); precision(i) = tp / (tp + fp); end fprintf('平均召回率: %.3f, 平均精确率: %.3f\n', mean(recall), mean(precision));

逻辑说明:cvpartition分层划分保证每折里都有少数类样本。过采样只作用于X_train,X_test保持原始不平衡比例,这样评估结果才反映真实场景。

参数说明:'KernelFunction'换成'linear'可以对比线性边界下的效果;fitcsvm之外也可以换fitcensemble或fitcknn,SMOTE 对分类器类型不敏感,但对距离度量敏感。

4. 混合类型特征与边界样本:SMOTE 在真实数据上的四个改造点

4.1 类别型特征不能直接算欧氏距离

真实表格里常有「设备型号」「区域编码」这类列。如果直接 one-hot 编码后算欧氏距离,一个型号差异会被放大成 $\sqrt{2}$ 的距离,压过传感器读数的差异。常见做法是:数值列做标准化后算欧氏距离,类别列单独用汉明距离,最后加权合并。

function D = mixed_distance(X_num, X_cat, w_num, w_cat) % 数值列欧氏距离 + 类别列汉明距离的加权组合 D_num = pdist2(X_num, X_num) / max(pdist2(X_num, X_num(:))); D_cat = pdist2(X_cat, X_cat, 'hamming'); D = w_num * D_num + w_cat * D_cat; end

逻辑说明:D_num归一化到 [0,1] 避免量纲主导,pdist2的'hamming'选项直接算类别列的不匹配比例。w_num和w_cat按业务重要性调,默认可以取 0.7 和 0.3。

参数说明:归一化用max而不是mean,是因为距离矩阵的最大值更能代表「最远两点」的尺度。

4.2 边界少数类样本要区别对待

SMOTE 对所有少数类样本一视同仁地插值,但靠近多数类边界的少数类样本,插值后很容易生成「骑墙」样本,反而干扰分类器。Borderline-SMOTE 的思路是:只对「近邻里多数类占多数」的边界样本做插值,且插值方向偏向多数类一侧,把决策边界往外推。

在 MATLAB 里实现这个变体,只需要在生成循环前加一层筛选:

% 筛选边界少数类样本 D_all = pdist2(X_min, X); [~, nn_all] = sort(D_all, 2); m = 10; % 看最近 10 个邻居 nn_labels_all = y(nn_all(:, 1:m)); is_border = sum(nn_labels_all == 0, 2) > m/2; X_min_border = X_min(is_border, :);

逻辑说明:is_border标记出近邻里多数类超过一半的少数类样本,后续只对这些样本做插值。如果边界样本太少,可以放宽到>= m/2。

参数说明:m取 10 是经验值,样本量小的时候降到 5。

4.3 缺失值处理:先补再插还是先插再补

特征里有 NaN 时,pdist2会返回 NaN,整个距离矩阵就废了。两条路:一是先做缺失值填补(fillmissing),再跑 SMOTE;二是把缺失值当成一个额外的类别,用混合距离处理。我一般选第一条,因为填补方法可控,而且 SMOTE 本身不负责处理缺失。

X_filled = fillmissing(X, 'knn', 'NumNeighbors', 5);

逻辑说明:fillmissing的'knn'方法用最近邻的均值填补,比全局均值更贴近局部分布。

参数说明:'NumNeighbors'取 5 到 10 之间,太大容易把多数类的值填到少数类样本上。

4.4 过采样之后要不要做欠采样

少数类补到和多数类一样多,训练集规模翻倍,训练时间也翻倍。如果多数类样本本身有大量冗余,可以在 SMOTE 之后对多数类做随机欠采样,把总规模压下来。常见组合是 SMOTE + Tomek Links:先过采样,再删掉那些和少数类互为最近邻的多数类样本,清理决策边界。

% SMOTE 后用 Tomek Links 清理 [X_bal, y_bal] = smote(X, y, 'K', 5, 'Ratio', 0.8); % 找互为最近邻的异类样本对 D_bal = pdist2(X_bal, X_bal); D_bal(1:size(X_bal,1)+1:end) = inf; [~, nn_bal] = min(D_bal, [], 2); tomek_idx = false(size(X_bal,1), 1); for i = 1:size(X_bal,1) j = nn_bal(i); if y_bal(i) ~= y_bal(j) && nn_bal(j) == i tomek_idx(i) = true; end end X_clean = X_bal(~tomek_idx, :); y_clean = y_bal(~tomek_idx);

逻辑说明:互为最近邻且标签不同的样本对,就是决策边界上的「钉子」,删掉它们能让边界更干净。tomek_idx标记要删除的样本。

参数说明:'Ratio'取 0.8 而不是 1.0,是给后续清理留出余量,避免清理后少数类又不够。

5. SMOTE 在 MATLAB 里的避坑清单:五条血泪经验

5.1 现象:smote报错「K must be less than the number of minority samples」

原因:少数类样本数小于等于K值,近邻凑不齐。比如少数类只有 4 个样本,K设了 5。

解决:把K降到n_min - 1,或者先检查sum(y==1)再决定参数。手写版本里min(k, n_min-1)就是干这个的。

5.2 现象:过采样后交叉验证准确率反而下降

原因:在全量数据上做了 SMOTE 再划分训练验证集,合成样本同时出现在训练和验证里,评估结果虚高;或者反过来,合成样本质量差,把噪声带进了训练集。

解决:严格在每折训练集内部过采样,验证集保持原始分布。同时用 3.2 节的入侵率指标检查合成样本质量。

5.3 现象:合成样本的某些特征值超出物理范围

原因:插值公式在两个样本连线上取点,如果两个样本的某个特征差异很大,中间值可能落在合理范围之外。比如温度特征两个样本是 20 和 80,插值出 50 没问题,但如果是「开关状态」0 和 1,插值出 0.5 就没有物理意义。

解决:对有明显边界的特征,插值后做截断或取整;类别型特征不要参与插值,用 4.1 的混合距离方案。

5.4 现象:pdist2内存溢出

原因:样本量上万时,pdist2返回的 $n \times n$ 距离矩阵占用内存是 $n^2$ 量级,10000 个样本就是 800MB。

解决:用knnsearch替代pdist2,它只返回 $k$ 个近邻而不存全矩阵;或者分批计算距离。

[idx, ~] = knnsearch(X_min, X_min, 'K', k+1); knn = idx(:, 2:end); % 第一列是自身,去掉

5.5 现象:过采样后模型对多数类的识别能力下降

原因:少数类补得太多,模型偏向少数类,多数类的召回率被牺牲。Ratio设成 1.0 甚至更高时尤其明显。

解决:Ratio从 0.3 开始试,逐步加到 0.8 左右,用验证集上的 F1 或 AUC 选最优点。不要默认补到 1:1,那个比例只适合少数类极度稀缺的场景。

6. 把 SMOTE 接进 MATLAB 分类 pipeline 的两个进阶技巧

6.1 用自定义距离函数替换欧氏距离

smote官方函数不支持自定义距离,但手写版本可以。如果你的特征里有周期性变量(比如小时、角度),欧氏距离会把 23 点和 0 点算成很远,实际上它们只差 1 小时。这时候可以传入一个自定义距离函数:

function d = circular_distance(X1, X2, period) % 周期性特征的距离:取环形最短路径 diff = abs(X1 - X2); d = min(diff, period - diff); end

逻辑说明:对每个周期性维度算环形距离,再和其他维度的欧氏距离合并。period是周期长度,小时就是 24。

参数说明:这个函数要嵌进pdist2的自定义距离接口,或者直接在knnsearch里用'Distance'参数指定。

6.2 用 AUC 而不是准确率来选过采样强度

不平衡数据里准确率是个误导性指标。300 个样本里 12 个少数类,全预测多数类就有 96% 准确率。选Ratio参数时,我一般跑一组值,画 AUC 曲线:

ratios = 0.2:0.2:1.5; aucs = zeros(size(ratios)); for i = 1:length(ratios) [X_bal, y_bal] = smote(X_train, y_train, 'K', 5, 'Ratio', ratios(i)); mdl = fitcsvm(X_bal, y_bal, 'KernelFunction', 'rbf'); [~, scores] = predict(mdl, X_test); [~, ~, ~, aucs(i)] = perfcurve(y_test, scores(:,2), 1); end plot(ratios, aucs, '-o'); xlabel('Ratio'); ylabel('AUC');

逻辑说明:perfcurve返回的第四个输出是 AUC 值,scores(:,2)是预测为正类的分数。曲线峰值对应的Ratio就是当前数据下的较优值。

参数说明:ratios范围根据数据不平衡程度调,极端不平衡可以从 0.1 开始试。perfcurve需要预测分数而不是硬标签,所以用predict的两个输出。

这套流程跑下来,SMOTE 在 MATLAB 里就不再是一个黑匣子函数,而是一个你能控制插值方向、能检查合成质量、能接进交叉验证的组件。我自己的习惯是:任何过采样结果,先画散点图看一眼,再算入侵率,最后跑 AUC 曲线定参数——三步都过了才敢往生产模型里放。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 14:29:19

MCP连接实战:从协议原理到AI工作台工具调用的故障排查指南

最近在社区帮人看MCP相关的问题&#xff0c;我答疑最多的不是“怎么配”&#xff0c;而是“配完了为什么还是连不上”。很多朋友把一个非常简单的概念想复杂了&#xff0c;或者反过来把配置流程想得太简单。今天我用一个真实跑通的路径&#xff0c;把AI工作台类型工具&#xff…

作者头像 李华
网站建设 2026/10/10 14:28:20

AllData搭建数据湖仓平台,集成开源项目Apache Doris/Kylin/Paimon/Amoro,建设数仓分析平台、数仓建模平台、数据湖分析平台、数据湖运维平台

►顶部微信名片可直接添加市场总监&#xff0c;商务咨询、方案沟通即时响应 ►点击链接了解更新详情&#xff1a;演示体验、社群咨询、商务采购&#xff1a; https://docs.qq.com/doc/DVHlkSEtvVXVCdEFo 日常中最怕的不是数据不够&#xff0c;而是数据散、实时难、运维重&#…

作者头像 李华
网站建设 2026/10/10 14:28:12

小白程序员快速上手大模型实战指南:Coding Agent 开发全流程解析

本文详细解析了 Coding Agent 在软件开发中的应用&#xff0c;涵盖规划、执行、部署与监控三个阶段。强调 Agent 高效使用不等于长时间自主运行&#xff0c;需人类在关键节点进行判断、纠偏和验收。文章提出了五项核心能力&#xff1a;设计人机协作方式、让 Agent 自主工作、审…

作者头像 李华
网站建设 2026/10/10 14:26:31

联辉科 LTK8329直流电机驱动芯片:12V/4A,覆盖小家电、玩具、电子锁、机器人四大应用场景

在12V及以下电池供电的运动控制产品中&#xff0c;当负载电流需求从2.5A跃升至4A时&#xff0c;电机驱动芯片面临的不再仅仅是导通损耗的线性增加&#xff0c;而是散热、限流保护、电源电容配置等一系列系统性挑战的全面升级。对于小家电、玩具、电子锁、机器人等成本敏感、空间…

作者头像 李华
网站建设 2026/10/10 14:25:16

企业获客预算分配路径分析:工具投入和人力投入的优先级

从投入产出比角度拆解&#xff0c;工具和人力分别适合解决拓客链路里的哪个阶段的问题。招人和买工具&#xff0c;两笔预算的产出周期有什么不同&#xff1f;招一个业务员&#xff0c;从入职到真正能独立出单&#xff0c;中间要经历熟悉产品、学话术、建立自己的客户资源这几个…

作者头像 李华