TF2-03求导

API说明

  1. Graph模式使用的是gradients函数:
    tf.gradients(
        ys,
        xs,
        grad_ys=None,
        name='gradients',
        gate_gradients=False,
        aggregation_method=None,
        stop_gradients=None,
        unconnected_gradients=tf.UnconnectedGradients.NONE
    )
  • 该函数还有一个相关函数,用来终止梯度计算。
    • 该函数返回的Tensor不会作为求导使用。
    tf.stop_gradient(
        input,
        name=None
    )
  1. Eager模式使用的是GradientTape类
    • 提供with上下文模式。
    • 提供如下几个函数完成梯度计算
      1. gradient
      2. watch
      3. reset
      4. stop_recording:创建自己的上下文环境。在这个环境中的操作不被跟踪。
      5. jacobian
  • 构造器定义如下:

    __init__(
        persistent=False,
        watch_accessed_variables=True
    )

自动求导使用例子

Graph模式使用例子

import tensorflow  as tf

一阶简单导数

  • y = x ^ 2
import tensorflow  as tf
g = tf.Graph()
with g.as_default():
    x = tf.constant(1.0)
    y = x * x
    grad = tf.gradients(y, x)

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re)
[2.0]

多元求导

  • z = x ^2 + y ^3
g = tf.Graph()
with g.as_default():
    x = tf.constant(1.0)
    y = tf.constant(2.0)
    z = x ** 2 + y ** 3
    grad = tf.gradients(z, [x, y])

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re)
[2.0, 12.0]

聚合求导

  • y_1 = x ^ 2
  • y_2 = x ^ 3
  • \dfrac{\partial (y_1 + y_2)}{\partial x} = \dfrac{\partial y_1}{\partial x} + \dfrac{\partial y_2}{\partial x}
# 如果ys是列表,则计算导数和, 如果xs是列表,与上面一样,作为求导变量
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y_1 = x * x
    y_2 = x ** 3
    grad = tf.gradients([y_1, y_2], x)

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re)
[16.0]
  • 关于聚合求导的说明:
    • 当ys是列表的时候,最后求的导数是累加和;累加和的方式有很多种,
      1. ADDN:全部计算好后,在调用ADD_N操作计算
      2. EXPERIMENTAL_ACCUMULATE_N:这个是计算一个累加一个,应该可以减少内存占用
      3. EXPERIMENTAL_TREE:这个方法具体什么算法不知道(估计利用了树这样数据结构来累加),但文档说降低性能,能提高内存的利用率,而且未来可能不再支持。
    • 其实作用一样,就是性能与内存的平衡而已。
# 如果ys是列表,则计算导数和, 如果xs是列表,与上面一样,作为求导变量
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y_1 = x * x
    y_2 = x ** 3
    grad = tf.gradients([y_1, y_2], x, aggregation_method=tf.AggregationMethod.EXPERIMENTAL_ACCUMULATE_N)

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re)
[16.0]

grad_ys用来设置(hold)初始梯度

  • 根据链式求导规则,等价于对梯度加权,该参数与ys长度一样,对饮每个ys。
  • y_1 = x ^ 2
  • y_2 = x ^ 3
  • grad_ys = [ 0.3, 0.7 ]
  • \dfrac{\partial (0.3 \ast y_1 + 0.7 \ast y_2)}{\partial x} = \dfrac{\partial 0.3 \ast y_1}{\partial y_1} \dfrac{\partial y_1}{\partial x} + \dfrac{\partial 0.7 \ast y_2}{\partial y_2} \dfrac{\partial y_2}{\partial x} = 0.3 \ast \dfrac{\partial y_1}{\partial x} + 0.7 \ast \dfrac{\partial y_2}{\partial x}
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y_1 = x * x
    y_2 = x ** 3
    grad = tf.gradients([y_1, y_2], x, grad_ys=[0.3, 0.7])

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re)   # 9.6
[9.599999]

高阶梯度计

  • y = x ^ 4
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y = x ** 4
    grad_1 = tf.gradients(y, x)
    grad_2 = tf.gradients(grad_1, x)

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad_2)
print(re) 
[48.0]

