在Tensorflow中使用可变长度文本

Mar*_*ona 6 python string text variable-length-array tensorflow

我正在构建一个Tensorflow模型来对文本短语进行推理.为简单起见,假设我需要一个具有固定数量的输出类但输入中的可变长度文本的分类器.换句话说,我的迷你批次将是一系列短语,但并非所有短语都具有相同的长度.

data = ['hello',
        'my name is Mark',
        'What is your name?']
Run Code Online (Sandbox Code Playgroud)

我的第一个预处理步骤是在字典中构建一个包含所有可能单词的字典,并将每个单词映射到整数单词-Id.输入变为:

data = [[1],
        [2, 3, 4, 5],
        [6, 4, 7, 3]
Run Code Online (Sandbox Code Playgroud)

处理这种输入的最佳方法是什么?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]]}))
Run Code Online (Sandbox Code Playgroud)

其中占位符的第一个维度是小批量大小.

如果输入序列是不同长度的句子中的单词怎么办?

feed_dict={input:[[1, 2, 3], [1]]}
Run Code Online (Sandbox Code Playgroud)

mda*_*ust 5

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

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', '?']
Run Code Online (Sandbox Code Playgroud)

首先要做的是将其拆分为单词(注意问号前的空格。)

words = tf.string_split(lines," ")
Run Code Online (Sandbox Code Playgroud)

单词现在将是一个稀疏张量(形状 [3,7])。其中索引的两个维度是[行号,位置]。这表示为:

indices    values
 0 0       'hello'
 1 0       'my'
 1 1       'name'
 1 2       'is'
 ...
Run Code Online (Sandbox Code Playgroud)

现在您可以进行单词查找:

indices    values
 0 0       'hello'
 1 0       'my'
 1 1       'name'
 1 2       'is'
 ...
Run Code Online (Sandbox Code Playgroud)

这将返回一个稀疏张量,其中单词被其词汇索引替换。

现在您可以通过查看每行的最大位置来读出序列长度:

table = tf.contrib.lookup.index_table_from_tensor(vocabulary)
word_indices = table.lookup(words)
Run Code Online (Sandbox Code Playgroud)

因此,如果您正在处理可变长度序列,则可能需要放入 lstm ......所以让我们对输入使用词嵌入(它需要密集输入):

line_number = word_indices.indices[:,0]
line_position = word_indices.indices[:,1]
lengths = tf.segment_max(data = line_position, 
                         segment_ids = line_number)+1
Run Code Online (Sandbox Code Playgroud)

现在嵌入的形状为 [3,7,100], [lines, words, embedding_dim]。

然后可以构建一个简单的 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)
Run Code Online (Sandbox Code Playgroud)

并在整个序列中运行,处理填充。

LSTM_SIZE = 50
lstm = tf.nn.rnn_cell.BasicLSTMCell(LSTM_SIZE)
Run Code Online (Sandbox Code Playgroud)

现在输出的形状为 [3,7,50] 或 [line,word,lstm_size]。如果你想获取每行最后一个单词的状态,你可以使用 (hidden! undocumented!)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))
Run Code Online (Sandbox Code Playgroud)

这会进行所有索引混洗以选择最后一个时间步的输出。这给出了 [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())
Run Code Online (Sandbox Code Playgroud)

我还没有弄清楚细节,但我认为这可能全部被一个tf.contrib.learn.DynamicRnnEstimator取代。


cha*_*255 0

前几天我正在构建一个序列到序列翻译器。我决定将其固定为 32 个单词的长度(略高于平均句子长度),尽管您可以根据需要将其长度设置为任意长。然后,我向字典中添加了一个 NULL 单词,并用它填充了所有句子向量。这样我就可以告诉模型序列的末尾在哪里,并且模型只会在输出末尾输出 NULL。例如,采用“嗨,你叫什么名字?”这样的表达方式。这将变成“嗨,你叫什么名字?NULL NULL NULL NULL ... NULL”。它工作得很好,但训练过程中的损失和准确性会比实际情况高一些,因为模型通常会得到正确的 NULL,这会计入成本。

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

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