news 2026/10/4 1:14:14

样本划分算法SPXY/KS/RS解析:光谱建模避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
样本划分算法SPXY/KS/RS解析:光谱建模避坑指南

简介:面向机器学习建模场景的资源包,提供校正集与验证集划分的三种经典算法实现——SPXY、KS与RS,用于解决样本划分时分布不均、代表性不足等问题,适合本科、硕士及科研人员开展数据处理与模型验证实验。压缩包共5个文件,整体仅6KB,包含4个.m脚本和1个txt使用说明;.m脚本分别对应三种划分算法及主程序,txt文件则提示如何准备并代入样本数据,结构轻简便于直接调用。已有332人学习下载,兼容MATLAB 2014/2019a/2021a等常见版本。资源内附运行结果,可直观对比三种划分方法的效果差异;通过SPXY、KS和RS的源码结合说明文档,读者能快速理解算法逻辑并自行代入数据完成划分,为后续建模训练提供可靠的校正集与验证集构建思路,从而提升模型评估的可靠性与泛化能力。

1. 校正集和验证集一拆,模型就“变脸”:SPXY、KS、RS 到底在拆什么?

做机器学习建模时,很多人盯着算法和调参,却把样本划分这个前置步骤当成“随机一拆”就完事。尤其在近红外、拉曼等光谱定量建模里,校正集和验证集怎么分,直接决定模型是“真会”还是“背题”。随机划分看着公平,但当样品分布不均时,验证集很可能漏掉边界样本,模型评估虚高,换一批样品就翻车。SPXY、KS、RS 三种经典算法,解决的就是“怎么拆才更有代表性”这个问题。下面从原理、Python 实现到避坑,给出一套可以照着复用的方案。适合化学计量学、光谱建模,以及机器学习课程设计与期末复习时想补上数据处理这一环的从业者。

2. 先把三种划分算法讲透:RS、KS、SPXY 各自的适用边界与选型理由

2.1 校正集、验证集与测试集:同一个“验证”,在不同场景下含义不同

在机器学习教科书里,train/validation/test 是三个各司其职的集合:训练集用来更新模型参数,验证集用来比较超参数组合,测试集只在最终评估时碰一次。但在化学计量学或实际光谱建模项目里,样本量通常只有几十到几百个,项目里往往只分两个集合:校正集和验证集。这里的“验证集”实际上承担的是测试集职责——训练阶段完全不碰,最后用它计算 RMSE、R² 等指标。如果还要做超参数搜索,就应再从校正集里切一块做内部验证,而不是把外部验证集拿进来一起调参。名字先想清楚,后面才不容易数据泄漏。

很多人误以为只要分出一个验证集就算完成了,却忽略了“验证集应该代表未来会遇到的新样品”。如果验证集和校正集的分布严重不一致,模型在验证集上的成绩就没有参考价值。所以划分算法的本质,是尽量让两个集合都覆盖原始样本空间,同时保证校正集足够有信息量。RS、KS、SPXY 就是三条不同的路径:RS 靠概率,KS 靠特征空间距离,SPXY 在 KS 基础上再叠加响应变量 y 的距离。

2.2 RS 随机划分:实现最简单,但分布最容易“偏科”

RS(Random Sampling)就是随机抽样,把样本按固定比例随机分到校正集和验证集,最常用的实现是 sklearn 的 train_test_split。为了不丢样本索引,一般传索引数组而不是直接传 X:

import numpy as np from sklearn.model_selection import train_test_split n_samples = 60 X_all = np.random.rand(n_samples, 5) y_all = np.random.rand(n_samples) idx = np.arange(n_samples) cal_idx, val_idx = train_test_split( idx, test_size=0.3, random_state=42 ) X_cal, X_val = X_all[cal_idx], X_all[val_idx] y_cal, y_val = y_all[cal_idx], y_all[val_idx]

