Bootstrap

笔记小结:神经网络中的层与块

本文为李沐老师《动手学深度学习》笔记小结,用于个人复习并记录学习历程,适用于初学者

之前首次介绍神经网络时,我们关注的是具有单一输出的线性模型然后,当考虑具有多个输出的网络时, 我们利用矢量化算法来描述整层神经元。 

在讨论下去,我们就会发现,研究讨论“比单个层大”但“比整个模型小”的组件很有价值,故而引入了神经网络的概念。 

(block)可以描述单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的一个好处是可以将一些块组合成更大的组件, 这一过程通常是递归的, 通过定义代码来按需生成任意复杂度的块, 我们可以通过简洁的代码实现复杂的神经网络。

自定义块

想要深入了解“块”,我们可以自己动手实现一个:

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
        super().__init__()
        self.hidden = nn.Linear(20, 256)  # 隐藏层
        self.out = nn.Linear(256, 10)  # 输出层

    # 定义模型的前向传播,即如何根据输入X返回所需的模型输出
    def forward(self, X):
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        return self.out(F.relu(self.hidden(X)))

 实例化这个类:

net = MLP()
net.forward(X) #等效于net(X)

输出:

tensor([[ 0.2535, -0.1106,  0.1944, -0.1779, -0.1540,  0.0939,  0.2560,  0.1141,
          0.0368, -0.1281],
        [ 0.0286,  0.0554,  0.1914,  0.0065, -0.0017,  0.2003,  0.0707,  0.0512,
         -0.0016, -0.0085]], grad_fn=<AddmmBackward0>)

 这个例子十分简单,唯一值得注意的是我们在执行net(X)的时候实际上就是在调用net.forward(x)。

顺序快

来看看Sequential类是如何工作的。

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)
        return X

调用:

net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

输出:

tensor([[ 0.0011,  0.1545, -0.0147, -0.0517, -0.1129,  0.1292,  0.3381,  0.0989,
         -0.0893, -0.0295],
        [-0.0605,  0.1350, -0.0213, -0.1577, -0.0175,  0.0041,  0.2423, -0.0636,
         -0.0778,  0.0949]], grad_fn=<AddmmBackward0>)
代码解释

如果对代码有疑问,或者想深入了解一下,不妨看一下ai的解释。(注:下面一段由ai生成

这段代码定义了一个名为 `MySequential` 的类,它继承自 PyTorch 的 `nn.Module` 类。这个类实现了一个自定义的序列模型,类似于 PyTorch 的 `nn.Sequential`,但是提供了一种自定义的方式来添加模块。下面是代码的逐行解释:

1. `class MySequential(nn.Module)`:定义了一个名为 `MySequential` 的类,它继承自 `nn.Module`。

2. `__init__(self, *args)`:构造函数接受任意数量的位置参数 `*args`。这些参数应该是 `nn.Module` 的子类实例。

3. `super().__init__()`:调用父类 `nn.Module` 的构造函数来执行必要的初始化。

4. `for idx, module in enumerate(args):`:遍历所有传入的参数。`enumerate` 函数返回每个参数的索引(`idx`)和参数本身(`module`)。

5. `self._modules[str(idx)] = module`:将每个模块实例添加到 `_modules` 字典中。索引被转换为字符串,因为 `_modules` 字典的键需要是字符串类型。这里使用字符串索引而不是数字索引是为了保持模块添加的顺序,类似于 `OrderedDict`。

6. `def forward(self, X)`:定义了模型的前向传播方法。

7. `for block in self._modules.values():`:遍历 `_modules` 字典中的所有值(即所有模块实例)。

8. `X = block(X)`:对输入 `X` 应用当前模块 `block`,并更新 `X` 为模块的输出。

9. `return X`:返回经过所有模块处理后的最终输出。

这个自定义的 `MySequential` 类允许你以顺序的方式堆叠多个神经网络模块。每个模块将按照它们添加到 `MySequential` 实例中的顺序依次应用于输入数据 `X`。这与 PyTorch 的内置 `nn.Sequential` 类的功能类似,但提供了更多的灵活性,例如通过索引访问特定的模块或修改模块的顺序。

enumerate()

enumerate()是 Python 中的一个内置函数,它用于将一个可迭代对象(如列表、元组、字符串等)组合为一个索引序列,同时返回元素的索引和值。这个函数经常用于 for 循环中,使得你可以在遍历列表或其它序列的同时获得元素的索引和值。

my_list = ['apple', 'banana', 'cherry']
for index, value in enumerate(my_list):
    print(index, value)

#输出:
#0 apple
#1 banana
#2 cherry

如果你想从非零索引开始计数,可以传递 start参数:

for index, value in enumerate(my_list, start=1):
    print(index, value)

#输出:
#1 apple
#2 banana
#3 cherry

小结

• ⼀个块可以由许多层组成;⼀个块可以由许多块组成。

• 块可以包含代码。

• 块负责⼤量的内部处理,包括参数初始化和反向传播。

• 层和块的顺序连接由Sequential块处理。

;