链式求导与stop_gradients的使用

  • stop_gradients用来终止链式求导。真心来说tensorflow的求导与PyTorch比起来还是被甩几条街。
  • y = x ^ 2
  • z = 5 \sqrt{y}
  • \dfrac{\partial z}{\partial x} = \dfrac{\partial z}{\partial y} \dfrac{\partial y}{\partial x} = (\dfrac{5}{2} y ^{- \frac{1}{2}}) (2 x)
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y = x * x
    z = 5 * tf.sqrt(y)
    grad = tf.gradients(z, x)

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re) 
[5.0]
  • stop_gradients的使用
    • 表示中断链式求导的量
      • 中断会导致z到x的不连接,对这种不连接情况使用unconnected_gradients指定处理方式
        • NONE:默认(NONE继续下去导致异常)
        • ZERO:返回0(ZERO继续下去还是0)
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y = x * x
    z = 5 * tf.sqrt(y)
    grad = tf.gradients(z, x, stop_gradients=[y], unconnected_gradients=tf.UnconnectedGradients.ZERO)   # NONE

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re) 
[0.0]
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y = x * x
    z = 5 * tf.sqrt(y)
    grad = tf.gradients(z, y, stop_gradients=[y])  
    # grad = tf.gradients(z, y)  

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re) 
[1.25]
  • stop_gradient函数的使用
g = tf.Graph()
with g.as_default():
    x = tf.constant(2.0)
    y = x * x
    y_ = tf.stop_gradient(y)   # 返回一个不给求导跟踪的Tensor。
    z = 5 * tf.sqrt(y_)
    grad = tf.gradients(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO)   # NONE
    # grad = tf.gradients(z, y_)   # NONE

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re) 
[0.0]

gate_gradients参数的使用

  • 这个参数是控制梯度的并行计算的。与在计算的时候使用一个元组来控制并行运算中,该梯度的计算完成情况。比如链式求导的情况。
    • 比如两个matmul操作的梯度依赖输入值,不使用gate_gradients可能会出现有一个梯度在其他梯度之前便应用到某个输入中。
    • 在单纯的求导环境中,这个参数没有什么意义。
import tensorflow  as tf
g = tf.Graph()
with g.as_default():
    x = tf.constant(1.0)
    y = x * x
    grad = tf.gradients(y, x, gate_gradients=True)

sess = tf.compat.v1.Session(graph=g)
re = sess.run(grad)
print(re)
[2.0]

使用求导来实现函数的最小值与最大值求解。

  • 这里我们用来求如下函数的极值点。

    • y = 2 (x - 2) ^2 + 3
  • 算法:

    • 使用一个迭代循环
    • 每次求函数的导数
    • 判定导数的正负
      • 正: x就减
      • 负: x就加
    • n辞循环后,我们有理由相信x就是最小值点。
    • 把x带入函数,可以求得最小值点
import tensorflow  as tf

vals = []   # 

g = tf.Graph()
sess = tf.compat.v1.Session(graph=g)
with g.as_default():
    x = tf.constant(0.1)
    rate = tf.constant(0.1)
    N = 40
    for i in range(N):
        y = 2 * ((x - 2) ** 2) + 3
        grad = tf.gradients(y, x)
        x -= rate * grad
        v = sess.run(x)
        x = tf.constant(v[0])
        vals.append(v[0])
        
print(F"------ 极值点:{v[0]}")   # 最后一次极值点
print(F"------ 极值:{(lambda x: 2 * ((x - 2) ** 2) + 3)(v[0])}")

# 可视化逼近过程
%matplotlib inline
import matplotlib.pyplot as plt

plt.plot(range(N), vals)
plt.show()

------ 极值点:1.9999998807907104
------ 极值:3.0000000000000284
使用导数求极值的过程可视化
  • Tensorflow2.0使用图模式编程,也是醉了。

Eager模式使用例子

  • 在Eager模式中求导使用是tf.GradientTape

编程模式

  • tf.GradientTape类是一个需要with上下文支持的类。
    1. 构建tf.GradientTape对象
    2. 开启tf.GradientTape上下文跟踪
    3. 在上下文中定义求导函数 与求导变量
    4. 调用GradientTape对象对象实现计算
