TensorFlow/PaddlePaddle反向传播变量更新及DropBlock梯度截断疑问
一、未禁用梯度时,是否所有变量都会被反向传播更新?
答案:否。
存在以下场景或运算类型会导致变量无法通过反向传播被优化器更新:
- 无梯度定义的运算:像TensorFlow的
tf.size()、tf.argmax(),PaddlePaddle的paddle.fluid.layers.size()、paddle.argmax()这类操作,输出是张量形状、索引等与输入数值无关的统计量,框架未为其定义梯度计算逻辑,反向传播时梯度会在此中断。 - 数学上不可导的运算:比如取整函数
tf.floor()、paddle.floor(),或是基于离散布尔值的条件分支选择(如tf.where()的部分分支操作),这类操作本身不具备可导性,无法传递梯度。 - 显式标记为不可训练的变量:如果变量被设置为不可训练(如TensorFlow中
tf.Variable(trainable=False),PaddlePaddle中paddle.create_parameter(stop_gradient=True)),即便未手动添加梯度停止操作,优化器也不会对其进行更新。
二、指定代码的梯度计算
针对以下代码流程:
step1:scalar_y = tf.size(tensor_x) 或 scalar_y = paddle.fluid.layers.size(tensor_x)
step2:tensor_z = scalar_y * tensor_x
计算scalar_y对tensor_x的梯度:
由于tf.size()/paddle.fluid.layers.size()仅返回tensor_x的元素个数,与tensor_x的具体数值无关,该操作的梯度为与tensor_x同形状的全0张量。
若计算tensor_z对tensor_x的梯度,因scalar_y相对于tensor_x的数值变化属于常数,梯度结果就是scalar_y本身(即tensor_x的元素个数)。
三、DropBlock实现中的梯度处理
针对代码output = inputs * mask * tf.to_float(tf.size(mask)) / tf.reduce_sum(mask):
不需要将所有变量用tf.stop_gradient包裹,正确的做法是仅对mask、tf.size(mask)、tf.reduce_sum(mask)这些与掩码生成相关的部分添加tf.stop_gradient。
原因:DropBlock的mask是训练时随机生成的二进制掩码,属于随机噪声,我们不需要让梯度更新影响掩码的生成逻辑,只需要让梯度沿着inputs的路径传递。若不对掩码相关部分停止梯度,反向传播时会计算掩码的梯度,但掩码并非可训练变量,不仅会增加不必要的计算开销,还可能导致梯度异常。
内容的提问来源于stack exchange,提问作者Truthuleave