这里 test_size=0.3 表示验证集占 30%,random_state 固定随机种子。传索引的好处是后续保存划分结果、做复现都很方便,不一定要把 X 本身切来切去。随机划分的优点是复杂度低、代码一行业,缺点是它完全不看样本分布。极端案例:100 个样本里只有 3 个是高浓度样品,随机一拆,这 3 个可能全进验证集;校正集缺少高浓度段,模型只能靠插值预测,验证集分数反而“虚高”。当数据量很大且分布相对均匀时,RS 是够用的;数据量小或分布偏斜时,它更像碰运气。

2.3 KS 如何用距离把样本“铺”满整个特征空间?

KS(Kennard-Stone)不依赖 y,只用 X 之间的欧氏距离做贪心选择。它做的事情是:从全样本里挑出一部分最有代表性的样本作为校正集,让这些样本在特征空间里尽量分散、尽量“铺满”整个空间,剩下的是验证集。具体步骤:

  1. 计算所有样本两两之间的欧氏距离,得到 n×n 的距离矩阵。
  2. 找出距离最远的两个样本,放入校正集。
  3. 对每个尚未入选的样本,计算它与所有已入选样本之间的最小距离。
  4. 从中取出最小距离最大的样本,加入校正集。
  5. 重复第 3、4 步,直到校正集样本数达到设定值。

这个逻辑很像“在空间里均匀插旗子”:已选的点越密,候选点到已选点集合的最近距离就越小,于是算法总是优先拉进那些身处“空地”的样本。最终校正集的凸包基本覆盖原始数据范围,验证集则落在被包围的位置,这样模型在校正集上学到的边界不会和验证集差太远。KS 不需要随机种子,是确定性算法;它也不关心 y,所以即使响应变量还没测出来,也可以先用光谱 X 做划分。

2.4 SPXY 在 KS 基础上多看了 y 一眼

SPXY(Sample set Partitioning based on joint X-Y distances)是 KS 的扩展,名字直译就是“基于 X-Y 联合距离的样本集划分”。它在距离公式里加入响应变量 y 的差异,避免校正集只覆盖了 X 空间却漏掉了 y 的极值。比如浓度从 0.1% 到 15%,如果只用 KS,低浓度和高浓度样本完全有可能因为 X 距离较近而集中在验证集,导致校正集浓度范围很窄,模型被迫外推。SPXY 把 y 的差异和 X 的差异联合起来,校正集的浓度覆盖就会明显改善。

SPXY 的距离定义为:

dxy(p,q) = dx(p,q)/max(dx) + dy(p,q)/max(dy)

其中 dx(p,q) 是样本 p、q 在 X 空间的欧氏距离,dy(p,q) 是它们在 y 空间的绝对距离。分别除以各自的最大值,是为了把 X 距离和 y 距离都压到 0~1,避免某一方因为量纲太大而主宰整个距离。如果 y 是常数,dy 部分为 0,SPXY 自动退化为 KS。如果 y 是多列输出,常见做法是先把每列 min-max 归一化到 0~1,再计算综合的欧氏距离。

2.5 一张表看清选型:原理、特点与计算量

下表把三种方法放在一起对比:

算法距离依据是否需要 y主要特点适用场景计算量
RS随机概率不需要实现简单,分布不可控数据量大、分布均匀、快速基线O(n)
KSX 的欧氏距离不需要确定性,校正集覆盖 X 空间光谱特征空间、样本量不大O(n²)
SPXYX + y 联合距离需要连续 y同时覆盖 X 和 y 范围定量模型、y 跨度大O(n²)

计算量的“O(n²)”主要来自距离矩阵的构造和每次贪心比较。n 在几百到几千时完全可接受;如果样本量到几万,全距离矩阵会很占内存,需要先对 X 做聚类或随机抽样删减候选集,再用 KS/SPXY 在代表点上划分。总的来说,RS 适合做快速粗筛,KS 是光谱建模的默认选择,SPXY 适合那些 y 分布严重不均、担心验证集“超纲”的定量任务。

