动手学深度学习v2笔记-Day8-深度学习计算

动手学深度学习v2

Day 8

内容均为本人现阶段学习中浅显的理解,如有任何错误,欢迎指出

本章以及之后内容均运行在NVIDIA独立显卡的机器上


0x00 层和块

之前我们已经了解了神经网络中的结构,现在引入的概念
块可以描述单个层、由多个层组成的组件或者整个模型
我们可以将很多块组合成更大的组件,而这个过程通常是递归的
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() 两个函数
也就是参数和前向传播过程的定义

0x01 参数管理

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])

0x02 自定义层

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)

0x03 读写文件

主要目的是为了保存模型或者中间结果,可以想一下保存什么内容可以存储模型训练的进度
当然可以保存整个模型,另外,我们也可以通过保存现有模型训练的所有参数,然后用代码生成模型结构来实现目标
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)

0x04 GPU

将之前在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)

这一节基本就是在讲一些函数或者类的使用,遇到的时候查文档也可以

你可能感兴趣的:(动手学深度学习v2笔记,深度学习,python,神经网络,人工智能,pytorch)