深度学习——深度学习计算二
延续上一章的学习,本章继续记录深度学习计算的知识点。
参考书:
《动手学深度学习》
框架的延后初始化(defers initialization), 即直到数据第一次通过模型传递时,框架才会动态地推断出每个层的大小
在以后,当使用卷积神经网络时, 由于输入维度(即图像的分辨率)将影响每个后续层的维数, 有了该技术将更加方便。
延后初始化使框架能够自动推断参数形状,使修改模型架构变得容易,避免了一些常见的错误。 我们可以通过模型传递数据,使框架最终初始化参数。
当我们实例化一个多层感知机时:
一旦我们知道输入维数是20,框架可以通过代入值20来识别第一层权重矩阵的形状。 识别出第一层的形状后,框架处理第二层,依此类推,直到所有形状都已知为止。
注意,在这种情况下,只有第一层需要延迟初始化,但是框架仍是按顺序初始化的。 等到知道了所有的参数形状,框架就可以初始化参数。
深度学习成功背后的一个因素是神经网络的灵活性: 我们可以用创造性的方式组合不同的层,从而设计出适用于各种任务的架构。
有时我们会遇到或要自己发明一个现在在深度学习框架中还不存在的层。 在这些情况下,必须构建自定义层。
首先,我们构造一个没有任何参数的自定义层。
要构建它,我们只需继承基础层类并实现前向传播功能。
#下面的CenteredLayer类要从其输入中减去均值。 要构建它,我们只需继承基础层类并实现前向传播功能。
import torch
from torch import nn
import torch.nn.functional as F
class Centerdlayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self,x):
return x-x.mean()
layer = Centerdlayer()
print(layer(torch.FloatTensor([1,2,3,4,5])))
#现在,我们可以将层作为组件合并到更复杂的模型中
net = nn.Sequential(nn.Linear(8,128),Centerdlayer())
#作为额外的健全性检查,我们可以在向该网络发送随机数据后,检查均值是否为0。
#由于我们处理的是浮点数,因为存储精度的原因,我们仍然可能会看到一个非常小的非零数。
y = net(torch.rand(4,8))
print(y.mean())
#结果:
tensor([-2., -1., 0., 1., 2.])
tensor(-5.1223e-09, grad_fn=<MeanBackward0>)
让我们实现自定义版本的全连接层。该层需要两个参数,一个用于表示权重,另一个用于表示偏置项。
在此实现中,我们使用修正线性单元作为激活函数。 该层需要输入参数: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)
linear = Mylinear(5,3)
print(linear.weight)
#我们可以[使用自定义层直接执行前向传播计算]。
print(linear(torch.rand(2,5)))
#我们还可以(使用自定义层构建模型),就像使用内置的全连接层一样使用自定义层
net = nn.Sequential(Mylinear(64,8),Mylinear(8,1))
print(net(torch.rand(2,64)))
#结果:
Parameter containing:
tensor([[ 0.6645, -1.0066, -0.3903],
[ 1.7685, 1.4849, 0.2311],
[-0.1649, -0.9360, -0.5300],
[ 1.1137, 1.1452, -1.5475],
[ 0.4353, -0.8462, 0.3522]], requires_grad=True)
tensor([[2.6093, 0.0000, 0.3777],
[2.6460, 0.0000, 0.0000]])
tensor([[0.0000],
[0.4022]])
对于单个张量,我们可以直接调用load和save函数分别读写它们。
这两个函数都要求我们提供一个名称,save要求将要保存的变量作为输入。
#加载和保存张量
x = torch.arange(1,4)
print(x)
torch.save(x,"x-file")
#将存储在文件中的数据读回内存
x2 = torch.load("x-file")
print(x2)
#可以存储一个张量列表,再读回内存
y = torch.zeros_like(x)
torch.save([x,y],"x-files")
x2,y2 = torch.load("x-files")
print(x2,y2)
#也可以读取或写入从字符串映射到张量的字典
mydict = {"x":x,"y":y}
torch.save(mydict,"mydict")
mydict2 = torch.load("mydict")
print(mydict2)
print(mydict2["x"])
#结果:
tensor([1, 2, 3])
tensor([1, 2, 3])
tensor([1, 2, 3]) tensor([0, 0, 0])
{'x': tensor([1, 2, 3]), 'y': tensor([0, 0, 0])}
tensor([1, 2, 3])
需要注意的一个重要细节是,这将保存模型的参数而不是保存整个模型。
例如,如果我们有一个3层多层感知机,我们需要单独指定架构。 因为模型本身可以包含任意代码,所以模型本身难以序列化。
因此,为了恢复模型,我们需要用代码生成架构, 然后从磁盘加载参数。
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"))
print(clone.eval()) # eval()的主要作用是将字符串作为Python代码进行解析和执行
#由于两个实例具有相同的模型参数,在输入相同的X时, 两个实例的计算结果应该相同。 让我们来验证一下。
y_clone = clone(x)
print(y_clone == y)
#结果:
MLP(
(hidden): Linear(in_features=20, out_features=256, bias=True)
(output): Linear(in_features=256, out_features=10, bias=True)
)
tensor([[True, True, True, True, True, True, True, True, True, True],
[True, True, True, True, True, True, True, True, True, True]])
在PyTorch中,CPU和GPU可以用torch.device(‘cpu’) 和torch.device(‘cuda’)表示。
如果有多个GPU,我们使用torch.device(f’cuda:{i}') 来表示第 块GPU( 从0开始)。 另外,cuda:0和cuda是等价的。
import torch
from torch import nn
print(torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1'))
#查询可用gpu的数量
print(torch.cuda.device_count())
#这两个函数允许我们在不存在所需所有GPU的情况下运行代码
def try_gpu(i=0): #@save
"""如果存在,则返回gpu(i),否则返回cpu()"""
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus(): #@save
"""返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"""
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
查询张量所在的设备。默认情况下,张量是在CPU上创建的。
x = torch.tensor([1, 2, 3])
print(x.device)
我们可以在创建张量时指定存储设备,一般来说,我们需要确保不创建超过GPU显存限制的数据
X = torch.ones(2, 3, device=try_gpu())
print(X)
深度学习框架要求计算的所有输入数据都在同一设备上,无论是CPU还是GPU
类似地,神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
#当输入为GPU上的张量时,模型将在同一GPU上计算结果
print(net(X))
print(net[0].weight.data.device)
本章简单记录了一下深度学习计算中要注意的一些问题,延后初始化概念,对层的自定义,对模型或参数的加载和保存,利用GPU进行计算等。
果而勿矜,果而勿伐,果而勿骄,果而不得已,果而勿强。
–2023-10-6 进阶篇