# 1. 构建tf.GradientTape对象
gp = tf.GradientTape()
x = tf.constant(5.0)

# 2. 开启tf.GradientTape上下文跟踪
with gp:
    # 3. 在上下文中定义求导函数 与求导变量
    gp.watch(x)    # 如果没有这个watch,求导就是None
    y = x * x      # 求导函数

# 4. 调用GradientTape对象对象实现计算
grad = gp.gradient(y, x)
print(grad)

tf.Tensor(10.0, shape=(), dtype=float32)

tf.GradientTape的持久性

  • 使用构造器参数persistent=False控制,默认是False
    • Fasle:计算一次后,图消失。不能再次调用gradient函数
gp = tf.GradientTape(persistent=True)   # 注意这个参数是True与False差异
x = tf.constant(5.0)
with gp:
    gp.watch(x)   
    y = x * x  

grad = gp.gradient(y, x)
print(grad)
grad = gp.gradient(y, x)
print(grad)
tf.Tensor(10.0, shape=(), dtype=float32)
tf.Tensor(10.0, shape=(), dtype=float32)

对Variable可训练类型的自动跟踪

  • 通过watch_accessed_variables参数控制,默认是True
gp = tf.GradientTape() 
x = tf.Variable(5.0)
with gp:
    y = x * x  

grad = gp.gradient(y, x)
print(grad)
tf.Tensor(10.0, shape=(), dtype=float32)

多元求导

gp = tf.GradientTape(persistent=True)
x = tf.constant(5.0)
y = tf.constant(2.0)

with gp:
    gp.watch(x)  
    gp.watch(y)  
    z = x ** 2 + 2 * y

grad_x_y = gp.gradient(z, [x, y])
grad_x = gp.gradient(z, [x])
grad_y = gp.gradient(z, y)
print(grad_x_y)
print(grad_x)
print(grad_y)     # 注意返回的类型:Tensor与list的差别
[<tf.Tensor: shape=(), dtype=float32, numpy=10.0>, <tf.Tensor: shape=(), dtype=float32, numpy=2.0>]
[<tf.Tensor: shape=(), dtype=float32, numpy=10.0>]
tf.Tensor(2.0, shape=(), dtype=float32)

聚合求导

  • 就是求导函数是一个list列表类型。
gp = tf.GradientTape()
x = tf.constant(5.0)

with gp:
    gp.watch(x)  
    y_1 = x ** 2 
    y_2 = 2 * x

grad = gp.gradient([y_1, y_2], x)
print(grad)

tf.Tensor(12.0, shape=(), dtype=float32)

对GradientTape上下文停止跟踪

  • 这种实际提供了内存节省,有一定的优化作用。

  • 注意函数返回一个上下文对象:

@contextmanager
stop_recording()
gp = tf.GradientTape()
x = tf.constant(5.0)

with gp:
    gp.watch(x)  
    y_1 = x ** 2 
    with gp.stop_recording():    # 停止跟踪
        y_2 = 2 * x

grad = gp.gradient([y_1, y_2], x)    # y_2 返回 0 ,因为没有提供求导跟踪,聚合的时候作为0使用。
print(grad)
tf.Tensor(10.0, shape=(), dtype=float32)

reset()提供跟踪重置

  • reset之前的跟踪被清除。
gp = tf.GradientTape()
x = tf.constant(5.0)

with gp:
    gp.watch(x)  
    y_1 = x ** 2 
    gp.reset()
    gp.watch(x)        # x被清除需要重新watch, y_1被清除
    y_2 = 2 * x

grad = gp.gradient([y_1, y_2], x)    # x被清除,y_1被清除
print(grad)
tf.Tensor(2.0, shape=(), dtype=float32)

向量与矩阵的导数

  • 传说中的雅可比导数与雅可比矩阵。

    • 微分变量是向量与矩阵的情况。
  • batch_jacobian与jacobian

    • batch_jacobian计算多维的情况
  • y = x ^ 2

  • x = (x_1, x_2)

  • y = x ^ 2 = (x_1, x_2) ^ 2 = (x_1 ^2 , x_2 ^2)

  • 雅可比矩阵为

    • \begin{bmatrix} { \dfrac{\partial y_1}{ \partial x_1} } & { \dfrac{ \partial y_1}{ \partial x_2 } } \\ { \dfrac{ \partial y_2 }{ \partial x_1 } } & { \dfrac{ \partial y_2 }{ \partial y_2 } } \end{bmatrix}
