在TensorFlow中将stop_gradient与AdamOptimizer一起使用

时间:2018-07-03 15:36:48

标签: python tensorflow machine-learning backpropagation

当每次反向传播迭代中某些参数保持固定时,我正在尝试实现一个训练/微调框架。我希望能够在迭代之间更改一组更新或固定参数。 TensorFlow方法tf.stop_gradient(显然会迫使某些参数的梯度保持为零)对于此目的非常有用,并且如果更新或固定参数的设置在每次迭代之间均不变,则它在不同的优化程序中效果很好。如果与stochastic gradient descent一起使用,它还可以处理各种更新或固定参数集。我的问题是,tf.stop_gradientAdam optimizer一起使用时无法处理此类情况。更具体地说,它的确在tf.compute_gradients的输出中将固定参数的梯度保持为零,但在应用梯度(tf.apply_gradients)时,固定参数的值会发生变化。我想这是因为即使梯度为零,Adam优化器中的优化步骤也不为零(基于Kingma and Ba's paper中的算法1)。是否有一种便宜的方法可以在每次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是一些非零梯度值,而abW的更新元素的新值。在第二次迭代中,我们将分配给二进制掩码矩阵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 != abb != b)。将优化器从Adam更改为SGD时,固定参数的值保持与预期相同。

2 个答案:

答案 0 :(得分:0)

我找到了问题的解决方案,并在此处分享,以防其他人发现它有用。在第一次迭代之后,在第一次迭代中已更新的那些参数的力矩已经不为零。因此,即使在第二次迭代中将其梯度设为零,由于其非零动量张量,它们也会被更新。为了阻止更新,仅使用tf.stop_gradient是不够的,我们还必须删除它们的动力。对于Adam优化器,可以通过优化器的get_slot方法:opt.get_slot(par, 'm')opt.get_slot(par,'v')来完成,其中前者和后者可以访问参数{的第一和第二动量张量{1}}。在问题的示例中,我们必须添加以下行以冻结第二次迭代中的parW[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]]和{{1} },我们可以在第一次迭代中将它们的动量恢复到原始值。

答案 1 :(得分:0)

或者,当您要更新变量的不同子集时,可以将变量的不同子集传递给apply_gradients。