PyTorch 神经网络构建与训练笔记

一、构建神经网络的核心方法

PyTorch 中构建模型主要基于nn.Module基类及模型容器,核心分为三种方式,均需结合forward方法定义正向传播逻辑。

(一)直接继承nn.Module基类构建模型

这是最灵活的模型构建方式,需手动定义所有网络层并编排传播顺序。

1. 实现步骤
  1. 定义模型类,继承nn.Module
  2. __init__方法中初始化网络层(如全连接层、批归一化层、展平层等);
  3. 重写forward方法,定义输入数据的流转路径(层的调用顺序 + 激活函数等)。
2. 代码示例

python

复制代码
import torch
from torch import nn
import torch.nn.functional as F

class Model_Seq(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(Model_Seq, self).__init__()
        self.flatten = nn.Flatten()  # 展平层,将28×28图像转为一维向量
        self.linear1 = nn.Linear(in_dim, n_hidden_1)  # 全连接层1
        self.bn1 = nn.BatchNorm1d(n_hidden_1)  # 批归一化层1
        self.linear2 = nn.Linear(n_hidden_1, n_hidden_2)  # 全连接层2
        self.bn2 = nn.BatchNorm1d(n_hidden_2)  # 批归一化层2
        self.out = nn.Linear(n_hidden_2, out_dim)  # 输出层

    def forward(self, x):
        x = self.flatten(x)
        x = self.linear1(x)
        x = self.bn1(x)
        x = F.relu(x)  # 激活函数(使用nn.functional)
        x = self.linear2(x)
        x = self.bn2(x)
        x = F.relu(x)
        x = self.out(x)
        x = F.softmax(x, dim=1)  # 输出概率分布
        return x

# 超参数赋值与模型实例化
in_dim, n_hidden_1, n_hidden_2, out_dim = 28*28, 300, 100, 10
model_seq = Model_Seq(in_dim, n_hidden_1, n_hidden_2, out_dim)
3. 运行特点

模型打印时会清晰展示各层结构及参数(如输入输出维度、批归一化参数等)。

(二)使用nn.Sequential按层顺序构建模型

nn.Sequential是有序的层容器,按传入顺序自动执行正向传播,代码更简洁,适合简单线性结构的模型。

1. 三种实现方式
方式 特点 代码示例片段
可变参数传入 无法为层指定名称,仅按顺序编号 Seq_arg = nn.Sequential(nn.Flatten(), nn.Linear(in_dim, n_hidden_1), ...)
add_module方法 可手动为每层指定名称,动态添加层 Seq_module.add_module("flatten", nn.Flatten())
OrderedDict传入 通过有序字典指定层名和层,结构更清晰 from collections import OrderedDict``Seq_dict = nn.Sequential(OrderedDict([("flatten", nn.Flatten()), ...]))
2. 运行特点

使用add_moduleOrderedDict时,打印模型会显示自定义的层名(如flattenlinear1),便于调试。

(三)继承nn.Module+ 模型容器构建模型

结合基类的灵活性与容器的结构化优势,常用容器包括nn.Sequentialnn.ModuleListnn.ModuleDict

1. 三种容器的用法对比
容器类型 核心特点 代码实现要点
nn.Sequential 层有序组合,自动执行正向传播 __init__中定义子容器(如self.layer1 = nn.Sequential(Linear, BatchNorm1d)),forward中调用子容器
nn.ModuleList 层的列表,需手动遍历执行 用列表初始化(self.layers = nn.ModuleList([Flatten(), Linear(), ...])),forward中循环调用每层
nn.ModuleDict 层的字典(键为层名),需指定执行顺序 用字典初始化(self.layers_dict = nn.ModuleDict({"flatten": Flatten(), ...})),forward中按列表指定的层名顺序调用
2. 示例:nn.ModuleDict实现

python

复制代码
class Model_dict(nn.Module):
    def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
        super(Model_dict, self).__init__()
        self.layers_dict = nn.ModuleDict({
            "flatten": nn.Flatten(),
            "linear1": nn.Linear(in_dim, n_hidden_1),
            "bn1": nn.BatchNorm1d(n_hidden_1),
            "relu": nn.ReLU(),
            "linear2": nn.Linear(n_hidden_1, n_hidden_2),
            "bn2": nn.BatchNorm1d(n_hidden_2),
            "out": nn.Linear(n_hidden_2, out_dim),
            "softmax": nn.Softmax(dim=1)
        })

    def forward(self, x):
        # 手动指定层的执行顺序
        layers = ["flatten", "linear1", "bn1", "relu", "linear2", "bn2", "relu", "out", "softmax"]
        for layer in layers:
            x = self.layers_dict[layer](x)
        return x

二、自定义网络模块(以残差块为例)

针对复杂网络(如 ResNet),需自定义基础模块,再组合成完整网络。残差块解决了深层网络梯度消失问题,核心是 "输入与输出相加" 的残差连接。

(一)两种核心残差块

1. 基础残差块(RestNetBasicBlock

适用场景:输入与输出的通道数、分辨率一致(无需维度调整)。

结构:2 个 3×3 卷积层 + 批归一化层 + ReLU 激活,最后将输入与卷积输出相加后再激活。

代码示例

python

复制代码
class RestNetBasicBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride):
        super(RestNetBasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, x):
        output = self.conv1(x)
        output = F.relu(self.bn1(output))
        output = self.conv2(output)
        output = self.bn2(output)
        return F.relu(x + output)  # 残差连接:输入x与输出相加
2. 下采样残差块(RestNetDownBlock

适用场景:输入与输出的通道数或分辨率不一致(需维度匹配)。

额外结构 :添加 1×1 卷积层(extra),调整输入的通道数和分辨率,确保与卷积输出可相加。

代码示例

python

复制代码
class RestNetDownBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride):
        super(RestNetDownBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride[0], padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride[1], padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)
        # 1×1卷积调整输入维度
        self.extra = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], padding=0),
            nn.BatchNorm2d(out_channels)
        )

    def forward(self, x):
        extra_x = self.extra(x)  # 调整输入维度
        output = self.conv1(x)
        output = F.relu(self.bn1(output))
        output = self.conv2(output)
        output = self.bn2(output)
        return F.relu(extra_x + output)  # 调整后的输入与输出相加

