当每次反向传播迭代中某些参数保持固定时,我正在尝试实现一个训练/微调框架。我希望能够在迭代之间更改一组更新或固定参数。 TensorFlow方法tf.stop_gradient
(显然会迫使某些参数的梯度保持为零)对于此目的非常有用,并且如果更新或固定参数的设置在每次迭代之间均不变,则它在不同的优化程序中效果很好。如果与stochastic gradient descent一起使用,它还可以处理各种更新或固定参数集。我的问题是,tf.stop_gradient
与Adam 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
是一些非零梯度值,而a
和b
是W
的更新元素的新值。在第二次迭代中,我们将分配给二进制掩码矩阵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时,固定参数的值保持与预期相同。
答案 0 :(得分:0)
我找到了问题的解决方案,并在此处分享,以防其他人发现它有用。在第一次迭代之后,在第一次迭代中已更新的那些参数的力矩已经不为零。因此,即使在第二次迭代中将其梯度设为零,由于其非零动量张量,它们也会被更新。为了阻止更新,仅使用tf.stop_gradient
是不够的,我们还必须删除它们的动力。对于Adam优化器,可以通过优化器的get_slot
方法:opt.get_slot(par, 'm')
和opt.get_slot(par,'v')
来完成,其中前者和后者可以访问参数{的第一和第二动量张量{1}}。在问题的示例中,我们必须添加以下行以冻结第二次迭代中的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]]
和{{1} },我们可以在第一次迭代中将它们的动量恢复到原始值。
答案 1 :(得分:0)
或者,当您要更新变量的不同子集时,可以将变量的不同子集传递给apply_gradients。