PyTorch学习笔记4—— 深度学习计算

在这里插入图片描述

4.1 模型构造

在线性回归和softmax回归中已经陆续用到了这些方法,这里系统回顾。

4.1.2 最简单的开始分析

import torch
from torch import nn
from torch.nn import functional as F

net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

X = torch.rand(2, 20)
net(X)

在这个例子中,我们通过实例化nn.Sequential来构建我们的模型,层的执行顺序是作为参数传递的。简而言之,nn.Sequential定义了一种特殊的Module,即在PyTorch中表示一个块的类。它维护了一个由Module组成的有序列表,注意,两个全连接层都是Linear类的实例,Linear类本身就是Module的子类。正向传播(forward)函数也非常简单:它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。注意,到目前为止,我们一直在通过net(X)调用我们的模型来获得模型的输出。这实际上是net.call(X)的简写。

4.1.2 继承MODULE类来构造模型(自定义块)

Module类是nn模块里提供的一个模型构造类,是所有神经网络模块的基类。此处用继承Module类来构造本节开头提到的MLP,这里重载了Module类的__init__函数和forward函数,分别用于创建模型参数和定义前向计算。

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):
        # 调用`MLP`的父类`Block`的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数`params`(稍后将介绍)
        super().__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)))

以上的MLP类无需定义反向传播,pytorch中会自动求梯度而自动生成反向传播所需要的forward函数来完成前向计算

我们还可以实例化MLP类得到模型变量net, 下面的代码初始化net并传入输入数据x做一次前向计算。 其中,net(X)会调用MLP继承自Module类的_call_函数,此函数将调用MLP类定义的forward函数完成前向计算

我们在构造函数中实例化多层感知机的层,然后在每次调用正向传播函数时调用这些层。注意一些关键细节。首先,我们定制的__init__函数通过super().init()调用父类的__init__函数,省去了重复编写适用于大多数块的模版代码的痛苦。然后我们实例化两个全连接层,分别为self.hidden和self.out。注意,除非我们实现一个新的运算符,否则我们不必担心反向传播函数或参数初始化,系统将自动生成这些。

net = MLP()
net(X)

output:

tensor([[-0.2297, -0.0396, -0.3351,  0.0677,  0.2896, -0.0685, -0.1796, -0.2028,
          0.1920, -0.2273],
        [-0.1520, -0.0249, -0.3276, -0.0050,  0.1500, -0.0456, -0.0526, -0.1227,
          0.1207, -0.1618]], grad_fn=<AddmmBackward>)

4.1.3 顺序块

为了构建我们自己的简化的MySequential,我们只需要定义两个关键函数: 1. 一种将块逐个追加到列表中的函数。 2. 一种正向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。

下面的MySequential类提供了与默认Sequential类相同的功能。

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for block in args:
            # 这里,`block`是`Module`子类的一个实例。我们把它保存在'Module'类的成员变量
            # `_children` 中。`block`的类型是OrderedDict。
            self._modules[block] = block

    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X

我们在之前也学习过

net = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Linear(256, 10),
)
print(net)

output:

Sequential(
  (0): Linear(in_features=784, out_features=256, bias=True)
  (1): ReLU()
  (2): Linear(in_features=256, out_features=10, bias=True)
)

4.1.4 嵌套调用

'''第一个块'''
class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变。
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及`relu`和`dot`函数。
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数。
        X = self.linear(X)
        # 控制流
        while X.abs().sum() > 1:
            X /= 2
        return X.sum()


'''第二个块'''
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)

做个总结,

层也是块。

一个块可以由许多层组成。

一个块可以由许多块组成。

块可以包含代码。

块负责大量的内部处理,包括参数初始化和反向传播。

层和块的顺序连接由Sequential块处理。

虽然Sequential可以使得模型构造简单,但是直接继承Module类可以扩展模型构造的灵活性

4.2 模型参数的访问、初始化和共享

我们先定义一个含单隐藏层的多层感知机,依旧使用默认方式初始化参数,并做一次前向计算;我们从nn导入了init模块,包含多种模型初始化方法

import torch
from torch import nn
from torch.nn import init

net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1)) #已经默认初始化
print(net)

X = torch.rand(2, 4)
Y = net(X).sum()

print(Y)

output:

Sequential(
  (0): Linear(in_features=4, out_features=3, bias=True)
  (1): ReLU()
  (2): Linear(in_features=3, out_features=1, bias=True)
)
tensor(0.9349, grad_fn=<SumBackward0>)

4.2.1 访问模型参数

我们可以通过Module类的parameters()或者named_parameters方法来访问所有参数,后者除了返回参数还会返回其名字

print(type(net.named_parameters()))
for name, param in net.named_parameters():
    print(name, param.size())

output:

