系列文章
李沐《动手学深度学习》预备知识 张量操作及数据处理
李沐《动手学深度学习》预备知识 线性代数及微积分
李沐《动手学深度学习》线性神经网络 线性回归
李沐《动手学深度学习》线性神经网络 softmax回归
李沐《动手学深度学习》多层感知机 模型概念和代码实现
李沐《动手学深度学习》多层感知机 深度学习相关概念
教材:李沐《动手学深度学习》
一、层和块
(一)块的概念
块(block):可以描述单个层、由多个层组成的组件或整个模型本身
- 使用块进行抽象的一个好处是可以将一些块组合成更大的组件, 这一过程通常是递归的;
- 通过定义代码来按需生成任意复杂度的块, 我们可以通过简洁的代码实现复杂的神经网络。
(二)块的实现
- 从编程的角度来看,块由类表示。每个块必须提供的基本功能:
- 将输入数据作为其前向传播函数的参数;
- 通过前向传播函数来生成输出;
- 计算其输出关于输入的梯度,可通过其反向传播函数进行访问,通常这是自动发生的;
- 存储和访问前向传播计算所需的参数;
- 根据需要初始化模型参数。
- 层和块的顺序连接由Sequential块处理:
- nn.Sequential定义了一种特殊的Module, 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表;
- 通过net(X)调用模型来获得模型的输出,这实际上是net.call(X)的简写。 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
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)
二、参数管理
具有单隐藏层的多层感知机:
import torch
from torch import nn
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
(一)参数访问:用于调试、诊断和可视化
当通过Sequential类定义模型时,可以通过索引来访问模型的任意层。
参数访问方式一:
print(net[2].state_dict()) #获得第二个全连接层的参数
print(type(net[2].bias)) #第二个全连接层偏置的类型
print(net[2].bias) #第二个全连接层的偏置 (参数是复合的对象,包含值、梯度和额外信息。)
print(net[2].bias.data) #第二个全连接层偏置的数值
参数访问方式二:
net.state_dict()['2.bias'].data
从嵌套块收集参数时,也可以像通过嵌套列表索引一样访问它们
#获得第一个主要的块中、第二个子块的第一层的偏置项。
rgnet[0][1][0].bias.data
(二)参数初始化(内置初始化、自定义初始化)
深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。
- 内置初始化:PyTorch的nn.init模块提供了多种预置初始化方法
调用内置初始化器,将所有权重参数初始化为标准差为0.01的高斯随机变量,偏置参数初始化为0:
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
net.apply(init_normal)
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)
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)
-
自定义初始化
使用以下的分布为任意权重参数 w w w定义初始化方法:
w { U ( 5 , 10 ) 可能性 0.25 0 可能性 0.5 U ( − 10 , − 5 ) 可能性 0.25 w \begin{cases} U(5,10) & \text{可能性 0.25 } \\ 0 & \text{可能性 0.5}\\ U(-10,-5) & \text{可能性 0.25} \end{cases} w⎩ ⎨ ⎧U(5,10)0U(−10,−5)可能性 0.25 可能性 0.5可能性 0.25
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)
m.weight.data *= m.weight.data.abs() >= 5
net.apply(my_init)
net[0].weight[:2]
- 也可以直接设置参数值
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]
(三)参数绑定:在不同模型组件间共享参数
为了在多个层间共享参数,可以定义一个稠密层,然后使用它的参数来设置另一个层的参数
- 第三个和第五个神经网络层的参数是绑定的,不仅值相等,而且由相同的张量表示;
- 改变其中一个参数,另一个参数也会改变;
- 反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。
# 我们需要给共享层一个名称,以便可以引用它的参数
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])
三、延后初始化
- 框架的延后初始化:直到数据第一次通过模型传递时,框架才会动态地推断出每个层的大小。
四、自定义层
(一)不带参数的层
构建一个CenteredLayer类,要从其输入中减去均值:
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
(二)带参数的层
实现自定义版本的全连接层:
- 需要两个参数,一个用于表示权重,另一个用于表示偏置项;
- 使用修正线性单元作为激活函数;
- in_units和units分别表示输入数和输出数。
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
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)
五、读写文件
(一)加载和保存张量
save和load函数可用于张量对象的文件读写:
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')
x2
(二)加载和保存模型参数
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
深度学习框架提供了内置函数来保存和加载整个网络,但是只会保存模型的参数而不是保存整个模型。 因为模型本身可以包含任意代码,所以模型本身难以序列化。 因此,为了恢复模型,需要用代码生成架构, 然后从磁盘加载参数。
#模型保存
torch.save(net.state_dict(), 'mlp.params')
#用代码生成架构
clone = MLP()
#加载保存好的参数
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
六、GPU
(一)计算设备
- 可以指定用于存储和计算的设备,如CPU和GPU。 默认情况下,张量是在内存中创建的,然后使用CPU计算它。
在PyTorch中,CPU和GPU可以用torch.device(‘cpu’) 和torch.device(‘cuda’)表示。 应该注意的是,cpu设备意味着所有物理CPU和内存, 这意味着PyTorch的计算将尝试使用所有CPU核心。 然而,gpu设备只代表一个卡和相应的显存。 如果有多个GPU,我们使用torch.device(f’cuda:{i}') 来表示第 i i i 块GPU(从0开始)。 另外,cuda:0和cuda是等价的。文章来源:https://www.toymoban.com/news/detail-814644.html
import torch
from torch import nn
torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
- 查询可用GPU的数量:
torch.cuda.device_count()
- 定义函数try_gpu,如果申请的GPU存在,就返回GPU(i),不存在就使用CPU
def try_gpu(i=0):
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
- 定义函数try_all_gpus,返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"
def try_all_gpus():
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
try_gpu(), try_gpu(10), try_all_gpus()
(二)张量与GPU
- 可以查询张量所在的设备。 默认情况下,张量是在CPU上创建的
x = torch.tensor([1, 2, 3])
x.device
#返回device(type='cpu')
- 将张量存储在GPU上
X = torch.ones(2, 3, device=try_gpu())
X
Y = torch.rand(2, 3, device=try_gpu(1))
Y
- 复制:对多个项进行操作时不同项目必须在同一个设备上
Z = X.cuda(1)
print(X)
print(Z)
#返回结果显示X在cuda0,Z在cuda1
(三)神经网络与GPU
神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上。文章来源地址https://www.toymoban.com/news/detail-814644.html
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
到了这里,关于李沐《动手学深度学习》深度学习计算的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!