目录
一、延后初始化
在学习前面章节的时候,不知道你们有没有这种感觉,这些张量的形状自己总是搞不明白。作者一开始是这样的,后来代码写多了才渐渐理解的。
可在遇到这些东西的时候,还是要稍微停下来时间思考一下。
那是因为,到目前为止,我们忽略了建立网络时需要做的以下这些事情:
-
我们定义了网络架构,但没有指定输入维度。
-
我们添加层时没有指定前一层的输出维度。
-
我们在初始化参数时,甚至没有足够的信息来确定模型应该包含多少参数
既然提出来问题了,那肯定是有解决办法的。这个问题的解决办法就是:框架的延后初始化
有了该技术将更加方便。 现在我们在编写代码时无须知道维度是什么就可以设置参数, 这种能力可以大大简化定义和修改模型的任务。
1.1实例化网络
实例化一个感知机:
python
import torch
import torch.nn as nn
net = nn.Sequential(nn.LazyLinear(256),
nn.ReLU(),
nn.LazyLinear(10))
print(net[0].weight)
print(net)
这里的LazyLinear()就是延后初始化,我们不知道输入,只知道输出。
运行结果:
<UninitializedParameter>
Sequential(
(0): LazyLinear(in_features=0 , out_features=256, bias=True)
(1): ReLU()
(2): LazyLinear(in_features=0 , out_features=10, bias=True)
)
我们给这个网络一个特征值。
python
X = torch.rand(2,20)
net(X)
print(net[0].weight)
print(net)
运行结果:
Parameter containing:
tensor(\[-0.1142, -0.1500, -0.2170, ..., -0.0608, -0.0959, 0.0969,
-0.1418, 0.1375, 0.2137, ..., 0.1531, -0.0622, 0.1335,
-0.1582, -0.0558, 0.1994, ..., -0.0048, -0.0288, -0.1821,
...,
0.1771, -0.0401, 0.1677, ..., 0.1472, 0.1971, -0.1043,
-0.0685, -0.1094, -0.0304, ..., 0.2133, -0.2186, -0.1285,
0.1542, 0.0978, 0.0081, ..., -0.1878, -0.1788, 0.1976],
requires_grad=True)
Sequential(
(0): Linear(in_features=20 , out_features=256, bias=True)
(1): ReLU()
(2): Linear(in_features=256 , out_features=10, bias=True)
)
可以看到,发生了变化,这就是pytorch的延后初始化。
二、自定义层
我们可以用创造性的方式组合不同的层,从而设计出适用于各种任务的架构。 但还是那个问题,再厉害的框架,也不可能包揽所有种类的层。
这个时候,就需要我们手搓一个我们专用的层,也就是自定义层。
2.1不带参数的层
这是一个把输入减去输入的均值的层,虽然可能在实际中没有任何意义,但是这里的重点在于,PyTorch框架里面没有。
python
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()
让我们验证它是否能按预期工作。
python
layer = CenteredLayer()
print(layer(torch.FloatTensor([1, 2, 3, 4, 5])))
运行结果:
tensor(-2., -1., 0., 1., 2.)
我们可以像5.1节那样,把它合并到更复杂的模型中,但在这里就不掩饰了。
2.2带参数的层
回想一下我们之前学过的那些全连接层,需要两个参数,一个用于表示权重,另一个用于表示偏置项。该层需要输入参数:in_units和units,分别表示输入数和输出数。
python
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)))#向前传播
matmul()是矩阵乘法。
我们使用修正线性单元(ReLU)作为激活函数。
除此之外,应该没有什么需要解释的地方了。
运行结果:
tensor(\[-1.2881, -1.2804, 0.7337,
-0.5448, -1.2296, -0.8849,
-0.0550, -0.0579, 0.2759,
-0.2484, -1.8061, 0.4572,
0.2487, -0.8685, 0.1223], requires_grad=True)
tensor(\[0.5101, 2.0328, 2.0510,
0.8924, 0.0000, 0.0000])
三、读写文件
目前为止,我们讨论了如何处理数据, 以及如何构建、训练和测试深度学习模型。
但是,我们目前训练的模型,都存储在内存中。程序运行停止之后就没有了。
我们目前手搓的模型体量都很小,但是在训练FashionMINIST的时候,已经感觉有些吃力了。那些数以亿万计的模型消耗的时间成本更是不知多少。
所以,现在是时候学习如何加载和存储权重向量和整个模型了。
3.1加载和保存张量
对于张量,我们使用load和save方法进行加载和保存。
单个张量的保存和加载:
python
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')#保存
print(torch.load('x-file'))#加载
运行结果:
tensor(0, 1, 2, 3)
张量列表也是用这个方法:
python
y = torch.zeros(4)
torch.save([x,y],'x-files')
print(torch.load('x-files'))
运行结果:
tensor(\[0, 1, 2, 3\]), tensor(\[0., 0., 0., 0.\])
我们还可以写入或读取从字符串映射到张量的字典 。 当我们要读取或写入模型中的所有权重时,这很方便。
python
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
print(mydict2)
运行结果:
{'x': tensor(0, 1, 2, 3), 'y': tensor(0., 0., 0., 0.)}
3.2加载保存模型参数
如果我们想要保存整个模型,只靠上面学到的是不够的,因为模型可能有数百个参数散布在各处。
于是,深度学习框架提供了内置函数来保存和加载整个网络。
但要注意,这将保存模型的参数而不是保存整个模型。
因为模型本身可以包含任意代码,所以模型本身难以序列化。为了恢复模型,我们需要用代码生成架构, 然后从磁盘加载参数。
为了理解上面的话,我们来创建一个多层感知机演示一下。
python
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')#保存参数
这个是我们常用的单个隐藏层,隐藏层经过relu函数激活的多层感知机。
我们调用save方法,通过**.state_dict()**获取参数字典,保存在mlp.params参数中。
python
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
print(clone)
现在我们重新创建一个同类型的多层感知机clone,然后让他加载保存的参数。
运行结果:
MLP(
(hidden): Linear(in_features=20, out_features=256, bias=True)
(output): Linear(in_features=256, out_features=10, bias=True)
)
两个实例具有相同的模型参数,在输入相同的X时, 两个实例的计算结果应该相同。 让我们来验证一下。
python
Y_clone = clone(X)
print(Y_clone == Y)
运行结果:
tensor(\[True, True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True, True])