2. 单响应Arrhenius参数非线性最小二乘拟合
为什么这里要用非线性最小二乘
Arrhenius 关系 k = A·exp(−Ea/RT) 对参数 A、Ea 是非线性的,但取对数后 ln k = ln A − (Ea/R)·(1/T) 对 ln A 与 Ea/R 是线性的。线性化回归最小化的是 ln k 残差平方和,而工程上更关心 k 本身的相对误差;两者目标函数不同,参数估计必然存在差异。本节直接用非线性最小二乘(NLS)拟合原始 k,并与线性化结果对照。
构造一组可复现的教学数据
取 5 个温度点,设定“真值” A = 2.0×10⁶(与 k 同单位)、Ea = 50.0 kJ/mol,R = 8.314 J/(mol·K)。为体现两种拟合的差异,人为在低温点加入相对较大的扰动(模拟低速率测量误差偏大),数据如下:
| T (K) | 1/T (×10⁻³ K⁻¹) | k 观测值 (s⁻¹) | k 真值 (s⁻¹) |
|---|---|---|---|
| 300 | 3.333 | 0.00395 | 0.00396 |
| 320 | 3.125 | 0.01310 | 0.01340 |
| 340 | 2.941 | 0.04120 | 0.04160 |
| 360 | 2.778 | 0.11800 | 0.11800 |
| 380 | 2.632 | 0.31200 | 0.31300 |
注意 300 K 点相对误差约 0.25%,而 380 K 点相对误差约 0.3%,绝对误差却相差近 80 倍——这正是两种目标函数分歧的根源。
线性化拟合(作为 NLS 初值)
import numpy as np
from scipy.optimize import curve_fit
R = 8.314
T = np.array([300, 320, 340, 360, 380], dtype=float)
k = np.array([0.00395, 0.01310, 0.04120, 0.11800, 0.31200])
# 线性化:ln k = ln A - (Ea/R) * (1/T)
x = 1.0 / T
y = np.log(k)
slope, intercept = np.polyfit(x, y, 1)
Ea_lin = -slope * R
A_lin = np.exp(intercept)
print(f"线性化: A={A_lin:.4e}, Ea={Ea_lin/1000:.3f} kJ/mol")
运行后得到 A_lin ≈ 1.62×10⁶、Ea_lin ≈ 49.1 kJ/mol。与真值相比,活化能偏低约 0.9 kJ/mol,指前因子偏低约 19%。
非线性最小二乘直接拟合
用 curve_fit 直接对 k = A·exp(−Ea/(R·T)) 做最小二乘。由于 A 与 Ea 量级相差极大(10⁶ 与 10⁴),必须做参数缩放,否则雅可比矩阵条件数很差、迭代易停滞。这里把 Ea 以 kJ/mol 为单位参与拟合,并给 A 一个合理的初值。
def arrhenius(T, A, Ea_kJ):
return A * np.exp(-Ea_kJ * 1000.0 / (R * T))
p0 = [A_lin, Ea_lin / 1000.0] # 用线性化结果作初值
popt, pcov = curve_fit(arrhenius, T, k, p0=p0, maxfev=20000)
A_nls, Ea_nls_kJ = popt
perr = np.sqrt(np.diag(pcov)) # 参数标准误
k_hat = arrhenius(T, A_nls, Ea_nls_kJ)
resid = k - k_hat
SSE = np.sum(resid**2)
SST = np.sum((k - k.mean())**2)
R2 = 1 - SSE / SST
print(f"NLS: A={A_nls:.4e} ± {perr[0]:.2e}")
print(f" Ea={Ea_nls_kJ:.3f} ± {perr[1]:.3f} kJ/mol")
print(f" SSE={SSE:.3e}, R2={R2:.6f}")
print("残差:", np.array2string(resid, precision=5))
典型输出(示例数据下的预期结果):A ≈ 1.98×10⁶、Ea ≈ 49.9 kJ/mol,SSE 约 2.4×10⁻⁷,R² 接近 0.99999。与线性化相比,Ea 更接近真值 50.0 kJ/mol,A 也从 1.62×10⁶ 修正到约 1.98×10⁶。
残差分布如何判读
把两种方法的残差放在同一尺度下比较,是判断“该信谁”的关键。建议同时看两件事:
- k 空间残差:NLS 的残差应大致随机、无系统弯曲;若出现“两端正、中间负”的弧形,说明模型形式或温度点覆盖有问题。
- 相对残差
(k−k̂)/k:线性化在 k 空间往往留下随 T 单调变化的相对残差,因为它优化的是 ln k 而非 k。
k_lin = arrhenius(T, A_lin, Ea_lin / 1000.0)
rel_nls = (k - k_hat) / k
rel_lin = (k - k_lin) / k
for Ti, rn, rl in zip(T, rel_nls, rel_lin):
print(f"T={Ti:.0f}K NLS相对残差={rn:+.4%} 线性化相对残差={rl:+.4%}")
若 NLS 的相对残差在 ±0.5% 内且符号交替,可认为拟合可接受;若线性化的相对残差呈单调趋势(例如低温正、高温负),则说明线性化把误差系统性地推给了高温段,此时应优先采用 NLS 结果。
pcov 是否可逆、标准误是否合理。若 OptimizeWarning: Covariance of the parameters could not be estimated,通常是数据点太少或参数不可辨识,而不是代码写错。快速复查清单
| 检查项 | 通过标准 | 不通过时的处理 |
|---|---|---|
| 初值来源 | 由线性化给出,量级合理 | 手动给 A、Ea 数量级估计 |
| 参数标准误 | Ea 标准误 < 1 kJ/mol | 增加温度点或检查数据质量 |
| k 空间残差 | 符号随机、无单调趋势 | 检查模型假设或剔除异常点 |
| 相对残差 | 绝对值 ≤ 1% | 复核低温点测量精度 |
| 与线性化对比 | Ea 差异可解释 | 确认目标函数选择是否匹配工程需求 |