MATLAB版SMOTE实战:从K近邻到合成样本的完整拆解与避坑指南

简介:这份资源是面向机器学习初学者与数据挖掘工程师的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:n+1: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:k+1); % 取前 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 end

sort(D, 2)对每一行独立排序,idx(:, 2:k+1)跳过第一列(自身,距离为 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 条样本,你设k=5,代码在knn = idx(:, 2:k+1)这里直接索引越界。原因就是近邻数不能超过同类样本总数减一。解决:在调用前加一行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 都强制走一遍“标准化 → 清洗 → 生成 → 质量评估”这四步,再也没出现过生成完效果反而变差的情况。希望这份拆解能帮到你,代码跑起来之后,先从k=5、ratio=0.3这组保守参数开始试,稳了再往上调。

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

相关新闻

Hive参数调优实战:Tez与MR引擎下的12个核心参数深度解析

Hive参数调优实战:Tez与MR引擎下的12个核心参数深度解析

1. 为什么调优Hive参数不是“锦上添花”&#xff0c;而是“生死线”&#xff1f;你有没有遇到过这样的场景&#xff1a;一条看似简单的SELECT COUNT(*) FROM sales_detail WHERE dt2024-06-01&#xff0c;在Hive里跑了47分钟才出结果&#xff1f;而同一张表、同样分区&#xff…

2026/10/21 4:37:50 阅读更多 →
DETR实战:从YOLO到端到端目标检测的迁移指南

DETR实战:从YOLO到端到端目标检测的迁移指南

几年前我拿到一个安全帽小样本检测项目&#xff0c;最初照着惯性用了YOLO&#xff0c;结果在密集遮挡和远处小目标上反复调锚框、调NMS阈值&#xff0c;调试成本高得离谱。后来把一批业务数据切换到基于Transformer的DETR上&#xff0c;才第一次体会到“端到端”三个字的分量。…

2026/10/21 4:37:50 阅读更多 →
桶排序实战笔记:分桶公式、复杂度分析与典型应用

桶排序实战笔记:分桶公式、复杂度分析与典型应用

基础算法集训走到第九天&#xff0c;前八天基本被快速排序、归并排序、堆排序这些“比较派”占满了&#xff0c;第九天却专门留给了桶排序。第一反应是&#xff1a;一个号称“非比较排序”的方法&#xff0c;值得单独一天吗&#xff1f;实际把这节课吃透之后&#xff0c;我收回…

2026/10/21 4:37:50 阅读更多 →

最新新闻

插件化架构如何重塑知识工作流:从设计到落地的完整指南

插件化架构如何重塑知识工作流:从设计到落地的完整指南

1. 从"知识工作插件"这个命名说起&#xff1a;它到底想解决什么问题第一次看到"knowledge-work-plugins"这个命名&#xff0c;我的直觉是&#xff1a;这不是一个普通的工具库&#xff0c;而是一套面向"知识工作者"的扩展机制。所谓知识工作者&am…

2026/10/21 5:14:05 阅读更多 →
DeepSeek+MCP:从零构建能调用工具的智能体实战指南

DeepSeek+MCP:从零构建能调用工具的智能体实战指南

最近AI圈子里聊得最猛的两个词&#xff0c;一个是MCP&#xff0c;一个是智能体。刚好前段时间我花了两周时间&#xff0c;把DeepSeek接进MCP生态里做了一个能查资料、能算数、能操作本地文件的智能体Demo&#xff0c;整体跑通之后回头再看&#xff0c;这个组合的价值比想象中大…

2026/10/21 5:14:05 阅读更多 →
VSCode C++开发环境配置:从零构建可验证、可诊断的跨平台工具链

VSCode C++开发环境配置:从零构建可验证、可诊断的跨平台工具链

1. 为什么VSCode的C环境配置总让人卡在第一步&#xff1f; “VSCode的C环境配置”——这八个字&#xff0c;几乎是我过去三年里被问得最多的问题&#xff0c;没有之一。无论是某高校计算机导论课刚接触编程的大一新生&#xff0c;还是从Java转岗想补强底层能力的后端工程师&am…

2026/10/21 5:14:05 阅读更多 →
知识工作插件化架构设计与落地实践指南

知识工作插件化架构设计与落地实践指南

1. 从"知识工作插件"这个命名说起&#xff1a;它到底想解决什么问题第一次看到"knowledge-work-plugins"这个命名&#xff0c;我的直觉是&#xff1a;这不是一个具体的工具&#xff0c;而是一类架构思路的统称。它指向的是"把知识工作者的重复性劳动&…

2026/10/21 5:14:05 阅读更多 →
技能学习实操体系:如何从拆解到刻意练习真正掌握一项能力

技能学习实操体系:如何从拆解到刻意练习真正掌握一项能力

说到"skills"这个词&#xff0c;我其实挺有感触。这些年不管是带团队、做项目&#xff0c;还是自己折腾转型&#xff0c;被问得最多的就是"你说我学点什么技能好"。一开始我还认真给建议&#xff0c;后来发现大多数人真正的问题&#xff0c;根本不是缺一个…

2026/10/21 5:14:05 阅读更多 →
litellm 统一大模型调用中间层:从 SDK 到生产级代理的完整实践

litellm 统一大模型调用中间层:从 SDK 到生产级代理的完整实践

1. 项目概述与核心定位1.1 这个项目到底解决什么问题第一次接触 litellm 是在一个多模型混用的项目里。当时团队同时接入了好几家不同厂商的大模型服务&#xff0c;每家的接口协议、鉴权方式、返回结构都不一样&#xff0c;代码里到处是 if-else 分支&#xff0c;维护起来非常痛…

2026/10/21 5:13:05 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →