李沐动手学深度学习V2-层和块
1.自定义层:#自定义层:目前如果存在一个在深度学习框架中还不存在的层。 在这些情况下,你必须构建自定义层。import torchimport torch.nn.functional as Ffrom torch import nn#构造一个没有任何参数的自定义层class CenteredLayer(nn.Module):def __init__(self):super().__init__(
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.总结
- 一个块可以由许多层组成;一个块可以由许多块组成。
- 块可以包含代码。
- 块负责大量的内部处理,包括参数初始化和反向传播。
- 层和块的顺序连接由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)
更多推荐
所有评论(0)