第二章 辨识数据采集:激励信号设计、采样率选择与数据预处理
大家好,我是老张。今天咱们聊聊辨识数据采集这个环节。说实话,很多工程师把精力都放在算法上,却忽略了数据质量。我见过太多案例——算法调得再好,数据一塌糊涂,结果全是白费功夫。
数据采集,说白了就是给系统做一次“体检”。你得设计合适的“体检项目”,选对“测量仪器”,最后还得把“体检报告”整理干净。这一章,我就把这三个环节掰开揉碎了讲。
核心观点:辨识数据的质量,直接决定了模型精度的天花板。数据不好,再牛的算法也救不了。
2.1 激励信号设计
为什么要设计激励信号?你想想看,系统就像一个人,你问它“你还好吗?”,它可能回你“嗯”。但如果你给它来点刺激,它才会真正“开口说话”。激励信号就是用来“刺激”系统的,让它把动态特性全抖出来。
我个人习惯,最常用的两种信号是PRBS和扫频信号。它们各有各的脾气。
2.1.1 PRBS信号
PRBS,全称是伪随机二进制序列。说白了,它就是在两个值之间来回跳的信号。为什么叫“伪随机”?因为它看起来随机,其实是有规律的。
PRBS的核心参数:
- 阶数n:决定了序列长度,长度 = 2^n - 1。n越大,信号越“随机”。
- 时钟周期T_clk:信号保持一个值的最小时间。这决定了信号的带宽。
- 幅值:一般取系统正常工作范围的5%~20%。
我在项目中遇到过一件事。有一次给一个温度系统做辨识,我用了PRBS信号,结果模型死活不准。后来发现,温度系统响应太慢,PRBS跳变太快,系统根本来不及反应。嗯,这里要注意——PRBS的时钟周期必须大于系统的上升时间。
经验之谈:PRBS的时钟周期T_clk,我一般取系统时间常数的1/5到1/10。这样既能激励出高频特性,又不至于让系统“跟不上”。
PRBS的生成代码(Python):
import numpy as np
def generate_prbs(n, T_clk, num_periods=1):
"""
生成PRBS信号
n: 阶数
T_clk: 时钟周期(采样点数)
num_periods: 周期数
"""
# 线性反馈移位寄存器
reg = np.ones(n, dtype=int)
seq_len = 2**n - 1
seq = np.zeros(seq_len)
for i in range(seq_len):
seq[i] = reg[-1]
# 反馈多项式:x^n + x^(n-1) + 1(常用)
feedback = reg[-1] ^ reg[-2]
reg = np.roll(reg, 1)
reg[0] = feedback
# 扩展到时钟周期
signal = np.repeat(seq, T_clk)
# 映射到[-1, 1]
signal = 2 * signal - 1
return np.tile(signal, num_periods)
2.1.2 扫频信号
扫频信号,就是频率从低到高慢慢扫过去。它特别适合用来摸清系统的“频率响应”。我个人觉得,扫频信号比PRBS更直观——你一看频谱图,就知道系统在哪个频率段“兴奋”,哪个频率段“萎了”。
扫频信号的参数:
- 起始频率f_start:一般取系统最低工作频率的1/10。
- 终止频率f_end:一般取系统最高工作频率的10倍。
- 扫频时间T_sweep:决定了频率变化的速度。太短了,系统来不及响应;太长了,浪费时间。
- 幅值:同PRBS,取5%~20%。
注意:扫频信号有一个坑——如果扫频速度太快,系统会出现“瞬态响应”和“稳态响应”混在一起的情况。我曾经吃过这个亏,扫频结果乱七八糟,后来把扫频时间延长了3倍才搞定。
扫频信号的生成代码:
def generate_chirp(f_start, f_end, T_sweep, fs):
"""
生成线性扫频信号
f_start: 起始频率 (Hz)
f_end: 终止频率 (Hz)
T_sweep: 扫频时间 (s)
fs: 采样率 (Hz)
"""
t = np.linspace(0, T_sweep, int(T_sweep * fs))
# 瞬时频率线性变化
inst_freq = f_start + (f_end - f_start) * t / T_sweep
# 相位是频率的积分
phase = 2 * np.pi * np.cumsum(inst_freq) / fs
signal = np.sin(phase)
return t, signal
2.1.3 PRBS vs 扫频信号:怎么选?
| 特性 | PRBS | 扫频信号 |
|---|---|---|
| 频谱特性 | 宽频带,能量均匀 | 窄频带,能量集中 |
| 对非线性敏感度 | 低 | 高 |
| 实现难度 | 简单(数字电路直接生成) | 中等(需要D/A转换) |
| 适用场景 | 线性系统、弱非线性系统 | 强非线性系统、需要频率响应分析 |
我个人习惯:如果系统比较“规矩”(线性度好),我首选PRBS。如果系统有强非线性,或者我想仔细看看频率响应,那就用扫频信号。
2.2 采样率选择
采样率选多少?这个问题看似简单,其实坑很多。
基本原则:采样率必须满足奈奎斯特采样定理——采样率 ≥ 2 × 系统最高频率。但实际工程中,我一般取5~10倍。
为什么?你想想看,奈奎斯特定理只是保证“不混叠”,但没保证“辨识精度”。采样率太低,高频信息丢失;采样率太高,数据量爆炸,计算也慢。
我的经验公式:
- 对于慢系统(温度、液位):采样率 = 10~20倍系统带宽
- 对于中速系统(电机转速):采样率 = 5~10倍系统带宽
- 对于快速系统(振动、电流):采样率 = 3~5倍系统带宽
我曾经在一个电机辨识项目里,采样率设得太高(100kHz),结果数据量巨大,跑一次辨识要半小时。后来降到10kHz,精度几乎没变,速度却快了10倍。嗯,这里要提醒大家——采样率不是越高越好,够用就行。
2.3 数据预处理
数据采回来了,但别急着用。原始数据里有很多“脏东西”——趋势项、噪声、异常值。不处理干净,模型就会学歪。
2.3.1 去趋势
什么是趋势项?就是数据中缓慢变化的成分。比如温度系统,环境温度在慢慢上升,这个趋势不是系统本身的动态特性,必须去掉。
去趋势的方法:
- 均值移除:最简单,减去数据的均值。适用于趋势是常数的情况。
- 线性去趋势:拟合一条直线,然后减去。适用于趋势是线性变化的情况。
- 高通滤波:用高通滤波器滤掉低频趋势。适用于趋势频率已知的情况。
代码示例:
from scipy import signal
def detrend_signal(data, method='linear'):
"""
去趋势
method: 'constant'(均值移除), 'linear'(线性去趋势)
"""
if method == 'constant':
return data - np.mean(data)
elif method == 'linear':
# 使用scipy的detrend函数
return signal.detrend(data, type='linear')
else:
raise ValueError("不支持的方法")
2.3.2 滤波
滤波是为了去掉高频噪声。但要注意——滤波也会改变信号的相位和幅值。搞不好,你会把有用的信息也滤掉。
我常用的滤波器:
- 低通滤波器:最常用。截止频率取系统带宽的2~3倍。
- 中值滤波器:对付“野点”(异常值)特别好用。
- 零相位滤波:用filtfilt函数,可以避免相位偏移。我个人强烈推荐。
避坑指南:我曾经用普通低通滤波器处理数据,结果模型辨识出来总是有延迟。后来才发现,普通滤波器会引入相位滞后。改用零相位滤波后,问题就解决了。
零相位滤波代码:
from scipy.signal import butter, filtfilt
def zero_phase_lowpass(data, cutoff, fs, order=4):
"""
零相位低通滤波
cutoff: 截止频率 (Hz)
fs: 采样率 (Hz)
"""
nyquist = 0.5 * fs
normal_cutoff = cutoff / nyquist
b, a = butter(order, normal_cutoff, btype='low')
# 使用filtfilt实现零相位
filtered = filtfilt(b, a, data)
return filtered
2.3.3 数据预处理流程总结
我一般按这个顺序处理数据:
- 检查数据完整性:有没有缺失值?有没有明显异常?
- 去趋势:先去掉缓慢变化的趋势项。
- 滤波:用零相位低通滤波器去掉高频噪声。
- 归一化:把数据缩放到[-1, 1]或[0, 1]区间,方便后续计算。
- 分段:把数据分成训练集和验证集。
小技巧:预处理完成后,一定要看一眼处理前后的对比图。如果滤波后的信号“太光滑”,说明截止频率设得太低了,丢失了信息。如果还是“毛刺很多”,说明截止频率太高了,噪声没滤干净。
本章知识体系
下面这张图,是我自己总结的数据采集全流程。你可以把它当作一个检查清单。
好了,这一章的内容就到这里。数据采集是辨识工作的“地基”,地基不牢,房子再漂亮也白搭。下一章我们会讲怎么用这些数据来辨识模型参数,到时候见。