使用 PyTorch 构建模型

7 阅读8分钟

使用 PyTorch 构建模型

torch.nn.Module 与 torch.nn.Parameter

在本视频中,我们将探讨 PyTorch 为构建深度学习网络提供的部分工具。

Parameter 外,我们讨论的所有类均是 torch.nn.Module 的子类。这是 PyTorch 的基类,用于封装 PyTorch 模型及其组件的特有行为。

torch.nn.Module 的一个核心行为是参数注册:如果某个 Module 子类包含可学习权重,这些权重会被表示为 torch.nn.Parameter 实例。Parameter 类是 torch.Tensor 的子类,其特殊之处在于:当它被赋值为 Module 的属性时,会自动加入该模块的参数列表,可通过 Module 类的 parameters() 方法访问这些参数。

以下是一个简单示例:包含两个线性层和一个激活函数的极简模型。我们将创建模型实例并查看其参数:

import torch

class TinyModel(torch.nn.Module):

    def __init__(self):
        super(TinyModel, self).__init__()

        self.linear1 = torch.nn.Linear(100, 200)
        self.activation = torch.nn.ReLU()
        self.linear2 = torch.nn.Linear(200, 10)
        self.softmax = torch.nn.Softmax()

    def forward(self, x):
        x = self.linear1(x)
        x = self.activation(x)
        x = self.linear2(x)
        x = self.softmax(x)
        return x

tinymodel = TinyModel()

print('The model:')
print(tinymodel)

print('\n\nJust one layer:')
print(tinymodel.linear2)

print('\n\nModel params:')
for param in tinymodel.parameters():
    print(param)

print('\n\nLayer params:')
for param in tinymodel.linear2.parameters():
    print(param)

输出结果

The model:
TinyModel(
  (linear1): Linear(in_features=100, out_features=200, bias=True)
  (activation): ReLU()
  (linear2): Linear(in_features=200, out_features=10, bias=True)
  (softmax): Softmax(dim=None)
)


Just one layer:
Linear(in_features=200, out_features=10, bias=True)


Model params:
Parameter containing:
tensor([[-0.0325,  0.0925,  0.0762,  ..., -0.0461, -0.0054, -0.0105],
        [-0.0427, -0.0640,  0.0435,  ...,  0.0433,  0.0916, -0.0133],
        [-0.0473, -0.0265, -0.0956,  ...,  0.0372,  0.0948, -0.0595],
        ...,
        [-0.0199, -0.0486, -0.0992,  ...,  0.0489,  0.0565,  0.0477],
        [-0.0683, -0.0132,  0.0483,  ..., -0.0446,  0.0588, -0.0445],
        [ 0.0228,  0.0845,  0.0379,  ..., -0.0771, -0.0376, -0.0193]],
       requires_grad=True)
Parameter containing:
tensor([ 0.0777,  0.0467,  0.0190, -0.0037,  0.0860, -0.0509, -0.0807,  0.0310,
        -0.0194, -0.0489, -0.0647,  0.0686,  0.0410, -0.0566, -0.0538, -0.0696,
        -0.0907,  0.0282,  0.0475,  0.0838,  0.0895,  0.0517,  0.0170, -0.0803,
         0.0415, -0.0524, -0.0582, -0.0762, -0.0583, -0.0032, -0.0860, -0.0393,
        -0.0134, -0.0790,  0.0419,  0.0626,  0.0341,  0.0739,  0.0603,  0.0698,
        -0.0665,  0.0251,  0.0908, -0.0642,  0.0536, -0.0369, -0.0818,  0.0255,
         0.0792, -0.0415, -0.0886,  0.0825, -0.0054,  0.0737,  0.0421, -0.0795,
         0.0038, -0.0563,  0.0573,  0.0677, -0.0697,  0.0237,  0.0594,  0.0543,
         0.0835,  0.0051, -0.0188,  0.0354,  0.0106, -0.0148,  0.0009, -0.0281,
        -0.0766,  0.0983, -0.0359, -0.0079, -0.0010, -0.0358, -0.0609,  0.0635,
         0.0991, -0.0616,  0.0189,  0.0392, -0.0892, -0.0068,  0.0909, -0.0107,
        -0.0554,  0.0348, -0.0978,  0.0040,  0.0684, -0.0270, -0.0904,  0.0211,
         0.0345,  0.0983, -0.0087, -0.0335, -0.0231, -0.0837,  0.0347, -0.0970,
         0.0488, -0.0597, -0.0401, -0.0139, -0.0641, -0.0790,  0.0482, -0.0706,
        -0.0793, -0.0033, -0.0973,  0.0670, -0.0678,  0.0403,  0.0707,  0.0683,
         0.0053, -0.0666,  0.0535, -0.0662,  0.0303, -0.0118,  0.0726, -0.0831,
         0.0549, -0.0277,  0.0696, -0.0032,  0.0572,  0.0043, -0.0362, -0.0566,
        -0.0827, -0.0393,  0.0035,  0.0370, -0.0402, -0.0677,  0.0745, -0.0344,
         0.0992, -0.0715, -0.0954,  0.0048, -0.0418,  0.0039,  0.0306,  0.0870,
        -0.0736, -0.0577, -0.0796,  0.0211,  0.0168,  0.0533,  0.0133, -0.0145,
         0.0186, -0.0635,  0.0111,  0.0384,  0.0234, -0.0463, -0.0422,  0.0619,
         0.0507,  0.0908,  0.0912, -0.0227,  0.0442, -0.0536, -0.0587,  0.0065,
         0.0539, -0.0354, -0.0954,  0.0985,  0.0645,  0.0718, -0.0706,  0.0139,
        -0.0793, -0.0998, -0.0227,  0.0739, -0.0217, -0.0213, -0.0416, -0.0943,
        -0.0012,  0.0423,  0.0323, -0.0839, -0.0244, -0.0991,  0.0973,  0.0134],
       requires_grad=True)