3. 从零实现 Kennard-Stone:用欧氏距离选出一个“均匀覆盖”的校正集

3.1 算法步骤拆解:先找最远两点,再逐步“填空”

打开编辑器之前,先明确一点:这个函数要返回的是“校正集索引”和“验证集索引”,而不是把数据直接切好。因为后面还要做标准化、异常值处理,换成索引操作最灵活。函数输入是 X 矩阵和校正集样本数 n_cal,输出是两个索引数组。算法只有两个阶段:初始化阶段先找欧氏距离最远的一对样本;迭代阶段不断把“离当前校正集最近距离最大”的样本拉进来。

这里“最近距离最大”听起来绕,其实很好理解:对每个未选样本,先算出它到当前校正集里所有样本的距离,取最小值,表示它离校正集最近有多远;再在所有未选样本里挑出这个最小值最大的那个。这就是所谓的“让下一个填进最空旷的位置”。当校正集数量接近目标时,剩下的验证集自然就是那些离校正集并不太远、又能代表局部波动的样本。

3.2 可直接复用的 Python 函数:ks_split

import numpy as np from scipy.spatial.distance import cdist def ks_split(X, n_cal, metric="euclidean"): n = X.shape[0] if not 1 < n_cal < n: raise ValueError("n_cal 必须在 2 和 n-1 之间") dist = cdist(X, X, metric=metric) np.fill_diagonal(dist, 0.0) # 防止自环干扰 # 初始化:挑距离最远的两个样本 flat_idx = np.argmax(dist) i, j = np.unravel_index(flat_idx, dist.shape) selected = [int(i), int(j)] remaining = [k for k in range(n) if k not in selected] # 贪心增加样本 while len(selected) < n_cal: min_d = dist[remaining][:, selected].min(axis=1) add_pos = int(np.argmax(min_d)) add_idx = remaining[add_pos] selected.append(add_idx) remaining.pop(add_pos) cal_idx = np.array(sorted(selected)) val_idx = np.array(sorted(remaining)) return cal_idx, val_idx

逻辑说明:np.argmax(dist)返回的是距离矩阵展平后的最大位置,np.unravel_index把它还原成“第几行、第几列”,对应的两个样本就是全空间里相距最远的一对。dist[remaining][:, selected]先取出未选样本对应的行,再取已选样本对应的列,形成候选样本到已选样本的子矩阵;.min(axis=1)算出每个候选点离已选集合的最近距离,np.argmax再挑最近距离最大的那个。selected是校正集,remaining是验证集,因为校正集负责覆盖空间;如果你想把选出的样本当验证集,只需要交换返回值,但建模时要知道校正集将缺失边界,一般不建议。

参数说明:metric默认欧氏距离,光谱数据吸光度同量纲,直接用没问题;如果特征包含温度、浓度等不同量纲,建议先对 X 做标准化,或者改用标准化距离,否则距离会被数值大的特征主导。n_cal一般设置为总样本的 60%~80%。样本数少于 50 时建议取 75%,保证验证集至少有 10 个左右;超过 200 个样本时可以取 70% 或更少,因为验证集只需要足够反映分布,不需要太大。

一个优化技巧:不必每次重新切矩阵。可以用一维数组保存所有样本到当前已选集合的最近距离,每新增一个样本,就把它与新样本的列距离做逐元素取小:

min_dists = dist[:, selected].min(axis=1) while len(selected) < n_cal: cand = min_dists[remaining] add_idx = remaining[int(np.argmax(cand))] selected.append(add_idx) remaining.pop(remaining.index(add_idx)) min_dists = np.minimum(min_dists, dist[:, add_idx])

这个版本在 n 较大时更友好,逻辑和上面完全一致。如果数据量超过 5000,建议不要一次性存全距离矩阵,而是分批计算候选样本到已选样本的距离,或者先用 KMeans 对样本做粗聚类,再从每个簇内选代表样本。

