TensorFlow可变序列长度与批量大小处理及embedding_lookup内存警告解决
嘿,这个问题我之前做文本模型的时候也踩过坑,来给你理清楚前因后果和解决办法!
首先,先解释你看到的那个警告:
Converting sparse IndexedSlices to a dense Tensor of unknown shape. This may consume a large amount of memory.
当你把输入占位符x的形状设为[None, None](同时允许批次大小和句子长度可变)时,TensorFlow没办法提前推断出embedding_lookup输出的静态形状。为了节省内存,它会先用IndexedSlices这种稀疏结构来存储查找结果,但后续操作(比如你提到的tf.gather或者梯度计算)需要密集张量,这时候就会触发稀疏转密集的操作。因为形状未知,TensorFlow会按最大可能的维度分配内存,就可能导致内存占用过高,所以弹出这个警告。
好在你已经做了分桶(把长度相近的句子放一起,同一桶内长度一致),这给解决问题提供了很大便利,下面是几个实用的解决方案:
方案1:用tf.gather替代tf.nn.embedding_lookup
你的输入是密集的词汇索引序列,tf.gather可以直接从embedding矩阵中取出对应索引的向量,返回的是标准的密集张量,完全不会产生IndexedSlices,自然也就消除了警告。代码示例:
vocab_size = 10000 # 你的词表大小 embed_dim = 128 # 嵌入维度 # 定义embedding矩阵 embedding_matrix = tf.get_variable("embedding", shape=[vocab_size, embed_dim]) # 可变形状的输入占位符 x = tf.placeholder(tf.int32, shape=[None, None], name='x') # 用tf.gather替代embedding_lookup embedded = tf.gather(embedding_matrix, x)
方案2:显式指定动态形状(保留embedding_lookup)
如果你还是想用tf.nn.embedding_lookup,可以在查找后立刻用动态形状重新reshape,让TensorFlow明确输出的密集形状,避免稀疏转密集的隐式转换:
x = tf.placeholder(tf.int32, shape=[None, None], name='x') embedding_matrix = tf.get_variable("embedding", shape=[vocab_size, embed_dim]) embedded = tf.nn.embedding_lookup(embedding_matrix, x) # 获取当前批次的动态形状 batch_size = tf.shape(x)[0] seq_len = tf.shape(x)[1] # 重新reshape为明确的动态形状 embedded = tf.reshape(embedded, [batch_size, seq_len, embed_dim])
方案3:利用分桶优势,传入当前批次的句子长度
既然数据提供方会告诉你每个批次的句子长度,你可以把这个长度作为标量占位符传入,后续用它来约束输出的有效形状,既消除警告又能避免不必要的内存占用:
x = tf.placeholder(tf.int32, shape=[None, None], name='x') # 新增一个标量占位符,传入当前批次的句子长度 current_seq_len = tf.placeholder(tf.int32, shape=[], name='current_seq_len') embedding_matrix = tf.get_variable("embedding", shape=[vocab_size, embed_dim]) embedded = tf.nn.embedding_lookup(embedding_matrix, x) # 裁剪到当前批次的实际句子长度(虽然分桶后填充已经对齐,但更保险) embedded = embedded[:, :current_seq_len, :]
最后补充一句:因为你已经做了分桶,每个批次的句子长度是固定的,所以在喂数据时,TensorFlow会自动识别当前批次的实际形状,以上方案都能很好地适配你的需求,解决警告问题的同时,也不会影响模型的灵活性。
内容的提问来源于stack exchange,提问作者hyperio

