[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


目录

第1章 Varialbe变量概述

1.1 Varialbe变量基础

1.2 求导方法概述

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

2.1 相同点

2.2 不同点

第3章 手工求导

3.1 Tensor自变量的手工求导

3.2 Varialbe因变量的手工求导

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

4.1 半自动求导概述

4.2 tf.GradientTape简介

4.3  代码示例1:元Varaible变量在某一点(x)处的一阶导数

4.4 代码示例2:多元Varaible变量在某一点(x1, x2, .....xn) 处的一阶偏导数

4.5 显式指定需要求导的变量:tape.watch()方法

4.6 tf.GradientTape(persistent=True)的作用

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

5.0 环境转变

5.1 梯度下降迭代

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

5.3 可视化迭代过程

5.4 可视化原函数

第6章 Varialbe变量的全自动求导



第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的主要内容,如果未能解决你的问题,请参考以下文章

深度学习系列 Part

使用亚马逊的云服务器EC2做深度学习配置TensorFlow

[TensorFlow系列-20]:TensorFlow基础 - Varialbe对象的手工求导和半自动链式求导tf.GradientTape

模块“tensorflow”没有属性“GPUOptions”

无法安装旧版本的 tensorflow

Win10上安装TensorFlow(官方文档翻译)