3.3 参数设置与输出检查:选多少样本、看什么指标

划分完第一步是看数量,第二步是看 y 范围。数量上直接打印集合长度即可:

cal_idx, val_idx = ks_split(X, n_cal=45) print(f"校正集: {len(cal_idx)}, 验证集: {len(val_idx)}") print(f"校正集 y: min={y[cal_idx].min():.3f}, max={y[cal_idx].max():.3f}") print(f"验证集 y: min={y[val_idx].min():.3f}, max={y[val_idx].max():.3f}")

尽管 KS 不依赖 y,打印这些值能帮你发现异常:如果校正集 y 范围明显小于全体,说明只靠 X 距离不足以把 y 边界选中,这时候应该换 SPXY 或在 y 上加权重;如果验证集 y 的最小值或最大值比校正集更极端,说明验证集里有模型从未见过的“超纲”样本,这种情况下验证集 RMSE 高不一定是模型不好,而是划分过于苛刻。

这里还要强调一个常见误用:KS 选出的“最有代表性样本”到底当校正集还是验证集?不同工具和资料给的返回值可能完全相反。在本方案里,selected 是校正集,remaining 是验证集。因为校正集应当包含更多边界信息,模型才会学到完整的输入-输出关系。如果你用某个现成函数发现返回的集合范围特别小、剩余集合范围特别大,那大概率是“选代表样本”的目的被用反了。

4. SPXY 实现:把 y 的差异也折算成距离,验证集不再“超纲”

4.1 距离联合公式与两种归一化

SPXY 的关键改动只在距离矩阵的构造上,后面的贪心选择流程和 KS 完全一样。先计算 X 空间的距离 dx,再计算 y 空间的距离 dy,最后把两部分按各自最大值归一后相加。如果希望 X 和 y 的权重不相等,可以加一个权重系数 alpha:

dxy = alpha * dx / max(dx) + (1 - alpha) * dy / max(dy)

alpha 默认取 0.5,表示 X 和 y 同样重要。alpha 越大越偏向 KS,越小越偏向纯 y 距离。对于 y 分布特别偏的建模任务,比如浓度跨越三个数量级,把 alpha 调到 0.3~0.4 通常能让校正集覆盖更多高值样本。但 alpha 太小也会带来反效果:校正集会像在 y 轴上均匀抽头,忽略 X 空间的相关结构,模型训练变得不稳定。

y 的归一化方式也要说清楚。最稳妥的是对 y 做 min-max 缩放,让每个响应变量的取值范围都变成 0~1。这样做不是因为统计性质更好,而是为了让多个 y 列在欧氏距离里“公平参与”。如果直接拿原始浓度(0~15)和原始密度(0.5~0.9)拼在一起算距离,浓度会完全覆盖密度的差异,SPXY 就名存实亡了。

4.2 Python 实现:spxy_split 与 KS 的差异

import numpy as np from scipy.spatial.distance import cdist def spxy_split(X, y, n_cal, alpha=0.5): n = X.shape[0] if not 1 < n_cal < n: raise ValueError("n_cal 必须在 2 和 n-1 之间") # X 空间距离 dx = cdist(X, X, metric="euclidean") # y 空间距离:先逐列 min-max 归一化到 [0,1] y = np.asarray(y, dtype=float) if y.ndim == 1: y = y.reshape(-1, 1) y_min = y.min(axis=0) y_range = y.max(axis=0) - y_min y_range[y_range == 0] = 1.0 # 避免常数列除零 y_norm = (y - y_min) / y_range dy = cdist(y_norm, y_norm, metric="euclidean") # 联合距离 dx_max = dx.max() dy_max = dy.max() if dx_max == 0: raise ValueError("X 无差异,无法划分") if dy_max > 0: dxy = alpha * dx / dx_max + (1 - alpha) * dy / dy_max else: dxy = dx / dx_max # y 无差异时退化为 KS # 以下贪心过程与 ks_split 相同 flat_idx = np.argmax(dxy) i, j = np.unravel_index(flat_idx, dxy.shape) selected = [int(i), int(j)] remaining = [k for k in range(n) if k not in selected] while len(selected) < n_cal: min_d = dxy[remaining][:, selected].min(axis=1) add_pos = int(np.argmax(min_d)) selected.append(remaining[add_pos]) remaining.pop(add_pos) cal_idx = np.array(sorted(selected)) val_idx = np.array(sorted(remaining)) return cal_idx, val_idx

