1. 背景

语言模型在自然语言处理具有重要的地位,它是一种基于概率统计的模型,主要的目标是描述字/词在句子中的概率。同时,语言模型是从大量的语料信息中训练出的模型,它“学习”到的词的概率是通用语义的信息,可以和很多实际应用相结合,尤其是在一些基于统计模型的领域,如语音识别,分词,情感分析以及机器翻译等领域,有着广泛的应用。近些年随着各种新的算法的出现以及模型结构的改进,语言模型的性能也在逐渐进步,在很多应用场景,尤其是语音识别和机器翻译等领域,取得了很好的效果。

在58集团这样的分类网站的业务背景下,语言模型也有很多的实际应用的场景。例如,58的租房,二手房等业务,每天都有大量的发帖,用户在搜索这些帖子后的展示顺序是非常重要的,我们希望将最优质和最适合的帖子展示在网页的最前面,以提高服务的质量。语言模型则可以根据这些帖子的内容,评价帖子的文本质量,并作为帖子的一个质量因子,参与帖子的排序。在这篇文章中,我们也将主要讲解语言模型的构建,及其在58相关业务的帖子文本质量评价上的应用。

2. 语言模型简介

语言模型是用来表示某一种语言的概率模型,其目的是描述给定词序列的概率分布,也就是计算一个句子/段落/文档的概率,或者说,就是判断一句话符合自然语言的表达习惯的可能性。经过近二三十年的发展,主流语言模型的实现方法也经历了从n-gram、RNN到LSTM、GRU等方法过程,如图1所示。

图1 语言模型的发展历程

一段文字是不是自然语言,可以通过这段文字的概率分布进行判断。当然我们无法确切的给出“是”或者“否”的答案,但是我们可以通过这段文字的概率分布,来确定其是自然语言的“可能性”的大小。比如有一段文字,由w1, w2, w3, …., wm这m个词构成,一般我们可以通过以下方式来计算其联合概率:

也就是当前的词出现的概率,是和这个词之前的所有词都是相关的。但基于马尔可夫假设,可以简化语言模型的计算,即wi出现的概率只和wi之前的n-1个词有关,这就是n-gram语言模型。如果词表大小为_|V| , 那么对于n-gram来讲,就需要一个 |V|n_规模的矩阵来存储,随着n的增大,需要的内存也会指数级增长。一般而言,n等于5,就已经会占用很大的存储空间了。

为了解决n-gram的局限性,即n-gram模型的参数随着n呈指数级增长,Bengio等人在2003年提出了使用循环神经网络(Recurrent NeuralNetwork, RNN)来实现语言模型。循环神经网络理论上可以“向前看”任意多个词,而所需的存储空间并不会指数级增长,而是和词表的大小规模相关。

即便RNN理论上RNN可以“记忆”某个词之前很长的内容,但是训练起来是比较困难的,往往伴随着梯度爆炸和梯度消失的问题, 通常梯度爆炸可以通过Gradient Clipping的方式解决,而解决RNN的梯度消失问题,可以通过替换激活函数(比如将激活函数从sigmoid换为relu)来解决,但更好的方式是使用LSTM等改进版的循环神经网络。LSTM早在1997年就由Schmidhuber等人提出,但直到2007年之后 ,LSTM及其一些改进的模型,才开始在各个领域逐渐超越传统模型。近些年随着各种改进版的LSTM结构出现,如GRU,BiLSTM等,语言模型的性能也在不断改进。因此在实际的应用中,我们也采用了LSTM的结构来实现语言模型。

3. 语言模型训练和部署

完成一个语言模型,一般需要三个步骤,分别是数据的准备,训练调参,以及导出模型,如果需要线上预测,还需要将模型部署在线上提供服务,整体的过程如图2所示。

图2. 语言模型的实现流程

3.1 数据准备

数据准备是训练模型的第一步,如果数据的获取、清洗和预处理没有做好,之后训练的模型的效果会受到严重的影响,所以数据的准备是非常重要的。在准备数据的时候,需要获取与业务相关的数据,例如我们在做58租房或二手房帖子文本质量评测的时候,就需要获取租房或二手房的历史帖子的内容;同时数据要尽可能的均衡,例如可以随机抽取过去30天的50万条的帖子数据,然后进行清洗和预处理。数据清洗的目的是,使清洗后的数据是“高质量”的文本,这样使用这些“高质量”文本训练出的模型才能够更准确地识别出“低质量”的帖子内容。

