首页 文章

使用LSTM教程代码来预测句子中的下一个单词?

提问于
浏览
17

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

(使用tensorflow 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的 KeyedVectors.load_word2vec_format() ),在每个句子中加载时将输入语料库中的每个单词转换为该表示,然后LSTM将吐出一个向量相同的维度,我们会尝试找到最相似的单词(例如使用gensim的 similar_by_vector(y, topn=1) ) .

使用softmax从相对较慢的 similar_by_vector(y, topn=1) 电话中拯救我们吗?


顺便说一下,对于已存在的word2vec,我的问题部分Using pre-trained word2vec with LSTM for word generation是类似的 . 然而,目前那里的答案并不是我所希望的,这是一个简单的英语解释,可以为我打开灯光,并插入我理解中的任何差距 . Use pre-trained word2vec in lstm language model?是另一个类似的问题 .

UPDATE: Predicting 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?

ANOTHER UPDATE: 另一个问题基本上是一样的问题:Predicting Next Word of LSTM Model from Tensorflow Example它链接到Predicting next word using the language model tensorflow example(再次,答案不是我正在寻找的东西) .

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

一个例子(带有字符RNN,并使用mxnet)是 sample() 末尾附近显示的 sample() 函数 . 您可以在训练期间调用 sample() ,但您也可以在训练后调用它,并使用您想要的任何句子 .

4 回答

  • 4

    主要问题

    加载单词

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

    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 应包含单词ids(打印出 word_to_id 表示映射) . 例如,它应该看起来像:[1,52,562,246] ......

    显示预测

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

    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),尽管困惑是 Headers 中的预测 .

    子问题

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

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

    为什么要使用softmax?

    最终预测是由与隐藏层输出的余弦相似性确定的 . 在LSTM之后有一个FC层,它将嵌入状态转换为最终字的单热编码 .

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

    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 equations

    我/我怎么样?带来一个预先训练过的word2vec模型,而不是那个未初始化的模型?

    我不知道,对不起 .

  • 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
    

    PTBModel__init__ 函数中,您需要添加以下行:

    self.logits = logits
    

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

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

    为什么要使用softmax?

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

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

    不,原则上它可以是任何 Value . 但是,使用尺寸低于嵌入维度的隐藏状态并没有多大意义 .

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

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

    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)
    
  • 2

    有很多问题,我会尝试澄清一些问题 .

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

    这里的关键点是,下一个单词生成实际上是词汇表中的单词分类 . 所以你需要一个分类器,这就是输出中有softmax的原因 .

    原理是,在每个时间步,模型将基于最后一个字嵌入和前一个字的内部存储器输出下一个字 . tf.contrib.rnn.static_rnn 自动将输入组合到内存中,但我们需要提供最后一个字嵌入并对下一个字进行分类 .

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

    这些教程是高级的 . 如果你想深入了解细节,我建议你看看普通的python / numpy中的源代码 .

  • 6

    您可以在答案的最后找到所有代码 .


    我估计你的大部分问题(为何Softmax,如何使用预训练嵌入层等等)都得到了解答 . 不过就像你一样还在等待一个简洁的代码从种子生成生成的文本,在这里我试着报告我最终是如何自己做的 .

    从官方的Tensorflow教程开始,我努力工作,以便能够轻松地从 生产环境 的模型中生成单词 . 幸运的是,在您在问题中提到的几乎所有答案中得到一些答案之后,我对问题(和解决方案)有了更好的了解 . 这可能包含错误,但至少它会运行并生成一些文本......

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

    我将循环中包含下一个单词建议,以生成一个完整的句子,但您只需将其简化为一个单词 .

    假设你按照tensorflow给出的当前教程(写作时v1.4)here,这将在训练后保存模型 .

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


    从已保存的模型生成文本

    我假设我们在一个新的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_stepsbatch_size 设置为1,因为我们想要 sample 1 word at a time ,而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 模块保存),我们首先需要 rebuild the graph (使用 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))
    

    最后是一个函数,它接受一个种子,你的模型,将word映射到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; DR

    别忘了添加这行:

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

    ptb_lstm.py 文件中,在 PTBModel 类的 __init__ 定义中,在 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
    

    更新

    至于恢复旧的检查点(对我来说,6个月前保存的模型,不确定当时使用的确切TF版本)和最近的张量流(至少1.6),它可能会引发一些关于未找到的变量的错误(请参阅注释) . 在这种情况下,您应该使用this script更新检查点 .

    另外,请注意,对于我来说,我必须进一步修改它,因为我注意到 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()
    

    这样,您可以验证您确实具有正确的名称(或旧检查点版本中的错误名称) .

相关问题