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,并与线性化结果对照。

线性化路径(ln k 空间) 原始数据 (T, k) 5 个温度点 变换 y=ln k, x=1/T 小 k 被放大权重 OLS 直线回归 min Σ(ln kᵢ − ŷᵢ)² 斜率 → Ea, 截距 → A Ea = −slope·R 残差含义:ln k 的绝对偏差 低 k 点(低温)相对误差被放大 高 k 点(高温)权重被压缩 → 参数偏向低温段 非线性最小二乘(k 空间) 原始数据 (T, k) 不取对数 模型 k̂=A·exp(−Ea/RT) 初值来自线性化 Gauss-Newton / LM 迭代 min Σ(kᵢ − k̂ᵢ)² 收敛判据 ΔSSE/SSE < 1e−8 残差含义:k 的绝对偏差 高温大 k 点主导 SSE 低温小 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⁻¹)
3003.3330.003950.00396
3203.1250.013100.01340
3402.9410.041200.04160
3602.7780.118000.11800
3802.6320.312000.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 结果。

做 NLS 时始终用线性化结果作初值,并检查 pcov 是否可逆、标准误是否合理。若 OptimizeWarning: Covariance of the parameters could not be estimated,通常是数据点太少或参数不可辨识,而不是代码写错。
不要用 NLS 的 R² 去和线性化的 R² 直接比大小——两者定义在不同空间(k 空间 vs ln k 空间),数值不可直接对照。要比就比同一空间下的 SSE 或相对残差。

快速复查清单

检查项通过标准不通过时的处理
初值来源由线性化给出,量级合理手动给 A、Ea 数量级估计
参数标准误Ea 标准误 < 1 kJ/mol增加温度点或检查数据质量
k 空间残差符号随机、无单调趋势检查模型假设或剔除异常点
相对残差绝对值 ≤ 1%复核低温点测量精度
与线性化对比Ea 差异可解释确认目标函数选择是否匹配工程需求