当前位置: 首页 > news >正文

PyTorch 神经网络模型构建与训练笔记(2)

一、核心工具回顾:nn.Module vs nn.functional

在正式构建模型前,需明确 PyTorch 中两个核心工具的区别 —— 二者均用于实现网络组件,但设计逻辑与适用场景不同,是后续模型构建的基础。

对比维度nn.Modulenn.functional
本质类(需实例化),继承自nn.Module纯函数(直接调用)
参数管理自动定义、管理weight/bias等可学习参数需手动定义、传入weight/bias,无自动管理
状态切换(如 Dropout)调用model.eval()后自动切换测试模式需手动传入training=True/False,无自动切换
兼容性可与nn.Sequential等模型容器结合无法与nn.Sequential结合,复用性差
适用场景卷积层(nn.Conv2d)、全连接层(nn.Linear)等带参数的组件激活函数(F.relu)、池化层(F.max_pool2d)等无参数的组件

二、三种模型构建方法

PyTorch 提供多种模型构建方式,可根据网络复杂度与灵活性需求选择,核心是 “层的组织与数据流定义”。

方法 1:直接继承 nn.Module 基类(最灵活)

适用于复杂网络(需自定义数据流逻辑),核心是重写__init__(定义层)与forward(定义前向传播数据流)两个方法。

代码示例(手写数字分类网络)

python

import torch
from torch import nn
import torch.nn.functional as F# 1. 定义模型类,继承nn.Module
class Model_Seq(nn.Module):def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):super(Model_Seq, self).__init__()  # 调用父类构造函数# 定义网络层(可学习参数由nn.Module自动管理)self.flatten = nn.Flatten()  # 展平层:将28×28图像转为784维向量self.linear1 = nn.Linear(in_dim, n_hidden_1)  # 全连接层1:输入→隐藏层1self.bn1 = nn.BatchNorm1d(n_hidden_1)  # 批量归一化层1:加速训练self.linear2 = nn.Linear(n_hidden_1, n_hidden_2)  # 全连接层2:隐藏层1→隐藏层2self.bn2 = nn.BatchNorm1d(n_hidden_2)  # 批量归一化层2self.out = nn.Linear(n_hidden_2, out_dim)  # 输出层:隐藏层2→10分类(0-9)# 2. 定义前向传播:数据如何流经各层def forward(self, x):x = self.flatten(x)  # 展平:[batch,1,28,28]→[batch,784]x = self.linear1(x)  # 全连接1x = self.bn1(x)      # 批量归一化1x = F.relu(x)        # 激活函数(用nn.functional)x = self.linear2(x)  # 全连接2x = self.bn2(x)      # 批量归一化2x = F.relu(x)        # 激活函数x = self.out(x)      # 输出层x = F.softmax(x, dim=1)  # 分类概率归一化(dim=1:按样本维度)return x# 3. 实例化模型(超参数:输入维度784=28×28,隐藏层1=300,隐藏层2=100,输出=10)
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)
print(model_seq)  # 打印模型结构,可查看各层参数
运行结果(模型结构)

plaintext