(二)组合残差块构建 ResNet18

ResNet18 由 "初始卷积层 + 4 个残差层 + 池化层 + 全连接层" 组成,每个残差层含多个残差块。

1. 网络结构代码

python

复制代码
class RestNet18(nn.Module):
    def __init__(self):
        super(RestNet18, self).__init__()
        # 初始卷积与池化
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.bn1 = nn.BatchNorm2d(64)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # 4个残差层(基础块+下采样块组合)
        self.layer1 = nn.Sequential(RestNetBasicBlock(64, 64, 1), RestNetBasicBlock(64, 64, 1))
        self.layer2 = nn.Sequential(RestNetDownBlock(64, 128, [2, 1]), RestNetBasicBlock(128, 128, 1))
        self.layer3 = nn.Sequential(RestNetDownBlock(128, 256, [2, 1]), RestNetBasicBlock(256, 256, 1))
        self.layer4 = nn.Sequential(RestNetDownBlock(256, 512, [2, 1]), RestNetBasicBlock(512, 512, 1))
        # 自适应平均池化与全连接输出
        self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))
        self.fc = nn.Linear(512, 10)  # 10分类任务

    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.maxpool(out)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.avgpool(out)
        out = out.reshape(x.shape[0], -1)  # 展平为向量
        out = self.fc(out)
        return out

三、模型训练核心流程

模型构建完成后,需按固定流程进行训练与验证,共 6 个关键步骤:

  1. 加载预处理数据集 :加载已完成归一化、划分(训练集 / 测试集)的数据集,使用DataLoader实现批量加载。
  2. 定义损失函数 :根据任务类型选择(如分类用CrossEntropyLoss,回归用MSELoss),需实例化nn.XxxLoss
  3. 定义优化方法 :选择优化器(如 SGD、Adam),传入模型可学习参数与超参数(学习率、动量等),调用torch.optim库。
  4. 循环训练模型
    • 切换模型至训练模式(model.train());
    • 批量读取数据,执行正向传播(output = model(input));
    • 计算损失(loss = criterion(output, label));
    • 反向传播求梯度(loss.backward());
    • 优化器更新参数(optimizer.step()),清空梯度(optimizer.zero_grad())。
  5. 循环测试 / 验证模型
    • 切换模型至评估模式(model.eval());
    • 关闭梯度计算(with torch.no_grad():);
    • 计算验证集损失与准确率,评估模型泛化能力。
  6. 可视化结果:绘制训练 / 验证损失曲线、准确率曲线,分析模型收敛情况(是否过拟合 / 欠拟合)。
相关推荐
黎宇幻生21 分钟前
Java全栈学习笔记39
java·笔记·学习
老兵发新帖3 小时前
主流神经网络快速应用指南
人工智能·深度学习·神经网络
遇印记4 小时前
大二java学习笔记:二维数组
java·笔记·学习
bnsarocket5 小时前
Verilog和FPGA的自学笔记6——计数器(D触发器同步+异步方案)
笔记·fpga开发·verilog·自学·硬件编程
取酒鱼食--【余九】6 小时前
深度学习经典网络解析:ResNet
网络·人工智能·深度学习·神经网络·resnet·卷积神经网络·残差神经网络
LK_077 小时前
【Open3D】Ch.3:顶点法向量估计 | Python
开发语言·笔记·python
li星野7 小时前
打工人日报#20251011
笔记·程序人生·fpga开发·学习方法
摇滚侠7 小时前
Spring Boot 3零基础教程,yml配置文件,笔记13
spring boot·redis·笔记
QT 小鲜肉7 小时前
【个人成长笔记】在Ubuntu中的Linux系统安装 anaconda 及其相关终端命令行
linux·笔记·深度学习·学习·ubuntu·学习方法
QT 小鲜肉7 小时前
【个人成长笔记】在Ubuntu中的Linux系统安装实验室WIFI驱动安装(Driver for Linux RTL8188GU)
linux·笔记·学习·ubuntu·学习方法