本文环境:

  • Pycharm 2025.1
  • Python 3.12.9
  • Pytorch 2.6.0+cu124

1 基本骨架

nn.Module 是 PyTorch 中所有神经网络模块的基类,用于构建和管理深度学习模型。无论是简单的线性层(nn.Linear)还是复杂的 CNN(如 ResNet),它们都继承自 nn.Module

  • 基类作用

    所有神经网络模块(层/模型)的基类,用于组织参数、定义前向传播。

  • 关键特性

    • 管理参数(如权重、偏置)和子模块(如卷积层、全连接层)。
    • 必须实现 forward() 方法定义计算逻辑。
    • 自动支持反向传播(通过 Autograd)。
image-20250521220044315

1.1 初始化

  • __init__()方法中初始化模型属性。

    class MyModel(nn.Module):
        def __init__(self):
            super().__init__()  # 必须调用父类初始化
            # 在这里定义模型层/参数
    

1.2 实现 forward() 方法

image-20250521222903170
  • forward()方法中实现数据流的计算逻辑。

  • 通过逐层运算完成输入到输出的映射。

        def forward(self, input):
            # 定义前向传播逻辑
            output = input + 1
            return output
    

1.3 使用模型

# 实例化模型
model = MyModel()

# 准备输入数据
x = torch.tensor(1)

# 调用模型(会自动调用forward方法)
output = model(x)  # 调用模型实例来触发前向传播

print(output)
image-20250521223354710

注意事项:

  1. 必须继承 nn.Module:所有自定义模型都应继承自 nn.Module
  2. 必须调用 super().__init__():在 __init__ 方法中初始化父类。
  3. 实现 forward 方法:定义模型的前向传播逻辑。
  4. 不要直接调用 forward:应该通过调用模型实例来触发前向传播。

2 卷积层(Convolution)

2.1 卷积操作

​ 卷积操作通过滑动一个称为卷积核(或滤波器)的小矩阵对输入数据进行局部加权求和。

​ 卷积操作的主要功能是提取局部特征:

  • 底层卷积提取边缘、颜色变化等低级特征。
  • 中层卷积提取纹理、图案等中级特征。
  • 高层卷积提取物体部件等高级特征。

​ 通过图像动画直观理解卷积的行为

​ (动画源:https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md)。

Pytorch 中的卷积层:https://docs.pytorch.org/docs/stable/nn.html#convolution-layers

卷积类型 输入维度 卷积核维度 典型应用场景
Conv1d (batch_size, channels, length) (out_channels, in_channels, kernel_size) 时序数据、文本、音频信号
Conv2d (batch_size, channels, height, width) (out_channels, in_channels, kernel_height, kernel_width) 图像处理、2D 特征图
Conv3d (batch_size, channels, depth, height, width) (out_channels, in_channels, kernel_depth, kernel_height, kernel_width) 视频处理、3D 医学图像

Conv2d 为例:

image-20250521225241574

Conv2d 对输入的多通道信号(如 RGB 图像)执行二维卷积操作,提取空间特征。其数学表达式为:
o u t ( N i , C o u t j ) = b i a s ( C o u t j ) + ∑ k = 0 C i n − 1 w e i g h t ( C o u t j , k ) ⋆ i n p u t ( N i , k ) \mathrm{out}(N_i,C_{\mathrm{out}_j})=\mathrm{bias}(C_{\mathrm{out}_j})+\sum_{k=0}^{C_{\mathrm{in}}-1}\mathrm{weight}(C_{\mathrm{out}_j},k)\star\mathrm{input}(N_i,k) out(Ni,Coutj)=bias(Coutj)+k=0Cin1weight(Coutj,k)input(Ni,k)
​ 其中:

  • ⋆ \star 表示 2D 互相关运算(实际实现中与卷积等价,但无需翻转核)。
  • 输入尺寸: ( N , C i n , H i n , W i n ) (N,C_{\mathrm{in}},H_{\mathrm{in}},W_{\mathrm{in}}) (N,Cin,Hin,Win) ( C i n , H i n , W i n ) (C_{\mathrm{in}},H_{\mathrm{in}},W_{\mathrm{in}}) (Cin,Hin,Win)
  • 输出尺寸: ( N , C o u t , H o u t , W o u t ) (N,C_{\mathrm{out}},H_{\mathrm{out}},W_{\mathrm{out}}) (N,Cout,Hout,Wout) ( C o u t , H o u t , W o u t ) (C_{\mathrm{out}},H_{\mathrm{out}},W_{\mathrm{out}}) (Cout,Hout,Wout)
  • 输出尺寸公式:

H o u t = ⌊ H i n + 2 × padding [ 0 ] − dilation [ 0 ] × ( kernel_size [ 0 ] − 1 ) − 1 stride [ 0 ] + 1 ⌋ W o u t = ⌊ W i n + 2 × padding [ 1 ] − dilation [ 1 ] × ( kernel _ size [ 1 ] − 1 ) − 1 stride [ 1 ] + 1 ⌋ \begin{aligned}H_{out}&=\left\lfloor\frac{H_{in}+2\times\text{padding}[0]-\text{dilation}[0]\times(\text{kernel\_size}[0]-1)-1}{\text{stride}[0]}+1\right\rfloor\\W_{out}&=\left\lfloor\frac{W_{in}+2\times\text{padding}[1]-\text{dilation}[1]\times(\text{kernel}\_\text{size}[1]-1)-1}{\text{stride}[1]}+1\right\rfloor\end{aligned} HoutWout=stride[0]Hin+2×padding[0]dilation[0]×(kernel_size[0]1)1+1=stride[1]Win+2×padding[1]dilation[1]×(kernel_size[1]1)1+1

参数 说明 示例值
in_channels 输入通道数(如 RGB 图像为 3) 3
out_channels 输出通道数(卷积核数量) 16
kernel_size 卷积核尺寸(整数或元组) 3(3, 5)
stride 滑动步长(控制输出尺寸缩减) 2(2, 1)
padding 填充像素数(保持输入输出尺寸一致) 1(0, 1)
padding_mode 填充模式('zeros''reflect''replicate' 'zeros'
dilation 空洞卷积的扩张率(增大感受野) 2
groups 分组卷积(groups=in_channels时为深度卷积) 1(标准卷积)
bias 是否添加可学习偏置 True

2.2 代码示例

  1. 数据加载

    • CIFAR-10 数据集:包含 10 类 32×32 彩色图像,共 60,000 张(50,000 训练 + 10,000 测试)。
    • transform=ToTensor():将 PIL 图像转换为 PyTorch 张量并归一化到 [0, 1] 范围。
    • DataLoader:实现批量加载 (batch_size=64) 和潜在的多进程数据加载。
    import torchvision
    from torch import nn
    from torch.utils.data import DataLoader
    from torch.utils.tensorboard import SummaryWriter
    from torchvision import transforms
    
    # 加载CIFAR10数据集
    dataset = torchvision.datasets.CIFAR10(
        root='./dataset',  # 数据集存放路径
        train=False,  # 是否为训练集
        download=True,  # 是否下载数据集
        transform=transforms.ToTensor()  # 数据预处理
    )
    
    # 加载数据集
    dataloader = DataLoader(dataset, batch_size=64)  # 每个批次加载64张图片
    
  2. 定义模型

    • 输入输出通道:3→9(CIFAR-10 是 RGB 三通道输入)。
    • 卷积核参数:3×3大小,步长 1,无填充 (padding=0)。
    • 输出尺寸计算:(32−3)/1+1=30,所以输出为 9 通道的 30×30 特征图。
    # 定义模型
    class MyModel(nn.Module):
        def __init__(self):
            # 调用父类的初始化方法
            super(MyModel, self).__init__()
            # 定义卷积层,输入通道数为3,输出通道数为9,卷积核大小为3,步长为1,填充为0
            self.conv1 = nn.Conv2d(3, 9, 3, stride=1, padding=0)
    
  3. 可视化

    • SummaryWriter:TensorBoard 日志记录工具。
    • reshape 操作:将 9 通道输出转换为 3 通道组(因为 TensorBoard 默认显示 3 通道图像)。
    • add_images:记录批次图像数据用于可视化对比。
    # 实例化模型
    model = MyModel()
    
    # 创建SummaryWriter对象,用于记录训练过程中的数据
    writer = SummaryWriter('logs')
    step = 0
    # 遍历数据集
    for data in dataloader:
        imgs, targets = data  # 获取图片和标签
        output = model(imgs).reshape((-1, 3, 30, 30))  # 将 9 通道输出转换为 3 通道组
        print(imgs.shape)  # 打印输入图片的形状
        print(output.shape)  # 打印输出图片的形状
    
        # 将输入图片和输出图片添加到SummaryWriter中
        writer.add_images('input', imgs, step)
        writer.add_images('output', output, step)
    
        step += 1  # 更新步数
    
    # 关闭SummaryWriter
    writer.close()
    

    数据流变化如下:

    输入: [64, 3, 32, 32] (batch, channel, height, width)
    ↓ 卷积(39通道)
    输出: [64, 9, 30, 30]
    ↓ reshape
    显示: [192, 3, 30, 30] (9通道分为33通道图像)
    

​ 可视化后的输入与输出结果如下,原始的输入图片被拆分为 3 张输出图片:

image-20250521231353863

3 池化层

3.1 池化操作

​ 池化是一种**降采样(downsampling)**操作,它通过滑动窗口在输入特征图上提取局部区域的特征值,然后根据池化类型(最大池化、平均池化等)输出该区域的代表值。

主要作用

  1. 降维:减少特征图的空间尺寸,降低后续层的计算负担。
  2. 特征不变性:增强模型对位置变化的鲁棒性(平移、旋转等)。
  3. 防止过拟合:通过减少参数数量,抑制模型对训练数据的过度拟合。
  4. 扩大感受野:使后续层能看到更广的输入区域。

常见类型

  • 最大池化(Max Pooling)

    最大池化选取窗口区域内的最大值作为输出,能够保留最显著的特征。

    image-20250524070124494
  • 平均池化(Average Pooling)

    计算窗口区域内的平均值,能保留整体特征但可能丢失显著特征。

    image-20250524070159999

​ 以 MaxPool2d 为例:

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

  • kernel_size:池化窗口大小,可以是整数(如 3 表示 3×3 窗口)或元组(如 (3, 2) 表示 3×2 窗口)。

  • stride:窗口移动步长,默认等于 kernel_size。若为元组(如 (2, 1)),则分别指定高度和宽度方向的步长。

  • padding:输入边缘的填充量(默认为 0),填充值为负无穷。可以是整数或元组。

  • dilation:控制窗口内元素的间距(默认为 1),用于扩大感受野。

  • return_indices:若为 True,返回最大值索引,用于后续反池化(如 MaxUnpool2d)。

  • ceil_mode:若为 True,输出尺寸向上取整;默认为 False(向下取整)。

  • 输入尺寸: ( N , C , H i n , W i n ) (N,C,H_{\mathrm{in}},W_{\mathrm{in}}) (N,C,Hin,Win) ( C , H i n , W i n ) (C,H_{\mathrm{in}},W_{\mathrm{in}}) (C,Hin,Win)

  • 输出尺寸: ( N , C , H o u t , W o u t ) (N,C,H_{\mathrm{out}},W_{\mathrm{out}}) (N,C,Hout,Wout) ( C , H o u t , W o u t ) (C,H_{\mathrm{out}},W_{\mathrm{out}}) (C,Hout,Wout)

3.2 代码示例

  1. 对张量池化。

    import torch
    import torchvision
    from torch import nn
    
    # 创建一个5x5的二维张量
    input = torch.tensor([[1, 2, 0, 3, 1],
                          [0, 1, 2, 3, 1],
                          [1, 2, 1, 0, 0],
                          [5, 2, 3, 1, 1],
                          [2, 1, 0, 1, 1]])
    # 将张量reshape为1x5x5的形状
    input = input.reshape((1, 5, 5))
    # 打印张量的形状
    print(input)
    
    # 定义一个自定义的模型类
    class MyModel(nn.Module):
        def __init__(self):
            super(MyModel, self).__init__()
            # 定义一个最大池化层,池化核大小为3,ceil_mode=True表示向上取整
            self.maxpool1 = nn.MaxPool2d(kernel_size=3, ceil_mode=True)
    
        def forward(self, x):
            # 对输入张量进行最大池化操作
            y = self.maxpool1(x)
            return y
    
    
    # 创建模型实例
    model = MyModel()
    # 对输入张量进行前向传播
    output = model(input)
    # 打印输出张量
    print(output)
    

    工作原理:

    • 输入 5×5 矩阵,池化核大小 3×3,步幅默认为 kernel_size (即 3)。

    • 由于ceil_mode=True,输出尺寸向上取整为 2×2。

    • 计算过程:在 3×3 窗口内取最大值,然后滑动窗口。

    image-20250524070618688
  2. 可视化池化操作。

    import torch
    import torchvision
    from torch import nn
    from torch.utils.data import DataLoader
    from torch.utils.tensorboard import SummaryWriter
    from torchvision import transforms
    
    # 加载CIFAR10数据集
    dataset = torchvision.datasets.CIFAR10(
        root='./dataset',  # 数据集的根目录
        train=False,  # 是否为训练集
        download=True,  # 是否下载数据集
        transform=transforms.ToTensor()  # 数据集的转换
    )
    
    # 创建数据加载器
    dataloader = DataLoader(dataset, batch_size=64)
    
    
    # 定义一个自定义的模型类
    class MyModel(nn.Module):
        def __init__(self):
            super(MyModel, self).__init__()
            # 定义一个最大池化层,池化核大小为3,ceil_mode=True表示向上取整
            self.maxpool1 = nn.MaxPool2d(kernel_size=3, ceil_mode=True)
    
        def forward(self, x):
            # 对输入张量进行最大池化操作
            y = self.maxpool1(x)
            return y
    
    
    # 创建模型实例
    model = MyModel()
    
    # 创建一个SummaryWriter对象,用于记录训练过程中的数据
    writer = SummaryWriter('logs')
    # 初始化step为0
    step = 0
    # 遍历dataloader中的数据
    for data in dataloader:
        # 获取输入图片和目标标签
        imgs, targets = data
        # 将输入图片添加到SummaryWriter中
        writer.add_images('input', imgs, step)
        # 使用模型对输入图片进行预测
        output = model(imgs)
        # 将预测结果添加到SummaryWriter中
        writer.add_images('output', output, step)
        # step加1
        step += 1
    
    # 关闭SummaryWriter
    writer.close()
    
    image-20250524071440861

4 非线性激活

​ 非线性激活函数是神经网络中引入非线性特性的核心组件,决定神经元是否被激活以及如何传递信息。

为什么需要非线性激活?

  1. 打破线性限制:没有激活函数时,多层网络等价于单层线性变换。
  2. 增强表达能力:可以逼近任意复杂函数(万能逼近定理)。
  3. 实现特征分层提取:从低级特征(边缘)到高级特征(物体)的逐步抽象。

4.1 ReLU

​ 数学表达式:
R e L U ( x ) = ( x ) + = max ⁡ ( 0 , x ) \mathrm{ReLU}(x)=(x)^+=\max(0,x) ReLU(x)=(x)+=max(0,x)
image-20250524073106726

  • 计算高效(无指数运算)。
  • 缓解梯度消失(正区间梯度恒为 1)。
  • 可能导致"神经元死亡"(负输入梯度为 0)。

4.2 Sigmoid

​ 数学表达式:
S i g m o i d ( x ) = σ ( x ) = 1 1 + exp ⁡ ( − x ) \mathrm{Sigmoid}(x)=\sigma(x)=\frac1{1+\exp(-x)} Sigmoid(x)=σ(x)=1+exp(x)1

  • 输出范围 (0, 1),适合概率输出。
  • 存在梯度消失问题(当 |x| 较大时梯度接近 0)。
  • 输出非零中心,可能影响梯度下降效率。
image-20250524073233683

4.3 代码实现

​ 以 Sigmoid 为例:

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

# 加载CIFAR10数据集
dataset = torchvision.datasets.CIFAR10(
    root='./dataset',  # 数据集的根目录
    train=False,  # 是否为训练集
    download=True,  # 是否下载数据集
    transform=transforms.ToTensor()  # 数据集的转换
)

# 创建数据加载器
dataloader = DataLoader(dataset, batch_size=64)

# 定义一个自定义的模型类
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.sigmoid1 = nn.Sigmoid()

    def forward(self, x):
        y = self.sigmoid1(x)  # 将输入x通过Sigmoid激活函数得到输出y
        return y


# 创建模型实例
model = MyModel()

# 创建一个SummaryWriter对象,用于记录训练过程中的数据
writer = SummaryWriter('logs')
# 初始化step为0
step = 0
# 遍历dataloader中的数据
for data in dataloader:
    # 获取输入图片和目标标签
    imgs, targets = data
    # 将输入图片添加到SummaryWriter中
    writer.add_images('input', imgs, step)
    # 使用模型对输入图片进行预测
    output = model(imgs)
    # 将预测结果添加到SummaryWriter中
    writer.add_images('output', output, step)
    # step加1
    step += 1

# 关闭SummaryWriter
writer.close()
image-20250524073450032

5 线性层

​ 线性层(又称全连接层,Fully Connected Layer)是神经网络中最基础的组件之一,其核心功能是通过线性变换将输入数据映射到输出空间。数学表达式为:
y = W x + b y=Wx+b y=Wx+b
其中:

  • x x x:输入向量,形状为 (batch_size, in_features)
  • W W W:权重矩阵,形状为 (out_features, in_features)
  • b b b:偏置向量,形状为 (out_features,)
  • y y y:输出向量,形状为 (batch_size, out_features)
image-20250524080119887

作用

  1. 特征变换:将高维输入(如图像展平后的像素值)映射到低维输出(如类别概率),实现特征降维或升维。
  2. 线性组合:通过权重矩阵 W W W 学习输入特征之间的线性关系,偏置 b b b 提供平移灵活性。
  3. 与其他层配合:通常与非线性激活函数(如 ReLU)结合,增强模型的表达能力。
特性 说明
权重共享 同一批次的所有样本共享相同的 W W W b b b
自动求导 PyTorch 自动计算梯度,支持反向传播优化 W W W b b b
多维输入支持 输入可以是任意维度,线性层仅对最后一维变换(如 (N,C,H,W)(N,C,H,out_features)

代码示例

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms


# 加载CIFAR10数据集
dataset = torchvision.datasets.CIFAR10(
    root='./dataset',  # 数据集的根目录
    train=False,  # 是否为训练集
    download=True,  # 是否下载数据集
    transform=transforms.ToTensor()  # 数据集的转换
)

# 创建数据加载器
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)


# 定义一个自定义的模型类
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.linear1 = nn.Linear(196608 // 64, 10)

    def forward(self, x):
        y = self.linear1(x)
        return y


# 创建模型实例
model = MyModel()

# 创建一个SummaryWriter对象,用于记录训练过程中的数据
writer = SummaryWriter('logs')
# 初始化step为0
step = 0
# 遍历dataloader中的数据
for data in dataloader:
    # 获取输入图片和目标标签
    imgs, targets = data
    # 将输入图片添加到SummaryWriter中
    writer.add_images('input', imgs, step)
    # 使用模型对输入图片进行预测
    output = model(imgs.reshape(64, 1, 1, -1))
    # 将预测结果添加到SummaryWriter中
    writer.add_images('output', output, step)
    # step加1
    step += 1

# 关闭SummaryWriter
writer.close()
image-20250524080619882

6 使用 Sequential

nn.Sequential 是 PyTorch 中的一个容器模块,用于按顺序组织多个子模块(如神经网络层)。它简化了模型的构建过程,适用于线性堆叠的网络结构。

  • 顺序执行nn.Sequential中的模块按定义的顺序依次执行,前一层的输出自动作为下一层的输入。
  • 简化代码:无需在 forward 方法中显式调用每一层,只需调用 Sequential 实例即可。

6.1 直接传入模块列表

​ 直接按顺序传入神经网络层,输入数据会依次通过这些层。

​ 模块按顺序编号(如 model1[0] 表示第一层)。

self.model1 = nn.Sequential(
    nn.Conv2d(3, 32, 5, padding=2),
    nn.MaxPool2d(2),
    nn.Conv2d(32, 32, 5, padding=2),
    # ... 其他层
)

print(model[0])   # 通过索引访问

6.2 使用 OrderedDict 命名层

​ 可通过名字访问特定层(如 model1.conv1)。

from collections import OrderedDict

self.model1 = nn.Sequential(OrderedDict([
    ('conv1', nn.Conv2d(3, 32, 5, padding=2)),
    ('pool1', nn.MaxPool2d(2)),
    # ... 其他层
]))

print(model.conv1)  # 通过名称访问

6.3 代码示例

​ 以 CIFAR10 模型为例:

image-20250527161255093

(1)输入 input 是 3 个通道的 32×32 大小的数据,该模型输入为 CIFAR10。
(2)经过一个 5×5 的 kernelsize 的卷积,变成 32 通道的 32×32 的大小。通道变成 32 是因为用了 32 个 3@5×5 的卷积核,每一个卷积核生成一个输出通道。输出通道数 32 表示卷积核的数量,每个卷积核会提取一种独特的特征(如边缘、纹理等)。32 个卷积核意味着生成 32 个不同的特征图,从而增强模型对输入特征的表达能力。
(3)经过一个最大池化,尺寸减半为 32 通道的 16×16 大小(最大池化不改变通道数)。
(4)经过一次卷积,通道数和尺寸不变,还是 32@16×16。
(5)经过一次最大池化,尺寸减半为 32 通道的 8×8 大小。
(6)经过一次卷积,通道数变为 64,尺寸不变。
(7)经过一次最大池化,尺寸减半为 64 通道的 4×4 大小。
(8)Flatten 展开为 64@4×4 大小的 64 通道的 1 行(4×4 方队变 1 行长队伍)。
(9)通过全连接层把输出变为 10(cifar10 有 10 个类)。

代码示例:

from collections import OrderedDict

import torch
from torch import nn
from torch.utils.tensorboard import SummaryWriter


class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.model1 = nn.Sequential(OrderedDict([
            ('conv1', nn.Conv2d(3, 32, 5, padding=2)),
            ('maxpool1', nn.MaxPool2d(2)),
            ('conv2', nn.Conv2d(32, 32, 5, padding=2)),
            ('maxpool2', nn.MaxPool2d(2)),
            ('conv3', nn.Conv2d(32, 64, 5, padding=2)),
            ('maxpool3', nn.MaxPool2d(2)),
            ('flatten', nn.Flatten()),
            ('linear1', nn.Linear(64 * 4 * 4, 64)),
            ('linear2', nn.Linear(64, 10))
        ]))

    def forward(self, x):
        x = self.model1(x)
        return x


model = MyModel()
print(model)

input = torch.ones(64, 3, 32, 32)
output = model(input)
print(output.shape)
image-20250527162711874

使用 tensorboard 可视化:

writer = SummaryWriter('logs')
writer.add_graph(model, input)
writer.close()
image-20250527162830152
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