22

我一直在尝试使用https://www.tensorflow.org/tutorials/recurrent来理解示例代码 ,您可以在https://github.com/tensorflow/models/blob/master/tutorials/rnn/ptb找到/ptb_word_lm.py

(使用张量流 1.3.0。)

对于我的问题,我总结了(我认为是)关键部分:

 size = 200
 vocab_size = 10000
 layers = 2
 # input_.input_data is a 2D tensor [batch_size, num_steps] of
 #    word ids, from 1 to 10000

 cell = tf.contrib.rnn.MultiRNNCell(
    [tf.contrib.rnn.BasicLSTMCell(size) for _ in range(2)]
    )

 embedding = tf.get_variable(
      "embedding", [vocab_size, size], dtype=tf.float32)
 inputs = tf.nn.embedding_lookup(embedding, input_.input_data)

inputs = tf.unstack(inputs, num=num_steps, axis=1)
outputs, state = tf.contrib.rnn.static_rnn(
    cell, inputs, initial_state=self._initial_state)

output = tf.reshape(tf.stack(axis=1, values=outputs), [-1, size])
softmax_w = tf.get_variable(
    "softmax_w", [size, vocab_size], dtype=data_type())
softmax_b = tf.get_variable("softmax_b", [vocab_size], dtype=data_type())
logits = tf.matmul(output, softmax_w) + softmax_b

# Then calculate loss, do gradient descent, etc.

我最大的问题是如何使用生成的模型来实际生成下一个单词建议,给定一个句子的前几个单词?具体来说,我想象流程是这样的,但我无法理解注释行的代码是什么:

prefix = ["What", "is", "your"]
state = #Zeroes
# Call static_rnn(cell) once for each word in prefix to initialize state
# Use final output to set a string, next_word
print(next_word)

我的子问题是:

  • 为什么要使用随机(未初始化、未经训练)的词嵌入?
  • 为什么要使用softmax?
  • 隐藏层是否必须匹配输入的维度(即 word2vec 嵌入的维度)
  • 如何/我可以引入一个预训练的 word2vec 模型,而不是那个未初始化的模型?

(我把它们都当作一个问题来问,因为我怀疑它们都是相互关联的,并且与我的理解中的一些差距有关。)

我期望在这里看到的是加载一个现有的 word2vec 词嵌入集(例如使用 gensim's KeyedVectors.load_word2vec_format()),在加载每个句子时将输入语料库中的每个词转换为该表示,然后 LSTM 会吐出一个向量相同的维度,我们会尝试找到最相似的词(例如使用 gensim's similar_by_vector(y, topn=1))。

使用 softmax 是否可以让我们免于相对较慢的similar_by_vector(y, topn=1)调用?


顺便说一句,对于我的问题的 pre-existing word2vec 部分Using pre-trained word2vec with LSTM for word generation是相似的。然而,目前那里的答案并不是我想要的。我所希望的是一个简单的英语解释,它为我打开了灯,并填补了我理解中的任何空白。  在 lstm 语言模型中使用预训练的 word2vec?是另一个类似的问题。

更新: Predicting the next word using the language model tensorflow examplePredicting the next word using the LSTM ptb model tensorflow example是类似的问题。但是,两者都没有显示代码来实际获取句子的前几个单词,并打印出对下一个单词的预测。我尝试从第二个问题和https://stackoverflow.com/a/39282697/841830(带有 github 分支)粘贴代码,但无法正常运行。我认为它们可能适用于早期版本的 TensorFlow?

另一个更新:另一个问题基本相同:Predicting Next Word of LSTM Model from Tensorflow Example 它链接到 Predicting next word using the language model tensorflow example(同样,那里的答案不是我想要的) .

如果仍然不清楚,我正在尝试编写一个名为 的高级函数getNextWord(model, sentencePrefix),其中model是我从磁盘加载的先前构建的 LSTM,sentencePrefix是一个字符串,例如“打开可能会返回“pod”。然后我可能会用“Open the pod”来调用它,它会返回“bay”,依此类推。

一个示例(使用字符 RNN,并使用 mxnet)是显示在https://github.com/zackchase/mxnet-the-straight-dope/blob/master/chapter05_recurrent-neural-networks/simple-sample()末尾附近的函数rnn.ipynb 你可以sample()在训练时调用,也可以在训练后调用它,用任何你想要的语句。

4

4 回答 4

7

我最大的问题是如何使用生成的模型在给定句子的前几个单词的情况下实际生成下一个单词建议?

即我正在尝试使用签名编写一个函数:getNextWord(model, sentencePrefix)

在我解释我的答案之前,首先对您的建议发表评论 # Call static_rnn(cell) once for each word in prefix to initialize state:请记住,static_rnn它不会返回像 numpy 数组这样的值,而是返回张量。您可以在 (1) 在会话中运行时将张量评估为一个值(会话保持计算图的状态,包括模型参数的值)和 (2) 使用计算所需的输入张量值。可以使用输入阅读器(本教程中的方法)或使用占位符(我将在下面使用)提供输入。

现在遵循实际答案:本教程中的模型旨在从文件中读取输入数据。@user3080953 的答案已经展示了如何使用您自己的文本文件,但据我了解,您需要更多地控制数据如何馈送到模型。为此,您需要定义自己的占位符,并在调用时将数据提供给这些占位符session.run()

在下面的代码中,我进行了子类化PTBModel并使其负责将数据显式地提供给模型。我介绍了一个特殊PTBInteractiveInput的接口,它具有类似于 的接口,PTBInput因此您可以重用PTBModel. 要训​​练您的模型,您仍然需要PTBModel.

class PTBInteractiveInput(object):
  def __init__(self, config):
    self.batch_size = 1
    self.num_steps = config.num_steps
    self.input_data = tf.placeholder(dtype=tf.int32, shape=[self.batch_size, self.num_steps])
    self.sequence_len = tf.placeholder(dtype=tf.int32, shape=[])
    self.targets = tf.placeholder(dtype=tf.int32, shape=[self.batch_size, self.num_steps])

class InteractivePTBModel(PTBModel):

  def __init__(self, config):
    input = PTBInteractiveInput(config)
    PTBModel.__init__(self, is_training=False, config=config, input_=input)
    output = self.logits[:, self._input.sequence_len - 1, :]
    self.top_word_id = tf.argmax(output, axis=2)

  def get_next(self, session, prefix):
    prefix_array, sequence_len = self._preprocess(prefix)
    feeds = {
      self._input.sequence_len: sequence_len,
      self._input.input_data: prefix_array,
    }
    fetches = [self.top_word_id]
    result = session.run(fetches, feeds)
    self._postprocess(result)

  def _preprocess(self, prefix):
    num_steps = self._input.num_steps
    seq_len = len(prefix)
    if seq_len > num_steps:
      raise ValueError("Prefix to large for model.")
    prefix_ids = self._prefix_to_ids(prefix)
    num_items_to_pad = num_steps - seq_len
    prefix_ids.extend([0] * num_items_to_pad)
    prefix_array = np.array([prefix_ids], dtype=np.float32)
    return prefix_array, seq_len

  def _prefix_to_ids(self, prefix):
    # should convert your prefix to a list of ids
    pass

  def _postprocess(self, result):
    # convert ids back to strings
    pass

在你需要添加这一行的__init__功能:PTBModel

self.logits = logits

为什么要使用随机(未初始化、未经训练)的词嵌入?

首先请注意,尽管嵌入在开始时是随机的,但它们将与网络的其余部分一起训练。您在训练后获得的嵌入将具有与您使用 word2vec 模型获得的嵌入相似的属性,例如,能够通过向量操作(国王 - 男人 + 女人 = 女王等)回答类比问题。对于语言建模(不需要带注释的训练数据)或神经机器翻译等训练数据,从头开始训练嵌入更为常见。

为什么要使用softmax?

Softmax 是将相似度得分(logits)向量归一化为概率分布的函数。您需要一个概率分布来训练具有交叉熵损失的模型并能够从模型中进行采样。请注意,如果您只对训练模型中最可能出现的单词感兴趣,则不需要 softmax,您可以直接使用 logits。

隐藏层是否必须匹配输入的维度(即 word2vec 嵌入的维度)

不,原则上它可以是任何值。然而,使用比嵌入维度更低的隐藏状态并没有多大意义。

