1.自定义层:

#自定义层:目前如果存在一个在深度学习框架中还不存在的层。 在这些情况下,你必须构建自定义层。
import torch
import torch.nn.functional as F
from torch import nn

#构造一个没有任何参数的自定义层
class CenteredLayer(nn.Module):
    def __init__(self):
        super().__init__()#__init__()函数不进行任何初始化操作

    def forward(self, X):
        return X - X.mean()#对X中每一个元素减去均值

layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))
'''
输出结果:
tensor([-2., -1.,  0.,  1.,  2.])
'''

#将自定义层作为组件合并到更复杂的模型中
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())

Y = net(torch.rand(4, 8))
Y.mean() #当给网络发送随机数据后,检查输出Y均值是否为0。 由于我们处理的是浮点数,因为存储精度的原因,我们仍然可能会看到一个非常小的非零数。
'''
输出结果:
tensor(1.8626e-09, grad_fn=<MeanBackward0>)
'''

#自定义一个带参数的层
class MyLinear(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        self.weight = nn.Parameter(torch.randn(in_units, units))#采用随机初始化参数,并由Parameter()函数包裹起来,目的是给这个参数一个名字标识
        self.bias = nn.Parameter(torch.randn(units,))
    def forward(self, X):
        linear = torch.matmul(X, self.weight.data) + self.bias.data
        return F.relu(linear)

#实例化MyLinear类并访问其模型权重参数
linear = MyLinear(5, 3)#输入5维,输出3维的层
linear.weight #随机初始化权重参数经过Parameter()函数包裹后的输出结果,多了一个"Parameter containing"名字标识
'''
输出结果:Parameter containing:
tensor([[ 0.3891, -0.4105, -0.7189],
        [-0.8655, -0.1785, -0.0529],
        [-0.1446,  1.7960,  0.1220],
        [ 0.4567,  0.0444, -0.5521],
        [ 0.8643, -1.2224,  0.5013]], requires_grad=True)
'''
#使用自定义层直接执行前向传播计算
linear(torch.rand(2, 5))
'''
输出结果:
tensor([[0.0000, 1.0663, 0.0194],
        [0.0000, 0.0000, 0.7687]])
'''


#使用自定义层构建模型,就像使用内置的全连接层一样使用自定义层
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))#Sequential()包含两个自定义层
net(torch.rand(2, 64))#给模型输入,得到输出
'''
输出结果:
tensor([[7.9190],
        [1.8329]])
'''

2 层和块

2.1 nn.Sequential层:

通过net(X)调用我们的模型来获得模型的输出。 这实际上是net.call(X)的简写,net.call(X)函数调用了forward()函数,call(X)函数是python中一个内置函数。 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。

import torch
from torch import nn
from torch.nn import functional as F

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

X = torch.rand(2, 20)
net(X)

2.2 自定义块

首先,我们定制的__init__函数通过super().init() 调用父类的__init__函数, 省去了重复编写模版代码的痛苦。 然后,我们实例化两个全连接层, 分别为self.hidden和self.out。 注意,除非我们实现一个新的运算符, 否则我们不必担心反向传播函数或参数初始化, 系统将自动生成这些。

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

2.3 手动实现nn.Sequential顺序块:

class MySequential(nn.Module):
	#__init__函数将每个模块逐个添加到有序字典_modules中
    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)

2.4 在前向传播函数中执行特定操作

Sequential类使模型构造变得简单, 允许我们组合新的架构,而不必定义自己的类。 然而,并不是所有的架构都是简单的顺序架构。 当需要更强的灵活性时,我们需要定义自己的块。 例如,我们可能希望在前向传播函数中执行Python的控制流。 此外,我们可能希望执行任意的数学运算, 而不是简单地依赖预定义的神经网络层。
在这个FixedHiddenMLP模型中,我们实现了一个隐藏层, 其权重(self.rand_weight)在实例化时被随机初始化,之后为常量。 这个权重不是一个模型参数,因此它永远不会被反向传播更新。因为创建时让requires_grad等于False。 然后,神经网络将这个固定层的输出通过一个全连接层。
注意,在返回输出之前,模型做了一些不寻常的事情: 它运行了一个while循环,在 𝐿1 范数大于 1 的条件下, 将输出向量除以 2 ,直到它满足条件为止。 最后,模型返回了X中所有项的和。 注意,此操作可能不会常用于在任何实际任务中, 只是向你展示如何将任意代码集成到神经网络计算的流程中。

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及relu和mm函数
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数
        X = self.linear(X)
        print(X)
        # 控制流
        while X.abs().sum() > 1:
            X /= 2
            print(X)
        return X.sum()
net = FixedHiddenMLP()
net(X)

2.5 嵌套块,混合搭配各种组合块

class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU())
        self.linear = nn.Linear(32, 16)

    def forward(self, X):
        return self.linear(self.net(X))

chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)

3.总结

  1. 一个块可以由许多层组成;一个块可以由许多块组成。
  2. 块可以包含代码。
  3. 块负责大量的内部处理,包括参数初始化和反向传播。
  4. 层和块的顺序连接由Sequential块处理。

4. 层和块全部源代码

import torch
from torch import nn
from torch.nn import functional as F

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

X = torch.rand(2, 20)
net(X)

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


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)

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及relu和mm函数
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数
        X = self.linear(X)
        print(X)
        # 控制流
        while X.abs().sum() > 1:
            X /= 2
            print(X)
        return X.sum()
net = FixedHiddenMLP()
net(X)

class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU())
        self.linear = nn.Linear(32, 16)

    def forward(self, X):
        return self.linear(self.net(X))

chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