
简介面向毕业设计与期末大作业的Python时间序列预测源码完整覆盖ARIMA、Transformer、LSTM三种模型的搭建、训练与对比。方案基于MIT-BIH公开心跳数据包含310组记录及预处理结果代码附详细注释部署简单适合希望复用高分项目或系统掌握时序预测流程的高校学生。资源共1179个文件压缩包约19.85MB核心类型包括心跳数据文件.hea/.dat/.atr、Python脚本.py、Jupyter Notebook分析笔记.ipynb、可视化图表.png、模型权重.pth与说明文档.md/README目录结构清晰便于按模块检索和二次开发。目前已有225人学习下载。项目经过严格调试可直接用于毕业设计、期末大作业或课程设计除完整源码与数据集外还提供对比实验图表、代码注释和文档说明能帮助新手理解三种算法的预测差异、复现关键步骤并降低环境配置与调参门槛提升答辩和提交准备效率。1. 为什么心跳时间序列预测要把 ARIMA、Transformer、LSTM 摆在一起比心跳心电/脉搏时间序列预测是很多可穿戴健康分析和医学数据处理任务里的前置步骤先预测未来一段波形再做异常形态匹配或节律判断。这类“基于ARIMA、Transformer、LSTM对心跳时间序列数据进行预测”的项目做的事本质上不是选一个模型跑通而是用统计模型、循环网络、注意力机制三种路线对同一段生理信号做闭环对比评测搞清楚数据里哪些是周期规律、哪些是随机噪声、哪些能被模型真正学到——这个结论会直接影响后续模型选型和部署方式。对从业者来说这类项目的价值很实在它覆盖了从数据清洗、滑窗切片、模型训练到统一评估的完整技术栈。新手可以用它建立时序预测的选型直觉熟手则能用它快速验证新数据集的“可预测性上限”。常见做法是取公开的心电数据集或直接使用可穿戴设备采集的脉搏波数据把原始信号重采样到固定频率后分别用三种模型预测未来 1 秒的心跳波形。下文按数据工程、ARIMA 基线、LSTM、Transformer、统一评估与实战技巧的顺序展开每一步都给出可复现的参数和代码。2. 心跳序列的数据工程从原始波形到模型能吃的样本2.1 数据准备与特征选择心电数据并不是拿来就能跑常见做法是使用公开心电图数据集或可穿戴设备采集的脉搏波截取单导联的心电信号通常用 MLII 导联或等效单通道并做 128Hz 或 250Hz 的统一重采样。模型能看懂的输入是三段式过去一段观察窗口的心跳波形以及对应的未来心跳波形。特征上注意直接使用归一化后的原始幅值即可不必过多构造手工特征心电信号本身波形形态已经足够丰富额外堆砌统计特征反而会干扰深度模型的学习。我一般会先用连续 10 秒的完整信号做切片。观察窗口设 5 秒、预测窗口设 1 秒这种比例对 R 波周期相对规律的数据很合适。如果数据里有较多早搏或运动伪迹可以把预测窗口缩到 0.5 秒降低任务难度。数据集划分也要按病人或时段切避免同一病人的不同片段同时出现在训练集和测试集里否则模型对特定病人的“记忆”会直接影响评估结果部署时表现会大打折扣。心跳序列是强周期信号但幅度和间期并不固定。数据增强方面我通常会加入轻微的时间缩放每段随机缩放 0.98~1.02但绝不在心电幅值上做随机平移因为那样等于把心电的基线漂移当成特征教给模型实际预测时反而会放大误差。最终每个样本是[batch, seq_len, 1]的张量喂给 LSTM 和 TransformerARIMA 则吃原始的一维数值序列。2.2 切片、归一化与数据集划分的标准流程把原始信号切成可监督学习的样本时通常会写成下面的 Python 函数。这一步是整个项目的基石切出来的数据质量直接决定三个模型的天花板。import numpy as np def make_windows(signal, fs128, obs_sec5, pred_sec1, step1): obs_len int(fs * obs_sec) # 观察窗口640个点 pred_len int(fs * pred_sec) # 预测窗口128个点 X, y [], [] for i in range(0, len(signal) - obs_len - pred_len, fs * step): x signal[i:i obs_len] t signal[i obs_len:i obs_len pred_len] # 对每个窗口做z-score避免不同样本之间的幅值差异 mu, sigma x.mean(), x.std() 1e-6 X.append((x - mu) / sigma) y.append((t - mu) / sigma) # 注意用观察窗口的均值/方差防止未来信息泄漏 return np.array(X)[..., None], np.array(y)[..., None]代码里有一个关键点归一化的均值和方差取自观察窗口而不是预测窗口。如果对整段数据先全局归一化再切片测试时会用到未来的统计量属于典型的信息泄漏。这种泄漏在深度模型里不容易马上暴露但会严重抬高测试集指标到了真实部署阶段模型立刻被打回原形——这一点务必留意。参数上step1表示窗口每次移动 1 秒适合数据量不大时做滑窗复用如果数据量充足我会把 step 改成 2 或 3减少相邻样本的相关性。否则训练集和验证集几乎重叠验证损失会异常好看但毫无参考价值等于在“开卷考试”。2.3 ARIMA、LSTM、Transformer 对输入形态的不同要求三个模型的输入侧差异很大。ARIMA 吃一维数值序列不需要切片成窗口对直接用观察窗口内的 640 个点去滚动预测后面 128 个点LSTM 需要三维输入[batch, time_steps, features]Transformer 的输入和 LSTM 类似但一般要在 token 维度上先做一次线性映射把单个幅值点映射成 d_model 维向量然后加上位置编码。在 LSTM 和 Transformer 的输入上是否需要特征数大于 1 的多通道如果做的是单导联心跳预测一个幅值特征足够了。强行加入心率、呼吸等外部变量会让模型学到的组合关系在测试时失效因为设备更换或佩戴位置变化后这些外部变量的分布会漂移。这类项目里单通道入门、多通道做扩展才是正常路径。此外我建议把观察窗口的点数和下游模型结构一起考虑如果采样率是 128Hz640 个点对 LSTM 来说属于中等长度可以直接输入如果采样率提升到 1000Hz观察窗口变成 5000 个点LSTM 训练速度会明显下降这时应该先用卷积层降采样或按心拍边界切块而不是硬着头皮把全部原始点直接喂进去。3. 基于 ARIMA 的统计基线先看传统方法的上限3.1 为什么 ARIMA 仍是心跳序列预测的合格基线虽然 ARIMA 没有显式的周期建模能力但它通过差分和自回归项能捕捉短时间窗口内连续的波形走向。尤其预测未来 0.5 到 1 秒的波形时ARIMA 的结果通常可作为深度学习模型是否真正有效的参照物。很多深度模型跑出来的漂亮指标对照 ARIMA 基线后会发现只是“噪声拟合”。判断心电数据是否适合 ARIMA核心是看差分后的序列是否平稳做法是 ADF 检验。多数心电信号经过一阶差分后能通过检验若不行就需要用二阶差分。但注意差分阶数过高会让模型把噪声也当成规律学进去预测结果会出现严重的锯齿状抖动。对心跳数据d 设为 1 或 2 就够。ARIMA 在这类项目的定位是基线和“可解释对照”。深度学习模型如果连 ARIMA 都无法稳定超过说明数据切分或训练流程可能有 bug而不是模型能力问题。这个判断听起来武断但在不少实测项目中确实管用ARIMA 差深度模型也好不到哪去ARIMA 好深度模型才有发力空间。这也解释了为什么这类项目里 ARIMA 几乎是必备对照组。3.2 用 statsmodels 快速构建心跳 ARIMA 模型并做滚动预测使用 statsmodels 库能快速搭建 ARIMA 模型常见代码如下。这是整套项目里最轻量的一段逻辑也是后续评估的“最低水位线”。from statsmodels.tsa.arima.model import ARIMA import numpy as np pred_len 128 # 预测未来1秒(128Hz) history list(train_signal[:640]) # 观察窗口5秒 predictions [] for t in range(len(train_signal[640:640pred_len])): model ARIMA(history, order(2, 1, 2)) fit model.fit(methodinnovations_mle, trendc) pred fit.forecast(steps1)[0] predictions.append(pred) history.append(train_signal[640t])这段代码是典型的滚动预测每预测一个点把真实观测值回填到历史里再进行下一轮预测。对心电这种准周期信号单步滚动预测比直接预测 128 个点要稳得多原因在于每一步都校正了误差不会让误差沿着预测窗口一路累积。但要注意代码里每次循环都重新拟合了 ARIMA 模型这在 128 步预测里耗时尚可如果预测窗口更长或数据量大效率会成为严重问题。这时候常见做法是每 10 步或 20 步重新拟合一次中间步骤直接用已有模型滚动预测虽然误差会稍微累积但速度提升非常明显。实际参数选择上methodinnovations_mle比默认的statespace在小样本上更稳定遇到收敛警告时可以换回后者对比。3.3 ARIMA 的阶数选择与心跳场景下的边界ARIMA 的参数p、d、q通常通过 AIC 网格搜索选定。对心电序列我一般把 p 和 q 的搜索范围放在 0~3d 固定在 1 或 2。更复杂的心电特征如早搏、房颤可能让搜索到的 p、q 偏大而模型会变得不稳定预测结果一旦波动过大就要回到简单阶数。在心跳数据上“简单模型 稳定输出”比“复杂模型 偶尔爆炸”重要得多。还有一点容易被忽略ARIMA 对缺失值很敏感。心跳数据常因为设备脱落或运动伪迹出现 NaN如果直接喂给 ARIMA拟合阶段会直接报错或输出异常值。通常做法是先用线性插值或前后向填充补齐缺口再进行 ARIMA 拟合。如果缺失段太长超过 2 秒建议直接丢弃该段信号不要硬补否则会把插值产生的虚假波形当成真实规律学进去。ARIMA 适合做整个项目评估的标准基线但它的明显边界是不能捕捉心率变异性中的长程依赖也不能对多步预测给出稳定输出误差随时间累积。所以它不会是最终方案但它能快速帮你确认数据是否存在明显的趋势异常并给深度学习模型划定一个最低标准——后续 LSTM 和 Transformer 的表现都要以它为参照系。4. 基于 LSTM 的循环预测模型把时序记忆用起来4.1 LSTM 为什么会成为心电时间序列预测的默认选择LSTM 在心跳预测中成为默认选择是因为它用门控机制解决了长序列训练时的梯度消失问题。心跳序列的每个波形是有记忆的下一个波形的形态往往取决于前面的多个心动周期。LSTM 通过输入门、遗忘门、输出门把“上一个心跳的形态”这种信息保留在细胞状态里从而做到基于周期记忆的预测。在结构上绝大多数项目采用双层 LSTM 加全连接输出层。第一层 LSTM 学习波形段的短时依赖第二层进一步抽象出周期模式。我一般会把第一层的 return_sequences 设为 True第二层只返回最后一个时间步的隐状态再接 Dense 层输出预测窗口长度。这样做比单层 LSTM 效果好而且参数量增加不多训练成本可控。LSTM 输入是[batch, seq_len, 1]的窗口样本。如果观察窗口是 640 个点直接把这 640 个点全部喂给 LSTM 并不算长对 LSTM 来说 640 步属于中等长度但如果采样率提高到 1000Hz观察窗口点数是原来的 4 倍就应该考虑先用卷积层降采样或把序列切分成较小的块否则训练速度会明显下降显存占用也会成倍增加。4.2 搭建并训练一个心跳波形 LSTM 预测模型以 PyTorch 为例搭建一个双层 LSTM 模型并完成训练闭环代码结构如下。这是整个项目里最核心的可复用代码段之一。import torch import torch.nn as nn class HeartLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, pred_len128): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropout0.2) self.head nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, pred_len) ) def forward(self, x): # x: [batch, seq_len, 1] out, _ self.lstm(x) # 取最后时间步的输出 out out[:, -1, :] # [batch, hidden_size] return self.head(out)这里有个容易踩坑的地方LSTM 的 dropout 参数dropout0.2只在num_layers 1时生效且它作用于层与层之间不是时间步之间。如果为了提效果而把 dropout 加在输出层之前要自己插入 Dropout 层而不是靠 LSTM 的参数。这是 PyTorch 文档里写明但很多人忽略的细节。训练参数上我一般用 AdamW 优化器、学习率 1e-3 配合 cosine 衰减batch size 64损失函数用 HuberLossSmooth L1。它对心电波形中偶尔出现的异常峰不敏感不会像 MSE 那样被个别离群点带偏。训练轮数不建议太多心电数据增强手段有限跑 30 到 50 个 epoch 之后基本就能看到验证损失是否触底再多就容易过拟合。4.3 LSTM 在心跳预测中的预测步数与误差累积问题LSTM 直接输出未来 128 个点训练时是 teacher forcing 的预测时却是一次性给出 128 个点这个“训练-预测不一致”是 LSTM 模型最常见的误差来源。虽然它并不像 ARIMA 那样逐点滚动但本质上仍要面对长期预测误差累积的问题只是发生位置从推理阶段挪到了训练阶段。缓解办法有两种第一种是 Scheduled Sampling训练时以概率 p 用模型自己的预测值代替真实值作为下一步输入p 随训练进度衰减。第二种是把模型改成 seq2seq 结构编码器读入观察窗口解码器逐步输出预测点每一步把上一步的输出作为输入。对心跳预测项目第二种更容易落地因为波形预测本质上是逐点生成的不是一次性回归。我一般会优先尝试 LSTM 的多步直接输出如果验证集误差明显高于训练集误差再切到 seq2seq 结构。在实际数据上单步误差通常能压得很低但多步误差会随步数明显上升所以对比 ARIMA、Transformer 时要统一在同样的预测窗口下评估不能用不同步数量级的结果互相比较。5. 基于 Transformer 的注意力预测长程依赖与并行训练5.1 Transformer 在时间序列预测任务里的定位Transformer 原本用在自然语言处理上核心是自注意力机制。它在心跳预测上的优势有两点一是能直接建模长距离依赖心跳波形中相距很远的心动周期也能通过注意力建立关联二是训练时可并行处理整个序列不像 LSTM 必须逐步计算这在观察窗口较长时能明显提升训练速度。但 Transformer 也有代价它没有内置的时间顺序概念必须加位置编码才能让模型感知波形点的先后。常见做法是使用三角函数位置编码因为它的相对位置信息可以通过线性变换表达。对心跳序列来说周期性强三角函数位置编码能很好地编码相对距离但具体效果还是需要用实验来验证不能想当然。在输入形态上Transformer 通常不直接吃原始采样点而是先做 token 映射。常见操作是把 5 秒的观察窗口640 个点按 16 或 32 个点切块每个块是一个 token经过线性映射成 d_model 维向量再加上位置编码才进入 Transformer 编码器。这样做的原因是单个心拍大约 0.8 秒切块后的 token 能更好地表达一个个局部波形片段而不是让模型在单个采样点上浪费注意力。5.2 PyTorch 实现一个轻量 Transformer 编码器并用于心电预测直接构造 Transformer 的编码器部分作为预测模型实现代码大致如下。这里的结构是后续调参的主战场。import torch import torch.nn as nn import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len1000): super().__init__() pe torch.zeros(max_len, d_model) pos torch.arange(0, max_len).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(pos * div_term) pe[:, 1::2] torch.cos(pos * div_term) self.register_buffer(pe, pe.unsqueeze(0)) def forward(self, x): return x self.pe[:, :x.size(1)] class HeartTransformer(nn.Module): def __init__(self, d_model64, nhead4, num_layers2, pred_len128): super().__init__() self.token_embed nn.Linear(16, d_model) self.pos_enc PositionalEncoding(d_model) encoder_layer nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward128, batch_firstTrue, dropout0.1) self.encoder nn.TransformerEncoder(encoder_layer, num_layers) self.head nn.Linear(d_model, pred_len) def forward(self, x): # x: [batch, 640, 1] - 切块为 [batch, 40, 16] b, seq, _ x.shape x x.reshape(b, seq // 16, 16) # 40个token x self.token_embed(x) # [batch, 40, d_model] x self.pos_enc(x) x self.encoder(x) x x.mean(dim1) # 全局平均池化 return self.head(x)这段代码里d_model64、nhead4、num_layers2是我常用的一组轻量参数。心电数据量通常不大参数过多会过拟合。注意token_embed把每个 16 点的块映射到 d_model 维而不是一个点一个点地进入注意力机制否则注意力的计算复杂度跟序列长度的平方成正比训练会非常慢。位置编码这里用的是经典的三角函数版本register_buffer保证它不会作为模型参数被训练。如果换用可学习位置编码在小数据集上反而容易过拟合因为位置信息被当作可训练的偏置项模型会记住训练集里的波形相位而不是学到通用的相对位置关系。5.3 Transformer 在心跳任务上的数据量与调参要点Transformer 在心跳项目上最大的问题是数据量需求比 LSTM 更大。自注意力机制参数共享程度低自由度更高同样的数据量下更容易过拟合。缓解手段除了加 dropout还可以用数据增强、更小的 d_model、更浅的结构。数据量只有几千个样本时不建议一上来就堆参数量。具体到心跳序列nhead4比nhead8在小数据上更稳定。num_layers2足够超过 4 层在几千个样本规模下几乎必过拟合。学习率方面Transformer 对学习率更敏感我一般先用 1e-4 起步配合 warmup 策略前 10 个 step 从 1e-6 线性升到 1e-4再用 cosine 退火。这个细节经常决定 Transformer 是否收敛很多复现失败都卡在固定学习率这里。此外心电信号是强周期信号Transformer 需要明确地利用周期信息。如果只用位置编码而不做任何周期先验模型也能学但需要更多数据。我在实际项目中会把单个心拍长度作为先验知识把观察窗口按心拍分成若干段段内用 Transformer 编码器建模细节段间用简单平均池化这样预测效果往往比纯 Transformer 更稳——这种带先验的结构设计在小数据场景下非常管用。6. 统一评测框架与避坑指南6.1 统一评测框架三个模型要在同一把尺子下比较在 ARIMA、LSTM、Transformer 三者的比较上最常见的问题是评估方式不一致。ARIMA 用单步滚动预测LSTM 用一次性多步输出Transformer 用多步输出最后放在同一个图表里却没有标注各自的任务难度这种对比毫无说服力。统一的评测方式是三个模型全都预测未来 128 个点1 秒波形用同样长度的观察窗口640 个点评估指标用 RMSE、MAE 以及波形形态相似度如动态时间规整 DTW 距离。RMSE 对峰值敏感MAE 对整体偏移敏感DTW 则能衡量波形“形状”是否对得上。推荐把三项都算出来如果 RMSE 低但 DTW 高说明波形偏移了半个相位形态不对这样的预测在医学场景里是不能用的。评估时还要按病人或时间段划分数据集。常见做法是把前 70% 的数据按时间顺序作为训练集中间 15% 做验证集最后 15% 做测试集。如果数据来自多个个体必须保证每个个体的数据只出现在一个集合里这是心电数据处理中最重要的评估纪律。违反这条纪律的评估结果换个体数据后基本都会崩。6.2 训练与评估中的主要踩坑记录我在这类预测项目里踩过不少坑挑几个高频的分享。问题一验证损失很低但可视化预测波形完全对不上相位。现象是训练曲线非常漂亮RMSE 也低但把预测波形和真实波形叠在一起看峰和谷完全错位。原因是验证集与训练集切片重叠太多滑窗步长太小时相邻样本高度相关模型等于在“开卷考试”。解决方法是增大滑窗间隔或按时间段切分验证集确保验证集和训练集没有时间重叠。问题二ARIMA 输出全是上一个点的延迟复制。现象是预测曲线比真实曲线滞后一个采样点看起来几乎重合但实际上只是平移。原因是差分阶数不够序列仍然非平稳模型学到的就是“把最近值平移过去”。解决方法是先做 ADF 检验确认 d 的取值如果仍存在明显趋势考虑对信号做高通滤波去除基线漂移再进入 ARIMA 拟合。问题三LSTM 训练到一半 loss 变成 NaN。常见原因是学习率过大或数据里有 NaN。解决方法是先把数据中的 NaN 值显式检查并补齐再把学习率降到 1e-4 试跑 5 个 epoch如果仍然 NaN检查 LSTM 输出层是否出现了梯度爆炸必要时对梯度做裁剪clip_grad_norm_处理。这个问题的隐藏版本是 loss 不变成 NaN 但开始剧烈震荡同样要从学习率和数据异常两个方向排查。问题四Transformer 在小数据集上严重过拟合。现象是训练损失不断下降验证损失在第 10 个 epoch 后开始上升。解决方法是减小 d_model、加深 dropout、加入早停同时确认注意力头数是否过大4 个头一般够了。如果数据量实在太小应该优先把 token 切块变大减少序列长度降低模型自由度。6.3 模型选型结论与部署视角的建议在跑完对比实验之后我的经验是如果预测窗口很短0.5 秒以内ARIMA 通常够用深度模型未必有明显优势如果预测窗口在 1 秒左右LSTM 是性价比最高的选择训练稳定、参数少、部署也容易如果数据量很大、预测窗口更长且有能力做调参和训练优化Transformer 才有机会胜出——但它的优势并不会自动出现需要做足调参工作。部署视角上LSTM 的推理速度在 CPU 上已经很快1 秒窗口的单次推理大概在几十毫秒量级适合实时心跳监测Transformer 虽然训练慢但推理时也可以接受只是模型文件和内存占用更大。ARIMA 部署最简单不需要深度学习框架但滚动拟合需要保留状态每一步预测都要更新历史数组时序性依赖比深度模型更重。7. 实战技巧用模型对比的结果反推数据质量与调参方向这类项目的最终交付物不只是三个预测图而是一整套可以复用的评测脚本。我会在项目里额外写一个脚本把三个模型的预测结果和真实波形叠加可视化逐段打印每个模型的误差。这样做的好处是能直接发现数据切分、预处理和模型结构上的问题而不是对着总指标一头雾水。具体技巧是把测试集按心拍边界切分成独立片段逐段对比三个模型的误差。如果 ARIMA 在某些片段上表现得比 LSTM 还好那这些片段很可能是高质量、强周期、无噪声的而 LSTM 有优势的片段往往含有轻微的早搏或心率变异。这种“按片段归因”的做法能帮你快速定位数据中哪些模式是模型真正需要的而不是盲目调参数。验证阶段我习惯在最终评估时再增加一个“留一对象”交叉验证每次拿一个对象的完整数据做测试集其余对象的数据训练。这样出来的指标比随机切分可靠得多也更接近真实部署时的表现。虽然训练次数会翻倍但对心跳预测这种小数据量任务来说时间成本完全可控。另一个实用习惯是保存每个模型的预测残差序列分析残差是否存在自相关。如果 LSTM 或 Transformer 的残差仍然有明显的周期性说明模型没有完全学到心电的节律信息通常是因为观察窗口不够长或者目标窗口里有多个心拍导致输出平滑化。此时把预测目标从“逐点波形”换成“下一个心拍间隔或波形模板”效果往往立竿见影。整个项目做下来我最大的体会是不要把三个模型当作竞争关系而是当作三条可互相验证的路径。ARIMA 定义了数据的最低可预测性LSTM 展示了循环网络的稳定上限Transformer 则带你探索数据量足够时注意力机制能走多远。对某个具体心电预测任务判断模型是否有效可以先用 ARIMA 定基线再让 LSTM 打底最后看 Transformer 是否值得投入调参成本——这样安排项目逻辑清晰每个模型都有明确的角色和边界文档写起来也更有说服力。希望帮到你。本文还有配套的精品资源点击获取