PyTorch基础3

时间:2024-11-24 07:10:15

目录

自动微分

1. 基础概念

2. 计算梯度

2.1 标量梯度计算

2.2 向量梯度计算

2.3 多标量梯度计算

2.4 多向量梯度计算

3. 梯度上下文控制

3.1 控制梯度计算

3.2 累计梯度

3.3 梯度清零

3.4 案例:函数最优解

3.5 转换错误


自动微分

自动微分模块torch.autograd负责自动计算张量操作的梯度,具有自动求导功能。

自动微分模块是构成神经网络训练的必要模块,可以实现网络权重参数的更新,使得反向传播算法的实现变得简单而高效。

1. 基础概念

  • 张量

    Torch中一切皆为张量,属性requires_grad决定是否对其进行梯度计算。默认是 False,如需计算梯度则设置为True。

  • 计算图

    torch.autograd通过创建一个动态计算图来跟踪张量的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。

  • 反向传播

    使用tensor.backward()方法执行反向传播,从而计算张量的梯度。这个过程会自动计算每个张量对损失函数的梯度。

  • 梯度

    计算得到的梯度通过属性grad访问,这些梯度用于优化模型参数,以最小化损失函数。

2. 计算梯度

tensor.backward()

执行反向传播,计算张量的梯度。

2.1 标量梯度计算

import torch

# 创建张量,数据类型必须是float
x = torch.tensor(data=[7.0],requires_grad=True,dtype = torch.float32)

# 操作张量
y = x**2 + 2*x + 18

# 计算梯度,即:反向传播
y.backward()

# 读取梯度值
print(x.grad)

2.2 向量梯度计算

x = torch.tensor(data=[1.0,2.0,5.3],requires_grad=True)

y = x**2 + 2*x +7

# 需要变为标量:求和 或者 求均值
z = y.sum()

z.backward()

print(x.grad)

若出现错误:RuntimeError: grad can be implicitly created only for scalar outputs:

原因:将非标量数据用于求梯度。

2.3 多标量梯度计算

x1 =torch.tensor(5.0,requires_grad = True,dtype = torch.float64)
x2 =torch.tensor(3.0,requires_grad = True,dtype = torch.float64)

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

y.backward()

print(x1.grad,x2.grad)

2.4 多向量梯度计算

# 1. 创建两个标量
x1 = torch.tensor([5.0, 6.0, 7.5], requires_grad=True)
x2 = torch.tensor([3.0, 5.2, 6.4], requires_grad=True)

# 2. 构建运算公式
y = x1**2 + 2 * x2 + 7

# 3. 向量构建
# z = y.sum()
z = y.mean()

# 4. 计算梯度,也就是反向传播
z.backward()

# 5. 读取梯度值
print(x1.grad, x2.grad)

3. 梯度上下文控制

梯度计算的上下文控制和设置对于管理计算图、内存消耗、以及计算效率至关重要。

3.1 控制梯度计算

梯度计算是有性能开销的,某些数据并不需要梯度计算,需手动关闭

# x1 = torch.tensor(5.0)
# print(x.requires_grad)

x2 = torch.tensor(6.0,requires_grad=True)
print(x2.requires_grad)

y = x2**2 +2*x2 +10
print(y.requires_grad)# 由x2的requires_grad=True

# 关闭梯度计算
# 方法一:torch.no_grad()
with torch.no_grad():
    y = x2**2 +2*x2 +10 
print(y.requires_grad)

# 方法二:使用装饰器
@torch.no_grad()
def fun(x):
    return x2**2 +2*x2 +10 

y=fun(x2)
print(y.requires_grad)

# 方法三:全局设置
# 须谨慎使用:因为当前module的某些需要梯度计算的变量变得不可导,
torch.set_grad_enabled(False)
y = x2**2 +2*x2 +10 
print(y.requires_grad)

3.2 累计梯度

默认情况下,当我们重复对一个自变量进行梯度计算时,梯度是累加的。

x = torch.tensor([1.0,2.0,5.3],dtype = torch.float64,device='cuda:0',requires_grad=True)

for  i in range(5):
    y=x**2+2*x+10
    z = y.mean()
    z.backward()
    print(x.grad)

3.3 梯度清零

大多数情况下是不需要梯度累加的,则在反向传播之前需先对梯度进行清零。

x = torch.tensor([1.0,2.0,5.3],dtype = torch.float64,device='cuda:0',requires_grad=True)
y=x**2+2*x+10
z = y.mean()
for  i in range(5):
    # 在反向传播之前,将梯度清零
    if x.grad is not None:
        x.grad.zero_()
        
    z.backward(retain_graph=True)
    print(x.grad)

3.4 案例:函数最优解

通过梯度下降找到损失函数最小值,权重(w)的最优情况。

import torch
import matplotlib.pyplot as plt
import numpy as np

w = np.linspace(-15,5,1000)
loss = w**2+10*w +10
plt.grid()
plt.plot(w ,loss)
# 损失函数最低点可视化
plt.show()

w = torch.tensor(20.0,requires_grad=True)
for i in range(100):
    loss = w**2+10*w +10
    if w.grad is not None:
        w.grad.zero_()
    
    # 计算梯度
    loss.backward()

    # 梯度下降
    w.data.sub_(0.1*w.grad.data)
    print(f'{i}',w.item(),loss.item(),w.grad)

3.5 转换错误

当requires_grad=True时,在调用numpy转换为ndarray时报错如下:

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

解释:可以计算梯度的张量无法通过numpy()转换为ndarray。

解决办法:叶子结点

detach()产生的张量是作为叶子结点存在的,并且该张量和原张量共享数据,只是该张量不需要计算梯度。

x = torch.tensor([20.0,15.21,7.12],requires_grad=True)
x_np = x.detach()
print(id(x),id(x_np))# 说明空间不同
print(id(x.data),id(x_np.data))# 说明数据共享

# 修改其中一个值
x_np[0]=100.0
print(x)
print(x_np)