如何/我可以引入一个预训练的 word2vec 模型,而不是那个未初始化的模型?

这是一个使用给定 numpy 数组初始化嵌入的自包含示例。如果您希望嵌入在训练期间保持固定/恒定,请设置trainableFalse.

import tensorflow as tf
import numpy as np
vocab_size = 10000
size = 200
trainable=True
embedding_matrix = np.zeros([vocab_size, size]) # replace this with code to load your pretrained embedding
embedding = tf.get_variable("embedding",
                            initializer=tf.constant_initializer(embedding_matrix),
                            shape=[vocab_size, size],
                            dtype=tf.float32,
                            trainable=trainable)
于 2017-09-14T10:18:49.043 回答
7

主要问题

加载单词

加载自定义数据而不是使用测试集:

reader.py@ptb_raw_data

test_path = os.path.join(data_path, "ptb.test.txt")
test_data = _file_to_word_ids(test_path, word_to_id)  # change this line

test_data应该包含单词 id(打印出来word_to_id用于映射)。例如,它应该看起来像: [1, 52, 562, 246] ...

显示预测

我们需要logits在调用中返回 FC 层 ( )的输出sess.run

ptb_word_lm.py@PTBModel.__init__

    logits = tf.reshape(logits, [self.batch_size, self.num_steps, vocab_size])
    self.top_word_id = tf.argmax(logits, axis=2)  # add this line

ptb_word_lm.py@run_epoch

  fetches = {
      "cost": model.cost,
      "final_state": model.final_state,
      "top_word_id": model.top_word_id # add this line
  }

稍后在函数中,vals['top_word_id']将有一个整数数组,其中包含顶部单词的 ID。查找此内容word_to_id以确定预测的单词。前段时间我用小模型做了这个,前 1 的准确率相当低(20-30% iirc),尽管困惑是在标题中预测的。

子问题

为什么要使用随机(未初始化、未经训练)的词嵌入?

您必须询问作者,但在我看来,训练嵌入使这更像是一个独立的教程:它不是将嵌入视为黑匣子,而是展示了它是如何工作的。

为什么要使用softmax?

最终预测不是由与隐藏层输出的余弦相似度决定的。在 LSTM 之后有一个 FC 层,将嵌入状态转换为最终单词的 one-hot 编码。

这是神经网络中的操作和维度的草图:

word -> one hot code (1 x vocab_size) -> embedding (1 x hidden_size) -> LSTM -> FC layer (1 x vocab_size) -> softmax (1 x vocab_size)

隐藏层是否必须匹配输入的维度(即 word2vec 嵌入的维度)

从技术上讲,没有。如果您查看 LSTM 方程,您会注意到 x(输入)可以是任意大小,只要适当调整权重矩阵即可。

LSTM 方程

如何/我可以引入一个预训练的 word2vec 模型,而不是那个未初始化的模型?

我不知道,对不起。

于 2017-09-11T01:12:00.910 回答
4

有很多问题,我会尝试澄清其中的一些。

给定句子的前几个单词,如何使用生成的模型实际生成下一个单词建议?

这里的重点是,下一代词实际上是词汇表中的词分类。所以你需要一个分类器,这就是为什么输出中有一个softmax。

原理是,在每个时间步,模型会根据最后一个词嵌入和前一个词的内部记忆来输出下一个词。tf.contrib.rnn.static_rnn自动组合输入到内存中,但我们需要提供最后一个词嵌入并对下一个词进行分类。

我们可以使用预训练的 word2vec 模型,只需使用预训练的矩阵初始化embedding矩阵。我认为为了简单起见,本教程使用随机矩阵。内存大小与嵌入大小无关,可以使用更大的内存大小来保留更多信息。

这些教程是高级别的。如果您想深入了解细节,我建议您查看纯 python/numpy 中的源代码。

于 2017-09-09T09:39:20.117 回答
2

您可以在答案末尾找到所有代码。


我认为您的大多数问题(为什么使用 Softmax、如何使用预训练嵌入层等)都已得到解答。但是,由于您仍在等待简洁的代码从种子生成生成的文本,因此我在这里尝试报告我自己最终是如何做到的。

