[Pytorch系列-20]:Pytorch基础 - Varialbe变量的手工求导和半自动链式求导torch.autograd.grad

作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客

本文网址:https://blog.csdn.net/HiWangWenBing/article/details/120251332


目录

第1章 Varialbe变量求导概述

1.1 Varialbe变量基础

1.2 求导方法概述

1.3 准备条件

第2章 Tensorflow与Pytorch的Varialbe对象的比较

2.1 相同点

2.2 不同点

第3章 手工求导

3.1 Tensor自变量的手工求导

3.2 Varialbe因变量的手工求导

第4章 Varialbe变量的半自动求导

4.1 半自动求导概述

4.2  torch.autograd.grad半自动求导

4.3 代码示例1:

4.4 代码示例2:

4.5  非求导属性的对象不能求导:requires_grad = False

4.6 多次求导,保留上下文:torch.autograd.grad(y,x1, retain_graph=True)

4.7 对pytorch动图图的几点说明

第5章 半自动求导的应用:梯度下降迭代

5.0 环境转变

5.1 梯度下降迭代

5.2 自动求导、梯度下降迭代的关键点

5.3 可视化迭代过程

5.4 可视化原函数

第6章 Pytorch Varialbe对象与Tensor对象的合并与统一

第7章 Varialbe对象的全自动求导




第1章 Varialbe变量求导概述

1.1 Varialbe变量基础

[Pytorch系列-19]:Pytorch基础 - Variable变量的使用方法与 Tensor变量的比较_文火冰糖(王文兵)的博客-CSDN博客

1.2 求导方法概述

(1)手工求导

这种求导方法,需要程序员预先根据原函数定义好导函数,然后直接利用导函数求导。

这种求导方法,对变量没有限制,可以是普通的tensor对象,也可以是Varialbe对象。

  • tensor对象的手工求导
  • Varialbe对象的手工求导

(2)自动求导

自动求导就是不需要程序员手工定义导函数,而是利用框架提供的求导工具进行求导。

自动求导由分为:

  • 半自动求导:这种求导与
  • 全自动求导

1.3 准备条件

#环境准备
import numpy as np
import math
import matplotlib.pyplot as plt
%matplotlib inline
import torch
from torch.autograd import Variable

print("Hello World")
print(torch.__version__)
print(torch.cuda.is_available())
Hello World
1.8.0
False

第2章 Tensorflow与Pytorch的Varialbe对象的比较

2.1 相同点

(1)封装性

他们都是在普通的tensor对象的基础上的进一步封装。

(2)原理相同

都是基于复合函数链式求导的基本原理

2.2 不同点

 (1)变量导数/梯度的存放位置

  • Pytorch的Varialbe对象:除了存放Varialbe对象的自身的参数值外,还增加了一个grad属性,全启动求导时存放该变量对应导数值。
  • Tensorflow的Varialbe对象:只能存放Varialbe对象的自身的参数值,导数值需要定义其他Varialbe对象存放导数值。

(2)Varialbe对象与普通的tensor对象的关系

  • Pytorch已经把Varialbe对象与普通的tensor对象进行了整合,统一成tensor对象,并通过requires_grad属性来标识是否需要进行自动求导,存放导数值。
  • Tenorflow中的Varialbe对象与 普通的Tensor对象,还没有合并,是相互独立的。

(3)自动求导的方式

  • Pytorch支持半自动求导全自动求导
  • Tensorflow只支持半自动求导。

(4)前向数据流图与反向数据流图的组织方式不同

  • Pytorch把上图中的所有信息,都存放在函数的数据流图中,包括每个参数的当前的梯度值,包括Yx和Wx,Bx。也就是说Pytorch的前向计算的数据流图和反向求导的数据流图示统一的,不需要显式的为反向求导建立数据流图和相应的上下文。
  • Tensorflow的正向计算的数据流图和反向求导的数据流图示分离的,在自动求导时,必须通过显式的方式建立反向求导建立数据流图和相应的上下文,使用完后,该上下文的信息释放。

第3章 手工求导

3.1 Tensor自变量的手工求导

这种方式,必须人工预先根据导数的公式,写出原函数的导函数。

并通过导函数计算导数,这种方法,虽然说是求导,实际上就是普通的正向函数计算。、

这种情形下,其实并不一定需要Varialbe变量,普通的Tensor变量也是可以的。

# 一元函数在某点(x_i)处对一元Tensor变量求导
# Tensor张量与手工求导函数
print("自变量:张量tensor       => 自变量值")
x_tensor = torch.Tensor([1.0])  
print("x_tensor =", x_tensor)
print("x_tensor.grad =", x_tensor.grad)

print("\n因变量:一元原函数     => 原函数值")
y_tensor = x_tensor ** 2 + 1    # y = x^2 + 1
print("y_tensor =",y_tensor)

print("\n对原函数的所有变量分别手工求偏导(通过手工定义的导函数)")
y_tensor_grad_x  = 2 * x_tensor  # dy = 2*x

print("\n因变量:手工求导后     => 导函数值")
print("y_tensor_grad_x =", y_tensor_grad_x)
print("y_tensor_grad_x.grad =", y_tensor_grad_x.grad)
自变量:张量tensor       => 自变量值
x_tensor = tensor([1.])
x_tensor.grad = None

因变量:一元原函数     => 原函数值
y_tensor = tensor([2.])

对原函数的所有变量分别手工求偏导(通过手工定义的导函数)

因变量:手工求导后     => 导函数值
y_tensor_grad_x = tensor([2.])
y_tensor_grad_x.grad = None

3.2 Varialbe因变量的手工求导

这个过程与Tensor自变量的手工求导完全相同,无非就先定义导函数,然后正向函数计算。

因此,如果是手工求导,是否是Varialbe变量还是Tensor变量,其实并不重要。

只有需要利用平台提供的自动求导的工具,就行自动求导时,Varialbe变量的作用才体现出来。

# 一元函数在某点(x_i)处对一元Variable变量求导
# Tensor张量与手工求导函数
print("自变量:张量tensor       => 自变量值")
# Variable的参数必须是tensor
x_var = torch.autograd.Variable(torch.Tensor([1]), requires_grad = True)
print("x_var =", x_var)
print("x_var.grad =", x_var.grad)

print("\n因变量:一元原函数     => 原函数值")
y_var = x_var ** 2 + 1    # y = x^2 + 1
print("y_var =",y_var)

print("\n对原函数的所有变量分别手工求偏导(通过手工定义的导函数)")
y_var_grad_x  = 2 * x_var  # dy = 2*x

print("\n因变量:手工求导后     => 导函数值")
print("y_var_grad_x =", y_var_grad_x)
自变量:张量tensor       => 自变量值
x_var = tensor([1.], requires_grad=True)
x_var.grad = None

因变量:一元原函数     => 原函数值
y_tensor = tensor([2.], grad_fn=<AddBackward0>)

对原函数的所有变量分别手工求偏导(通过手工定义的导函数)

因变量:手工求导后     => 导函数值
y_var_grad_x = tensor([2.], grad_fn=<MulBackward0>)

备注:

  • Varialbe是一种tensor
  • tensor也是一种Varialbe
  • 未自动求导前,Varialbe对象和tensor对象的梯度grad都是None

Varialbe对象与Tensor对象,在手工求导方面,其实是相同的。

第4章 Varialbe变量的自动求导

4.1 半自动求导概述

在引入Variable后,在forward时,自动生成了计算图,

backward就不需要我们手工计算了,pytorch将根据计算图自动计算梯度。

自动求导不支持一连串数值点的的自动求导,只支持单个节点。

但自动求导支持多元函数(多元参数)的自动一次性求所有参数的偏导。

所谓半自动求导:就是导数或梯度时,需要程序员指定对哪些参数进行参数进行求导,而不是所有参数根据trainable属性,自动全部求导。

4.2  torch.autograd.grad半自动求导

这种方法,使用torch的全局函数,需要指定求导的函数以及相应的偏导数对象。

如:y = wx + b

dy_dw, dy_db = torch.autograd.grad(y, [w,b], retain_graph=True)

备注:

  • 这种方式获得的梯度,直接通过函数返回,并没有存放到w,b的tensor中。
  • 这种方式与Tensorflow的半自动求导方式类似
  • 这种方式与Tensorflow的半自动求导不同的是:pytorch不需要通过手工with语句建立求导上下文。

4.3 代码示例1:

# 一元Variable变量与自动求导
print("自变量:Variable对象      => 自变量值")
x_variable =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = True)
print("x_variable =", x_variable)

print("\n因变量:一元原函数       => 函数值")
y_variable = x_variable ** 2
print("y_variable =", y_variable)

print("\n对指定的数据流对象下的指定的参数求导数")
dy_dx = torch.autograd.grad(y_variable, x_variable)

print("\n因变量:自动求导后      => 导数值 ")
print("x_variable =", x_variable)
print("x_variable.grad =", x_variable.grad)
print("dy_dx =", dy_dx)
自变量:Variable对象      => 自变量值
x_variable = tensor([3.], requires_grad=True)

因变量:一元原函数       => 函数值
y_variable = tensor([9.], grad_fn=<PowBackward0>)

对指定的数据流对象下的指定的参数求导数

因变量:自动求导后      => 导数值 
x_variable = tensor([3.], requires_grad=True)
x_variable.grad = None
dy_dx = (tensor([6.]),)

4.4 代码示例2:

# 多元Variable变量与自动求导
print("创建变量")
x1 =  torch.autograd.Variable(torch.Tensor([1.0]), requires_grad = True)
x2 =  torch.autograd.Variable(torch.Tensor([2.0]), requires_grad = True)
x3 =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = True)
x4 =  torch.autograd.Variable(torch.Tensor([4.0]), requires_grad = False)
#x4 =  torch.Tensor([4.0])
print(x1)
print(x2)
print(x3)
print(x4)

y1 = 1*x1 + 2*x2 + 3*x3 + 4*x4
y  = (y1 - 1)**2

print("单独求偏导")
# torch.autograd.grad能够自动更加y函数,建立求导上行文,
# 不需要像Tensorflow那样,通过with语句建立tape对象
dy_dx1 = torch.autograd.grad(y,x1, retain_graph=True)
dy_dx2 = torch.autograd.grad(y,x2, retain_graph=True)
dy_dx3 = torch.autograd.grad(y,x3, retain_graph=True)
dy_dx4 = torch.autograd.grad(y,x4, retain_graph=True)
print(dy_dx1)
print(dy_dx2)
print(dy_dx3)
print(dy_dx4)

print("批量求偏导")
dy_dx1,dy_dx2,dy_dx3,dy_dx4 = torch.autograd.grad(y,[x1, x2, x3, x4])
print(dy_dx1)
print(dy_dx1.grad)
print(dy_dx2)
print(dy_dx2.grad)
print(dy_dx3)
print(dy_dx3.grad)
print(dy_dx4)
print(dy_dx4.grad)
创建变量
tensor([1.], requires_grad=True)
tensor([2.], requires_grad=True)
tensor([3.], requires_grad=True)
tensor([4.], requires_grad=True)
单独求偏导
(tensor([58.]),)
(tensor([116.]),)
(tensor([174.]),)
(tensor([232.]),)
批量求偏导
tensor([58.])
None
tensor([116.])
None
tensor([174.])
None
tensor([232.])
None

备注:

torch.autograd.grad半自动求导,不会自动更新variable对象的grad成员变量。

4.5  非求导属性的对象不能求导:requires_grad = False

# 一元Variable变量与自动求导
print("自变量:Variable对象      => 自变量值")
x_variable =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = False)
print("x_variable =", x_variable)

print("\n因变量:一元原函数       => 函数值")
y_variable = x_variable ** 2
print("y_variable =", y_variable)

print("\n对指定的数据流对象下的指定的参数求导数")
dy_dx = torch.autograd.grad(y_variable, x_variable)

print("\n因变量:自动求导后      => 导数值 ")
print("x_variable =", x_variable)
print("x_variable.grad =", x_variable.grad)
print("dy_dx =", dy_dx)
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

出错原因:

x_variable =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = False)

x_variable不具备求导的能力。

4.6 多次求导,保留上下文:torch.autograd.grad(y,x1, retain_graph=True)

# 一元Variable变量与自动求导
print("自变量:Variable对象      => 自变量值")
x_variable =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = True)
print("x_variable =", x_variable)

print("\n因变量:一元原函数       => 函数值")
y_variable = x_variable ** 2
print("y_variable =", y_variable)

print("\n对指定的数据流对象下的指定的参数求导数")
dy_dx = torch.autograd.grad(y_variable, x_variable)
dy_dx = torch.autograd.grad(y_variable, x_variable)

print("\n因变量:自动求导后      => 导数值 ")
print("x_variable =", x_variable)
print("x_variable.grad =", x_variable.grad)
print("dy_dx =", dy_dx)
RuntimeError: Trying to backward through the graph a second time, but the saved intermediate results have already been freed. Specify retain_graph=True when calling .backward() or autograd.grad() the first time.

错误原因:

dy_dx = torch.autograd.grad(y_variable, x_variable)这行完一次求导后,就释放求导的上下文。

解决方法1:retain_graph=True

