Day 79:【99天精通Python】深度学习入门 (PyTorch) 下篇 - 搭建神经网络 (nn.Module)

前言

欢迎来到第79天!

在昨天的课程中,我们手动管理权重 wb,手动计算 Loss,手动更新梯度。这对于理解原理很有帮助,但如果要搭建一个几百层的神经网络,这样写非得累死。

PyTorch 提供了强大的 torch.nn 模块,它封装了各种神经网络层(全连接层、卷积层、循环层)和损失函数。我们只需要像搭积木一样把它们拼在一起,就能构建出复杂的深度学习模型。

本节内容:

  • nn.Module:神经网络的基类
  • 常用层:nn.Linear, nn.ReLU
  • 损失函数:nn.MSELoss, nn.CrossEntropyLoss
  • 优化器:torch.optim (SGD, Adam)
  • 实战练习:手写数字识别 (MNIST)

一、搭建神经网络:搭积木

在 PyTorch 中,所有的神经网络都必须继承 nn.Module 类。

1.1 定义模型结构

假设我们要解决昨天的线性回归问题 y = wx + b,这在神经网络中就是一个全连接层 (Linear Layer)

import torch
import torch.nn as nn

class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        # 定义层:输入特征1个,输出特征1个
        # y = w * x + b
        self.linear = nn.Linear(in_features=1, out_features=1)

    def forward(self, x):
        # 定义前向传播路径
        out = self.linear(x)
        return out

model = LinearRegressionModel()
print(model)

二、损失函数与优化器

2.1 损失函数 (Loss Function)

不需要手写 (y-y_pred)**2 了,直接调用:

  • 回归问题:nn.MSELoss() (均方误差)
  • 分类问题:nn.CrossEntropyLoss() (交叉熵)

2.2 优化器 (Optimizer)

不需要手写 w -= lr * w.grad 了,优化器帮我们管理所有参数的更新。

  • SGD (随机梯度下降):最基础,稳但慢。
  • Adam:自适应学习率,快且收敛好(推荐)。
import torch.optim as optim

criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

三、完整的训练循环 (重构昨日代码)

看看代码变得多么简洁:

x_train = torch.rand(100, 1) * 10
y_train = 2 * x_train + 5 + torch.randn(100, 1) * 0.5

# 1. 实例化模型、损失、优化器
model = LinearRegressionModel()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 2. 训练循环
for epoch in range(1000):
    # 前向
    y_pred = model(x_train)
    loss = criterion(y_pred, y_train)
    
    # 反向 (标准三步走)
    optimizer.zero_grad() # 清零
    loss.backward()       # 求导
    optimizer.step()      # 更新

    if epoch % 100 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}")

# 查看学习到的参数
# model.parameters() 是一个生成器
for name, param in model.named_parameters():
    print(name, param.item())

四、实战:手写数字识别 (MNIST)

这是深度学习界的"Hello World"。我们要训练一个网络,识别 0-9 的手写数字图片。

4.1 加载数据 (Torchvision)

PyTorch 提供了 torchvision 库,内置了常用数据集。

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# 数据预处理:转为 Tensor,并归一化
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,)) # MNIST的均值和标准差
])

# 下载数据集
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)

# 数据加载器 (自动分批次 Batch)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)

4.2 定义网络 (多层感知机 MLP)

图片是 28x28 像素的,我们要把它拉平成 784 个特征,然后通过几层全连接层,最后输出 10 个分类的概率。

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 定义层
        self.fc1 = nn.Linear(784, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 10) # 10分类
        self.relu = nn.ReLU()         # 激活函数

    def forward(self, x):
        # x.shape: [64, 1, 28, 28] -> 展平 -> [64, 784] 
        x = x.view(-1, 784) 
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x) # 最后一层不需要激活(CrossEntropyLoss会处理Softmax)
        return x

model = MLP()

4.3 训练与测试

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

optimizer = optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()

def train(epoch):
    model.train() # 切换到训练模式
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        
        if batch_idx % 100 == 0:
            print(f"Epoch {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}")

def test():
    model.eval() # 切换到评估模式
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += criterion(output, target).item()
            # 获取预测结果 (最大概率的索引)
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    print(f'\nTest set: Accuracy: {correct}/{len(test_loader.dataset)} ({100. * correct / len(test_loader.dataset):.2f}%)\n')

# 运行 3 个 Epoch
for epoch in range(1, 4):
    train(epoch)
    test()

运行只需几分钟,准确率通常能达到 98% 以上!


五、常见问题

Q1:model.train()model.eval() 有什么用?

有些层(如 Dropout, BatchNorm)在训练和测试时的行为是不一样的。

  • 训练时:Dropout 会随机丢弃神经元。
  • 测试时:Dropout 不工作,利用所有神经元。
    所以必须手动切换模式。

Q2:nn.ReLU 是什么?

激活函数。如果没有它,多少层网络叠加最后都等价于一层线性变换(线性代数原理)。激活函数引入了非线性,让神经网络能拟合任意复杂的曲线。

Q3:view(-1, 784) 是什么?

相当于 NumPy 的 reshape-1 表示自动推断 Batch Size。


六、小结

PyTorch 神经网络

构建模型 nn.Module

数据加载 DataLoader

训练管道

init: 定义层

forward: 定义流向

transforms (预处理)

batch_size (分批)

zero_grad (清零)

backward (求导)

step (更新)

关键要点

  1. nn.Module 是所有模型的父类。
  2. Optimizer 帮我们自动更新权重。
  3. DataLoader 帮我们自动分批处理数据。
  4. 三步走zero_grad -> backward -> step,背下来!

七、课后作业

  1. 保存模型:查阅 torch.savetorch.load,将训练好的 MNIST 模型保存到硬盘,并写一个新的脚本加载它进行预测。
  2. 卷积神经网络 (CNN):将 MLP 改为 CNN(使用 nn.Conv2dnn.MaxPool2d)。CNN 提取图片特征的能力更强,准确率应该能达到 99%。
  3. CIFAR-10:MNIST 是黑白的,尝试挑战 CIFAR-10 数据集(彩色图片分类,飞机、汽车、鸟等)。

下节预告

Day 80:项目篇 - 微信小程序后端开发 (上) - 深度学习有点烧脑,我们换个口味。明天开始,我们将开发一个微信小程序的后端 API,打通手机与服务器的连接。


系列导航

  • 上一篇:Day 78 - 深度学习PyTorch上
  • 下一篇:Day 80 - 小程序后端开发上(待更新)
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