CNN-LSTM-Attention时间序列预测:从原理到PyTorch实战

📅 2026/8/22 11:45:40
CNN-LSTM-Attention时间序列预测:从原理到PyTorch实战
这次我们来看一个面向时间序列预测的神经网络模型组合CNN-LSTM-Attention。这个方向在金融、气象、能源、交通等领域有广泛应用也是近年来学术研究和工程实践的热点。对于想要入门深度学习、寻找论文创新点或者解决实际预测问题的同学来说理解并实践这个组合模型是一个很好的起点。本文的核心不是空谈理论而是带你从零开始逐行解读一个可运行的CNN-LSTM-Attention时间序列预测代码。我们会重点关注这个模型的架构设计、代码实现逻辑、训练过程以及如何用它进行预测。无论你是刚接触深度学习的小白还是有一定基础想寻找新思路的研究者这篇文章都将提供一套完整的、可复现的实践指南。我们将从环境搭建开始一步步解析模型定义、数据处理、训练循环和预测可视化确保你能看懂每一行代码的作用并能将其应用到自己的数据集上。1. 核心能力速览在深入代码之前我们先快速了解CNN-LSTM-Attention组合模型的核心特性和应用场景。能力项说明模型类型深度学习混合神经网络用于时间序列预测核心组件CNN卷积神经网络、LSTM长短期记忆网络、Attention注意力机制主要功能对具有时序依赖性的数据进行未来值预测如股价、销量、温度输入数据单变量或多变量时间序列数据格式通常为[样本数, 时间步长, 特征数]输出结果未来一个或多个时间点的预测值硬件门槛支持CPU/GPU。GPU可大幅加速训练但模型参数量通常不大中等配置电脑即可运行。显存占用取决于批量大小batch size、序列长度和模型复杂度。对于中小型数据集4GB以上显存的GPU足够。纯CPU推理内存占用可控。代码依赖Python, PyTorch / TensorFlow, NumPy, Pandas, Matplotlib 等适合场景学术研究论文创新、模型对比、课程项目、行业应用需求预测、异常检测不适合场景超长序列如数万步可能需更复杂结构对实时性要求极高的在线预测需优化推理速度。这个模型的优势在于结合了三者的长处CNN擅长提取局部特征和模式LSTM擅长捕捉长期时序依赖Attention机制则能让模型在预测时更关注历史序列中更重要的部分从而提升预测精度和可解释性。2. 适用场景与使用边界2.1 谁适合使用这个模型深度学习初学者通过一个结构清晰、组件经典的组合模型理解CNN、LSTM和Attention是如何协同工作的。高年级本科生/研究生寻找时间序列预测领域的论文课题此模型可作为强基线Baseline或创新基础。算法工程师/数据分析师需要为业务如销量预测、负载预测搭建一个效果优于传统统计方法如ARIMA的预测模型。竞赛参与者在Kaggle等平台的时间序列预测赛中此模型是一个有力的候选方案。2.2 能解决什么问题单步预测根据过去N天的数据预测下一天的值。多步预测根据过去N天的数据预测未来M天的值M1。多变量预测不仅利用目标变量的历史值还引入其他相关变量如预测电价时加入天气、日期类型等特征。2.3 需要注意的边界与风险数据质量决定上限模型无法从噪声大或规律性差的数据中学到有效模式。预处理去噪、归一化至关重要。过拟合风险如果模型复杂度过高而数据量太少容易在训练集上表现很好在测试集上表现糟糕。需要使用验证集、早停Early Stopping、Dropout等策略。序列平稳性经典时间序列模型通常假设数据是平稳的。对于有明显趋势或季节性的数据需要进行差分等处理或确保模型有足够能力学习这些模式。外推能力有限模型在历史数据分布内预测效果较好对于远超历史范围的极端值预测能力较弱。合规与伦理在金融预测等领域应用时需明确模型结果仅供参考不能作为唯一的投资决策依据并注意相关法律法规。3. 环境准备与前置条件为了顺利运行后续的代码你需要准备好以下环境。我们将以PyTorch框架为例进行讲解因为它在研究和原型开发中非常灵活。3.1 基础软件环境操作系统Windows 10/11, Linux (如Ubuntu 20.04), 或 macOS。本文指令以Windows/Linux为例。Python版本推荐Python 3.8 到 3.10。版本过高或过低可能导致某些库不兼容。包管理工具pip或conda。3.2 核心Python库以下是必须安装的库及其大致用途库名用途安装命令piptorch深度学习框架用于构建和训练模型pip install torch torchvision torchaudio(请根据CUDA版本选择合适命令)numpy数值计算处理数组数据pip install numpypandas数据处理与分析读写CSV等pip install pandasmatplotlib绘图可视化数据与预测结果pip install matplotlibscikit-learn机器学习工具用于数据划分、评估指标pip install scikit-learnCUDA与GPU支持可选但推荐 如果你想使用GPU加速训练需要确保你的电脑有NVIDIA显卡。安装了对应版本的NVIDIA显卡驱动。安装了与驱动和PyTorch版本匹配的CUDA Toolkit。最简便的方法是访问 PyTorch官网 使用其提供的安装命令生成器选择你的系统、包管理工具、CUDA版本它会给出准确的安装命令。例如对于CUDA 11.8pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu1183.3 验证安装创建一个新的Python脚本或直接在终端中运行以下代码检查环境是否就绪import torch import numpy as np import pandas as pd import matplotlib import sklearn print(fPyTorch version: {torch.__version__}) print(fCUDA available: {torch.cuda.is_available()}) if torch.cuda.is_available(): print(fGPU device: {torch.cuda.get_device_name(0)}) print(fNumPy version: {np.__version__}) print(fPandas version: {pd.__version__})如果所有库都能成功导入且CUDA available显示为True如果你安装了GPU版说明环境配置成功。4. 项目结构与代码逐行解读现在我们开始构建一个完整的CNN-LSTM-Attention时间序列预测项目。我们将按照以下逻辑顺序展开数据准备与预处理模型定义CNN-LSTM-Attention训练循环预测与评估假设我们有一个名为time_series_data.csv的数据文件其中有一列value代表我们要预测的时序值。4.1 数据准备与预处理任何机器学习项目的起点都是数据。对于时间序列我们需要将其转化为模型可以学习的“样本-标签”对。import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler import torch from torch.utils.data import Dataset, DataLoader # 1. 加载数据 df pd.read_csv(time_series_data.csv) # 假设我们只使用 value 这一列进行单变量预测 data df[value].values.reshape(-1, 1) # 转换为二维数组方便归一化 # 2. 数据归一化 (非常重要能加速模型收敛) scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data) # 3. 创建数据集函数将时间序列转化为监督学习格式 def create_dataset(data, time_step60): 将时间序列数据转换为 (样本数, time_step, 特征数) 的X和对应的标签y。 Args: data: 归一化后的序列形状为 (序列长度, 特征数) time_step: 用过去多少时间步来预测未来 Returns: X: 输入数据形状为 (样本数, time_step, 特征数) y: 输出标签形状为 (样本数, 预测特征数) X, y [], [] for i in range(len(data) - time_step): # 取第i到itime_step的数据作为一个样本 X.append(data[i:(i time_step), :]) # 取第itime_step的数据作为该样本的标签预测下一个点 y.append(data[i time_step, :]) return np.array(X), np.array(y) # 设置时间步长例如用过去60个点预测下一个点 TIME_STEP 60 X, y create_dataset(scaled_data, TIME_STEP) # 4. 划分训练集和测试集 (按时间顺序划分不能随机打乱) train_size int(len(X) * 0.8) # 80%用于训练 X_train, X_test X[:train_size], X[train_size:] y_train, y_test y[:train_size], y[train_size:] # 5. 转换为PyTorch张量 X_train torch.FloatTensor(X_train) y_train torch.FloatTensor(y_train) X_test torch.FloatTensor(X_test) y_test torch.FloatTensor(y_test) print(f训练集形状: X_train {X_train.shape}, y_train {y_train.shape}) print(f测试集形状: X_test {X_test.shape}, y_test {y_test.shape})代码解读MinMaxScaler将数据缩放到[0,1]区间这是训练神经网络的常见操作。create_dataset函数是核心。它通过滑动窗口将一长条时间序列切割成许多个短序列。每个短序列time_step长度作为输入X其紧接着的下一个值作为要预测的标签y。切记时间序列数据划分不能随机打乱必须按时间先后顺序划分否则就“数据泄露”了用未来的信息预测过去。4.2 定义CNN-LSTM-Attention模型接下来是重头戏模型定义。我们将按CNN - LSTM - Attention - 全连接层的顺序构建。import torch.nn as nn import torch.nn.functional as F class CNN_LSTM_Attention(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size, dropout_prob0.2): Args: input_size: 输入特征维度对于单变量预测就是1 hidden_size: LSTM隐藏层神经元数量 num_layers: LSTM堆叠的层数 output_size: 输出维度预测未来几个点单步预测就是1 dropout_prob: Dropout概率防止过拟合 super(CNN_LSTM_Attention, self).__init__() self.hidden_size hidden_size self.num_layers num_layers # 1. 一维卷积层 (CNN) - 用于提取局部时序特征 # 假设我们使用一个卷积层卷积核大小为3输出通道数为64 self.conv1d nn.Conv1d(in_channelsinput_size, out_channels64, kernel_size3, padding1) # 卷积后接一个激活函数和池化层 self.relu nn.ReLU() self.pool nn.MaxPool1d(kernel_size2, stride2) # 计算经过卷积池化后的序列长度供后续LSTM使用 # 公式: L_out floor((L_in 2*padding - dilation*(kernel_size-1) -1) / stride 1) # 池化: L_out floor((L_in - kernel_size) / stride 1) # 这里我们简化处理在forward中动态计算 # 2. LSTM层 # 卷积层的输出通道数(64)作为LSTM的输入特征维度 self.lstm nn.LSTM(input_size64, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout_prob if num_layers1 else 0) # 3. 注意力机制层 # 我们实现一个简单的加性注意力Additive Attention self.attention_linear nn.Linear(hidden_size, hidden_size) self.attention_vector nn.Linear(hidden_size, 1, biasFalse) self.softmax nn.Softmax(dim1) # 4. 全连接输出层 self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x 形状: (batch_size, time_step, input_size) 例如 (32, 60, 1) # --- CNN 部分 --- # PyTorch的Conv1d期望输入为 (batch, channels, length) # 我们需要将 (batch, length, channels) 转置为 (batch, channels, length) x x.permute(0, 2, 1) # 变为 (batch_size, input_size, time_step) x self.conv1d(x) # 输出 (batch_size, 64, time_step) x self.relu(x) x self.pool(x) # 输出 (batch_size, 64, time_step//2) 假设stride2 # 将维度转换回LSTM期望的格式: (batch_size, new_time_step, 64) x x.permute(0, 2, 1) # 变为 (batch_size, new_time_step, 64) # --- LSTM 部分 --- lstm_out, (hidden, cell) self.lstm(x) # lstm_out 形状: (batch_size, new_time_step, hidden_size) # 它包含了每个时间步的隐藏状态 # --- Attention 部分 --- # 计算注意力权重 # 对lstm_out的每个时间步的隐藏状态进行线性变换 u torch.tanh(self.attention_linear(lstm_out)) # (batch, seq_len, hidden) attn_weights self.softmax(self.attention_vector(u)).squeeze(-1) # (batch, seq_len) # 将注意力权重应用到LSTM输出上得到上下文向量 # attn_weights.unsqueeze(-1): (batch, seq_len, 1) # lstm_out: (batch, seq_len, hidden) # 对应元素相乘后按时间步求和 context_vector torch.sum(attn_weights.unsqueeze(-1) * lstm_out, dim1) # (batch, hidden) # --- 输出层 --- output self.fc(context_vector) # (batch, output_size) return output, attn_weights # 同时返回输出和注意力权重用于可视化分析模型解读CNN层(self.conv1d)将原始的时序信号如(batch, 60, 1)进行一维卷积。卷积核沿着时间轴滑动可以捕捉局部如相邻几个时间点的波动模式。MaxPooling用于降维减少计算量并增强特征。LSTM层(self.lstm)接收CNN提取的局部特征序列。LSTM的门控机制使其能够学习长期依赖关系记住或忘记历史信息这对时间序列预测至关重要。Attention层这是模型的“点睛之笔”。它不是一个独立的层而是一种机制。self.attention_linear和self.attention_vector共同学习一个权重分布attn_weights。这个权重分布代表了模型在做出最终预测时对LSTM输出的每一个时间步的重视程度。最后用这个权重对LSTM所有时间步的输出进行加权求和得到context_vector上下文向量。这意味着模型可以动态地决定历史序列中哪部分信息对当前预测更重要。全连接层(self.fc)将富含信息的上下文向量映射到最终的预测值。为什么这个组合有效CNN像“显微镜”看清局部细节短期波动。LSTM像“历史学家”理清事件间的长期因果和依赖。Attention像“总指挥”在决策预测时回顾历史LSTM的输出并决定哪些历史片段更值得参考。4.3 训练模型定义好模型和数据后就可以开始训练了。# 超参数设置 INPUT_SIZE 1 # 输入特征维度单变量为1 HIDDEN_SIZE 128 # LSTM隐藏单元数 NUM_LAYERS 2 # LSTM层数 OUTPUT_SIZE 1 # 输出维度单步预测为1 LEARNING_RATE 0.001 NUM_EPOCHS 100 BATCH_SIZE 32 # 初始化模型、损失函数、优化器 device torch.device(cuda if torch.cuda.is_available() else cpu) model CNN_LSTM_Attention(INPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, OUTPUT_SIZE).to(device) criterion nn.MSELoss() # 回归任务常用均方误差损失 optimizer torch.optim.Adam(model.parameters(), lrLEARNING_RATE) # 创建DataLoader方便批量加载数据 from torch.utils.data import TensorDataset, DataLoader train_dataset TensorDataset(X_train, y_train) train_loader DataLoader(train_dataset, batch_sizeBATCH_SIZE, shuffleTrue) # 训练集可以shuffle test_dataset TensorDataset(X_test, y_test) test_loader DataLoader(test_dataset, batch_sizeBATCH_SIZE, shuffleFalse) # 测试集不要shuffle # 训练循环 train_losses [] test_losses [] for epoch in range(NUM_EPOCHS): model.train() # 切换到训练模式 epoch_train_loss 0 for batch_X, batch_y in train_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) # 前向传播 predictions, _ model(batch_X) # 我们暂时不关心注意力权重 loss criterion(predictions, batch_y) # 反向传播与优化 optimizer.zero_grad() # 清空过往梯度 loss.backward() # 反向传播计算梯度 optimizer.step() # 更新模型参数 epoch_train_loss loss.item() * batch_X.size(0) avg_train_loss epoch_train_loss / len(train_loader.dataset) train_losses.append(avg_train_loss) # 在测试集上评估 model.eval() # 切换到评估模式 with torch.no_grad(): # 不计算梯度节省内存和计算 epoch_test_loss 0 for batch_X, batch_y in test_loader: batch_X, batch_y batch_X.to(device), batch_y.to(device) predictions, _ model(batch_X) loss criterion(predictions, batch_y) epoch_test_loss loss.item() * batch_X.size(0) avg_test_loss epoch_test_loss / len(test_loader.dataset) test_losses.append(avg_test_loss) # 每10个epoch打印一次损失 if (epoch1) % 10 0: print(fEpoch [{epoch1}/{NUM_EPOCHS}], Train Loss: {avg_train_loss:.6f}, Test Loss: {avg_test_loss:.6f}) print(训练完成)训练要点DataLoader的shuffle参数训练集需要打乱shuffleTrue这有助于模型学习更通用的模式避免对数据顺序产生依赖。测试集绝对不能打乱shuffleFalse必须保持时间顺序以进行公平评估。model.train()和model.eval()这两个模式主要影响Dropout和BatchNorm等层的行为。训练时开启评估时关闭。torch.no_grad()在测试/验证阶段使用可以显著减少内存消耗并加速计算。4.4 预测与结果可视化训练完成后我们用模型在测试集上进行预测并将结果反归一化与真实值对比。# 使用训练好的模型进行预测 model.eval() with torch.no_grad(): # 对整个测试集进行预测 test_predictions, attn_weights model(X_test.to(device)) test_predictions test_predictions.cpu().numpy() # 移回CPU并转成numpy # 反归一化将预测值变回原始尺度 # 注意y_test是归一化后的我们需要用同样的scaler将其和预测值一起反归一化 y_test_actual scaler.inverse_transform(y_test.numpy()) test_predictions_actual scaler.inverse_transform(test_predictions) # 可视化对比 import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) plt.plot(y_test_actual, labelActual Values, colorblue, alpha0.7) plt.plot(test_predictions_actual, labelPredicted Values, colorred, alpha0.7, linestyle--) plt.title(Time Series Prediction: CNN-LSTM-Attention) plt.xlabel(Time Step) plt.ylabel(Value) plt.legend() plt.grid(True) plt.show() # 计算评估指标 from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(y_test_actual, test_predictions_actual) mse mean_squared_error(y_test_actual, test_predictions_actual) rmse np.sqrt(mse) r2 r2_score(y_test_actual, test_predictions_actual) print(f评估指标:) print(f 平均绝对误差 (MAE): {mae:.4f}) print(f 均方误差 (MSE): {mse:.4f}) print(f 均方根误差 (RMSE): {rmse:.4f}) print(f 决定系数 (R²): {r2:.4f})可视化与评估图表可以直观地看出预测曲线红色虚线是否紧跟真实曲线蓝色实线。MAE/MSE/RMSE衡量预测值与真实值的平均误差越小越好。R²分数衡量模型对数据波动的解释能力越接近1越好。4.5 注意力权重可视化进阶Attention机制的一个好处是可解释性。我们可以可视化测试集中某个样本的注意力权重看看模型在预测时更关注历史序列的哪些部分。# 取测试集第一个样本的注意力权重进行可视化 sample_idx 0 sample_X X_test[sample_idx].unsqueeze(0).to(device) # 增加batch维度 _, sample_attn_weights model(sample_X) sample_attn_weights sample_attn_weights.squeeze().cpu().numpy() # (seq_len,) # 绘制注意力权重图 plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) # 绘制该样本的输入序列归一化后的 plt.plot(range(TIME_STEP), X_test[sample_idx].numpy().squeeze(), labelInput Sequence) plt.xlabel(Past Time Steps) plt.ylabel(Normalized Value) plt.title(Input Time Series (Sample)) plt.grid(True) plt.subplot(1, 2, 2) # 绘制注意力权重分布 plt.bar(range(len(sample_attn_weights)), sample_attn_weights) plt.xlabel(Past Time Steps) plt.ylabel(Attention Weight) plt.title(Attention Weights Distribution) plt.grid(True) plt.tight_layout() plt.show()这张图能告诉我们模型在预测第61个点时它对前60个历史点中的哪些赋予了更高的权重。例如如果权重集中在最近的时间点说明模型更依赖近期信息如果权重分布较广说明模型综合考虑了更长的历史。5. 模型调优与改进思路跑通基础代码只是第一步。要让模型在你自己数据上表现更好需要尝试以下方向5.1 超参数调优时间步长 (TIME_STEP)用过去多少数据预测未来太小可能信息不足太大可能引入噪声且增加计算负担。可以通过实验选择。LSTM隐藏层大小 (HIDDEN_SIZE)和层数 (NUM_LAYERS)增加它们可以提升模型容量但也更容易过拟合。学习率 (LEARNING_RATE)最重要的超参数之一。太大可能导致震荡不收敛太小则收敛慢。可以尝试使用学习率调度器如torch.optim.lr_scheduler.ReduceLROnPlateau。批量大小 (BATCH_SIZE)受限于GPU显存。一般越大训练越稳定但可能泛化能力稍差。Dropout概率 (dropout_prob)防止过拟合的有效手段通常设置在0.2到0.5之间。5.2 模型结构改进更复杂的CNN使用多个卷积层或不同尺寸的卷积核Inception思路来捕捉多尺度特征。双向LSTM (Bi-LSTM)nn.LSTM设置bidirectionalTrue让模型同时考虑过去和未来的上下文信息对于时间序列需谨慎使用因为严格来说未来信息不可知但在某些场景下有效。不同的Attention尝试缩放点积注意力Scaled Dot-Product Attention即Transformer中的注意力或多头注意力Multi-Head Attention。残差连接在CNN或LSTM层之间添加残差连接缓解深层网络梯度消失问题。序列到序列 (Seq2Seq)结构对于多步预测可以使用Encoder-Decoder架构Encoder可以是CNN-LSTM-Attention编码整个输入序列Decoder逐步生成未来多个时间点的预测。5.3 数据处理与特征工程多变量输入修改INPUT_SIZE将其他相关特征如星期几、是否节假日、相关指标一并输入模型。更复杂的归一化尝试标准化StandardScaler或其他针对时间序列的归一化方法。处理缺失值与异常值在数据预处理阶段进行清洗或插补。加入时间特征将时间戳分解为年、月、日、小时、星期几等作为额外特征。6. 常见问题与排查方法在实践过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案训练损失不下降学习率太大或太小数据未归一化模型结构有误检查初始损失值绘制学习曲线检查数据范围调整学习率确保数据已归一化简化模型先测试测试损失远大于训练损失 (过拟合)模型过于复杂训练数据太少训练轮次太多观察训练/测试损失曲线增加Dropout使用L2正则化收集更多数据使用早停法GPU显存不足 (CUDA out of memory)批量大小太大序列长度太长模型参数量太大使用torch.cuda.empty_cache()监控显存使用减小BATCH_SIZE减小TIME_STEP使用梯度累积尝试更小的模型预测结果是一条直线或常数模型没有学到任何东西学习率过低激活函数问题检查模型输出是否随输入变化检查梯度是否消失检查数据预处理增大学习率检查网络层是否被正确激活尝试更复杂的模型RuntimeError: 输入维度不匹配CNN/LSTM/Linear层的输入输出维度设置错误打印每一层处理前后的张量形状仔细核对__init__中定义的各层参数并在forward中打印关键步骤的x.shape评估指标R²为负数模型预测效果比直接用均值预测还要差检查数据划分是否泄露检查反归一化是否正确确保测试集没有参与训练检查scaler是否用训练集拟合并应用于测试集7. 最佳实践与使用建议从简单开始先用一个简单的LSTM模型跑通流程确保数据管道和训练循环正确再加入CNN和Attention。保存与加载模型训练好的模型要及时保存方便后续使用和部署。# 保存 torch.save(model.state_dict(), cnn_lstm_attn_model.pth) # 加载 model.load_state_dict(torch.load(cnn_lstm_attn_model.pth)) model.eval()使用TensorBoard或WandB可视化训练过程、损失曲线、注意力权重等方便调试和分析。交叉验证对于时间序列可以使用“时序交叉验证”Time Series Split来更稳健地评估模型性能。考虑部署如果模型用于生产环境需要考虑将PyTorch模型转换为TorchScript或ONNX格式以提高推理效率并与不同平台集成。8. 总结与下一步通过本文的逐行解读你应该已经掌握了CNN-LSTM-Attention时间序列预测模型从理论到实现的完整路径。这个模型结构清晰融合了深度学习中几个经典且有效的模块非常适合作为入门深度学习时间序列预测的第一个“高级”项目。最值得尝试的点在于其可解释性通过Attention权重和性能潜力结合了局部特征提取与长期依赖建模。你最先应该验证的是数据预处理流程和基础的LSTM模型是否工作正常这是所有复杂模型的基石。最容易踩的坑通常是数据泄露打乱了时间序列和维度错误各层输入输出形状不匹配。按照文中的代码结构仔细核对每一步的张量形状能避开大部分问题。下一步你可以更换数据集尝试用这个模型预测股票价格、电力负荷、天气温度等真实数据。进行超参数搜索使用网格搜索Grid Search或随机搜索Random Search寻找最优参数组合。实现多步预测修改模型和损失函数实现预测未来多个时间点。对比实验分别训练CNN、LSTM、CNN-LSTM、LSTM-Attention等模型与CNN-LSTM-Attention在同一个测试集上对比用数据证明Attention或CNN的加入是否带来了提升。希望这份详尽的指南能帮助你快速上手并在此基础上开展自己的研究和应用。代码已力求清晰建议动手运行一遍并尝试修改其中的参数和结构这是学习深度学习最有效的方式。