NLP词法分析(一):中文分词技术

文分词介绍

中文分词相较于英文分词要难许多,由于英文自己就是由单词与空格组成的,而中文则是由独立的字组成的,但同时语义倒是有词来表达的。所以对于中文的分析与研究,首先应寻找合适的方法进行分词。现有的中文分词技术主要分为规则分词,统计分词与规则加统计相结合的方式,接下来将分别介绍。

html

规则分词

规则分词主要是经过构建词典,对须要进行分词的语句与词典中的词语进行匹配,从而实现切分,具体主要有正向最大匹配法,逆向最大匹配法,双向最大匹配法。
python

正向最大匹配法

正向最大匹配法实现的原理以下:假设分词词典中最长词语的长度为 i, 那么则选取须要进行分词的语句中的前 i个字,查询是否匹配词典中长度为i的词。若是匹配,则进行切分;若是不匹配,则选取i-1个字查询分词词典是否匹配,以此类推,直至成功切分。接着对于剩余的字段采起一样的方法进行切分,直到最后彻底切分。

好比咱们如今要对“正向最大匹配法”进行分词,而咱们的分词字典最长的词语长度为4。首先选取前四个字“正向最大”,词典中并没有匹配,那么接着选取前三个字“正向最”,依旧没有,再选择前两个字“正向”,成功匹配,则进行切分,接着选取以后的四个字“最大匹配”,以此类推,继续切分,直至切分完毕。
web

逆向最大匹配法

逆向最大匹配法的原理与正向是基本一致的,只是逆向最大匹配法是从句尾开始进行匹配。因为汉语中偏正结构较多,通常逆向最大匹配法的结果准确度要稍高于正向最大匹配法
算法

双向最大匹配法

双向最大匹配法是在正向与逆向最大匹配法的基础上对二者结果进行比较,选取切分词数较少的一个结果做为最终分词。双向最大匹配法在实际中运用更为广

接下来,咱们能够经过python实现简单的三种最大匹配法:网络

#读取训练字典
def readDict(path):
    dictionary=[]
    maximum=0
    with open(path,'r',encoding='utf-8') as f:
        for line in f:
            line=line.strip()
            if line:
                dictionary.append(line)
                if maximum<len(line):
                    maximum=len(line)
            else:
                continue           
    return dictionary,maximum

#正向最大匹配法
def forwardCut(text,dictionary,maximum):
    result=[]
    index=0
    while index<len(text):
        word=None
        for i in range(maximum,0,-1):
            if len(text)-index<i+1:
                continue
            piece=text[index:index+i]
            if piece in dictionary:
                word=piece
                result.append(word)
                index+=i
                break
        if word ==None:
            result.append(text[index:index+1])
            index+=1
            
    return result

#逆向最大匹配法
def backwardCut(text,dictionary,maximum):
    result=[]
    index=len(text)
    while index>0:
        word=None
        for i in range(maximum,0,-1):
            if index-i<0:
                continue
            piece=text[index-i:index]
            if piece in dictionary:
                word=piece
                result.append(word)
                index-=i
                break
        if word==None:
            result.append(text[index-1:index])
            index-=1
            
    return result[::-1]

#双向最大匹配法
def compare(text,dictionary,maximum):
    Fresult=forwardCut(text,dictionary,maximum)
    Bresult=backwardCut(text,dictionary,maximum)
    if len(Fresult)<len(Bresult):
        return '/'.join(Fresult)
    else:
        return '/'.join(Bresult)

接下来,咱们经过输入自制词典来比较三种分词方法的结果。词典以下:
在这里插入图片描述
正向最大匹配法:app

dictionary,maximum=readDict('dic.utf8')
print('/'.join(forwardCut('双向最大匹配法',dictionary,maximum)))

输出结果为:
在这里插入图片描述机器学习

逆向最大匹配法:svg

print('/'.join(backwardCut('双向最大匹配法',dictionary,maximum)))

输出结果为:
在这里插入图片描述学习

双向最大匹配法:ui

print(compare('双向最大匹配法',dictionary,maximum))

输出结果为:
在这里插入图片描述

规则分词在通常的分词任务中已经可以获得较好的结果了,可是其缺点是须要大量人力物力去维护与更新分词词典。同时,对于新词,规则分词很难可以正确切割。相反,统计分词法较好的解决的这个问题。

统计分词

统计分词的基本思想是基于统计的概念,若是相连的字在不一样的地方出现的次数越多,则其越有可能为一个词。所以咱们能够利用出项几率来推测成词的可能性,从而实现分词。常见的统计分词方法有隐含马尔可夫(HMM)、条件随机场(CRF)等。接下来咱们首先创建统计分词的语言模型,接着简单介绍一下HMM