# 一元Variable变量与自动求导
print("自变量:Variable对象      => 自变量值")
x_variable =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = True)
print("x_variable =", x_variable)

print("\n因变量:一元原函数       => 函数值")
y_variable = x_variable ** 2
print("y_variable =", y_variable)

print("\n对指定的数据流对象下的指定的参数求导数")
dy_dx = torch.autograd.grad(y_variable, x_variable, retain_graph=True)
dy_dx = torch.autograd.grad(y_variable, x_variable)

print("\n因变量:自动求导后      => 导数值 ")
print("x_variable =", x_variable)
print("x_variable.grad =", x_variable.grad)
print("dy_dx =", dy_dx)

解决方法2:重选构建动图图(包括正向与反向)

# 一元Variable变量与自动求导
print("自变量:Variable对象      => 自变量值")
x_variable =  torch.autograd.Variable(torch.Tensor([3.0]), requires_grad = True)
print("x_variable =", x_variable)

print("\n因变量:一元原函数       => 函数值")
y_variable = x_variable ** 2
print("y_variable =", y_variable)

print("\n对指定的数据流对象下的指定的参数求导数")
dy_dx = torch.autograd.grad(y_variable, x_variable)

y_variable = x_variable ** 2
dy_dx = torch.autograd.grad(y_variable, x_variable)

print("\n因变量:自动求导后      => 导数值 ")
print("x_variable =", x_variable)
print("x_variable.grad =", x_variable.grad)
print("dy_dx =", dy_dx)

y_variable = x_variable ** 2 =》表明重选构建动态图,包括正向与反向。

4.7 对pytorch动图图的几点说明

(1)重选构建动态图:

y_variable = x_variable ** 2

(2)释放动态图:

torch.autograd.grad()

