在文章NLP入门实例推荐中提到,对话系统有两种形式:
基于检索的对话系统 模式:s1,s2–>R,即字符串s1和s2符合一定的规则。
基于生成的对话系统 模式:s1–>s2,即由字符串s1得到s2。 两者各有优势与不足。首先基于检索的对话系统,将回复使用的数据预先存储,那么优点就是回复的内容不会有语法和语义的错误,但因为不会像基于生成的对话系统那样创造出新的回答,因此也无法对未训练的问题作出回答。 本篇是聊天系统的第一篇文章,参考博客deep-learning-for-chatbots-part-1和deep-learning-for-chatbots-part-1,另外附上中文翻译。该博客是由WILDML所写,就是那个实现TextCNN的博主,因此以这篇文章入手对话系统,希望能在对话系统方向养成良好的代码风格和研究主线。 原文的github地址为tensorflow v0.9,有部分的函数已经更改。在实际的运行当中使用的TensorFlow的版本为v1.2,因此做了一些修改,并将修改后的代码上传到github地址,欢迎fork和start。按照以往的惯例,本篇文章依然分为数据处理、模型构建、模型训练、结果分析四部分。 数据处理原文中使用Ubuntu对话数据集(论文来源 github地址)。这个数据集(Ubuntu Dialog Corpus, UDC)是目前最大的公开对话数据集之一,它是来自Ubuntu的IRC网络上的对话日志。这篇论文介绍了该数据集生成的具体细节。下面简单介绍一下数据的格式。 训练数据有1,000,000条实例,其中一半是正例(label为1),一半是负例(label为0,负例为随机生成)。每条实例包括一段上下文信息(context),即Query;和一段可能的回复内容(utterance),即Response;Label为1表示该Response确实是Query的回复,Label为0则表示不是。下面是数据示例: 数据集的生成使用了NLTK工具,包括分词、stemmed、lemmatized等文本预处理步骤;同时还使用了NER技术,将文本中的实体,如姓名、地点、组织、URL等替换成特殊字符。这些文本预处理并不是必须的,但是能够提升一些模型的性能。据统计,query的平均长度为86个word,而response的平均长度为17个word,更多的数据统计信息见Jupyter notebook。 数据集也包括了测试和验证集,但这两部分的数据和训练数据在格式上不太一样。在测试集和验证集中,对于每一条实例,有一个正例和九个负例数据(也称为干扰数据)。模型的目标在于给正例的得分尽可能的高,而给负例的得分尽可能的低。下面是数据示例: 模型的评测方式有很多种。其中最常用到的是recall@k,即经模型对候选的response排序后,前k个候选中存在正例数据(正确的那个)的占比;显然k值越大,该指标会越高,因为这对模型性能的要求越松。 在Ubuntu数据集中,负例数据都是随机生成的;然而在现实中,想要从全部的数据中随机生成负例是不可能的。谷歌的Smart Reply则使用了聚类技术,然后将每个类的中取一些作为负例,这样生成负例的方式显得更加合理(考虑了负例数据的多样性,同时减少时间开销)。 数据集的原始格式为csv格式,我们需要先将其转为TensorFlow专有的格式,这种格式的好处在于能够直接从输入文件中load tensors,并让TensorFlow来处理洗牌(shuffling)、批量(batching)和队列化(queuing)等操作。预处理中还包括创建一个字典库,将词进行标号,TFRecord文件将直接存储这些词的标号。 每个实例包括如下几个字段: Query:表示为一串词标号的序列,如[231, 2190, 737, 0, 912];
Query的长度;
Response:同样是一串词标号的序列;
Response的长度;
Label;
Distractor_[N]:表示负例干扰数据,仅在验证集和测试集中有,N的取值为0-8;
Distractor_[N]的长度;
数据预处理的Python脚本见这里,生成了3个文件:train.tfrecords, validation.tfrecords 和 test.tfrecords。你可以尝试自己运行程序,或者直接下载和使用预处理后的数据。 为了使用TensoFlow内置的训练和评测模块,我们需要创建一个输入函数:这个函数返回输入数据的batch。因为训练数据和测试数据的格式不同,我们需要创建不同的输入函数。输入函数需要返回批量(batch)的特征和标签值(如果有的话)。类似于如下: def input_fn(): # TODO Load and preprocess data here return batched_features, labels因为我们需要在模型训练和评测过程中使用不同的输入函数,为了防止重复书写代码,我们创建一个包装器(wrapper),名称为create_input_fn,针对不同的mode使用相应的code,如下: def create_input_fn(mode, input_files, batch_size, num_epochs=None): def input_fn(): # TODO Load and preprocess data here return batched_features, labels return input_fn完整的code见udc_inputs.py。整体上,这个函数做了如下的事情: (1) 定义了示例文件中的feature字段;
(2) 使用tf.TFRecordReader来读取input_files中的数据;
(3) 根据feature字段的定义对数据进行解析;
(4) 提取训练数据的标签;
(5) 产生批量化的训练数据;
(6) 返回批量的特征数据及对应标签; 模型构建这篇博文将建立的NN模型为两层Encoder的LSTM模型(Dual Encoder LSTM Network),这种形式的网络被广泛应用在chatbot中(尽管可能效果并不是最佳的那个,你可以尽可能地尝试其他的NN模型)。seq2seq模型常用于机器翻译领域,并取得了较大的效果。使用Dual LSTM模型的原因在于这个模型被证明在这个数据集有较好的效果(详情见这里),这可以作为我们后续模型效果的验证。
|