news 2026/8/28 7:49:23

PyTorch实战:波士顿房价预测项目全流程解析与神经网络回归实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch实战:波士顿房价预测项目全流程解析与神经网络回归实践

简介:机器学习中的回归任务是预测连续数值输出的基础问题,其核心原理是通过建立输入特征与目标变量之间的映射关系来最小化预测误差。在深度学习框架中,PyTorch以其动态计算图和直观的API设计,为构建和训练神经网络模型提供了高效灵活的工具,特别适合教学和原型开发。通过实现经典的波士顿房价预测项目,可以贯通PyTorch的核心组件如Tensor、Dataset、DataLoader、Module、Optimizer和Loss Function,掌握从数据预处理、模型定义、训练循环到评估可视化的完整工作流。该项目不仅演示了如何使用前馈神经网络处理结构化数据,还涵盖了特征标准化、损失函数选择、优化器配置以及过拟合防治等工程实践关键点,为后续处理图像分类、文本生成等更复杂任务奠定了可迁移的范式基础。

1. 项目缘起与核心价值

最近在整理一些经典的机器学习入门项目,发现波士顿房价预测这个“老伙计”依然有着不可替代的教学价值。它不像图像识别那样需要庞大的计算资源,也不像自然语言处理那样涉及复杂的序列建模,但它却是一个绝佳的、能让你亲手触摸到机器学习全流程的“麻雀”。从数据加载、预处理、模型定义、训练到评估,每一个环节都清晰可见,非常适合用来理解一个预测系统是如何从零到一构建起来的。

这次我决定用PyTorch来实现它。虽然现在TensorFlow和PyTorch在学术界和工业界都平分秋色,但PyTorch以其动态计算图和更“Pythonic”的API设计,对于初学者和研究者来说,上手和理解模型内部运作的直观性要强得多。你写的每一行代码,几乎都能直接对应到数学公式或数据处理逻辑上,这种透明感是快速建立信心的关键。波士顿房价数据集本身是一个小型回归数据集,包含506个样本,每个样本有13个特征(如人均犯罪率、住宅平均房间数等),目标是预测房屋的中位数价值。用前馈神经网络(FNN)来解决这个回归问题,既能让我们实践PyTorch的基础操作,又能深入理解神经网络如何处理结构化数据。

这个项目的核心价值在于“贯通”。它不是一个炫技的复杂模型,而是一个让你把PyTorch的TensorDatasetDataLoaderModuleOptimizerLoss Function这几个核心组件串起来,形成一个完整工作流的实战案例。做完这个项目,你不仅能得到一个可以预测房价的简单系统,更重要的是,你能掌握用PyTorch解决一个真实机器学习问题的标准范式,这个范式可以无缝迁移到图像分类、文本生成等更复杂的任务中去。无论你是刚学完Python基础想踏入AI大门的新手,还是想从其他框架切换到PyTorch的开发者,这个项目都是一个理想的起点。

2. 环境搭建与数据初探:避开第一个坑

工欲善其事,必先利其器。搭建一个稳定、隔离的PyTorch开发环境是第一步,这里也是最容易出问题的地方。我强烈建议使用Anaconda来管理Python环境和包依赖,它能有效避免版本冲突。

2.1 创建并激活Conda环境

打开你的终端(Windows用Anaconda Prompt,Mac/Linux用终端),执行以下命令来创建一个名为pytorch_boston的新环境,并指定Python版本(这里用3.9,一个比较稳定且兼容性广的版本):

conda create -n pytorch_boston python=3.9 conda activate pytorch_boston

2.2 安装PyTorch:CPU还是GPU?

