动手学习pyTorch之【计算过程】——从基础模块类开始

Computation

  • 神经网络块:描述单个层、由多个层组成的组件或整个模型本身(模块化)。块由类表示,它的任何子类都必须定义一个将其输入转换为输出的前向传播函数并且必须存储任何必需的参数。 注意,有些块不需要任何参数。为了计算梯度,块还必须具有反向传播函数。 在自定义时,由于自动微分提供了一些后端实现,我们只需要考虑前向传播函数和必需的参数。
  • 模块实例:nn.Sequential是一种表示一个块的类,它定义了一种特殊的Module,即维护了一个由Module组成的有 序列表。上面的实例化例子中,两个全连接层都是Linear类的实例, Linear类本身就是Module的子类,层的执行顺序是作为参数传递的。 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

X = torch.rand(2, 20)
net(X)
'''
tensor([[-0.1216,  0.0153,  0.0546, -0.0989, -0.0582,  0.1448, -0.3097, -0.0478,
         -0.1381,  0.0593],
        [-0.1315,  0.0540,  0.0157, -0.0701, -0.2307,  0.0710, -0.2731, -0.0527,
         -0.2170,  0.1010]], grad_fn=<AddmmBackward0>)
'''
  • 自定义块

    • 将输入数据作为其前向传播函数的参数
    • 通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。例如上面模型中的第一个全连接层接收一个20维的输入,但是返回一个维度为256的输出。
    • 计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。
    • 存储和访问前向传播计算所需的参数
    • 根据需要初始化模型参数

    自定义MLP():net(X)会调用MLP继承自Module类的__call__函数,这个函数将调用MLP类定义的forward函数来完成前向计算,这一点是为什么直接使用net(x)就能完成向前计算的原因。

    # 自定义一个具有256个隐藏单元的隐藏层和一个10维的输出层
    # 继承了Module类!在定义子类的时候,必须在括号内指定父类的名称
    class MLP(nn.Module):
        def __init__(self):  # 调用MLP的父类Module的构造函数来执行必要的初始化
            super().__init__()  # super() 用来调用父类,帮助父类和子类关联起来
            # 继承之后,便可以给子类定义属性和方法
            self.hidden = nn.Linear(20, 256)  # 隐藏层
            self.out = nn.Linear(256, 10)
    
        # 定义模型的前向传播
        def forward(self, X):
            return self.out(F.relu(self.hidden))
        
        
    net = MLP()
    net(X)
    

    **自定义Sequential():**它可以接收一个子模块的有序字典或者一系列子模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算。我们需要定义两个关键函数:

    • 一种将块逐个追加到列表中的函数。
    • 一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。

    __init__函数将每个模块逐个添加到有序字典_modules中,_modules优点是在模块的参数初始化过程中, 系统知道在_modules字典中查找需要初始化参数的子块。当MySequential的前向传播函数被调用时, 每个添加的块都按照它们被添加的顺序执行。

    # 实例化Sequential
    class MySequential(nn.Module):
        def __init__(self, *args):
            super().__init__()
            # 如果传入的是一个OrderedDict
            # 函数中以列表或者元组的形式传参时所有传入的位置参数都会被args接收生成一个元组,故args[0]
            if len(args) == 1 and isinstance(args[0], OrderedDict):
                for key, module in args[0].items():  # item取元素
                    self.add_module(key, module)  # add_module方法会将module添加进self._modules(一个OrderedDict)
            else:  # 传入的是一些Module
                for idx, module in enumerate(args):  # 枚举取变量
                    self.add_module(str(idx), module)  # 主要要str(key)
    
        def forward(self, X):
            for block in self._modules.values():
                X=block(X)
            return X
    
    
    net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
    

    进阶

    FixedHiddenMLP中,我们实现了一个隐藏层, 其权重self.rand_weight在实例化时被随机初始化,之后为常量。 这个权重不是一个模型参数,因此它永远不会被反向传播更新。 然后,神经网络将这个固定层的输出通过一个全连接层。

    注意,在返回输出之前,模型运行了一个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)
            # 控制流
            while X.abs().sum() > 1:
           
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值