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

系列文章

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

目录


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

一、层和块

(一)块的概念

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

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

(二)块的实现

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

二、参数管理

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

python 复制代码
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类定义模型时,可以通过索引来访问模型的任意层。
参数访问方式一:

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

参数访问方式二:

python 复制代码
net.state_dict()['2.bias'].data

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

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

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

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

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

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

python 复制代码
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:

python 复制代码
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:

python 复制代码
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
python 复制代码
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. 也可以直接设置参数值
python 复制代码
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]

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

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

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

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

(二)带参数的层

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

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

五、读写文件

(一)加载和保存张量

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

python 复制代码
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

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

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)

深度学习框架提供了内置函数来保存和加载整个网络,但是只会保存模型的参数而不是保存整个模型。 因为模型本身可以包含任意代码,所以模型本身难以序列化。 因此,为了恢复模型,需要用代码生成架构, 然后从磁盘加载参数。

python 复制代码
#模型保存
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是等价的。

python 复制代码
import torch
from torch import nn

torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
  1. 查询可用GPU的数量:
python 复制代码
torch.cuda.device_count()
  1. 定义函数try_gpu,如果申请的GPU存在,就返回GPU(i),不存在就使用CPU
python 复制代码
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(),]"
python 复制代码
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上创建的
python 复制代码
x = torch.tensor([1, 2, 3])
x.device
#返回device(type='cpu')
  1. 将张量存储在GPU上
python 复制代码
X = torch.ones(2, 3, device=try_gpu())
X
Y = torch.rand(2, 3, device=try_gpu(1))
Y
  1. 复制:对多个项进行操作时不同项目必须在同一个设备上
python 复制代码
Z = X.cuda(1)
print(X)
print(Z)
#返回结果显示X在cuda0,Z在cuda1

(三)神经网络与GPU

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

python 复制代码
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu())
相关推荐
NAGNIP几秒前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab1 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab1 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP5 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年5 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼5 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS5 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区7 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈7 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang7 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx