目录
自动微分
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)