逻辑说明:与ks_split相比,只有距离矩阵变了,贪心选择完全一样。y_range[y_range == 0] = 1.0是为了处理某个响应变量是常数的情况,避免除零,这种情况下该列距离全是 0,不会影响综合距离。dy_max为 0 时说明 y 完全没有差异,SPXY 自动退化为 KS,因此不需要额外报错。

关于“全局归一化会不会泄漏验证集信息”:这里要澄清。SPXY 在划分时用全体 y 的 min-max 构造距离,只是为了让索引选择更合理,并不把 y 的数值传给后续模型。后续建模只使用校正集的 X 和 y 去拟合,验证集的 y 只用于评估。所以这种全局归一化属于“划分策略”的一部分,不是特征工程泄漏。真正需要警惕的是第 5 章说的先全局标准化再划分。

使用示例与 KS 一样。多输出回归任务里,只需把 y 传成二维数组,函数会自动按列归一化再算综合距离;如果不同 y 列对任务重要性不同,可以在调用前自己给每一列乘一个权重,权重列参与缩放后再传入。

4.3 检查 y 覆盖范围:验证 SPXY 是否真的“押中”了边界

SPXY 的优势应该在结果里体现,最直观的检查方式是计算校正集的 y 范围占全体 y 范围的比例。可以写一个极简函数:

def range_coverage(y_all, cal_idx): y_cal = y_all[cal_idx] r_cal = y_cal.max() - y_cal.min() r_all = y_all.max() - y_all.min() return r_cal / r_all print("校正集 y 覆盖率:", range_coverage(y_all, cal_idx))

如果覆盖率低于 0.9,说明校正集没有覆盖到低端或高端,模型有外推风险。发生这种情况时,优先增大 n_cal,其次把 alpha 调小到 0.3 左右,让 y 距离在联合距离里占更大权重。但注意,覆盖率不是越高越好:验证集也需要保留一部分边界样本来检验模型的外推能力。一个比较健康的划分结果是校正集覆盖 90%~100%,验证集的 y 最大值和最小值落在校正集范围内或略微超出,但超出幅度不超过全体极差的 10%。

在完成覆盖检查后,还可以对比 KS 和 SPXY 在同一数据上的 RMSE。通常 SPXY 会让验证集的 RMSE 略高于 KS,因为验证集里包含了更多 y 边界样本,评估更苛刻;但模型在新批次上的表现会更稳定。如果你的验证集 RMSE 反而更低了,往往不是算法神奇,而是重叠样本太多,需要去重后再比较。

5. 校正集划分避坑指南:信息泄漏、重复样本与类别失衡

不管用 RS、KS 还是 SPXY,下面这些坑都值得对照排查。每一条都是实际项目中踩过的场景,按“现象-原因-解决”来写,方便你直接拿去用。

5.1 先全局标准化再划分,导致验证集信息泄漏

现象:模型在校正集和验证集上的 RMSE 都很好看,但部署到新批次数据时误差明显变大,换一台仪器甚至直接失效。

原因:很多人拿到数据后习惯先做一遍 StandardScaler 再去划分。这样一来,验证集的均值和方差已经参与了全局均值和方差的计算,特征变换后的验证集数据偷偷包含了全局统计信息,等效于训练阶段“见过”验证集。光谱数据里常见的去基线、变量标准化(SNV)也同理,只要是先在全样本上估计参数再做划分,都存在这个泄漏风险。

