[TensorFlow系列-20]:TensorFlow基础 - Varialbe对象的手工求导和半自动链式求导tf.GradientTape
Posted 文火冰糖的硅基工坊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[TensorFlow系列-20]:TensorFlow基础 - Varialbe对象的手工求导和半自动链式求导tf.GradientTape相关的知识,希望对你有一定的参考价值。
作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客
本文网址:https://blog.csdn.net/HiWangWenBing/article/details/120391296
目录
第2章 Tensorflow与Pytorch的Varialbe对象的比较
4.3 代码示例1:元Varaible变量在某一点(x)处的一阶导数
4.4 代码示例2:多元Varaible变量在某一点(x1, x2, .....xn) 处的一阶偏导数
4.5 显式指定需要求导的变量:tape.watch()方法
4.6 tf.GradientTape(persistent=True)的作用
第1章 Varialbe变量概述
1.1 Varialbe变量基础
[TensorFlow系列-19]:TensorFlow基础 - Variable变量的使用方法与Tensor变量的比较_文火冰糖(王文兵)的博客-CSDN博客
1.2 求导方法概述
(1)手工求导
这种求导方法,需要程序员预先根据原函数定义好导函数,然后直接利用导函数求导。
这种求导方法,对变量没有限制,可以是普通的tensor对象,也可以是Varialbe对象。
- tensor对象的手工求导
- Varialbe对象的手工求导
(2)自动求导
自动求导就是不需要程序员手工定义导函数,而是利用框架提供的求导工具进行求导。
自动求导由分为:
- 半自动求导:这种求导与
- 全自动求导
第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 = tf.constant(1.0)
print("x_tensor =", x_tensor)
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)
自变量:张量tensor => 自变量值 x_tensor = tf.Tensor(1.0, shape=(), dtype=float32) 因变量:一元原函数 => 原函数值 y_tensor = tf.Tensor(2.0, shape=(), dtype=float32) 对原函数的所有变量分别手工求偏导(通过手工定义的导函数) 因变量:手工求导后 => 导函数值 y_tensor_grad_x = tf.Tensor(2.0, shape=(), dtype=float32)
3.2 Varialbe因变量的手工求导
这个过程与Tensor自变量的手工求导完全相同,无非就先定义导函数,然后正向函数计算。
因此,如果是手工求导,是否是Varialbe变量还是Tensor变量,其实并不重要。
只有需要利用平台提供的自动求导的工具,就行自动求导时,Varialbe变量的作用才体现出来。
# 一元函数在某点(x_i)处对一元Variable变量求导
# Tensor张量与手工求导函数
print("自变量:张量tensor => 自变量值")
x_var = tf.Variable(1.0)
print("x_tensor =", x_var)
print("\\n因变量:一元原函数 => 原函数值")
y_var = x_var ** 2 + 1 # y = x^2 + 1
print("y_tensor =",y_var)
print("\\n对原函数的所有变量分别手工求偏导(通过手工定义的导函数)")
y_var_grad_x = 2 * x_var # dy = 2*x
print("\\n因变量:手工求导后 => 导函数值")
print("y_tensor_grad_x =", y_var_grad_x)
自变量:张量tensor => 自变量值 x_tensor = <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0> 因变量:一元原函数 => 原函数值 y_tensor = tf.Tensor(2.0, shape=(), dtype=float32) 对原函数的所有变量分别手工求偏导(通过手工定义的导函数) 因变量:手工求导后 => 导函数值 y_tensor_grad_x = tf.Tensor(2.0, shape=(), dtype=float32)
第4章 Varialbe变量的半自动求导
4.1 半自动求导概述
在引入Variable后,在forward时,自动生成了计算图,
backward就不需要我们手工计算了,pytorch将根据计算图自动计算梯度。
自动求导不支持一连串数值点的的自动求导,只支持单个节点。
但自动求导支持多元函数(多元参数)的自动一次性求所有参数的偏导。
所谓半自动求导:就是导数或梯度时,需要程序员指定对哪些参数进行参数进行求导,而不是所有参数根据trainable属性,自动全部求导。
4.2 tf.GradientTape简介
(1)建立反向求导的数据流图
前面提到,Tensorflow的正向计算的数据流图和反向求导的数据流图示分离的,在自动求导时,必须通过显式的方式建立反向求导建立数据流图和相应的上下文,使用完后,该上下文的信息释放。
tf.gradient的作用就是为反向求导建立数据流图和相应的上下文。
通过with语句,为函数表达式建立正向的数据流图,更重要的是为函数表达式建立反向求导的数据流图和相应的上下文,上下文的信息保存在tape对象中。
因为对函数表达式中的参数进行反向求导,需要一些中间数据,因此在建立求导的上下文时,Tape对象会检查函数表达式中的variable对象以及variable对象是否设置了trainable属性。
需要注意的是:这里的函数表达式可以有多个函数表示式,而不仅限于一个函数表达式。
(2)反向半自动求导:tape.gradient(复合函数的表达式,需要求导的自变量。
所谓半自动求导,就是需要程序员手工指定:
- 函数表达式的对象
- 需求求导/求梯度的自变量参数列表
(3)全自动求导:
所谓全自动求导,程序员只需要通过函数表达式的对象,就可以完成trainable=True的所有参数的求导,不需要程序员手工指定自变量参数列表。
目前来看,tensorflow并不支持全自动求导。
4.3 代码示例1:元Varaible变量在某一点(x)处的一阶导数
# 一元Variable变量与自动求导
print("自变量:Variable对象 => 自变量值")
x_variable = tf.Variable(3.0)
print("x_variable =", x_variable)
print("\\n因变量:一元原函数 => 函数值")
y_variable = x_variable ** 2
print("y_variable =", y_variable)
print("\\n建立求导数据流图和上下文 => ")
with tf.GradientTape() as tape:
y_variable = x_variable ** 2
print("\\n利用tape对象,对指定的数据流对象下的指定的参数求导数")
dy_dx = tape.gradient(y_variable, x_variable)
print("\\n因变量:自动求导后 => 导数值 ")
print("x_variable =", x_variable)
print("dy_dx =", dy_dx)
自变量:Variable对象 => 自变量值 x_variable = <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=3.0> 因变量:一元原函数 => 函数值 y_variable = tf.Tensor(9.0, shape=(), dtype=float32) 建立求导数据流图和上下文 => 利用tape对象,对指定的数据流对象下的指定的参数求导数 因变量:自动求导后 => 导数值 x_variable = <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=3.0> dy_dx = tf.Tensor(6.0, shape=(), dtype=float32)
4.4 代码示例2:多元Varaible变量在某一点(x1, x2, .....xn) 处的一阶偏导数
# 多元Variable变量与自动求导
print("创建变量")
x1 = tf.Variable(1.0)
x2 = tf.Variable(2.0)
x3 = tf.Variable(3.0, trainable= False)
x4 = tf.constant(4.0)
print(x1)
print(x2)
print(x3)
print(x4)
print("创建反向求导数据流图以及求导上下文tape")
with tf.GradientTape(persistent=True) as tape:
y1 = 1*x1 + 2*x2 + 3*x3 + 4*x4
y = (y1 - 1)**2
print("单独求偏导")
dy_dx1 = tape.gradient(y,x1)
dy_dx2 = tape.gradient(y,x2)
dy_dx3 = tape.gradient(y,x3)
dy_dx4 = tape.gradient(y,x4)
print(dy_dx1)
print(dy_dx2)
print(dy_dx3)
print(dy_dx4)
print("批量求偏导")
dy_dx1,dy_dx2,dy_dx3,dy_dx4 = tape.gradient(y,[x1, x2, x3, x4])
print(dy_dx1)
print(dy_dx2)
print(dy_dx3)
print(dy_dx4)
创建变量 <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0> <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0> <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=3.0> tf.Tensor(4.0, shape=(), dtype=float32) 创建反向求导数据流图以及求导上下文tape 单独求偏导 tf.Tensor(58.0, shape=(), dtype=float32) tf.Tensor(116.0, shape=(), dtype=float32) None None 批量求偏导 tf.Tensor(58.0, shape=(), dtype=float32) tf.Tensor(116.0, shape=(), dtype=float32) None None
备注:
默认情况下constant变量和trainable= False的变量都无法进行自动求导。
4.5 显式指定需要求导的变量:tape.watch()方法
# watch非trainable参数
print("创建变量")
x1 = tf.Variable(1.0)
x2 = tf.Variable(2.0)
x3 = tf.Variable(3.0, trainable= False)
x4 = tf.constant(4.0)
print(x1)
print(x2)
print(x3)
print(x4)
print("创建反向求导数据流图以及求导上下文tape")
with tf.GradientTape(persistent=True, ) as tape:
# 显式地为x3, x4 建立求导上下文
tape.watch(x3)
tape.watch(x4)
#默认为trainable的Variable对象建立求导上行文
y1 = 1*x1 + 2*x2 + 3*x3 + 4*x4
y = (y1 - 1)**2
print("批量求偏导")
dy_dx1,dy_dx2,dy_dx3,dy_dx4 = tape.gradient(y,[x1, x2, x3, x4])
print(dy_dx1)
print(dy_dx2)
print(dy_dx3)
print(dy_dx4)
del tape
创建变量
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0>
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0>
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=3.0>
tf.Tensor(4.0, shape=(), dtype=float32)
创建反向求导数据流图以及求导上下文tape
批量求偏导
tf.Tensor(58.0, shape=(), dtype=float32)
tf.Tensor(116.0, shape=(), dtype=float32)
tf.Tensor(174.0, shape=(), dtype=float32)
tf.Tensor(232.0, shape=(), dtype=float32)
4.6 tf.GradientTape(persistent=True)的作用
默认情况下,tape.gradient () 执行一次求导后,会自动释放tape的上下文,以便节省内存空间。
但如果需要连续多次求导时,自动释放tape的上下文就会导致问题,persistent=True, 这样执行tape.gradient后,不会自动释放tape上下文,需要程序员手工del tape,释放tape对象。这就是为什么上文中,最后就有一个语句:del tape.
如下是persistent=False时,连续两次求导会导致错误的代码:
# persistent=false
print("创建变量")
x1 = tf.Variable(1.0)
x2 = tf.Variable(2.0)
x3 = tf.Variable(3.0, trainable= False)
x4 = tf.constant(4.0)
print(x1)
print(x2)
print(x3)
print(x4)
print("创建反向求导数据流图以及求导上下文tape")
with tf.GradientTape(persistent=False) as tape:
# 显式地为x3, x4 建立求导上下文
tape.watch(x3)
tape.watch(x4)
#默认为trainable的Variable对象建立求导上行文
y1 = 1*x1 + 2*x2 + 3*x3 + 4*x4
y = (y1 - 1)**2
print("批量求偏导")
dy_dx1,dy_dx2,dy_dx3,dy_dx4 = tape.gradient(y,[x1, x2, x3, x4])
dy_dx1,dy_dx2,dy_dx3,dy_dx4 = tape.gradient(y,[x1, x2, x3, x4])
print(dy_dx1)
print(dy_dx2)
print(dy_dx3)
print(dy_dx4)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-25-3a166f85b629> in <module>
22 print("批量求偏导")
23 dy_dx1,dy_dx2,dy_dx3,dy_dx4 = tape.gradient(y,[x1, x2, x3, x4])
---> 24 dy_dx1,dy_dx2,dy_dx3,dy_dx4 = tape.gradient(y,[x1, x2, x3, x4])
25 print(dy_dx1)
26 print(dy_dx2)
C:\\ProgramData\\Anaconda3\\envs\\tensorflow2.4_py3.8\\lib\\site-packages\\tensorflow\\python\\eager\\backprop.py in gradient(self, target, sources, output_gradients, unconnected_gradients)
1025 """
1026 if self._tape is None:
-> 1027 raise RuntimeError("A non-persistent GradientTape can only be used to"
1028 "compute one set of gradients (or jacobians)")
1029 if self._recording:
RuntimeError: A non-persistent GradientTape can only be used tocompute one set of gradients (or jacobians)
第5章 半自动求导的应用:梯度下降迭代
5.0 环境转变
环境准备
import numpy as np
import tensorflow as tf
print("hello world")
print("tensorflow version:", tf.__version__)
5.1 梯度下降迭代
print("定义迭代的自变量参数以及初始值")
x1_variable = tf.Variable(2.0)
x2_variable = tf.Variable(2.0)
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.numpy())
x2_data.append(x2_variable.numpy())
y_data.append(y_variable.numpy())
print("\\n初始点:", x1_data[0], x2_data[0], y_data[0])
while(iterations):
# 创建自动求导的数据流图和上下文对象tape
with tf.GradientTape() as tape:
y_variable = loss_fun (x1_variable, x2_variable)
# 指定参数,半自动求导
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)
#计算当前最新的y值
y_variable = loss_fun (x1_variable, x2_variable)
#保存数据
x1_data.append(x1_variable.numpy())
x2_data.append(x2_variable.numpy())
y_data.append(y_variable.numpy())
#下次迭代做准备
iterations = iterations -1
print("\\n迭代后的数据:")
print("x1_variable =", x1_variable.numpy())
print("x2_variable =", x2_variable.numpy())
print("y_variable =", y_variable.numpy())
定义迭代的自变量参数以及初始值 x1_variable = <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0> x2_variable = <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0> y_variable = tf.Tensor(9.0, shape=(), dtype=float32) 初始点: 2.0 2.0 9.0 迭代后的数据: 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
6.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章 Varialbe变量的全自动求导
Tensorflow不支持全自动求导
作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客
本文网址:https://blog.csdn.net/HiWangWenBing/article/details/120391296
以上是关于[TensorFlow系列-20]:TensorFlow基础 - Varialbe对象的手工求导和半自动链式求导tf.GradientTape的主要内容,如果未能解决你的问题,请参考以下文章
使用亚马逊的云服务器EC2做深度学习配置TensorFlow
[TensorFlow系列-20]:TensorFlow基础 - Varialbe对象的手工求导和半自动链式求导tf.GradientTape