以用于文本质量评测的语言模型训练为例,我们可以进行以下数据的处理:

  • 删除不相关的内容。例如,去掉无用的标题、链接、广告内容等。

  • 一些特殊符号的处理。例如,删除文本中的换行符\r, 软键盘字符,表情符号等。

  • 标点符号的处理。一般而言,标点符号是停用词的一种,通常要删掉。

  • 停用词的处理。对于一些语气词,虚词,代词,或者没有特定含义的词等,可以作为停用词被删掉。

  • html文本处理。对于带有html标签的文本,需要去掉这些标签并还原文本的格式。

  • 文字编码。在训练模型之前,需要对语料进行“数字”编码。最简单的编码方式是one-hot编码。但由于one-hot编码比较稀疏,而且当语料的词汇数很大时,编码的维度也会变得很大,所以一般采用其他的编码或多种编码相结合的方式,比较常见的是embedding的编码方式,编码维度相比于one-hot大大降低,模型训练速度也会加快很多。

  • 数据分批训练。为了加快训练速度,一般将语料分成多个batch,分批输入模型进行训练。

3.2 模型训练

首先要确定训练模型使用的损失函数和优化方法。LSTM模型采用的是交叉熵损失函数,具体的是将预测值prediction和目标值target之间的交叉熵作为目标函数。LSTM的优化方法有很多种,例如SGD, RMSprop等,而在实际应用中发现Adam优化方法的效果很不错,通常要好与其他几种优化方法。值得注意的是,语言模型的通用衡量指标“困惑度”(perplexity)和交叉熵损失的内涵是一致的,它们是呈指数关系的,具体的表达式是 perplexity = 2cross_entropy,因此困惑度有时也可以作为模型训练的损失函数。

其次我们简单描述一下语言模型的训练过程和数据维度。

  • 训练语言模型时,一般是一批一批地输入数据进行训练,每一批数据让多个句子(batch_size)同时训练。

  • 每个句子的单词数为num_steps,在LSTM/RNN中,句子长度就是时间步(time step)的长度,因此num_steps既代表了LSTM模型的时间序列的长度,也代表了句子长度。

  • 在自然语言处理中,每个词(或者汉语中的字)都是用词向量来表示的,可以采用one-hot或者embedding的方式将词编码成词向量。通常在tensorflow中使用embedding的方式对词进行编码,编码的维度对应的是embedding_size。因此实际上每次输入到LSTM模型中的数据的大小是:batch_size num_steps embedding_size。

  • LSTM每个单元有4个神经网络,神经网络的隐藏层的节点数为hidden_size, 那么这个LSTM单元中就有4*hidden_size个隐藏单元。

  • 每个LSTM的输出都是向量,包括状态向量Ct和输出向量ht,它们的长度都是当前LSTM单元的hidden_size。

  • LSTM的输出通常要加一个softmax全连接层,将输出映射到“词汇表”的维度,最终的输出值则是“词汇表”中每个字的“概率值”。

根据输出值,可以使用交叉熵损失或者困惑度(perplexity)来表征模型的好坏。同时在使用训练之后的语言模型进行文本质量评价时,我们也可以使用困惑度来表征文本质量的高低。

在训练的过程中,通常也有一些常用的技巧。例如:

  • 为了防止过拟合,一般要在LSTM单元中加上dropout层。

  • 数据集的编码不要直接采用one-hot,因为过于稀疏以及维度较大,通常使用embedding的方式作降维,例如将embedding size设置为128左右。

  • 在设置学习率的时候,可以先将学习率设置的较大一些,然后逐渐降低学习率,或者使用early stopping(早停法),当训练的模型在验证集上效果逐渐变差时,停止训练。

  • 采用多层LSTM的时候,增加层数不一定会增加模型的准确率,通常2~3层就足够,在一些情况下2层lstm layer就能保证较高的准确率,而且参数会少很多,训练和预测速度也会加快。

  • 复杂的LSTM的结构不一定会提高模型的准确率,而且过于复杂的结构会增加参数的数量,在训练和线上预测时,会严重影响速度。因此需要根据实际情况选择合适的模型结构和参数。

最后简单介绍一下对模型性能的评估。上面我们已经提到了使用交叉熵损失或“困惑度”(perplexity)来作为语言模型的损失函数,困惑度的物理上的含义是指,“如果根据语言模型随机地挑选词语,那么平均需要挑选多少次才能挑到正确的词”[参考论文:NeuralMachine Translation and Sequence-to-sequence Models: A Tutorial],也就是说困惑度越小,被挑选的词就越“确定”、“范围越小”,概率越大。相应的,如果使用“困惑度”来评价文本质量,输入语言模型的文本越是符合自然语言的习惯,语言模型针对这段文本给出的概率值就越大,相应的“困惑度”也就越低,同时我们可以根据“困惑度”的值,做一些处理(例如归一化处理),作为文本的质量分数。

但是单从“困惑度”,或者“损失函数”在训练过程中判断语言模型的好坏,是不够直观的,通常还可以使用另一种方式,比如使用语言模型来生成一段文本等具体任务,人为的观察语言模型的好坏。因此,可以每训练几轮存储一个临时的checkpoint模型,然后执行一些具体任务,比如生成一段和租房业务相关的文本,通过观察这段文本的通顺程度、合理程度,来直观上判断训练出的模型的性能。