统计分词语言模型

假设字符串的长度为m,那么对于这个字符串的几率分布能够描述为:
P ( w 1 , w 2 , . . . , w m ) P(w_1,w_2,...,w_m)
经过链式法则,咱们能够计算其几率值:
P ( w 1 , w 2 , . . . , w m ) = P ( w 1 ) P ( w 2 w 1 ) P ( w 3 w 2 , w 1 ) P ( w i w i 1 , w i 2 , . . , w 1 ) P ( w m w m 1 . . . w 1 ) P(w_1,w_2,...,w_m)=P(w_1)P(w_2|w_1)P(w_3|w_2,w_1)···P(w_i|w_{i-1},w_{i-2},..,w_1) ···P(w_m|w_{m-1}...w_1)
因为实际计算中,上式很是复杂,通常采用n-gram模型进行简化计算。n-gram模型即只考虑与字距离在n之内的文本对于这个字的影响。这样条件几率 P ( w i w i 1 , w i 2 , . . , w 1 ) P(w_i|w_{i-1},w_{i-2},..,w_1) 能够表示为:
P ( w i w i 1 , w i 2 , . . , w 1 ) = P ( w i w i 1 , w i 2 , . . , w i ( n 1 ) ) P(w_i|w_{i-1},w_{i-2},..,w_1)=P(w_i|w_{i-1},w_{i-2},..,w_{i-(n-1)})
这样,当n=1时,为unigram模型:
P ( w i w i 1 , w i 2 , . . , w 1 ) = P ( w i ) P ( w i 1 ) P ( w 1 ) P(w_i|w_{i-1},w_{i-2},..,w_1)=P(w_i)P(w_{i-1})···P(w_1)
即为每一个字单独的几率,可是unigram模型没法保留相邻字之间的关系,因此用于分词效果很不理想。当n=2,3时,模型能够保留必定的字之间的信息,又不至于使得计算过为复杂。

HMM模型

HMM即隐含马尔可夫模型,其基本思想为,将字在构造词的过程当中所承担的角色归为4类:B(begin),M(middle),E(end),S(single),接着,句子的分词任务即可以转化成对对句子中每一个字进行标注角色的任务。在介绍HMM分词以前,首先须要介绍隐含马尔可夫过程。

隐含马尔可夫过程

通常来讲,马尔可夫假设为:对于某一个随机过程,假设其存在n个状态S,那么对于某个特定的状态 S i S_i ,其几率分布只与前一个状态 S i 1 S_{i-1} 有关。即:
P ( S i S 1 , S 2 , . . . S i 1 ) = P ( S i S i 1 ) P(S_i|S_1,S_2,...S_{i-1})=P(S_i|S_{i-1})
而符合马尔可夫假设的过程即为马尔可夫过程,也叫作马尔可夫链,通常的马尔可夫链表示以下:
在这里插入图片描述
上图中,每一个圆表明一个状态,而线表明状态的转移与其几率。
而隐含马尔可夫过程是马尔可夫过程的扩展。在隐含马尔可夫过程当中,不一样时刻的状态 S i S_i 是不可见的,因此没法经过观测 S i S_i 来推测不一样状态的几率分布。可是其在每一个状态时会输出一个特定的特征 σ i \sigma_i ,而且这个特征 σ i \sigma_i 的几率分布与且仅与当前的状态有关。这就是隐含马尔可夫过程:
在这里插入图片描述
上图中,x表明状态,y表明对应特征。

HMM分词