Model_Seq((flatten): Flatten(start_dim=1, end_dim=-1)(linear1): Linear(in_features=784, out_features=300, bias=True)(bn1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(linear2): Linear(in_features=300, out_features=100, bias=True)(bn2): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(out): Linear(in_features=100, out_features=10, bias=True)
)

方法 2:使用 nn.Sequential 按层顺序构建(最简单)

适用于层顺序固定、无复杂分支的网络(如简单全连接、基础 CNN),无需重写forward(自动按顺序执行),支持三种实现方式。

方式 1:可变参数法(无层名,简洁)

直接将层作为参数传入nn.Sequential,缺点是无法给层命名,不利于调试。

python

import torch
from torch import nn# 超参数(与方法1一致)
in_dim, n_hidden_1, n_hidden_2, out_dim = 28*28, 300, 100, 10# 按顺序定义层
Seq_arg = nn.Sequential(nn.Flatten(),          # 展平nn.Linear(in_dim, n_hidden_1),  # 全连接1nn.BatchNorm1d(n_hidden_1),     # 批量归一化1nn.ReLU(),             # 激活函数(用nn.Module的ReLU类)nn.Linear(n_hidden_1, n_hidden_2),  # 全连接2nn.BatchNorm1d(n_hidden_2),     # 批量归一化2nn.ReLU(),             # 激活函数nn.Linear(n_hidden_2, out_dim), # 输出层nn.Softmax(dim=1)      # 概率归一化
)print(Seq_arg)  # 层名默认以0、1、2...编号
方式 2:add_module 法(手动指定层名)

通过add_module("层名", 层实例)手动添加层,可自定义层名,便于后续访问特定层。

python

Seq_module = nn.Sequential()
# 逐个添加层并命名
Seq_module.add_module("flatten", nn.Flatten())
Seq_module.add_module("linear1", nn.Linear(in_dim, n_hidden_1))
Seq_module.add_module("bn1", nn.BatchNorm1d(n_hidden_1))
Seq_module.add_module("relu1", nn.ReLU())
Seq_module.add_module("linear2", nn.Linear(n_hidden_1, n_hidden_2))
Seq_module.add_module("bn2", nn.BatchNorm1d(n_hidden_2))
Seq_module.add_module("relu2", nn.ReLU())
Seq_module.add_module("out", nn.Linear(n_hidden_2, out_dim))
Seq_module.add_module("softmax", nn.Softmax(dim=1))print(Seq_module)  # 层名与自定义一致
方式 3:OrderedDict 法(有序字典指定层名)

借助collections.OrderedDict(有序字典),一次性定义 “层名 - 层实例” 映射,既有序又有命名。

python

from collections import OrderedDict# 用OrderedDict包装层名与层实例
Seq_ordered = nn.Sequential(OrderedDict([("flatten", nn.Flatten()),("linear1", nn.Linear(in_dim, n_hidden_1)),("bn1", nn.BatchNorm1d(n_hidden_1)),("relu1", nn.ReLU()),("linear2", nn.Linear(n_hidden_1, n_hidden_2)),("bn2", nn.BatchNorm1d(n_hidden_2)),("relu2", nn.ReLU()),("out", nn.Linear(n_hidden_2, out_dim)),("softmax", nn.Softmax(dim=1))
]))print(Seq_ordered)  # 效果与add_module一致

方法 3:继承 nn.Module + 模型容器(灵活 + 模块化)

适用于复杂网络(含分支、多模块) ,通过nn.Sequential/nn.ModuleList/nn.ModuleDict等容器将层 “模块化打包”,提升代码可读性。

容器 1:nn.Sequential(模块内顺序固定)

将多个层打包为一个 “子模块”,子模块内自动按顺序执行。

python

class Model_lay(nn.Module):def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):super(Model_lay, self).__init__()self.flatten = nn.Flatten()# 用nn.Sequential打包“全连接+批量归一化”为子模块self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1))self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.BatchNorm1d(n_hidden_2))self.out = nn.Sequential(nn.Linear(n_hidden_2, out_dim))def forward(self, x):x = self.flatten(x)x = F.relu(self.layer1(x))  # 子模块1 + 激活x = F.relu(self.layer2(x))  # 子模块2 + 激活x = F.softmax(self.out(x), dim=1)  # 输出子模块 + 概率归一化return x# 实例化
model_lay = Model_lay(28*28, 300, 100, 10)
print(model_lay)  # 子模块内的层以0、1编号
容器 2:nn.ModuleList(模块列表,灵活遍历)

将层存储为 “列表”,需在forward中手动循环遍历执行,适用于层数量不确定的场景。

python

class Model_lst(nn.Module):def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):super(Model_lst, self).__init__()# 用nn.ModuleList包装层列表self.layers = nn.ModuleList([nn.Flatten(),nn.Linear(in_dim, n_hidden_1),nn.BatchNorm1d(n_hidden_1),nn.ReLU(),nn.Linear(n_hidden_1, n_hidden_2),nn.BatchNorm1d(n_hidden_2),nn.ReLU(),nn.Linear(n_hidden_2, out_dim),nn.Softmax(dim=1)])def forward(self, x):# 手动循环遍历层列表for layer in self.layers:x = layer(x)return x# 实例化
model_lst = Model_lst(28*28, 300, 100, 10)
print(model_lst)  # 层以列表索引0-8编号
容器 3:nn.ModuleDict(模块字典,按键调用)

将层存储为 “字典”(键 = 层名,值 = 层实例),需在forward中按预设顺序调用,适用于需动态选择层的场景。

python

class Model_dict(nn.Module):def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):super(Model_dict, self).__init__()# 用nn.ModuleDict包装层字典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):# 预设层执行顺序(按键调用)layer_order = ["flatten", "linear1", "bn1", "relu", "linear2", "bn2", "relu", "out", "softmax"]for layer_name in layer_order:x = self.layers_dict[layer_name](x)return x# 实例化
model_dict = Model_dict(28*28, 300, 100, 10)
print(model_dict)  # 层以字典键命名

三、自定义网络模块:残差块与 ResNet18

当基础层无法满足复杂网络需求时(如深层网络的梯度消失问题),需自定义模块。以残差块(Residual Block) 为例,其核心是 “残差连接”(输入直接加到输出),是 ResNet 系列网络的基础。

1. 基础残差块:RestNetBasicBlock(输入输出形状一致)