这是关键一步。安装命令需要去PyTorch官网(https://pytorch.org/)根据你的系统配置生成。官网的安装选择器非常直观,你需要选择:

  1. PyTorch Build:稳定版(Stable)。
  2. Your OS:你的操作系统(Windows, Linux, Mac)。
  3. Package:推荐使用CondaPip。Conda有时能更好地处理一些底层依赖(如CUDA相关的库)。
  4. Language:Python。
  5. Compute Platform:计算平台。这是核心选择:
    • 如果你有NVIDIA显卡并想使用GPU加速:选择对应的CUDA版本(如CUDA 11.8)。你需要先确认你的显卡驱动支持该CUDA版本。GPU训练在小数据集上优势不明显,但对于建立正确的流程和未来处理大数据至关重要。
    • 如果你没有GPU或想先简化流程:选择“CPU”。完全没问题,波士顿数据集很小,CPU训练也很快。

以在Windows系统下,使用Conda安装CPU版本的PyTorch为例,官网生成的命令可能类似于:

conda install pytorch torchvision torchaudio cpuonly -c pytorch

等待安装完成。安装后,可以在Python环境中验证:

import torch print(torch.__version__) # 输出PyTorch版本,如 2.2.0 print(torch.cuda.is_available()) # 输出False(CPU版本)或True(GPU版本)

2.3 加载与审视波士顿房价数据集

PyTorch本身不包含这个数据集,但我们可以从sklearn(scikit-learn)中方便地获取。首先安装scikit-learn和后续会用到的pandasmatplotlib

pip install scikit-learn pandas matplotlib

接下来,我们加载数据并看看它的“长相”:

import numpy as np import pandas as pd from sklearn.datasets import load_boston # 注意:新版本sklearn中已移除,需用替代方案 import torch from torch.utils.data import Dataset, DataLoader import matplotlib.pyplot as plt # 由于sklearn新版本移除了load_boston,我们使用一种兼容性更好的方式 # 从网络或本地加载数据 try: # 方法1:尝试从sklearn加载(旧版本) boston = load_boston() X = boston.data y = boston.target feature_names = boston.feature_names except AttributeError: # 方法2:如果失败,使用pandas从网络获取(推荐,更稳定) data_url = "http://lib.stat.cmu.edu/datasets/boston" raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None) data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]]) target = raw_df.values[1::2, 2] X = data y = target # 波士顿数据集的标准特征名 feature_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT'] print(f"数据形状: X={X.shape}, y={y.shape}") # 输出: X=(506, 13), y=(506,) print(f"特征名: {feature_names}") # 将数据转换为PyTorch张量,并指定数据类型为浮点数 X_tensor = torch.from_numpy(X).float() y_tensor = torch.from_numpy(y).float().view(-1, 1) # 将y从(506)变为(506, 1),与模型输出维度匹配 # 查看前5个样本 df = pd.DataFrame(X, columns=feature_names) df['MEDV'] = y print(df.head())

运行这段代码,你会看到一个DataFrame,清晰地展示了13个特征和1个目标值(MEDV,即房价中位数)。这一步看似简单,但至关重要。一个常见的坑是忽略了对数据本身的观察。你需要留意数据的尺度(比如TAX房产税和RM房间数数值相差巨大),以及是否有缺失值(波士顿数据集是完整的)。这些观察直接决定了下一步——数据预处理——该如何进行。

3. 数据预处理与数据集封装:为模型提供“标准餐”

原始数据很少能直接丢给神经网络。不同的特征具有不同的量纲和范围,这会导致梯度下降过程收敛缓慢,甚至不稳定。因此,标准化(Standardization)是必不可少的一步。它的目的是将每个特征的分布调整为均值为0、标准差为1的标准正态分布。

3.1 特征标准化与数据集划分

我们使用sklearnStandardScaler来方便地计算训练集的均值和标准差,并用它们来转换所有数据(包括后续的测试集),避免数据泄露。

from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 1. 划分训练集和测试集(通常8:2) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) print(f"训练集大小: {X_train.shape}, 测试集大小: {X_test.shape}") # 2. 标准化特征 scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) # 拟合训练集,并转换 X_test_scaled = scaler.transform(X_test) # 使用训练集的参数转换测试集 # 3. 转换为PyTorch张量 X_train_tensor = torch.from_numpy(X_train_scaled).float() y_train_tensor = torch.from_numpy(y_train).float().view(-1, 1) X_test_tensor = torch.from_numpy(X_test_scaled).float() y_test_tensor = torch.from_numpy(y_test).float().view(-1, 1)

这里有一个非常重要的经验点scalerfit(计算均值和标准差)只能在训练集上进行。然后用这个训练好的scaler去转换训练集和测试集。如果用了测试集的数据参与fit,就相当于在训练时“偷看”了测试集的信息,会严重高估模型在真实未知数据上的性能,这是机器学习中一个典型的数据泄露错误。

3.2 构建PyTorch Dataset与DataLoader

