自动微分
深度学习需要通过自动计算微分来实现误差反向传播,故需要一种能够自动微分计算梯度的机制,在本文种会介绍Tensorflow2的这种机制如何使用
梯度带tf.GradientTape
直接翻译是梯度带,Gradient是梯度很好理解,Tape按我的理解是像以前复读机的磁带,是一种记录的介质。在tf.GradientTape中,这个磁带通过观测模型的计算过程,记录下计算过程所可能产生的梯度值。所以其代码看起来是这样的:
#先定义能被观测的变量
x = tf.Variable(3.0)
#通过以下部分开始记录计算
with tf.GradientTape() as tape:
y = x**2
#提取出dy/dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()
#结果是6
非标量的梯度
如果x是向量[1.0,2.0,3.0],对x的系数或对x求导会发生什么呢?看如下代码:
x = tf.Variable([1.0,2.0,3.0])
c = tf.Variable(1.0)
with tf.GradientTape() as tape:
# tape.watch(x)
y = x*c
grad = tape.gradient(y,[c,x])
print(grad)
#结果是[<tf.Tensor: shape=(), dtype=float32, numpy=6.0>,
#<tf.Tensor: shape=(3,), dtype=float32, numpy=array([1., 1., 1.], dtype=float32)>]
如果对系数求导,返回的是x向量元素之和;如果对x求导,得到的是shape和x相同的系数向量
控制观测的内容
在tf.GradientTape中,不是所有的量都会被观测,如果输出未被观测的梯度,Tensorflow会返回None。默认会观测变量,而常量不会被观测,如果需要控制观测的内容,需要自己进行配置
观测常量
观测常量比较检测,在tape里面使用tape.watch指令即可,如下所示:
x = tf.constant(3.0)
with tf.GradientTape() as tape:
tape.watch(x)#观测常量
y = x**2
# dy = 2x * dx
dy_dx = tape.gradient(y, x)
print(dy_dx.numpy())
#结果是6
不观测变量
使用watch_accessed_variables=False先取消默认观测变量,再根据自己需要观测不同的量
x0 = tf.Variable(0.0)
x1 = tf.Variable(10.0)
with tf.GradientTape(watch_accessed_variables=False) as tape:
tape.watch(x1)
y0 = tf.math.sin(x0)
y1 = tf.nn.softplus(x1)
y = y0 + y1
ys = tf.reduce_sum(y)
# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1)
grad = tape.gradient(ys, {'x0': x0, 'x1': x1})
print('dy/dx0:', grad['x0']) #结果是None,没被观测
print('dy/dx1:', grad['x1'].numpy())#结果是0.9999546
获取梯度出错,变成None的几种可能
1.使用张量替换了变量
x = tf.Variable(2.0)
for epoch in range(2):
with tf.GradientTape() as tape:
y = x+1
print(type(x).__name__, ":", tape.gradient(y, x))
x = x + 1 # 这里直接加会将变量x变成标量,正确方法是 `x.assign_add(1)`
2.在 TensorFlow 之外进行了计算
x = tf.Variable([[1.0, 2.0],
[3.0, 4.0]], dtype=tf.float32)
with tf.GradientTape() as tape:
x2 = x**2
# 这一步用了numpy,所以会出错,为了避免这种情况,采用tf内置的mean函数
y = np.mean(x2, axis=0)
y = tf.reduce_mean(y, axis=0)
print(tape.gradient(y, x))#结果是None
3.通过整数或字符串获取梯度
x = tf.constant(10)#这里是整数,整数是没有梯度的
with tf.GradientTape() as g:
g.watch(x)
y = x * x
print(g.gradient(y, x))
4.通过有状态对象获取梯度
变量的状态就是它的值,Tensorflow只会记录其当前状态,其历史状态会丢失
x0 = tf.Variable(3.0)
x1 = tf.Variable(0.0)
with tf.GradientTape() as tape:
# Update x1 = x1 + x0. 这里x1更新后状态刷新,他是怎么来的没有保存
x1.assign_add(x0)
# The tape starts recording from x1.
y = x1**2 # y = (x1 + x0)**2
# This doesn't work.
print(tape.gradient(y, x0)) #dy/dx0 = 2*(x1 + x0)
#想要修改也很容易,只要新设一个x2=x1+x0, y=x2**2即可