专栏首页AI研习社深度学习对话系统实战篇 -- 简单 chatbot 代码实现

深度学习对话系统实战篇 -- 简单 chatbot 代码实现

本文的代码都可以到我的 github 中下载:https://github.com/lc222/seq2seq_chatbot 前面几篇文章我们已经介绍了 seq2seq 模型的理论知识,并且从 tensorflow 源码层面解析了其实现原理,本篇文章我们会聚焦于如何调用 tf 提供的 seq2seq 的 API,实现一个简单的 chatbot 对话系统。这里先给出几个参考的博客和代码:

  1. tensorflow 官网 API 指导(http://t.cn/R8MiZcR )
  2. Chatbots with Seq2Seq Learn to build a chatbot using TensorFlow(http://t.cn/R8MiykP )
  3. DeepQA(http://t.cn/R8MiVld )
  4. Neural_Conversation_Models(http://t.cn/RtthjXn )

经过一番调查发现网上现在大部分 chatbot 的代码都是基于 1.0 版本之前的 tf 实现的,而且都是从 tf 官方指导文档 nmt 上进行迁移和改进,所以基本上大同小异,但是在实际使用过程中会发现一个问题,由于 tf 版本之间的兼容问题导致这些代码在新版本的 tf 中无法正常运行,常见的几个问题主要是:

  • seq2seq API 从 tf.nn 迁移到了 tf.contrib.legacy_seq2seq;
  • rnn 目前也大都使用 tf.contrib.rnn 下面的 RNNCell;
  • embedding_attention_seq2seq 函数中调用 deepcopy(cell) 这个函数经常会爆出(TypeError: can't pickle _thread.lock objects)的错误

关于上面第三个错误这里多说几句,因为确实困扰了我很久,基本上我在网上找到的每一份代码都会有这个错(DeepQA 除外)。首先来讲一种最简单的方法是将 tf 版本换成 1.0.0,这样问题就解决了。

然后说下不想改 tf 版本的办法,我在网上找了很久,自己也尝试着去找 bug 所在,错误定位在 embedding_attention_seq2seq 函数中调用 deepcopy 函数,于是就有人尝试着把 deepcopy 改成 copy,或者干脆不进行 copy 直接让 encoder 和 decoder 使用相同参数的 RNNcell,但这明显是不正确的做法。我先想出了一种解决方案就是将 embedding_attention_seq2seq 的传入参数中的 cell 改成两个,分别是 encoder_cell 和 decoder_cell,然后这两个 cell 分别使用下面代码进行初始化:

encoCell = tf.contrib.rnn.MultiRNNCell([create_rnn_cell() for _ in range(num_layers)],)
   decoCell = tf.contrib.rnn.MultiRNNCell([create_rnn_cell() for _ in range(num_layers)],)

这样做不需要调用 deepcopy 函数对 cell 进行复制了,问题也就解决了,但是在模型构建的时候速度会比较慢,我猜测是因为需要构造两份 RNN 模型,但是最后训练的时候发现速度也很慢,就先放弃了这种做法。

然后我又分析了一下代码,发现问题并不是单纯的出现在 embedding_attention_seq2seq 这个函数,而是在调用 module_with_buckets 的时候会构建很多个不同 bucket 的 seq2seq 模型,这就导致了 embedding_attention_seq2seq 会被重复调用很多次,后来经过测试发现确实是这里出现的问题,因为即便不使用 model_with_buckets 函数,我们自己为每个 bucket 构建模型时同样也会报错,但是如果只有一个 bucket 也就是只调用一次 embedding_attention_seq2seq 函数时就不会报错,其具体的内部原理我现在还没有搞清楚,就看两个最简单的例子:

  import tensorflow as tf
   import copy

   cell = tf.contrib.rnn.BasicLSTMCell(10)
   cell1 = copy.deepcopy(cell)#这句代码不会报错,可以正常执行

   a = tf.constant([1,2,3,4,5])
   b = copy.deepcopy(a)#这句代码会报错,就是can't pickle _thread.lock objects。

可以理解为a已经有值了,而且是tf内部类型,导致运行时出错???
还是不太理解tf内部运行机制,为什么cell没有线程锁,但是a有呢==

所以先忽视原因,只看解决方案的话就是,不适用 buckets 构建模型,而是简单的将所有序列都 padding 到统一长度,然后直接调用一次 embedding_attention_seq2seq 函数构建模型即可,这样是不会抱错的。(希望看到这的同学如果对这里比较理解可以指点一二,或者互相探讨一下)

最后我也是采用的这种方案,综合了别人的代码实现了一个 embedding+attention+beam_search 等多种功能的 seq2seq 模型,训练一个基础版本的 chatbot 对话机器人,tf 的版本是 1.4。写这份代码的目的一方面是为了让自己对 tf 的 API 接口的使用方法更熟悉,另一方面是因为网上的一些代码都很繁杂,想 DeepQA 这种,里面会有很多个文件还实现了前端,然后各种封装,显得很复杂,不适合新手入门,所以就想写一个跟 textcnn 相似风格的代码,只包含四个文件,代码读起来也比较友好。接下来就让我们看一下具体的代码实现吧。最终的代码我会放在 github 上

数据处理

这里我们借用 [DeepQA](https://github.com/Conchylicultor/DeepQA#chatbot) 里面数据处理部分的代码,省去从原始本文文件构造对话的过程直接使用其生成的 dataset-cornell-length10-filter1-vocabSize40000.pkl 文件。有了该文件之后数据处理的代码就精简了很多,主要包括:

1. 读取数据的函数 loadDataset()

2. 根据数据创建 batches 的函数 getBatches() 和 createBatch()

3. 预测时将用户输入的句子转化成 batch 的函数 sentence2enco()

具体的代码含义在注释中都有详细的介绍,这里就不赘述了,见下面的代码:

padToken, goToken, eosToken, unknownToken = 0, 1, 2, 3

   class Batch:
       #batch类,里面包含了encoder输入,decoder输入,decoder标签,decoder样本长度mask
       def __init__(self):
           self.encoderSeqs = []
           self.decoderSeqs = []
           self.targetSeqs = []
           self.weights = []

   def loadDataset(filename):
       '''       读取样本数据       :param filename: 文件路径,是一个字典,包含word2id、id2word分别是单词与索引对应的字典和反序字典,                       trainingSamples样本数据,每一条都是QA对       :return: word2id, id2word, trainingSamples       '''
       dataset_path = os.path.join(filename)
       print('Loading dataset from {}'.format(dataset_path))
       with open(dataset_path, 'rb') as handle:
           data = pickle.load(handle)  # Warning: If adding something here, also modifying saveDataset
           word2id = data['word2id']
           id2word = data['id2word']
           trainingSamples = data['trainingSamples']
       return word2id, id2word, trainingSamples

   def createBatch(samples, en_de_seq_len):
       '''       根据给出的samples(就是一个batch的数据),进行padding并构造成placeholder所需要的数据形式       :param samples: 一个batch的样本数据,列表,每个元素都是[question, answer]的形式,id       :param en_de_seq_len: 列表,第一个元素表示source端序列的最大长度,第二个元素表示target端序列的最大长度       :return: 处理完之后可以直接传入feed_dict的数据格式       '''
       batch = Batch()
       #根据样本长度获得batch size大小
       batchSize = len(samples)
       #将每条数据的问题和答案分开传入到相应的变量中
       for i in range(batchSize):
           sample = samples[i]
           batch.encoderSeqs.append(list(reversed(sample[0])))  # 将输入反序,可提高模型效果
           batch.decoderSeqs.append([goToken] + sample[1] + [eosToken])  # Add the <go> and <eos> tokens
           batch.targetSeqs.append(batch.decoderSeqs[-1][1:])  # Same as decoder, but shifted to the left (ignore the <go>)
           # 将每个元素PAD到指定长度,并构造weights序列长度mask标志
           batch.encoderSeqs[i] = [padToken] * (en_de_seq_len[0] - len(batch.encoderSeqs[i])) + batch.encoderSeqs[i]
           batch.weights.append([1.0] * len(batch.targetSeqs[i]) + [0.0] * (en_de_seq_len[1] - len(batch.targetSeqs[i])))
           batch.decoderSeqs[i] = batch.decoderSeqs[i] + [padToken] * (en_de_seq_len[1] - len(batch.decoderSeqs[i]))
           batch.targetSeqs[i] = batch.targetSeqs[i] + [padToken] * (en_de_seq_len[1] - len(batch.targetSeqs[i]))

       #--------------------接下来就是将数据进行reshape操作,变成序列长度*batch_size格式的数据------------------------
       encoderSeqsT = []  # Corrected orientation
       for i in range(en_de_seq_len[0]):
           encoderSeqT = []
           for j in range(batchSize):
               encoderSeqT.append(batch.encoderSeqs[j][i])
           encoderSeqsT.append(encoderSeqT)
       batch.encoderSeqs = encoderSeqsT

       decoderSeqsT = []
       targetSeqsT = []
       weightsT = []
       for i in range(en_de_seq_len[1]):
           decoderSeqT = []
           targetSeqT = []
           weightT = []
           for j in range(batchSize):
               decoderSeqT.append(batch.decoderSeqs[j][i])
               targetSeqT.append(batch.targetSeqs[j][i])
               weightT.append(batch.weights[j][i])
           decoderSeqsT.append(decoderSeqT)
           targetSeqsT.append(targetSeqT)
           weightsT.append(weightT)
       batch.decoderSeqs = decoderSeqsT
       batch.targetSeqs = targetSeqsT
       batch.weights = weightsT

       return batch

   def getBatches(data, batch_size, en_de_seq_len):
       '''       根据读取出来的所有数据和batch_size将原始数据分成不同的小batch。对每个batch索引的样本调用createBatch函数进行处理       :param data: loadDataset函数读取之后的trainingSamples,就是QA对的列表       :param batch_size: batch大小       :param en_de_seq_len: 列表,第一个元素表示source端序列的最大长度,第二个元素表示target端序列的最大长度       :return: 列表,每个元素都是一个batch的样本数据,可直接传入feed_dict进行训练       '''
       #每个epoch之前都要进行样本的shuffle
       random.shuffle(data)
       batches = []
       data_len = len(data)
       def genNextSamples():
           for i in range(0, data_len, batch_size):
               yield data[i:min(i + batch_size, data_len)]

       for samples in genNextSamples():
           batch = createBatch(samples, en_de_seq_len)
           batches.append(batch)
       return batches

   def sentence2enco(sentence, word2id, en_de_seq_len):
       '''       测试的时候将用户输入的句子转化为可以直接feed进模型的数据,现将句子转化成id,然后调用createBatch处理       :param sentence: 用户输入的句子       :param word2id: 单词与id之间的对应关系字典       :param en_de_seq_len: 列表,第一个元素表示source端序列的最大长度,第二个元素表示target端序列的最大长度       :return: 处理之后的数据,可直接feed进模型进行预测       '''
       if sentence == '':
           return None
       #分词
       tokens = nltk.word_tokenize(sentence)
       if len(tokens) > en_de_seq_len[0]:
           return None
       #将每个单词转化为id
       wordIds = []
       for token in tokens:
           wordIds.append(word2id.get(token, unknownToken))
       #调用createBatch构造batch
       batch = createBatch([[wordIds, []]], en_de_seq_len)
       return batch

模型构建

有了数据之后看一下模型构建的代码,其实主体代码还是跟前面说到的 tf 官方指导文档差不多,主要分为以下几个功能模块:

1. 一些变量的传入和定义

2. OutputProjection 层和 sampled_softmax_loss 函数的定义

3. RNNCell 的定义和创建

4. 根据训练或者测试调用相应的 embedding_attention_seq2seq 函数构建模型

5. step 函数定义,主要用于给定一个 batch 的数据,构造相应的 feed_dict 和 run_opt

代码如下所示:

import tensorflow as tf
   from my_seq2seq_chatbot.seq2seq import embedding_attention_seq2seq
   class Seq2SeqModel():

       def __init__(self, source_vocab_size, target_vocab_size, en_de_seq_len, hidden_size, num_layers,
                    batch_size, learning_rate, num_samples=1024,
                    forward_only=False, beam_search=True, beam_size=10):
           '''           初始化并创建模型           :param source_vocab_size:encoder输入的vocab size           :param target_vocab_size: decoder输入的vocab size,这里跟上面一样           :param en_de_seq_len: 源和目的序列最大长度           :param hidden_size: RNN模型的隐藏层单元个数           :param num_layers: RNN堆叠的层数           :param batch_size: batch大小           :param learning_rate: 学习率           :param num_samples: 计算loss时做sampled softmax时的采样数           :param forward_only: 预测时指定为真           :param beam_search: 预测时是采用greedy search还是beam search           :param beam_size: beam search的大小           '''
           self.source_vocab_size = source_vocab_size
           self.target_vocab_size = target_vocab_size
           self.en_de_seq_len = en_de_seq_len
           self.hidden_size = hidden_size
           self.num_layers = num_layers
           self.batch_size = batch_size
           self.learning_rate = tf.Variable(float(learning_rate), trainable=False)
           self.num_samples = num_samples
           self.forward_only = forward_only
           self.beam_search = beam_search
           self.beam_size = beam_size
           self.global_step = tf.Variable(0, trainable=False)

           output_projection = None
           softmax_loss_function = None
           # 定义采样loss函数,传入后面的sequence_loss_by_example函数
           if num_samples > 0 and num_samples < self.target_vocab_size:
               w = tf.get_variable('proj_w', [hidden_size, self.target_vocab_size])
               w_t = tf.transpose(w)
               b = tf.get_variable('proj_b', [self.target_vocab_size])
               output_projection = (w, b)
               #调用sampled_softmax_loss函数计算sample loss,这样可以节省计算时间
               def sample_loss(logits, labels):
                   labels = tf.reshape(labels, [-1, 1])
                   return tf.nn.sampled_softmax_loss(w_t, b, labels=labels, inputs=logits, num_sampled=num_samples, num_classes=self.target_vocab_size)
               softmax_loss_function = sample_loss

           self.keep_drop = tf.placeholder(tf.float32)
           # 定义encoder和decoder阶段的多层dropout RNNCell
           def create_rnn_cell():
               encoDecoCell = tf.contrib.rnn.BasicLSTMCell(hidden_size)
               encoDecoCell = tf.contrib.rnn.DropoutWrapper(encoDecoCell, input_keep_prob=1.0, output_keep_prob=self.keep_drop)
               return encoDecoCell
           encoCell = tf.contrib.rnn.MultiRNNCell([create_rnn_cell() for _ in range(num_layers)])

           # 定义输入的placeholder,采用了列表的形式
           self.encoder_inputs = []
           self.decoder_inputs = []
           self.decoder_targets = []
           self.target_weights = []
           for i in range(en_de_seq_len[0]):
               self.encoder_inputs.append(tf.placeholder(tf.int32, shape=[None, ], name="encoder{0}".format(i)))
           for i in range(en_de_seq_len[1]):
               self.decoder_inputs.append(tf.placeholder(tf.int32, shape=[None, ], name="decoder{0}".format(i)))
               self.decoder_targets.append(tf.placeholder(tf.int32, shape=[None, ], name="target{0}".format(i)))
               self.target_weights.append(tf.placeholder(tf.float32, shape=[None, ], name="weight{0}".format(i)))

           # test模式,将上一时刻输出当做下一时刻输入传入
           if forward_only:
               if beam_search:#如果是beam_search的话,则调用自己写的embedding_attention_seq2seq函数,而不是legacy_seq2seq下面的
                   self.beam_outputs, _, self.beam_path, self.beam_symbol = embedding_attention_seq2seq(
                       self.encoder_inputs, self.decoder_inputs, encoCell, num_encoder_symbols=source_vocab_size,
                       num_decoder_symbols=target_vocab_size, embedding_size=hidden_size,
                       output_projection=output_projection, feed_previous=True)
               else:
                   decoder_outputs, _ = tf.contrib.legacy_seq2seq.embedding_attention_seq2seq(
                       self.encoder_inputs, self.decoder_inputs, encoCell, num_encoder_symbols=source_vocab_size,
                       num_decoder_symbols=target_vocab_size, embedding_size=hidden_size,
                       output_projection=output_projection, feed_previous=True)
                   # 因为seq2seq模型中未指定output_projection,所以需要在输出之后自己进行output_projection
                   if output_projection is not None:
                       self.outputs = tf.matmul(decoder_outputs, output_projection[0]) + output_projection[1]
           else:
               # 因为不需要将output作为下一时刻的输入,所以不用output_projection
               decoder_outputs, _ = tf.contrib.legacy_seq2seq.embedding_attention_seq2seq(
                   self.encoder_inputs, self.decoder_inputs, encoCell, num_encoder_symbols=source_vocab_size,
                   num_decoder_symbols=target_vocab_size, embedding_size=hidden_size, output_projection=output_projection,
                   feed_previous=False)
               self.loss = tf.contrib.legacy_seq2seq.sequence_loss(
                   decoder_outputs, self.decoder_targets, self.target_weights, softmax_loss_function=softmax_loss_function)

               # Initialize the optimizer
               opt = tf.train.AdamOptimizer(learning_rate=self.learning_rate, beta1=0.9, beta2=0.999, epsilon=1e-08)
               self.optOp = opt.minimize(self.loss)
               self.saver = tf.train.Saver(tf.all_variables())

       def step(self, session, encoder_inputs, decoder_inputs, decoder_targets, target_weights, go_token_id):
           #传入一个batch的数据,并训练性对应的模型
           # 构建sess.run时的feed_inpits
           feed_dict = {}
           if not self.forward_only:
               feed_dict[self.keep_drop] = 0.5
               for i in range(self.en_de_seq_len[0]):
                   feed_dict[self.encoder_inputs[i].name] = encoder_inputs[i]
               for i in range(self.en_de_seq_len[1]):
                   feed_dict[self.decoder_inputs[i].name] = decoder_inputs[i]
                   feed_dict[self.decoder_targets[i].name] = decoder_targets[i]
                   feed_dict[self.target_weights[i].name] = target_weights[i]
               run_ops = [self.optOp, self.loss]
           else:
               feed_dict[self.keep_drop] = 1.0
               for i in range(self.en_de_seq_len[0]):
                   feed_dict[self.encoder_inputs[i].name] = encoder_inputs[i]
               feed_dict[self.decoder_inputs[0].name] = [go_token_id]
               if self.beam_search:
                   run_ops = [self.beam_path, self.beam_symbol]
               else:
                   run_ops = [self.outputs]

           outputs = session.run(run_ops, feed_dict)
           if not self.forward_only:
               return None, outputs[1]
           else:
               if self.beam_search:
                   return outputs[0], outputs[1]

接下来我们主要说一下我做的主要工作,就是 beam_search 这部分,其原理想必大家看过前面的文章应该已经很清楚了,那么如何编程实现呢,首先我们要考虑的是在哪里进行 beam search,因为 beam search 是在预测时需要用到,代替 greedy 的一种搜索策略,所以第一种方案是在 tf 之外,用 python 实现,这样做的缺点是 decode 速度会很慢。第二种方案是在 tf 内模型构建时进行,这样做的好处是速度快但是比较麻烦。

在网上找了很久在 tensorflow 的一个 issue(http://t.cn/R8M6mDo ) 里面发现了一个方案,他的思路是修改 loop_function 函数,也就是之前根据上一时刻输出得到下一时刻输入的函数,在 loop function 里面实现 top_k 取出概率最大的几个序列,并把相应的路径和单词对应关系保存下来。但是存在一个问题就是一开始 decode 的时候传入的是一句话,也就是 batch_size 为 1,但是经过 loop_function 之后返回的是 beam_size 句话,但是再将其传入 RNNCell 的时候就会报错,如何解决这个问题呢,想了很久决定直接从 decode 开始的时候就把输入扩展为 beam_size 个,把 encoder 阶段的输出和 attention 向量都变成 beam_size 维的 tensor,就说把 decoder 阶段的 RNN 输入的 batch_size 当做为 beam_size。

但是这样做仍然会出现一个问题,就是你会发现最后的输出全部都相同,原因就在于 decoder 开始的时候样本是 beam_szie 个完全相同的输入,所以经过 loop_function 得到的 beam_size 个最大序列也是完全相同的,为了解决这个问题我们需要在第一次编码的时候不取整体最大的前 beam_size 个序列,而是取第一个元素编码结果的前 beam_size 个值作为结果。这部分代码比较多就只贴出来 loop_function 的函数,有兴趣的同学可以去看我 github 上面的代码,就在 seq2seq 文件中。

def loop_function(prev, i, log_beam_probs, beam_path, beam_symbols):
       if output_projection is not None:
           prev = nn_ops.xw_plus_b(prev, output_projection[0], output_projection[1])
       # 对输出概率进行归一化和取log,这样序列概率相乘就可以变成概率相加
       probs = tf.log(tf.nn.softmax(prev))
       if i == 1:
           probs = tf.reshape(probs[0, :], [-1, num_symbols])
       if i > 1:
           # 将当前序列的概率与之前序列概率相加得到结果之前有beam_szie个序列,本次产生num_symbols个结果,
           # 所以reshape成这样的tensor
           probs = tf.reshape(probs + log_beam_probs[-1], [-1, beam_size * num_symbols])
       # 选出概率最大的前beam_size个序列,从beam_size * num_symbols个元素中选出beam_size个
       best_probs, indices = tf.nn.top_k(probs, beam_size)
       indices = tf.stop_gradient(tf.squeeze(tf.reshape(indices, [-1, 1])))
       best_probs = tf.stop_gradient(tf.reshape(best_probs, [-1, 1]))

       # beam_size * num_symbols,看对应的是哪个序列和单词
       symbols = indices % num_symbols  # Which word in vocabulary.
       beam_parent = indices // num_symbols  # Which hypothesis it came from.
       beam_symbols.append(symbols)
       beam_path.append(beam_parent)
       log_beam_probs.append(best_probs)

       # 对beam-search选出的beam size个单词进行embedding,得到相应的词向量
       emb_prev = embedding_ops.embedding_lookup(embedding, symbols)
       emb_prev = tf.reshape(emb_prev, [-1, embedding_size])
       return emb_prev

模型训练

其实模型训练部分的代码很简单,就是每个 epoch 都对样本进行 shuffle 然后分 batches,接下来将每个 batch 的数据分别传入 model.step() 进行模型的训练,这里比较好的一点是,DeepQA 用的是 embedding_rnn_seq2seq 函数,训练过程中 loss 经过 30 个人 epoch 大概可以降到 3 点多,但是我这里改成了 embedding_attention_seq2seq 函数,最后 loss 可以降到 2.0 以下,可以说效果还是很显著的,而且模型的训练速度并没有降低,仍然是 20 个小时左右就可以完成训练。

for e in range(FLAGS.numEpochs):
       print("----- Epoch {}/{} -----".format(e + 1, FLAGS.numEpochs))
       batches = getBatches(trainingSamples, FLAGS.batch_size, model.en_de_seq_len)
       for nextBatch in tqdm(batches, desc="Training"):
           _, step_loss = model.step(sess, nextBatch.encoderSeqs, nextBatch.decoderSeqs, nextBatch.targetSeqs,
                                     nextBatch.weights, goToken)
           current_step += 1
           if current_step % FLAGS.steps_per_checkpoint == 0:
               perplexity = math.exp(float(step_loss)) if step_loss < 300 else float('inf')
               tqdm.write("----- Step %d -- Loss %.2f -- Perplexity %.2f" % (current_step, step_loss, perplexity))
               checkpoint_path = os.path.join(FLAGS.train_dir, "chat_bot.ckpt")
               model.saver.save(sess, checkpoint_path, global_step=model.global_step)

贴上两张图看一下训练的效果,这里用的是 deepQA 的截图,因为我的代码训练的时候忘了加 tensorboard 的东西:

模型预测

预测好模型之后,接下来需要做的就是对模型效果进行测试,这里也比较简单,主要是如何根据 beam_search 都所处的结果找到对应的句子进行输出。代码如下所示:

      if beam_search:
           sys.stdout.write("> ")
           sys.stdout.flush()
           sentence = sys.stdin.readline()
           while sentence:
               #将用户输入的句子转化为id并处理成feed_dict的格式
               batch = sentence2enco(sentence, word2id, model.en_de_seq_len)
               beam_path, beam_symbol = model.step(sess, batch.encoderSeqs, batch.decoderSeqs, batch.targetSeqs,
                                                   batch.weights, goToken)
               paths = [[] for _ in range(beam_size)]
               curr = [i for i in range(beam_size)]
               num_steps = len(beam_path)
               #根据beam_path和beam_symbol得到真正的输出语句,存在paths中
               for i in range(num_steps-1, -1, -1):
                   for kk in range(beam_size):
                       paths[kk].append(beam_symbol[i][curr[kk]])
                       curr[kk] = beam_path[i][curr[kk]]
               recos = set()
               print("Replies --------------------------------------->")
               #转换成句子并输出
               for kk in range(beam_size):
                   foutputs = [int(logit) for logit in paths[kk][::-1]]
                   if eosToken in foutputs:
                       foutputs = foutputs[:foutputs.index(eosToken)]
                   rec = " ".join([tf.compat.as_str(id2word[output]) for output in foutputs if output in id2word])
                   if rec not in recos:
                       recos.add(rec)
                       print(rec)
               print("> ", "")
               sys.stdout.flush()
               sentence = sys.stdin.readline()

接下来我们看一下几个例子,这里 beam_size=5,并去掉了一些重复的回答:

本文分享自微信公众号 - AI研习社(okweiwu)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-02-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Tensorflow实践:用神经网络训练分类器

    任务: 使用tensorflow训练一个神经网络作为分类器,分类的数据点如下: ? 螺旋形数据点 原理 数据点一共有三个类别,而且是螺旋形交织在一起,显然是线性...

    用户1332428
  • TensorFlow从0到1丨第2篇:TensorFlow核心编程

    上一篇Hello, TensorFlow!中的代码还未解释,本篇介绍TensorFlow核心编程的几个基本概念后,那些Python代码就很容易理解了。 与Ten...

    用户1332428
  • 谷歌开放人工智能语言处理技术

    据投资资讯网VentureBeat (http://venturebeat.com/)报道,谷歌从2016年5月12日起开放SyntaxNet的源代码,作为谷歌...

    人工智能快报
  • TensorFlow从0到1丨开篇:Hello TensorFlow !

    我以官方文档为主线,开始对TensorFlow的学习。这期间会把我的理解进行持续的输出,作为《TensorFlow从0到1》系列。它不会止于翻译和笔记、语言和工...

    用户1332428
  • TensorFlow从0到1 | 第十二章:TensorFlow构建3层NN玩转MNIST

    上一篇 11 74行Python实现手写体数字识别展示了74行Python代码完成MNIST手写体数字识别,识别率轻松达到95%。这算不上一个好成绩,不过我并不...

    用户1332428
  • TensorFlow从0到1 | 第十一章 74行Python实现手写体数字识别

    到目前为止,我们已经研究了梯度下降算法、人工神经网络以及反向传播算法,他们各自肩负重任: 梯度下降算法:机器自学习的算法框架; 人工神经网络:“万能函数”的形式...

    用户1332428
  • TensorFlow中的多线程

    TensorFlow提供两个类帮助实现多线程,一个是tf.train.Coordinator,另一个是tf.train.QueueRunner。Coordina...

    用户1332428
  • TensorFlow从0到1 | 第八篇:万能函数的形态:人工神经网络

    之前花了不小的篇幅来解释线性回归,尽管线性模型本身十分简单,但是确定模型参数的过程,却是一种数据驱动的、自学习的通用方式。准确的说,这个过程,是基于数据的、运用...

    用户1332428
  • 黑猿大叔-译文 | TensorFlow实现Batch Normalization

    原文:Implementing Batch Normalization in Tensorflow(https://r2rt.com/implementing-...

    用户1332428
  • TensorFlow从0到1丨第3篇:人类学习的启示

    上一篇TensorFlow的内核基础介绍了TF Core中的基本构造块,在介绍其强大的API之前,我们需要先明了TF所要解决的核心问题:机器学习。 什么是机器学...

    用户1332428

扫码关注云+社区

领取腾讯云代金券