了解了隐含马尔可夫过程,即可以介绍HMM分词模型了。经过上述内容咱们能够很容易的发现HMM分词是典型的隐含马尔可夫过程。其中B\M\E\S表明不一样的状态,而句子中每一个字则表明其不一样状态所产生的特征。而咱们的任务则是根据几率分布推测句子中每个字背后的状态,从而达到分词。
经过数学模型推导过程以下:
假设用 λ = λ 1 λ 2 . . . λ n \lambda=\lambda_1\lambda_2...\lambda_n 表示句子与其中每个字。而用 σ = σ 1 σ 2 . . . σ n \sigma=\sigma_1\sigma_2...\sigma_n 表示对应的状态。则分词任务能够描述成:
m a x P = m a x P ( σ λ ) = P ( σ 1 σ 2 . . . σ n λ 1 λ 2 . . . λ n ) max P=max P(\sigma|\lambda)=P(\sigma_1\sigma_2...\sigma_n|\lambda_1\lambda_2...\lambda_n)
经过贝叶斯公式能够获得:
P ( σ λ ) = P ( λ σ ) P ( σ ) P ( λ ) P(\sigma|\lambda)=\frac{P(\lambda|\sigma)P(\sigma)}{P(\lambda)}
对于 P ( λ ) P(\lambda) ,其只与分词时使用的训练集有关,是个常数。则咱们的模型能够转化为求:
m a x P ( λ σ ) P ( σ ) max P(\lambda|\sigma)P(\sigma)
根据隐含马尔可夫过程的假设
P ( λ σ ) = P ( λ 1 σ 1 ) P ( λ 2 σ 2 ) P ( λ n σ n ) P(\lambda|\sigma)=P(\lambda_1|\sigma_1)P(\lambda_2|\sigma_2)···P(\lambda_n|\sigma_n)
同时,对于 P ( σ ) P(\sigma) :
P ( σ ) = P ( σ 1 ) P ( σ 2 σ 1 ) P ( σ n σ 1 , σ 2 . . . σ n 1 ) P(\sigma)=P(\sigma_1)P(\sigma_2|\sigma_1)···P(\sigma_n|\sigma_1,\sigma_2...\sigma_{n-1})
根据马尔可夫过程的假设,当前状态只与上一状态有关:
P ( σ ) = P ( σ 1 ) P ( σ 2 σ 1 ) P ( σ 3 σ 2 ) P ( σ n σ n 1 ) P(\sigma)=P(\sigma_1)P(\sigma_2|\sigma_1)P(\sigma_3|\sigma_2)···P(\sigma_n|\sigma_{n-1})
那么, P ( σ λ ) P ( σ ) P(\sigma|\lambda)P(\sigma) 能够转化为:
P ( σ 1 ) P ( λ 1 σ 1 ) P ( σ 2 σ 1 ) P ( λ 2 σ 2 ) . . . P ( σ n σ n 1 ) P ( λ n σ n ) P(\sigma_1)P(\lambda_1|\sigma_1)P(\sigma_2|\sigma_1)P(\lambda_2|\sigma_2)...P(\sigma_n|\sigma_{n-1})P(\lambda_n|\sigma_n)
其中, P ( λ i σ i ) P(\lambda_i|\sigma_i) 称为发射几率,而 P ( σ i 1 σ i ) P(\sigma_{i-1}|\sigma_i) 称为转移几率。经过具体设部分转移几率,能够排除部分不合理的转移几率,例如EB等。

veterbi算法

在实际中,通常运用veterbi算法求解HMM模型。veterbi算法一种动态规划算法,其在语音识别,机器翻译等领域应用及其普遍。其核心的思想是:若是最终的最优路径通过某个 σ i \sigma_i ,那么从初始节点到 σ i 1 \sigma_{i-1} 也应该是最优路径。由于每一节点只会影响先后两个节点选择,那么假设最短路径不通过最优的子路径,那么用最优子路经代替现有路径,将生成更优路径,便与以前的自相矛盾了。
这样,咱们在考虑每个 σ i \sigma_i 时,只须要求出通过每个 σ i 1 \sigma_{i-1} 的最优路径,再考虑从每一个 σ i 1 \sigma_{i-1} 到每一个 σ i \sigma_i 的最优路径。这样假设每步最多有 l l 个状态,那么每步的计算量不超过 l 2 l^2 。计算效率为 o ( n l 2 ) o(n·l^2) ,n为节点个数。
如今考虑分词问题,假设有一n个字符的字符串,那么分词任务为为每一字符贴上几率最大的状态标签(B/M/E/S)。转换成算法描述,即有n个节点,每一个节点有四种可能的状态 σ \sigma 。这样,根据以前的语言模型描述,节点i-1到节点i的几率即为其发射几率乘转移几率 P ( σ i σ i 1 ) P ( λ i σ i ) P(\sigma_i|\sigma_{i-1})P(\lambda_i|\sigma_i) ,根据veterbi算法,将最大几率描述为最短路径,在计算节点i时,咱们已经求获得节点i-1四个状态的最短子路径,那么咱们只须要分别计算节点i四个状态与以前求得的4条最短子路径相结合的最短路径便获得了到节点i的四条最短子路径。最终,咱们将获得4条最终经过节点n四个状态的最优路径,经过比较路径长短即可以获得最终路径。这样,根据训练集所得的发射几率与转移几率,咱们即可以求模型的最短路径,从而实现分词。

HMM在现现在的分词领域运用很是普遍,但它也有必定的缺点,过多的假设致使了与实际状况的偏离。可是在真实的语料中,更多的序列是多重的交互特征形式,部分序列存在长程相关性。
此外,条件随机场(CRF)与deep learning的发展拓宽了中文分词的技术,基于机器学习与神经网络的分词方法也逐渐获得实践。

[1]涂铭,刘祥,刘树春. Python天然语言处理实战:核心技术与算法. 北京. 机械工业出版社,2018.4