在Tensorflow中使用可变长度文本

时间:2016-07-27 17:23:41

标签: python string text tensorflow variable-length-array

我正在构建一个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]]}

4 个答案:

答案 0 :(得分:4)

另外两个答案是正确的,但细节不多。我自己只是想看看如何做到这一点。

TensorFlow中有一些机器可以用于所有这些(对于某些部件,它可能有点过分)。

从字符串张量(shape [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取代。

答案 1 :(得分:1)

这个怎么样? (我没有实现这个。但也许这个想法会起作用。) 此方法基于BOW表示。

  1. tf.string
  2. 获取您的数据
  3. 使用tf.string_split
  4. 拆分它
  5. 使用tf.contrib.lookup.string_to_index_table_from_filetf.contrib.lookup.string_to_index_table_from_tensor查找单词的索引。张量的长度可以变化。
  6. 查找索引的嵌入。
  7.     word_embeddings = tf.get_variable(“word_embeddings”,
                                          [vocabulary_size, embedding_size])
        embedded_word_ids = tf.nn.embedding_lookup(word_embeddings, word_ids)`
    
    1. 总结嵌入。并且您将获得固定长度(=嵌入大小)的张量。也许您可以选择其他方法sum。(avgmean或其他方式)
    2. 也许已经太晚了:)祝你好运。

答案 2 :(得分:0)

前几天我正在建立一个序列来翻译。我所做的是决定做的是固定长度为32个单词(比平均句子长度略高),尽管你可以根据需要制作它。然后我在字典中添加了一个NULL字,并用它填充了我的所有句子向量。这样我就可以告诉模型序列结束的位置,模型只会在输出结束时输出NULL。例如,用表达式"嗨,你的名字是什么?"这将成为"嗨,你的名字是什么? NULL NULL NULL NULL ... NULL"。它工作得很好但是你在训练期间的损失和准确性会比实际上高一些,因为模型通常会得到正确的NULL,这会花费成本。

还有另一种称为屏蔽的方法。这也允许您为固定长度序列构建模型,但仅评估成本直到较短序列的末尾。您可以在输出序列中搜索NULL的第一个实例(或预期输出,以较大者为准),并仅计算到该点的成本。另外我认为像tf.dynamic_rnn这样的张量流函数支持屏蔽,这可能更有效。我不确定,因为我只尝试了第一种填充方法。

最后,我认为在Seq2Seq模型的张量流示例中,它们使用不同大小序列的桶。这可能会解决你的记忆问题。我想你可以在不同大小的模型之间共享变量。

答案 3 :(得分:0)

所以这就是我所做的(不确定100%是否是正确的方式):

在你的词汇表中,每个键都是一个指向一个特定单词的数字,添加另一个键,表示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]
]

现在,如果将此乘以损失,则基本上可以消除由于填充而引入的所有损失。

希望这会有所帮助。