资讯动态

近红外光谱深度学习回归建模:从数据预处理到1D-CNN实践

发布时间:2026/9/10 10:10:14 来源:尧图企业网站定制
简介这是一份面向计算机类专业学生毕业设计或课程作业的完整工程资源聚焦基于深度学习的近红外光谱数据回归分析解决光谱数据预处理、模型构建与回归预测这一完整流程问题。项目以Python为主要实现语言并引入C用于数据预处理或计算密集环节以提升效率适合有一定深度学习基础、希望上手真实回归任务的学习者。资源共9个文件以8个Python源码文件为主涵盖ConvNet、DeepVit、SpectFormer等不同深度模型实现并配有1个Markdown说明文档便于理解代码结构、运行逻辑与复现实验压缩包整体仅27KB轻量易用。该资源已有233人浏览学习学习热度可观。通过源码可掌握近红外光谱数据的特征工程方法、深度学习回归模型的搭建与训练流程以及基于TensorFlow或PyTorch的模型验证与调参思路对完成毕设或课程设计具有直接的参考和复用价值。1. 近红外光谱回归建模为什么最后选择了深度学习近红外光谱数据的回归分析是化学计量学和工业在线检测里最常出现的一类任务输入一条覆盖几百到几千个波长点的吸光度曲线输出水分、蛋白质、辛烷值这类连续指标。做这种题目绝大多数教材会先教偏最小二乘PLS回归因为光谱矩阵共线性重线性方法配合预处理往往已经能用。但真把一批复杂样品拿到手谱峰重叠、颗粒散射、温湿度漂移叠加在一起时PLS 能挖出来的信息就到头了。于是基于深度学习的近红外光谱数据回归分析模型这几年顺理成章成了毕设和课程作业的热门选题。它做的事情很直接让一维卷积或全连接网络自己去光谱里找特征绕开人工设计特征和线性假设。这篇文章按做这类题目最顺的路径展开——先把光谱数据洗干净再定网络结构最后给出可直接改的训练代码和几个答辩时能多拿分的验证细节。2. 近红外光谱数据预处理清洗、平滑与增强2.1 光谱数据在回归任务里的真实形态拿到手的近红外光谱数据常见格式是 CSV 或文本表格每一行是一个样本前面若干列是波长点的吸光度值最后一列是标签。波长点数量从几百到几千不等但样本量常常只有几百条。这是整个建模过程里第一个要正视的矛盾——特征维度远大于样本量而且相邻波长点高度相关直接丢给全连接网络参数空间大过拟合几乎是必然的。另一个容易被忽视的问题是数据来源。实验室里的光谱经常按样品顺序连续采集同一个样品可能测了 2 到 3 条谱如果直接随机划分数据集同一样品的重复谱会被同时塞进训练集和验证集验证指标会比真实水平好看很多这就是数据泄露。处理这类数据的第一原则是先看谱图文件里有没有样品编号、采集批次这类列划分必须按样品而不是按行做。2.2 预处理方法对比与参数选择近红外光谱的预处理方法很成熟目的无非是消除基线漂移、颗粒散射和高频噪声。下面这几种是我处理光谱回归数据时最常用的按优先级排方法解决什么问题常用参数或公式注意事项均值中心化基线整体平移X - X.mean(axis0)通常最先做配合其他方法使用SNV 标准正态变量变换颗粒散射导致的样本间幅值差异每行减去自身均值再除自身标准差逐样本独立计算不跨样本统计SG 平滑高频随机噪声window_length11, polyorder2窗口过大会抹掉窄吸收峰一阶导数消除常值基线凸显峰形变化np.gradient(X, axis1)会放大噪声一般放在平滑之后MSC 多元散射校正加性乘性散射效应以平均光谱为参考谱做回归校正适合颗粒较大的固体粉末光谱我一般把 SNV 和 SG 平滑组合成默认管线因为近红外光谱里颗粒散射和仪器噪声最常同时出现。SG 平滑的窗口长度要跟着光谱分辨率走波长间隔在 1nm 左右时 11 到 15 个点很安全如果谱峰本身很尖锐窗口别超过 9。一阶导数能明显拉开重叠峰但会让噪声变大用在深度学习模型上时数据量不足反而容易把模型带偏。2.3 一条可复用的预处理管线下面这段代码可以直接作为模型输入的预处理模块支持原始模式、SNV、SG 平滑和导数的组合import numpy as np from scipy.signal import savgol_filter def snv(x: np.ndarray) - np.ndarray: 标准正态变量变换逐样本去均值、除标准差消除散射带来的幅值差异。 return (x - x.mean()) / (x.std() 1e-8) def preprocess_spectra(X: np.ndarray, mode: str snvsg, window: int 11, polyorder: int 2) - np.ndarray: X X.astype(np.float64) if snv in mode: X np.apply_along_axis(snv, axis1, arrX) if sg in mode: X np.apply_along_axis( lambda row: savgol_filter(row, window_lengthwindow, polyorderpolyorder), axis1, arrX ) if deriv in mode: X np.gradient(X, axis1) return Xapply_along_axis让每个样本独立走一遍处理避免跨样本统计量泄漏到验证集。snv末尾加1e-8是为了防止全零光谱导致除零。这里有个决定模型上限的细节预处理参数只能在训练集上确定。比如你要做均值中心化就只计算训练集的光谱均值验证集和测试集减同一个均值而不是各自减自己的均值。对 SNV 这类逐样本方法影响不大但对任何跨样本统计方法来说这是红线。2.4 数据集划分与光谱增强划分代码我会直接写成按组分from sklearn.model_selection import GroupShuffleSplit groups df[sample_id].values # 同一个样品重复测的光谱编号相同 split GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(split.split(X, y, groupsgroups))GroupShuffleSplit的核心作用是保证同一样品的所有重复光谱全部落在同一侧。如果数据里没有样品编号就把连续采集的一段光谱当成一个组用谱图索引的区间来划分。光谱增强对深度学习模型很有用尤其是样本量不足 500 时。我常用的增强是给训练集加高斯噪声和随机缩放rng np.random.default_rng(0) noise rng.normal(0, 1e-3 * X_train.std(), X_train.shape) X_train_aug np.concatenate([X_train, X_train noise], axis0) y_train_aug np.concatenate([y_train, y_train], axis0)噪声方差取训练集整体标准差的千分之一左右太小没效果太大会把真实吸收峰盖掉。增强只作用于训练集验证集和测试集保持原始状态否则验证损失会被“增强过”的数据污染失去对比意义。3. 回归网络选型与训练从 MLP 基线到 1D-CNN3.1 先跑通 MLP 基线确认标签可学习很多人一上来就上卷积但我的习惯是先搭一个简单的多层感知机MLP做基线。MLP 不是用来拿最好结果的它回答两个问题预处理的谱图信息是否真的和标签相关以及整个训练管线有没有跑通。如果 MLP 在验证集上的 R² 连 0.5 都到不了后面的复杂模型大概率也不会太好问题往往出在标签质量或数据划分上。MLP 结构不需要复杂按下面配置就够层输出维度参数说明Linear512把整条光谱展平后压到隐藏层BatchNorm1d512对 batch 内样本做归一化稳定训练ReLU Dropout(0.3)512Dropout 防过拟合Linear128继续压缩特征ReLU Dropout(0.3)128第二个隐藏层Linear1回归输出层不加激活函数MLP 的参数量和波长点数直接挂钩1000 个波长点时第一层就有几十万个参数几百条样本几乎必然过拟合。所以 MLP 的隐藏层不要贪宽512 已经偏大加 Dropout 是必须的。输出层绝对不能用 Sigmoid 或 ReLU回归任务要求输出没有上限直接裸线性层。3.2 1D-CNN 为什么是近红外光谱最合适的结构一维卷积在光谱数据上有个天然优势卷积核沿波长方向滑动每个核只看相邻的几个波长点这正好对应近红外光谱里吸收峰的特征——一个吸收带通常跨越几个到几十个纳米局部波形比全局平均值更有信息量。卷积既能提取局部形状特征又把参数数量压了下来这是 MLP 做不到的。近红外光谱回归里我用得最多的一组结构如下import torch.nn as nn class SpectralCNN(nn.Module): def __init__(self, n_wavelengths: int 1024): super().__init__() self.features nn.Sequential( nn.Conv1d(1, 32, kernel_size7, stride1, padding3), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size5, stride1, padding2), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size5, stride1, padding2), nn.BatchNorm1d(128), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.head nn.Sequential( nn.Flatten(), nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 1), ) def forward(self, x): return self.head(self.features(x))第一个卷积核大小取 7对应 7 个相邻波长点覆盖一个窄吸收峰的宽度。padding 设为 3保证卷积后波长维长度不变。每个卷积块后接 BatchNorm 和 MaxPool池化步长为 2把波长维逐步减半相当于不断放大感受野。最后一层用AdaptiveAvgPool1d(1)而不是直接 Flatten好处是最终特征图的尺寸和输入波长点数量无关换一批不同分辨率的光谱数据也能跑。唯一需要同步调整的只有n_wavelengths但由于全局平均池化的存在这个参数实际上只影响第一层输入维度网络主体不需要改。3.3 回归任务的损失函数与训练超参回归模型最常用的损失函数是均方误差 MSE它对大误差样本惩罚更重梯度方向也稳定。如果标签里有离群值MSE 会被离群样本牵着走这时可以改用nn.SmoothL1Loss它在误差较大时梯度为常数不会因为单个坏点导致训练震荡。近红外光谱数据一般比较干净默认 MSE 就够用。训练配置我一般这样给超参数建议值说明batch_size16 或 32光谱样本通常只有几百条太大容易收敛到差的局部解初始学习率1e-3Adam 配合 1e-3 起步验证损失停滞再降低学习率调度ReduceLROnPlateau(factor0.5, patience10)验证损失连续 10 个 epoch 不降就降一半weight_decay1e-5 到 1e-4相当于 L2 正则能有效压低验证误差早停patience25记录验证 RMSE 最优的模型权重停止后再恢复激活函数方面隐藏层用 ReLU 是最稳的选择近红外光谱本身是连续的平滑信号不存在 ReLU 死亡导致梯度消失的问题。想再激进一点可以用 SiLU有时能提零点几个百分点的 R²但换来的是训练更敏感。评价回归模型不能只看 loss必须回到原始量纲看 RMSE 和 R²这两个指标才是毕设报告里最有说服力的数字。4. 用 PyTorch 跑通基于深度学习的近红外光谱回归模型4.1 数据装载与标签标准化训练模型前我先把光谱数据转成 PyTorch 的 TensorDataset并给输入加一个通道维。近红外光谱数据本质上是一条一维信号PyTorch 的 Conv1d 期望输入形状是(batch, channels, length)所以要在中间加一维。标签标准化这一步容易被忽略但影响很大。水分含量、蛋白质含量这类标签的量纲和数值范围五花八门如果标签均值是 50、方差是 200MSE 的初始值会非常大模型前几个 epoch 都在补偿标签偏移量。常见做法是对训练集标签做 z-score 标准化保存均值和标准差预测完再还原import torch import numpy as np from torch.utils.data import TensorDataset, DataLoader y_mean y_train.mean() y_std y_train.std() y_train_norm (y_train - y_mean) / y_std train_ds TensorDataset( torch.tensor(X_train, dtypetorch.float32).unsqueeze(1), torch.tensor(y_train_norm, dtypetorch.float32).unsqueeze(1) ) train_loader DataLoader(train_ds, batch_size32, shuffleTrue)unsqueeze(1)把输入从(B, L)变成(B, 1, L)这个 1 就是卷积层的通道数对应单条光谱。标签也做了unsqueeze(1)让输出的形状和 MSE 计算的要求一致。shuffleTrue每个 epoch 都打乱样本顺序避免模型学到采集顺序带来的伪模式。4.2 训练循环、早停与学习率调度训练循环里我习惯把验证集评估和早停写在一起每轮 epoch 先训练再验证验证 RMSE 下降才保留当前权重import torch.nn as nn from torch.optim.lr_scheduler import ReduceLROnPlateau model SpectralCNN() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) scheduler ReduceLROnPlateau(optimizer, modemin, factor0.5, patience10) criterion nn.MSELoss() X_val_t torch.tensor(X_val, dtypetorch.float32).unsqueeze(1) y_val_t torch.tensor(y_val, dtypetorch.float32).unsqueeze(1) best_val_rmse float(inf) best_state None patience 25 wait 0 for epoch in range(300): model.train() train_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() loss criterion(model(xb), yb) loss.backward() optimizer.step() train_loss loss.item() * xb.size(0) model.eval() with torch.no_grad(): val_pred model(X_val_t) val_rmse torch.sqrt(criterion(val_pred, y_val_t)).item() scheduler.step(val_rmse) if val_rmse best_val_rmse - 1e-5: best_val_rmse val_rmse best_state {k: v.clone() for k, v in model.state_dict().items()} wait 0 else: wait 1 if wait patience: break if epoch % 10 0: print(fepoch {epoch:3d} | train_loss {train_loss/len(train_loader.dataset):.4f} | val_rmse {val_rmse:.4f}) model.load_state_dict(best_state)modemin对应验证 RMSE 越小越好调度器检测到验证值 10 个 epoch 不下降就把学习率乘 0.5。patience25是验证 RMSE 不再创新高时允许等待的最大轮数等到 25 轮直接跳出循环然后用best_state恢复历史最优权重。注意这里验证 RMSE 是在标准化后的标签上算的最终报告指标要还原到原始量纲再计算否则 RMSE 数值小得离谱答辩时容易说不清。4.3 评价指标计算与结果验证模型训练完第一件事是看预测值和真实值的散点图与回归指标。我固定计算三个指标from sklearn.metrics import r2_score, mean_absolute_error model.eval() with torch.no_grad(): pred_norm model(X_val_t).numpy().ravel() pred pred_norm * y_std y_mean y_true y_val rmse float(np.sqrt(np.mean((y_true - pred) ** 2))) r2 r2_score(y_true, pred) mae mean_absolute_error(y_true, pred) print(fRMSE {rmse:.3f} | R2 {r2:.4f} | MAE {mae:.3f})R² 反映模型的解释力正常应该在 0.9 以上才算模型基本可用RMSE 的单位和标签一致直接代表平均预测偏差比如水分含量预测 RMSE 为 0.5%意味平均误差在半个百分点左右。画出验证集散点图时点应该紧贴 yx 对角线如果在低值区或高值区系统性偏离说明该区间样本量不足或者模型对该区间不敏感。4.4 三个最常见的训练故障现象可能原因处理办法loss 为 NaN学习率过大或输入光谱含有 NaN/Inf预处理后检查np.isnan(X).sum()学习率调到 1e-4验证 RMSE 远高于训练 RMSE过拟合参数太多样本太少加大 Dropout 到 0.5weight_decay 提到 1e-3缩小全连接层宽度训练 loss 不下降标签未标准化网络太深难收敛确认标签做了 z-score先跑 MLP 基线验证数据本身可学习遇到 loss 不降不要把精力放在换模型上。先回到预处理和数据划分看标签分布是否异常看训练集和验证集的光谱范围是否重叠。很多时候问题根本不在结构上而在数据本身。5. 答辩多拿分的细节波长注意力与过拟合排查5.1 用一维类 Grad-CAM 定位重要波长区间深度学习模型被诟病最多的就是不可解释。近红外光谱数据自带波长坐标可以让模型输出对输入光谱求梯度看哪些波长点对预测结果贡献最大。类 Grad-CAM 的手法在一维信号上同样成立model.eval() x torch.tensor(X_val[0], dtypetorch.float32).unsqueeze(0).unsqueeze(0) x.requires_grad_(True) pred model(x).sum() pred.backward() importance x.grad.abs().squeeze().numpy() # importance 与原始波长坐标对齐 # 如果使用的是标准化后的光谱先记录原始光谱的波长轴sum()是把回归输出聚合成标量才能触发反向传播。梯度绝对值越大说明该波长点对预测输出的影响越大。把 importance 画成曲线叠在平均光谱下面高贡献区间如果落在水分子 O-H 吸收带或蛋白质 N-H 吸收带附近就可以在答辩时直接说“模型学到的是化学意义上的特征不只是在背数据”。如果输入层梯度噪声太大改为对最后一个卷积层输出的特征图做梯度加权平均结果更稳定。5.2 可学习的波长注意力权重比输入层梯度更直观的做法是在网络里加一个可学习的波段权重向量初始化全 1训练完直接看哪些波段的权重被放大class SpectralAttention(nn.Module): def __init__(self, n_wavelengths: int): super().__init__() self.mask nn.Parameter(torch.ones(1, 1, n_wavelengths)) def forward(self, x): return x * torch.sigmoid(self.mask)nn.Parameter会被优化器自动更新sigmoid把权重限制在 0 到 1 之间训练结束后查看self.mask的分布就能知道模型保留了哪些区间。注意初始化不能全 0否则一开始所有波长都被抑制模型学不出来。5.3 过拟合排查的实操顺序最后整理一个排查过拟合的固定流程照着做能省一半时间。第一步看验证集 RMSE 和训练集 RMSE 的差距差距在 20% 以内属正常超过 50% 就先把 Dropout 提到 0.5。第二步看权重衰减weight_decay 从 1e-5 逐步加到 1e-3每次只改一个参数看验证集表现。第三步减少网络容量把卷积层通道数减半或全连接层压缩到 32光谱任务对宽度不敏感对深度更不敏感。第四步检查增强强度噪声加到原光谱标准差的千分之一以上时训练误差会抬高但不该抬高验证误差。记录每次实验的 train_rmse、val_rmse、R² 到 CSV 里做对照比凭感觉调参数可靠得多。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价