PyTorch通过DatasetDataLoader来高效地管理和加载数据。Dataset负责定义如何读取单个样本,DataLoader负责批量加载、打乱顺序、多进程读取等。

class BostonHousingDataset(Dataset): """波士顿房价数据集封装""" def __init__(self, features, targets): self.features = features self.targets = targets def __len__(self): return len(self.features) def __getitem__(self, idx): return self.features[idx], self.targets[idx] # 创建Dataset实例 train_dataset = BostonHousingDataset(X_train_tensor, y_train_tensor) test_dataset = BostonHousingDataset(X_test_tensor, y_test_tensor) # 创建DataLoader batch_size = 32 train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False) # 测试集不需要打乱

为什么使用DataLoader?它带来了几个好处:

  1. 批量训练:神经网络通常使用小批量随机梯度下降,DataLoader自动将数据分成指定大小的批次。
  2. 数据打乱:在每个训练周期(epoch)开始时打乱数据,有助于模型学习更通用的模式,避免因数据顺序带来的偏差。
  3. 并行加载:通过设置num_workers参数,可以利用多进程预加载数据到内存,减少GPU等待数据的时间(对于大数据集尤其重要)。

4. 前馈神经网络模型设计:从公式到代码

前馈神经网络,也叫多层感知机(MLP),是深度学习中最基础的架构。对于波士顿房价预测这个回归任务,我们的网络结构可以设计为:输入层(13个神经元) -> 隐藏层1(64个神经元) -> 隐藏层2(32个神经元) -> 输出层(1个神经元)。

4.1 模型类定义与层结构

在PyTorch中,我们通过继承torch.nn.Module类来定义自己的模型。

import torch.nn as nn class BostonPricePredictor(nn.Module): def __init__(self, input_dim): super(BostonPricePredictor, self).__init__() # 定义网络层 self.fc1 = nn.Linear(input_dim, 64) # 全连接层1: 13 -> 64 self.fc2 = nn.Linear(64, 32) # 全连接层2: 64 -> 32 self.fc3 = nn.Linear(32, 1) # 输出层: 32 -> 1 (房价) # 定义激活函数和Dropout层(用于防止过拟合,可选) self.relu = nn.ReLU() self.dropout = nn.Dropout(p=0.2) # 以20%的概率随机丢弃神经元 def forward(self, x): # 定义前向传播路径 out = self.fc1(x) out = self.relu(out) out = self.dropout(out) # 通常在激活函数后加Dropout out = self.fc2(out) out = self.relu(out) # 第二个隐藏层后也可以加Dropout,这里省略了 out = self.fc3(out) # 输出层不使用激活函数,因为回归任务直接输出数值 return out

关键点解析

  • nn.Linear:全连接层,实现y = xA^T + b的线性变换。你需要指定输入特征数和输出特征数。
  • nn.ReLU:修正线性单元激活函数,引入非线性,使网络能够学习复杂的模式。公式为f(x) = max(0, x)
  • nn.Dropout:在训练过程中,随机将一部分神经元的输出置零。这是一种正则化技术,可以防止网络对某些特定的神经元产生过度的依赖,从而减轻过拟合。注意:Dropout只在训练时启用,在模型评估(验证/测试)时需要关闭。PyTorch的model.eval()模式会自动处理这一点。
  • forward方法:定义了数据从输入到输出的计算图。必须重写此方法。

4.2 为什么选择这样的结构?

  • 隐藏层维度:从64到32是常见的做法,逐层压缩特征信息。初始维度不宜过小(如8),否则模型容量不足;也不宜过大(如512),对于这个小数据集容易过拟合。64和32是一个经验性的平衡点。
  • 激活函数:隐藏层使用ReLU,因为它计算高效,能缓解梯度消失问题,在实践中效果通常很好。
  • 输出层:回归任务的输出层通常不使用激活函数,因为我们希望输出任何实数范围内的值。如果使用了Sigmoid或Tanh,输出会被限制在(0,1)或(-1,1)内,这与房价范围不符。
  • Dropout:在第一个隐藏层后添加Dropout是一种有效的正则化手段。概率p=0.2意味着每次前向传播时,该层有20%的神经元会被随机屏蔽。

5. 训练循环的构建:损失、优化与迭代

