带有 Keras 的渐变带 returns 0

GradientTape with Keras returns 0

我已经尝试将 GradientTape 与 Keras 模型(简化)一起使用,如下所示:

import tensorflow as tf
tf.enable_eager_execution()

input_ = tf.keras.layers.Input(shape=(28, 28))
flat = tf.keras.layers.Flatten()(input_)
output = tf.keras.layers.Dense(10, activation='softmax')(flat)
model = tf.keras.Model(input_, output)
model.compile(loss='categorical_crossentropy', optimizer='sgd')

import numpy as np
inp = tf.Variable(np.random.random((1,28,28)), dtype=tf.float32, name='input')
target = tf.constant([[1,0,0,0,0,0,0,0,0,0]], dtype=tf.float32)
with tf.GradientTape(persistent=True) as g:
    g.watch(inp)
    result = model(inp, training=False)

print(tf.reduce_max(tf.abs(g.gradient(result, inp))))

但是对于inp的某些随机值,梯度处处为零,而对于其余部分,梯度幅度非常小(<1e-7)。

我也用 MNIST 训练的 3 层 MLP 进行了尝试,结果是一样的,但是用没有激活的 1 层线性模型进行了尝试。

这是怎么回事?

根据模型的输出计算梯度通常意义不大,通常您会根据损失计算梯度,这告诉模型变量应该去哪里才能达到您的目标。在这种情况下,您将优化输入而不是模型参数,但它是相同的。

import tensorflow as tf
import numpy as np
tf.enable_eager_execution()  # Not necessary in TF 2.x

tf.random.set_random_seed(0)  # tf.random.set_seed in TF 2.x
np.random.seed(0)
input_ = tf.keras.layers.Input(shape=(28, 28))
flat = tf.keras.layers.Flatten()(input_)
output = tf.keras.layers.Dense(10, activation='softmax')(flat)
model = tf.keras.Model(input_, output)
model.compile(loss='categorical_crossentropy', optimizer='sgd')

inp = tf.Variable(np.random.random((1, 28, 28)), dtype=tf.float32, name='input')
target = tf.constant([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=tf.float32)
with tf.GradientTape(persistent=True) as g:
    g.watch(inp)
    result = model(inp, training=False)
    # Get the loss for the example
    loss = tf.keras.losses.categorical_crossentropy(target, result)

print(tf.reduce_max(tf.abs(g.gradient(loss, inp))))
# tf.Tensor(0.118953675, shape=(), dtype=float32)

您正在计算 softmax 输出层的梯度——因为 softmax 总是总和为 1,所以梯度(在多输入情况下,在维度 AFAIK 上 summed/averaged 是有意义的)必须为 0——层的整体输出不能改变。我认为,当您获得大于 0 的小值时,是数值问题。
当您删除激活函数时,此限制不再成立并且激活可以变得更大(意味着幅度 > 0 的梯度)。

您是否正在尝试使用梯度下降来构造输入,从而导致某个 class 的概率非常大(如果不是,请忽略此...)? @jdehesa 已经包含了一种通过损失函数来做到这一点的方法。请注意,您 也可以 通过 softmax 来完成它,如下所示:

import tensorflow as tf
tf.enable_eager_execution()

input_ = tf.keras.layers.Input(shape=(28, 28))
flat = tf.keras.layers.Flatten()(input_)
output = tf.keras.layers.Dense(10, activation='softmax')(flat)
model = tf.keras.Model(input_, output)
model.compile(loss='categorical_crossentropy', optimizer='sgd')

import numpy as np
inp = tf.Variable(np.random.random((1,28,28)), dtype=tf.float32, name='input')   
with tf.GradientTape(persistent=True) as g:
    g.watch(inp)
    result = model(inp, training=False)[:,0]

print(tf.reduce_max(tf.abs(g.gradient(result, inp))))

请注意,我仅获取第 0 列中的结果,对应于第一个 class(我删除了 target,因为它未被使用)。这将仅为此 class 的 softmax 值计算梯度,这是有意义的。

一些注意事项:

  • 在渐变带上下文管理器中进行索引很重要!如果你在外面做(例如在你调用 g.gradient 的行中,这将不起作用(没有渐变)
  • 您也可以使用 logits(softmax 之前的值)的梯度。这是不同的,因为可以通过降低其他 classes 的可能性来增加 softmax 概率,而只能通过增加所讨论的 class 的 "score" 来增加 logits。