李沐《动手学深度学习》深度学习计算

系列文章

李沐《动手学深度学习》预备知识 张量操作及数据处理
李沐《动手学深度学习》预备知识 线性代数及微积分
李沐《动手学深度学习》线性神经网络 线性回归
李沐《动手学深度学习》线性神经网络 softmax回归
李沐《动手学深度学习》多层感知机 模型概念和代码实现
李沐《动手学深度学习》多层感知机 深度学习相关概念

目录

  • 系列文章
  • 一、层和块
    • (一)块的概念
    • (二)块的实现
  • 二、参数管理
    • (一)参数访问:用于调试、诊断和可视化
    • (二)参数初始化(内置初始化、自定义初始化)
    • (三)参数绑定:在不同模型组件间共享参数
  • 三、延后初始化
  • 四、自定义层
    • (一)不带参数的层
    • (二)带参数的层
  • 五、读写文件
    • (一)加载和保存张量
    • (二)加载和保存模型参数
  • 六、GPU
    • (一)计算设备
    • (二)张量与GPU
    • (三)神经网络与GPU


教材:李沐《动手学深度学习》

一、层和块

(一)块的概念

块(block):可以描述单个层、由多个层组成的组件或整个模型本身

  • 使用块进行抽象的一个好处是可以将一些块组合成更大的组件, 这一过程通常是递归的;
  • 通过定义代码来按需生成任意复杂度的块, 我们可以通过简洁的代码实现复杂的神经网络。

李沐《动手学深度学习》深度学习计算_第1张图片

(二)块的实现

  1. 从编程的角度来看,块由类表示。每个块必须提供的基本功能:
    • 将输入数据作为其前向传播函数的参数
    • 通过前向传播函数来生成输出
    • 计算其输出关于输入的梯度,可通过其反向传播函数进行访问,通常这是自动发生的;
    • 存储和访问前向传播计算所需的参数;
    • 根据需要初始化模型参数。
  2. 层和块的顺序连接由Sequential块处理:
    • nn.Sequential定义了一种特殊的Module, 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表;
    • 通过net(X)调用模型来获得模型的输出,这实际上是net.call(X)的简写。 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)
  1. 一个块可以由许多层组成;一个块可以由许多块组成。
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)

二、参数管理

具有单隐藏层的多层感知机:

import torch
from torch import nn

net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)

(一)参数访问:用于调试、诊断和可视化

当通过Sequential类定义模型时,可以通过索引来访问模型的任意层。
参数访问方式一:

print(net[2].state_dict()) #获得第二个全连接层的参数 
print(type(net[2].bias)) #第二个全连接层偏置的类型
print(net[2].bias) #第二个全连接层的偏置 (参数是复合的对象,包含值、梯度和额外信息。)
print(net[2].bias.data) #第二个全连接层偏置的数值

参数访问方式二:

net.state_dict()['2.bias'].data

从嵌套块收集参数时,也可以像通过嵌套列表索引一样访问它们

#获得第一个主要的块中、第二个子块的第一层的偏置项。
rgnet[0][1][0].bias.data

(二)参数初始化(内置初始化、自定义初始化)

深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。

  1. 内置初始化:PyTorch的nn.init模块提供了多种预置初始化方法

调用内置初始化器,将所有权重参数初始化为标准差为0.01的高斯随机变量,偏置参数初始化为0:

def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)
        nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]

调用内置初始化器,将参数初始化为1:

def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]

使用Xavier初始化第一个神经网络层,将第三个神经网络层初始化为常量值42:

def init_xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)

net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)
  1. 自定义初始化
    使用以下的分布为任意权重参数 w w w定义初始化方法:
    w { U ( 5 , 10 ) 可能性 0.25  0 可能性 0.5 U ( − 10 , − 5 ) 可能性 0.25 w \begin{cases} U(5,10) & \text{可能性 0.25 } \\ 0 & \text{可能性 0.5}\\ U(-10,-5) & \text{可能性 0.25} \end{cases} w U(5,10)0U(10,5)可能性 0.25 可能性 0.5可能性 0.25
def my_init(m):
    if type(m) == nn.Linear:
        print("Init", *[(name, param.shape)
                        for name, param in m.named_parameters()][0])
        nn.init.uniform_(m.weight, -10, 10)
        m.weight.data *= m.weight.data.abs() >= 5

net.apply(my_init)
net[0].weight[:2]
  1. 也可以直接设置参数值
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]

(三)参数绑定:在不同模型组件间共享参数

为了在多个层间共享参数,可以定义一个稠密层,然后使用它的参数来设置另一个层的参数

  • 第三个和第五个神经网络层的参数是绑定的,不仅值相等,而且由相同的张量表示;
  • 改变其中一个参数,另一个参数也会改变;
  • 反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。
# 我们需要给共享层一个名称,以便可以引用它的参数
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])

三、延后初始化

  • 框架的延后初始化:直到数据第一次通过模型传递时,框架才会动态地推断出每个层的大小。

四、自定义层

(一)不带参数的层

构建一个CenteredLayer类,要从其输入中减去均值:

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

(二)带参数的层

实现自定义版本的全连接层:

  • 需要两个参数,一个用于表示权重,另一个用于表示偏置项;
  • 使用修正线性单元作为激活函数;
  • 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)

五、读写文件

(一)加载和保存张量

save和load函数可用于张量对象的文件读写:

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')
x2

(二)加载和保存模型参数

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'))
clone.eval()

六、GPU

(一)计算设备

  1. 可以指定用于存储和计算的设备,如CPU和GPU。 默认情况下,张量是在内存中创建的,然后使用CPU计算它。

在PyTorch中,CPU和GPU可以用torch.device(‘cpu’) 和torch.device(‘cuda’)表示。 应该注意的是,cpu设备意味着所有物理CPU和内存, 这意味着PyTorch的计算将尝试使用所有CPU核心。 然而,gpu设备只代表一个卡和相应的显存。 如果有多个GPU,我们使用torch.device(f’cuda:{i}') 来表示第 i i i 块GPU(从0开始)。 另外,cuda:0和cuda是等价的。

import torch
from torch import nn

torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
  1. 查询可用GPU的数量:
torch.cuda.device_count()
  1. 定义函数try_gpu,如果申请的GPU存在,就返回GPU(i),不存在就使用CPU
def try_gpu(i=0):  
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')
  1. 定义函数try_all_gpus,返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"
def try_all_gpus():  
    devices = [torch.device(f'cuda:{i}')
             for i in range(torch.cuda.device_count())]
    return devices if devices else [torch.device('cpu')]

try_gpu(), try_gpu(10), try_all_gpus()

(二)张量与GPU

  1. 可以查询张量所在的设备。 默认情况下,张量是在CPU上创建的
x = torch.tensor([1, 2, 3])
x.device
#返回device(type='cpu')
  1. 将张量存储在GPU上
X = torch.ones(2, 3, device=try_gpu())
X
Y = torch.rand(2, 3, device=try_gpu(1))
Y
  1. 复制:对多个项进行操作时不同项目必须在同一个设备上
Z = X.cuda(1)
print(X)
print(Z)
#返回结果显示X在cuda0,Z在cuda1

(三)神经网络与GPU

神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上。

net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())

你可能感兴趣的:(李沐《动手学深度学习》学习笔记,深度学习,人工智能,pytorch,算法)