模型定义好后,我们需要定义如何衡量它的好坏(损失函数),以及如何让它变得更好(优化器)。

5.1 初始化模型、损失函数与优化器

# 初始化模型 input_dim = X_train_tensor.shape[1] # 13 model = BostonPricePredictor(input_dim) # 定义损失函数 - 均方误差损失,回归任务最常用的损失函数 criterion = nn.MSELoss() # 定义优化器 - Adam优化器,自适应学习率,通常比SGD收敛更快更稳 optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 学习率是一个超参数 # 如果有GPU,将模型和数据移动到GPU上 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) print(f"使用设备: {device}")

为什么是MSE和Adam?

  • MSE(均方误差):计算预测值与真实值之差的平方的平均值。它对大的误差给予更大的惩罚,这使得优化过程更关注于减少那些预测得特别不准的样本的影响,非常符合回归任务的目标。
  • Adam优化器:它结合了动量(Momentum)和自适应学习率(RMSProp)的优点。对于大多数问题,Adam的默认参数就能工作得很好,不需要像SGD那样精细地调整学习率和动量参数,对初学者更友好。

5.2 完整的训练与验证循环

训练是一个迭代的过程:前向传播计算预测和损失 -> 反向传播计算梯度 -> 优化器更新模型参数。

num_epochs = 500 # 遍历整个训练集的次数 train_losses = [] val_losses = [] for epoch in range(num_epochs): # -------------------- 训练阶段 -------------------- model.train() # 设置为训练模式(启用Dropout等) running_train_loss = 0.0 for batch_features, batch_targets in train_loader: # 将数据移动到设备(CPU/GPU) batch_features, batch_targets = batch_features.to(device), batch_targets.to(device) # 前向传播 predictions = model(batch_features) loss = criterion(predictions, batch_targets) # 反向传播与优化 optimizer.zero_grad() # 清空上一轮的梯度,非常重要! loss.backward() # 反向传播,计算梯度 optimizer.step() # 根据梯度更新参数 running_train_loss += loss.item() * batch_features.size(0) epoch_train_loss = running_train_loss / len(train_loader.dataset) train_losses.append(epoch_train_loss) # -------------------- 验证阶段 -------------------- model.eval() # 设置为评估模式(禁用Dropout等) running_val_loss = 0.0 with torch.no_grad(): # 在此上下文中不计算梯度,节省内存和计算 for batch_features, batch_targets in test_loader: batch_features, batch_targets = batch_features.to(device), batch_targets.to(device) predictions = model(batch_features) loss = criterion(predictions, batch_targets) running_val_loss += loss.item() * batch_features.size(0) epoch_val_loss = running_val_loss / len(test_loader.dataset) val_losses.append(epoch_val_loss) # 每50个epoch打印一次日志 if (epoch + 1) % 50 == 0: print(f'Epoch [{epoch+1:04d}/{num_epochs}], Train Loss: {epoch_train_loss:.4f}, Val Loss: {epoch_val_loss:.4f}')

几个必须注意的细节

  1. optimizer.zero_grad()千万不能省略!PyTorch的梯度是累加的。如果不清零,下一次loss.backward()时,梯度会与上一次的梯度相加,导致更新方向错误。
  2. model.train()model.eval():这两个模式主要影响DropoutBatchNorm等层的行为。训练时必须用train(),评估时必须用eval()
  3. with torch.no_grad():在验证和测试时,我们不需要计算梯度(因为不更新参数)。使用这个上下文管理器可以显著减少内存消耗并加速计算。
  4. 损失计算:我们在每个batch里累加的是loss.item() * batch_size,最后除以数据集总大小来得到平均损失。这是因为loss是当前这个batch的平均损失。

6. 模型评估、可视化与结果分析

训练完成后,我们不能只看最后的损失值,还需要多维度地评估模型性能,并可视化训练过程来诊断模型行为。

6.1 绘制训练与验证损失曲线

损失曲线是观察模型学习状态的“心电图”。

plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_losses, label='Training Loss', color='blue', linewidth=2) plt.plot(range(1, num_epochs+1), val_losses, label='Validation Loss', color='red', linestyle='--', linewidth=2) plt.xlabel('Epoch') plt.ylabel('Mean Squared Error Loss') plt.title('Training and Validation Loss over Epochs') plt.legend() plt.grid(True, linestyle='--', alpha=0.7) plt.show()

