仅更新 Tensorflow 中词嵌入矩阵的一部分
Posted
技术标签:
【中文标题】仅更新 Tensorflow 中词嵌入矩阵的一部分【英文标题】:Update only part of the word embedding matrix in Tensorflow 【发布时间】:2016-06-18 14:57:55 【问题描述】:假设我想在训练期间更新一个预训练的词嵌入矩阵,有没有办法只更新词嵌入矩阵的一个子集?
我查看了 Tensorflow API 页面,发现:
# Create an optimizer.
opt = GradientDescentOptimizer(learning_rate=0.1)
# Compute the gradients for a list of variables.
grads_and_vars = opt.compute_gradients(loss, <list of variables>)
# grads_and_vars is a list of tuples (gradient, variable). Do whatever you
# need to the 'gradient' part, for example cap them, etc.
capped_grads_and_vars = [(MyCapper(gv[0]), gv[1])) for gv in grads_and_vars]
# Ask the optimizer to apply the capped gradients.
opt.apply_gradients(capped_grads_and_vars)
但是,我如何将其应用于词嵌入矩阵。假设我这样做:
word_emb = tf.Variable(0.2 * tf.random_uniform([syn0.shape[0],s['es']], minval=-1.0, maxval=1.0, dtype=tf.float32),name='word_emb',trainable=False)
gather_emb = tf.gather(word_emb,indices) #assuming that I pass some indices as placeholder through feed_dict
opt = tf.train.AdamOptimizer(1e-4)
grad = opt.compute_gradients(loss,gather_emb)
然后如何使用opt.apply_gradients
和tf.scatter_update
更新原始嵌入矩阵? (另外,如果compute_gradient
的第二个参数不是tf.Variable
,tensorflow 会抛出错误)
【问题讨论】:
这里如何定义“子集”? 只有嵌入矩阵中的一部分行。由于每一行都是一个词嵌入向量,它只是来自原始词嵌入矩阵的词嵌入向量的一个子集 deeplearning.net/software/theano/tutorial/faq_tutorial.html 这是我想要在 Tensorflow 中实现的目标 【参考方案1】:TL;DR:opt.minimize(loss)
的默认实现,TensorFlow 将为word_emb
生成一个稀疏 更新,该更新仅修改参与的word_emb
的行在向前传球中。
tf.gather(word_emb, indices)
操作相对于word_emb
的梯度是tf.IndexedSlices
对象(see the implementation for more details)。这个对象表示一个稀疏张量,除了indices
选择的行之外,它处处为零。对opt.minimize(loss)
的调用调用AdamOptimizer._apply_sparse(word_emb_grad, word_emb)
,它对tf.scatter_sub(word_emb, ...)
* 的调用仅更新word_emb
中由indices
选择的行。
另一方面,如果您想修改由opt.compute_gradients(loss, word_emb)
返回的tf.IndexedSlices
,您可以对其indices
和values
属性执行任意TensorFlow 操作,并创建一个新的tf.IndexedSlices
,它可以传递给opt.apply_gradients([(word_emb, ...)])
。例如,您可以使用MyCapper()
(如示例中的示例)使用以下调用来限制渐变:
grad, = opt.compute_gradients(loss, word_emb)
train_op = opt.apply_gradients(
[tf.IndexedSlices(MyCapper(grad.values), grad.indices)])
同样,您可以通过创建具有不同索引的新 tf.IndexedSlices
来更改将要修改的索引集。
* 一般来说,如果你想只更新 TensorFlow 中的变量的一部分,你可以使用tf.scatter_update()
, tf.scatter_add()
, or tf.scatter_sub()
operators,它分别设置,添加到(+=
)或减去(-=
)之前的值存储在变量中。
【讨论】:
你确定这像宣传的那样有效吗?请参阅我的问题here 和其中的链接。似乎出于某种原因,TensorFlow 正在将 IndexedSlices 转换为密集张量并且更新速度变慢。 如果您的嵌入变量是tf.gather()
(或tf.nn.embedding_lookup()
)的直接params
参数,它肯定有效。如果梯度通过没有专门用于处理IndexedSlices
的梯度函数的更多操作进行反向传播,则IndexedSlices
将转换为密集张量(目前我相信只有tf.concat()
具有这样的专业化)。
想知道这是否可以仅用于更新特定的词向量,因为我的大部分词都有预训练的向量,但有一些是新的并且需要训练。
tf.scatter_sub op 似乎没有专门用于处理 IndexedSlices 的梯度函数。我问了一个问题,我想你可能知道。 ***.com/questions/59763351/…【参考方案2】:
由于您只想选择要更新的元素(而不是更改渐变),因此可以执行以下操作。
让indices_to_update
是一个布尔张量,表示您希望更新的索引,entry_stop_gradients
在链接中定义,然后:
gather_emb = entry_stop_gradients(gather_emb, indices_to_update)
(Source)
【讨论】:
【参考方案3】:其实我也在为这样的问题苦苦挣扎。就我而言,我需要使用 w2v 嵌入来训练模型,但并非所有标记都存在于嵌入矩阵中。因此,对于那些不在矩阵中的标记,我进行了随机初始化。当然,已经训练嵌入的令牌不应该更新,因此我想出了这样一个解决方案:
class PartialEmbeddingsUpdate(tf.keras.layers.Layer):
def __init__(self, len_vocab,
weights,
indices_to_update):
super(PartialEmbeddingsUpdate, self).__init__()
self.embeddings = tf.Variable(weights, name='embedding', dtype=tf.float32)
self.bool_mask = tf.equal(tf.expand_dims(tf.range(0,len_vocab),1), tf.expand_dims(indices_to_update,0))
self.bool_mask = tf.reduce_any(self.bool_mask,1)
self.bool_mask_not = tf.logical_not(self.bool_mask)
self.bool_mask_not = tf.expand_dims(tf.cast(self.bool_mask_not, dtype=self.embeddings.dtype),1)
self.bool_mask = tf.expand_dims(tf.cast(self.bool_mask, dtype=self.embeddings.dtype),1)
def call(self, input):
input = tf.cast(input, dtype=tf.int32)
embeddings = tf.stop_gradient(self.bool_mask_not * self.embeddings) + self.bool_mask * self.embeddings
return tf.gather(embeddings,input)
其中 len_vocab - 是您的词汇长度, weights - 权重矩阵(其中一些不应更新)和 indices_to_update - 应更新的标记的索引。之后我应用了这个层而不是 tf.keras.layers.Embeddings。希望对遇到同样问题的大家有所帮助。
【讨论】:
以上是关于仅更新 Tensorflow 中词嵌入矩阵的一部分的主要内容,如果未能解决你的问题,请参考以下文章