PyTorch 神经网络从零搭建实战:基础概念与完整项目实现

摘要: 本文从神经网络基础概念出发,讲解 PyTorch 中 nn.Module 的使用方法,通过波士顿房价预测和手写数字识别两个完整实战项目,手把手教你搭建、训练、保存和加载神经网络模型。
适用读者:深度学习入门者 | 难度:入门 | 预计阅读时间:35 分钟


一、前言

PyTorch 提供了 torch.nn 模块来方便地构建和训练神经网络。本文将从最基础的概念讲起,通过两个完整的实战项目,帮助你掌握 PyTorch 神经网络开发的完整流程。

学习完本文后,你将能够:

  • 理解神经网络的核心组件(层、激活函数、损失函数、优化器)
  • 使用 nn.Module 自定义网络结构
  • 完成从数据加载到模型保存的完整训练流程
  • 加载已保存的模型进行推理

二、神经网络核心概念

2.1 什么是神经网络

神经网络是一种模仿生物神经系统结构的计算模型,由大量相互连接的"神经元"组成。每个神经元接收输入信号,经过加权求和与非线性变换后产生输出。

2.2 核心组件

组件 作用 PyTorch 对应
层(Layer) 对数据进行线性变换 nn.Linear, nn.Conv2d
激活函数 引入非线性 nn.ReLU, nn.Sigmoid
损失函数 衡量预测与真实值的差距 nn.MSELoss, nn.CrossEntropyLoss
优化器 更新模型参数 torch.optim.Adam, torch.optim.SGD

2.3 前向传播与反向传播

  • 前向传播(Forward):数据从输入层经过各隐藏层到输出层,得到预测结果
  • 反向传播(Backward):根据损失函数计算梯度,从输出层反向传播到输入层
  • 参数更新:优化器根据梯度更新模型参数

三、PyTorch 网络搭建基础

3.1 继承 nn.Module

所有自定义网络都需要继承 torch.nn.Module 类:

import torch
import torch.nn as nn

class MyNet(nn.Module):
    def __init__(self, n_input, n_output):
        super(MyNet, self).__init__()
        # 定义网络层
        self.hidden = nn.Linear(n_input, 100)
        self.predict = nn.Linear(100, n_output)

    def forward(self, x):
        # 定义前向传播逻辑
        out = self.hidden(x)
        out = torch.relu(out)  # 激活函数
        out = self.predict(out)
        return out

3.2 使用 Sequential 快速搭建

对于简单的层堆叠,可以使用 nn.Sequential

import torch.nn as nn

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Linear(256, 128),
    nn.ReLU(),
    nn.Linear(128, 10)
)

四、实战项目一:波士顿房价预测

这是一个经典的回归问题,我们将搭建一个简单的全连接网络来预测房价。

4.1 数据准备与网络定义

import torch
import numpy as np

# 读取数据
ff = open('data').readlines()
data = []
for item in ff:
    out = item.strip().split(' ')
    data.append(out)
data = np.array(data).astype(np.float32)

# 划分特征和标签
Y = data[:, -1]
X = data[:, 0:-1]

# 划分训练集和测试集
X_train = torch.tensor(X[0:496, ...], dtype=torch.float32)
Y_train = torch.tensor(Y[0:496, ...], dtype=torch.float32)
X_test = torch.tensor(X[496:, ...], dtype=torch.float32)
Y_test = torch.tensor(Y[496:, ...], dtype=torch.float32)

# 定义网络
class Net(torch.nn.Module):
    def __init__(self, n_feature, n_output):
        super(Net, self).__init__()
        self.hidden = torch.nn.Linear(n_feature, 100)
        self.predict = torch.nn.Linear(100, n_output)

    def forward(self, x):
        out = self.hidden(x)
        out = torch.relu(out)
        out = self.predict(out)
        return out

4.2 训练循环

net = Net(13, 1)
loss_func = torch.nn.MSELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.01)

for i in range(10000):
    # 前向传播
    pred = net.forward(X_train)
    pred = torch.squeeze(pred)  # 去掉多余维度
    loss = loss_func(pred, Y_train) * 0.001

    # 反向传播与参数更新
    optimizer.zero_grad()  # 梯度归零
    loss.backward()        # 计算梯度
    optimizer.step()       # 更新参数

    if i % 1000 == 0:
        print('ite:{}, loss:{}'.format(i, loss.item()))

    # 测试集评估
    pred_test = net.forward(X_test)
    pred_test = torch.squeeze(pred_test)
    loss_test = loss_func(pred_test, Y_test) * 0.001

4.3 保存与加载模型

# 保存整个模型
torch.save(net, 'model.pkl')

# 加载模型
# net = torch.load('model.pkl')

# 推荐方式:只保存模型参数
torch.save(net.state_dict(), 'params.pkl')

# 加载参数
# net = Net(13, 1)
# net.load_state_dict(torch.load('params.pkl'))

五、实战项目二:手写数字识别(MNIST)

这是一个经典的分类问题,我们将使用 CNN(卷积神经网络)来识别手写数字。

5.1 数据加载

import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
import torch.utils.data as data_utils

# 加载 MNIST 数据集
train_data = dataset.MNIST(
    root='mnist',
    train=True,
    transform=transforms.ToTensor(),
    download=True
)

test_data = dataset.MNIST(
    root='mnist',
    train=False,
    transform=transforms.ToTensor(),
    download=False
)

