光谱定量分析模型构建与验证
第1章 光谱数据预处理(下):从原始信号到可用特征
各位好,我是老张。在光谱分析这行摸爬滚打十几年,我最大的体会就是:预处理做得好,模型成功一半。上一章我们聊了基线校正和去噪,今天咱们继续往下走,把剩下的几个硬骨头啃下来。
说实话,很多新手拿到光谱数据,上来就建模。结果呢?模型在训练集上漂亮得很,一上真实样本就崩。为什么?因为原始光谱里藏着太多「脏东西」——散射效应、随机噪声、样本间差异……这些不处理干净,模型学到的全是假规律。
今天的内容,我按实战顺序来讲:导数光谱、散射校正、数据增强,最后聊聊预处理流程怎么设计才靠谱。
1.1 导数光谱:把重叠峰「拆」开
先问个问题:两个峰挨得太近,肉眼分不开怎么办?
嗯,导数光谱就是干这个的。它不改变峰的位置,但能把峰形变尖锐,让重叠的峰「现出原形」。
一阶导数:消除基线偏移
一阶导数说白了就是光谱的斜率。基线平移了,斜率不变。所以它能干掉基线漂移。
公式很简单:
dx(i) = [x(i+1) - x(i-1)] / (2 * Δλ)
这里 Δλ 是波长间隔。我习惯用 Savitzky-Golay 求导,一步到位,既平滑又求导。
举个例子:
from scipy.signal import savgol_filter
# 一阶导数,窗口11点,多项式阶数2
deriv1 = savgol_filter(spectrum, window_length=11, polyorder=2, deriv=1)
二阶导数:分辨重叠峰
二阶导数更狠。它反映的是光谱的「弯曲程度」。两个重叠峰,在一阶导数里可能还糊在一起,到二阶导数里就分开了。
代码几乎一样,改个参数:
# 二阶导数
deriv2 = savgol_filter(spectrum, window_length=15, polyorder=3, deriv=2)
我曾经在一个近红外项目中,用二阶导数把三个重叠的C-H伸缩峰分开了。当时客户看了直呼神奇——其实原理很简单,就是数学。
1.2 散射校正:让光谱「对齐」
散射是固体和液体光谱的噩梦。颗粒大小、装样松紧、温度变化……都会导致散射。同一个样品,测三次,三条线歪歪扭扭。
怎么办?两个主流方法:多元散射校正(MSC)和标准正态变量变换(SNV)。
多元散射校正(MSC)
MSC 的思路很直白:找一条「理想光谱」做参考,把其他光谱往它上面「掰」。
步骤:
- 计算所有光谱的平均值,作为参考光谱
- 对每条光谱,用最小二乘法拟合:x_i = a_i * x_ref + b_i
- 校正:x_i_corrected = (x_i - b_i) / a_i
代码实现:
def msc_correct(spectra):
# spectra: 样本×波长 矩阵
ref = np.mean(spectra, axis=0)
corrected = np.zeros_like(spectra)
for i in range(spectra.shape[0]):
# 线性拟合
coeff = np.polyfit(ref, spectra[i], deg=1)
corrected[i] = (spectra[i] - coeff[1]) / coeff[0]
return corrected
标准正态变量变换(SNV)
SNV 更简单粗暴:对每条光谱,减去均值,除以标准差。
说白了,就是把每条光谱「标准化」到均值为0、方差为1。
def snv_correct(spectrum):
return (spectrum - np.mean(spectrum)) / np.std(spectrum)
SNV 和 MSC 什么关系?我个人的理解是:SNV 是 MSC 的简化版。MSC 用参考光谱做基准,SNV 用自身统计量做基准。实际效果上,两者经常差不多。
我建议:先试 SNV,因为它快、无参数。如果效果不理想,再上 MSC。
1.3 数据增强:给模型「喂」更多样本
深度学习时代,数据增强是标配。光谱数据也一样——样本不够?造!
常用的三种方法:
| 方法 | 操作 | 适用场景 |
|---|---|---|
| 加噪 | 添加高斯噪声 | 模拟仪器波动 |
| 平移 | 沿波长轴左右移动 | 模拟波长偏移 |
| 缩放 | 乘以随机系数 | 模拟浓度/厚度变化 |
代码示例:
def augment_spectrum(spectrum, noise_level=0.01, shift_max=2, scale_range=(0.95, 1.05)):
# 加噪
noise = np.random.normal(0, noise_level, spectrum.shape)
spec = spectrum + noise
# 平移(整数点)
shift = np.random.randint(-shift_max, shift_max+1)
spec = np.roll(spec, shift)
# 缩放
scale = np.random.uniform(*scale_range)
spec = spec * scale
return spec
1.4 预处理流程设计原则
好了,方法都讲完了。但怎么组合?先做哪个后做哪个?
我总结了一个通用流程:
原始光谱 → 去噪 → 基线校正 → 散射校正 → 导数 → 数据增强 → 建模
但这不是铁律。我画了一张图,帮你理清思路:
几个原则,我这些年总结的:
- 先粗后细:先做全局校正(去噪、基线),再做局部增强(导数)
- 能不导就不导:导数会放大噪声,如果基线校正后峰已经能分开,就别用导数
- 数据增强放最后:所有校正做完,再增强,避免增强后的数据被后续处理破坏
- 验证集不动:数据增强只对训练集做,验证集和测试集保持原始状态
好了,今天的内容就到这儿。预处理这块,说白了就是「把脏数据洗干净」。方法很多,但核心思路不变:保留化学信息,去除物理干扰。你想想看,光谱分析的本质不就是从物理信号里提取化学信息吗?
下一章我们聊特征选择,到时候见。