简介:面向天气温度回归预测这一典型连续值预测场景,该项目基于Pytorch构建全连接神经网络,采用Adam优化器完成模型训练与温度拟合。代码源自个人大作业,评审分达95分以上,经过严格调试可直接运行,适合有一定Python基础、希望入门Pytorch回归任务的开发者学习借鉴。资源包共11个文件,体积仅218KB,包含Python主程序、CSV格式温度样本数据、3张可视化结果图(样本温度折线图、损失函数输出图、实际温度与预测温度对比图),以及gitignore、license等工程配置文件,xml与iml为IDE项目描述,整体结构清晰、便于快速定位核心代码。已有340人学习下载,通过学习可系统了解从数据加载、网络层设计、损失计算到Adam优化配置的完整实现,并能借助实际温度与预测温度对比图、损失变化图直观评估模型效果,对掌握回归预测项目开发流程和PyTorch实践均有帮助。
1. 温度回归预测与全连接网络的选型逻辑
在机房温度监控、冷库温控这类场景里,温度预测并不一定要上时序模型。把过去24小时的温度记录展开成滞后特征,一个三层的全连接神经网络配上Adam优化器,通常就能解释测试集上八到九成的温度方差,训练时间按分钟计。这个项目做的就是这件事:基于PyTorch全连接神经网络与Adam优化器,对temps.csv中的温度序列做回归预测,main.py单文件覆盖数据加载、样本构造、模型训练、指标评估和核心图表的输出。它不适合拿来炫技,但很适合作为回归预测的第一个可复现基线——刚学完PyTorch基础、需要一份能跑通的课程大作业源码的开发者,用这份代码起步是有效的参考。
2. 数据探索与样本构造:从temps.csv到可训练的滞后特征
2.1 解压后先看哪些文件
拿到压缩包后,先不要急着运行main.py。目录里的.idea、misc.xml、modules.xml、inspectionProfiles是PyCharm的工程配置,换机器直接删除也不影响运行,天气预测.iml同理。真正有价值的是以下三类:
temps.csv:原始温度时间序列数据main.py:加载数据、构造样本、定义模型、训练、评估和绘图的完整流程- 三张PNG图:
样本数据温度折线图.png、损失函数输出值.png、实际温度vs预测温度 折线图.png,这些图帮助你快速验证训练结果是否正常
.gitignore里一般会忽略__pycache__、.idea和虚拟环境目录,如果你要在自己的仓库里维护这份代码,建议保留。
2.2 读取温度数据与缺失值检查
温度回归的第一步永远是确认数据形状和缺失情况。数据通常是两列:时间戳date和温度值temp,也可能只有一列温度数值序列。用pandas读取后打印头几行和统计信息:
import pandas as pd df = pd.read_csv("temps.csv", parse_dates=["date"]) print(df.head()) print(df.info()) print(df.isnull().sum())parse_dates会把时间列解析成datetime类型,便于后续画折线图和对齐索引。检查缺失值这一步容易被跳过,但温度传感器上报中断产生的空值,会让滞后特征构造阶段的dropna()静默删除过多样本,导致训练样本数骤减。这里需要明确统计量纲:df.describe()里的mean和std决定后续标准化参数,务必记录,因为预测结果还原真实温度时还要用到。
2.3 把温度序列转为有监督回归样本
温度序列是典型的自相关数据——今天的温度大概率接近昨天同一时刻。常见做法是用滑动窗口构造滞后特征,将无监督的时间序列转换为(特征, 标签)的回归样本。
def series_to_supervised(data, win_size=24): cols = [] for i in range(win_size, 0, -1): cols.append(data.shift(i)) df_s = pd.concat(cols, axis=1) df_s.columns = [f"lag_{i}" for i in range(win_size, 0, -1)] df_s["target"] = data return df_s.dropna()shift(i)把温度序列整体下移i个位置,lag_24是24小时前的温度,target是当前时刻待预测的真实温度。dropna()去掉头部窗口内没有完整历史记录的样本。这一设计抓住了温度回归与普通回归的差异:样本间存在时间依赖,不能像MNIST那样随机打乱。
窗口大小的选择直接影响模型容量。温度数据按小时采集时,24小时覆盖一个完整昼夜周期,模型容易学到"当前温度与昨天同时刻温度强相关"这类规律;如果数据粒度是分钟级,窗口可能要扩到48甚至72。
| 数据粒度 | 建议窗口大小 | 理由 |
|---|---|---|
| 小时级 | 24 | 覆盖一个昼夜周期 |
| 半小时级 | 48 | 与24小时周期对应 |
| 分钟级 | 60~120 | 短时惯性为主,窗口过大带来噪声 |
2.4 标准化与时间顺序切分
温度值量纲对全连接网络的收敛速度影响很大。输入输出都做标准化,可以让Adam在更平坦的误差曲面上搜索。这里的关键区别是所有标准化参数只从训练集拟合:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() train_size = int(len(df_s) * 0.8) train_df, test_df = df_s.iloc[:train_size], df_s.iloc[train_size:] scaler.fit(train_df) train_scaled = scaler.transform(train_df) test_scaled = scaler.transform(test_df)不能先对整个数据集fit再切分,否则测试集的均值和方差信息泄漏到训练过程,验证指标会虚高。另一个常见误用是train_test_split(shuffle=True),在温度预测里这样随机打散样本,会让模型在训练时见到"未来"的数据。时间序列任务要按时间顺序切分:前80%做训练,后20%做验证。切分后,把特征列和标签列拆成numpy数组,再转换成PyTorch张量并封装为TensorDataset。
3. 模型定义与Adam优化器:全连接网络的层数、损失函数与参数配置
3.1 为什么全连接网络对滞后特征有效
温度序列的短程自相关性很强,lag_1到lag_24这24个特征已经携带了目标值的大部分信息。全连接网络本质上是在学习这些滞后特征到目标温度的非线性映射,比如夜间降温曲线的凹性、白天升温的饱和趋势,这些都能被ReLU激活函数的分段线性组合逼近。相比LSTM或Transformer,全连接网络没有序列建模的归纳偏置,但它收敛快、训练稳定,在小样本温度回归任务上往往是性价比最高的选择。
3.2 用nn.Module定义一个三层全连接网络
import torch.nn as nn class TemperatureMLP(nn.Module): def __init__(self, input_dim, hidden_dims=(128, 64), dropout=0.1): super().__init__() layers = [] last_dim = input_dim for h in hidden_dims: layers.append(nn.Linear(last_dim, h)) layers.append(nn.ReLU()) layers.append(nn.Dropout(dropout)) last_dim = h layers.append(nn.Linear(last_dim, 1)) self.net = nn.Sequential(*layers) def forward(self, x): return self.net(x)这里用nn.Sequential把线性层、ReLU和Dropout串联起来,结构清晰且便于调试。输入维度input_dim等于窗口大小win_size,因为每个样本只有一列温度特征。中间层用128和64两个隐藏维度,对几千条样本规模的数据集不会过参数化。Dropout(0.1)只在训练时生效,model.eval()后自动关闭,防止验证阶段误伤预测结果。
| 层 | 输入维度 | 输出维度 | 激活/操作 |
|---|---|---|---|
| Linear | 24 | 128 | ReLU + Dropout(0.1) |
| Linear | 128 | 64 | ReLU + Dropout(0.1) |
| Linear | 64 | 1 | 无激活 |
3.3 损失函数与回归指标的取舍
回归任务的默认损失函数是nn.MSELoss(),即均方误差。它和F.mse_loss等价,封装了均值和张量维度处理。预测值与真实温度偏差的平方被放大,所以异常温度毛刺会主导梯度方向——如果数据里有传感器尖峰噪声,建议先清洗再训练。评估阶段我习惯额外计算RMSE和R²,它们相对损失值更可读,具体实现放在第5章。
3.4 Adam优化器的参数配置与固定随机种子
import torch model = TemperatureMLP(input_dim=win_size) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.MSELoss() torch.manual_seed(42)Adam的核心是每个参数维护一阶动量m_t和二阶动量v_t,用指数移动平均估计梯度的一阶矩和二阶矩,并做偏差校正。相比SGD,它对学习率不那么敏感,尤其适合全连接网络这种参数量不大但梯度尺度不一的场景。lr=1e-3是Adam的默认配置,在温度回归这类平滑目标上收敛稳定。
Adam的betas参数需要了解但不必经常改:betas=(0.9, 0.999)表示一阶动量衰减为0.9、二阶动量衰减为0.999。eps=1e-8是防止除零的数值稳定项。weight_decay可选,设为1e-5能起到L2正则化作用,对抗小样本过拟合,我通常会在验证loss出现轻微上升时开启。随机种子固定在main.py开头,保证每次运行得到相同的初始化权重和数据划分,排查问题时才能对比不同版本的结果。
4. 训练循环与超参数调优:loss曲线、早停与常见收敛问题
4.1 训练循环的完整实现
epochs = 200 train_losses, val_losses = [], [] for epoch in range(epochs): model.train() total_loss = 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb) loss = criterion(pred.squeeze(), yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() total_loss += loss.item() * xb.size(0) if (epoch + 1) % 10 == 0: model.eval() val_pred = model(xv_tensor) val_loss = criterion(val_pred.squeeze(), yv_tensor).item() train_losses.append(total_loss / len(train_loader.dataset)) val_losses.append(val_loss) print(f"Epoch {epoch+1}/{epochs} train_loss: {train_losses[-1]:.6f} val_loss: {val_loss:.6f}")optimizer.zero_grad()必须在loss.backward()之前调用,否则每个batch的梯度会累加,等于用大了一倍的batch size训练,表现为loss周期性震荡。clip_grad_norm_把整个参数集的梯度L2范数截断到1.0,防止个别异常样本造成梯度爆炸;如果你的数据已经标准化,这个设置一般不会被触发,但留着它能在温度尖峰数据上避免训练发散。
4.2 验证流程与loss曲线输出
验证阶段严禁打开梯度计算。model.eval()切换BatchNorm和Dropout的行为,torch.no_grad()则彻底停掉自动求导,省显存、提速。验证用同一批固定数据xv_tensor,保证每个epoch对比的是同一分布,这样loss曲线才有可比性。
每10个epoch记录一次val_loss,用matplotlib把两条曲线画到损失函数输出值.png中:
import matplotlib.pyplot as plt plt.plot(range(10, epochs + 1, 10), train_losses, label="train_loss") plt.plot(range(10, epochs + 1, 10), val_losses, label="val_loss") plt.xlabel("epoch") plt.ylabel("MSE") plt.legend() plt.savefig("损失函数输出值.png", dpi=150)ReduceLROnPlateau可以在验证loss连续多次不下降时自动降低学习率,这对Adam后期收敛很有帮助——Adam前期收敛快,动态调整学习率比固定lr=1e-3更稳。
4.3 超参数推荐与调优方向
| 超参数 | 推荐值 | 过大时表现 | 过小时表现 |
|---|---|---|---|
| learning_rate | 1e-3 | 训练不稳定,loss震荡 | 收敛慢,200轮不够用 |
| batch_size | 32 | 收敛噪声大,占用显存多 | 收敛太慢,每轮迭代次数少 |
| hidden_dim | (128, 64) | 过拟合,训练时间变长 | 表达力不足,欠拟合 |
| dropout | 0.1 | 欠拟合,信息丢失 | 过拟合,验证loss变高 |
| epochs | 200 | 后期过拟合 | 未收敛,loss仍下降 |
温度回归数据量通常不大,batch_size设为32即可。如果你发现训练loss下降但val_loss升高,说明模型开始记忆噪声,优先增大dropout到0.2或减少隐藏层宽度,而不是降低epochs。反过来如果训练loss始终高位,先检查自己是否忘了做标准化,再确认lr是否过小。
4.4 训练阶段三个需要停下来的信号
第一个信号是验证loss连续20轮不降反升,这是过拟合的明确警报,此时训练loss往往仍在缓慢下降——模型在背训练样本。第二个信号是前10轮训练loss完全不动,多半是学习率过小或数据量纲问题。第三个信号是loss值出现nan,属于学习率过大导致的梯度爆炸,把lr从1e-3降到1e-4重跑即可。早停可以直接写在训练循环里:
best_val_loss = float("inf") patience = 20 wait = 0 # 每个epoch结束后更新早停逻辑 if val_loss < best_val_loss: best_val_loss = val_loss wait = 0 torch.save(model.state_dict(), "best_model.pt") else: wait += 1 if wait >= patience: breakbest_model.pt保存的是历史最优权重,而不是最后一个epoch的模型。这在温度回归里很重要,因为Adam后期会在最优解附近小幅震荡,最优权重往往出现在第60到100轮之间,早停配合best_model.pt不让前面学到的规律被后期震荡覆盖。
5. 预测结果验证:逆标准化、R²/RMSE计算与误差修正技巧
5.1 推理阶段输出与实际温度对比图
评估预测效果时,要把标准化后的预测值还原到真实温度量纲,否则计算出的指标无法直观反映"差几度"。加载保存的最优权重,关闭dropout后做推理:
model.load_state_dict(torch.load("best_model.pt")) model.eval() with torch.no_grad(): y_pred_scaled = model(xv_tensor).squeeze().numpy() y_pred = scaler.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel() y_true = scaler.inverse_transform(yv_tensor.numpy().reshape(-1, 1)).ravel()inverse_transform用训练集拟合的均值和标准差还原温度。注意这里必须用同一个scaler对象,不能用重新fit的scaler,否则输出量纲会偏离真实温度范围。绘图时x轴用测试集的时间索引,画两条折线,一条是实际温度,一条是预测温度,重叠程度越高说明回归效果越好。
5.2 回归指标的计算口径
温度预测的误差单位是摄氏度,这是RMSE比MSE更直观的原因。R²表示模型解释了测试集温度方差的百分比,接近1说明预测和真实值高度同步:
from sklearn.metrics import mean_squared_error, r2_score rmse = mean_squared_error(y_true, y_pred, squared=False) r2 = r2_score(y_true, y_pred) print(f"RMSE: {rmse:.2f}°C R²: {r2:.4f}")注意RMSE和训练loss的数值不能直接对比——训练loss来自标准化空间,RMSE来自温度原始空间,两者量纲不同。如果你发现RMSE在3°C以上,先检查预测曲线是否整体滞后于真实曲线。
5.3 温度峰谷位置滞后误差的修正技巧
全连接网络基于滞后特征做回归,它对剧烈温度变化的响应天然偏慢:真实温度在清晨快速上升时,预测值往往还在前几个时刻的低位区间。一个实用的修正技巧是计算残差的时间分布:
import numpy as np residual = y_true - y_pred mean_residual = np.mean(residual) print(f"平均偏差: {mean_residual:.2f}°C")如果mean_residual明显不为0,说明模型存在系统性偏差。修正方法是给预测值加一个偏置项y_pred_corrected = y_pred + mean_residual,这个操作的原理是:时序回归在多个时段的滞后误差有正有负,但温升/温降不对称时,整体偏差会向一个方向累积。通常修正后RMSE能下降0.2到0.5°C。对于峰值滞后问题,可以额外统计残差绝对值最大的时段,如果集中在凌晨和正午,说明模型对昼夜突变捕捉不足,这时增加lag_24特征的权重或把窗口从24扩到48是更直接的解决路径。
本文还有配套的精品资源,点击获取