如何解读损失曲线?

  • 理想情况:训练损失和验证损失都平稳下降,并最终收敛到一个较低的值,且两者之间差距很小。这说明模型学习良好,没有过拟合或欠拟合。
  • 过拟合:训练损失持续下降,但验证损失在某个点后开始上升或停滞。这意味着模型记住了训练数据的噪声,而非一般规律。解决方案包括:增加Dropout比率、使用更小的网络、获取更多数据、使用更强的数据增强(对于图像等)或早停(Early Stopping)。
  • 欠拟合:训练损失和验证损失都很高,且下降缓慢或很早就停滞了。这说明模型复杂度不够,无法捕捉数据中的模式。解决方案包括:增加网络层数或神经元数量、训练更多轮次、减少正则化强度。

6.2 在测试集上进行最终评估

我们用训练好的模型在整个测试集上做一次前向传播,计算几个关键的回归评估指标。

model.eval() all_predictions = [] all_targets = [] with torch.no_grad(): for batch_features, batch_targets in test_loader: batch_features, batch_targets = batch_features.to(device), batch_targets.to(device) predictions = model(batch_features) all_predictions.append(predictions.cpu()) all_targets.append(batch_targets.cpu()) # 合并所有batch的结果 all_predictions = torch.cat(all_predictions, dim=0).numpy() all_targets = torch.cat(all_targets, dim=0).numpy() # 计算评估指标 from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score mse = mean_squared_error(all_targets, all_predictions) rmse = np.sqrt(mse) # 均方根误差,与目标值单位一致,更直观 mae = mean_absolute_error(all_targets, all_predictions) # 平均绝对误差,对异常值不敏感 r2 = r2_score(all_targets, all_predictions) # 决定系数,越接近1越好 print(f"测试集评估结果:") print(f" 均方误差 (MSE): {mse:.2f}") print(f" 均方根误差 (RMSE): {rmse:.2f} (千美元)") # 波士顿房价单位是千美元 print(f" 平均绝对误差 (MAE): {mae:.2f} (千美元)") print(f" 决定系数 (R² Score): {r2:.4f}")

指标解读

  • RMSE:大约是预测房价的平均误差(单位:千美元)。例如,RMSE=4.5意味着平均预测误差约为4500美元。这是最直观的指标。
  • MAE:另一个平均误差指标,它对极端预测误差的惩罚小于RMSE。
  • :表示模型对目标变量方差的解释比例。0.75意味着模型解释了房价75%的波动。对于波士顿房价数据集,R²达到0.8以上通常就算是一个不错的模型了。

6.3 可视化预测值与真实值的对比

散点图可以直观地看出模型的预测效果。

plt.figure(figsize=(8, 8)) plt.scatter(all_targets, all_predictions, alpha=0.6, edgecolors='k') # 绘制理想预测线(y=x) min_val = min(all_targets.min(), all_predictions.min()) max_val = max(all_targets.max(), all_predictions.max()) plt.plot([min_val, max_val], [min_val, max_val], 'r--', lw=2, label='Perfect Prediction') plt.xlabel('True House Price (MEDV)') plt.ylabel('Predicted House Price (MEDV)') plt.title('True vs. Predicted House Prices on Test Set') plt.legend() plt.grid(True, linestyle='--', alpha=0.5) plt.show()

如果点紧密分布在红色对角线附近,说明预测准确。如果点呈现明显的系统性偏离(如整体偏高或偏低),或者有特殊的分布模式,则说明模型存在某种偏差。

7. 模型优化与超参数调优实战

我们构建的第一个模型只是一个基线。要提升性能,需要进行系统的调优。超参数调优有点像“炼丹”,但有其科学方法。

7.1 构建一个更灵活的模型类

为了方便实验不同的网络结构,我们改进一下模型类,使其可以通过参数配置。

class FlexibleBostonPredictor(nn.Module): def __init__(self, input_dim, hidden_dims=[64, 32], dropout_rate=0.2): super(FlexibleBostonPredictor, self).__init__() layers = [] prev_dim = input_dim # 动态构建隐藏层 for i, hidden_dim in enumerate(hidden_dims): layers.append(nn.Linear(prev_dim, hidden_dim)) layers.append(nn.ReLU()) layers.append(nn.Dropout(dropout_rate)) prev_dim = hidden_dim # 输出层 layers.append(nn.Linear(prev_dim, 1)) # 将层序列包装成 Sequential self.network = nn.Sequential(*layers) def forward(self, x): return self.network(x)

