中文分词简介

在汉语中,词是以字为单位的,但是一篇文章的语义表达却仍然是以词来作为划分的。因此,在处理中文文本时,需要进行分词处理,将句子转化成为词的表示。这个切片过程就是中文分词,通过计算机自动识别出句子的词。

规则分词

通过构建字典,在切分语句时,将语句中的每个字符串与字典中的词逐一比较,找到则切分,找不到则不切分。

正向最大匹配法

假定分词字典中的最长词有i个汉字字符,则用被处理文档的当前字串中的前 i个字作为匹配字段,查找字典,若字典中存在这样的一个i字词,则匹配成功,匹配字段作为一个词切分出来。如果字典中找不到这样的一个i字词,则匹配失败,将匹配字段中的最后一个字去掉,对剩下的字串重新进行匹配处理。直到匹配成功,再从文档中选出下一个i字字串进行同上的匹配,直到文章被扫描完为止。

逆向最大匹配法

逆向最大匹配分词切分与正向最大匹配的方向相反,它从被处理文档的末端开始匹配扫描,每次取最末端的i个字符(i为字典中的最长字数)作为匹配字段,若匹配失败,则去掉匹配字段的前一个字,继续匹配。相应地,它使用的分词字典是逆序字段,其中的每个词条都按照逆序存放。

实际处理时,先将文档进行倒排处理。然后根据逆序字典,对逆序文档用正向最大匹配法处理即可。

双向最大匹配法

综合正向最大匹配和逆向最大匹配,选取词数切分最少(划分出更少的词)的作为结果;若词数相同,则选取单字较少的那个。规则分词相对比较简单,我这里就略写了。

统计分词

语言模型

语言模型(概率论角度):为含有m个词的字符串确定其概率分布NLP 分词词向量 nlp分词技术_中文分词,其中NLP 分词词向量 nlp分词技术_NLP 分词词向量_02依次表示文本中的各个词语。
书中此处介绍的语言模型应该是为了求某一句子出现的概率,或者用来预判估计下一个词,为HMM模型做了一定铺垫。
此时按照概率论(条件概率),有下式成立
NLP 分词词向量 nlp分词技术_概率论_03
显然,当文本过长后,有关概率的计算变得难度大了起来。为了解决这个问题提出n元模型。仅仅考虑前n-1个词对当前词的概率影响,即有下式。(对于句子中前n-1个词语,为原式)
NLP 分词词向量 nlp分词技术_最大匹配_04
一般使用频率计数比例计算n元条件概率,下式中NLP 分词词向量 nlp分词技术_最大匹配_05,表示字串NLP 分词词向量 nlp分词技术_概率论_06在语料集中出现的次数。
NLP 分词词向量 nlp分词技术_概率论_07
n元模型,n=1时,容易发现各个词之间是相互独立的(P(w)=P(w1)P(w2)…P(wn)),当n>=2时,可以保留语序信息。可采用拉普拉斯平滑算法解决分子分母为0情况。

HMM模型

HMM(Hidden Markov model)隐马尔可夫模型可用于分词。
HMM模型首先对句子进行了数学抽象:NLP 分词词向量 nlp分词技术_最大匹配_08。其中NLP 分词词向量 nlp分词技术_中文分词_09表示该句子,NLP 分词词向量 nlp分词技术_中文分词_10表示句子中的第i个字,该字可以为B(词首)、M(词中)、E(词尾)、S(单独成词),我们用NLP 分词词向量 nlp分词技术_概率论_11表示第i个字的标注,将分词问题转化为字的标注问题。
那么此时我们的目标函数即为
NLP 分词词向量 nlp分词技术_NLP 分词词向量_12
同时,利用贝叶斯公式对其进行变换可得
NLP 分词词向量 nlp分词技术_中文分词_13
上式中,NLP 分词词向量 nlp分词技术_自然语言处理_14NLP 分词词向量 nlp分词技术_中文分词_09确定时为定值,因此我们仅仅比较分子即可。我们接下来主要针对分子进行处理,首先是NLP 分词词向量 nlp分词技术_概率论_16,我们做出马尔可夫假设,得到
NLP 分词词向量 nlp分词技术_自然语言处理_17
对于NLP 分词词向量 nlp分词技术_概率论_18,我们做出另一个假设——齐次马尔科夫假设(类似之前的二元模型)
NLP 分词词向量 nlp分词技术_中文分词_19
在这里NLP 分词词向量 nlp分词技术_最大匹配_20称为发射概率,NLP 分词词向量 nlp分词技术_自然语言处理_21称为转移概率。均是通过语料集计算而来,感觉在求NLP 分词词向量 nlp分词技术_中文分词_22时,可以采用拉普拉斯平滑算法。下式中NLP 分词词向量 nlp分词技术_自然语言处理_23函数表示其在语料集出现的次数。如,NLP 分词词向量 nlp分词技术_中文分词_24表示在语料库中字NLP 分词词向量 nlp分词技术_自然语言处理_25标注为NLP 分词词向量 nlp分词技术_概率论_26的次数,NLP 分词词向量 nlp分词技术_概率论_27表示在语料集中NLP 分词词向量 nlp分词技术_概率论_28后一个字标注为NLP 分词词向量 nlp分词技术_最大匹配_29的次数等等。
NLP 分词词向量 nlp分词技术_中文分词_30
该模型可以通过Viterbi算法进行求解。
input:
NLP 分词词向量 nlp分词技术_概率论_31
NLP 分词词向量 nlp分词技术_自然语言处理_32
NLP 分词词向量 nlp分词技术_概率论_33
output:
NLP 分词词向量 nlp分词技术_自然语言处理_34
process
设置状态转移概率数组v(v[t][y]表示第t个字是y状态的可能性),以及路径记录数组path

