五、自动微分
1、基础概念
模块 autograd 负责自动计算张量操作的梯度,具有自动求导功能;autograd 创建一个动态计算图来跟踪张量的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。
属性 requires_grad 决定是否对张量进行梯度计算,默认不进行。
方法 backward 进行反向传播,计算张量梯度。
tensor.grad 返回梯度值
2、计算梯度
元素必须为浮点数类型
2.1、标量
# 张量的梯度计算
import torch
x = torch.tensor(1.0,requires_grad = True)
y = x**2 +2*x +3
y.backward() # 梯度计算,(1,求y的导数;2、将标量带入导数函数求值)
# x.grad 表示求导带入值结果
print(x.grad)
import torch
# 多标量的梯度计算
x1 = torch.tensor(1., requires_grad=True)
x2 = torch.tensor(2., requires_grad=True)
y = x1**2 + 3*x2 +5
y.backward()
print(x1.grad)
print(x2.grad)
2.2、向量
损失函数接收向量后,需要进行整合称为一个元素(一半使用sum)才能进行反向传播。
反向传播后自动拆分为不同元素值的结果。
# 向量的梯度计算
import torch
x = torch.tensor([1.0,2.0,3.0],requires_grad = True)
y = x**2 +2*x +5
print(y)
y = y.sum()
print(y)
y.backward()
print(x.grad)
import torch
# 多向量的梯度计算
x1 = torch.tensor([1.,2.], requires_grad=True)
x2 = torch.tensor([2.,5.], requires_grad=True)
y = x1**2 + 3*x2 +5
y1 = y.sum()
y1.backward()
print(x1.grad)
print(x2.grad)
import torch
# 多向量的梯度计算
x1 = torch.tensor([1.,2.], requires_grad=True)
x2 = torch.tensor([2.,5.], requires_grad=True)
y = x1**2 + 3*x2 +5
y2 = y.mean()
y2.backward()
print(x1.grad)
print(x2.grad)
2.3、矩阵
# 矩阵的梯度计算
import torch
x1 = torch.tensor([[1.,2.],[3.,4.]], requires_grad=True)
y = x1**2 + 3*x1 +5
y2 = y.sum()
y2.backward()
print(x1.grad)
# 多矩阵的梯度计算
import torch
x1 = torch.tensor([[1.,2.],[3.,4.]], requires_grad=True)
x2 = torch.tensor([[11.,2.],[1.,22.]], requires_grad=True)
y = x1**2 + 3*x2 +5
y2 = y.sum()
y2.backward()
print(x1.grad)
print(x2.grad)
3、梯度控制
由于 autograd 自动计算梯度,也就是在每个损失函数操作时都会自动运行,浪费资源,所以在无需求导的损失函数时,可以进行关闭求导功能。
3.1、全局控制
创建tensor时,默认 requires_grad 等于 False;set_grad_enabled(False)
# 全局控制
import torch
x = torch.tensor(3.0,requires_grad = False)
y = x**2 +2*x +3
try:y.backward() print(x.grad)
except:print("操作报错")x = torch.tensor(3.0,requires_grad = True)
y = x**2 +2*x +3
torch.set_grad_enabled(False)
try:y.backward() print(x.grad)
except:print("操作报错")
3.2、with进行上下文管理
with torch.no_grad():在这个代码块内创建的损失函数,不会求导
# with 控制
import torch
x = torch.tensor(3.0,requires_grad = True)
with torch.no_grad():y = x**2 +2*x +3
try:y.backward() print(x.grad)
except:print("操作报错")
3.3、装饰器函数控制
将with torch.no_grad() 封装到函数,其他函数需要控制计算时候,装饰这个函数即可。
# 装饰器控制
import torchx = torch.tensor(3.0,requires_grad = True)def zsq(func):def wrapper(*args):with torch.no_grad():return func(*args)return wrapper@zsq
def fun():y = x**2 +2*x +3try:y.backward() print(x.grad)except:print("操作报错")fun() # 调用函数
4、梯度清零
在多个损失函数反向传播或重复反向传播情况下,梯度值将累计以和的形式返回结果
# 累计梯度
import torch
# 多个损失函数反向传播
x =torch.tensor(4.0,requires_grad=True)y = 2*x**2 +7 # 第一个损失函数
y.backward()
print(x.grad) # 导数结果为 16.z = x**2 # 第二个损失函数
z.backward()
print(x.grad) # 导数结果为 8. 累加就是24.
当进行梯度计算时,无法直观反应某次梯度的值,所以需要梯度清零:grad.zero_(),需要梯度存在后才可以使用清零,否则获取梯度为None,清零会报错,清零时将元素值变成0.,不会变成None
# 梯度清理
import torch
x =torch.tensor(4.0,requires_grad=True)
y = 2*x**2 +7
try:x.grad.zero_()
except:print("梯度为None,不能清零")
y.backward() # 反向传播
print(x.grad)z = x**2
z.backward()
print(x.grad)x.grad.zero_() # 梯度清理
print(x.grad is None)
print(x.grad)
5、梯度下降算法结合
import torch
w = torch.tensor(5., requires_grad=True) # 初始化 wnum =0while True: num+=1if num > 50: break# 创建损失函数loss = w**2a=0# 梯度清零if w.grad is None: pass else: a = w.grad.dataw.grad.zero_()# 方向传播loss.backward()b = w.grad.dataif (b>a and a!=0) or b ==0:break# 当前斜率print("斜率:\n",w.grad)w.data = w.data - 0.4*w.grad# 当前斜率print("更新的横坐标:\n",w.data)# 当前斜率print("----------",num)
6、叶子节点
当一个tensor设置为可以求导,那么其性质变换, 与普通tensor有区别,对数据操作时需要索引出一个只有其数据的普通tensor
叶子节点 detach() ,将tensor的数据创建为新的tensor,两者内存不一样,数据共享,这时候可以对新的tensor数据操作。
import torch
x= torch.tensor([1., 2., 3.], requires_grad=True)try:x2 = x.numpy() # 如果x是一个可以求导的张量,那么它就不能直接当作普通tensor使用print(x2)
except :print("转化错误")try:x3 = x.detach()# 取出叶子节点print(x3)print(x)x2 = x3.numpy() # 取出叶子节点后就可以转numpy了print(x2)
except :pass