Parameter containing:
tensor([[-0.0378,  0.0120, -0.0634,  ..., -0.0130, -0.0370,  0.0552],
        [-0.0607, -0.0181,  0.0372,  ..., -0.0564,  0.0311,  0.0431],
        [-0.0271, -0.0468, -0.0190,  ..., -0.0386, -0.0475,  0.0432],
        ...,
        [ 0.0580,  0.0439, -0.0165,  ...,  0.0058, -0.0386, -0.0543],
        [ 0.0665,  0.0706, -0.0084,  ..., -0.0480, -0.0147, -0.0007],
        [-0.0245,  0.0570,  0.0129,  ...,  0.0079,  0.0124, -0.0635]],
       requires_grad=True)
Parameter containing:
tensor([-0.0126,  0.0066, -0.0381, -0.0654,  0.0264, -0.0689, -0.0204, -0.0653,
        -0.0593,  0.0483], requires_grad=True)


Layer params:
Parameter containing:
tensor([[-0.0378,  0.0120, -0.0634,  ..., -0.0130, -0.0370,  0.0552],
        [-0.0607, -0.0181,  0.0372,  ..., -0.0564,  0.0311,  0.0431],
        [-0.0271, -0.0468, -0.0190,  ..., -0.0386, -0.0475,  0.0432],
        ...,
        [ 0.0580,  0.0439, -0.0165,  ...,  0.0058, -0.0386, -0.0543],
        [ 0.0665,  0.0706, -0.0084,  ..., -0.0480, -0.0147, -0.0007],
        [-0.0245,  0.0570,  0.0129,  ...,  0.0079,  0.0124, -0.0635]],
       requires_grad=True)
Parameter containing:
tensor([-0.0126,  0.0066, -0.0381, -0.0654,  0.0264, -0.0689, -0.0204, -0.0653,
        -0.0593,  0.0483], requires_grad=True)

这展示了 PyTorch 模型的核心结构:

  • __init__() 方法:定义模型的层和其他组件;
  • forward() 方法:实现模型的计算逻辑。

注:可通过打印模型/子模块查看其结构。

常见层类型

线性层(Linear Layers)

线性层(也称为全连接层)是最基础的神经网络层:每个输入都会通过层权重影响所有输出。若模型有 m 个输入和 n 个输出,权重矩阵的维度为 m×n。示例:

lin = torch.nn.Linear(3, 2)
x = torch.rand(1, 3)
print('Input:')
print(x)

print('\n\nWeight and Bias parameters:')
for param in lin.parameters():
    print(param)

y = lin(x)
print('\n\nOutput:')
print(y)

输出结果

Input:
tensor([[0.8703, 0.5852, 0.7673]])


Weight and Bias parameters:
Parameter containing:
tensor([[-0.1071,  0.0400, -0.2025],
        [ 0.5296,  0.4683,  0.1641]], requires_grad=True)
Parameter containing:
tensor([-0.0167, -0.3568], requires_grad=True)


Output:
tensor([[-0.2420,  0.5041]], grad_fn=<AddmmBackward0>)

将输入 x 与线性层权重做矩阵乘法并加上偏置,结果与输出 y 完全一致。

关键特性:lin.weightParameter(Tensor 子类),默认开启 Autograd 梯度追踪(这是 Parameter 与普通 Tensor 的核心区别)。

线性层广泛应用于深度学习模型,尤其在分类器中——分类器的最后通常会有一个/多个线性层,最后一层的输出维度等于分类任务的类别数。

卷积层(Convolutional Layers)

卷积层专为处理具有强空间相关性的数据设计,广泛应用于计算机视觉(检测局部特征并组合为高层特征),也用于 NLP(捕捉单词的上下文语义)。

