之前我们已经了解了神经网络中的层结构,现在引入块的概念
块可以描述单个层、由多个层组成的组件或者整个模型
我们可以将很多块组合成更大的组件,而这个过程通常是递归的
1.Module实现块
任何一个神经网络中的层都应该是Module的子类
回顾之前的多层感知机,输入到线性层,然后通过一个激活函数到输出层
# 多层感知机
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
print(net(X))
这里的我们可以手动实现多层感知机
# 多层感知机
class MLP(nn.Module): # 任何一个层都应该是Module的子类
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256) # 隐藏层
self.out = nn.Linear(256, 10) # 输出层
def forward(self, x):
return self.out(F.relu(self.hidden(x))) # 定义整个模型的前向运算
print(MLP(X))
2.顺序块
上面的nn.Sequential()
也可以手动实现如下
class MySequential(nn.Module): # 也需要集成Module类
def __init__(self, *args):
super().__init__()
for block in args:
self._modules[block] = block # 特殊的容器torch会知道放到里面的都是一些层,key就是module的名字 有序字典
def forward(self, X):
for block in self._modules.values(): # 就是按输入顺序计算
X = block(X)
return X
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
print(net(X))
然而有的时候这种顺序运行每个层或块的Sequential并不能完全满足我们的需求,因此,手动实现中我们可以加入定制化的操作,这也是手动实现的好处,使得我们网络的灵活性更强
class FixedHiddenMLP(nn.Module):
def __init__(self):
super().__init__()
self.rand_weight = torch.rand((20, 20), requires_grad=False) # 这里我们随便定义了一个weight
self.linear = nn.Linear(20, 20)
def forward(self, X): # 手动定义的好处可以在前向传播运算中自己实现一些步骤,更灵活
X = self.linear(X)
X = F.relu(torch.mm(X, self.rand_weight) + 1) # 随便举个例子
X = self.linear(X)
while X.abs().sum() > 1:
X /= 2
return X.sum()
net = FixedHiddenMLP()
print(net(X))
3.嵌套块
在网络中可以嵌套各种层和块
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()) # Sequential的输入可以是任何nn.Module的子类,这也就让我们可以嵌套各种模块
print(chimera(X))
手动定义块的关键就是 __init__() 和 forward() 两个函数
也就是参数和前向传播过程的定义
1.参数
# 单隐藏层的多层感知机
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
# 取出每一层的状态 事实上就是取出weight和bias,这里以输出层举例
print(net[2].state_dict())
print(net[2].bias.data)
# 取出全部的
net.named_parameters()
for name, param in net.named_parameters():
print(name, param)
2.网络结构
def block1():
return nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 4), nn.ReLU())
def block2():
net = nn.Sequential()
# 这里add_module和直接Sequential的区别是可以加一个字符串,以便后续显示层的名字
for i in range(4):
net.add_module(f'block {i}', block1())
return net
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
print(rgnet) # 可以方便的将网络结构打印出来
3.参数初始化
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01) # 下划线放后面表示这个函数不返回值,直接替换前边的变量,原地计算
# nn.init.constant_(m.weight, 1) # 将权重全部初始化为同一个常数的结果就是不会有梯度变化
nn.init.zeros_(m.bias)
net.apply(init_normal) # net中所有的module应用一下init_module函数
print(net[0].weight.data[0], net[0].bias.data[0])
# 我们需要给共享层一个名称,以便可以引用它的参数,这种在多个层之间共享权重的时候很有效
# 这里shared层无论添加多少个,无论在哪里,都是同一组参数的
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])
1.简单层
跟自定义块也没有什么本质区别,都是继承nn.Module然后重写 __init__() 和 forward() 函数
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
2.带参数的层
自定义的线性层,没本质区别,好处实验中才能得知了
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)
主要目的是为了保存模型或者中间结果,可以想一下保存什么内容可以存储模型训练的进度
当然可以保存整个模型,另外,我们也可以通过保存现有模型训练的所有参数,然后用代码生成模型结构来实现目标
1.保存张量
保存一个张量,并且重新加载
x = torch.arange(4)
torch.save(x, 'x-file') # 保存单一张量
x_save = torch.load('x-file') # 读取张量
print(x == x_save)
保存张量列表,并重新加载
y = torch.arange(4)
torch.save([x, y], 'x-y')
x_save, y_save = torch.load('x-y')
print(x_save == x, y_save == y)
保存张量字典,并重新加载
z = {'x': x, 'y': y}
torch.save(z, 'dict')
z_save = torch.load('dict')
2.保存模型
利用之前的state_dict()
函数来序列化存储参数
# 之前的MLP模型
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)
print(net.state_dict()) # 可以打印网络参数
torch.save(net.state_dict(), 'net.params')
clone = MLP()
clone.load_state_dict(torch.load('net.params'))
print(clone.eval())
Y_clone = clone(X)
print(Y_clone == Y)
将之前在CPU上存储的变量放到GPU中存储 很简单
import d2l.torch
import torch
from torch import nn
print(torch.device('cpu'), torch.cuda.device('cuda'), torch.cuda.device('cuda:0'))
print(torch.cuda.device_count()) # 查看有多少个可以用的GPU
x = torch.tensor([1, 2, 3], dtype=torch.float32)
print(x.device) # 默认是存储在CPU上
y = torch.ones(2, 3, device=d2l.torch.try_gpu(0)) # device可以选择存储位置,多个GPU中的一个
print(y)
z = x.cuda(d2l.torch.try_gpu()) # 用cuda方法移动到GPU上
print(z + y)
print(z[0])
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=d2l.torch.try_gpu()) # 将网络移动到GPU上,才能使用在GPU上存储的数据
print(net(z)) # net(x)就不行,因为x是存储在CPU上的
print(net[0].weight.data.device)
这一节基本就是在讲一些函数或者类的使用,遇到的时候查文档也可以