年轻的心内科实习生小王第一天到急诊科报到。李老师递给他一张心电图纸,说:「看一眼,告诉我这位患者的心律正常吗?」小王盯着那条波浪线,只觉得它像过山车的轨道,忽上忽下,完全看不出门道。李老师笑了笑:「别急。判断一张心电图,就像认出窗外走过的熟人——先看整体节奏,再比对记忆里的波形,最后靠经验练出的直觉。」 这段话其实点出了时间序列分类的三大思路:从波形里找特征、把当前波形与模板对齐比较、让模型从海量数据里自动学规律。所谓时间序列分类,就是把按时间顺序记录的数据(比如心电图、手环加速度、机器振动)归到预先定好的类别里(正常/异常、挥手/翻转、健康/故障)。这篇文章就跟着小王从「看见波形」到「看懂波形」的过程,一起学会如何从波形中识别模式。
时间序列分类是医疗、可穿戴设备与工业监测等领域最核心的算法能力之一,掌握它意味着你能从一堆「会动的数据」里读出决策信号——这对 AI 从业者是极高的实用价值。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
时间序列分类问题概述 分类器 → 类别标签 任务定义:输入时序波形 → 输出离散类别标签 区别于回归(预测连续值)和聚类(无标签),分类是预测离散类别。 D(i,j) = d(i,j) + min{ DTW距离排序 (k=5) 时间序列分类可视化:DTW动态时...
时序分类概述
小王看着手里的心电图直发愁。李老师指着纸上的波浪线说:「这条线不是一条简单的线,它是心脏每一次跳动在时间轴上的『脚印』。你的任务,就是根据这串脚印判断它属于正常人还是心脏病患者。」小王恍然大悟:原来所谓时间序列,就是一串按固定时间间隔记录下来的观测值;而时间序列分类,就是把这串脚印归到预设的类别里——正常或者异常。
从技术角度看,分类问题的关键在于需要一个「判官」:输入一段波形,输出一个类别。这个判官在数学上就是一个函数 f,它把整条序列压缩成一个标签。与普通表格不同,这里的输入有严格的时间顺序——把心电图的前后两段调换,它就不再是原来的心电图了。
具体地,一条时间序列可以写成 X = (x₁, x₂, …, x_T),每个 x_t 是第 t 个时刻的观测值。分类任务则是找到函数 f,把 X 映射到类别 y。在医疗场景中,X 是心电图信号,y 来自「正常/异常」两个类别;在手势识别场景中,X 是加速度计的三轴读数,y 是「挥手/翻转/点击」。只要数据带时间顺序、标签事先定好,问题就是时间序列分类。
小王在急诊室开始整理病例:手边有心电图、血氧仪曲线、体温曲线。李老师提醒他:「这些波形虽然来源不同,但方法论是相通的——先分类,再诊断。」小王终于明白,时间序列分类是所有波形分析的第一步。
- 序列表达X 是长度为 T 的观测值列表,下标 t 表示第几个时刻,顺序不能打乱。
- 模型映射f 是把序列输入映射到类别编号的输出函数,y 的取值来自有限的 C 个类别。
特征工程提取
李老师让小王把正常和异常的心电图各挑十张,问:「你能用几个数字概括它们的区别吗?」小王看了看,说:「正常的心跳快慢差不多、波形高度稳定;异常的忽快忽慢、忽高忽低。」李老师点头:「这不就找到特征了!」所谓特征工程,就是把波形压缩成几个有判别力的数字,就像用身高体重描述一个人,而不是画出他的完整轮廓。
这些数字有明确的医学含义:均值对应平均心率水平,方差对应心律的稳定程度,偏度与峰度描述波形是否对称、是否尖锐。对于工业振动信号,波形的主要频率成分往往是故障的标志,这时要把信号从时间域变换到频率域,看看能量集中在哪些频段。
最常用的三类特征是:统计特征,描述波形的整体分布,如均值、方差、偏度;频域特征,通过傅里叶变换揭示波形的主要节奏;形状特征,关注波峰、波谷的位置与幅值。在提取这些特征之前,通常还要做标准化(z-score 或归一化),让不同幅度的波形站在同一起跑线上,避免幅值大的样本主导距离计算。
小王把特征提取后的表格交给李老师,李老师看了一眼:「你把波形变成了数字,这就是波形识别的第一步。」小王心想,原来医生扫一眼心电图,脑中早已完成了特征提取。
- 平均值把整个波形每个时刻的观测值加起来,再除以样本数 T,得到波形的重心。
- 样本方差每个观测值减去均值后平方求和,再除以 T-1,衡量波形围绕重心波动的剧烈程度。
动态时间规整
小王遇到一对难题:两张心电图形态很相似,但一张心跳快、一张心跳慢,逐点对比怎么都对不上。李老师拿出一张透明纸,把快的心电图纵向「压扁」、慢的「拉长」,让两个波峰的位置大致对齐:「先对齐再比,就像比对两个速度不同的人跑同一条赛道——我们比的是跑姿,不是比时间。」
这就是动态时间规整(DTW)的核心思想:允许时间轴发生非线性伸缩,找到让两段波形整体最相似的对应方式。这里的「规整」二字,意思就是把时间轴掰弯、拉直,让两张图的关键点尽量对齐。它不要求两段序列等长,心跳快慢不一的波形也能公平比较。
计算方法是用动态规划填一张 T×T 的代价表。D(i,j) 表示序列前 i 个点与前 j 个点的最小累积距离。递推时只能向右、向下或斜对角前进,保证时间顺序不倒退;每一步都在三个方向里选代价最小的路径,最终得到的 D(T,T) 就是两段波形对齐后的总差异。把 DTW 距离放进最近邻分类器,就构成经典的 DTW+1NN 分类器——这也是许多时序分类问题的强基线。
小王用透明纸对齐了两张心电图,发现它们几乎完全重合:「原来它们只是心跳快慢不同,本质是同一类波形!」他意识到,DTW 能化解时序分析里最让人头疼的「错位」问题。
- 局部匹配代价d(i,j) 是序列 A 第 i 个点和序列 B 第 j 个点的平方差,表示「这两点像不像」。
- 递推最小累积当前累计距离取左、下、斜对角三个方向中最小的,再加局部代价,保证对齐路径连续且单调。
距离度量对比
小王问:「既然 DTW 这么强,为什么还要学欧氏距离?」李老师把两张心电图纸叠在一起,上下一一对应划竖线:「如果两个人同时开始、同样的时长、节奏一致,直接逐点比就行,这就是欧氏距离;可一旦节奏错开,这种『对表』就失灵了。」说完他又展示了 DTW 那套可以拉伸的对齐路径:「所以选择距离度量,取决于数据是否对齐。」
欧氏距离公式简洁、计算飞快,但它默认两序列等长且逐点对应。时序数据一旦出现时间偏移——同一个特征早出现几拍或晚出现几拍——欧氏距离就会把这些错位误判为巨大差异。DTW 虽然灵活,但代价不小:填一张 m×n 的代价表,时间和空间复杂度都是 O(mn),序列一长就非常吃力。
工程上常用的加速技巧是「下界剪枝」,比如 LB_Keogh:先快速算出一个距离下限,如果这个下限已经超过当前最好的成绩,就直接跳过这个候选,不必计算完整 DTW。这样一来,两万条候选序列里真正需要跑完整 DTW 的往往只剩几十条,速度提升非常明显。
这张对比让小王明白:没有「最好」的距离度量,只有「最合适」的。快节奏的初筛场景用欧氏距离加特征;慢节奏、要求高的场景才动用 DTW,并配合剪枝加速。
- 逐点差分x_t 与 y_t 是同一时刻的观测值,先求差再平方,避免正负相互抵消。
- 总和开方把整个序列的平方差累加后开平方根,得到两个序列的直线距离。
深度学习分类
李老师带小王参观隔壁 AI 实验室,指着屏幕上训练的模型说:「这位『AI 见习医生』没见过心电图教材,只是看了上万张心电图和答案,自己琢磨出了怎么看波形。」小王惊讶:「它不需要人工提取特征吗?」「不需要——卷积核就像它的『眼睛』,在波形上滑动,自动捕捉高尖的 R 波、缺失的 P 波。」
深度学习方法让特征提取与分类一步到位。它不再需要人先手工设计均值、方差或频域特征,而是直接从原始波形中学习判别信息。对数据量充足的场景,深度模型往往能超过手工特征 + DTW 的组合,因为它能学出人想不到的隐藏模式。
一维 CNN 用卷积核在时间轴上滑动,逐层提取从局部到全局的模式;RNN/LSTM 则擅长建模长程依赖,记住前面几拍的心跳来预测当前状态;编码器-分类器结构先把波形压缩成低维向量,再做判别。当自己的数据不够时,还能用迁移学习把其他任务学到的特征搬过来,用少量数据微调即可。
小王看着屏幕上自动浮现的高亮区域——模型自己找到了 QRS 段。「原来经验直觉,本质上就是见多识广后的模式总结。」他不禁感叹。
- 卷积求和卷积核长度为 K,把窗口内 K 个输入 x_{t+k} 与权重 w_k 逐项相乘、累加并加偏置 b。
- 非线性激活对线性求和结果套上激活函数 g(如 ReLU),让模型能表达复杂的非线性模式。
评估调优部署
出科考试那天,李老师给小王准备了三个文件夹:训练题、模拟题、真题。「做完训练题还要做模拟题,最后才做真题。用真题调答案,考试就失去意义了。」小王按顺序做完三套题,李老师又问他:「你只答对了 95 道题,但漏掉了 3 个危重病人,你觉得你合格吗?」小王愣住了。
这揭示了评估时间序列分类的三条要点。第一,数据切分要按时间顺序:先训练后测试,绝不能让未来的信息提前泄露到训练集中。第二,指标不能只看准确率:当异常样本很少时,模型把一切都判成正常也能拿到很高的准确率,但它完全没用。对「漏诊」敏感的场景要重点看召回率,综合看 F1。第三,调参要用验证集:在验证集上对比候选模型,选定后再到测试集上做最终评估,避免「调参调到了测试集上」。
医疗场景的实际做法往往是对比多个候选:DTW+1NN、统计特征+随机森林、一维 CNN 或 LSTM。先按时间顺序切分好数据,在验证集上反复调整超参数,最后用测试集统一评分。这样选出的模型既不会过拟合训练集,也不会「作弊」偷看未来。
最终,小王交出的模型在测试集上既保持了高召回率,又维持了合理准确率。李老师在考核表上写下「合格」两个字。小王看着屏幕上跳动的波形,终于明白了老师第一天说的那句话:看波形,其实是在读数据讲述的故事。
- 精确率 P在所有被预测为「异常」的样本里,真正异常的比例。
- 召回率 R在所有真实异常的样本里,被模型成功抓出的比例。
- F1 调和平均精确率和召回率任何一个偏低,F1 都会被拉低,逼着模型兼顾两者。
AI 应用场景
心电图自动筛查
阶段:模型训练与推理部署
解决的问题:传统方式需要心内科医生逐张人工读图,工作量大且疲劳时容易漏诊;AI 先用特征提取 + DTW/深度模型对全量心电图做初筛,标出异常波形,医生只需复核高概率异常样本,效率与准确率双升。
具体案例:某智慧医疗公司为基层医院部署「心电初筛」系统,每天处理 2 万条 10 秒心电图,异常召回率超过 98%,将医生的读图时间缩短 70%。
智能手环手势识别
阶段:数据采集与特征工程
解决的问题:手环里的加速度计在不同人的手上节奏快慢不同,直接比较原始读数会误判;用 DTW 对齐手势动作,再配合统计特征,能让「挥手」「翻转」「敲击」等动作识别得更稳定。
具体案例:可穿戴厂商收集大量用户手势数据,用 DTW+KNN 做端侧轻量分类模型,实现在离线状态下仍能 90% 以上准确率识别手势。
工业设备轴承故障预警
阶段:在线异常检测与维护决策
解决的问题:机器振动传感器的信号是典型时序数据,正常与故障波形的差异有时只在频域或局部形状上;用特征提取 + CNN 分类器分析振动波形,可以在设备真正损坏前发现异常。
具体案例:某制造工厂在关键轴承上安装振动传感器,模型在波形上识别出早期点蚀特征,提前两周报警,避免了产线停机损失。
Python 演示
import mathimport random def make_wave(seed, abnormal=False, n=60): """生成一段带噪声的模拟心电波形。""" rnd = random.Random(seed) base = [0.05 * math.sin(2 * math.pi * 2 * i / n) for i in range(n)] wave = [] for i, v in enumerate(base): v += rnd.uniform(-0.08, 0.08) if abnormal: v += 0.30 * math.exp(-((i - n * 0.40) / (n * 0.02)) ** 2) v += 0.08 * math.sin(2 * math.pi * 8 * i / n) else: v += 0.18 * math.exp(-((i - n * 0.50) / (n * 0.02)) ** 2) wave.append(v) return wave def dtw_dist(a, b): """计算两个序列的动态时间规整距离。""" m, n = len(a), len(b) d = [[float('inf')] * (n + 1) for _ in range(m + 1)] d[0][0] = 0.0 for i in range(1, m + 1): for j in range(1, n + 1): cost = (a[i - 1] - b[j - 1]) ** 2 d[i][j] = cost + min(d[i - 1][j], d[i][j - 1], d[i - 1][j - 1]) return math.sqrt(d[m][n]) def knn_predict(train, train_y, query): """用 DTW 距离做最近邻分类。""" best_dist, best_label = float('inf'), None for q, y in zip(train, train_y): dist = dtw_dist(q, query) if dist < best_dist: best_dist, best_label = dist, y return best_label def main(): train, train_y = [], [] for i in range(20): train.append(make_wave(i, abnormal=False)) train_y.append(0) for i in range(20): train.append(make_wave(i + 100, abnormal=True)) train_y.append(1) test, test_y = [], [] for i in range(5): test.append(make_wave(i + 50, abnormal=False)) test_y.append(0) for i in range(5): test.append(make_wave(i + 200, abnormal=True)) test_y.append(1) correct = 0 for query, true in zip(test, test_y): pred = knn_predict(train, train_y, query) correct += (pred == true) print("DTW + 1NN 准确率: {:.1f}%".format(100 * correct / len(test))) if __name__ == "__main__": main()
rnd = random.Random(seed)用固定种子生成可重复的模拟波形,保证实验结果可复现。if abnormal: v += 0.30 * math.exp(...)给异常波形叠加一个高尖峰,模拟心电图中「高尖 R 波」的特征。d = [[float('inf')] * (n + 1) for _ in range(m + 1)]初始化代价表,无穷大表示不可达边界。d[i][j] = cost + min(d[i - 1][j], d[i][j - 1], d[i - 1][j - 1])DTW 动态规划递推核心,取三个方向最小累计代价。dist = dtw_dist(q, query)用 DTW 距离衡量两个波形之间的相似度。if dist < best_dist: best_dist, best_label = dist, y找到当前最近的训练样本,用它的类别作为预测结果。correct += (pred == true)统计预测正确的次数,用于计算最终准确率。
常见误区
误区看到「分类」两个字,立刻想到堆 CNN/LSTM,认为手工特征和 DTW 是过时技术,数据先扔给神经网络再说。
正解深度学习在数据量大时优势明显,但在数据量小、波形规整的场景(如单一患者心电图初筛、简单手势识别),手工特征 + DTW + 1NN 往往更稳定、更省算力,还容易解释。李老师对小王说的第一句话就是「先看特征,再谈模型」——选模型要看样本量,而不是追新。
为何常见:深度学习在图像、语音领域被大量宣传,学习者误以为它是所有问题的万能解,忽略了小样本时序数据的特殊性。
误区两张心电图都是 50 个点,直接对应相减求平均就能判断相似度,何必搞 DTW 这么麻烦。
正解心跳快慢、节奏漂移会导致相似波形错位,逐点比较会把错位算成巨大差异,明明相似的波形被判成不同类别。DTW 的核心价值就是允许时间轴非线性伸缩,把「跑姿」而不是「时间」对齐。若序列已经严格对齐(如同设备同频率采集),欧氏距离才够用。
为何常见:人们习惯将向量逐位比较,忽视了时间序列的「时间弹性」这个本质属性。
误区特征提取之后就是一行一行的数值表,直接打乱后随机划分训练测试集就行。
正解时序数据的顺序承载因果与时间依赖,随机打乱会破坏「预测未来」的含义,且同一时间窗口的数据容易泄漏。正确做法是按时间先后切分,训练集在前、测试集在后,防止模型「偷看未来」。特征表虽然形态上像表格,但样本背后的时间轴并没有消失。
为何常见:表格分类的惯性思维——在非时序任务里随机划分是标准操作,直接迁移到时序任务就踩坑。
误区模型在测试集上准确率 98%,直接上线部署,一定很可靠。
正解当类别不平衡或错判代价不同时,准确率会骗人:比如 100 个样本中 99 个正常,全部判正常也能拿 99% 准确率,却漏掉了最危险的 1 个异常。医疗场景要盯住召回率,综合看 F1,还要用验证集调参、测试集最终评估,避免「调参调到了测试集上」。
为何常见:准确率是最直觉的指标,初学者容易只看一个数就下结论,忽略了实际业务里漏诊往往比误报更致命。
面试题
Q1. 时间序列数据与普通表格数据最本质的区别是什么?
- A.按时间顺序排列,顺序本身承载信息← 正确答案
- B.数据量通常比表格数据要小很多
- C.每个特征必须是连续数值变量
- D.不需要任何预处理就能直接建模
答案:A。时序数据的顺序就像心电图的时间轴,打乱顺序等于把波形拆碎,信息就丢了;普通表格的行顺序通常可互换。B 说数据量小不对,C 说必须连续不对,D 说不需预处理也不对。
Q2. 下列哪个场景最适合用时间序列分类解决?
- A.根据心电图波形判断心律正常或异常← 正确答案
- B.根据客户年龄和收入预测消费金额
- C.对商品评价文本进行情感正负判别
- D.根据用户点击记录计算点击转化率
答案:A。心电图判断是典型的时序分类——输入是波形,输出是类别。B 是回归,C 是文本分类,D 是数值计算,都不属于时间序列分类。
Q3. 动态时间规整(DTW)的核心思想是什么?
- A.允许时间轴非线性伸缩后再比较波形← 正确答案
- B.把时间序列直接压缩成一个数字排序
- C.用傅里叶变换代替所有距离计算
- D.把序列打乱后计算平均值差值大小
答案:A。DTW 像把两张心电图放在透明纸上拉伸对齐再比,允许时间轴伸缩。B、C、D 都与 DTW 无关:压缩排序会丢失结构,傅里叶变换是频域工具,打乱序列更是破坏了时间顺序。
Q4. 在 DTW 动态规划递推公式中 d(i,j) 表示什么?
- A.第 i 点和第 j 点之间的局部距离← 正确答案
- B.两个序列长度差值的绝对值大小
- C.整个序列整体的平均绝对高度值
- D.动态规划路径的层数累计总和
答案:A。d(i,j) 是当前两个点的差值平方,决定这一小步对齐得像不像;后面 min 三项是前面最省力的累计代价。其他选项把 d(i,j) 误当成序列级或路径级概念。
Q5. 以下哪一项属于常用的时序特征提取方法?
- A.统计均值方差和频域的变换特征← 正确答案
- B.对序列做随机打乱后的排序号
- C.计算文本中每个词语的出现次数
- D.把类别标签映射成独热向量
答案:A。均值、方差属于统计特征,傅里叶变换属于频域特征,都是给波形「画像」的常用手段。B 破坏顺序,C 是文本特征,D 是标签编码,都与特征提取无关。
Q6. 关于时间序列分类的评估,正确做法有哪些?
- A.按时间顺序划分训练集和测试集← 正确答案
- B.随机打乱样本后均匀划分即可
- C.同时关注准确率与召回率指标← 正确答案
- D.用测试集反复调参再报告结果
答案:AC。时序数据必须按时间切分,防止未来信息泄漏,所以 A 对、B 错;测试集反复调参会让指标虚高,D 错。只看准确率会忽视漏诊,所以要同时关注召回率,C 对。这就像考试要按时间顺序做题,且不能拿真题当模拟题练手。
Q7. DTW 的时间复杂度通常是多少?
- A.与两序列长度的乘积成正比关系← 正确答案
- B.与两序列长度之和成正比关系
- C.与序列长度完全无关的常数级
- D.与序列长度的平方呈指数增长
答案:A。DTW 要填一张 m×n 的代价表,所以时间和空间都是 O(mn),即与乘积成正比。它不是线性的 O(m+n),也不是常数级,更不是指数级。这段代价表就像在方格棋盘上逐格下棋,每一格都要算一次。
Q8. 与欧氏距离相比,DTW 的局限包括哪些?
- A.时间复杂度与空间开销通常更高← 正确答案
- B.无法处理任意长度的两个序列
- C.没有全局约束时可能产生不合理对齐← 正确答案
- D.计算前不需要任何参数与约束设置
答案:AC。DTW 要填充 m×n 代价矩阵,开销更大,A 对;它能处理长度不等的序列,B 错;不加窗约束时可能把波峰对齐到很远的地方,路径不自然,C 对;「无需参数」本身不是局限,故 D 不选。
Q9. 关于用一维 CNN 处理时序数据,下列说法正确的有?
- A.卷积核在时间轴上滑动提取局部特征← 正确答案
- B.卷积输出直接做分类而无需任何预处理
- C.多个卷积层叠加能学习更复杂的模式← 正确答案
- D.卷积核的权重与位置无关始终共享← 正确答案
答案:ACD。卷积核像一个小窗口在时间轴上滑动捕捉局部模式,A 对;多卷积层堆叠可以逐层抽象,C 对;同一卷积核在整条序列上共享权重,D 对。B 错在至少需要归一化、池化等必要结构,不可能完全不做预处理。
Q10. 在 DTW 最近邻检索中使用 LB_Keogh 下界函数的主要目的是什么?
- A.快速排除不可能更优的候选序列← 正确答案
- B.把 DTW 复杂度降到线性时间级别
- C.计算比例距离更精确的替代距离
- D.完全取代所有统计特征的提取
答案:A。LB_Keogh 给出 DTW 距离的下界,如果下界已经超过当前最好成绩,就直接跳过该候选,避免计算完整 DTW 矩阵。它不把复杂度降为 O(n),也不改变距离精度,更与特征提取无关。这就像先扫一眼病历号就把明显不符的患者排除,不必逐一细看。