以下是早期视频中 LeNet5 模型的卷积层实现:

import torch.nn.functional as F

class LeNet(torch.nn.Module):

    def __init__(self):
        super(LeNet, self).__init__()
        # 1个输入图像通道(黑白),6个输出通道,5x5卷积核
        self.conv1 = torch.nn.Conv2d(1, 6, 5)
        self.conv2 = torch.nn.Conv2d(6, 16, 3)
        # 仿射变换:y = Wx + b
        self.fc1 = torch.nn.Linear(16 * 6 * 6, 120)  # 6*6 来自图像维度
        self.fc2 = torch.nn.Linear(120, 84)
        self.fc3 = torch.nn.Linear(84, 10)

    def forward(self, x):
        # 2x2 窗口的最大池化
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 若池化窗口为正方形,可仅指定单个数值
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # 排除批次维度的所有维度
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

卷积层解析(以 conv1 为例)

  1. 输入通道:第一个参数为输入通道数。LeNet5 处理 1×32×32 的黑白图像,故输入通道为 1;若处理RGB图像,该值为 3。
  2. 输出通道:第二个参数为输出通道数(即卷积层要学习的特征数),此处为 6。
  3. 卷积核尺寸:第三个参数为卷积核(窗口)尺寸,此处的 5 表示 5×5 卷积核;若需非正方形核,可传入元组(如 (3,5) 表示 3×5 核)。

卷积层的输出是激活图(输入张量中特征分布的空间表示):

  • conv1 输出维度为 6×28×28(6个特征,28×28的空间维度;28 来自 32-5+1);
  • 经 ReLU 激活和 2×2 最大池化后,维度变为 6×14×14(池化将 2×2 区域合并为1个值,取最大值);
  • conv2 接收 6 个输入通道,输出 16 个通道,3×3 核,输出 16×12×12;经池化后变为 16×6×6,展平为 576 维向量后传入线性层。

扩展:PyTorch 提供 1D/2D/3D 卷积层,还支持步长(stride)、填充(padding)等可选参数,详见官方文档。

循环层(Recurrent Layers)

循环神经网络(RNN)用于处理序列数据(如时间序列、自然语言、DNA序列),通过维护隐藏状态(类似“记忆”)记录序列上下文信息。

RNN 及其变体(LSTM/GRU)的内部结构较复杂,以下是基于 LSTM 的词性标注器示例(分类单词的词性:名词/动词等):

class LSTMTagger(torch.nn.Module):

    def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):
        super(LSTMTagger, self).__init__()
        self.hidden_dim = hidden_dim

        self.word_embeddings = torch.nn.Embedding(vocab_size, embedding_dim)

        # LSTM 接收词嵌入作为输入,输出维度为 hidden_dim 的隐藏状态
        self.lstm = torch.nn.LSTM(embedding_dim, hidden_dim)

        # 将隐藏状态映射到标签空间的线性层
        self.hidden2tag = torch.nn.Linear(hidden_dim, tagset_size)

    def forward(self, sentence):
        embeds = self.word_embeddings(sentence)
        lstm_out, _ = self.lstm(embeds.view(len(sentence), 1, -1))
        tag_space = self.hidden2tag(lstm_out.view(len(sentence), -1))
        tag_scores = F.log_softmax(tag_space, dim=1)
        return tag_scores

构造函数参数说明

  • vocab_size:输入词汇表大小(每个单词对应 vocab_size 维的独热向量);
  • tagset_size:输出标签集大小(词性类别数);
  • embedding_dim:词嵌入空间维度(将高维独热向量映射到低维空间,语义相近的词在空间中距离更近);
  • hidden_dim:LSTM 隐藏状态(记忆)的维度。

前向传播逻辑

  1. 输入为单词的独热向量索引,经嵌入层映射到 embedding_dim 维空间;
  2. LSTM 迭代处理嵌入序列,输出 hidden_dim 维的隐藏状态;
  3. 线性层将隐藏状态映射到标签空间,log_softmax 将输出归一化为概率分布(表示单词对应各标签的概率)。

扩展:可参考 PyTorch 官方教程《Sequence Models and LSTM Networks》查看完整运行示例。

Transformer

Transformer 是通用型网络,凭借 BERT 等模型成为 NLP 领域的SOTA(最优性能)方案。其架构超出本视频范围,但 PyTorch 提供 Transformer 类,可定义注意力头数、编码器/解码器层数、dropout、激活函数等参数(甚至可通过该类构建 BERT 模型)。

torch.nn.Transformer 还包含细分组件类:

  • TransformerEncoder / TransformerDecoder(编码器/解码器);
  • TransformerEncoderLayer / TransformerDecoderLayer(编码层/解码层)。

详见 Transformer 官方文档

其他层与函数