7.2 尝试不同的超参数组合

我们可以手动尝试几组不同的超参数,观察效果。

def train_and_evaluate(config, X_train_t, y_train_t, X_val_t, y_val_t): """训练并评估给定配置的模型""" hidden_dims = config['hidden_dims'] dropout = config['dropout'] lr = config['lr'] batch_size = config['batch_size'] epochs = config['epochs'] # 创建DataLoader train_dataset = BostonHousingDataset(X_train_t, y_train_t) val_dataset = BostonHousingDataset(X_val_t, y_val_t) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False) # 初始化模型、损失、优化器 model = FlexibleBostonPredictor(input_dim, hidden_dims=hidden_dims, dropout_rate=dropout).to(device) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=lr) # 简化的训练循环(省略详细日志) for epoch in range(epochs): model.train() for feats, targs in train_loader: feats, targs = feats.to(device), targs.to(device) optimizer.zero_grad() loss = criterion(model(feats), targs) loss.backward() optimizer.step() # 在验证集上评估 model.eval() val_loss = 0.0 with torch.no_grad(): for feats, targs in val_loader: feats, targs = feats.to(device), targs.to(device) val_loss += criterion(model(feats), targs).item() * feats.size(0) avg_val_loss = val_loss / len(val_loader.dataset) return avg_val_loss, model # 假设我们从训练集中再分出一个验证集用于调参 from sklearn.model_selection import train_test_split indices = np.arange(len(X_train_scaled)) X_train_sub, X_val, y_train_sub, y_val = train_test_split(X_train_scaled, y_train, test_size=0.2, random_state=42) # ... 转换为Tensor ... # 定义几组不同的配置 configs = [ {'hidden_dims': [64, 32], 'dropout': 0.2, 'lr': 0.001, 'batch_size': 16, 'epochs': 300}, {'hidden_dims': [128, 64, 32], 'dropout': 0.3, 'lr': 0.0005, 'batch_size': 32, 'epochs': 400}, {'hidden_dims': [256, 128], 'dropout': 0.1, 'lr': 0.01, 'batch_size': 64, 'epochs': 200}, ] best_loss = float('inf') best_model = None best_config = None for config in configs: print(f"尝试配置: {config}") val_loss, model = train_and_evaluate(config, X_train_sub_tensor, y_train_sub_tensor, X_val_tensor, y_val_tensor) print(f" 验证集损失: {val_loss:.4f}\n") if val_loss < best_loss: best_loss = val_loss best_model = model best_config = config print(f"最佳配置: {best_config}") print(f"最佳验证损失: {best_loss:.4f}")

7.3 使用学习率调度器

固定学习率可能不是最优的。我们可以在训练中动态调整学习率,例如当验证损失不再下降时降低学习率。

from torch.optim.lr_scheduler import ReduceLROnPlateau # 在训练循环中,初始化优化器后,添加调度器 optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # 初始学习率可以设大一点 scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=20, verbose=True) # 参数说明:监控指标(验证损失)在20个epoch内没有下降('min'模式)时,将学习率乘以0.5。 # 在每个epoch的验证阶段结束后,更新调度器 # ... 计算 epoch_val_loss ... scheduler.step(epoch_val_loss) # 将当前验证损失传给调度器

学习率调度器是一种“早停”的温和形式,它不是在性能停滞时完全停止训练,而是降低学习率,让模型在参数空间中进行更精细的搜索,往往能获得更好的最终性能。

8. 项目总结与扩展思考

通过这个项目,我们完整地走了一遍基于PyTorch的深度学习项目流程:从环境配置、数据加载与预处理、模型定义、训练循环构建、到最终的评估与可视化。这个流程是具有高度通用性的模板。