torch.autograd.grad(retain_graph=False

(3)不释放动态图,继续保留动态图信息

torch.autograd.grad(retain_graph=True

第5章 半自动求导的应用:梯度下降迭代

5.0 环境转变

#环境准备
import numpy as np
import math
import matplotlib.pyplot as plt
%matplotlib inline
import torch
from torch.autograd import Variable

print("Hello World")
print(torch.__version__)
print(torch.cuda.is_available())
Hello World
1.8.0
False

5.1 梯度下降迭代

print("定义迭代的自变量参数以及初始值")
x1_variable =  torch.autograd.Variable(torch.Tensor([2.0]), requires_grad = True)
x2_variable =  torch.autograd.Variable(torch.Tensor([2.0]), requires_grad = True)
print("x1_variable =", x1_variable)
print("x2_variable =", x2_variable)

# 定义原函数
def loss_fun(x1, x2):
    y = x1**2 + x2**2 + 1
    return (y)

# 获取y初始值
y_variable = loss_fun (x1_variable, x2_variable)
print("y_variable   =", y_variable)

#定义学习率
learnning_rate = 0.1

#定义迭代次数
iterations = 30

#定义存放迭代过程数据的列表
x1_data = []
x2_data = []
y_data  = []

# 保存初始值:需转换成numpy,便于matlab可视化
x1_data.append(x1_variable.data.numpy())
x2_data.append(x2_variable.data.numpy())
y_data.append(y_variable.data.numpy())
print("\n初始点:", x1_data[0], x2_data[0], y_data[0])


while(iterations):
    # 创建自动求导的数据流图和上下文对象tape
    y_variable = loss_fun (x1_variable, x2_variable)
    
    #  指定参数,半自动求导
    dy_dx1,dy_dx2 = torch.autograd.grad(y_variable, [x1_variable, x2_variable])
    
    # 梯度下降迭代
    x1_variable.data = x1_variable.data - (learnning_rate * dy_dx1)
    x2_variable.data = x2_variable.data - (learnning_rate * dy_dx2)
        
    #计算当前最新的y值
    y_variable = loss_fun (x1_variable, x2_variable)
    
    #保存数据
    x1_data.append(x1_variable.data.numpy())
    x2_data.append(x2_variable.data.numpy())
    y_data.append(y_variable.data.numpy())
    
    #下次迭代做准备
    iterations = iterations -1
    
print("\n迭代后的数据:")
print("x1_variable =", x1_variable.data.numpy())
print("x2_variable =", x2_variable.data.numpy())
print("y_variable   =", y_variable.data.numpy())
定义迭代的自变量参数以及初始值
x1_variable = tensor([2.], requires_grad=True)
x2_variable = tensor([2.], requires_grad=True)
y_variable   = tensor([9.], grad_fn=<AddBackward0>)

初始点: [2.] [2.] [9.]

迭代后的数据:
x1_variable = [0.00247588]
x2_variable = [0.00247588]
y_variable   = [1.0000123]

备注:

(1) 函数 y = x1**2 + x2**2 + 1

  • 其理论极小值为1
  • 位于(x1=0, x2=0)处。

(2)自动求导,梯度下降过程

  • 起始点坐标(x=2, y=2)
  • 学习率=0.1
  • 迭代次数=30
  • 迭代后坐标:  x1=0.00247588,  x2_variable=0.00247588
  • 迭代后的最小值:y_variable = 1.0000123

5.2 自动求导、梯度下降迭代的关键点

    #  指定参数,半自动求导
    dy_dx1,dy_dx2 = tape.gradient(y_variable, [x1_variable, x2_variable])

    
    # 梯度下降迭代
    x1_variable.assign_sub(learnning_rate * dy_dx1)
    x2_variable.assign_sub(learnning_rate * dy_dx2)

(1)偏导/梯度的存放空间

tensorflow的半自动求导后的导数值,并不是与variable对象存放在一起的,而是通过函数返回,因此需要dy_dx1,dy_dx2存放返回的导数值。

(2)偏导/梯度的更新或迭代

通过variable.assign_sub()函数进行梯度迭代。

5.3 可视化迭代过程

fig = plt.figure()
ax1 = plt.axes(projection='3d')        #使用matplotlib.pyplot创建坐标系
ax1.scatter3D(x1_data, x2_data, y_data, cmap='Blues')  #绘制三维散点图
plt.show()

5.4 可视化原函数

# 可视化原函数的图形
x1_sample = np.arange(-10, 10, 1)
x2_sample = np.arange(-10, 10, 1)    #X,Y的范围

x1_grid, x2_grid = np.meshgrid(x1_sample,x2_sample)      #空间的点序列转换成网格点

y_grid = loss_fun(x1_grid,x2_grid)                 #生成z轴的网格数据

figure = plt.figure()
ax1 = plt.axes(projection='3d')             #创建三维坐标系

ax1.plot_surface(x1_grid, x2_grid, y_grid ,rstride=1,cstride=1,cmap='rainbow')

第6章 Pytorch Varialbe对象与Tensor对象的合并与统一

在最新的Pytorch中, Varialbe对象已经与Tensor对象合并与统一

设置了 requires_grad=True的tensor对象的功能与varaible对象完全一致。

设置了 requires_grad=False的tensor对象的功能与传统的tensor对象完全一致。

后续讨论将不再使用专有的Varialbe对象,而直接使用统一的Tensor对象。

print("定义样本tensor")
x = torch.Tensor([2.])
y = torch.Tensor([2.])

print("定义参数tensor")
w = torch.Tensor([1.])
b = torch.tensor([1.], requires_grad=True)  # 使能自动梯度计算方法1
print(x)
print(w)
print(b)

print("\n使能tensor的梯度属性,替代variable变量")
w.requires_grad_()                         # 使能自动梯度计算方法2
print(w)
print(b)

print("\n生成loss函数的动态数据流图")
f_pred = w * x + b
loss = (f_pred - y)**2

print("\n自动计算梯度")
torch.autograd.grad(loss, [w,b], retain_graph=True)
print(loss)
print(w.grad)   #autograd.grad的计算结果被函数返回,并为存放到w和b的grad参数中!!!
print(b.grad)

print("\n生成loss函数的动态数据流图")
f_pred = w * x + b
loss = (f_pred - y)**2
print("自动反向求梯度")
loss.backward()  
print(loss)
print(w.grad)
print(b.grad)
定义样本tensor
定义参数tensor
tensor([2.])
tensor([1.])
tensor([1.], requires_grad=True)

使能tensor的梯度属性,替代variable变量
tensor([1.], requires_grad=True)
tensor([1.], requires_grad=True)

生成loss函数的动态数据流图

自动计算梯度
tensor([1.], grad_fn=<PowBackward0>)
None
None

生成loss函数的动态数据流图
自动反向求梯度
tensor([1.], grad_fn=<PowBackward0>)
tensor([4.])
tensor([2.])

第7章 Varialbe对象的全自动求导

待续。。。。。。。


作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客

本文网址:https://blog.csdn.net/HiWangWenBing/article/details/120251332

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文火冰糖的硅基工坊

你的鼓励是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值