适用于输入与输出通道数、分辨率相同的场景,直接将输入x与卷积后的输出相加,缓解梯度消失。

python

import torch
import torch.nn as nn
import torch.nn.functional as Fclass RestNetBasicBlock(nn.Module):def __init__(self, in_channels, out_channels, stride):super(RestNetBasicBlock, self).__init__()# 残差块核心:2个3×3卷积+批量归一化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)    # 卷积1output = F.relu(self.bn1(output))  # 批量归一化1 + 激活output = self.conv2(output)    # 卷积2output = self.bn2(output)      # 批量归一化2return F.relu(x + output)  # 残差连接:输入x + 输出 → 激活

2. 下采样残差块:RestNetDownBlock(调整形状一致)

适用于输入与输出通道数 / 分辨率不同的场景,需通过 1×1 卷积(extra模块)调整输入x的形状,确保与输出可相加。

python

class RestNetDownBlock(nn.Module):def __init__(self, in_channels, out_channels, stride):super(RestNetDownBlock, self).__init__()# 主路径:2个3×3卷积(stride[0]控制分辨率下采样)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)  # 1×1卷积调整输入形状output = self.conv1(x)   # 主路径卷积1output = F.relu(self.bn1(output))  # 批量归一化1 + 激活output = self.conv2(output)   # 主路径卷积2output = self.bn2(output)     # 批量归一化2return F.relu(extra_x + output)  # 调整后的输入 + 主路径输出 → 激活

3. 组合残差块:ResNet18 网络

ResNet18 由 “1 个初始卷积层 + 4 个残差块组 + 平均池化 + 全连接层” 组成,通过堆叠上述两种残差块实现深层结构。

python

class RestNet18(nn.Module):def __init__(self):super(RestNet18, self).__init__()# 初始卷积层(将输入3通道→64通道,分辨率下采样)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个残差块组(每组含2个残差块)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))  # 下采样→128通道self.layer3 = nn.Sequential(RestNetDownBlock(128, 256, [2, 1]), RestNetBasicBlock(256, 256, 1))  # 下采样→256通道self.layer4 = nn.Sequential(RestNetDownBlock(256, 512, [2, 1]), RestNetBasicBlock(512, 512, 1))  # 下采样→512通道# 分类头(平均池化→展平→全连接)self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))  # 自适应池化:无论输入尺寸,输出1×1self.fc = nn.Linear(512, 10)  # 512通道→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)  # 展平:[batch,512,1,1]→[batch,512]out = self.fc(out)return out

四、模型训练的完整流程

构建完模型后,需通过 “数据加载→损失定义→优化器选择→训练 / 验证→可视化” 完成端到端训练,核心是 “前向传播算损失→反向传播求梯度→优化器更新参数” 的循环。

步骤 1:加载预处理数据集

使用torchvision.datasets加载公开数据集(如 MNIST、CIFAR-10),并通过DataLoader实现批量加载与预处理(如归一化、数据增强)。

python

import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader# 1. 预处理:ToTensor(转为张量)+ 归一化(均值=0.5,标准差=0.5)
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])# 2. 加载CIFAR-10数据集(训练集+测试集)
train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)# 3. 批量加载(shuffle=True:训练集打乱,提升泛化性)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=2)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=2)

步骤 2:定义损失函数与优化器

  • 损失函数:根据任务选择(分类任务用CrossEntropyLoss,回归任务用MSELoss)。
  • 优化器:常用SGD(随机梯度下降)或Adam,需指定学习率(lr)等超参数。

python

import torch.optim as optim# 1. 实例化模型(ResNet18)
model = RestNet18()
# 2. 定义损失函数(分类任务:交叉熵损失)
criterion = nn.CrossEntropyLoss()
# 3. 定义优化器(SGD,学习率0.001,动量0.9)
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

步骤 3:循环训练模型(Epoch 级 + Batch 级)

  • Epoch:遍历整个训练集一次为 1 个 Epoch。
  • Batch:每次训练用一个 Batch 的数据更新参数,减少内存占用与训练波动。

python

num_epochs = 10  # 总训练轮次for epoch in range(num_epochs):running_loss = 0.0  # 记录当前Epoch的累计损失# 切换为训练模式(启用Dropout、BatchNorm更新)model.train()# 遍历训练集的每个Batchfor i, data in enumerate(train_loader, 0):# 1. 读取Batch数据(输入图像x,标签labels)inputs, labels = data# 2. 梯度清零(避免累计梯度)optimizer.zero_grad()# 3. 前向传播:计算模型输出outputs = model(inputs)# 4. 计算损失loss = criterion(outputs, labels)# 5. 反向传播:求梯度loss.backward()# 6. 优化器更新参数optimizer.step()# 7. 记录损失running_loss += loss.item()# 每200个Batch打印一次损失if i % 200 == 199:print(f'[{epoch + 1}, {i + 1}] loss: {running_loss / 200:.3f}')running_loss = 0.0print('Finished Training')