gp = tf.GradientTape(persistent=True)
x = tf.constant([5.0, 2.0])

with gp:
    gp.watch(x)  
    y = x ** 2

grad = gp.gradient(y, x)
print(grad)
jaco = gp.jacobian(y, x)
print(jaco)
tf.Tensor([10.  4.], shape=(2,), dtype=float32)
tf.Tensor(
[[10.  0.]
 [ 0.  4.]], shape=(2, 2), dtype=float32)

检测自动跟踪的Variable变量

  • 这个用来调试优化程序不错:调用watched_variables()函数返回自动跟踪的变量。
gp = tf.GradientTape() 
x = tf.Variable(5.0)
with gp:
    y = x * x  

vars = gp.watched_variables()
print(vars)
(<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=5.0>,)

使用GradientTape实现极小值点计算

  • 这个与上面一样的实现模式,只是使用TF2.0的实时模式。
# 小封装一下函数
def cal_grad(dy, dx):
    gp = tf.GradientTape() 
    tx = dx
    with gp:
        ty = dy(tx)
    grad = gp.gradient(ty, tx)
    return grad

vals = []
N  = 50
rate = tf.constant(0.1)
f = lambda p : 2 * ((p - 2) ** 2) + 3
x = tf.Variable(0.1)

for i in  range(N):
    delta = cal_grad(f, x)
    x.assign_sub(rate * delta)
    vals.append(x.numpy())

print(F"------ 极值点:{x.numpy()}")   # 最后一次极值点
print(F"------ 极值:{(lambda x: 2 * ((x - 2) ** 2) + 3)(x.numpy())}")

# 可视化逼近过程
%matplotlib inline
import matplotlib.pyplot as plt

plt.plot(range(N), vals)
plt.show()

------ 极值点:1.9999998807907104
------ 极值:3.0000000000000284
使用梯度求极值的过程可视化

关于gradient成员函数的参数

  1. output_gradients参数
    • 权重参数(具体看上面图模式编程说明)
  2. unconnected_gradients参数
    • 终止求导后的处理方式
gradient(
    target,
    sources,
    output_gradients=None,
    unconnected_gradients=tf.UnconnectedGradients.NONE
)
gp = tf.GradientTape(persistent=True) 
x = tf.Variable(5.0)
with gp:
    y = x * x  
    z = 2 * y + tf.sqrt(y) 
    s = 3 * (x ** 2)

vars = gp.watched_variables()
print(vars)
a = [0.1, 0.9]

z_x = gp.gradient(z, x)
print(F"z_x = {z_x.numpy()}")
s_x = gp.gradient(s, x)
print(F"y_x = {s_x.numpy()}")

print(F"手工权重:z_x + z_y = {  a[0] * z_x.numpy() + a[1] * s_x.numpy()}")
print(F"手工无权重:z_x + z_y = {  z_x.numpy() + s_x.numpy()}")

grad = gp.gradient([z, s], x, output_gradients=a)
print(F"权重:z_x + z_y = {grad.numpy()}")

grad = gp.gradient([z, s], x)
print(F"无权重:z_x + z_y = {grad.numpy()}")


(<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=5.0>,)
z_x = 21.0
y_x = 30.0
手工权重:z_x + z_y = 29.1
手工无权重:z_x + z_y = 51.0
权重:z_x + z_y = 29.09999656677246
无权重:z_x + z_y = 51.0

高阶导数

  • 通过嵌套实现高阶导数计算。
x = tf.Variable(4.0)

with tf.GradientTape() as g_2:         # 二阶导数
    # g_2.watch(x)
    with tf.GradientTape() as g_1:     # 一阶导数
        # g_1.watch(x)
        y = x ** 3
    dy_dx = g_1.gradient(y, x)     # 1阶

d2y_dx2 = g_2.gradient(dy_dx, x)  # 2阶
print(F"一阶导数{dy_dx}")
print(F"二阶导数{d2y_dx2}")
一阶导数48.0
二阶导数24.0

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269