在 TensorFlow 中处理可变长度文本
Posted
技术标签:
【中文标题】在 TensorFlow 中处理可变长度文本【英文标题】:Working with variable-length text in Tensorflow 【发布时间】:2016-12-01 20:26:30 【问题描述】:我正在构建一个 Tensorflow 模型来对文本短语进行推理。 为简单起见,假设我需要一个具有固定数量的输出类但输入中的 可变长度文本 的分类器。换句话说,我的小批量将是一系列短语,但并非所有短语都具有相同的长度。
data = ['hello',
'my name is Mark',
'What is your name?']
我的第一个预处理步骤是构建字典中所有可能单词的字典,并将每个单词映射到其整数单词 ID。输入变为:
data = [[1],
[2, 3, 4, 5],
[6, 4, 7, 3]
处理这种输入的最佳方式是什么? tf.placeholder() 可以处理同一批数据中的可变大小输入吗? 或者我应该填充所有字符串,使它们都具有相同的长度,等于最长字符串的长度,使用一些占位符来代替缺失的单词?如果某些字符串比其他大多数字符串长得多,这似乎是非常低效的内存。
-- 编辑--
这是一个具体的例子。
当我知道我的数据点的大小(并且所有数据点都具有相同的长度,例如 3)时,我通常会使用以下内容:
input = tf.placeholder(tf.int32, shape=(None, 3)
with tf.Session() as sess:
print(sess.run([...], feed_dict=input:[[1, 2, 3], [1, 2, 3]]))
占位符的第一个维度是小批量大小。
如果输入序列是不同长度的句子中的单词怎么办?
feed_dict=input:[[1, 2, 3], [1]]
【问题讨论】:
文本通常由序列模型处理。 IE,您的模型接受当前单词和上一步的输出,并且您堆叠模型的副本。作为基线,您可以从“词袋”开始——只需将所有词一起添加到单个字典向量中。 感谢您的回复。我的问题更多是关于 Tensorflow 的数据结构而不是模型。我可以使用以词袋表示的文本馈送的 RNN。仍然如果我的数据点有不同的长度,我应该在哪里或如何存储这种数据? 我编辑了删除对词嵌入的引用的问题,并举了一个更具体的例子来澄清我的问题。 【参考方案1】:这个怎么样? (我没有实现这个。但也许这个想法会奏效。) 此方法基于BOW 表示。
-
以
tf.string
获取您的数据
使用tf.string_split
拆分它
使用tf.contrib.lookup.string_to_index_table_from_file
或tf.contrib.lookup.string_to_index_table_from_tensor
查找单词的索引。这个张量的长度可以变化。
查找索引的嵌入。
-
总结嵌入。你会得到一个固定长度的张量(=嵌入大小)。也许您可以选择其他方法然后
sum
。(avg
、mean
或其他)
也许为时已晚 :) 祝你好运。
【讨论】:
【参考方案2】:其他两个答案是正确的,但细节很少。我只是在研究自己如何做到这一点。
TensorFlow 中的机制可以解决所有这些问题(对于某些部分来说,这可能是矫枉过正)。
从一个字符串张量开始(形状[3]):
import tensorflow as tf
lines = tf.constant([
'Hello',
'my name is also Mark',
'Are there any other Marks here ?'])
vocabulary = ['Hello', 'my', 'name', 'is', 'also', 'Mark', 'Are', 'there', 'any', 'other', 'Marks', 'here', '?']
首先要做的是把它分成单词(注意问号前的空格。)
words = tf.string_split(lines," ")
单词现在将是一个稀疏张量(形状 [3,7])。其中索引的两个维度是[行号,位置]。这表示为:
indices values
0 0 'hello'
1 0 'my'
1 1 'name'
1 2 'is'
...
现在您可以进行单词查找了:
table = tf.contrib.lookup.index_table_from_tensor(vocabulary)
word_indices = table.lookup(words)
这将返回一个稀疏张量,其中单词被其词汇索引替换。
现在您可以通过查看每行的最大位置来读出序列长度:
line_number = word_indices.indices[:,0]
line_position = word_indices.indices[:,1]
lengths = tf.segment_max(data = line_position,
segment_ids = line_number)+1
因此,如果您正在处理可变长度序列,则可能需要放入一个 lstm ......所以让我们对输入使用词嵌入(它需要密集输入):
EMBEDDING_DIM = 100
dense_word_indices = tf.sparse_tensor_to_dense(word_indices)
e_layer = tf.contrib.keras.layers.Embedding(len(vocabulary), EMBEDDING_DIM)
embedded = e_layer(dense_word_indices)
现在嵌入的形状将是 [3,7,100], [lines, words, embedding_dim]。
那么就可以构建一个简单的lstm了:
LSTM_SIZE = 50
lstm = tf.nn.rnn_cell.BasicLSTMCell(LSTM_SIZE)
并在整个序列中运行,处理填充。
outputs, final_state = tf.nn.dynamic_rnn(
cell=lstm,
inputs=embedded,
sequence_length=lengths,
dtype=tf.float32)
现在输出的形状为 [3,7,50] 或 [line,word,lstm_size]。如果您想获取每行最后一个单词的状态,可以使用(隐藏!未记录!)select_last_activations
函数:
from tensorflow.contrib.learn.python.learn.estimators.rnn_common import select_last_activations
final_output = select_last_activations(outputs,tf.cast(lengths,tf.int32))
这会执行所有索引改组以选择上一个时间步的输出。这给出了 [3,50] 或 [line, lstm_size]
的大小init_t = tf.tables_initializer()
init = tf.global_variables_initializer()
with tf.Session() as sess:
init_t.run()
init.run()
print(final_output.eval().shape())
我还没有弄清楚细节,但我认为这可能都可以用一个 tf.contrib.learn.DynamicRnnEstimator 代替。
【讨论】:
【参考方案3】:这就是我所做的(不确定它是否 100% 正确):
在每个键是指向一个特定单词的数字的 vocab dict 中,添加另一个键,例如 K,它指向 "<PAD>"
(或您要用于填充的任何其他表示)
现在您的输入占位符将如下所示:
x_batch = tf.placeholder(tf.int32, shape=(batch_size, None))
其中 None 表示您的小批量中最大的短语/句子/记录。
我使用的另一个小技巧是将每个短语的长度存储在我的小批量中。例如:
如果我的输入是:x_batch = [[1], [1,2,3], [4,5]]
然后我存储:len_batch = [1, 3, 2]
稍后我在我的小批量中使用这个len_batch
和一个短语的最大大小(l_max
)来创建一个二进制掩码。现在从上面看到l_max=3
,所以我的面具看起来像这样:
mask = [
[1, 0, 0],
[1, 1, 1],
[1, 1, 0]
]
现在,如果将其与损失相乘,您基本上可以消除由于填充而引入的所有损失。
希望这会有所帮助。
【讨论】:
【参考方案4】:前几天我正在构建一个序列到序列转换器。我决定做的是固定长度为 32 个单词(这比平均句子长度高一点),尽管你可以随心所欲。然后我在字典中添加了一个 NULL 词,并用它填充了我所有的句子向量。这样我就可以告诉模型我的序列结束在哪里,并且模型只会在其输出结束时输出 NULL。例如,使用“嗨,你叫什么名字?”这样的表达方式。这将变成“嗨,你叫什么名字?NULL NULL NULL NULL ... NULL”。它工作得很好,但是你在训练期间的损失和准确率会比实际高一些,因为模型通常会得到正确的 NULL,这会计入成本。
还有另一种方法称为屏蔽。这也允许您为固定长度的序列构建模型,但只评估到较短序列结束的成本。您可以在输出序列(或预期输出,以较大者为准)中搜索 NULL 的第一个实例,并仅评估到该点的成本。另外我认为一些张量流函数,如 tf.dynamic_rnn 支持屏蔽,这可能更节省内存。我不确定,因为我只尝试了第一种填充方法。
最后,我认为在 Seq2Seq 模型的 tensorflow 示例中,他们使用存储桶来存储不同大小的序列。这可能会解决您的内存问题。我认为您可以在不同大小的模型之间共享变量。
【讨论】:
以上是关于在 TensorFlow 中处理可变长度文本的主要内容,如果未能解决你的问题,请参考以下文章
如何在 UITableView 中优雅地处理来自网络的可变长度文本?
测试精度 0.5 TensorFlow RNN 可变长度字符串