【NLP-笔记】中文分词技术

中文分词简介

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

规则分词

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

正向最大匹配法

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

逆向最大匹配法

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

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

双向最大匹配法

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

统计分词

语言模型

语言模型(概率论角度):为含有m个词的字符串确定其概率分布 P ( ω 1 , ω 2 , . . . , ω m ) P(\omega_1,\omega_2,...,\omega_m) P(ω1,ω2,...,ωm),其中 ω 1 − ω m \omega_1-\omega_m ω1ωm依次表示文本中的各个词语。
书中此处介绍的语言模型应该是为了求某一句子出现的概率,或者用来预判估计下一个词,为HMM模型做了一定铺垫。
此时按照概率论(条件概率),有下式成立
P ( ω 1 , ω 2 , . . . , ω m ) = P ( ω 1 ) P ( ω 2 ∣ ω 2 ) P ( ω 3 ∣ ω 1 ω 2 ) . . . P ( ω m ∣ ω 1 ω 2 . . . ω m − 1 ) P(\omega_1,\omega_2,...,\omega_m)=P(\omega_1)P(\omega_2|\omega_2)P(\omega_3|\omega_1\omega_2)...P(\omega_m|\omega_1\omega_2...\omega_{m-1}) P(ω1,ω2,...,ωm)=P(ω1)P(ω2ω2)P(ω3ω1ω2)...P(ωmω1ω2...ωm1)
显然,当文本过长后,有关概率的计算变得难度大了起来。为了解决这个问题提出n元模型。仅仅考虑前n-1个词对当前词的概率影响,即有下式。(对于句子中前n-1个词语,为原式)
P ( ω i ∣ ω 1 , . . . , ω i − 1 ) ≈ P ( ω i ∣ ω i − ( n − 1 ) . . . ω i − 1 ) P(\omega_i|\omega_1,...,\omega_{i-1})\approx P(\omega_i|\omega_{i-(n-1)}...\omega_{i-1}) P(ωiω1,...,ωi1)P(ωiωi(n1)...ωi1)
一般使用频率计数比例计算n元条件概率,下式中 c o u n t ( ω ) count(\omega) count(ω),表示字串 ω \omega ω在语料集中出现的次数。
P ( ω i ∣ ω i − ( n − 1 ) , . . . , ω i − 1 ) = c o u n t ( ω i − ( n − 1 ) , . . . , ω i − 1 ω i ) c o u n t ( ω i − ( n − 1 ) , . . . , ω i − 1 ) P(\omega_i|\omega_{i-(n-1)},...,\omega_{i-1}) = \dfrac{count(\omega_{i-(n-1)},...,\omega_{i-1}\omega_i)}{count(\omega_{i-(n-1)},...,\omega_{i-1})} P(ωiωi(n1),...,ωi1)=count(ωi(n1),...,ωi1)count(ωi(n1),...,ωi1ωi)
n元模型,n=1时,容易发现各个词之间是相互独立的(P(w)=P(w1)P(w2)…P(wn)),当n>=2时,可以保留语序信息。可采用拉普拉斯平滑算法解决分子分母为0情况。

HMM模型

