深度学习笔记(八):块和层

层的基本功能

  • 输入:输入数据作为此层前向传播的变量。
  • 输出通过此层运算生成输出。
  • 参数管理:可以通过一组参数描述。
  • 对于多层感知机而言,整个模型及其组成层都是这种架构。整个模型接受原始输入(特征),生成输出(预测), 并包含一些参数(所有组成层的参数集合)。 同样,每个单独的层接收输入(由前一层提供), 生成输出(到下一层的输入),并且具有一组可调参数, 这些参数根据从下一层反向传播的信号进行更新。

    自定义层

    建议首先阅读本文的自定义块部分,再查看本章节。实际上,自定义块解决的是网络连接问题;而自定义层规定了层内参数如何传递的问题。因此自定义块强调多个层的连接方式,自定义层则给定一个新的运算方式。某种意义上,在定义块的时候,层也可以在块中进行定义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Mylinear(nn.Module):
    def __init__(self, inp, outp):
    super(MyLinear, self).__init__()
    #require_grad = True
    self.w = nn.Parameter(torch.randn(outp, inp))
    self.b = nn.Parameter(torch.randn(outp))
    def forward(self, x)
    x = x @ self.w.t() + self.b
    return x

    块可以描述单个层、由多个层组成的组件或整个模型本身。

    块的基本功能

  • 特征输入:输入数据作为前向传播的变量。
  • 前向传播:通过前向传播函数生成输出。
  • 反向传播:计算输出相对于输入的梯度(自动求导)。
  • 参数管理:初始化、存储、访问和更新模型参数。
  • 自定义块

    1
    2
    3
    4
    5
    6
    7
    8
    class MLP(nn.Module):
    def __init__(self):
    super().__init__()
    self.hidden = nn.Linear(20, 256)
    self.out = nn.Linear(256, 10)

    def forward(self, X):
    return self.out(F.relu(self.hidden(X)))

    深度学习中PyTorch块的概念和Python编程中类的概念是一致的。在上述代码中,我们定义了MLP这个类,该类继承于nn.Module父类。完成块的定义只需要定义一个包含构造函数(init)和前向传播函数(forward)的类

    MLP类包含了构造函数和前向传播函数。对于构造函数,我们没有传入新的参数,首先super().__init__()方法即调用了父类nn.Module__init__方法;接着,我们定义对象的属性hiddenout。根据构造函数可知,对象构造时不需要传入参数,我们定义方法直接采用了实例化的线性回归模型。对于前向传播函数,返回一个前向的计算公式。

    值得注意的是,我们定义了维度,但在大多数块中,我们不提前定义维度,而是采用延迟初始化的技术,当数据第一次通过网络进行传播时,才开始定义维度。

    我们接着为MLP创造一个实例net,并调用实例netforward方法。

    1
    2
    net = MLP()
    net(X)

    在PyTorch框架中,nn.Module是所有神经网络模块的基类,它包含了一些基本的方法和属性,用来构建和训练神经网络。其中一个重要的特性是它的__call__方法,这个方法使得任何继承自nn.Module的类的实例都可以像调用普通函数那样被调用。

    当你创建了一个继承自nn.Module的类的实例(如你的MLP类),并尝试使用这个实例调用时(比如net(X)),实际上发生的是以下几步:

    1. __call__方法被自动触发:当你使用net(X)这样的语法时,Python会寻找net__call__方法,并将X作为参数传递给它。

    2. 转发到forward方法:nn.Module__call__方法内部实现了对forward方法的调用。这意味着,当你写net(X)时,它实际上是执行了net.forward(X)

    3. 执行forward方法:在forward方法中,定义了输入数据X如何通过网络流动,包括经过哪些层、激活函数等,并最终返回输出结果。

    因此,net(X)的调用看似简单,但背后是通过nn.Module__call__方法,自动将其转化为对forward方法的调用。这样的设计使得模型的使用更加直观和方便,同时也保持了代码的清晰和组织性。

    此外,不可以直接使用MLP(X)这种方式调用。在PyTorch中,必须先创建类的实例才能使用它的方法,包括forward方法。直接使用类名调用方法通常是面向静态方法或类方法的,而forward方法并不是静态或类方法,它依赖于类实例的状态(即类实例的属性,如你的例子中的self.hiddenself.out层)。

    顺序块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #定义顺序块,用以存储神经网络
    class MySequential(nn.Module):

    #定义类的初始化函数
    def __init__(self, *args):
    super().__init__() #调用父类的初始化函数

    #通过循环不断地向顺序块中添加神经网络
    for idx, module in enumerate(args):
    #这里, module是Module子类的一个实例。我们把它保存在'Module'类的成员
    #变量_modules中。_module的类型是OrderedDict
    self._modules[str(idx)] = module

    #定义前向传播函数
    def forward(self, X):
    #OrderedDict保证了按照成员添加地顺序遍历它们
    for block in self._modules.values():
    X = block(X) #顺序地调用X进入神经网络

    return X

    这段代码定义了一个名为MySequential的Python类,它是nn.Module的子类,用于在PyTorch框架中创建一个神经网络的顺序容器。这个容器可以按照定义的顺序执行多个网络层或其他模块:

    类定义和初始化

    • class MySequential(nn.Module): 这行代码定义了一个名为MySequential的类,它继承自nn.Modulenn.Module是所有神经网络模块的基类,提供了很多基本功能,如参数管理、模型保存和加载等。

    • def __init__(self, *args): 这是类的构造函数,用于初始化新的MySequential实例。*args允许在创建实例时传递一个动态数量的参数,这些参数都应该是nn.Module的实例,比如各种网络层。

    • super().__init__(): 这行代码调用父类(nn.Module)的构造函数,是Python中常见的做法,用于正确初始化继承自父类的部分。

    添加模块到容器

    • for idx, module in enumerate(args): 这个循环遍历传给构造函数的所有模块。enumerate函数同时返回每个模块的索引(idx)和模块本身(module)。

    • self._modules[str(idx)] = module: 这行代码将每个传入的模块添加到self._modules字典中。_modulesnn.Module中用于存储子模块的内置容器,其类型是OrderedDict。使用OrderedDict确保模块会按照添加的顺序被存储和调用。

    定义前向传播

    • def forward(self, X): 这是nn.Module必须实现的方法,定义了模型的前向传播逻辑,即如何处理输入数据X

    • for block in self._modules.values(): 这个循环遍历self._modules中存储的所有模块。由于self._modules是一个有序字典,这保证了模块按照它们被添加的顺序执行。

    • X = block(X): 在循环中,输入X被依次传递给每个模块(block),模块处理后的输出成为下一模块的输入。这样可以顺序地应用所有定义的网络层或函数到输入数据上。

    • return X: 最后,经过所有模块处理后的结果X被返回。这是该顺序容器处理输入后的最终输出。

    块的嵌套

    块是nn.Module方法的组合,事实上,在PyTorch中,块本身也可以和nn.Module进行顺序块的定义。如:

    1
    nn.Sequential(MLP, nn.Relu, nn.Relu(*arg))

    欢迎关注我的其它发布渠道