模型结构摆在那儿,参数全是未知数——这种局面我在建模里碰到的次数,比想象中多得多。写方程容易,把方程里那堆系数定下来才是真正见功力的地方。经典辨识法就是干这件事的一套老办法:模型形式已经给定,输入输出数据也测到了,剩下的任务就是从数据里把参数反推出来。它不追求花哨,但胜在可解释、可复现、可验证,特别适合做数学建模竞赛题、做控制系统设计、做工程数据分析的人。这篇笔记是我把自己这几年在辨识上踩过的坑、走过的弯路、以及最后沉淀下来的操作流程整理出来的一次复盘,从数据采集一直讲到参数验证,尽量把每一步为什么这么做都说清楚。
1. 经典辨识法到底解决什么问题:先把问题边界划清楚
刚接触辨识的人容易把所有"求参数"的问题都归到辨识里,结果方法选错、数据采错,后面怎么调都白搭。所以第一步不是打开编辑器写代码,而是判断你手头这个问题究竟属于哪一类。
1.1 三类建模问题的界线:白箱、灰箱、黑箱
工程和科研里的建模问题,我习惯按"结构已知程度"分成三档。白箱是机理完全清楚、参数也有物理意义的情况,比如牛顿第二定律里质量、阻尼系数、刚度系数都能量出来,剩下的只是把它们填进方程求解。黑箱是机理完全不清楚,只能靠输入输出关系去拟合,神经网络、随机森林这类方法就是干这个的。而经典辨识法真正的主战场在中间的灰箱:模型结构有理论依据(比如一阶惯性加纯滞后、二阶传递函数、离散差分方程),但参数没法直接测量,或者测量成本极高。
举个建模比赛里常见的例子:传染病模型里,传播率、恢复率、潜伏期转化率这些参数没有仪器能直接读出来,只能靠每日新增病例数据去反推。再比如机械臂的关节模型,惯量、摩擦系数、电机时间常数,理论上可以查手册,但装配之后的有效值跟手册值差得远,只能通过激励实验辨识。这类问题的共同特征是:方程的形式你有信心,缺的是系数。
区分这三类很关键,因为它直接决定了你的方法边界。灰箱问题硬上黑箱方法,会丢掉物理约束,得到一堆没有意义的系数;白箱问题去做辨识,是浪费时间。我见过不少同学明明机理清楚,却非要用数据拟合,最后拟合出来的参数和物理量纲都对不上。
1.2 辨识的三要素:数据、模型集、准则
所有辨识方法本质上都是同一个套路:给定数据、给定候选模型集合、给定一个衡量"拟合好坏"的准则,然后在模型集合里挑出让准则最优的那个。这三要素缺一不可,而且顺序不能乱。
数据决定了你能辨识出多少信息,这是物理层面的限制。模型集决定了你的搜索空间,选太小会欠拟合(真模型不在集合里),选太大又会过拟合(噪声被当成规律)。准则决定了你怎么定义"好",最常用的就是预测误差平方和,但也可以是最小二乘、极大似然、最小预测误差熵等等。
这三者里,我个人的经验是数据的重要性被严重低估。很多人卡在参数估不准,其实问题压根不在算法,而在于激励信号太温和,输入变化幅度不够,导致某些参数方向上的信息几乎为零。这种时候换再高级的算法也没用,因为信息根本不在数据里。
1.3 为什么先做经典辨识而不是直接上神经网络
现在工具太方便了,遇到参数问题,很多人第一反应是丢给神经网络或者交给大模型去猜。我不反对用新工具,但顺序上建议先做经典辨识,理由有三个。
一是可解释性。经典辨识给出的参数有明确物理意义,能直接写进论文、拿去和文献值对比。二是样本效率。辨识二阶系统,几百个采样点就够,神经网络往往需要几千上万条。在实验成本高的场景下,这是个硬约束。三是可诊断性。经典方法出问题的时候,你能定位到是激励不足、噪声有色、还是模型阶次不对;神经网络出问题,通常只能看到损失不下降,原因全靠猜。
我自己的做法是:先用经典辨识拿到一组有物理意义的参数作为基准,再看神经网络能不能在预测精度上明显超过它。如果超不过,就说明经典方法已经够用,没必要引入黑箱的复杂度;如果超过了,也至少有个可解释的参照系,不至于完全失控。
2. 数据采集与实验设计:辨识的成败在动手之前就定了
辨识圈里有句老话:给你一组烂数据,再好的算法也救不回来。数据环节做扎实,后面的事半功倍;数据环节敷衍,后面就是在给噪声调参。这一章讲的是采集阶段最容易被忽略、但影响最大的几件事。
2.1 激励信号的选型:阶跃、脉冲、M序列、扫频
激励信号的本质任务,是把系统各个频段、各个参数方向都"戳"一遍。信号太温和,系统没被充分激发,某些参数就辨识不出来。下面这张表是我总结的常见激励信号适用场景,实际选型时可以直接对照。
| 激励信号 | 频带覆盖 | 适用场景 | 主要缺点 |
|---|---|---|---|
| 阶跃信号 | 低频为主 | 一阶、二阶系统快速粗辨 | 高频信息少,有色噪声下偏差大 |
| 脉冲信号 | 宽带但能量低 | 冲击响应试验、快速测试 | 信噪比低,对采样率要求高 |
| M序列 / PRBS | 宽带可调 | 离散系统、在线辨识 | 需要设计周期和幅值,易被非线性干扰 |
| 正弦扫频 | 逐点精确 | 频域辨识、传递函数拟合 | 耗时,一次实验覆盖频点有限 |
| 白噪声 | 全频带 | 理论分析、仿真验证 | 实际很难生成,幅值受限 |
选型的核心判断依据是:你要辨识的参数主要受哪个频段影响。比如辨识系统增益,低频信息就够,一个阶跃信号足矣;辨识时间常数或者高频极点,就得靠 M 序列这类宽带信号。我早期做过一次实验,想辨识一个含高频动态的二阶系统,结果只用了一个缓慢的阶跃,测出来的高频极点完全是噪声里抠出来的,跟真值差了百分之三十。后来换成时钟周期合适的 PRBS,同一套算法,误差直接降到百分之三以内。信号选错,算法再对也白搭。
还有一点常被忽略:激励幅值。幅值太小,输出变化淹在噪声里;幅值太大,系统进入非线性区(比如电机饱和、阀门死区),线性模型的假设就破了。我的经验是先在允许范围内做一次小幅值试探,看看输出是否线性变化,再逐步加大到最大允许幅值的三分之二左右。
2.2 采样周期与数据长度怎么算
采样周期选得太粗会丢掉动态信息,选得太细会造成数据冗余、矩阵条件数变差,两个极端都难受。工程上常用的经验判据是:采样频率取系统期望带宽的 5 到 10 倍,或者说采样周期取系统上升时间的十分之一到二十分之一。
举个具体的算例。假设你辨识的是一个时间常数约为 0.5 秒的一阶系统,那么它的带宽大致在 (1/(2\pi \times 0.5)) 附近,也就是 0.3 赫兹量级。按 10 倍带宽取采样频率,也就是 3 赫兹,对应采样周期约 0.33 秒。要是系统上升时间约 1.5 秒,按十分之一取,采样周期约 0.15 秒,两者取个中间值,采样周期定在 0.1 到 0.2 秒之间比较稳妥。
数据长度方面,有个粗略的经验法则:每个待辨识参数至少对应 10 到 50 个有效数据点。二阶离散差分方程通常有 4 到 5 个参数,那至少需要 50 到 250 个采样点,我一般会准备 500 到 1000 个点,留出余量给预处理和验证集切分。数据太短,参数估计的方差大;数据太长,又可能引入系统工况漂移,尤其是做在线辨识时要权衡。
2.3 数据预处理:去趋势、去野值、滤波的先后顺序
预处理顺序错了,会引入假信息。我的固定流程是这样的:先去野值,再去趋势,最后才滤波。
去野值放在最前面,是因为野值会影响后面所有步骤的统计量,尤其是去趋势用的均值和中位数。常用的野值判据是 3σ 准则或者中位数绝对偏差(MAD)准则,后者对野值本身更鲁棒。去趋势是为了处理传感器漂移或者工况缓慢变化,常用方法是一阶差分或者减去滑动平均。滤波放最后,是因为滤波本身会改变信号的相位和幅值,如果在去趋势之前滤波,滤波器的滞后可能被误判成趋势。
这里必须提醒一个坑:滤波会引入相位延迟,如果输入输出两路信号用了相同的滤波器,延迟基本相同,影响不大;但如果只滤了输出没滤输入,两路信号就对不齐了,辨识出来的模型会出现虚假的纯滞后,甚至把时间常数估错。我做实验时固定用同一个滤波器参数对两路信号处理,这一点从来不敢省。
注意:低通滤波的截止频率不要低于系统带宽,否则会把有用的动态信息一起滤掉,模型阶次会被低估。
3. 核心算法逐个拆解:从最小二乘到极大似然
前面把数据和问题理清楚了,这一章进入算法本身。我按"上手难度"和"抗噪能力"两条线,把经典方法串一遍。每个方法我都会说清楚:它假设了什么、在什么情况下失效、以及实际用的时候要注意什么。
3.1 最小二乘:一切参数辨识的原点
最小二乘是所有辨识方法的祖宗,理解它,后面那些变体就都能看懂了。它的核心假设非常朴素:模型输出可以写成"回归向量乘以参数向量",加上一个残差项。比如一个二阶离散差分方程:
y(k) = -a1*y(k-1) - a2*y(k-2) + b1*u(k-1) + b2*u(k-2) + e(k)把[-y(k-1), -y(k-2), u(k-1), u(k-2)]组成回归向量 φ(k),把[a1, a2, b1, b2]组成参数向量 θ,那么y(k) = φ(k)^T θ + e(k)。把所有时刻的方程叠起来写成矩阵形式Y = Φθ + E,最小二乘解就是:
θ_hat = (Φ^T Φ)^{-1} Φ^T Y这个公式要背下来,因为它出现的频率太高了。它的几何意义是把观测向量 Y 投影到回归矩阵 Φ 张成的空间里,投影残差和空间正交。这个正交性就是"最小"的来源。
最小二乘的致命弱点在于它假设残差 e(k) 和回归向量 φ(k) 不相关。如果噪声是白噪声,这个假设成立,估计是无偏的;但如果噪声是有色噪声(实际系统里几乎都是),噪声通过-a1*y(k-1)这类项反馈进了回归向量,正交性被破坏,估计就偏了。而且这个偏差不是随机误差,是系统性偏差,数据再多也消不掉。
注意:很多人以为数据量够大最小二乘就一定准,这是误解。有色噪声下,最小二乘是一致但有偏的估计,偏差大小取决于噪声和系统的耦合程度。
3.2 递推最小二乘与遗忘因子:在线辨识的主力
实际工程中,数据往往是一个一个来的,或者系统参数会缓慢漂移,这时候一次性求逆的批处理最小二乘就不合适了,得用递推形式。递推最小二乘的核心思想是:新来一个数据点,就在旧估计的基础上做一次修正,不需要重新算矩阵求逆。
它的更新公式大致是这样:先算增益向量K(k) = P(k-1)φ(k) / (λ + φ(k)^T P(k-1)φ(k)),然后更新参数θ(k) = θ(k-1) + K(k)(y(k) - φ(k)^T θ(k-1)),最后更新协方差矩阵P(k) = (P(k-1) - K(k)φ(k)^T P(k-1)) / λ。这里的 λ 就是遗忘因子,取值通常在 0.95 到 0.99 之间。
遗忘因子的作用,是给旧数据降权,让算法能跟上参数变化。λ 等于 1 就是标准递推最小二乘,所有历史数据等权;λ 小于 1 时,大约1/(1-λ)个采样点之前的旧数据权重衰减到很低。比如 λ 取 0.98,有效记忆长度约 50 个点。遗忘因子太小,估计会跟着噪声剧烈抖动;太大,又跟不上参数变化,跟踪滞后。
我实际调这块的体会是:先别急着定 λ,而是把 λ 从 1 开始逐步往下调,观察参数估计曲线。如果曲线收敛后是平的,λ 就取 1;如果曲线收敛后有缓慢漂移,说明参数在变,再往下调 λ,调到曲线既不抖又能跟上为止。这个过程比拍脑袋定一个数靠谱得多。
3.3 辅助变量法:对付有色噪声的一把好手
辅助变量法的思路很巧妙:既然最小二乘失效是因为回归向量和噪声相关,那我就构造一个新的向量,它跟回归向量高度相关,但跟噪声不相关,用它去做投影。这个新向量就是"辅助变量"。
构造辅助变量最常用的办法,是用一个"预滤波器"对输入输出信号再走一遍,生成一组干净的工具信号。常用的辅助变量有:延迟的输入信号、用高估阶次模型产生的预测输出、以及通过相关分析得到的中间变量。延迟输入法实现最简单:因为输入 u(k) 通常和噪声不相关,用u(k-1)这类延迟项去替代回归向量里的y(k-1),就能打破噪声的反馈回路。
辅助变量法的优点是计算量小、不需要迭代、在有色噪声下能得到一致估计。缺点是辅助变量的选择有点技巧性,选得不好,虽然一致但方差可能很大。我的经验是:先用最小二乘跑一遍拿到一个粗略参数,再用这个粗略模型生成预测输出作为辅助变量,迭代一两轮,效果通常能明显改善。
3.4 广义最小二乘与增广最小二乘:把噪声模型也辨识出来
有色噪声下另一个思路是:不回避噪声,干脆把噪声的模型也一起辨识出来。增广最小二乘(RELS)就是这个思路的简化版,它在回归向量里加入残差的延迟项,形式上是把噪声建模成移动平均过程。广义最小二乘(GLS)更彻底,它把噪声建模成自回归过程,通过迭代的方式交替估计系统参数和噪声参数。
这两个方法的共同点是都引入了噪声模型,好处是估计精度高,代价是计算复杂、需要迭代、初值敏感。我在实际项目里的选择标准是:如果只是为了预测输出,用增广最小二乘就够,实现简单;如果要精确分离信号和噪声(比如做故障诊断、要拿噪声特征做判断),那就上广义最小二乘。
需要提醒的是,这类方法对模型阶次很敏感。噪声模型阶次给低了,滤不干净;给高了,容易过拟合,把系统动态当成噪声。一般噪声模型阶次不超过系统阶次加一,我通常从一阶噪声模型开始试。
3.5 相关分析两步法与极大似然、梯度校正
相关分析两步法是先做非参数辨识再做参数辨识的经典组合。第一步,用输入输出的互相关函数估计系统的脉冲响应;第二步,把脉冲响应当成数据,用最小二乘去拟合一个参数模型。它的好处是对噪声不敏感,因为相关运算本身就是一种平均,白噪声经过相关运算后会衰减。缺点是要求输入是白噪声或者至少是持续激励信号,否则相关函数估计不准。
对于白噪声激励下的线性系统,相关分析两步法能给出非常好的结果,我在做仿真验证时常用它作为"金标准"去对比其他算法。但真实系统里很少能施加理想白噪声,所以它更多是理论分析工具。
极大似然法是理论上最优的方法,它能达到克拉美-罗下界,也就是方差理论最小值。代价是它需要假设噪声的概率分布,还要做非线性优化,计算量大,而且有局部极值的问题。实际用的时候,我通常把它作为最后一步精细化的工具:用前面某个方法拿到初值,再交给极大似然去微调。
梯度校正法(又叫随机逼近法)是最简单的一类,它沿着负梯度方向一点点挪参数,优点是计算量极小、容易实现,缺点是收敛慢。它适合嵌入式设备这种算力受限的场景,或者作为其他算法的初始化手段。
4. 亲手跑一遍:Python 实现递推最小二乘辨识二阶系统
光看公式容易飘,这一章我带你从头到尾跑一遍完整流程,包括造数据、写算法、看结果、做验证。代码用的是 numpy,不依赖任何专用工具箱,复制下来就能跑。
4.1 数据生成与差分方程构造
先造一个已知参数的系统,这样我们才知道辨识结果准不准。假设真实系统是:
y(k) = 1.5*y(k-1) - 0.7*y(k-2) + 0.8*u(k-1) + 0.5*u(k-2) + e(k)参数真值是 a1=-1.5, a2=0.7, b1=0.8, b2=0.5。输入用幅值在 -1 到 1 之间的随机信号,噪声用高斯白噪声,标准差取 0.05,模拟一个信噪比合理的场景。
import numpy as np np.random.seed(42) N = 1000 u = np.random.uniform(-1, 1, N) e = np.random.normal(0, 0.05, N) a1_true, a2_true, b1_true, b2_true = -1.5, 0.7, 0.8, 0.5 y = np.zeros(N) for k in range(2, N): y[k] = (-a1_true * y[k-1] - a2_true * y[k-2] + b1_true * u[k-1] + b2_true * u[k-2] + e[k])这段代码里有个细节要注意:循环从 k=2 开始,因为差分方程需要两个历史值,前两个点作为初始条件保留为零。实际做辨识时,前几十个点因为初始条件不准,通常不参与统计,或者用零初始化的方式让算法自己收敛。
4.2 递推最小二乘代码实现与逐行说明
接下来实现递推最小二乘。参数向量按[a1, a2, b1, b2]排列,回归向量按[-y(k-1), -y(k-2), u(k-1), u(k-2)]构造。
theta = np.zeros(4) P = np.eye(4) * 1000 # 初始协方差给大值,表示对初值不信任 lam = 1.0 # 遗忘因子,先取1做标准RLS theta_history = [] for k in range(2, N): phi = np.array([-y[k-1], -y[k-2], u[k-1], u[k-2]]) y_pred = phi @ theta K = P @ phi / (lam + phi @ P @ phi) theta = theta + K * (y[k] - y_pred) P = (P - np.outer(K, phi) @ P) / lam theta_history.append(theta.copy()) print("辨识结果:", theta) print("真实参数: [-1.5 0.7 0.8 0.5]")跑出来的结果通常在真值附近浮动百分之一到百分之几。初始协方差 P 给大值,是因为我们对初始参数完全没信心,让算法在前几个点快速调整;如果初值有把握(比如上一轮辨识的结果),P 可以给小一点,收敛会更快。
4.3 结果验证:残差白度、交叉验证与参数置信区间
参数估计出来还不算完,必须验证。我固定做三件事。
第一件是看残差。计算e_hat(k) = y(k) - φ(k)^T θ,然后检查残差的自相关函数。如果模型结构正确、噪声是白噪声,残差应该接近白噪声,自相关在非零延迟处应该很小。如果残差自相关有明显峰值,说明模型阶次低了,或者残差里还有没被解释的动态,得回去加阶次。
第二件是交叉验证。把数据分成两段,前 70% 用于辨识,后 30% 用于验证,看模型在验证集上的预测误差。如果训练集误差小、验证集误差大,就是过拟合,得降阶次或者加正则化。这个步骤在建模竞赛里尤其重要,评委很看重模型的泛化能力。
第三件是看参数置信区间。递推最小二乘的协方差矩阵 P 直接给出了参数的方差估计,参数标准差就是 P 对角线元素的平方根。如果某个参数的标准差跟它的估计值一个量级,说明这个参数没被辨识出来,多半是激励不够。这时候要回去检查输入信号在那个参数方向上的激励是否充分。
提示:参数置信区间宽,先别怀疑算法,先怀疑数据。九成情况是激励信号设计的问题。
5. 常见问题与排查技巧实录
辨识这件事,算法只是其中一环,真正消耗时间的是排查各种异常。这一章我把这些年遇到的问题整理成速查表,配上我自己的排查思路。
5.1 参数估不准的典型症状与对策
| 症状 | 可能原因 | 排查方法 | 对策 |
|---|---|---|---|
| 参数稳定偏离真值 | 有色噪声导致最小二乘有偏 | 检查残差自相关 | 换成辅助变量或增广最小二乘 |
| 参数剧烈抖动 | 遗忘因子过小或数据噪声大 | 看参数随时间曲线 | 增大遗忘因子、增加滤波 |
| 参数收敛极慢 | 初值差、激励不足 | 检查 P 矩阵收敛情况 | 加大初始 P、增强激励 |
| 某些参数估不出来 | 该参数方向无激励 | 看参数置信区间 | 换激励信号或增加幅值 |
| 训练好验证差 | 过拟合、阶次过高 | 交叉验证对比 | 降低模型阶次、加正则 |
| 出现虚假纯滞后 | 输入输出滤波不一致 | 对比两路信号延迟 | 用相同滤波器处理两路 |
这张表基本覆盖了我遇到的八成问题。剩下两成通常是数值问题,下一节专门讲。
5.2 数值稳定性与病态矩阵的处理
最小二乘要求解(Φ^T Φ)^{-1},当数据存在共线性或者量纲差异大时,这个矩阵会接近奇异,求逆结果被放大误差污染。几个实用的处理办法:
一是做归一化。把输入输出都减均值、除标准差,让各列量级接近,能显著改善条件数。这个方法我每回都用,几乎不花钱。
二是用 QR 分解或者奇异值分解代替直接求逆。numpy 里的np.linalg.lstsq内部就是用这种方法,数值稳定性远好于显式求逆。批处理辨识时直接用这个函数,别自己写求逆。
三是检查条件数。np.linalg.cond(Φ)如果超过 10 的 6 次方,基本可以判定矩阵病态,这时候估计结果不可信,得回数据层面找原因,比如是不是某段数据几乎没变化。
递推最小二乘也有对应的稳定性问题,主要来自 P 矩阵的对称性和正定性在数值迭代中可能被破坏。工程上常用 UD 分解或者平方根滤波来保持正定性,如果嫌复杂,至少每迭代若干步重新对称化一次 P 矩阵,能缓解不少。
5.3 几个踩过的坑
第一个坑是数据对齐。输入输出采集如果用不同设备,时钟可能有微小偏差,这个偏差会在辨识中表现为虚假滞后,让模型阶次判断出错。解决办法是做一次互相关,找到互相关峰值位置,据此对齐两路信号。
第二个坑是量纲。曾经有个项目,输入是电压信号在毫伏量级,输出是温度在摄氏度量级,量纲差了几个数量级。最小二乘直接跑出来的参数极小,看着像是估不出来,其实是数值精度问题。归一化之后一切正常。
第三个坑是验证集泄漏。有段时间我把数据随机打乱后切分训练验证集,结果验证误差异常地好,后来才发现系统的动态是连续相关的,随机切分会让相邻点落到不同的集合里,造成信息泄漏。正确做法是按时间顺序切分,前一段训练,后一段验证。
6. 方法选型的一点个人经验
走到这里,方法也讲完了,实现也跑过了,问题也排查过了。最后分享几条我在实际项目里形成的选型直觉。
如果模型阶次不超过三阶、噪声水平不高、又有条件设计实验,我一般先用最小二乘加归一化快速跑一遍,看看参数量级和置信区间。这一步花不了十分钟,但能帮你判断问题难度。如果置信区间很宽,别急着换算法,先回去改实验设计。这是我见过最多的误判:把数据问题当成算法问题,反复折腾算法,效率极低。
如果噪声有色明显,我习惯直接上辅助变量法,实现成本比广义最小二乘低,效果通常也够用。只有在需要精确分离噪声特征时才上广义最小二乘或极大似然。递推形式我只在系统参数会漂移、或者需要在线更新的时候才用,静态问题用批处理更省事,也更容易复现。
在建模竞赛的语境下,我还会额外做一件事:把辨识出来的参数代回原方程的物理约束里检查一遍。比如传染病模型的传播率必须是非负的,如果辨识结果出现负值,那一定是模型或者数据出了问题。这种物理约束的检查,往往是发现错误最快的手段,比看残差曲线还直接。
辨识这个事,说到底是和数据打交道的过程。算法是工具,数据是原料,验证是质检。三样都到位,参数自然就准了。