
基于 PyTorch 的新冠数据回归预测代码先说结论这套代码的核心是用 PyTorch 搭一个多层感知机回归模型对新冠相关的时序数据做数值预测。它解决的是“有历史数据、想预测未来走势”的问题适合入门深度学习回归任务、想熟悉 PyTorch 训练流程的人参考。我把完整思路、数据处理细节、模型结构、训练调参和踩坑记录都整理出来了。1. 项目背景与整体设计思路1.1 为什么选择回归预测这个任务新冠疫情的数据有一个很明显的特点它是典型的时间序列。每日新增、累计确诊、住院人数、重症人数这些指标都存在时间上的先后依赖关系。做这类数据预测本质上是在回答一个问题——根据过去一段时间的变化规律下一阶段的值大概是多少。回归预测是这个问题最直接的建模方式。与分类任务不同回归任务的输出是一个连续数值而不是一个类别标签。比如判断“明天新增确诊是高、中、低”是分类而预测“明天新增确诊具体是多少例”就是回归。这个项目选择回归是因为业务上需要的往往不是模糊的等级而是一个可量化的参考值。用 PyTorch 来做主要有三个原因。第一PyTorch 的自动求导机制让反向传播变得非常省事不需要手动推导梯度公式。第二它的Dataset和DataLoader接口对时间序列数据的批量加载非常友好。第三社区生态成熟遇到问题很容易搜到解决方案。对于这种中小规模的数据集PyTorch 的灵活性和易用性比 TensorFlow 更有优势。1.2 方案选型背后的核心考量这个项目的整体方案可以拆成几个关键选择每个选择背后都有明确的理由。数据处理上采用滑窗法。滑窗的核心逻辑是用过去 N 天的数据作为特征预测未来 M 天的数据。举个例子如果用过去 7 天的新增确诊预测下一天的值那么就会把数据切分成[x1, x2, ..., x7] - y这样的样本对一个 7 天的窗口就是一个样本窗口不断向后滑动生成大量训练样本。选择滑窗而不是直接把整个时间序列塞进模型是因为普通的多层感知机并不能直接处理变长时间序列。它需要固定维度的输入。滑窗把时间依赖关系转换成了固定维度的特征向量让模型可以从历史窗口中学习规律。模型结构选择上采用多层感知机而不是 LSTM 或 Transformer。这里有一个对新手很重要的认知并不是所有时间序列预测都必须用循环神经网络。当数据量不大、特征维度不高时一个带非线性激活函数的全连接网络已经能拟合很多规律。LSTM 的优势在于捕捉长距离依赖但如果序列长度只有几十条LSTM 的优势根本发挥不出来反而更容易过拟合。训练策略上使用 Adam 优化器和 MSE 损失函数。MSE 即均方误差是回归任务最常用的损失函数它会放大较大误差的影响促使模型优先把偏差大的样本学好。Adam 优化器则结合了动量和自适应学习率的优点在大多数场景下不需要手动调整学习率也能收敛得不错。1.3 数据获取与预处理流程做这个项目时数据来源是最先需要解决的问题。公开渠道能拿到的新冠数据大多是 CSV 格式通常包含国家或地区、日期、累计确诊、累计死亡、每日新增等字段。建议先下载原始数据存到本地然后只保留需要的列这样可以避免每次运行都去拉取网络数据。拿到原始数据之后第一件事是清洗检查是否有缺失值尤其要注意某些日期可能没有记录更新。确认数据类型正确日期列要转成标准的datetime格式数值列要转成float。按日期排序因为时间序列分析对顺序非常敏感乱序数据会让模型学到完全错误的关系。决定是否做差分处理。如果原始数据是累计值比如累计确诊通常建议转成每日新增也就是用后一天减去前一天。这样可以把非平稳序列转化成相对平稳的序列更利于模型学习。做完差分之后还需要做归一化。归一化是回归预测中一个极其重要的环节。如果原始数值范围差异太大比如新增确诊从几十到几万模型在训练时梯度震荡会非常严重。这里选用 MinMaxScaler把数据压缩到 0 到 1 之间公式是(x - min) / (max - min)。也可以用标准化即减去均值除以标准差但 MinMaxScaler 对于有明显上下界的数据更直观反归一化时也更容易理解。数据准备好之后按时间顺序切分成训练集和测试集。这里有个容易踩的坑时间序列数据不能随机切分。如果随机打乱再切分就相当于让模型偷看了未来的数据测试集就失去了意义。正确的做法是比如前 80% 的数据作为训练集后 20% 作为测试集严格按时间顺序切割。2. 核心细节解析与实操要点2.1 滑窗函数的具体实现滑窗是整个数据预处理的核心环节。我直接给出一个实测可用的实现import numpy as np import torch from torch.utils.data import Dataset def create_sequences(data, window_size7, predict_step1): 将时序数据转换为滑窗样本 data: 一维数组归一化后的数据 window_size: 用过去多少天作为特征 predict_step: 预测未来第几天的值 X, y [], [] for i in range(len(data) - window_size - predict_step 1): X.append(data[i : i window_size]) y.append(data[i window_size predict_step - 1]) return np.array(X), np.array(y)这段代码的逻辑很直白。X是形状为(样本数, window_size)的特征矩阵y是形状为(样本数,)的目标值。也就是说每个样本是由连续window_size天的数据组成的一维向量对应的目标值是窗口结束之后第predict_step天的值。窗口大小的选择需要结合业务和经验。窗口太小比如 3 天模型看到的历史信息太少很难捕捉周期性规律。窗口太大比如 30 天样本数量会减少而且可能引入过多无关信息。对于新冠每日新增这类数据7 天是一个比较合理的起点因为一周的周期性能反映出工作日和周末的报告差异。predict_step的含义也要搞清楚。如果设为 1就是预测下一天设为 3就是预测三天后。这个参数决定任务难度预测越远不确定性越大误差通常也会越大。写这个函数时有几个细节要注意。循环的边界条件必须仔细验证否则很容易越界或漏掉样本。最好的测试方法是用一个长度很小的数组跑一遍打印输出人工核对样本是否正确。2.2 数据归一化与反归一化的重难点归一化处理这段代码看起来简单但在整个项目中承担了双重责任。训练时要归一化预测完成后还要反归一化把模型的输出还原成真实量纲。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) data_normalized scaler.fit_transform(data.reshape(-1, 1)).flatten()训练完成后假设模型输出了一个预测值它是在 0 到 1 之间的归一化数值。要还原成真实的每日新增需要pred_real scaler.inverse_transform(pred_normalized.reshape(-1, 1)).flatten()反归一化这一步看起来简单但却是初学者最常出错的地方。常见错误有两种。第一种是忘记了反归一化直接拿归一化的预测值去跟真实值比较结果误差看起来很小实际上毫无意义。第二种是inverse_transform的输入形状不对scaler是在二维形状上训练的反归一化时也必须传入二维数组。另外还有一个更深层的问题归一化的对象到底是原始值还是差分值。如果你做了差分处理那么你训练的是差分序列模型预测出来的也是差分值。要得到最终的预测值必须再加上前一天的原始值。这个还原链条如果断了预测结果会完全偏离。2.3 数据集类与批次加载配置PyTorch 推荐用Dataset子类来封装数据配合DataLoader做批次加载。这样做的优势在于数据加载和模型训练解耦后续如果要加数据增强或者验证集拆分只需要改Dataset内部逻辑。class CovidDataset(Dataset): def __init__(self, X, y): self.X torch.tensor(X, dtypetorch.float32) self.y torch.tensor(y, dtypetorch.float32) def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx]DataLoader的配置里有几个参数值得单独说。batch_size决定每次迭代送入模型的样本数。这个项目的数据量通常不大几千条样本左右batch_size 设置为 32 或 64 都合理。较大的 batch_size 会加快训练但可能导致模型泛化能力下降较小的 batch_size 梯度更新更频繁训练更不稳定。对于这个任务32 是一个稳妥的选择。shuffle参数需要特别留意。在训练集中数据按时间顺序排列窗口之间有一定重叠如果不对数据进行打乱模型会按照时间顺序连续看到样本容易陷入局部模式。建议训练集设置shuffleTrue。但验证集和测试集必须设置shuffleFalse因为评估时需要严格按时间顺序验证不能打乱。train_dataset CovidDataset(X_train, y_train) test_dataset CovidDataset(X_test, y_test) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse)2.4 模型结构设计与激活函数选择这个项目的模型结构并不复杂核心是一个多层感知机。但结构不复杂不代表不需要精心设计。每一层的维度、激活函数的选择、是否加 Dropout都直接影响最终的预测效果。import torch.nn as nn class RegressionMLP(nn.Module): def __init__(self, input_dim, hidden_dims[64, 32], dropout0.1): super(RegressionMLP, self).__init__() layers [] prev_dim input_dim for hidden_dim in hidden_dims: layers.append(nn.Linear(prev_dim, hidden_dim)) layers.append(nn.ReLU()) layers.append(nn.Dropout(dropout)) prev_dim hidden_dim layers.append(nn.Linear(prev_dim, 1)) self.net nn.Sequential(*layers) def forward(self, x): return self.net(x)输入维度等于window_size也就是滑窗的特征数量。两个隐藏层分别设置为 64 和 32 个神经元最后输出层维度为 1对应单个预测值。激活函数选用 ReLU 而不是 Sigmoid 或 Tanh原因是 ReLU 可以缓解梯度消失问题计算也更高效。ReLU 在输入为负时输出为 0在输入为正时输出等于输入本身。这种稀疏激活性让深层网络更好训练。但在实际使用中要注意一个现象如果某个神经元的所有输入都是负的它的输出一直是 0梯度传不回去这个神经元就“死”了。Dropout 在一定程度上可以缓解过拟合但也可能加剧神经元死亡所以 dropout 比率不宜设置过大0.1 是经验值。输出层不使用激活函数这是回归任务的关键。如果输出层加了 Sigmoid输出就被限制在 0 到 1 之间无法表达任意范围的预测值。所以在回归任务中输出层通常是纯线性层。2.5 损失函数选择与优化器配置损失函数选用nn.MSELoss()也就是均方误差损失。它的计算方式是求预测值和真实值之差的平方的平均值。为什么不用平均绝对误差两者都是回归常用的损失函数但 MSE 对大误差的惩罚更重。如果预测值和真实值差 100平方后是 10000梯度会非常大迫使模型优先修正这些错误样本。这在很多场景下是优点但也意味着 MSE 对异常值比较敏感。如果数据集中存在明显异常点比如某天数据上报异常导致数值暴涨MSE 会让模型花大量精力去拟合这个异常点。优化器选用torch.optim.Adam。Adam 的核心思想是给每个参数自适应地调整学习率训练初期梯度大时步长相对大后期梯度小时步长变小。这对超参数调整经验不多的新手特别友好。import torch.optim as optim model RegressionMLP(input_dim7) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr0.001)学习率lr0.001是 Adam 的默认推荐值也是大多数神经网络任务的合理起点。如果训练后发现 loss 震荡剧烈可以降到 0.0001如果 loss 下降太慢可以试试 0.003。2.6 训练循环的完整实现训练循环是 PyTorch 项目中最模板化的部分但也是新手容易出错的地方。最容易范的错误是忘记调用optimizer.zero_grad()。PyTorch 的梯度是累积的如果不手动清零上一次迭代的梯度会和当前梯度累加导致参数更新方向和幅度完全错误。def train_model(model, train_loader, criterion, optimizer, epochs200, print_every20): model.train() for epoch in range(epochs): total_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() outputs model(X_batch) loss criterion(outputs.squeeze(), y_batch) loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(train_loader) if (epoch 1) % print_every 0: print(fEpoch [{epoch1}/{epochs}], Loss: {avg_loss:.6f})outputs.squeeze()这行要对齐维度。模型输出的形状是(batch_size, 1)而目标值y_batch的形状是(batch_size,)如果不做squeeze()直接用criterion计算容易出现形状不匹配的报错。新旧版本的 PyTorch 对于形状不匹配的损失计算处理方式有差异统一做好squeeze()是最稳妥的做法。训练结束后记得调用model.eval()切换模型到评估模式。在评估模式下Dropout 会失效BatchNorm 会使用统计量而不是批次统计量保证预测结果稳定。这个切换在使用内置的model.eval()时可以自动完成但如果在推理阶段忘了切回来带有 Dropout 的模型每次预测都会得到略微不同的结果而且偏差较大。3. 实操过程与完整实现3.1 环境搭建与依赖安装在开始写代码之前先把环境搭好。PyTorch 的安装是整个项目的第一道关卡也是很多新手卡住的地方。如果电脑有 NVIDIA 独立显卡建议安装 GPU 版本训练速度会快很多。安装之前先确认显卡驱动版本和 CUDA 版本匹配。可以用nvidia-smi查看显卡驱动支持的 CUDA 版本然后在 PyTorch 官网选择对应的安装命令。如果没有独立显卡也没关系CPU 版本完全可以跑通这个项目。这种数据量在几千条的回归任务CPU 训练也就几秒钟到一分钟的事GPU 不是必需品。推荐用 Anaconda 创建独立环境避免依赖冲突conda create -n pytorch_env python3.9 conda activate pytorch_env pip install torch torchvision pip install numpy pandas scikit-learn matplotlib用 Anaconda 的原因很简单Python 环境隔离做得好不同项目之间不会互相污染。你可能会在另一个项目里用到 TensorFlow如果所有包都装在同一个环境里版本冲突会让人崩溃。验证安装是否成功import torch print(torch.__version__) print(torch.cuda.is_available())如果torch.cuda.is_available()返回 False不代表安装失败只是说明当前环境没有可用的 GPU。3.2 数据加载与清洗的完整流程数据加载这部分代码是整个项目最花时间的部分。直接用现成的公开数据然后做清洗。假设拿到的 CSV 文件包含date、confirmed、deaths、recovered等列只需要关注date和confirmed。import pandas as pd df pd.read_csv(covid_data.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 转为每日新增 df[daily_new] df[confirmed].diff().fillna(0) # 只保留需要的列 data df[daily_new].values.astype(float)这里有几个细节值得注意。diff()计算的是前后行的差值第一行的差值结果是 NaN需要用fillna(0)填充。用累计值转每日新增后第一天的每日新增设为 0 是合理的因为之前没有数据。还需要检查数据中是否有负数。理论上每日新增不可能是负数但数据更新可能出现回溯修正导致某天的差值变成负数。处理这类异常值的方式是直接视为噪声或置为 0不要让异常值干扰模型训练。如果总数据量太少比如只有几十条建议增加数据来源或者换一个更长周期的数据集。神经网络对数据量是有基本要求的样本太少时模型很容易过拟合。3.3 数据集切分与归一化的具体操作切分数据前需要先把差分后的值做归一化或者先切分再归一化两种做法都可以但要注意避免信息泄漏。推荐的做法是先切分再用训练集的数据拟合 scaler再用同一个 scaler 转换测试集。train_size int(len(data_normalized) * 0.8) train_raw data_normalized[:train_size] test_raw data_normalized[train_size:] scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(train_raw.reshape(-1, 1)) train_scaled scaler.transform(train_raw.reshape(-1, 1)).flatten() test_scaled scaler.transform(test_raw.reshape(-1, 1)).flatten()为什么先切分再拟合 scaler如果先用全部数据拟合 scaler再切分那么测试集的最小值和最大值信息已经被用于计算归一化参数这相当于把测试集的信息提前泄露给了模型。严格来说这会让模型的评估结果偏乐观。虽然在这个项目中影响可能不大但养成这样的好习惯做更复杂项目时会少踩很多坑。切分比例选择 80/20。如果不放心还可以从训练集中再切出一部分作为验证集比如 70/15/15用验证集来判断是否过拟合决定何时停止训练。3.4 滑窗生成与训练测试集构建数据归一化之后就可以用滑窗函数生成样本了。window_size 7 predict_step 1 X_train, y_train create_sequences(train_scaled, window_size, predict_step) X_test, y_test create_sequences(test_scaled, window_size, predict_step) print(训练集形状:, X_train.shape, y_train.shape) print(测试集形状:, X_test.shape, y_test.shape)这里要强调一个容易忽略的问题对训练集和测试集要分别调用create_sequences不能把两个数据集合并后再一起滑窗。因为测试集窗口的起始位置如果落在训练集内部就会造成信息泄漏。举个例子训练集是第 1 天到第 80 天测试集是第 81 天到第 100 天。如果对全部数据一次性滑窗那么生成的某些测试样本可能会包含第 80 天之前的数据也就是包含了训练集的数据这样测试集就不干净了。3.5 训练过程与损失曲线观察训练过程我建议实时绘制损失曲线。这是观察模型收敛情况最直观的方式。每 20 个 epoch 打印一次平均损失同时把损失记录到一个列表中训练结束后用 matplotlib 画出来。import matplotlib.pyplot as plt losses [] for epoch in range(epochs): total_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() outputs model(X_batch) loss criterion(outputs.squeeze(), y_batch) loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(train_loader) losses.append(avg_loss) if (epoch 1) % print_every 0: print(fEpoch [{epoch1}/{epochs}], Loss: {avg_loss:.6f}) plt.plot(losses) plt.xlabel(Epoch) plt.ylabel(MSE Loss) plt.title(Training Loss Curve) plt.show()正常情况下的损失曲线应该是先快速下降然后逐渐平缓。如果曲线出现平坦下不去说明模型可能欠拟合需要增加模型容量或者降低学习率再看看。如果训练损失持续下降但验证集误差反而上升说明模型过拟合了此时应该增加 Dropout 比率、减少隐藏层神经元数量或者提前停止训练。epochs 设置为 200 是一个比较合理的参考值。训练完 200 轮后观察损失是否已经收敛。如果最后几轮的损失还在明显下降可以适当增加 epochs如果损失早就平了200 轮已经足够。3.6 测试集评估与反归一化还原训练完成后在测试集上评估模型效果。评估时不需要计算梯度用torch.no_grad()包裹可以省内存和加速。model.eval() with torch.no_grad(): y_pred_scaled model(torch.tensor(X_test, dtypetorch.float32)).squeeze().numpy() y_pred scaler.inverse_transform(y_pred_scaled.reshape(-1, 1)).flatten() y_true scaler.inverse_transform(y_test.reshape(-1, 1)).flatten()注意这里有个细节反归一化的y_test是指差分后的每日新增值或者归一化后的原始值取决于你前面是否做了差分。如果是差分序列想要得到预测的真实累计值需要把预测的差分值累加到最后一个真实值上。真实值 预测差分值 上一天的原始值如果项目需要的是累计确诊的预测而非每日新增的预测这一步的还原逻辑就非常重要一旦忽略预测结果会整体偏移。评估指标建议打印均方根误差和平均绝对误差from sklearn.metrics import mean_squared_error, mean_absolute_error rmse np.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f})RMSE 对较大误差更敏感MAE 更能反映整体平均偏差水平。两个指标一起看可以对模型的预测能力有一个更全面的判断。3.7 预测结果可视化对比最后把真实值和预测值画在同一个坐标系里是回归项目中最直观的验证方式。看到两条曲线贴得越近就越有说服力。plt.figure(figsize(12, 6)) plt.plot(y_true, labelTrue, markero) plt.plot(y_pred, labelPredicted, markerx) plt.xlabel(Test Sample Index) plt.ylabel(Daily New Cases) plt.title(True vs Predicted) plt.legend() plt.show()如果两条曲线整体趋势一致只是局部有偏移说明模型学到了主要规律。如果预测曲线是一条接近水平的直线说明模型基本失效可能的原因是特征提取不足、模型容量太小或者数据在归一化时出了问题。预测曲线如果出现明显滞后比如真实值上涨时预测值还在低位徘徊这可能说明窗口长度太短模型看不到足够长的历史依赖。这时可以尝试增大window_size比如从 7 改成 14 再观察。4. 常见问题与排查技巧实录4.1 模型输出始终是常数的问题这是我见过最普遍的问题无论输入什么样本模型的输出都接近同一个值。造成这个现象的原因通常有几个。第一个原因是数据本身没有明显的可学习模式。如果每日新增数据在测试集期间几乎是平稳的模型的理性选择确实是输出一个接近均值的常数因为这样 MSE 最小。第二个原因是特征与目标的关系太弱。如果只看过去 7 天的数据无法预测未来某天的突发变化比如防控政策调整导致的数值突变模型就会退化为预测均值。第三个原因是模型容量不足。隐藏层神经元太少网络没有能力表达输入和输出之间的非线性关系。可以尝试把隐藏层维度从[64, 32]调整到[128, 64]看看。排查方法也很简单先用训练集再预测一次看模型是否能把训练集的样本拟合好。如果训练集上也是一条平线说明模型容量或训练过程有问题如果训练集能拟合好、测试集是平线说明是泛化能力问题。4.2 数据归一化泄漏导致评估虚高数据归一化泄漏是指在切分数据之前就用整个数据集拟合 scaler导致测试集的信息被模型间接使用。这种情况下测试集的评估结果会偏乐观但模型在实际部署时表现会大打折扣。正确做法是先用训练集fit再用训练集和测试集分别transform。如果你发现自己写的是scaler.fit_transform(data_normalized)然后手动切分建议立刻改成训练集单独fit的方式。这个问题在时间序列预测中特别容易被忽略因为大家习惯了分类任务中先归一化再切分的做法。但回归和时序任务对信息泄漏的容忍度更低从一开始就要养成正确习惯。4.3 维度不匹配的常见报错与解法PyTorch 的报错信息通常很长最关键的信息在最后几行。最常见的维度报错有两种情况。第一种是RuntimeError: size mismatch。通常是模型的输入维度和实际传入数据维度不一致。比如定义模型时input_dim7但传入的 X 形状是(batch_size, 14)就会报错。检查方式是打印X_train.shape确认第二维等于模型输入维度。第二种是RuntimeError: mat1 and mat2 shapes cannot be multiplied。这通常是nn.Linear的输入特征数不对。比如上一层的输出维度是 64但下一层nn.Linear定义的输入维度写成了 32。检查方法是逐层打印每个线性层的输入输出维度或者直接画出模型结构。还有一种情况比较隐蔽用outputs.squeeze()和y_batch计算损失时如果y_batch还被多包了一层维度比如形状是(batch_size, 1)两者仍然能计算但结果可能不是你想要的。建议在训练循环开始时打印一次形状信息确认无误再继续。4.4 训练损失震荡不收敛的排查步骤损失震荡是深度学习中常见但让人头疼的问题。通常的排查顺序如下。先看学习率是否过大。lr0.001如果震荡明显可以改成0.0003或0.0001。学习率过大会导致参数在最优值附近来回跳跃看起来就是 loss 曲线波浪状。再看 batch_size 是否过小。batch_size 太小会让梯度估计的方差变大每次更新方向不稳定。可以尝试把batch_size从 32 改成 64。再看数据是否归一化。如果数据没做归一化不同特征的数值范围差异大梯度更新的尺度也不一致模型很难收敛。这个项目里归一化是必须的不是可选项。最后看模型结构是否过深。对于这种中小数据集三层全连接已经足够过深的网络反而容易导致优化困难。4.5 反归一化后预测值异常大的排查预测值在反归一化后出现超出合理范围的数值比如负值或巨大值通常有两个原因。第一个原因是模型输出本身就不合理。如果训练没有收敛模型输出可能是任意值反归一化后自然也不合理。先把训练损失降到足够低再检查预测结果。第二个原因是反归一化时用错了 scaler。如果训练时用的是差分序列的 scaler但反归一化时却用了原始值的 scaler映射关系就错了。建议在代码中明确区分scaler_diff和scaler_raw并且确保inverse_transform使用的 scaler 与训练时一致。5. 实操心得与扩展方向5.1 新手的三个关键习惯第一打印形状信息。在写 PyTorch 项目时把每一步的数据形状打出来尤其是数据集构造完成后和训练循环开始时。很多人花大量时间找维度 bug实际上打印一行print(X_batch.shape, y_batch.shape)就能立刻定位。第二用固定随机种子。为了让实验结果可复现在代码开头设置随机种子import torch import numpy as np torch.manual_seed(42) np.random.seed(42)第三从小处起步。先只用 50 条数据跑通整个流程确认数据流没有问题再换成全量数据。这样可以在几秒钟内完成一次迭代调试效率非常高。5.2 如何从 MLP 升级到更复杂的模型如果这个项目你已经跑通了想进一步挑战可以考虑往三个方向扩展。第一个方向是引入 LSTM。LSTM 是专门设计来处理序列数据的循环神经网络它通过门控机制来记住长期信息。对于时间序列预测特别是当数据量增大后LSTM 通常比 MLP 表现更好。第二个方向是引入注意力机制。注意力机制可以让模型在预测时自动关注历史数据中更重要的时间点而不是像滑窗那样对所有位置一视同仁。第三个方向是多步预测。目前项目只预测一天可以扩展为预测未来 7 天或者 30 天的序列。多步预测有两种策略递归预测和直接预测。递归预测是把预测值当作下一步的输入直接预测是让模型一次输出多个目标值。两者各有优劣递归预测误差会累积直接预测则对模型的表达能力要求更高。5.3 关于数据与现象的几点补充最后想说的是用公开数据做这种预测项目本质上是一个技术学习和工程实践的过程。数据本身会有很多噪声真实世界的疫情发展受大量复杂因素影响不是简单的历史数据就能准确预测的。所以把重心放在掌握 PyTorch 回归建模的完整流程上比追求极低的预测误差更有价值。你会在这个过程中建立起数据集构造、模型设计、训练评估、误差分析的完整技术框架这套能力可以平移到很多其他预测场景中比如流量预测、销量预测、天气数据预测等。