<class 'generator'>
0.weight torch.Size([3, 4])
0.bias torch.Size([3])
2.weight torch.Size([1, 3])
2.bias torch.Size([1])

可以看出,返回的名字自动加上了层数的索引作为前缀;对于使用Sequential类构造的神经网络,可以用方括号[]访问任一层

for name, param in net[0].named_parameters():
    print(name, param.size(), type(param))

output:

weight torch.Size([3, 4]) <class 'torch.nn.parameter.Parameter'>
bias torch.Size([3]) <class 'torch.nn.parameter.Parameter'>

param返回的类型是torch.nn.parameter.Paremeter,这是Tensor的子类;如果一个Tensor是Parameter,将会自动被添加到模型的参数列表里

4.2.2 初始化参数

如果在init模块中没有想要的初始化方法,就要自己实现,这一节内容书上较为详尽,用时看书即可

4.2.3 共享模型参数

方法:1.Module类的forward函数里多次调用同一个层
2.如果传入Sequential的模块是同一个Module实例,参数也是共享的

4.2.4 自定义层

可以利用Module类自定义层,从而被反复调用
这一节建议参考第二版书

4.2.5 读写文件

4.2.5.1 加载和保存张量

对于单个张量,我们可以直接调用load和save函数分别读写它们。这两个函数都要求我们提供一个名称,save要求将要保存的变量作为输入。

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")
print(x2)

output:

tensor([0, 1, 2, 3])

我们可以存储一个张量列表,然后把它们读回内存。

x = torch.arange(4)
y = torch.zeros(4)
torch.save([x, y], 'x-files')
x2, y2 = torch.load('x-files')
print((x2, y2))

ouput:

(tensor([0, 1, 2, 3]), tensor([0., 0., 0., 0.]))

甚至可以写入或读取从字符串映射到张量的字典。当我们要读取或写入模型中的所有权重时,这很方便。

x = torch.arange(4)
y = torch.zeros(4)
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
print(mydict2['x'])
print(mydict2['y'])
print(mydict2)

output:

tensor([0, 1, 2, 3])
tensor([0., 0., 0., 0.])
{'x': tensor([0, 1, 2, 3]), 'y': tensor([0., 0., 0., 0.])}

4.2.5.2 加载和保存模型参数

深度学习框架提供了内置函数来保存和加载整个网络。需要注意的一个重要细节是,这将保存模型的参数而不是保存整个模型。例如,如果我们有一个3层多层感知机,我们需要单独指定结构。因为模型本身可以包含任意代码,所以模型本身难以序列化。因此,为了恢复模型,我们需要用代码生成结构,然后从磁盘加载参数。让我们从熟悉的多层感知机开始尝试一下。

在pytorch中,Module的可学习参数, 模块模型包含在参数中。state_dict是一个从参数名称映射到参数Tensor的字典对象

class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.hidden = nn.Linear(3, 2)
        self.act = nn.ReLU()
        self.output = nn.Linear(2, 1)

    def forward(self, x):
        a = self.act(self.hidden(x))
        return self.output(a)

net = MLP()
print(net.state_dict())

output:

OrderedDict([('hidden.weight', tensor([[-0.3529,  0.4916,  0.5747],
        [ 0.2375, -0.1873,  0.4313]])), ('hidden.bias', tensor([ 0.3667, -0.4421])), ('output.weight', tensor([[0.0303, 0.2033]])), ('output.bias', tensor([-0.7012]))])

只有具有可学习参数的层(卷积层,线性层)才有state_dict中的条目;优化器(optim)也有一个state_dict,其中包含关于优化器状态以及所使用的超参数信息

 optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

output:

<bound method Optimizer.state_dict of SGD (
Parameter Group 0
    dampening: 0
    lr: 0.001
    momentum: 0.9
    nesterov: False
    weight_decay: 0
)>

4.2.5.3 保存和加载模型

有两种方法:
1.仅保存和加载模型参数(state_dict);
2.保存和加载整个模型

1.保存和加载state_dict (这是较为推荐的方法)

'''保存'''
torch.save(model.state_dict(), PATH) #推荐后缀名是pt或者pth

'''加载'''
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))

2.保存和加载整个模型

'''保存'''
torch.save(model, PATH)

'''加载'''
model = torch.load(PATH)

实例展示:

X = torch.randn(2, 3)
Y = net(X)
PATH = "./net.pt"
torch.save(net.state_dict(), PATH)

net2 = MLP()
net2.load_state_dict(torch.load(PATH))
Y2 = net2(X)
Y2 == Y

OUT:

tensor([[True],
        [True]])

由于net和net2有一样的模型参数,故对于一个x进行计算,结果是相同的。

4.3 GPU计算

由于本人电脑是CPU型,故等用到GPU时,再来更新这部分内容。

发表评论

项目已添加到购物车。
0 项 - ¥0.00