多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

机器学习建模样本划分:SPXY、KS与RS算法解析及Matlab实现

机器学习建模样本划分:SPXY、KS与RS算法解析及Matlab实现 简介这是一份基于MATLAB开发的机器学习校正集与验证集划分工具包面向本科、硕士阶段从事光谱分析、化学计量学、模式识别等研究的师生解决样本集划分中如何兼顾特征空间与目标值分布的关键问题。压缩包体积精简仅6KB共5个文件核心为4个.m脚本与1个txt说明其中SPXY、KS、RS三种经典划分算法的源码均可直接调用txt文件详细说明了样本划分函数包的数据格式与调用方式便于用户快速上手。当前已有332人学习下载适合需要完成数据预处理、建立稳健预测模型或撰写相关论文的研究者。通过运行内置示例读者可直观观察三种算法在样本空间中的选取差异节省手动编码与调试时间也能为后续模型泛化性能评估提供可靠的数据划分基础。在近红外光谱、环境监测等高维数据建模场景中该工具包可帮助用户系统比较不同划分策略对模型精度的影响。1. 机器学习建模第一步不是调参是先把校正集和验证集划明白做过近红外光谱、化学计量学或者任何小样本机器学习建模的人应该都吃过样本划分的亏同一份数据随便随机抽一份训练集模型精度可能差出好几个点。这个压缩包里没有花哨的神经网络只有三个划分样本集的基础函数——spxy.m、KS.m、RS.m外加一个说明文件。SPXY 是考虑 X 和 Y 联合距离的样本划分KS 是经典的 Kennard-Stone 最大最小距离法RS 是随机划分。对刚入门机器学习、尤其是做光谱建模的本科硕士来说这三个函数足够把“校正集和验证集划分”这件事从玄学变成可复现的操作。这篇笔记就拆一下这三个函数的用法、参数含义和里面最容易翻车的细节。2. 先把三个算法分清SPXY 的联合距离、KS 的贪心挑选和 RS 的随机兜底2.1 校正集和验证集到底在划分什么很多人在建模时把数据集划分等同于“随便抽 70% 当训练集”这在样本量几百上千、特征独立同分布时问题不大但到了近红外光谱、高光谱这类场景样本少、特征高度共线、还可能有异常值随机划分会让校正集和验证集的分布不一致模型在验证集上的表现完全不能反映真实泛化能力。校正集calibration set是用来拟合模型的验证集validation set是用来检验模型没见过的样本预测能力的。划分的核心目标不是让两个集合样本数满足某个比例而是让校正集尽可能覆盖全样本空间验证集尽可能代表未来真实预测时遇到的分布。SPXY、KS、RS 就是三个实现这个目标的不同策略。2.2 SPXY把 X 和 Y 的距离都算进去SPXY 全称是 Sample set Partitioning based on joint X-Y distances它的核心思想是在计算样本间距离时同时考虑光谱矩阵 X 空间的距离和响应值 Y 空间的距离然后相加作为综合距离。具体做法分两步。第一步对 X 空间的距离矩阵和 Y 空间的距离矩阵分别做归一化消除量纲差异第二步对每个样本对计算 D_xy D_x / max(D_x) D_y / max(D_y)得到联合距离。选样本时先挑 D_xy 最大的两个样本进校正集之后每一步都选“到已选样本最小距离最大”的那个样本加入直到校正集数量达标。这个算法的价值在于回归类任务中只按 X 空间距离划分比如 KS会选到光谱差异大但 Y 值集中在中间区间的样本导致验证集里出现极端 Y 值预测失准。SPXY 把 Y 拉进距离计算能让校正集同时覆盖 X 和 Y 的极端区域。2.3 KS只认 X 空间的欧氏距离原理简单但很实用KS 算法即 Kennard-Stone策略是贪心式的最大最小距离先计算所有样本两两之间的欧氏距离选距离最远的两个样本进入校正集然后在剩余样本里逐个计算每个样本到已选校正集所有样本的最小距离选那个最小距离最大的样本进校正集重复到数量满足要求。KS 的好处是确定性强同一个数据集跑十次结果完全一样不需要设随机种子适合做对比实验。它的前提假设是光谱/特征空间里的距离能代表样本差异这对纯 X 空间的分类、聚类任务很合适。缺点是它只考虑 X 不考虑 Y在回归任务里可能让校正集偏离 Y 的分布。2.4 RS随机抽样作为基线和兜底方案RS 就是随机把样本按比例分到两个集合代码逻辑最简单。它的价值在于作为基准用 RS 划分跑出来的模型精度是“最差也能达到的水平”对比 KS 和 SPXY 的提升才有意义。它另一个用途是快速检查数据比如先跑一遍 RS 看建模流程有没有报错。要注意 RS 每次运行结果都可能不同在 Matlab 里需要调用 rng 函数固定随机数种子否则同一个脚本两次运行得到的校正集不一样复现实验结果会非常麻烦。这个包里的 RS.m 是否默认固定了种子需要打开看具体实现如果没固定建议在使用前补一行 rng(2024) 之类的设置。算法距离依据是否考虑 Y确定性适用场景SPXYX 归一化距离 Y 归一化距离是确定回归、光谱定量建模KSX 空间欧氏距离否确定分类、聚类、特征空间划分RS无纯随机否不确定基线对比、快速验证流程3. Matlab 里跑通三种划分调用接口、参数设置和输出解读3.1 文件清单和函数入口压缩包里解压后有五个文件spxy.m、KS.m、RS.m、Untitled.m 和一个“样本划分选择 函数包需自行带入数据.txt”。其中 spxy.m、KS.m、RS.m 是三个算法函数Untitled.m 是演示脚本txt 是使用说明。建议先把 Untitled.m 打开看一遍它一般会生成一组模拟数据或者读入示例数据然后依次调用三个函数。多数这种函数包的结构是输入样本矩阵 X、响应值 Y、划分比例输出校正集和验证集的索引。3.2 最小可运行的调用示例以最常见的近红外光谱数据为例假设 X 是 n×p 的光谱矩阵n 是样本数p 是波长点数Y 是 n×1 的浓度值% 载入准备好的光谱数据和浓度值 % 假设 X 和 Y 已经在工作区里 % X: n×p double % Y: n×1 double cal_ratio 0.75; % 校正集占 75% % 调用 KS 算法划分返回校正集索引和验证集索引 [cal_idx_ks, val_idx_ks] KS(X, Y, cal_ratio); % 调用 SPXY 算法划分 [cal_idx_spxy, val_idx_spxy] spxy(X, Y, cal_ratio); % 调用 RS 算法划分 rng(42); % 固定随机种子保证可复现 [cal_idx_rs, val_idx_rs] RS(X, Y, cal_ratio); % 用校正集索引提取数据 X_cal X(cal_idx_spxy, :); Y_cal Y(cal_idx_spxy, :); X_val X(val_idx_spxy, :); Y_val Y(val_idx_spxy, :); disp([校正集样本数: , num2str(length(cal_idx_spxy))]); disp([验证集样本数: , num2str(length(val_idx_spxy))]);逻辑说明KS 和 spxy 的输入输出接口基本一致都是传入 X、Y 和校正集比例返回两个索引向量。RS 在调用前加了 rng(42)目的是固定随机数生成器的种子这样每次运行划分结果都一样不然同样的数据两次建模结果对不上找人排查问题都说不清楚。rng 后面的数字可以随意指定建议选一个自己记得住的固定值42 只是常见习惯。参数说明X 必须是二维数值矩阵每一行代表一个样本Y 是列向量行数必须和 X 一致cal_ratio 是校正集占比一般在 0.6 到 0.85 之间光谱建模常用 0.75 到 0.8样本量小于 100 时建议不低于 0.75否则验证集太小评估结果波动大。3.3 函数内部的关键逻辑和输出格式打开 spxy.m 看源码核心几步值得留意。它一定会先计算 X 的成对欧氏距离矩阵和 Y 的成对距离矩阵然后对两个矩阵分别做归一化再相加。有两点要检查一是归一化是否按 max 做的有的版本按 sum 或 mean 归一化这会影响选样本的结果二是初始样本对是不是从联合距离最大的一对开始有的实现会退化用 KS 的初始点虽然一般没问题但严格来说 SPXY 文献里推荐用联合距离最大的一对作为起点。KS.m 里的逻辑相对固定计算距离矩阵、选最远样本对、迭代找最大最小距离。它的时间复杂度是 O(n²·m)n 是样本数m 是校正集目标样本数样本量超过 5000 时跑起来会比较慢可以考虑先用 KS 选一个约等于目标数量的子集再跑 SPXY算是工程上的折中。RS.m 大概率是 randperm 随机打乱索引后按比例切分逻辑最简洁。如果它内部已经设置了 rng(seed)那外部不需要重复设置如果没有建议在调用前自己固定种子。3.4 结果怎么用索引对齐别搞错三个函数返回的都是索引不是直接返回划分后的矩阵。新手最容易在这里出错——拿到索引后直接用X(cal_idx, :)提取数据没问题但如果用X(randperm, :)这类操作重新排序后再取索引样本顺序就错位了。提示用索引提取校正集和验证集以后建议立刻检查维度确认 X_cal 的行数等于 round(n × cal_ratio)Y_cal 行数一致避免后续建模时报维度不匹配或者更隐蔽的对齐错误。4. 划分完别急着建模先做分布一致性检查和对比实验4.1 用均值、标准差和范围评估划分质量划分到底好不好不能只看验证集误差先看两个集合在 X 和 Y 上的分布是否一致。一个常用的做法是分别计算校正集和验证集在 Y 上的均值、标准差和最小值最大值看是否接近。如果验证集的 Y 均值明显高于校正集说明划分把高浓度样本集中到了验证集模型在校正集上拟合的浓度区间没法覆盖验证集预测结果会系统性偏差。X 空间的检查一般用主成分得分分布。对 X 做主成分分析取前两三个主成分画散点图看校正集和验证集在得分空间里是否均匀交错。如果两个集合在主成分得分空间里明显分成两团说明划分方式有问题模型在验证集上的表现会偏低。用 Matlab 写一个快速检查脚本% 对校正集和验证集做分布一致性检查 % 假设 X_cal, Y_cal, X_val, Y_val 已经由索引提取好了 % 检查 Y 分布 stats_cal [mean(Y_cal), std(Y_cal), min(Y_cal), max(Y_cal)]; stats_val [mean(Y_val), std(Y_val), min(Y_val), max(Y_val)]; disp(校正集 Y 统计: 均值, 标准差, 最小, 最大); disp(stats_cal); disp(验证集 Y 统计: 均值, 标准差, 最小, 最大); disp(stats_val); % 主成分分析看 X 空间分布 [coeff, score, latent] pca(X_cal); score_val (X_val - mean(X_cal)) * coeff; % 用校正集的均值和载荷映射验证集 figure; scatter(score(:,1), score(:,2), 40, b, filled); hold on; scatter(score_val(:,1), score_val(:,2), 40, r, filled); legend(校正集, 验证集); xlabel(PC1); ylabel(PC2);逻辑说明这段代码先用 mean、std、min、max 四个统计量对比两个集合的 Y 分布再用 pca 对校正集做主成分分解然后用同样的系数把验证集映射到主成分空间画在一张散点图里。如果两色点分布均匀交错说明划分合理如果验证集点聚集在某一侧需要考虑换算法或者调整划分比例。参数说明pca 函数返回的 score 是校正集在主成分空间的坐标latent 是各主成分的方差贡献。用(X_val - mean(X_cal)) * coeff而不是直接对 X_val 单独做 pca是因为要保证两个集合在同一个坐标空间里比较单独做 pca 会让两个空间的轴方向不一致没法对比。4.2 三种划分横向对比的建模实验分布检查通过以后再用同一套建模流程分别试验三种划分方式。常见做法是用偏最小二乘回归PLSR或主成分回归PCR建模对比验证集的均方根误差RMSE和决定系数 R²。% 三种划分方式分别建模对比验证集表现 % 用 plsregress 做偏最小二乘回归设置 10 个潜变量 ncomp 10; % SPXY 划分建模 [~, ~, ~, ~, beta_spxy] plsregress(X_cal_spxy, Y_cal_spxy, ncomp); Y_pred_spxy [ones(size(X_val_spxy,1),1), X_val_spxy] * beta_spxy; rmse_spxy sqrt(mean((Y_val_spxy - Y_pred_spxy).^2)); % KS 划分建模 [~, ~, ~, ~, beta_ks] plsregress(X_cal_ks, Y_cal_ks, ncomp); Y_pred_ks [ones(size(X_val_ks,1),1), X_val_ks] * beta_ks; rmse_ks sqrt(mean((Y_val_ks - Y_pred_ks).^2)); % RS 划分建模 [~, ~, ~, ~, beta_rs] plsregress(X_cal_rs, Y_cal_rs, ncomp); Y_pred_rs [ones(size(X_val_rs,1),1), X_val_rs] * beta_rs; rmse_rs sqrt(mean((Y_val_rs - Y_pred_rs).^2)); fprintf(RMSE (SPXY): %.4f\n, rmse_spxy); fprintf(RMSE (KS): %.4f\n, rmse_ks); fprintf(RMSE (RS): %.4f\n, rmse_rs);逻辑说明plsregress 返回的第五个输出 beta 是回归系数向量第一项是截距所以预测时需要在 X_val 前面拼接一列 1。RMSE 用验证集的真实值和预测值计算值越小说明模型在这个划分下的验证集表现越好。一般会得出 SPXY 和 KS 优于 RS 的结论尤其在样本量小、X 共线性强的光谱数据上。参数说明ncomp 是 PLSR 的潜变量数取值需要结合交叉验证确定光谱数据一般 5 到 15 之间。这里只是为了横向对比三种划分方式固定 ncomp10 保证公平——三种划分用同样的潜变量数差异就只能归结为划分方式不同。4.3 交叉验证和独立验证的差别如果数据量足够建议再留一批样本做独立测试集不参与任何划分和建模。SPXY 和 KS 划分出来的验证集本质上是从同一批数据里切出来的反复用同一批数据调参多少会引入选择偏差。交叉验证评估的是模型在“同一分布内”的稳定性独立测试集才是真正模拟未来新样本的表现。实际操作中我一般把数据分成三步先用 SPXY 切出 20% 作为独立测试集剩下 80% 再用 KS 或 SPXY 划分校正集和验证集这样一套流程下来评估结论更可信。如果样本量实在不够至少不要在同一个验证集上反复调参超过两三次。5. 实战避坑五个最容易翻车的划分错误5.1 数据没标准化就计算距离划分结果偏向大数值变量现象用 SPXY 或 KS 划分后验证集在某些变量上的分布和校正集差异明显建模精度远低于预期。原因欧氏距离对变量的量纲非常敏感。光谱数据如果有的波长点数值在几千的量级有的噪声波段在零点几的量级距离计算会被大数值变量主导小数值变量对划分几乎没贡献。KS 和 SPXY 的距离计算都建立在欧氏距离基础上没标准化等于把变量权重交给了量纲。解决在调用划分函数之前先对 X 做标准化或至少做均值中心化。标准化用 zscore中心化用 X - mean(X)。注意标准化要放在划分之前不能在划分之后分别对校正集和验证集做——那样两个集合的均值和标准差不同等于人为引入了分布差异。% 先标准化再划分 X_std zscore(X); [cal_idx, val_idx] spxy(X_std, Y, cal_ratio);关键点zscore 默认按列计算均值和标准差光谱数据每一列是一个波长点按列标准化是合理的。如果数据里包含异常值zscore 会被极端值拉偏建议先考虑用稳健标准化或者中位数标准化。5.2 回归任务误用 KSY 值极端样本全被分到验证集现象KS 划分后验证集的 RMSE 特别高且预测值在浓度高值区间出现系统性偏低。原因KS 只考虑 X 空间距离不考虑 Y。光谱数据中 X 和 Y 并非完全单调对应有些样本 X 空间距离很近但 Y 值差异很大KS 不会为 Y 的极端值做特殊安排。结果就是校正集在 Y 的中间区间样本密度高极端浓度的样本很可能被漏进验证集模型在极端区间没有样本可学验证集却专门测它不擅长的区域。解决回归任务优先用 SPXY。如果一定要用 KS 做回归划分之后去检查一下 Y 的分布确认极端 Y 值在校正集里存在。一个辅助做法是先按 Y 值排序把 Y 值极大和极小的几个样本强制放进校正集再用 KS 处理剩余样本。5.3 分类任务用 SPXY类别比例被破坏现象二分类数据用 SPXY 划分后某一类样本在校正集里只剩几十个验证集里另一类占比过高模型的精确率和召回率严重失衡。原因SPXY 的联合距离里 X 和 Y 都被当成连续变量计算分类标签 Y 是离散的 0/1算出来的 Y 距离只有 0 和 1联合距离的权重分配对类别数据没有实际意义样本选择几乎退化成按 X 距离排序小类别样本很可能被忽略。解决分类任务不要用 SPXY。按类别分层随机抽样保证每个类别在校正集和验证集里的比例一致如果每个类别样本量很少考虑直接用全部少数类样本进校正集再用 KS 补足剩余样本量。5.4 样板数据没打乱划分结果出现顺序偏差现象同一个数据集用 RS 划分并固定种子后结果显示校正集样本全是前几十条记录验证集全是后几十条。原因数据本身是按某种顺序采集的比如按时间、按批次、按浓度梯度排列。RS 用 randperm 打乱索引后切分理论上不会出现这种问题但有些旧的算法函数偷懒直接按顺序切分前 75% 和后 25%没有打乱。解决使用前先看函数源码。检查 RS.m 里有没有 randperm如果没有需要在调用前手动打乱数据rng(2024); idx_shuffle randperm(size(X, 1)); X X(idx_shuffle, :); Y Y(idx_shuffle, :);然后再调用划分函数。这是所有划分方法的前提顺带说一句SPXY 和 KS 这类距离算法本身不依赖数据顺序但数据顺序如果和某些隐藏变量相关提前打乱总是更稳妥的。5.5 样本量太少时 SPXY 选出的校正集偏向边界样本现象样本总量只有四五十个SPXY 划分后校正集集中覆盖 X 和 Y 空间的边缘区域中间区域样本全留在验证集模型拟合精度很差。原因SPXY 的贪心策略本质是“尽量选远的样本”在小样本空间里边界样本离其他样本的距离天然更远优先被选中结果是校正集里全是极端样本中间区域没有代表。解决样本量小于 60 时建议先用 RS 或分层抽样做一次预划分再用 SPXY 在预划分结果内微调或者降低校正集比例到 0.60.7 再试。另一种做法是用多次划分取平均跑十次 SPXY比较每次验证集 RMSE选最稳定的那次结果。6. 更稳的划分流程先分层、再 SPXY、最后交叉验证经过几次项目实践我现在处理小样本光谱数据的标准流程是这样的稳定性明显好于直接调用单个函数。第一步先看样本量。如果样本量少于 100先检查 Y 的分布按 Y 值四分位数分层保证每层在校正集和验证集里都有代表。分层不是自己写复杂逻辑Matlab 里可以用histcounts配合discretize快速实现% 按 Y 值分层保证极端值不被随机分丢 edges quantile(Y, [0, 0.25, 0.5, 0.75, 1]); edges unique(edges); % 防止重复分位点 layer discretize(Y, edges);先看每个层里的样本数再把每层样本按 75/25 切分合并所有校正集部分和验证集部分。做完分层再用 SPXY 在分层后的校正集里做第二次筛选把校正集内部也覆盖得更均匀。这个组合的好处是保留了 SPXY 的边界覆盖能力同时避免了小样本下中间区域样本被漏掉的问题。第二步固定随机种子并做好记录。我习惯在脚本开头写清楚这次用的算法、比例、随机种子存成一个配置文件。后来回看实验结果很多当时觉得“这个算法不行”的结论其实是因为换了一次种子或者换了一种划分模型性能波动比算法间的差异还大。从那以后我每次做样本划分都强制在代码开头记录算法名、cal_ratio 和 rng 种子打印到命令行里存档方便回溯。第三步验证阶段不要只跑一次划分。SPXY 和 KS 是确定性算法但它们的初始样本对选择会影响后续过程。实际操作里可以用 bootstrap 的思路多跑几次 RS 划分每次固定不同的种子看模型的平均表现和方差。如果 RS 跑十次的 RMSE 波动范围接近 SPXY 的结果说明数据本身分布比较均匀换什么划分算法都差不多如果 RS 波动大到模型精度时好时坏那 SPXY 和 KS 的优势才是真实存在的。最后提醒一个最容易忽略的细节数据里如果有重复样本或近乎重复的样本SPXY 和 KS 会把它们拆到不同集合里导致验证集含有和校正集几乎一样的样本评估结果虚高。划分之前先按距离阈值去重比如计算样本间欧氏距离去掉距离小于某个阈值的样本对中靠后的那一个。这一步不做后面所有精度对比都是在自欺欺人。这套流程走下来虽然比直接调用函数多做几步但每次拿到的验证集评估都更有底气。希望帮到你。本文还有配套的精品资源点击获取
返回列表