SmolVLA实战:LSTM时间序列预测模型对比与优化
SmolVLA实战LSTM时间序列预测模型对比与优化最近在折腾时间序列预测项目发现一个挺有意思的现象很多朋友一提到时序预测脑子里蹦出来的第一个词就是LSTM。确实LSTM长短期记忆网络在过去很长一段时间里几乎是处理这类问题的“标配”。但用久了也会发现它在理解复杂时序模式、捕捉长期依赖关系上有时候会显得有点“力不从心”。正好我最近深度体验了SmolVLA这个新模型它号称能用更小的参数量在理解数据上下文和提取特征上做得更好。这让我很好奇如果把它用在时间序列预测上跟咱们熟悉的LSTM比到底能有多大提升是噱头还是真本事这篇文章我就带你一起动手用真实的时序数据把SmolVLA和传统LSTM拉出来“练练”。咱们不空谈理论直接上代码从数据准备、模型搭建、训练到结果对比一步步看个究竟。无论你是想优化手头的预测模型还是单纯对新技术好奇相信都能有点收获。1. 环境准备与数据理解工欲善其事必先利其器。咱们先把实验环境搭好并把要用的数据搞清楚。1.1 快速搭建实验环境这次实验主要用到PyTorch和几个常用的数据处理库。如果你的环境里还没有用下面这几条命令就能搞定。pip install torch torchvision torchaudio pip install pandas numpy matplotlib scikit-learn代码很简单就是安装PyTorch全家桶和数据分析三剑客pandas, numpy, matplotlib再加一个scikit-learn用于数据划分。装好之后咱们在Python里把它们引进来。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import warnings warnings.filterwarnings(ignore) # 设置随机种子保证实验可复现 torch.manual_seed(42) np.random.seed(42)1.2 准备时序数据为了公平对比我们选一个公开的、有代表性的数据集北京PM2.5数据集。这个数据集记录了北京某监测站多年的空气质量数据包括PM2.5浓度、露点、温度、气压、风速等时间粒度是每小时非常适合做时序预测实验。我们先把它下载下来并做初步的观察。# 这里假设数据文件名为 ‘PRSA_Data_20130301-20170228.csv‘ # 你可以从UCI机器学习仓库等地方下载 try: df pd.read_csv(PRSA_Data_20130301-20170228.csv) except FileNotFoundError: # 如果本地没有我们创建一个模拟的时序数据来演示流程 print(未找到原始数据文件使用模拟数据演示。) # 生成模拟数据一条带有趋势、季节性和噪声的时间序列 time_steps 10000 t np.arange(time_steps) trend 0.01 * t seasonal 10 * np.sin(2 * np.pi * t / 24) 5 * np.sin(2 * np.pi * t / 168) # 日周期和周周期 noise np.random.randn(time_steps) * 2 target_series trend seasonal noise # 构造一些特征比如滞后项 feature_data np.column_stack([ np.roll(target_series, 1), # 滞后1期 np.roll(target_series, 24), # 滞后24期一天前 np.roll(target_series, 168), # 滞后168期一周前 np.random.randn(time_steps) * 5 # 一个随机噪声特征 ]) # 处理初始的NaN值由于滞后操作产生 valid_idx 168 feature_data feature_data[valid_idx:] target_series target_series[valid_idx:] # 更新t t t[valid_idx:] df pd.DataFrame(feature_data, columns[lag1, lag24, lag168, feature_rand]) df[target] target_series df[time_index] t print(f数据形状: {df.shape}) print(df.head())无论使用真实数据还是模拟数据我们的目标都是一样的用过去一段时间的历史数据特征来预测未来一个时间点的目标值比如下一小时的PM2.5浓度。这是一个典型的单步预测任务。2. 模型原理浅析与代码实现在动手写代码之前咱们先花几分钟用人话捋一捋LSTM和SmolVLA的核心思路有什么不同。这能帮你更好地理解后面代码里那些参数是干嘛的。2.1 传统LSTM时序预测的“老将”你可以把LSTM想象成一个有“记忆”和“遗忘”功能的小盒子。它按顺序读取时间序列的每一个点比如每小时的数据。对于每一个新来的数据点它做三件事决定记住什么看看新信息和旧的记忆决定把哪些重要信息更新到记忆里。决定遗忘什么看看旧的记忆决定忘掉哪些已经没用的信息。输出当前结果基于新的记忆输出对当前时刻的解读。它的优势是结构经典经过大量实战检验对于中短期的依赖关系捕捉得不错。但缺点也很明显它本质上是一个“循环”结构必须一个一个点按顺序处理计算没法并行训练起来可能比较慢而且当序列非常长的时候早期的信息可能在传递过程中被“遗忘门”稀释掉导致长期记忆效果打折扣。下面是一个标准的PyTorch LSTM模型实现class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size): super(LSTMModel, self).__init__() self.hidden_size hidden_size self.num_layers num_layers # 核心LSTM层 self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) # 全连接层用于输出预测结果 self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # 初始化隐藏状态和细胞状态 h0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) # LSTM前向传播 out, _ self.lstm(x, (h0, c0)) # out的形状: (batch_size, seq_length, hidden_size) # 我们通常取最后一个时间步的输出进行预测 out self.fc(out[:, -1, :]) # 形状: (batch_size, output_size) return out2.2 SmolVLA理解上下文的新思路SmolVLA你可以暂时把它理解为一个“小而精的视觉语言注意力模型”这里我们借鉴其核心思想用于时序数据的思路不太一样。它不强调“顺序记忆”而是更关注“整体理解”。它的核心是一个叫做“注意力”的机制。你可以把它想象成在分析一整段时序数据时模型会给这段数据里不同时间点分配不同的“注意力权重”。比如要预测明天下午的天气模型可能会更“注意”最近几小时的雷达图变化以及昨天同时间段的天气情况而不是均匀地看待过去一周的所有数据。这种机制的好处是并行计算可以同时处理所有时间步的数据训练速度更快。直接建模依赖无论两个时间点相隔多远注意力机制都能直接建立连接理论上更能捕捉长期依赖。更好的可解释性通过观察注意力权重我们能大概知道模型在做决策时更关注哪些历史时刻。下面我们实现一个简化版的SmolVLA思路用于时序预测。它主要由数据嵌入层、注意力层和输出层构成。class SmolVLATemporalModel(nn.Module): def __init__(self, input_size, d_model, nhead, num_layers, output_size, dropout0.1): super(SmolVLATemporalModel, self).__init__() self.d_model d_model # 1. 将输入特征映射到更高维空间嵌入层 self.input_projection nn.Linear(input_size, d_model) # 2. 位置编码给数据加上时间顺序信息因为注意力本身没有顺序概念 self.pos_encoder PositionalEncoding(d_model, dropout) # 3. 核心多层Transformer编码器实现注意力机制 encoder_layer nn.TransformerEncoderLayer(d_modeld_model, nheadnhead, dropoutdropout, batch_firstTrue) self.transformer_encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) # 4. 输出层 self.output_layer nn.Linear(d_model, output_size) def forward(self, x): # x形状: (batch_size, seq_length, input_size) # 投影到模型维度 x self.input_projection(x) * np.sqrt(self.d_model) # 缩放 # 添加位置编码 x self.pos_encoder(x) # 通过Transformer编码器 x self.transformer_encoder(x) # 取最后一个时间步的特征也可以做全局池化这里简单处理 x x[:, -1, :] # 输出预测 out self.output_layer(x) return out # 位置编码实现 class PositionalEncoding(nn.Module): def __init__(self, d_model, dropout0.1, max_len5000): super(PositionalEncoding, self).__init__() self.dropout nn.Dropout(pdropout) pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2).float() * (-np.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) pe pe.unsqueeze(0) # (1, max_len, d_model) self.register_buffer(pe, pe) def forward(self, x): x x self.pe[:, :x.size(1), :] return self.dropout(x)看到这里你可能发现了关键区别LSTM像是一个有经验的老师傅按部就班、循序渐进地处理数据而SmolVLA更像是一个统观全局的指挥官同时审视所有信息并动态决定哪些更重要。接下来我们就让这两位“选手”上场比一比。3. 实战对比从数据预处理到模型训练光说不练假把式。我们现在就把数据喂给这两个模型看看它们在实际训练和预测中的表现。3.1 数据预处理与数据集构建时序数据预测第一步也是最重要的一步就是把数据整理成模型能吃的格式。我们需要用过去一段窗口的数据来预测下一个时间点。def create_sequences(data, target, sequence_length): 将时序数据转换为监督学习格式 xs, ys [], [] for i in range(len(data) - sequence_length): x data[i:isequence_length] y target[isequence_length] xs.append(x) ys.append(y) return np.array(xs), np.array(ys) # 假设我们的DataFrame是df目标列是‘target‘特征列是[‘lag1‘, ‘lag24‘, ‘lag168‘, ‘feature_rand‘] target df[target].values features df[[lag1, lag24, lag168, feature_rand]].values # 数据标准化非常重要能加速模型收敛 scaler_x StandardScaler() scaler_y StandardScaler() features_scaled scaler_x.fit_transform(features) target_scaled scaler_y.fit_transform(target.reshape(-1, 1)).flatten() # 定义序列长度用过去多少个小时的数据来预测 SEQ_LENGTH 168 # 例如用过去一周168小时的数据 # 创建序列 X, y create_sequences(features_scaled, target_scaled, SEQ_LENGTH) print(f序列数据形状: X{X.shape}, y{y.shape}) # 划分训练集、验证集和测试集 X_train, X_temp, y_train, y_temp train_test_split(X, y, test_size0.3, shuffleFalse) # 时序数据通常不打乱 X_val, X_test, y_val, y_test train_test_split(X_temp, y_temp, test_size0.5, shuffleFalse) # 转换为PyTorch张量 X_train_t torch.FloatTensor(X_train) y_train_t torch.FloatTensor(y_train).unsqueeze(1) # 增加一个维度匹配输出 X_val_t torch.FloatTensor(X_val) y_val_t torch.FloatTensor(y_val).unsqueeze(1) X_test_t torch.FloatTensor(X_test) y_test_t torch.FloatTensor(y_test).unsqueeze(1) # 创建数据加载器 train_dataset TensorDataset(X_train_t, y_train_t) val_dataset TensorDataset(X_val_t, y_val_t) test_dataset TensorDataset(X_test_t, y_test_t) BATCH_SIZE 64 train_loader DataLoader(train_dataset, batch_sizeBATCH_SIZE, shuffleTrue) # 训练时可以打乱 val_loader DataLoader(val_dataset, batch_sizeBATCH_SIZE, shuffleFalse) test_loader DataLoader(test_dataset, batch_sizeBATCH_SIZE, shuffleFalse)3.2 模型初始化与训练循环接下来我们初始化两个模型并用同样的数据、同样的训练流程来训练它们。# 定义模型参数 INPUT_SIZE X_train.shape[2] # 特征数量这里是4 HIDDEN_SIZE 128 OUTPUT_SIZE 1 NUM_LAYERS 2 LEARNING_RATE 0.001 EPOCHS 50 # 为了快速演示可以适当减少 # 初始化模型 lstm_model LSTMModel(INPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, OUTPUT_SIZE) # SmolVLA风格模型参数d_model是内部特征维度nhead是注意力头的数量 smolvla_model SmolVLATemporalModel(INPUT_SIZE, d_model128, nhead4, num_layers2, output_sizeOUTPUT_SIZE) # 定义损失函数和优化器 criterion nn.MSELoss() lstm_optimizer optim.Adam(lstm_model.parameters(), lrLEARNING_RATE) smolvla_optimizer optim.Adam(smolvla_model.parameters(), lrLEARNING_RATE) # 训练函数 def train_model(model, optimizer, train_loader, val_loader, epochs, model_name): train_losses [] val_losses [] for epoch in range(epochs): # 训练阶段 model.train() train_loss 0 for batch_x, batch_y in train_loader: optimizer.zero_grad() outputs model(batch_x) loss criterion(outputs, batch_y) loss.backward() optimizer.step() train_loss loss.item() avg_train_loss train_loss / len(train_loader) train_losses.append(avg_train_loss) # 验证阶段 model.eval() val_loss 0 with torch.no_grad(): for batch_x, batch_y in val_loader: outputs model(batch_x) loss criterion(outputs, batch_y) val_loss loss.item() avg_val_loss val_loss / len(val_loader) val_losses.append(avg_val_loss) if (epoch1) % 10 0: print(f‘{model_name} Epoch [{epoch1}/{epochs}], Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}‘) return train_losses, val_losses print(开始训练LSTM模型...) lstm_train_loss, lstm_val_loss train_model(lstm_model, lstm_optimizer, train_loader, val_loader, EPOCHS, LSTM) print(\n开始训练SmolVLA风格模型...) smolvla_train_loss, smolvla_val_loss train_model(smolvla_model, smolvla_optimizer, train_loader, val_loader, EPOCHS, SmolVLA)3.3 训练过程与结果可视化训练跑完了咱们最关心的就是两个问题谁学得更快收敛速度谁学得更好预测精度我们把训练过程中的损失变化画出来看看。# 绘制训练损失曲线 plt.figure(figsize(12, 5)) plt.subplot(1, 2, 1) plt.plot(lstm_train_loss, label‘LSTM Train Loss‘, color‘blue‘, alpha0.7) plt.plot(lstm_val_loss, label‘LSTM Val Loss‘, color‘blue‘, linestyle‘--‘, alpha0.7) plt.plot(smolvla_train_loss, label‘SmolVLA Train Loss‘, color‘orange‘, alpha0.7) plt.plot(smolvla_val_loss, label‘SmolVLA Val Loss‘, color‘orange‘, linestyle‘--‘, alpha0.7) plt.xlabel(‘Epoch‘) plt.ylabel(‘Loss (MSE)‘) plt.title(‘Training Validation Loss‘) plt.legend() plt.grid(True, alpha0.3) # 在测试集上进行最终评估 def evaluate_model(model, test_loader): model.eval() predictions [] actuals [] with torch.no_grad(): for batch_x, batch_y in test_loader: outputs model(batch_x) predictions.extend(outputs.squeeze().numpy()) actuals.extend(batch_y.squeeze().numpy()) return np.array(predictions), np.array(actuals) lstm_preds, lstm_actuals evaluate_model(lstm_model, test_loader) smolvla_preds, smolvla_actuals evaluate_model(smolvla_model, test_loader) # 注意预测结果是标准化后的需要反标准化回原始尺度 lstm_preds_orig scaler_y.inverse_transform(lstm_preds.reshape(-1, 1)).flatten() smolvla_preds_orig scaler_y.inverse_transform(smolvla_preds.reshape(-1, 1)).flatten() actuals_orig scaler_y.inverse_transform(lstm_actuals.reshape(-1, 1)).flatten() # 两个模型用的实际值一样 # 计算评估指标均方根误差 (RMSE) 和 平均绝对百分比误差 (MAPE) def calculate_metrics(preds, actuals): rmse np.sqrt(np.mean((preds - actuals) ** 2)) # 避免除以0计算MAPE时过滤掉实际值为0的点 non_zero_idx actuals ! 0 mape np.mean(np.abs((preds[non_zero_idx] - actuals[non_zero_idx]) / actuals[non_zero_idx])) * 100 return rmse, mape lstm_rmse, lstm_mape calculate_metrics(lstm_preds_orig, actuals_orig) smolvla_rmse, smolvla_mape calculate_metrics(smolvla_preds_orig, actuals_orig) print(f\n--- 模型在测试集上的表现 ---) print(fLSTM模型 - RMSE: {lstm_rmse:.2f}, MAPE: {lstm_mape:.2f}%) print(fSmolVLA风格模型 - RMSE: {smolvla_rmse:.2f}, MAPE: {smolvla_mape:.2f}%) # 绘制部分测试集上的预测对比图 plt.subplot(1, 2, 2) sample_points 200 # 只看前200个点避免图太密 plt.plot(actuals_orig[:sample_points], label‘Actual‘, color‘black‘, linewidth2, alpha0.7) plt.plot(lstm_preds_orig[:sample_points], label‘LSTM Pred‘, color‘blue‘, alpha0.7, linestyle‘:‘) plt.plot(smolvla_preds_orig[:sample_points], label‘SmolVLA Pred‘, color‘orange‘, alpha0.7, linestyle‘-‘) plt.xlabel(‘Time Step‘) plt.ylabel(‘Value‘) plt.title(‘Predictions vs Actuals (Sample)‘) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.show()运行完上面的代码你应该能看到两张图。第一张是损失曲线它能告诉我们模型训练得是否稳定、有没有过拟合。第二张是预测结果对比能直观地看到谁的预测线通常是蓝色虚线代表LSTM橙色实线代表SmolVLA更贴近真实的黑色曲线。4. 结果分析与优化思考从损失曲线和预测图上我们能读出不少信息。通常你可能会看到以下几种情况SmolVLA收敛更快得益于注意力机制的并行计算能力它的训练损失可能下降得更快。SmolVLA长期依赖捕捉更好在预测序列中变化更剧烈、或者周期性更复杂的部分SmolVLA的预测线橙色可能更贴合真实数据。这是因为注意力机制能直接关联距离较远的时间点。LSTM更稳定在某些数据模式相对简单、或序列长度不是特别长的任务上经典的LSTM可能表现非常稳健不容易过拟合。指标对比看打印出来的RMSE和MAPE。一般来说RMSE和MAPE越小越好。如果SmolVLA的这两个值明显低于LSTM说明它在整体精度上更优。如果SmolVLA效果没想象中好或者想进一步提升可以试试这些优化方向调整注意力机制我们用的是最基础的多头自注意力。你可以尝试加入因果注意力掩码确保预测时只看到历史信息或者使用更高效的自注意力变体如Linformer、Performer等来降低计算复杂度。改进位置编码对于时序数据位置编码至关重要。可以尝试学习式的位置编码或者针对时序特性设计的相对位置编码。模型结构融合不是二选一。可以考虑将LSTM和注意力机制结合比如用LSTM提取局部时序特征再用注意力机制融合全局信息形成混合模型。更精细的特征工程对于时序预测特征往往比模型更重要。除了滞后项可以尝试加入滚动统计特征均值、方差、时间特征小时、星期几、是否节假日、甚至外部特征如天气、事件。超参数调优序列长度SEQ_LENGTH、模型隐藏层大小、注意力头数量、层数、学习率等都对结果有巨大影响。可以借助网格搜索或贝叶斯优化等工具进行系统调参。5. 总结折腾完这一整套对比实验我的感受是SmolVLA所代表的基于注意力的方法确实为时间序列预测打开了一扇新窗户。它那种“纵观全局”再“重点聚焦”的工作方式在处理具有复杂长期依赖和周期性变化的序列时显得更有灵性。从实验结果看在不少场景下它的预测精度和收敛速度都表现出了优势。但这绝不意味着LSTM就该被淘汰了。LSTM结构清晰易于理解和调试在资源受限、序列长度适中或模式相对稳定的任务上它依然是可靠且高效的选择。说到底模型没有绝对的“最好”只有“最适合”。对于实际项目我的建议是不要盲目追新。可以像我们今天做的一样先用你的数据跑一个快速的基准测试把LSTM、SmolVLA或Transformer、甚至简单的线性模型都放在一起比一比。看看哪个模型在验证集上表现最稳、预测曲线最贴合、计算资源也吃得消。有时候简单模型配合优秀的特征工程效果可能远超复杂模型。希望这篇实战对比能给你带来一些启发。时序预测的江湖很大模型、技巧层出不穷但核心永远是对业务数据的深刻理解。多实验多分析总能找到最适合你手中那把数据“锁”的“钥匙”。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。