数据操作层

这类层不参与学习,但对模型至关重要:

1. 池化层(Pooling Layers)

最大池化/最小池化通过合并张量元素实现降维,取区域内最大值/最小值作为输出:

my_tensor = torch.rand(1, 6, 6)
print(my_tensor)

maxpool_layer = torch.nn.MaxPool2d(3)
print(maxpool_layer(my_tensor))

输出结果

tensor([[[0.8055, 0.4216, 0.3058, 0.9001, 0.7154, 0.0885],
         [0.1511, 0.7800, 0.3969, 0.8882, 0.7142, 0.0940],
         [0.9166, 0.3576, 0.9560, 0.7981, 0.2106, 0.0578],
         [0.3761, 0.0980, 0.5340, 0.3139, 0.0251, 0.7075],
         [0.0139, 0.4401, 0.7147, 0.5136, 0.4683, 0.6043],
         [0.1791, 0.6990, 0.7465, 0.8742, 0.1573, 0.6789]]])
tensor([[[0.9560, 0.9001],
         [0.7465, 0.8742]]])

输出的每个值对应输入 6×6 张量中 3×3 区域的最大值。

2. 归一化层(Normalization Layers)

对层输出做中心化和归一化,可避免梯度消失/爆炸,支持更高的学习率:

my_tensor = torch.rand(1, 4, 4) * 20 + 5
print(my_tensor)

print(my_tensor.mean())

norm_layer = torch.nn.BatchNorm1d(4)
normed_tensor = norm_layer(my_tensor)
print(normed_tensor)

print(normed_tensor.mean())

输出结果

tensor([[[24.5083, 21.6152, 20.5365, 16.9337],
         [19.2096, 24.8286,  8.5177, 13.6296],
         [22.0705, 13.4739, 13.3103,  9.9620],
         [ 5.4103, 14.9501,  6.9635, 21.0814]]])
tensor(16.0626)
tensor([[[ 1.3316,  0.2644, -0.1335, -1.4625],
         [ 0.4369,  1.3586, -1.3170, -0.4785],
         [ 1.6450, -0.2748, -0.3113, -1.0590],
         [-1.0582,  0.4505, -0.8126,  1.4202]]],
       grad_fn=<NativeBatchNormBackward0>)
tensor(1.1921e-07, grad_fn=<MeanBackward0>)

归一化后张量均值接近 0,数值分布更集中,激活函数能在梯度最陡的区域工作,提升学习效率。

3. Dropout 层

训练时随机屏蔽部分输入(推理时关闭),迫使模型学习稀疏表示,提升泛化能力:

my_tensor = torch.rand(1, 4, 4)

dropout = torch.nn.Dropout(p=0.4)
print(dropout(my_tensor))
print(dropout(my_tensor))

输出结果

tensor([[[1.0502, 0.7134, 0.0000, 1.3411],
         [0.0000, 0.0923, 0.7351, 1.3475],
         [1.5547, 0.0000, 0.7461, 0.0676],
         [0.4339, 1.2999, 1.5323, 0.0000]]])
tensor([[[0.0000, 0.7134, 0.0000, 1.3411],
         [0.0580, 0.0923, 0.7351, 1.3475],
         [1.5547, 0.1532, 0.7461, 0.0000],
         [0.4339, 0.0000, 0.0000, 0.0000]]])
  • p 参数:单个权重被屏蔽的概率(默认 0.5);
  • 两次输出的屏蔽位置不同(随机特性)。

激活函数

激活函数是深度学习的核心:若仅做线性变换,多层网络等价于单层矩阵乘法,无法拟合复杂函数。非线性激活函数使模型能逼近任意函数。

torch.nn.Module 封装了主流激活函数:

  • ReLU 及其变体(LeakyReLU、PReLU 等);
  • Tanh、Hardtanh、Sigmoid;
  • Softmax(常用于模型输出层)。

损失函数

损失函数衡量模型预测与真实标签的差距。PyTorch 提供多种损失函数:

  • MSE(均方误差,L2 范数);
  • 交叉熵损失(Cross Entropy Loss)、负对数似然损失(Negative Likelihood Loss)——适用于分类任务;
  • 其他自定义损失函数。

脚本总运行时间:0分0.027秒


总结

  1. 核心基类torch.nn.Module 是所有模型/层的基类,Parameter 自动注册为模型可学习参数;
  2. 基础层类型
    • 线性层:全连接,适用于分类器输出层;
    • 卷积层:处理空间相关数据(图像/NLP);
    • 循环层:处理序列数据(LSTM/GRU);
    • Transformer:NLP 领域 SOTA 方案;
  3. 辅助层:池化(降维)、归一化(稳定训练)、Dropout(防止过拟合);
  4. 关键组件:激活函数(引入非线性)、损失函数(衡量预测误差)。