丽水企业网站开发企业,大数据查询,wordpress福利博客,大连百度搜索排名优化深度学习——深度学习计算一 文章目录 前言一、层和块1.1. 自定义块1.2. 顺序块1.3. 在前向传播函数中执行代码1.4. 小结 二、参数管理2.1. 参数访问2.1.1. 目标参数2.1.2. 一次性访问所有参数2.1.3. 从嵌套块收集参数 2.2. 参数初始化2.2.1. 内置初始化2.2.2. 自定义初始化 2.… 深度学习——深度学习计算一 文章目录 前言一、层和块1.1. 自定义块1.2. 顺序块1.3. 在前向传播函数中执行代码1.4. 小结 二、参数管理2.1. 参数访问2.1.1. 目标参数2.1.2. 一次性访问所有参数2.1.3. 从嵌套块收集参数 2.2. 参数初始化2.2.1. 内置初始化2.2.2. 自定义初始化 2.3. 参数绑定 总结 前言
本章将深入探索深度学习计算的关键组件 即模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘 以及利用GPU实现显著的加速。 这些知识将使我们从深度学习“基础用户”变为“高级用户”。
参考书 《动手学深度学习》 一、层和块 在深度学习中层layer是构成神经网络的基本组件之一。每一层都包含一组神经元或称为节点这些神经元接收输入并输出一些值。不同类型的层可以实现不同的功能例如全连接层、卷积层、池化层等。 块block是由多个层组成的模块化结构。块可以看作是一个更高级的层它将多个层组合在一起并通过一些额外的操作如激活函数、正则化等来增加模型的灵活性和表达能力。块的设计可以帮助简化模型的结构提高模型的可读性和可维护性实现更为复杂的网络功能 层和块的概念在深度学习中非常重要它们可以帮助构建复杂的神经网络模型并提高模型的性能和效果。 在构造自定义块之前我们先回顾一下多层感知机的代码。 下面的代码生成一个网络其中包含一个具有256个单元和ReLU激活函数的全连接隐藏层 然后是一个具有10个隐藏单元且不带激活函数的全连接输出层。 import torch
from d2l import torch as d2l
from torch import nn
net nn.Sequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))
X torch.rand(2,20)
print(net(X))#结果
tensor([[ 0.0805, 0.0937, -0.0331, 0.1567, 0.0594, -0.3293, 0.1774, -0.0600,-0.0033, -0.3726],[ 0.0451, -0.0064, -0.1280, 0.0454, 0.2185, -0.2862, 0.1780, 0.0198,-0.0341, -0.5214]], grad_fnAddmmBackward0)
在这个例子中我们通过实例化nn.Sequential来构建我们的模型 层的执行顺序是作为参数传递的。
1.1. 自定义块 在实现我们自定义块之前我们简要总结一下每个块必须提供的基本功能 将输入数据作为其前向传播函数的参数。通过前向传播函数来生成输出。计算其输出关于输入的梯度可通过其反向传播函数进行访问。通常这是自动发生的。存储和访问前向传播计算所需的参数。根据需要初始化模型参数。
# 下面的MLP类继承了表示块的类
class MLP(nn.Module):# 用模型参数声明层。这里我们声明两个全连接的层def __init__(self):# 调用MLP的父类Module的构造函数来执行必要的初始化。# 这样在类实例化时也可以指定其他函数参数例如模型参数paramssuper().__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()
print(net(X))#结果
tensor([[-0.1863, -0.1673, 0.0547, 0.1255, -0.2258, -0.1138, -0.0232, 0.0543,0.0770, 0.0198],[-0.0964, -0.2234, 0.1306, 0.0934, -0.2134, -0.1488, 0.0052, 0.1475,0.1173, 0.0602]], grad_fnAddmmBackward0) 块的一个主要优点是它的多功能性。 我们可以子类化块以创建层如全连接层的类、 整个模型如上面的MLP类或具有中等复杂度的各种组件。 1.2. 顺序块 现在我们可以更仔细地看看Sequential类是如何工作的 回想一下Sequential的设计是为了把其他模块串起来。 为了构建我们自己的简化的MySequential 我们只需要定义两个关键函数 一种将块逐个追加到列表中的函数一种前向传播函数用于将输入按追加块的顺序传递给块组成的“链条”。 顺序块#下面的MySequential类提供了与默认Sequential类相同的功能。
class MySequential(nn.Module):def __init__(self,*args):super().__init__()for idx,module in enumerate(args):#这里module是Module子类的一个实例。我们把它保存在Module类的成员# 变量_modules中。_module的类型是OrderedDictself._modules[str(idx)] moduledef forward(self,X):# OrderedDict保证了按照成员添加的顺序遍历它们for block in self._modules.values():X block(X)return X#当MySequential的前向传播函数被调用时 每个添加的块都按照它们被添加的顺序执行。
#现在可以使用我们的MySequential类重新实现多层感知机。net MySequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))
#print(net(X))
1.3. 在前向传播函数中执行代码 Sequential类使模型构造变得简单 允许我们组合新的架构而不必定义自己的类。 然而并不是所有的架构都是简单的顺序架构。 当需要更强的灵活性时我们需要定义自己的块。 例如我们可能希望在前向传播函数中执行Python的控制流。 此外我们可能希望执行任意的数学运算 而不是简单地依赖预定义的神经网络层。 class FixedHiddenMLP(nn.Module):def __init__(self):super().__init__()# 不计算梯度的随机权重参数。因此其在训练期间保持不变self.rand_weight torch.rand((20,20),requires_grad True)self.linear nn.Linear(20,20)def forward(self,X):X self.linear(X)#使用创建的常量参数以及relu和mm函数X F.relu(torch.mm(X,self.rand_weight1)#复用全连接层这相当于两个全连接层共享参数X self.linear(X)#控制流while X.abs().sum() 1:X / 2return X.sum()在这个FixedHiddenMLP模型中我们实现了一个隐藏层 其权重self.rand_weight在实例化时被随机初始化之后为常量。 这个权重不是一个模型参数因此它永远不会被反向传播更新。 然后神经网络将这个固定层的输出通过一个全连接层。 注意在返回输出之前模型做了一些不寻常的事情 它运行了一个while循环在L1范数大于1的条件下 将输出向量除以2直到它满足条件为止。 最后模型返回了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())
print(chimera(X))#结果
tensor(0.1097, grad_fnSumBackward0)
1.4. 小结 一个块可以由许多层组成一个块可以由许多块组成。 块可以包含代码。 块负责大量的内部处理包括参数初始化和反向传播。 层和块的顺序连接由Sequential块处理。
二、参数管理 在选择了架构并设置了超参数后我们就进入了训练阶段。 此时我们的目标是找到使损失函数最小化的模型参数值。 经过训练后我们将需要使用这些参数来做出未来的预测。 之前的介绍中我们只依靠深度学习框架来完成训练的工作 而忽略了操作参数的具体细节。 接下来将介绍以下内容
访问参数用于调试、诊断和可视化参数初始化在不同模型组件间共享参数。
2.1. 参数访问 先看一下具有单隐藏层的多层感知机 import torch
from torch import nnnet nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X torch.rand(size(2, 4))
print(net(X))#结果
tensor([[-0.2487],[-0.1757]], grad_fnAddmmBackward0) 当通过Sequential类定义模型时 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样每层的参数都在其属性中。 如下所示我们可以检查第二个全连接层的参数。 print(net[2].state_dict())
#结果
OrderedDict([(weight, tensor([[-0.0454, -0.3495, 0.2659, 0.2088, 0.2453, 0.3455, 0.1744, -0.0094]])), (bias, tensor([-0.0710]))])
2.1.1. 目标参数
#下面的代码从第二个全连接层即第三个神经网络层提取偏置 提取后返回的是一个参数类实例并进一步访问该参数的值。print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)#结果
class torch.nn.parameter.Parameter
Parameter containing:
tensor([-0.0710], requires_gradTrue)
tensor([-0.0710])参数是复合的对象包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外我们还可以访问每个参数的梯度。 在上面这个网络中由于我们还没有调用反向传播所以参数的梯度处于初始状态。 print(net[2].weight.grad None)
#结果
True2.1.2. 一次性访问所有参数 下面我们将通过演示来比较访问第一个全连接层的参数和访问所有层。 print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])#结果
(weight, torch.Size([8, 4])) (bias, torch.Size([8]))
(0.weight, torch.Size([8, 4])) (0.bias, torch.Size([8])) (2.weight, torch.Size([1, 8])) (2.bias, torch.Size([1]))#这为我们提供了另一种访问网络参数的方式
print(net.state_dict()[2.bias].data)#结果
tensor([-0.0710])2.1.3. 从嵌套块收集参数 我们首先定义一个生成块的函数可以说是“块工厂”然后将这些块组合到更大的块中。 def block1():return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),nn.Linear(8, 4), nn.ReLU())def block2():net nn.Sequential()for i in range(4):# 在这里嵌套net.add_module(fblock {i}, block1())return netrgnet nn.Sequential(block2(), nn.Linear(4, 1))
print(rgnet(X))
print(rgnet)结果如下 因为层是分层嵌套的所以我们也可以像通过嵌套列表索引一样访问它们。 下面我们访问第一个主要的块中、第二个子块的第一层的偏置项。 print(rgnet[0][1][0].bias.data)
#结果
tensor([ 0.4090, -0.0318, 0.3773, 0.4772, 0.4299, -0.2994, 0.4322, 0.2694])
2.2. 参数初始化 知道了如何访问参数后现在我们看看如何正确地初始化参数。 深度学习框架提供默认随机初始化 也允许我们创建自定义初始化方法 满足我们通过其他规则实现初始化权重。 默认情况下PyTorch会根据一个范围均匀地初始化权重和偏置矩阵 这个范围是根据输入和输出维度计算出的。 PyTorch的nn.init模块提供了多种预置初始化方法。
2.2.1. 内置初始化
#内置初始化
#下面的代码将所有权重参数初始化为标准差为0.01的高斯随机变量 且将偏置参数设置为0。
def init_normal(m):if type(m) nn.Linear:nn.init.normal_(m.weight,mean 0,std0.01)nn.init.zeros_(m.bias)
net.apply(init_normal)
print(net[0].weight.data[0],net[0].bias.data[0])#我们还可以将所有参数初始化为给定的常数比如初始化为1
def init_constant(m):if type(m) nn.Linear:nn.init.constant_(m.weight, 1)nn.init.zeros_(m.bias)net.apply(init_constant)
print(net[0].weight.data[0], net[0].bias.data[0])
我们还可以对某些块应用不同的初始化方法.
例如下面我们使用Xavier初始化方法初始化第一个神经网络层 然后将第三个神经网络层初始化为常量值42。
def init_xavier(m):if type(m) nn.Linear:nn.init.xavier_uniform_(m.weight)
def init_42(m):if type(m) nn.Linear:nn.init.constant_(m.weight,42)net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)#结果
tensor([ 0.0060, -0.0043, 0.0009, 0.0124]) tensor(0.)
tensor([1., 1., 1., 1.]) tensor(0.)
tensor([-0.7014, -0.2135, 0.6632, 0.4671])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])
2.2.2. 自定义初始化 有时深度学习框架没有提供我们需要的初始化方法。 在下面的例子中我们使用以下的分布为任意权重参数 w w w定义初始化方法 w ∼ { U ( 5 , 10 ) 可能性 1 4 0 可能性 1 2 U ( − 10 , − 5 ) 可能性 1 4 \begin{aligned} w \sim \begin{cases} U(5, 10) \text{ 可能性 } \frac{1}{4} \\ 0 \text{ 可能性 } \frac{1}{2} \\ U(-10, -5) \text{ 可能性 } \frac{1}{4} \end{cases} \end{aligned} w∼⎩ ⎨ ⎧U(5,10)0U(−10,−5) 可能性 41 可能性 21 可能性 41
def my_init(m):if type(m) nn.Linear:print(init,*[(name,param.shape) for name,param in m.named_parameters()][0])nn.init.uniform_(m.weight,-10,10) #设置权重的初始值为-10到10之间的均匀分布m.weight.data * m.weight.data.abs() 5 #对权重进行截断操作将小于5的权重置为0。
net.apply(my_init)
print(net[0].weight[:2])#我们始终可以直接设置参数。
net[0].weight.data[:] 1
net[0].weight.data[0, 0] 42
print(net[0].weight.data[0])#结果
init weight torch.Size([8, 4])
init weight torch.Size([1, 8])
tensor([[-6.4263, 5.1428, -0.0000, -8.5624],[-9.4317, -0.0000, 0.0000, -0.0000]], grad_fnSliceBackward0)
tensor([42.0000, 6.1428, 1.0000, -7.5624])2.3. 参数绑定 有时我们希望在多个层间共享参数 我们可以定义一个稠密层然后使用它的参数来设置另一个层的参数。 #我们需要给共享层一个名称以便可以引用它的参数
shared nn.Linear(8,8)
net nn.Sequential(nn.Linear(4,8),nn.ReLU(),shared,nn.ReLU(),shared,nn.ReLU(),nn.Linear(8,1))net(X)
#检查参数是否相同
print(net[2].weight.data[0] net[4].weight.data[0])
net[2].weight.data[0,0] 100
# 确保它们实际上是同一个对象而不只是有相同的值
print(net[2].weight.data[0] net[4].weight.data[0])#结果
tensor([True, True, True, True, True, True, True, True])
tensor([True, True, True, True, True, True, True, True]) 这个例子表明第三个和第五个神经网络层的参数是绑定的。 它们不仅值相等而且由相同的张量表示。 因此如果我们改变其中一个参数另一个参数也会改变。 这里有一个问题当参数绑定时梯度会发生什么情况 答案是由于模型参数包含梯度因此在反向传播期间第二个隐藏层 即第三个神经网络层和第三个隐藏层即第五个神经网络层的梯度会加在一起。 尽管梯度加在一起可能看起来有点奇怪但在参数绑定的情况下这是正常的行为并且不会导致错误。 总结
本章了解了一下层和块的概念并且也深入了解了一下深度学习中的参数如何管理参数访问、参数初始化、参数绑定。
物或行或随或嘘或吹或强或羸或培或堕。是以圣人去甚去泰去奢
–2023-10-5 进阶篇