我从官方的 Tensorflow 教程开始苦苦挣扎,直到我可以轻松地从生成的模型中生成单词。幸运的是,在对您在问题中提到的几乎所有答案进行了一些回答之后,我对问题(和解决方案)有了更好的了解。这可能包含错误,但至少它会运行并生成一些文本......

给定句子的前几个单词,如何使用生成的模型实际生成下一个单词建议?

我会将下一个单词建议包装在一个循环中,以生成一个完整的句子,但您很容易将其简化为一个单词。

假设您遵循了 tensorflow(撰写本文时为 v1.4)给出的当前教程这将在训练后保存模型。

然后我们要做的就是从磁盘加载它,并编写一个函数,该函数接受这个模型和一些种子输入并返回生成的文本。


从保存的模型生成文本

我假设我们在一个新的 python 脚本中编写了所有这些代码。底部的整个脚本作为回顾,在这里我解释了主要步骤。

第一个必要步骤

FLAGS = tf.flags.FLAGS
FLAGS.model = "medium" # or whatever size you used

现在,非常重要的是,我们创建字典来将 id 映射到单词,反之亦然(因此我们不必读取整数列表......)。

word_to_id = reader._build_vocab('../data/ptb.train.txt') # here we load the word -> id dictionnary ()
id_to_word = dict(zip(word_to_id.values(), word_to_id.keys())) # and transform it into id -> word dictionnary
_, _, test_data, _ = reader.ptb_raw_data('../data')

然后我们加载配置类,也将num_stepsand设置batch_size为 1,因为我们希望一次采样 1 个单词,而 LSTM 一次也将处理 1 个单词。还即时创建输入实例:

eval_config = get_config()
eval_config.num_steps = 1
eval_config.batch_size = 1
model_input = PTBInput(eval_config, test_data)

建筑图

要加载保存的模型(由Supervisor.saver教程中的模块保存),我们首先需要重建图形(使用类很容易PTBModel),它必须使用与训练时相同的配置:

sess = tf.Session()
initializer = tf.random_uniform_initializer(-eval_config.init_scale, eval_config.init_scale)
# not sure but seems to need the same name for variable scope as when saved ....!!
with tf.variable_scope("Model", reuse=None, initializer=initializer):
    tf.global_variables_initializer()
    mtest = PTBModel(is_training=False, config=eval_config, input=model_input)

恢复保存的重量:

sess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint('../Whatever_folder_you_saved_in')) # the path must point to the hierarchy where your 'checkpoint' file is

...从给定的种子中采样单词:

首先,我们需要模型包含对 logits 输出的访问,或者更准确地说是整个词汇表的概率分布。所以在ptb_lstm.py文件中添加以下行:

# the line goes somewhere below the reshaping "logits = tf.reshape(logits, [self.batch_size, ..."
self.probas = tf.nn.softmax(logits, name="probas")

然后我们可以设计一些采样函数(你可以在这里随意使用任何你喜欢的东西,最好的方法是使用趋于平坦或锐化分布的温度进行采样),这是一个基本的随机采样方法:

def sample_from_pmf(probas):
    t = np.cumsum(probas)
    s = np.sum(probas)
    return int(np.searchsorted(t, np.random.rand(1) * s))

最后是一个函数,它接受一个种子、你的模型、将单词映射到 id 的字典,反之亦然,作为输入和输出生成的文本字符串:

def generate_text(session, model, word_to_index, index_to_word, 
                  seed='</s>', n_sentences=10):
    sentence_cnt = 0
    input_seeds_id = [word_to_index[w] for w in seed.split()]
    state = session.run(model.initial_state)

    # Initiate network with seeds up to the before last word:
    for x in input_seeds_id[:-1]:
        feed_dict = {model.initial_state: state,
                     model.input.input_data: [[x]]}
        state = session.run([model.final_state], feed_dict)

    text = seed
    # Generate a new sample from previous, starting at last word in seed
    input_id = [[input_seeds_id[-1]]]
    while sentence_cnt < n_sentences:
        feed_dict = {model.input.input_data: input_id,
                     model.initial_state: state}
        probas, state = session.run([model.probas, model.final_state],
                                 feed_dict=feed_dict)
        sampled_word = sample_from_pmf(probas[0])
        if sampled_word == word_to_index['</s>']:
            text += '.\n'
            sentence_cnt += 1
        else:
            text += ' ' + index_to_word[sampled_word]
        input_wordid = [[sampled_word]]

    return text

