十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch实战:波士顿房价预测与神经网络回归模型构建

PyTorch实战:波士顿房价预测与神经网络回归模型构建 简介机器学习中的回归分析是预测连续数值的核心技术其原理是通过建立输入特征与目标变量之间的数学关系模型。在深度学习领域前馈神经网络FNN是实现复杂非线性回归的强大工具它通过多层感知机结构自动学习特征表示。这项技术的价值在于能够处理高维、非线性的数据关系广泛应用于金融预测、销量预估、医疗诊断等场景。本文以经典的波士顿房价预测为切入点详细解析了使用PyTorch框架构建回归模型的完整流程涵盖了数据标准化、模型设计、训练优化等关键环节并深入探讨了如何通过调整网络结构、优化超参数来提升模型性能为初学者提供了一个扎实的工程实践范例。1. 项目缘起从数据到预测一个经典的机器学习入门实践如果你刚开始接触深度学习或者想找一个能串联起PyTorch核心概念、数据处理、模型构建、训练与评估全流程的实战项目那么“波士顿房价预测”绝对是一个绕不开的经典案例。它不像图像识别那样需要庞大的数据集和复杂的网络结构也不像自然语言处理那样有难以捉摸的语义理解。它的核心在于用一组清晰、结构化的数据比如房屋的房间数、犯罪率、离高速公路的距离等通过一个相对简单的神经网络模型去预测一个连续的数值——房价。这个过程能让你把PyTorch那些抽象的Tensor、Module、Optimizer、Loss等概念实实在在地用代码“跑”起来看到损失曲线下降最终得到一个能做出合理预测的模型。我之所以选择用前馈神经网络FNN来实现是因为它结构直观非常适合初学者理解神经网络的基本工作原理数据从输入层进入经过若干隐藏层的非线性变换最终从输出层得到一个预测值。整个项目就像搭积木从准备数据、定义网络、编写训练循环到评估结果每一步你都能清晰地看到自己在做什么以及为什么要这么做。网上有很多相关的教程和代码片段但很多要么过于简略跳过了关键细节比如数据为什么要标准化要么把代码和原理割裂开让人知其然不知其所以然。在这篇分享里我会结合我多次带新手复现这个项目的经验不仅把代码写出来更会重点解释每一个步骤背后的逻辑以及那些容易踩坑、但教程里常常一笔带过的地方。2. 环境搭建与数据初探万事开头细节决定成败在动手写模型之前有两件“脏活累活”必须做好一是搭建一个稳定、版本匹配的PyTorch环境二是彻底理解并处理好我们要用的数据。这两步做扎实了后面的模型训练才会顺利。2.1 PyTorch环境配置避开版本冲突的坑很多新手第一个跟头就摔在环境上。看着官网pip install torch好像很简单但装完一运行可能就报CUDA版本不匹配或者跟其他包冲突。我的建议是永远使用Anaconda或Miniconda来管理Python环境。它能很好地隔离不同项目所需的依赖。首先创建一个专用于本项目的虚拟环境conda create -n boston_housing python3.9 conda activate boston_housing选择Python 3.9是一个比较稳妥的版本兼容性好。接下来安装PyTorch。不要去pip直接装而是务必去PyTorch官网https://pytorch.org/使用它提供的安装命令生成器。你需要根据你的操作系统、包管理工具Conda/Pip、CUDA版本如果有NVIDIA GPU且已安装CUDA来选择。对于没有GPU的电脑就选择CUDANone。例如对于使用Conda且没有GPU的Windows用户官网可能会给出类似这样的命令conda install pytorch torchvision torchaudio cpuonly -c pytorch对于有GPU的用户命令会包含cudatoolkitxx.x。这里的关键是你的PyTorch版本、CUDA驱动版本、cudatoolkit版本必须兼容。一个常见的错误是系统安装了高版本的CUDA驱动比如12.x却安装了为低版本CUDA比如11.8编译的PyTorch导致无法调用GPU。如果不确定可以先安装CPU版本跑通流程再研究GPU加速。安装完成后在Python中验证import torch print(torch.__version__) # 查看PyTorch版本 print(torch.cuda.is_available()) # 查看GPU是否可用返回True则成功2.2 波士顿房价数据集理解你的“原料”波士顿房价数据集是一个经典的回归问题数据集包含506个样本每个样本有13个特征如人均犯罪率、住宅平均房间数等和1个目标值房屋中位数价格。我们可以直接从sklearn库中加载它这比从文件读取更方便。from sklearn.datasets import load_boston import pandas as pd # 注意新版本sklearn中load_boston已被移除因其涉及伦理问题。 # 我们可以使用一个替代的、或从本地加载。这里假设我们有一个本地副本或使用其他来源。 # 为演示我们使用一个模拟数据加载过程。 import numpy as np # 模拟加载数据实际中请确保你有正确的数据源 # 例如可以从网上下载原始的波士顿房价数据CSV文件 # 这里我们创建一个符合原数据集结构的模拟数据 np.random.seed(42) n_samples 506 n_features 13 # 生成模拟特征数据 data np.random.randn(n_samples, n_features) # 生成模拟目标值房价使其与特征有简单线性关系加噪声 true_weights np.random.randn(n_features) target data.dot(true_weights) np.random.randn(n_samples) * 0.1 # 将数据转换为PyTorch Tensor import torch from torch.utils.data import TensorDataset, DataLoader # 转换为浮点型Tensor features torch.tensor(data, dtypetorch.float32) labels torch.tensor(target, dtypetorch.float32).view(-1, 1) # 将标签reshape为 [506, 1]注意由于sklearn.datasets.load_boston在后续版本中被弃用在实际项目中你需要寻找替代的数据源比如从Kaggle下载或者使用其他类似的回归数据集如加州房价数据集。这里用模拟数据是为了流程演示的完整性。加载数据后千万别急着喂给模型。我们需要做两件至关重要的事数据探索和数据预处理。数据探索用pandas看看数据的基本情况。df pd.DataFrame(data.numpy()) # 将Tensor转回numpy供pandas使用 print(df.describe()) # 查看各特征的统计信息均值、标准差、最小值、最大值等你会发现不同特征的取值范围量纲差异巨大。比如“犯罪率”可能介于0到1之间而“房间数”可能在3到9之间。如果直接把这样的数据输入网络量纲大的特征会主导梯度下降的方向导致模型难以收敛或者收敛缓慢。因此标准化Standardization是必须的。数据预处理我们使用sklearn的StandardScaler对特征进行标准化使其均值为0标准差为1。对于回归问题的标签房价有时也需要进行缩放但更常见的做法是让模型直接学习原始尺度或者进行最小-最大归一化。这里我们对特征做标准化。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split(data, target, test_size0.2, random_state42) # 初始化标准化器并用训练集拟合 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 拟合并转换训练集 X_test_scaled scaler.transform(X_test) # 用训练集的参数转换测试集 # 转换为PyTorch Tensor X_train_tensor torch.tensor(X_train_scaled, dtypetorch.float32) y_train_tensor torch.tensor(y_train, dtypetorch.float32).view(-1, 1) X_test_tensor torch.tensor(X_test_scaled, dtypetorch.float32) y_test_tensor torch.tensor(y_test, dtypetorch.float32).view(-1, 1) # 创建DataLoader方便批量训练 train_dataset TensorDataset(X_train_tensor, y_train_tensor) test_dataset TensorDataset(X_test_tensor, y_test_tensor) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse)这里有几个关键点必须先划分再标准化用训练集的均值和标准差去标准化测试集这是为了模拟真实场景——我们不可能提前知道未来测试集的数据分布。DataLoader的作用它帮我们自动完成分批batching、打乱shuffle等操作让训练循环更简洁。标签view(-1, 1)这步很重要。回归问题的标签通常是一个标量但PyTorch的损失函数如MSELoss期望预测值和目标值的形状一致。view(-1, 1)把形状[n]的标签变成[n, 1]与模型的输出形状匹配。3. 前馈神经网络模型构建拆解PyTorch的Module有了干净的数据我们就可以搭建模型了。前馈神经网络也叫多层感知机MLP其核心就是全连接层Linear和非线性激活函数如ReLU的堆叠。3.1 网络结构设计深度与宽度的权衡对于波士顿房价预测这种特征数不多13个的问题一个包含1-2个隐藏层的网络通常就足够了。网络太深容易过拟合太宽可能学习到噪声。这里我们设计一个具有两个隐藏层的网络输入层13个神经元对应13个特征隐藏层164个神经元使用ReLU激活函数隐藏层232个神经元使用ReLU激活函数输出层1个神经元预测一个连续的房价值不使用激活函数回归任务输出层通常不用激活函数。用PyTorch实现这个网络需要继承torch.nn.Module类。import torch.nn as nn class BostonHousingModel(nn.Module): def __init__(self, input_dim13): super(BostonHousingModel, self).__init__() # 定义网络层 self.fc1 nn.Linear(input_dim, 64) # 第一层全连接 self.relu1 nn.ReLU() self.fc2 nn.Linear(64, 32) # 第二层全连接 self.relu2 nn.ReLU() self.fc3 nn.Linear(32, 1) # 输出层 # 可选初始化权重。好的初始化能加速收敛。 self._initialize_weights() def _initialize_weights(self): # 使用Kaiming初始化对ReLU激活函数友好 for m in self.modules(): if isinstance(m, nn.Linear): nn.init.kaiming_normal_(m.weight, modefan_out, nonlinearityrelu) if m.bias is not None: nn.init.constant_(m.bias, 0) def forward(self, x): # 定义前向传播路径 out self.fc1(x) out self.relu1(out) out self.fc2(out) out self.relu2(out) out self.fc3(out) # 输出层没有激活函数 return out代码解读与避坑点__init__中定义层这里定义了所有需要训练的参数权重和偏置。nn.Linear、nn.ReLU等都是nn.Module的子类。forward方法这是定义网络计算图的地方。它描述了数据x如何流经各个层。注意顺序一定要和__init__中定义的层对应。权重初始化nn.Linear的权重默认使用均匀初始化。对于使用ReLU的网络kaiming_normal_初始化是更好的选择它考虑了ReLU的非线性特性能有效缓解梯度消失或爆炸问题尤其是在网络较深时。这是一个提升模型收敛稳定性的小技巧。输出层无激活对于回归任务我们希望输出是任意实数。如果加了ReLU输出会被限制为非负数如果加了Sigmoid输出会被限制在(0,1)。这都不符合房价预测的需求。3.2 模型实例化与设备选择定义好类之后我们需要实例化模型并决定它在CPU还是GPU上运行。device torch.device(cuda if torch.cuda.is_available() else cpu) model BostonHousingModel(input_dim13).to(device) print(model)model.to(device)将模型的所有参数和缓冲区移动到指定的设备GPU/CPU。这是一个好习惯确保后续的输入数据也在同一个设备上。4. 训练循环的完整实现损失、优化与迭代模型和数据都准备好了接下来就是最核心的训练部分。训练的本质是一个优化问题我们通过反向传播计算损失函数关于模型参数的梯度然后用优化器根据梯度更新参数使得损失函数值不断减小。4.1 损失函数与优化器选择对于回归问题最常用的损失函数是均方误差MSE, Mean Squared Error它计算预测值与真实值之间差值的平方的平均值。PyTorch中对应nn.MSELoss()。 优化器我们选择Adam。它结合了动量Momentum和自适应学习率如RMSProp的优点在大多数深度学习任务上表现稳定且对学习率不那么敏感非常适合新手。criterion nn.MSELoss() # 损失函数均方误差 optimizer torch.optim.Adam(model.parameters(), lr0.001) # 优化器Adam学习率设为0.001学习率lr这是最重要的超参数之一。太大可能导致训练震荡甚至发散太小则收敛缓慢。0.001是Adam一个比较通用的初始值。我们也可以后续加入学习率调度器lr_scheduler来动态调整。model.parameters()告诉优化器需要更新哪些参数即模型中所有nn.Linear层的权重和偏置。4.2 完整的训练与验证循环我们将训练过程分成多个轮次epoch每个epoch遍历整个训练集一次。在每个epoch中我们还会在测试集上验证一次以监控模型在未见过的数据上的表现防止过拟合。num_epochs 200 train_losses [] val_losses [] for epoch in range(num_epochs): # ---------------------- 训练阶段 ---------------------- model.train() # 将模型设置为训练模式影响Dropout、BatchNorm等层 running_train_loss 0.0 for batch_idx, (data, target) in enumerate(train_loader): # 1. 将数据移动到设备 data, target data.to(device), target.to(device) # 2. 前向传播 outputs model(data) loss criterion(outputs, target) # 3. 反向传播与优化 optimizer.zero_grad() # 清空上一轮的梯度非常重要 loss.backward() # 反向传播计算梯度 optimizer.step() # 根据梯度更新参数 running_train_loss loss.item() * data.size(0) # 累加批次损失 # 计算平均训练损失 epoch_train_loss running_train_loss / len(train_loader.dataset) train_losses.append(epoch_train_loss) # ---------------------- 验证/测试阶段 ---------------------- model.eval() # 将模型设置为评估模式 running_val_loss 0.0 with torch.no_grad(): # 关闭梯度计算节省内存和计算资源 for data, target in test_loader: data, target data.to(device), target.to(device) outputs model(data) loss criterion(outputs, target) running_val_loss loss.item() * data.size(0) epoch_val_loss running_val_loss / len(test_loader.dataset) val_losses.append(epoch_val_loss) # 打印进度 if (epoch 1) % 20 0: print(fEpoch [{epoch1}/{num_epochs}], Train Loss: {epoch_train_loss:.4f}, Val Loss: {epoch_val_loss:.4f})逐行解析与核心经验model.train()和model.eval()这两个方法会改变某些特定层如Dropout,BatchNorm的行为。在训练时Dropout会随机丢弃神经元BatchNorm会使用当前批次的统计量在评估时Dropout不工作BatchNorm使用运行均值/方差。忘记切换模式是常见错误会导致评估结果不一致。optimizer.zero_grad()PyTorch的梯度是累加的。如果不每轮清空梯度会不断累积相当于增大了批大小导致训练出错。这行代码必须放在loss.backward()之前。loss.backward()和optimizer.step()这是训练的核心。backward()计算梯度step()根据梯度更新参数。with torch.no_grad()在验证和测试时我们不需要计算梯度。这个上下文管理器能显著减少内存消耗并加速计算。损失累加技巧loss.item()返回的是一个标量损失值针对当前批次。data.size(0)是当前批次的大小。我们累加的是loss * batch_size最后除以数据集总大小得到的是整个数据集上的平均损失这样更准确尤其是当最后一个批次不是完整批次时。打印频率每20个epoch打印一次避免输出刷屏又能清晰看到损失下降趋势。4.3 可视化训练过程训练完成后画出损失曲线能直观地判断模型的学习情况。import matplotlib.pyplot as plt plt.figure(figsize(10, 5)) plt.plot(range(1, num_epochs1), train_losses, labelTraining Loss, markero, markersize3) plt.plot(range(1, num_epochs1), val_losses, labelValidation Loss, markers, markersize3) plt.xlabel(Epoch) plt.ylabel(Loss (MSE)) plt.title(Training and Validation Loss over Epochs) plt.legend() plt.grid(True) plt.show()一个健康的训练过程应该是训练损失和验证损失都随着epoch增加而稳步下降并且两者最终保持在一个较低且接近的水平。如果训练损失持续下降但验证损失开始上升那就是过拟合的典型信号说明模型过于复杂记住了训练集的噪声。这时就需要考虑简化模型、增加数据、或使用正则化技术如Dropout、L2正则化。5. 模型评估、预测与调优思考训练结束模型收敛了我们怎么知道它到底好不好不能只看损失值还需要一些更直观的评估指标并看看它的具体预测效果。5.1 回归任务评估指标除了MSE常用的回归评估指标还有平均绝对误差MAE预测值与真实值绝对差值的平均值。它对异常值不如MSE敏感。决定系数R² Score表示模型对数据方差解释的比例越接近1越好。from sklearn.metrics import mean_absolute_error, r2_score model.eval() all_predictions [] all_targets [] with torch.no_grad(): for data, target in test_loader: data, target data.to(device), target.to(device) outputs model(data) all_predictions.append(outputs.cpu().numpy()) all_targets.append(target.cpu().numpy()) # 拼接所有批次的结果 all_predictions np.vstack(all_predictions).flatten() all_targets np.vstack(all_targets).flatten() # 计算指标 mse np.mean((all_predictions - all_targets) ** 2) mae mean_absolute_error(all_targets, all_predictions) r2 r2_score(all_targets, all_predictions) print(fTest MSE: {mse:.4f}) print(fTest MAE: {mae:.4f}) print(fTest R² Score: {r2:.4f})将这些指标与简单的基准模型比如用所有样本房价的平均值作为预测对比才能判断我们神经网络的价值。5.2 可视化预测结果将预测值与真实值画在散点图上可以直观地看模型的表现。理想情况下点应该紧密分布在yx这条对角线附近。plt.figure(figsize(8, 8)) plt.scatter(all_targets, all_predictions, alpha0.6) plt.plot([all_targets.min(), all_targets.max()], [all_targets.min(), all_targets.max()], r--, lw2) # yx 参考线 plt.xlabel(True House Price) plt.ylabel(Predicted House Price) plt.title(True vs. Predicted Prices) plt.grid(True) plt.show()如果点分布很散或者有明显的系统性偏差比如预测值普遍偏高或偏低说明模型还有改进空间。5.3 模型调优与改进思路如果对当前结果不满意可以从以下几个方向尝试优化调整网络结构加深或加宽网络尝试增加隐藏层数如3层或每层的神经元数量如128 64 32。但要注意过拟合风险。添加Dropout层在隐藏层后加入nn.Dropout(p0.2)随机丢弃一部分神经元这是一种有效的正则化手段。尝试其他激活函数如LeakyReLU、ELU等有时能缓解“神经元死亡”问题。调整超参数学习率尝试0.01 0.0001等或者使用学习率调度器如torch.optim.lr_scheduler.StepLR在训练过程中逐步降低学习率。批大小Batch Size尝试16 64等。较小的批大小可能带来更好的泛化能力但训练更不稳定较大的批大小训练更稳定但可能内存不足。优化器可以试试SGD配合动量有时SGD调得好能找到更优的解。改进数据处理特征工程波士顿数据集特征可能不够。可以尝试创造新的特征比如特征之间的交互项房间数*税率。更复杂的数据划分使用K折交叉验证来更稳健地评估模型性能避免因一次划分的随机性导致评估不准。应对过拟合早停Early Stopping监控验证集损失当其在连续多个epoch不再下降时提前停止训练。权重衰减L2正则化在优化器中加入weight_decay参数如weight_decay1e-4惩罚大的权重。获取更多数据这是解决过拟合最根本的方法但对于固定数据集可能不适用。调优是一个迭代和实验的过程。我的经验是一次只改变一个变量并记录每次实验的配置和结果这样才能清晰地知道是什么改动带来了效果提升。6. 项目总结与扩展思考走完这个完整的流程你应该已经对如何使用PyTorch构建一个端到端的机器学习项目有了扎实的体会。从数据加载、预处理到模型定义、训练循环再到评估和调优每一步都环环相扣。波士顿房价预测虽然简单但它麻雀虽小五脏俱全涵盖了监督学习回归任务的核心要素。在实际操作中有几个点我特别想再强调一下这些都是我踩过坑的地方数据标准化务必在划分训练/测试集之后进行这个顺序错了数据泄露就会发生模型评估结果会过于乐观不具备参考价值。训练循环中optimizer.zero_grad()、loss.backward()、optimizer.step()这三步的顺序和调用缺一不可尤其是清空梯度很容易忘记。善用model.train()和model.eval()特别是在模型包含Dropout或BatchNorm时模式切换直接影响模型行为。不要只看训练损失一定要在独立的验证集或测试集上评估模型这是检验模型泛化能力的唯一标准。这个项目完全可以作为你深度学习之旅的起点和模板。掌握了这个框架后你可以轻松地将其迁移到其他回归任务上比如预测股票价格、销售额、气温等。只需要替换数据集并根据新数据的特性适当调整网络结构比如输入维度、输出维度、隐藏层大小和超参数即可。更进一步你可以挑战更复杂的模型比如在隐藏层中使用更高级的激活函数或者尝试用卷积神经网络CNN来处理具有空间结构的数据用循环神经网络RNN处理时间序列数据。这个基于PyTorch的前馈神经网络实现就是你探索更广阔深度学习世界的一块坚实跳板。本文还有配套的精品资源点击获取
返回列表