Skip to main content
 首页 » 编程设计

python之在 TensorFlow 中将 stop_gradient 与 AdamOptimizer 结合使用

2024年12月31日26mq0036

当在每次反向传播迭代中,某组参数保持固定时,我正在尝试实现训练/微调框架。我希望能够在迭代之间更改更新或固定参数集。 TensorFlow 方法 tf.stop_gradient ,这显然强制某些参数的梯度保持为零,对于此目的非常有用,并且如果更新或固定参数集在迭代之间没有变化,则它可以与不同的优化器完美配合。如果与 stochastic gradient descent 一起使用,它还可以处理不同的更新或固定参数集。 .我的问题是 tf.stop_gradient 在与 Adam optimizer 一起使用时无法处理此类情况。 .更具体地说,它确实在 tf.compute_gradients 的输出中将固定参数的梯度保持为零,但是在应用梯度 (tf.apply_gradients) 时,固定参数确实会改变。我想这是因为即使梯度为零(基于 Kingma and Ba's paper 中的算法 1),Adam 优化器中的优化步骤也不为零。是否有一种廉价的方法可以在每次 Adam 迭代中卡住一组可变参数,而无需显式保存前一次迭代的固定参数值?

更多详情:

假设我有一个单层网络,带有权重矩阵变量 W 和一个二进制掩码矩阵占位符 MW 指定 W 的哪些元素应该在每次迭代中得到更新( 中的值 1)。我没有使用W来写这一层的输入/输出关系,而是修改如下

masked_W = MW*W + tf.stop_gradient(tf.abs(1-MW)*W) 

屏蔽 W 的某些元素不具有非零梯度。然后我使用 masked_W 形成层的输出,因此网络的损失取决于这个掩码变量。关键是 MW 在每次迭代中都会发生变化。假设 W 是一个包含 4 个元素的向量,初始化为全零向量。这是发生的事情:

opt=tf.AdamOptimizer(1e-5) 
sess.run(tf.global_variables_initializer()) 
grads_vars=opt.compute_gradients(loss, W) 
 
# initial value of W=[0,0,0,0] 
 
# first iteration: 
MW_val = [0,1,1,0] 
feed_dict={MW:MW_val, x: batch_of_data, y_:batch_of_labels} 
sess.run(opt.apply_gradients(grads_vars), feed_dict=feed_dict)) 
# gradient of  W=[0,xx,xx,0] 
# new value of W=[0,a,b,0] 

其中xx是一些非零梯度值,a和b是更新W <的元素的新值< code> 。在第二次迭代中,我们将分配给二进制掩码矩阵 MW 的值更改为 [1,0,0,1],因此我们希望 W[1]< 具有固定值 和 W[2] 并更新 W[0] 和 W[3] 的值。但这就是发生的事情:

# second iteration 
MW_val = [1,0,0,1] 
feed_dict={MW:MW_val, x: batch_of_data, y_:batch_of_labels} 
sess.run(opt.apply_gradients(grads_vars), feed_dict=feed_dict)) 
# gradient of  W=[xx,0,0,xx] 
# new value of W=[c,aa,bb,d] 

也就是说,虽然W[1]和W[2]的梯度为零,但它们得到了新的值(aa != a 和 bb != b)。将优化器从 Adam 更改为 SGD 时,固定参数的值与预期保持一致。

请您参考如下方法:

我找到了我的问题的解决方案并在这里分享它以防其他人发现它有用。在第一次迭代之后,那些在第一次迭代中更新的参数的矩已经不为零。因此,即使在第二次迭代中将它们的梯度设为零,它们也会因为它们的动量张量非零而被更新。为了防止更新,仅使用 tf.stop_gradient 是不够的,我们还必须去除它们的动量。对于 Adam 优化器,这可以通过优化器的 get_slot 方法完成:opt.get_slot(par, 'm') 和 opt.get_slot(par ,'v'),其中前者和后者分别允许访问参数 par 的第一和第二动量张量。在问题的示例中,我们必须添加以下行以在第二次迭代中卡住 W[1] 和 W[2]:

# momentums of W in the first iteration 
m_vals = opt.get_slot(W, 'm') 
v_vals = opt.get_slot(W, 'v') 
# mask them before running the second iteration 
masked_m_vals[[1,2]]=0 
masked_v_vals[[1,2]]=0 
sess.run(opt.get_slot(W, 'm').assign(masked_m_vals)) 
sess.run(opt.get_slot(W, 'v').assign(masked_v_vals)) 

最好保存掩蔽动量,在上面的例子中 m_vals[[1,2]] 和 v_vals[[1,2]],这样如果在第三次迭代中,我们放宽了W[1]和W[2]的固定约束,我们可以将它们的动量恢复到它们在第一次迭代中的原始值。