3.3 模型导出和部署

在模型训练完成之后,需要将模型部署到线上,提供预测服务。在上线之前,我们需要将训练好的模型导出,在实际使用tensorflow搭建和训练模型时,通常会用到以下几种模型格式来导出和存储训练模型,这些模型的应用场景是有所不同的,需要根据特定的场景进行选择。

  • checkpoint格式:保存的是模型的权重,只包含variables对象序列化后的数据,但不包括图结构,所以需要提供代码才能重建计算图。checkpoint模型主要用于模型训练过程中参数的备份和模型训练热启动。在训练中,可以每隔几个epoch会存储一次checkpoint模型,然后调用并查看模型的效果,比如在训练语言模型时,可以调用导出的checkpoint模型来自动生成一段文本,观察是否通顺,从直观上判断训练的模型的好坏。

  • GraphDef 格式:这种格式包含了protobuf对象序列化之后的数据和计算图,可以从中获取operators,tensors和variables的定义,但是不包括variables的值,所以训练的权值仍然需要从checkpoint格式的模型中恢复。

  • SavedModel格式:这种格式是用于Tensorflow Serving的,该格式可以看做GraphDef和checkpoint的结合,此外在使用的时候还需要指定模型的输入和输出参数的SignatureDef,以便在调用模型的时候传递输入输出数据。在实际上线模型对外提供服务的时候,一般需要采用这种格式的模型。

上面已经提到,通常使用SavedModel格式的模型用于线上服务,我们在实际的上线中也使用了SavedModel模型。Google官方提供了Tensorflow Serving的框架,可以实现从模型应用到实际生产的解决方案。Tensorflow serving提供了基于gRPC的客户端和服务端的通信,而且可以进行模型热更新和自动的模型版本管理,这样使用者只需要关注线下的模型训练即可,而不必花很大的精力在线上服务上。在58集团内部平台则提供了SCF服务来调用Tensorflow serving方式,更加的方便快捷,用户只需要将输入数据封装,然后发送到服务端;Tensorflow serving接收到请求后,返回模型的计算结果给SCF客户端,即完成了一次输入到预测的流程。

4. 语言模型的应用

模型部署完毕之后,就可以使用语言模型提供线上服务了,在我们的实际场景中,主要尝试了文本质量评价和文本生成两方面的应用。结合上面的介绍,我们在图3中简要的概括了的语言模型在一个实际应用的流程,整体上分为:数据准备、模型训练、线上部署和实际应用四个步骤。在接下来的内容中,我们将更加具体的介绍语言模型在这两种实际应用中的一些细节。

图3. 语言模型在实际应用场景中的流程

4.1 文本质量评价

在根据特定业务的语料(例如租房业务)训练语言模型之后,我们可以使用这个语言模型来对业务相关的帖子进行质量评价。通常帖子质量是有不同的评价维度的,可以是和具体业务相关的,例如租房帖子中房子的户型,面积,地理位置等;也可以是和帖子本身相关的,例如帖子内容的文本质量,而语言模型可以在文本质量评价上发挥作用。

在训练语言模型的时候我们已经提到了一个概念,叫做“困惑度”(perplexity),它和交叉熵损失本质上是等价的,可以作为训练模型时的目标函数,同时可以用来衡量文本符合自然语言表达习惯的程度。因此可以将“困惑度”作为一个度量的指标来判断帖子文本的内容是否合理通顺。

比如,可以将租房、二手房等帖子的内容作为输入,使用相应的语言模型计算帖子内容的“困惑度”,作为衡量帖子好坏的一个质量因子。具体地,当我们在使用语言模型评价58集团的租房网站上的帖子时,如果帖子中有较多的乱码,或者较多不相关的广告宣传,或者语句不通顺等,通过语言模型计算出来的“困惑度”就会较高,对应的帖子的质量分数也就会比较低。在实际应用中,我们会将语言模型评价出的文本质量分数,作为帖子的一个质量因子,参与线上帖子的排序。

此外,为了进一步保证语言模型对文本质量的评分是合理的,我们还需要将语言模型打分和人工打分进行对比。在实际的应用中,我们采用的方法是,首先使用语言模型对大量帖子进行质量评分;然后根据帖子的质量分数以及设定的质量分阈值,从这些帖子中随机选出“高、中、低”质量的帖子各若干条;之后人工对这些帖子进行评分,并对比语言模型评分和人工评分的结果。例如针对实际的个人租房业务的帖子的文本质量评分,如果以人工评分作为对比的标准,语言模型评价“高质量”和“中等质量”帖子的平均准确率在90%以上,在评价“低质量”帖子时的平均准确率在95%以上。由于我们在训练语言模型时使用的数据都是经过预处理和清洗的