自然语言处理(NLP,Natural Language Processing)是一个信息时代最重要的技术之一,简单来讲,就是让计算机能够理解人类语言的一种技术。在其中,分词技术是一种比较基础的模块。对于英文等拉丁语系的语言而言,由于词之间有空格作为词边际表示,词语一般情况下都能简单且准确的提取出来。而中文日文等文字,除了标点符号之外,字之间紧密相连,没有明显的词边界,因此很难将词提取出来。分词的意义非常大,在中文中,单字作为最基本的语义单位,虽然也有自己的意义,但表意能力较差,意义较分散,而词的表意能力更强,能更加准确的描述一个事物,因此在自然语言处理中,通常情况下词(包括单字成词)是最基本的处理单位。在具体的应用上,比如在常用的搜索引擎中,term如果是词粒度的话,不仅能够减少每个term的倒排列表长度,提升系统性能,并且召回的结果相关性高更准确。比如搜索query“的确”,如果是单字切分的话,则有可能召回“你讲的确实在理”这样的doc。分词方法大致分为两种:基于词典的机械切分,基于统计模型的序列标注切分两种方式。
基于词典的方法本质上就是字符串匹配的方法,将一串文本中的文字片段和已有的词典进行匹配,如果匹配到,则此文字片段就作为一个分词结果。但是基于词典的机械切分会遇到多种问题,最为常见的包括歧义切分问题和未登录词问题
歧义切分指的是通过词典匹配给出的切词结果和原来语句所要表达的意思不相符或差别较大,在机械切分中比较常见,比如下面的例子:“结婚的和尚未结婚的人”,通过机械切分的方式,会有两种切分结果:1,“结婚/的/和/尚未/结婚/的/人”;2,“结婚/的/和尚/未/结婚/的/人”。可以明显看出,第二种切分是有歧义的,单纯的机械切分很难避免这样的问题。
未登录词识别也称作新词发现,指的是词没有在词典中出现,比如一些新的网络词汇,如“网红”,“走你”;一些未登录的人名,地名;一些外语音译过来的词等等。基于词典的方式较难解决未登录词的问题,简单的case可以通过加词典解决,但是随着字典的增大,可能会引入新的bad case,并且系统的运算复杂度也会增加。
为了解决歧义切分的问题,在中文分词上有很多优化的方法,常见的包括正向最大匹配,逆向最大匹配,最少分词结果,全切分后选择路径等多种算法。
正向最大匹配指的是从左到右对一个字符串进行匹配,所匹配的词越长越好,比如“中国科学院计算研究所”,按照词典中最长匹配原则的切分结果是:“中国科学院/计算研究所”,而不是“中国/科学院/计算/研究所”。但是正向最大匹配也会存在一些bad case,常见的例子如:“他从东经过我家”,使用正向最大匹配会得到错误的结果:“他/从/东经/过/我/家”。
逆向最大匹配的顺序是从右向左倒着匹配,如果能匹配到更长的词,则优先选择,上面的例子“他从东经过我家”逆向最大匹配能够得到正确的结果“他/从/东/经过/我/家”。但是逆向最大匹配同样存在badcase:“他们昨日本应该回来”,逆向匹配会得到错误的结果“他们/昨/日本/应该/回来”。
针对正向逆向匹配的问题,将双向切分的结果进行比较,选择切分词语数量最少的结果。但是最少切分结果同样有bad case,比如“他将来上海”,正确的切分结果是“他/将/来/上海”,有4个词,而最少切分结果“他/将来/中国”只有3个词。
全切分方法就是将所有可能的切分组合全部列出来,并从中选择最佳的一条切分路径。关于路径的选择方式,一般有n最短路径方法,基于词的n元语法模型方法等。
n最短路径方法的基本思想就是将所有的切分结果组成有向无环图,每个切词结果作为一个节点,词之间的边赋予一个权重,最终找到权重和最小的一条路径作为分词结果。
基于词的n元语法模型可以看作是n最短路径方法的一种优化,不同的是,根据n元语法模型,路径构成时会考虑词的上下文关系,根据语料库的统计结果,找出构成句子最大模型概率。一般情况下,使用unigram和bigram的n元语法模型的情况较多。
针对基于词典的机械切分所面对的问题,尤其是未登录词识别,使用基于统计模型的分词方式能够取得更好的效果。基于统计模型的分词方法,简单来讲就是一个序列标注问题。
在一段文字中,我们可以将每个字按照他们在词中的位置进行标注,常用的标记有以下四个label:B,Begin,表示这个字是一个词的首字;M,Middle,表示这是一个词中间的字;E,End,表示这是一个词的尾字;S,Single,表示这是单字成词。分词的过程就是将一段字符输入模型,然后得到相应的标记序列,再根据标记序列进行分词。举例来说:“达观数据位是企业大数据服务商”,经过模型后得到的理想标注序列是:“BMMESBEBMEBME”,最终还原的分词结果是“达观数据/是/企业/大数据/服务商”。
在NLP领域中,解决序列标注问题的常见模型主要有HMM和CRF。
HMM(HiddenMarkov Model)隐马尔科夫模型应用非常广泛,基本的思想就是根据观测值序列找到真正的隐藏状态值序列。在中文分词中,一段文字的每个字符可以看作是一个观测值,而这个字符的词位置label(BEMS)可以看作是隐藏的状态。使用HMM的分词,通过对切分语料库进行统计,可以得到模型中5大要要素:起始概率矩阵,转移概率矩阵,发射概率矩阵,观察值集合,状态值集合。在概率矩阵中,起始概率矩阵表示序列第一个状态值的概率,在中文分词中,理论上M和E的概率为0。转移概率表示状态间的概率,比如B->M的概率,E->S的概率等。而发射概率是一个条件概率,表示当前这个状态下,出现某个字的概率,比如p(人|B)表示在状态为B的情况下人字的概率。
有了三个矩阵和两个集合后,HMM问题最终转化成求解隐藏状态序列最大值的问题,求解这个问题最长使用的是Viterbi算法,这是一种动态规划算法,具体的算法可以参考维基百科词条,在此不详细展开。(https://en.wikipedia.org/wiki/Viterbi_algorithm)
图1:HMM模型示意图
CRF(Conditionalrandom field,条件随机场)是用来标注和划分结构数据的概率化结构模型,通常使用在模式识别和机器学习中,在自然语言处理和图像处理等领域中得到广泛应用。和HMM类似,当对于给定的输入观测序列X和输出序列Y,CRF通过定义条件概率P(Y|X),而不是联合概率分布P(X,Y)来描述模型。CRF算法的具体算法可以参考维基百科词条。(https://en.wikipedia.org/wiki/Conditional_random_field)
图2:不同概率模型之间的关系及演化图
在实际应用中有很多工具包可以使用,比如CRF++,CRFsuite,SGD,Wapiti 等,其中CRF++的准确度较高。在分词中使用CRF++时,主要的工作是特征模板的配置。CRF++支持unigram,bigram两种特征,分别以U和B开头。举例来讲U00:%x[-2,0]表示第一个特征,特征取值是当前字的前方第二个字,U01:%x[-1,0]表示第二个特征,特征取值当前字前一个字,U02:%x[0,0]表示第三个特征,取当前字,以此类推。特征模板可以支持多种特征,CRF++会根据特征模板提取特征函数,用于模型的建立和使用。特征模板的设计对分词效果及训练时间影响较大,需要分析尝试找到适用的特征模板。
随着AlphaGo的大显神威,Deep Learning(深度学习)的热度进一步提高。深度学习来源于传统的神经网络模型。传统的神经网络一般由输入层,隐藏层,输出层组成,其中隐藏层的数目按需确定。深度学习可以简单的理解为多层神经网络,但是深度学习的却不仅仅是神经网络。深度模型将每一层的输出作为下一层的输入特征,通过将底层的简单特征组合成为高层的更抽象的特征来进行学习。在训练过程中,通常采用贪婪算法,一层层的训练,比如在训练第k层时,固定训练好的前k-1层的参数进行训练,训练好第k层之后的以此类推进行一层层训练。
图3:AlphaGo的神经网络模型的训练过程及架构
图4:Google Tensorflow官网的神经网络演示示意图
深度学习在很多领域都有所应用,在图像和语音识别领域中已经取得巨大的成功。从2012年开始,LSVRC(LargeScale Visual Recognition Challenge)比赛中,基于Deep Learningd计算框架一直处于领先。2015年LSVRC(http://www.image-net.org/challenges/LSVRC/2015/results)的比赛中,微软亚洲研究院(MSRA)在图像检测(Objectdetection),图像分类定位(Object Classification+localization)上夺冠,他们使用的神经网络深达152层。
在自然语言处理上,深度学习在机器翻译、自动问答、文本分类、情感分析、信息抽取、序列标注、语法解析等领域都有广泛的应用。2013年末google发布的word2vec工具,可以看做是深度学习在NLP领域的一个重要应用,虽然word2vec只有三层神经网络,但是已经取得非常好的效果。通过word2vec,可以将一个词表示为词向量,将文字数字化,更好的让计算机理解。使word2vec模型,我们可以方便的找到同义词或联系紧密的词,或者意义相反的词等。
图5:基于微信数据制作的word2vec模型测试: 编程
图6:基于微信数据制作的word2vec模型测试:韦德
词向量的意思就是通过一个数字组成的向量来表示一个词,这个向量的构成可以有很多种。最简单的方式就是所谓的one-hot向量。假设在一个语料集合中,一共有n个不同的词,则可以使用一个长度为n的向量,对于第i个词(i=0…n-1),向量index=i处值为1外,向量其他位置的值都为0,这样就可以唯一的通过一个[0,0,1,…,0,0]形式的向量表示一个词。one-hot向量比较简单也容易理解,但是有很多问题,比如当加入新词时,整个向量的长度会改变,并且存在维数过高难以计算的问题,以及向量的表示方法很难体现两个词之间的关系,因此一般情况下one-hot向量较少的使用。
如果考虑到词和词之间的联系,就要考虑词的共现问题。最简单的是使用基于文档的向量表示方法来给出词向量。基本思想也很简单,假设有n篇文档,如果某些词经常成对出现在多篇相同的文档中,我们则认为这两个词联系非常紧密。对于文档集合,可以将文档按顺编号(i=0…n-1),将文档编导作为向量索引,这样就有一个n维的向量。当一个词出现在某个文档i中时,向量i处值为1,这样就可以通过一个类似[0,1,0,…,1,0]形式的向量表示一个词。基于文档的词向量能够很好的表示词之间的关系,但是向量的长度和语料库的大小相关,同样会存在维度变化问题。
考虑一个固定窗口大小的文本片段来解决维度变化问题,如果在这样的片段中,两个词出现了,就认为这两个词有关。举例来讲,有以下三句话: “我/喜欢/你”,“我/爱/运动”,“我/爱/摄影”,如果考虑窗口的大小为1,也就是认为一个词只和它前面和后面的词有关,通过统计共现次数,我们能够得到下面的矩阵:
图7:基于文本窗口共现统计出来的矩阵
可以看到这是一个n*n的对称矩阵X,这个矩阵的维数会随着词典数量的增加而增大,通过SVD(Singular Value Decomposition,奇异值分解),我们可以将矩阵维度降低,但仍存在一些问题: 矩阵X维度经常改变,并且由于大部分词并不是共现而导致的稀疏性,矩阵维度过高计算复杂度高等问题。
Word2vec是一个多层的神经网络,同样可以将词向量化。在Word2vec中最重要的两个模型是CBOW(Continuous Bag-of-Word)模型和Skip-gram(Continuous Skip-gram)模型,两个模型都包含三层: 输入层,投影层,输出层。CBOW模型的作用是已知当前词Wt的上下文环境(Wt-2,Wt-1,Wt+1,Wt+2)来预测当前词,Skip-gram模型的作用是根据当前词Wt来预测上下文(Wt-2,Wt-1,Wt+1,Wt+2)。在模型求解中,和一般的机器学习方法类似,也是定义不同的损失函数,使用梯度下降法寻找最优值。Word2vec模型求解中,使用了Hierarchical Softmax方法和NegativeSampling两种方法。通过使用Word2vec,我们可以方便的将词转化成向量表示,让计算机和理解图像中的每个点一样,数字化词的表现。
深度学习有很多种不同类型的网络,在图像识别领域,CNN(Convolutional Neural Network,卷积神经网络)使用的较多,而在NLP领域,考虑到上下文的RNN(Recurrent Neural Networks,循环神经网络)取得了巨大的成功。在传统的神经网络中,从输入层到隐藏层到输出层,层之间是全连接的,但是每层内部的节点之间是无连接的。因为这样的原因,传统的神经网络不能利用上下文关系, 而在自然语言处理中,上下文关系非常重要,一个句子中前后词并不独立,不同的组合会有不同的意义,比如”优秀”这个词,如果前面是”不”字,则意义完全相反。RNN则考虑到网络前一时刻的输出对当前输出的影响,将隐藏层内部的节点也连接起来,即当前时刻一个节点的输入除了上一层的输出外,还包括上一时刻隐藏层的输出。RNN在理论上可以储存任意长度的转态序列,但是在不同的场景中这个长度可能不同。比如在词的预测例子中: 1,“他是亿万富翁,他很?”; 2,“他的房子每平米物业费40元,并且像这样的房子他有十几套,他很?”。从这两个句子中我们已经能猜到?代表“有钱”或其他类似的词汇,但是明显,第一句话预测最后一个词时的上线文序列很短,而第二段话较长。如果预测一个词汇需要较长的上下文,随着这个距离的增长,RNN将很难学到这些长距离的信息依赖,虽然这对我们人类相对容易。在实践中,已被证明使用最广泛的模型是LSTM(Long Short-Term Memory,长短时记忆)很好的解决了这个问题。
LSTM最早由Hochreiter及 Schmidhuber在1997年的论文中提出。首先LSTM也是一种RNN,不同的是LSTM能够学会远距离的上下文依赖,能够存储较远距离上下文对当前时间节点的影响。
所有的RNN都有一串重复的神经网络模块。对于标准的RNN,这个模块都比较简单,比如使用单独的tanh层。LSTM拥有类似的结构,但是不同的是,LSTM的每个模块拥有更复杂的神经网络结构:4层相互影响的神经网络。在LSTM每个单元中,因为门结构的存在,对于每个单元的转态,使得LSTM拥有增加或减少信息的能力。
图8:标准RNN模型中的重复模块包括1层结构
图9:LSTM模型中的重复模块包括4层结构
Keras(http://keras.io)是一个非常易用的深度学习框架,使用python语言编写,是一个高度模块化的神经网络库,后端同时支持Theano和TensorFlow,而Theano和TensorFlow支持GPU,因此使用keras可以使用GPU加速模型训练。Keras中包括了构建模型常用的模块,如Optimizers优化方法模块,Activations激活函数模块,Initializations初始化模块,Layers多种网络层模块等,可以非常方便快速的搭建一个网络模型,使得开发人员可以快速上手,并将精力放在模型设计而不是具体实现上。常见的神经网络模型如CNN,RNN等,使用keras都可以很快搭建出来,开发人员只需要将数据准备成keras需要的格式丢进网络训练即可。如果对keras中自带的layer有更多的需求,keras还可以自己定制所需的layer。
Keras项目中的example自带了多个示例,包括经典的mnist手写识别测试等,其中和NLP相关的示例有很多,比如基于imdb数据的情感分析、文本分类、序列标注等。其中lstm_text_generation.py示例可以用来参考设计序列标注问题,这个示例试图通过LSTM学习尼采的作品,通过序列标注的思想来训练一个文本生成器模型。下面着重看一下两个关键点:模型数据格式及模型设计。
训练数据准备
这段代码是数据准备的情况。将尼采全文进行数据切割,每40个字符为一个片段,将紧接这个片段的字符作为预测值,来进行训练。字符片段的间隔为3。
模型设计
在模型设计上,主要是使用了两层LSTM,每层的输出维度为512,并在每层LSTM后面加入了Dropout层,来防止过拟合。整个模型的输入维度是字符类别的个数,输入字符串长度是40,模型的输出维度也是字符类别长度。整个模型表达的意思是每输入40个字符,就会从模型中输出一个预测的字符。因为LSTM的对长依赖term的记忆性,因此在上下文很长(40个字符)的情况下也可以表现的很好。
基于上面的知识,可以考虑使用深度学习的方法进行中文分词。分词的基础思想还是使用序列标注问题,将一个句子中的每个字标记成BEMS四种label。模型整的输入是字符序列,输出是一个标注序列,因此这是一个标准的sequenceto sequence问题。因为一个句子中每个字的上下文对这个字的label类型影响很大,因此考虑使用RNN模型来解决。
测试硬件是Macbook Pro 2014 Mid高配版,带NvidiaGT 750M GPU,虽然GPU性能有限,但通过测试性能还是强过mac自带的i7 CPU。使用GPU进行模型运算,需要安装Nvidia的cuda相关程序及cuDNN库,会有较大的性能提升。软件方面使用python2.7,安装好了keras,theano及相关库。关于keras使用GPU训练的环境搭建问题,可以参考这篇文章(Run Keras on Mac OS with GPU,http://blog.wenhaolee.com/run-keras-on-mac-os-with-gpu/)
模型训练使用的是经典的bakeoff2005中的微软研究院的切分语料,将其中的train部分拿过来做训练,将test作为最终的测试。
首先,将训练样本中出现的所有字符全部映射成对应的数字,将文本数字化,形成一个字符到数据的映射。在分词中,一个词的label受上下文影响很大,因此参考之前提到的lstm_text_generation.py示例,我们将一个长度为n个字符的输入文本处理成n个长度为k的向量,k为奇数。举例来说,当k=7时,表示考虑了一个字前3个字和后三个字的上下文,将这个七个字作为一个输入,输出就是这个字的label类型(BEMS)。
参考lstm_text_generation.py 中的模型搭建方式,我们采用一层的LSTM构建网络,代码如下,
其中,输入的维度input_dim 是字符类别总数,hidden_node 是隐藏层的结点个数。在上面的模型中,第一层输入层Embedding的作用是将输入的整数向量化。在现在这个模型中,输入是一个一维向量,里面每个值是字符对应的整数,Embedding层就可以将这些整数向量化,简单来讲就是生成了每个字的字向量。接下来紧跟着一层是LSTM,它输出维度也是隐藏层的结点个数。Dropout层的作用是让一些神经节点随机不工作,来防止过拟合现象。Dense层是最后的输出,这里nb_classes的数目是4,代表一个字符的label。
模型建立好后开始训练,重复20次,训练的结果如下:
图10:基础模型(1层LSTM优化器RMSprop)训练20次
训练好后,我们使用msr_test的测试数据进行分词,并将最终的分词结果使用icwb2自带的脚本进行测试,结果如下:
图11:基础模型F Score: 0.845
可以看到基础模型的F值一般,比传统的CRF效果差的较多,因此考虑优化模型。
模型参数调整
首先想到的是模型参数的调整。Keras官方文档中提到,RMSprop优化方法在RNN网络中通常是一个好的选择,但是在尝试了其他的优化器后,比如Adam,发现可以取得更好的效果:
图12:1层LSTM优化器Adam训练20次
可以看到,Adam在训练过程中的精度就已经高于RMSprop,使用icwb2的测试结果为:
图11:修改优化器Adam后的模型F Score: 0.889
现在网络结构较简单,只有一层LSTM,参考文档示例中的模型设计,考虑使用两层的LSTM来进行测试,修改后的代码如下:
注意,第一层LSTM有个return_sequences =True可以将最后一个结果出入到输出序列,保证输出的tensor是3D的,因为LSTM的输入要求是3D的tensor。
两层LSTM模型训练过程如下:
图12:2层LSTM优化器Adam训练20次的模型
可以看到,两层LSTM使得模型更加复杂,训练时常也增加不少。模型训练后,使用icwb2的测试结果为:
图13:两层LSTM的模型F Score: 0.889
可以看到,随着模型的复杂,虽然F Score无提升,但是其他的指标有一定的提升。一般来说,神经网络在大量训练数据下也会有更好的效果,后续会继续尝试更大数据集更复杂模型的效果。
使用深度学习技术,给NLP技术给中文分词技术带来了新鲜血液,改变了传统的思路。深度神经网络的优点是可以自动发现特征,大大减少了特征工程的工作量,随着深度学习技术的进一步发展,在NLP领域将会发挥更大的作用。达观数据将在已有成熟的NLP算法及模型基础上,逐渐融合基于深度神经网络的NLP模型,在文本分类、序列标注、情感分析、语义分析等功能上进一步优化提升效果,来更好为客户服务。
本文作者:
高翔,达观数据联合创始人,上海交通大学通信硕士,负责达观数据产品技术相关开发管理工作,曾任职于盛大文学、盛大创新院,在搜索引擎、自然语言处理、机器学习及前端技术有着丰富的经验。