4、时间序列数据基础:时间序列概念、采样率与分辨率、数据窗口化、滑动窗口技术

各位同学,咱们今天聊点实在的。做嵌入式设备异常检测,说白了就是跟时间序列数据打交道。你想想看,传感器每隔几毫秒吐一个数值,温度、振动、电流……这些数据按时间排成一串,就是时间序列。我刚开始做这个方向时,总觉得不就是个数组嘛,后来踩了不少坑才明白——时间序列里的门道,比想象中深得多。

4.1 时间序列到底是什么?

时间序列,就是按时间顺序排列的观测值集合。每个数据点都带一个时间戳,比如:

2024-01-15 10:00:00.000, 25.3°C
2024-01-15 10:00:00.100, 25.4°C
2024-01-15 10:00:00.200, 25.2°C

嗯,就这么简单。但实际项目中,时间戳的精度、对齐方式、缺失值处理,才是真正让人头疼的地方。

核心要点:时间序列的三个要素——时间戳、观测值、采样间隔。缺一个,数据就没法用。

我在一个工业电机监测项目里遇到过这种情况:传感器采集的振动数据,时间戳居然用的是系统启动后的毫秒数,而不是绝对时间。结果数据一合并,根本对不上号。后来我强制要求所有设备都用NTP同步时间,才解决了这个问题。

4.2 采样率与分辨率——别搞混了

这两个概念,我见过太多人搞混了。咱们一次说清楚。

概念 定义 单位 我的经验
采样率 单位时间内采集多少个数据点 Hz(次/秒) 振动分析至少1kHz起步
分辨率 每个数据点能区分的最小变化 bit(位) 12位ADC够用,16位更好

采样率决定你能看到多快的变化。根据奈奎斯特定理,采样率至少要是信号最高频率的两倍。举个例子,电机振动频率是100Hz,那采样率至少200Hz。我一般会留3-5倍余量,用500Hz采样。

分辨率决定你能看到多小的变化。12位ADC能区分4096个等级,16位是65536个。我曾经在一个温度监测项目里用了10位ADC,结果0.5°C的变化根本看不出来,后来换成12位才解决问题。

避坑指南:采样率不是越高越好。高采样率意味着数据量大、存储压力大、处理时间更长。我曾经在一个电池供电的设备上设了10kHz采样率,结果电池半天就耗光了。后来降到1kHz,效果一样好,续航翻了三倍。

4.3 数据窗口化——为什么要切数据?

你想想看,连续采集的数据是无限长的。但我们的算法没法处理无限长的数据,对吧?所以需要把数据切成一段一段的,每一段就是一个窗口。

窗口化的目的有三个:

  • 降低计算量——只处理最近一段数据,而不是全部历史
  • 捕捉局部特征——异常往往发生在短时间内,窗口能聚焦
  • 适应实时性——嵌入式系统内存有限,不能存太多数据

窗口大小怎么选?我个人的习惯是:先看你的异常特征持续多久。比如电机轴承故障,振动异常通常持续0.1-0.5秒。那窗口大小就设1秒,留点余量。

小技巧:窗口大小最好是采样率的整数倍。比如采样率1000Hz,窗口大小设1000个点(1秒),这样计算FFT时特别方便。

4.4 滑动窗口技术——实时检测的核心

滑动窗口,说白了就是窗口在时间轴上不断往前移动。每移动一步,丢掉最旧的数据,加入最新的数据。这样就能持续监测,实时检测异常。

滑动窗口有两个关键参数:

  • 窗口大小(W)——每次处理多少个数据点
  • 滑动步长(S)——每次移动多少个数据点

步长决定了检测的实时性。步长越小,检测越频繁,但计算量也越大。我一般设步长为窗口大小的1/4到1/2。

// 滑动窗口的C语言实现示例
#define WINDOW_SIZE 100
#define STEP_SIZE 25

float buffer[WINDOW_SIZE];
int index = 0;

void slide_window(float new_data) {
    // 移动数据
    for (int i = 0; i < WINDOW_SIZE - 1; i++) {
        buffer[i] = buffer[i + 1];
    }
    buffer[WINDOW_SIZE - 1] = new_data;
    
    // 每STEP_SIZE个点计算一次特征
    index++;
    if (index >= STEP_SIZE) {
        float mean = calculate_mean(buffer, WINDOW_SIZE);
        float std = calculate_std(buffer, WINDOW_SIZE);
        // 异常检测逻辑
        check_anomaly(mean, std);
        index = 0;
    }
}

这段代码我实际用过。注意看,我没有每来一个数据就计算一次,而是每25个点算一次。这样既保证了实时性,又不会把CPU跑满。

关键点:滑动窗口的步长决定了检测延迟。步长25个点,采样率1000Hz,那延迟就是25ms。对于大多数机械故障检测,这个延迟完全可以接受。

4.5 实际项目中的坑与经验

说了这么多理论,来点实际的。我在做嵌入式异常检测时,遇到过几个典型问题:

  1. 时间戳漂移——设备长时间运行后,内部时钟会偏差。我建议定期用NTP同步,或者用GPS授时。
  2. 数据丢包——无线传感器偶尔会丢数据。我的做法是:丢包率小于1%时用插值填补,超过1%就标记为无效窗口。
  3. 窗口边界效应——异常刚好发生在两个窗口交界处,可能被漏检。解决方案是使用重叠窗口,比如步长设为窗口大小的1/4。

我曾经踩过的坑:在一个水处理项目中,我用了固定窗口大小,结果设备启动时数据不稳定,窗口里全是异常值,导致误报不断。后来我加了启动阶段的预热期,前100个窗口不检测,只用来建立基线。问题就解决了。

4.6 总结一下

时间序列数据是嵌入式异常检测的基石。采样率决定你能看到什么,分辨率决定你能看清什么,窗口化让你能处理实时数据,滑动窗口让你能持续监测。这四个概念,说白了就是一套组合拳。

我个人建议,刚开始做项目时,先拿一段真实数据跑一遍滑动窗口,看看窗口大小和步长对检测结果的影响。纸上得来终觉浅,绝知此事要躬行。嗯,今天就到这里,下节课咱们聊特征提取。

课后练习:找一段传感器数据(比如加速度计或温度数据),用Python或C实现滑动窗口,计算每个窗口的均值和方差。看看不同窗口大小对结果的影响。