深度学习计算(层与块)
区分层和块的小总结
1.层和块
1.1单输出和多输出神经网络的区别
单层神经网络
(1)接受一些输入; (2)生成相应的标量输出; (3)具有一组相关 参数(parameters),更新这些参数可以优化某目标函数
多输出神经网络
(1)接受一组输入, (2)生成相应的输出, (3)由一组可调整参数描述
以使用softmax为例子:
使用softmax回归时,输出层会生成一个向量,其长度等于类别的数量。向量的每个元素表示输入数据属于对应类别的预测概率。这些概率值是通过softmax函数计算的,确保所有输出值的和为1。
1.2块
事实证明,研究讨论“比单个层大”但“比整个模型小”的组件更有价值
这里说的组件就是即所谓的子结构或模块
书中举的retnet的例子
ResNet(残差网络)的成功在很大程度上归功于其独特的残差模块设计,这些模块通过引入残差连接(shortcut connections)解决了深层网络中的梯度消失/爆炸问题,使得训练更深层的网络成为可能。在ResNet-152中,这些残差模块以特定的重复模式组合成层组,进一步增强了网络的学习能力和泛化能力。这种层组的设计使得ResNet能够在保持模型复杂度的同时,有效地减少过拟合风险,提高在ImageNet和COCO等大规模数据集上的性能。
1.3层与块与层组的区别
一、层的定义与特点
定义:
- 在深度学习中,层(Layer)通常指的是神经网络中的一个处理单元,如全连接层、卷积层、池化层等。这些层负责接收输入数据,进行特定的计算或处理,并产生输出数据。
特点:
- 层是构成神经网络或其他系统的基础单元。
- 每层都有其特定的功能和作用,如特征提取、分类、回归等。
- 层与层之间通过连接(如权重)进行信息传递和交互。
二、块的定义与特点
定义:
- 在深度学习中,块(Block)通常是由多个层组成的结构。这些层可以是连续的,也可以是具有特定功能的组合,旨在实现更复杂的任务或处理流程。
特点:
- 块是层的组合,具有更高的抽象层次和更复杂的功能。
- 块可以看作是一个封装好的模块,可以重复使用在不同的神经网络或系统中。
- 块内部可能包含多种类型的层,以及层与层之间的连接和交互方式。
三、层组的定义与特点(相对于层和块)
定义:
- 层组(Layer Group)并不是一个通用的术语,但在某些上下文或特定领域中可能指代一组相互关联或具有共同特性的层。这些层可能属于同一个模块、处理相同的任务或具有相似的结构。
特点(相对于层和块):
- 层组强调的是层之间的关联性和共同性,而不是简单的组合或封装。
- 层组可能包含多个块,或者由多个具有特定关系的层直接组成。
- 层组的设计往往是为了实现更高级别的功能或优化整个系统的性能。
2.自定义块
从编程的角度来看,块由类(class)表示。 它的任何子类都必须定义一个将其输入转换为输出的前向传播函数, 并且必须存储任何必需的参数。 注意,有些块不需要任何参数。
2.1需要定义的部分
将输入数据作为其前向传播函数的参数。
通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。例如,我们上面模型中的第一个全连接的层接收一个20维的输入,但是返回一个维度为256的输出。
计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。
存储和访问前向传播计算所需的参数。
根据需要初始化模型参数。
2.2编写块
import torch
from torch import nn
from torch.nn import functional as F
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)))
块的一个主要优点是它的多功能性。 我们可以子类化块以创建层(如全连接层的类)、 整个模型(如上面的MLP
类)或具有中等复杂度的各种组件。
2.3Sequential
类
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):# enumerate 函数遍历了 args(一个包含多个 nn.Module 子类实例的可迭代对象,比如列表或元组)。enumerate 会为每个元素生成一个索引(idx)和该元素本身(module)
# 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
# 变量_modules中。_module的类型是OrderedDict
self._modules[str(idx)] = module
def forward(self, X):
# OrderedDict保证了按照成员添加的顺序遍历它们
#这里的block代表self._modules中保存的nn.Module子类的一个实例,也就是你添加到MySequential中的每一个神经网络层或模
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常数参数
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:
X /= 2
return X.sum()
其中self.rand_weight就是常数参数
2.41作用
-
引入先验知识:在某些情况下,您可能拥有关于网络应该如何处理输入数据的先验知识。通过将这些知识编码为常数参数,您可以直接在网络中利用这些信息,而无需从头开始学习它。
-
固定特征变换:常数参数可以用于实现一些固定的特征变换,这些变换对于所有输入都是相同的,并且不需要在训练过程中进行调整。例如,您可以使用常数参数来实现特定的滤波器或编码器,这些滤波器或编码器在应用于输入数据时会保持不变。
-
性能优化:在某些情况下,减少需要优化的参数数量可以提高训练过程的效率和稳定性。通过将某些参数设置为常数,您可以减少模型的复杂度,并可能减少过拟合的风险。然而,这也需要权衡,因为过少的可学习参数可能会限制模型的表达能力。
-
实现特定功能:常数参数还可以用于在神经网络中实现特定的功能或行为。例如,在自注意力机制中,常数参数可以用于初始化查询、键和值向量的线性变换的权重,尽管在实际应用中这些权重通常是可学习的。但是,在某些情况下,使用常数参数可能有助于简化模型或实现特定的设计目标。
-
调试和测试:在开发和调试神经网络时,使用常数参数可以帮助您隔离问题。通过将某些层的参数设置为常数,您可以更容易地确定问题是由网络的哪个部分引起的。此外,在测试阶段,使用固定的参数可以确保模型的预测是一致的,这有助于验证模型的稳定性和可靠性。
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)
结尾
-
一个块可以由许多层组成;一个块可以由许多块组成。
-
块可以包含代码。
-
块负责大量的内部处理,包括参数初始化和反向传播。
-
层和块的顺序连接由
Sequential
块处理。
更多推荐
所有评论(0)