tf.GradientTape 为渐变返回 None

Posted

技术标签:

【中文标题】tf.GradientTape 为渐变返回 None【英文标题】:tf.GradientTape returns None for gradient 【发布时间】:2021-10-07 09:44:12 【问题描述】:

我正在使用 tf.GradientTape().gradient() 来计算 representer point,它可用于计算给定训练示例对给定测试示例的“影响”。给定测试示例x_t 和训练示例x_i 的表示点计算为它们的特征表示f_tf_i 乘以权重alpha_i 的点积。

注意:此方法的详细信息对于理解问题不是必需的,因为主要问题是让渐变胶带起作用。话虽如此,我已经为任何感兴趣的人提供了以下一些详细信息的屏幕截图。

计算 alpha_i 需要微分,因为它表示如下:

在上面的等式中,L 是标准损失函数(多类分类的分类交叉熵),phi 是 pre-softmax 激活输出(所以它的长度是类的数量)。此外,alpha_i 可以进一步分解为alpha_ij,它是针对特定类j 计算的。因此,我们只需得到测试样例的预测类别(最终预测最高的类别)对应的pre-softmax输出phi_j

我使用 MNIST 创建了一个简单的设置并实现了以下功能:

def simple_mnist_cnn(input_shape = (28,28,1)):
  input = Input(shape=input_shape)
  x = layers.Conv2D(32, kernel_size=(3, 3), activation="relu")(input)
  x = layers.MaxPooling2D(pool_size=(2, 2))(x)
  x = layers.Conv2D(64, kernel_size=(3, 3), activation="relu")(x)
  x = layers.MaxPooling2D(pool_size=(2, 2))(x)
  x = layers.Flatten()(x) # feature representation 
  output = layers.Dense(num_classes, activation=None)(x) # presoftmax activation output 
  activation = layers.Activation(activation='softmax')(output) # final output with activation 
  model = tf.keras.Model(input, [x, output, activation], name="mnist_model")
  return model

现在假设模型已经过训练,我想计算给定训练示例对给定测试示例的预测的影响,可能是出于模型理解/调试目的。

with tf.GradientTape() as t1:
  f_t, _, pred_t = model(x_t) # get features for misclassified example
  f_i, presoftmax_i, pred_i = model(x_i)

  # compute dot product of feature representations for x_t and x_i
  dotps = tf.reduce_sum(
            tf.multiply(f_t, f_i))

  # get presoftmax output corresponding to highest predicted class of x_t
  phi_ij = presoftmax_i[:,np.argmax(pred_t)]

  # y_i is actual label for x_i
  cl_loss_i = tf.keras.losses.categorical_crossentropy(pred_i, y_i)

alpha_ij = t1.gradient(cl_loss_i, phi_ij)
# note: alpha_ij returns None currently
k_ij = tf.reduce_sum(tf.multiply(alpha_i, dotps))

上面的代码给出了以下错误,因为 alpha_ij 为无:ValueError: Attempt to convert a value (None) with an unsupported type (<class 'NoneType'>) to a Tensor.。但是,如果我更改 t1.gradient(cl_loss_i, phi_ij) -> t1.gradient(cl_loss_i, presoftmax_i),它将不再返回 None。不知道为什么会这样?在切片张量上计算梯度是否存在问题? “观察”太多变量是否存在问题?我对渐变胶带的工作不多,所以我不确定修复方法是什么,但希望能得到帮助。

有兴趣的朋友,这里有更多详情:

【问题讨论】:

为什么在张量流梯度中使用 numpy?这几乎肯定是问题所在。 不知道有没有问题?例如,在某些情况下,tensorflow 文档在梯度磁带内使用 numpy 操作:tensorflow.org/guide/autodiff。但可以肯定的是,我将np.argmax(pred_t) 切换为固定索引(例如0),问题仍然存在。 【参考方案1】:

我从来没有见过你watch 任何张量。请注意,默认情况下磁带仅跟踪 tf.Variable。您的代码中是否缺少此内容?否则我看不出t1.gradient(cl_loss_i, presoftmax_i) 是如何工作的。

无论哪种方式,我认为修复它的最简单方法就是这样做

all_gradients = t1.gradient(cl_loss_i, presoftmax_i)
desired_gradients = all_gradients[[:,np.argmax(pred_t)]]

所以只需在渐变之后进行索引。请注意,这可能会造成浪费(如果有很多类),因为您计算的梯度超出了您的需要。

为什么(我相信)您的版本不起作用的解释最容易在图纸中显示,但让我尝试解释一下:想象一下有向图中的计算。我们有

presoftmax_i -> pred_i -> cl_loss_i

将损失反向传播到 presoftmax 很容易。但后来你又建立了一个分支,

presoftmax_i -> presoftmax_ij

现在,当您尝试计算损失相对于presoftmax_ij 的梯度时,实际上没有反向传播路径(我们只能沿着箭头向后)。另一种思考方式:您计算presoftmax_ij计算损失之后。那损失怎么能靠它呢?

【讨论】:

感谢您的回复!这也是我的担忧。另外,我没有忘记带一块手表,所以我也不确定它是如何工作的。你知道我会如何观察直到在渐变磁带内部才定义的变量(例如 presoftmax_ij)?还要注意的一件事(这可能很明显)是任何索引似乎都是一个问题。例如,我将 presoftmax_i 更改为 presoftmax_i[:,:] ,这是等效的,后者返回 None 而前面的则不返回。 另外,如果我在计算 cl_loss_i 之前计算 presoftmax_ij,你会期望有什么改变吗?我尝试改变它,但这似乎也没有帮助。 1.您可以在磁带期间的任何时候watch 变量,因此在定义张量之后(甚至在磁带内部)调用它应该不是问题,但我不是 100% 确定。 2. 关于索引[:,:],它会创建张量的副本,所以我猜它会被视为“新”结果,并导致与使用某些i 进行索引相同的问题。 3.关于在损失之前计算_ij——很难说,因为看起来你编辑了问题并且_ij消失了。 ;) 但我认为,如果损失不是根据 _ij 计算的。

以上是关于tf.GradientTape 为渐变返回 None的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 tf.GradientTape 模拟 ReLU 梯度

tf.GradientTape详解

:模型训练和预测的三种方法(fit&tf.GradientTape&train_step&tf.data)

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

深度卷积生成对抗网络DCGAN——生成手写数字图片

深度卷积生成对抗网络DCGAN——生成手写数字图片