# 创建数据加载器
train_loader = data_utils.DataLoader(
    dataset=train_data,
    batch_size=64,
    shuffle=True
)

test_loader = data_utils.DataLoader(
    dataset=test_data,
    batch_size=64,
    shuffle=True
)

5.2 定义 CNN 网络

class CNN(torch.nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 卷积层
        self.conv = torch.nn.Sequential(
            torch.nn.Conv2d(1, 32, kernel_size=5, padding=2),
            torch.nn.BatchNorm2d(32),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(2)
        )
        # 全连接层
        self.fc = torch.nn.Linear(14 * 14 * 32, 10)

    def forward(self, x):
        out = self.conv(x)
        # 将多维特征图展平为向量
        out = out.view(out.size()[0], -1)
        out = self.fc(out)
        return out

关键理解: 卷积层输出的是多维特征图(如 [batch_size, 32, 14, 14]),全连接层需要一维向量输入,所以需要用 view 将其展平为 [batch_size, 14*14*32]

5.3 训练模型

cnn = CNN()
cnn = cnn.cuda()  # 放到 GPU 上
loss_func = torch.nn.CrossEntropyLoss()  # 分类问题用交叉熵损失
optimizer = torch.optim.Adam(cnn.parameters(), lr=0.01)

for epoch in range(10):
    # 训练阶段
    for i, (images, labels) in enumerate(train_loader):
        images = images.cuda()
        labels = labels.cuda()

        outputs = cnn(images)
        loss = loss_func(outputs, labels)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    # 测试阶段
    accuracy = 0
    loss_test = 0
    for i, (images, labels) in enumerate(test_loader):
        images = images.cuda()
        labels = labels.cuda()
        outputs = cnn(images)

        loss_test += loss_func(outputs, labels)
        _, pred = outputs.max(1)  # 取概率最大的类别
        accuracy += (pred == labels).sum().item()

    accuracy = accuracy / len(test_data)
    loss_test = loss_test / (len(test_data) // 64)
    print('epoch:{}, accuracy:{}, loss:{}'.format(
        epoch + 1, accuracy, loss_test.item()))

5.4 保存并加载模型进行推理

# 保存模型
torch.save(cnn, 'mnist_cnn.pkl')

# 加载模型进行推理
cnn = torch.load('mnist_cnn.pkl', weights_only=False)
cnn = cnn.cuda()
cnn.eval()  # 切换到评估模式

accuracy = 0
for i, (images, labels) in enumerate(test_loader):
    images = images.cuda()
    labels = labels.cuda()
    outputs = cnn(images)
    _, pred = outputs.max(1)
    accuracy += (pred == labels).sum().item()

    # 可视化预测结果
    images = images.cpu().numpy()
    labels = labels.cpu().numpy()
    pred = pred.cpu().numpy()

    for idx in range(images.shape[0]):
        im_data = images[idx]
        im_label = labels[idx]
        im_pred = pred[idx]
        print('label:', im_label, 'pred:', im_pred)

accuracy = accuracy / len(test_data)
print('Final accuracy:', accuracy)

重要提示:

  1. 新版 PyTorch 中加载模型需要设置 weights_only=False
  2. 推理时要调用 model.eval() 切换到评估模式,这会影响 Dropout 和 BatchNorm 层的行为
  3. 加载模型时必须定义与训练时相同的网络类

六、训练流程总结

完整的 PyTorch 神经网络训练流程如下:

# 1. 导入库
import torch
import torch.nn as nn

# 2. 数据准备
# - 定义 Dataset 和 DataLoader
# - 数据预处理(归一化、增强等)

# 3. 定义网络
class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        # 定义层
    def forward(self, x):
        # 前向传播
        return x

# 4. 初始化
model = MyModel().cuda()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# 5. 训练循环
for epoch in range(num_epochs):
    model.train()  # 训练模式
    for batch in train_loader:
        inputs, labels = batch
        inputs, labels = inputs.cuda(), labels.cuda()

        outputs = model(inputs)      # 前向传播
        loss = criterion(outputs, labels)  # 计算损失
        optimizer.zero_grad()        # 梯度清零
        loss.backward()              # 反向传播
        optimizer.step()             # 更新参数

    # 6. 验证
    model.eval()  # 评估模式
    # ... 在验证集上评估

# 7. 保存模型
torch.save(model.state_dict(), 'model.pth')

关键注意事项

  1. 设备管理:使用 torch.device('cuda' if torch.cuda.is_available() else 'cpu') 确保 GPU 可用时加速训练
  2. 梯度清零:每次迭代前必须执行 optimizer.zero_grad() 防止梯度累积
  3. 模式切换:训练时 model.train(),评估时 model.eval()(影响 Dropout、BatchNorm 等层)
  4. 数据结构:处理数据时要清楚数据的维度关系,知道如何用 Tensor 和 NumPy 进行取值和转换

七、总结

本文要点回顾:

  1. nn.Module 是 PyTorch 神经网络的基础类,所有自定义网络都需要继承它
  2. 前向传播forward 方法中定义,反向传播 由 PyTorch 自动完成
  3. 训练五步:前向传播 → 计算损失 → 梯度清零 → 反向传播 → 更新参数
  4. 数据处理 要清楚数据结构(维度、格式),灵活使用 Tensor 和 NumPy 操作
  5. 模型保存与加载 推荐使用 state_dict() 方式,更灵活安全

掌握这些基础后,你就可以尝试更复杂的网络结构和任务了。


参考资料

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