for y in states
 v[1][y] = Pi(y)*P(|y),path[y]=[y]
 end for
 for i in [2,n]
for y in statas
v[i][y]=max(v[i-1][yp]*P(y|yp)*P(|y)) 对于 yp  states
path[y]=path[y]+[ypm] (ypm为上一步取得最大值时的yp)
end for
 end for
 ym = arg{max(v[n][y]) ,对于 y in states}


算法输出path[ym],结束算法。

注:若输入没有P(NLP 分词词向量 nlp分词技术_自然语言处理_25|y),可以设置其为1。
该算法的时间复杂度为O(句子长度*状态数量^2),是一种比较快的算法。

其他分词方法

  1. 条件随机场(CRF,相比HMM,当前状态不仅和之前的状态相关还和之后的状态相关)
  2. 神经网络分词算法

混合分词

基于字典的分词方式进行分词,再用统计分词的方法进行辅助。多在实际工程中使用。

中文分词工具——jieba

jieba提供了三种分词方式

import jieba
sent = '中文分词是文本处理不可缺少的一步'
  1. 精确模式
    试图将句子最精确地切开,适合文本分析
seg_list = jieba.cut(sent,cut_all=False)
# seg_list = jieba.cut(sent) #默认为精确模式
  1. 全模式
    把文本中所有可以成词的词语都扫描出来
seg_list = jieba.cut(sent,cut_all=True)
  1. 搜索引擎模式
    在精确模式的基础上,再对长词进行切分,提高召回率
seg_list = jieba.cut(sent,cut_all=True)

附录

书上的代码大家估计已经看到了,这里就不再重复了,提供一下jieba中的实现函数。

jieba中viterbi算法的实现

MIN_FLOAT = -3.14e100
MIN_INF = float("-inf")
def viterbi(obs, states, start_p, trans_p, emit_p):
    V = [{}] #tabular
    mem_path = [{}]
    all_states = trans_p.keys()
    for y in states.get(obs[0], all_states): #init
        V[0][y] = start_p[y] + emit_p[y].get(obs[0], MIN_FLOAT)
        mem_path[0][y] = ''
    for t in range(1, len(obs)):
        V.append({})
        mem_path.append({})
        #prev_states = get_top_states(V[t-1])
        prev_states = [x for x in mem_path[t-1].keys() if len(trans_p[x]) > 0]

        prev_states_expect_next = set((y for x in prev_states for y in trans_p[x].keys()))
        obs_states = set(states.get(obs[t], all_states)) & prev_states_expect_next

        if not obs_states:
            obs_states = prev_states_expect_next if prev_states_expect_next else all_states

        for y in obs_states:
            prob, state = max([(V[t-1][y0] + trans_p[y0].get(y,MIN_INF) + emit_p[y].get(obs[t],MIN_FLOAT), y0) for y0 in prev_states])
            V[t][y] = prob
            mem_path[t][y] = state

    last = [(V[-1][y], y) for y in mem_path[-1].keys()]
    #if len(last)==0:
        #print obs
    prob, state = max(last)

    route = [None] * len(obs)
    i = len(obs) - 1
    while i >= 0:
        route[i] = state
        state = mem_path[i][state]
        i -= 1
    return (prob, route)