51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

PyTorch 自动微分模块

自动微分(Autograd)模块对张量做了进一步的封装,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,在神经网络的反向传播过程中,Autograd 模块基于正向计算的结果对当前的参数进行微分计算,从而实现网络权重参数的更新。

  1. 梯度基本计算 {#title-0} ========================

我们使用 backward 方法、grad 属性来实现梯度的计算和访问.

import torch

1. 单标量梯度的计算

y = x**2 + 20

def test01():

# 定义需要求导的张量
# 张量的值类型必须是浮点类型
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
# 变量经过中间运算
f = x ** 2 + 20
# 自动微分
f.backward()
# 打印 x 变量的梯度
# backward 函数计算的梯度值会存储在张量的 grad 变量中
print(x.grad)

2. 单向量梯度的计算

y = x**2 + 20

def test02():

# 定义需要求导张量
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)
# 变量经过中间计算
f1 = x ** 2 + 20

# 注意:
# 由于求导的结果必须是标量
# 而 f 的结果是: tensor([120., 420.])
# 所以, 不能直接自动微分
# 需要将结果计算为标量才能进行计算
f2 = f1.mean()  # f2 = 1/2 * x

# 自动微分
f2.backward()

# 打印 x 变量的梯度
print(x.grad)

3. 多标量梯度计算

y = x1 ** 2 + x2 ** 2 + x1*x2

def test03():

# 定义需要计算梯度的张量
x1 = torch.tensor(10, requires_grad=True, dtype=torch.float64)
x2 = torch.tensor(20, requires_grad=True, dtype=torch.float64)

# 经过中间的计算
y = x1**2 + x2**2 + x1*x2

# 将输出结果变为标量
y = y.sum()

# 自动微分
y.backward()

# 打印两个变量的梯度
print(x1.grad, x2.grad)

4. 多向量梯度计算

def test04():

# 定义需要计算梯度的张量
x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)
x2 = torch.tensor([30, 40], requires_grad=True, dtype=torch.float64)

# 经过中间的计算
y = x1 ** 2 + x2 ** 2 + x1 * x2
print(y)

# 将输出结果变为标量
y = y.sum()

# 自动微分
y.backward()

# 打印两个变量的梯度
print(x1.grad, x2.grad)

if name == 'main': test04()

  1. 控制梯度计算 {#title-1} ========================

我们可以通过一些方法使得在 requires_grad=True 的张量在某些时候计算不进行梯度计算。

import torch

1. 控制不计算梯度

def test01():

x = torch.tensor(10, requires_grad=True, dtype=torch.float64)
print(x.requires_grad)

# 第一种方式: 对代码进行装饰
with torch.no_grad():
    y = x ** 2
print(y.requires_grad)

# 第二种方式: 对函数进行装饰
@torch.no_grad()
def my_func(x):
    return x ** 2
print(my_func(x).requires_grad)


# 第三种方式
torch.set_grad_enabled(False)
y = x ** 2
print(y.requires_grad)

2. 注意: 累计梯度

def test02():

# 定义需要求导张量
x = torch.tensor([10, 20, 30, 40], requires_grad=True, dtype=torch.float64)

for _ in range(3):

    f1 = x ** 2 + 20
    f2 = f1.mean()

    # 默认张量的 grad 属性会累计历史梯度值
    # 所以, 需要我们每次手动清理上次的梯度
    # 注意: 一开始梯度不存在, 需要做判断
    if x.grad is not None:
        x.grad.data.zero_()

    f2.backward()
    print(x.grad)

6. 梯度下降优化最优解

def test03():

# y = x**2
x = torch.tensor(10, requires_grad=True, dtype=torch.float64)

for _ in range(5000):

    # 正向计算
    f = x ** 2

    # 梯度清零
    if x.grad is not None:
        x.grad.data.zero_()

    # 反向传播计算梯度
    f.backward()

    # 更新参数
    x.data = x.data - 0.001 * x.grad

    print('%.10f' % x.data)

if name == 'main': test03()

  1. 梯度计算注意 {#title-2} ====================

当对设置 requires_grad=True 的张量使用 numpy 函数进行转换时, 会出现如下报错:

Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.

此时, 需要先使用 detach 函数将张量进行分离, 再使用 numpy 函数.
注意: detach 之后会产生一个新的张量, 新的张量作为叶子结点,并且该张量和原来的张量共享数据, 但是分离后的张量不需要计算梯度。

import torch

1. detach 函数用法

def test01():

x = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)

# Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
# print(x.numpy())  # 错误
print(x.detach().numpy())  # 正确

2. detach 前后张量共享内存

def test02():

x1 = torch.tensor([10, 20], requires_grad=True, dtype=torch.float64)

# x2 作为叶子结点
x2 = x1.detach()

# 两个张量的值一样: 140421811165776 140421811165776
print(id(x1.data), id(x2.data))
x2.data = torch.tensor([100, 200])
print(x1)
print(x2)

# x2 不会自动计算梯度: False
print(x2.requires_grad)

if name == 'main': test02()

赞(2)
未经允许不得转载:工具盒子 » PyTorch 自动微分模块