深度学习实战(基于pytroch)系列(十五)模型构造

模型构造

让我们回顾一下在回顾一下在"多层感知机的实现"一节中含单隐藏层的多层感知机的实现方法。我们首先构造Sequential实例,然后依次添加两个全连接层。其中第一层的输出大小为256,即隐藏层单元个数是256;第二层的输出大小为10,即输出层单元个数是10。我们在上一章的其他节中也使用了Sequential类构造模型。这里我们介绍另外一种基于Module类的模型构造方法:它让模型构造更加灵活。

继承Module类来构造模型

Module类是torch.nn模块里提供的一个模型构造类,我们可以继承它来定义我们想要的模型。下面继承Module类构造本节开头提到的多层感知机。这里定义的MLP类重载了Module类的__init__函数和forward函数。它们分别用于创建模型参数和定义前向计算。前向计算也即正向传播。

导包

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

继承Module类构造MLP

Python的__init__ 起着构造器的作用,这里的 super(MLP, self).__init__(**kwargs)不能省略相当于调用父类的构造函数。nn.Module.__init__() 负责初始化:参数管理系统 _parameters, _buffers,子模块管理系统 _modules,钩子系统 :前向/反向传播钩子,状态标志:training 模式等管理神经网络所需的所有基础架构。所以必须调用父类的构造器。super(MLP, self).__init__(**kwargs)是老版本python的写法,实际上更推荐使用super().__init__(**kwargs)

python 复制代码
class MLP(nn.Module):
    # 声明带有模型参数的层,这里声明了两个全连接层
    def __init__(self, **kwargs):
        # 调用MLP父类Module的构造函数来进行必要的初始化
        #这里也可以改写成super().__init__(**kwargs)
        super(MLP, self).__init__(**kwargs)
        self.hidden = nn.Linear(20, 256)  # 隐藏层
        self.output = nn.Linear(256, 10)  # 输出层

    # 定义模型的前向计算
    def forward(self, x):
        return self.output(F.relu(self.hidden(x)))

测试MLP

python 复制代码
X = torch.randn(2, 20)
net = MLP()
print(net(X))

2. 实现MySequential类

Module类是一个通用的部件。事实上,Sequential类继承自Module类。当模型的前向计算为简单串联各个层的计算时,可以通过更加简单的方式定义模型。这正是Sequential类的目的:它提供add_module函数来逐一添加串联的Module子类实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算。通俗的来讲nn.Sequential本质上是继承了nn.module然后调用了父类的add_module方法,然后在 forward 中按顺序调用所有子模块。

python 复制代码
class MySequential(nn.Module):
    def __init__(self, *args):
        super(MySequential, self).__init__()
        for idx, module in enumerate(args):
            # 这里,module是Module子类实例
            # 我们把它保存在'Module'类型的成员变量中
            self.add_module(str(idx), module)
    
    def forward(self, x):
        # 按添加顺序遍历所有子模块
        for module in self.children():
            x = module(x)
        return x

测试MySequential

我们用MySequential类来实现前面描述的MLP类,并使用随机初始化的模型做一次前向计算。

python 复制代码
net = MySequential(
    nn.Linear(20, 256),
    nn.ReLU(),
    nn.Linear(256, 10)
)
print(net(X))

3. 构造复杂模型FancyMLP

虽然Sequential类可以使模型构造更加简单,且不需要定义forward函数,但直接继承Module类可以极大地拓展模型构造的灵活性。下面我们构造一个稍微复杂点的网络FancyMLP。在这个网络中,我们通过register_buffer函数创建训练中不被迭代的参数,即常数参数。在前向计算中,除了使用创建的常数参数外,我们还使用Tensor的函数和Python的控制流,并多次调用相同的层。

python 复制代码
class FancyMLP(nn.Module):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 使用register_buffer创建训练中不被迭代的参数(常数参数)
        self.register_buffer('rand_weight', torch.rand(20, 20))
        self.dense = nn.Linear(20, 20)
    
    def forward(self, x):
        x = self.dense(x)
        # 使用创建的常数参数,以及torch的relu函数和matmul函数
        x = F.relu(torch.mm(x, self.rand_weight) + 1)
        # 复用全连接层。等价于两个全连接层共享参数
        x = self.dense(x)
        # 控制流
        while x.norm() > 1:
            x /= 2
        if x.norm() < 0.8:
            x *= 10
        return x.sum()
python知识拓展(三种方法)

在Python中,类里面定义的方法分为三种:

python 复制代码
class Example:
    def instance_method(self, x):  # 实例方法
        return f"实例方法: {self} 收到 {x}"
    
    @classmethod
    def class_method(cls, x):      # 类方法
        return f"类方法: {cls} 收到 {x}"
    
    @staticmethod
    def static_method(x):          # 静态方法
        return f"静态方法: 收到 {x}"
实例方法(Instance Method)
  • 第一个参数必须是 self
  • 通过实例调用
  • 可以访问实例属性和其他实例方法
python 复制代码
class MLP(nn.Module):
    def __init__(self):           # ✅ 实例方法
        super().__init__()
        self.layer = nn.Linear(10, 5)
    
    def forward(self, x):         # ✅ 实例方法
        return self.layer(x)      # 通过self访问实例属性

# 使用
model = MLP()                    # 创建实例
output = model.forward(torch.randn(2, 10))  # 实例调用方法
类方法(Class Method)
  • 第一个参数必须是 cls
  • 使用 @classmethod 装饰器
  • 通过类或实例调用
  • 可以访问类属性,但不能访问实例属性
python 复制代码
class ModelFactory:
    model_count = 0  # 类属性
    
    @classmethod
    def create_model(cls, input_size, output_size):
        cls.model_count += 1
        return f"创建第{cls.model_count}个模型: {input_size}→{output_size}"

# 使用
print(ModelFactory.create_model(10, 5))  # 通过类调用
print(ModelFactory.create_model(20, 3))  # 通过类调用
静态方法(Static Method)
  • 没有特殊的第一个参数
  • 使用 @staticmethod 装饰器
  • 通过类或实例调用
  • 不能访问实例属性或类属性
python 复制代码
class MathUtils:
    @staticmethod
    def relu(x):
        return max(0, x)  # 纯函数,不依赖实例或类状态
    
    @staticmethod
    def normalize(x):
        return (x - x.mean()) / x.std()

# 使用
print(MathUtils.relu(-5))        # 0
print(MathUtils.relu(5))         # 5
调用方式的区别
python 复制代码
class TestClass:
    def instance_method(self):
        return f"实例方法, self={self}"
    
    @classmethod
    def class_method(cls):
        return f"类方法, cls={cls}"
    
    @staticmethod
    def static_method():
        return "静态方法"

# 创建实例
obj = TestClass()

# 调用方式对比
print(obj.instance_method())    # ✅ 实例调用实例方法
print(TestClass.instance_method(obj))  # ✅ 类调用实例方法(不常见)

print(obj.class_method())       # ✅ 实例调用类方法  
print(TestClass.class_method()) # ✅ 类调用类方法

print(obj.static_method())      # ✅ 实例调用静态方法
print(TestClass.static_method()) # ✅ 类调用静态方法
方法类型 第一个参数 装饰器 可访问的数据 PyTorch中使用频率
实例方法 self 实例属性、类属性 ⭐⭐⭐⭐⭐
类方法 cls @classmethod 类属性 ⭐⭐
静态方法 @staticmethod ⭐⭐

所以可以这样理解没有装饰器的一定是实例方法,并且需要第一个参数是self。三种方法都可以通过类和实例(实例等价于对象)调用。

测试FancyMLP

在这个FancyMLP模型中,我们使用了常数权重rand_weight(注意它不是模型参数)、做了矩阵乘法操作(torch.mm)并重复使用了相同的Dense层。下面我们来测试该模型的随机初始化和前向计算。

python 复制代码
net = FancyMLP()
print(net(X))

4. 嵌套调用模型

因为FancyMLPSequential类都是Module类的子类,所以我们可以嵌套调用它们。

python 复制代码
class NestMLP(nn.Module):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.net = nn.Sequential(
            nn.Linear(20, 64),
            nn.ReLU(),
            nn.Linear(64, 32),
            nn.ReLU()
        )
        self.dense = nn.Linear(32, 16)
    
    def forward(self, x):
        return self.dense(self.net(x))

测试嵌套模型

python 复制代码
net = nn.Sequential(
    NestMLP(),
    nn.Linear(16, 20),
    FancyMLP()
)
print(net(X))
扩展知识

net(X) 的调用流程:

  1. net(X) → 调用 net.__call__(X)
  2. net.__call__(X) → 执行前置处理 → 调用 net.forward(X) → 执行后置处理
  3. 返回结果

nn.Module__call__ 方法大致是这样的:

python 复制代码
class Module:
    def __call__(self, *input, **kwargs):
        # 前向传播前的处理
        for hook in self._forward_pre_hooks.values():
            input = hook(self, input)
        
        # 调用用户定义的forward方法
        result = self.forward(*input, **kwargs)
        
        # 前向传播后的处理
        for hook in self._forward_hooks.values():
            result = hook(self, input, result)
            
        return result

__call__方法提供的重要功能:

  1. 钩子执行 - 前向/后向传播的钩子函数

  2. 训练模式检查 - 自动处理dropout、batchnorm等

  3. 梯度计算 - 设置是否需要梯度

  4. 错误处理 - 统一的异常处理

相关推荐
海域云赵从友2 小时前
2025年印尼服务器选型指南:跨境业务落地的合规与性能双解
人工智能·git·github
xuehaikj3 小时前
【深度学习】YOLOv10n-MAN-Faster实现包装盒flap状态识别与分类,提高生产效率
深度学习·yolo·分类
sponge'3 小时前
opencv学习笔记9:基于CNN的mnist分类任务
深度学习·神经网络·cnn
用户5191495848453 小时前
cURL变量管理中的缓冲区越界读取漏洞分析
人工智能·aigc
iFlow_AI3 小时前
增强AI编程助手效能:使用开源Litho(deepwiki-rs)深度上下文赋能iFlow
人工智能·ai·ai编程·命令模式·iflow·iflow cli·心流ai助手
AI街潜水的八角3 小时前
深度学习杂草分割系统1:数据集说明(含下载链接)
人工智能·深度学习·分类
TG:@yunlaoda360 云老大3 小时前
谷歌云发布 Document AI Workbench 最新功能:自定义文档拆分器实现复杂文档处理自动化
运维·人工智能·自动化·googlecloud
苍何4 小时前
国内也有 GPT 质感的 App 了,阿里做到了。
人工智能
美团技术团队4 小时前
美团 LongCat 团队发布全模态一站式评测基准UNO-Bench
人工智能