TL;博士

不要忘记添加以下行:

self.probas = tf.nn.softmax(logits, name='probas')

ptb_lstm.py文件中,在类的__init__定义中PTBModel,行之后的任何位置logits = tf.reshape(logits, [self.batch_size, self.num_steps, vocab_size])

整个脚本,只需从您拥有的同一目录中运行它reader.pyptb_lstm.py

import reader
import numpy as np
import tensorflow as tf
from ptb_lstm import PTBModel, get_config, PTBInput

FLAGS = tf.flags.FLAGS
FLAGS.model = "medium"

def sample_from_pmf(probas):
    t = np.cumsum(probas)
    s = np.sum(probas)
    return int(np.searchsorted(t, np.random.rand(1) * s))

def generate_text(session, model, word_to_index, index_to_word, 
                  seed='</s>', n_sentences=10):
    sentence_cnt = 0
    input_seeds_id = [word_to_index[w] for w in seed.split()]
    state = session.run(model.initial_state)

    # Initiate network with seeds up to the before last word:
    for x in input_seeds_id[:-1]:
        feed_dict = {model.initial_state: state,
                     model.input.input_data: [[x]]}
        state = session.run([model.final_state], feed_dict)

    text = seed
    # Generate a new sample from previous, starting at last word in seed
    input_id = [[input_seeds_id[-1]]]
    while sentence_cnt < n_sentences:
        feed_dict = {model.input.input_data: input_id,
                     model.initial_state: state}
        probas, state = sess.run([model.probas, model.final_state],
                                 feed_dict=feed_dict)
        sampled_word = sample_from_pmf(probas[0])
        if sampled_word == word_to_index['</s>']:
            text += '.\n'
            sentence_cnt += 1
        else:
            text += ' ' + index_to_word[sampled_word]
        input_wordid = [[sampled_word]]

    print(text)

if __name__ == '__main__':

    word_to_id = reader._build_vocab('../data/ptb.train.txt') # here we load the word -> id dictionnary ()
    id_to_word = dict(zip(word_to_id.values(), word_to_id.keys())) # and transform it into id -> word dictionnary
    _, _, test_data, _ = reader.ptb_raw_data('../data')

    eval_config = get_config()
    eval_config.batch_size = 1
    eval_config.num_steps = 1
    model_input = PTBInput(eval_config, test_data, name=None)

    sess = tf.Session()
    initializer = tf.random_uniform_initializer(-eval_config.init_scale,
                                            eval_config.init_scale)
    with tf.variable_scope("Model", reuse=None, initializer=initializer):
        tf.global_variables_initializer()
        mtest = PTBModel(is_training=False, config=eval_config, 
                         input_=model_input)

    sess.run(tf.global_variables_initializer())

    saver = tf.train.Saver()
    saver.restore(sess, tf.train.latest_checkpoint('../models'))

    while True:
        print(generate_text(sess, mtest, word_to_id, id_to_word, seed="this sentence is"))
        try:
            raw_input('press Enter to continue ...\n')
        except KeyboardInterrupt:
            print('\b\bQuiting now...')
            break

更新

至于使用最近的 tensorflow(至少 1.6)恢复旧的检查点(对我来说是 6 个月前保存的模型,不确定当时使用的确切 TF 版本),它可能会引发一些关于未找到变量的错误(见评论)。在这种情况下,您应该使用此脚本更新您的检查点。

另外,请注意,对我来说,我必须进一步修改它,因为我注意到该saver.restore函数正在尝试读取lstm_cell变量,尽管我的变量被转换为basic_lstm_cell也导致NotFound Error. 因此,一个简单的解决方法是删除新名称中的checkpoint_convert.py脚本,即第 72-73 行中的一个小改动。basic_

检查检查点中包含的变量名称的一种方便方法是(CKPT_FILE是 , 之前的后缀.index.data0000-1000):

reader = tf.train.NewCheckpointReader(CKPT_FILE)
reader.get_variable_to_shape_map()

通过这种方式,您可以验证您确实拥有正确的名称(或旧检查点版本中的错误名称)。

于 2018-01-19T18:45:05.710 回答