步骤 4:循环测试 / 验证模型

训练后需在测试集上评估模型性能(如准确率),需切换为eval模式(禁用 Dropout、固定 BatchNorm 参数),并禁用梯度计算(节省内存)。

python

correct = 0  # 正确预测的样本数
total = 0    # 总样本数
# 切换为测试模式(禁用Dropout、BatchNorm不更新)
model.eval()# 禁用梯度计算(测试阶段无需求梯度)
with torch.no_grad():# 遍历测试集的每个Batchfor data in test_loader:images, labels = data# 前向传播计算输出outputs = model(images)# 取输出概率最大的类别作为预测结果_, predicted = torch.max(outputs.data, 1)# 累计总样本数与正确样本数total += labels.size(0)correct += (predicted == labels).sum().item()# 计算测试准确率
print(f'Accuracy of the network on the 10000 test images: {100 * correct / total} %')

步骤 5:可视化结果

通过matplotlib绘制 “训练损失曲线” 与 “测试准确率曲线”,直观观察模型训练趋势(如是否过拟合、是否收敛)。

python

import matplotlib.pyplot as plt
import numpy as np# 假设已记录每个Epoch的训练损失与测试准确率
train_losses = [0.8, 0.5, 0.3, 0.2, 0.15, 0.12, 0.1, 0.09, 0.08, 0.07]  # 示例数据
test_accs = [65, 75, 82, 85, 87, 89, 90, 91, 92, 92.5]  # 示例数据# 绘制训练损失曲线
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs+1), train_losses, 'b-', label='Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.legend()# 绘制测试准确率曲线
plt.subplot(1, 2, 2)
plt.plot(range(1, num_epochs+1), test_accs, 'r-', label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.title('Test Accuracy Curve')
plt.legend()plt.show()

五、总结

  1. 模型构建:PyTorch 提供三种核心方式,可根据复杂度选择 —— 简单网络用nn.Sequential,复杂网络用 “继承nn.Module+ 容器”,灵活度最高。
  2. 自定义模块:针对深层网络需求(如梯度消失),可通过自定义模块(如残差块)扩展基础组件,是构建 ResNet 等经典网络的关键。
  3. 训练流程:遵循 “数据加载→损失 / 优化器定义→训练→测试→可视化” 的标准化流程,核心是 “前向 - 反向 - 更新” 的参数迭代循环,需注意train()/eval()模式切换与梯度管理。
http://www.dtcms.com/a/398448.html

相关文章:

  • 某旅游学院网络安全项目:构建高效监控集中管理与巡检系统
  • 【开题答辩全过程】以 J2EE应用于母婴健康管理系统的开发与实施为例,包含答辩的问题和答案
  • 网站设计与制作公司中铁中基建设集团网站
  • 怎么样自己做百度网站做网站什么主题好做
  • es的java调用
  • Jenkins运维之路(初次调试共享库)
  • 离线下载npm包
  • 【UE5.6.1】UE5初学者教程学习笔记:编辑器操作 (1-7集)
  • 伊春seo公司seo网站页面诊断
  • Spring依赖注入:@Resource与@Autowired详解及避免空指针的最佳实践
  • 52Hz——FreeRTOS学习笔记——延时函数
  • 阿里巴巴做网站教程免费网站模板下载大全下载
  • 贪心算法之分数背包问题
  • LLMs之AgentDevP:FastGPT的简介、安装和使用方法、案例应用之详细攻略
  • 贪心算法之船舶装载问题
  • 面试_常见大厂面试题
  • 网站地图怎么建设wordpress文章页样式修改
  • 网站如何做交互热门seo推广排名稳定
  • 【第29话:路径规划】自动驾驶启发式搜索算法(A星搜索算法( A* 搜索算法))详解及代码举例说明
  • React学习教程,从入门到精通,React Router 语法知识点及使用方法详解(28)
  • Docker Compose 从入门到实践
  • D3.js 与数据可视化
  • RNA-seq分析之最佳cutoff(TCGA版)
  • 浏览器直接进入网站的注意事项钢筋网片价格
  • scrapy-redis项目:爬取某网站图书信息
  • (论文速读)DiffBlender:可组合和通用的多模态文本到图像扩散模型
  • 第三方网站测试工具:【Postman使用基础指南】
  • Pytest+requests进行接口自动化测试5.0(5种assert断言的封装 + pymysql)
  • C# MVC 模型绑定全解析:从基础机制到自定义绑定器实战指南
  • 企业网站网页设计专业的团队网站建设