解决:必须先划分,再在校正集上 fit 预处理对象。代码上建议这样写:

from sklearn.preprocessing import StandardScaler cal_idx, val_idx = spxy_split(X, y, n_cal=45) scaler = StandardScaler().fit(X[cal_idx]) X_cal_s = scaler.transform(X[cal_idx]) X_val_s = scaler.transform(X[val_idx])

所有需要估计参数的预处理步骤都走这个流程;不需要估计参数的步骤如简单一阶导数,可以在划分前做,但为了保险,也建议先划分再统一处理。

5.2 KS/SPXY 遇到相同或近似样本,划分结果“看心情”

现象:同一份数据跑两次 KS,得到的划分索引不一样;或者验证集里有一批样本和校正集中的样本几乎完全重复,导致验证集 RMSE 异常低。

原因:距离矩阵里重复样本之间的距离为 0,在 argmax 和 argmin 阶段会出现大量并列。不同环境下的排序稳定性不同,算法就会在不同并列项里随机挑一个。更关键的是,重复样本如果被拆到两侧,验证集等于在“背答案”,指标自然虚高。

解决:在做距离划分前先检查并处理重复样本。最简单的方法是检查 X 的重复行:

X_round = np.round(X, 6) unique_idx = np.unique(X_round, axis=0, return_index=True)[1]

如果重复样本来自同一个物理样品的平行测样,建议取均值后保留一条;如果来自同批次的多个独立样品,则应把整组样本放进同一侧,避免被拆散。可以给样本增加一个“批次编号”,先用批次编号分组,再对组内代表做划分,最后把整组索引映射回去。

5.3 分类任务直接套 SPXY,类别比例全乱

现象:二分类数据集用 SPXY 划分后,校正集里某一类只剩十几个样本,模型训练后把所有样本都预测成多数类,验证集准确率却还说得过去。

原因:SPXY 的 dy 要求 y 是连续值,类别标签 0/1 算出来的欧氏距离缺乏物理含义;而且 SPXY 按空间均匀选点,不会保类别比例。数据分布不均时,少数类样本所在区域的点更容易被当作“边界点”分进验证集,校正集少数类数量进一步缩水。

解决:分类任务优先用分层抽样:

cal_idx, val_idx = train_test_split( idx, test_size=0.3, stratify=y_all, random_state=42 )

如果还想用距离选代表样本,可以按类别分别做 KS,再把每个类选出的样本按原始比例合并成校正集。这种做法能同时保留类别比例和特征空间覆盖,代价是代码量略增,但对于不平衡分类任务非常值得。

5.4 random_state 不固定,复现实验像开盲盒

现象:RS 划分的结果每次运行都不同,KS 和 SPXY 大多数时候稳定,但要是一次脚本里混了去重、排序等随机操作,划分结果也会漂移。换一台电脑重跑实验,指标对不上,排查半天发现是划分变了。

原因:RS 完全依赖随机数生成器,不固定 random_state 就不可能有可复现结果;KS/SPXY 本身确定性,但筛选重复样本、打乱顺序等前置操作可能引入不确定性。很多人只把 seed 写在注释里,没有真正传参,等于没固定。

解决:所有带随机性的步骤都显式传入 random_state,并把划分出来的索引数组保存下来,作为后续建模的唯一入口:

np.savez("split_2025.npz", cal_idx=cal_idx, val_idx=val_idx) # 需要复现时 d = np.load("split_2025.npz") cal_idx, val_idx = d["cal_idx"], d["val_idx"]

保存索引比保存切好的 X_train 更灵活,因为改预处理、改模型时不需要重新划分。这也是项目进入中期后最实用的“后悔药”。

5.5 样本量太少时怎么办

现象:总共 15 个样本,校正集 10 个、验证集 5 个。换一种划分方式,验证集 RMSE 从 0.2 涨到 0.8,根本说不清是模型问题还是划分问题。