HMM(Hidden Markov model)隐马尔可夫模型可用于分词。
HMM模型首先对句子进行了数学抽象: λ = λ 1 λ 2 . . . λ n \lambda=\lambda_1\lambda_2...\lambda_n λ=λ1λ2...λn。其中 λ \lambda λ表示该句子, λ i ( 1 ≤ i ≤ n ) \lambda_i(1\leq i\leq n) λi(1in)表示句子中的第i个字,该字可以为B(词首)、M(词中)、E(词尾)、S(单独成词),我们用 o i ( o i ∈ { B , M , E , S } ) o_i(o_i\in \{B,M,E,S\}) oi(oi{B,M,E,S})表示第i个字的标注,将分词问题转化为字的标注问题。
那么此时我们的目标函数即为
max ⁡ P ( o ∣ λ ) = max ⁡ P ( o 1 , o 2 , . . . , o n ∣ λ 1 , λ 2 , . . . , λ n ) \max P(o|\lambda) =\max P(o_1,o_2,...,o_n|\lambda_1,\lambda_2,...,\lambda_n) maxP(oλ)=maxP(o1,o2,...,onλ1,λ2,...,λn)
同时,利用贝叶斯公式对其进行变换可得
P ( o ∣ λ ) = P ( λ ∣ o ) P ( o ) P ( λ ) P(o|\lambda) = \frac{P(\lambda|o)P(o)}{P(\lambda)} P(oλ)=P(λ)P(λo)P(o)
上式中, P ( λ ) P(\lambda) P(λ) λ \lambda λ确定时为定值,因此我们仅仅比较分子即可。我们接下来主要针对分子进行处理,首先是 P ( λ ∣ o ) P(\lambda |o) P(λo),我们做出马尔可夫假设,得到
P ( λ ∣ o ) = P ( λ 1 ∣ o 1 ) P ( λ 2 ∣ o 2 ) . . . P ( λ n ∣ o n ) P(\lambda|o) = P(\lambda_1|o_1)P(\lambda_2|o_2)...P(\lambda_n|o_n) P(λo)=P(λ1o1)P(λ2o2)...P(λnon)
对于 P ( o ) P(o) P(o),我们做出另一个假设——齐次马尔科夫假设(类似之前的二元模型)
P ( o ) = P ( o 1 ) P ( o 2 ∣ o 1 ) . . . P ( o n ∣ o 1 . . . o n − 1 ) ≈ P ( o 1 ) P ( o 2 ∣ o 1 ) . . . P ( o n ∣ o n − 1 ) P(o) = P(o_1)P(o_2|o_1)...P(o_n|o_1...o_{n-1})\\\approx P(o_1)P(o_2|o_1)...P(o_n|o_{n-1}) P(o)=P(o1)P(o2o1)...P(ono1...on1)P(o1)P(o2o1)...P(onon1)
在这里 P ( λ i ∣ o i ) ( 1 ≤ i ≤ n ) P(\lambda_i|o_i)(1\leq i \leq n) P(λioi)(1in)称为发射概率, P ( o i ∣ o i − 1 ) ( 1 ≤ i ≤ n ) P(o_i|o_{i-1}) (1\leq i \leq n) P(oioi1)(1in)称为转移概率。均是通过语料集计算而来,感觉在求 P ( λ i ∣ o i ) P(\lambda_i|o_i) P(λioi)时,可以采用拉普拉斯平滑算法。下式中 c o u n t count count函数表示其在语料集出现的次数。如, c o u n t ( λ i , o i ) count(\lambda_i,o_i) count(λi,oi)表示在语料库中字 λ i \lambda_i λi标注为 o i o_i oi的次数, c o u n t ( o i , o i − 1 ) count(o_i,o_{i-1}) count(oi,oi1)表示在语料集中 o i − 1 o_{i-1} oi1后一个字标注为 o i o_{i} oi的次数等等。
P ( λ i ∣ o i ) = c o u n t ( λ i , o i ) c o u n t ( o i ) P ( o i ∣ o i − 1 ) = c o u n t ( o i , o i − 1 ) c o u n t ( o i − 1 ) P(\lambda_i|o_i)=\frac{count(\lambda_i,o_i)}{count(o_i)}\\ P(o_i|o_{i-1})=\frac{count(o_i,o_{i-1})}{count(o_{i-1})} P(λioi)=count(oi)count(λi,oi)P(oioi1)=count(oi1)count(oi,oi1)
该模型可以通过Viterbi算法进行求解。
input:
P ( λ i ∣ o i ) ( 1 ≤ i ≤ n ) P ( o k 1 ∣ o k 2 ) ( o k 1 ∣ o k 2 ∈ { B , M , E , S } ) P i ( o k ) , 每 个 状 态 作 为 句 子 开 头 概 率 P(\lambda_i|o_i)(1\leq i\leq n) \\P(o_{k_1}|o_{k_2})(o_{k_1}|o_{k_2}\in \{B,M,E,S\})\\ Pi(o_k),每个状态作为句子开头概率 P(λioi)(1in)P(ok1ok2)(ok1ok2{B,M,E,S})Pi(ok),
s t a t e s = { B , M , E , S } states=\{B,M,E,S\} states={B,M,E,S}
λ = λ 1 λ 2 . . . λ n ( 输 入 句 子 ) \lambda=\lambda_1\lambda_2...\lambda_n (输入句子) λ=λ1λ2...λn()
output:
o = o 1 o 2 . . . o n o=o_1o_2...o_n o=o1o2...on
process
设置状态转移概率数组v(v[t][y]表示第t个字是y状态的可能性),以及路径记录数组path
for y in states
\quad v[1][y] = Pi(y)*P( λ 1 \lambda_1 λ1|y),path[y]=[y]
end for
for i in [2,n]
\quad for y in statas
\quad\quad v[i][y]=max(v[i-1][yp]*P(y|yp)*P( λ i \lambda_i λi|y)) 对于 ∀ \forall yp ∈ \in states
\quad\quad path[y]=path[y]+[ypm] (ypm为上一步取得最大值时的yp)
\quad end for
end for
ym = arg y _y y{max(v[n][y]) ,对于 ∀ \forall y in states}
算法输出path[ym],结束算法。

注:若输入没有P( λ i \lambda_i λi|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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值