几个值得深入思考和扩展的方向:

  1. 特征工程:我们直接使用了原始的13个特征。你可以尝试:

    • 创建新的特征,例如房间数与年龄的比率。
    • 使用领域知识选择更重要的特征。
    • 尝试不同的标准化方法(如MinMaxScaler)或分位数转换。
    • 使用sklearn.feature_selection中的方法进行特征选择。更好的特征往往比更复杂的模型带来更大的提升。
  2. 模型架构探索

    • 尝试更深的网络(如4-5层)或更宽的网络(每层更多神经元)。
    • 引入批归一化层(nn.BatchNorm1d),它通常能加速训练并提升模型稳定性。
    • 尝试不同的激活函数,如LeakyReLU、ELU等。
    • 对于结构化数据,可以探索树模型(如LightGBM、XGBoost)或TabNet等专门架构,并与简单的MLP对比。
  3. 正则化技术

    • 调整Dropout比率。
    • 在优化器中加入权重衰减(L2正则化),torch.optim.Adam(..., weight_decay=1e-4)
    • 使用早停(Early Stopping),在验证损失不再改善时停止训练,防止过拟合。
  4. 超参数自动化调优

    • 使用optunaRay TunesklearnGridSearchCV(配合PyTorch包装器如skorch)进行系统的超参数搜索,而不是手动尝试。
  5. 部署与推理

    • 将训练好的模型保存下来:torch.save(model.state_dict(), 'boston_model.pth')
    • 学习如何加载模型并进行单样本或批量预测,这更接近真实的应用场景。

这个波士顿房价预测项目就像一块“敲门砖”,它涉及的每一个环节——数据管道、模型定义、训练逻辑、评估指标——都是构建更复杂AI系统的基石。理解了这个流程,当你面对图像分类、机器翻译等任务时,你会发现核心框架惊人地相似,只是DatasetModelLoss的具体内容发生了变化。动手把这个项目做一遍,调试通,并尝试上述的一两个扩展点,你对PyTorch和深度学习工作流的理解会上一个坚实的台阶。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/28 7:47:30

Gomoon:桌面端原生大模型协作者,重塑本地工作流效率

简介&#xff1a;大模型本地化部署正从‘能跑’迈向‘好用’阶段&#xff0c;其核心挑战在于如何在保障隐私与低延迟前提下&#xff0c;深度融入操作系统级工作流。桌面端大模型工具需突破Web沙盒限制&#xff0c;实现文件系统直读、剪贴板监听、Shell语义解析等原生能力&#…

作者头像 李华
网站建设 2026/8/28 7:47:18

YOLO 户外配电变压器检测实战|2994 张 VOC+YOLO 双格式数据集,远距离小目标增强、无人机配网巡检全流程落地

目录 一、前言 二、2994 张配电变压器数据集完整参数与样本解析 2.1 数据集基础完整信息 2.2 数据集配网巡检专属优势 2.3 数据集固有短板与配套涨点优化方案 三、户外变压器多尺度涨点核心实现原理 四、三大配网巡检落地应用案例 案例 1 乡村 10kV 线路无人机全域盘点…

作者头像 李华
网站建设 2026/8/28 7:46:01

用Rust构建高效LLM推理引擎:GGUF解析与性能优化

之前在做本地大模型相关项目时&#xff0c;我一直在思考一个问题&#xff1a;llama.cpp 已经把 GGUF 格式和 CPU/GPU 推理做到了很成熟的程度&#xff0c;为什么还要用 Rust 再实现一套推理引擎&#xff1f;答案其实不复杂——Rust 能带来内存安全、部署成本和生态整合上的优势…

作者头像 李华
网站建设 2026/8/28 7:45:47

windows 驱动实例分析系列: libusb驱动分析-examples篇(下)

实现篇&#xff08;基于 examples 目录&#xff09;—— 技术细节、编译与使用1. 编程接口与 API 使用详解1.1 初始化与设备枚举所有示例均遵循 libusb-win32 标准流程&#xff1a;usb_init(); usb_find_busses(); usb_find_devices();usb_init 初始化内部状态&#xff0c;usb_…

作者头像 李华
网站建设 2026/8/28 7:45:46

RunSnack:用一条链接P2P直连共享GPU,告别云中转

AI 时代&#xff0c;最贵的硬件不是 CPU&#xff0c;而是 GPU。做深度学习的人都有过这种经历&#xff1a;本地显卡只有 8GB 显存&#xff0c;跑一个大模型推理就 OOM&#xff1b;公司机房有几张 A100&#xff0c;但申请流程写半天&#xff0c;审批要等一天&#xff1b;云厂商的…

作者头像 李华