原因:样本量太小时,单次划分的随机因素占比太大,验证集只有 5 个点,一个离群样本就能让 RMSE 翻倍。这时候讨论“SPXY 比 KS 好”没有意义,因为统计波动远大于算法差异。

解决:先用交叉验证做模型探索,不要一上来就固定单次划分。sklearn 的RepeatedKFold可以重复多次取样,输出 RMSE 均值和标准差:

from sklearn.model_selection import RepeatedKFold from sklearn.cross_decomposition import PLSRegression from sklearn.metrics import mean_squared_error rkf = RepeatedKFold(n_splits=5, n_repeats=10, random_state=42) rmse_list = [] for tr, te in rkf.split(X): pls = PLSRegression(n_components=5).fit(X[tr], y[tr]) yp = pls.predict(X[te]) rmse_list.append(mean_squared_error(y[te], yp, squared=False)) print(np.mean(rmse_list), np.std(rmse_list))

等预处理和超参数基本确定后,再用 KS/SPXY 分一次作为最终报告用的划分。交叉验证用于选模型,单次划分用于描述最终性能,两者各司其职,不要混用。

6. 用“范围覆盖率 + 重复划分”验证划分质量:一个 10 分钟的检查流程

6.1 校正集范围覆盖率:一张表看出是否覆盖了 y 的边界

划分完成后,我习惯先打印一张小表:校正集和验证集的样本数、y 均值、y 最小值、y 最大值。最简单也最重要的指标是校正集范围覆盖率,也就是第 4.3 节里的range_coverage。覆盖率低于 0.9 时,建模前就要先决定是增删样本、调 alpha,还是接受外推风险。不要等模型跑完再看这个数字,那时候已经晚了。

6.2 重复划分 + RMSE 波动,才能判断划分稳不稳

对于 RS,至少用 10 个不同种子各切一次,每次固定种子后跑同一个模型,记录验证集 RMSE。看均值和标准差,而不只看一次结果。标准差太大说明数据量不足或分布不稳,这时最有价值的信息不是 RMSE 均值,而是“为什么划分一变结果就变”。对于 SPXY/KS 这种确定性算法,可以通过对原始数据做轻微 bootstrap 扰动来观察索引是否大范围变化;变化大通常意味着数据里有重复或近似样本,需要回头做去重。

6.3 把划分索引存进 npz:模型结果的“后悔药”

无论最后用哪种方法,都把索引存好。.npz文件大小几乎可以忽略,但能让你三天后、三个月后完整复现当时的实验。我现在的习惯是每次跑数据先检查是否存在 split.npz,没有才重新划分;有了就直接加载。

这个习惯救过我一回:一个光谱项目验证集 R² 做到 0.96,换供应商数据后直接跌到 0.4。排查到最后才发现,初始划分用的是全局标准化 + 随机抽样,校正集根本没覆盖高浓度段。改成先划分、再用 SPXY 后,验证集 RMSE 变高了,但新批次预测反而稳定了。从那以后我不再把“划分”当预处理里的一行代码,而是当成和模型一样重要的实验变量。希望帮到你。

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

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

STM32与MRAM组合实现工业级非易失存储与掉电保护方案

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

作者头像 李华
网站建设 2026/10/4 1:13:49

C#+MySQL仓库管理系统实战:从表结构到事务部署避坑

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

作者头像 李华
网站建设 2026/10/4 1:12:37

Zynq-7020嵌入式ISP图像处理实战指南

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

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

Python循环结构实验全解析:7个实战关卡攻克for与while

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

作者头像 李华
网站建设 2026/10/4 1:11:33

企业级ELK日志系统设计与落地实践指南

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

作者头像 李华
网站建设 2026/10/4 1:11:12

FPGA数字钟综合实验:从Quartus II到硬件落地的全链路工程实践

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

作者头像 李华