中文分词方法汇总笔记
感谢知乎 @华天清 的总结
分词难点
- 分词算法: 不同的分词算法得到的分词结果不同,对下层的文本处理有较大的影响
- 未登录词的OOV的识别: 难以识别未登录词
- 歧义:
组合型歧义:在汉字字段AB中, A是词, B是词, AB仍是词, 则AB为组合型歧义字段。
例:我是/清华大学/的;我是/清华/大学/的
交集型歧义:在字段 ABC 中 AB是词 BC也是词, 则 ABC为交集型歧义
例:球拍/卖 球/拍卖
真歧义:在一句话中, 由人去判断也不知道哪个应该是词, 哪个应该不是词
例:球拍卖了 可以是 球/拍卖了 也可以是 球拍/卖了
分词方法
传统基于字典(规则分词)
一、 正向最大匹配法 FMM:
- 从左向右取待切分汉语句的m个字符作为匹配字段,m为大机器词典中最长词条个数。
- 查找大机器词典并进行匹配。若匹配成功,则将这个匹配字段作为一个词切分出来。
- 若匹配不成功,则将这个匹配字段的最后一个字去掉,剩下的字符串作为新的匹配字段,进行再次匹配,重复以上过程,直到切分出所有词为止。
二、逆向最大匹配法 RMM:
对文本从右至左切出最长的词,该算法是正向最大匹配的逆向思维,匹配不成功,将匹配字段的最前一个字去掉,实验表明,逆向最大匹配算法要优于正向最大匹配算法
汉语中偏正结构较多,若使用逆向匹配,可以适当的提高精度
三、双向匹配分词法 BMM:
将正向最大匹配法得到的分词结果和逆向最大匹配法的到的结果进行比较,从而决定正确的分词方法。
- 正逆向分词次数不同,选分词树较少的
- 词数相同,且结果相同 返回任意一个
- 词数相同,结果不同,返回单字最少的
# -*- coding:utf-8 -*-
# @Time : 2019/10/21 11:12
# @Author : Ray.X
# 初始化
word_dict = ['研究', '研究生', '生命', '起源', '南京市', '南京市长', '长江', '大桥', '长江大桥']
test_str = '在南京市长江大桥研究生命的起源'
# 遍历分词词典,获得最大分词长度
MaxLen = 0
for key in word_dict:
if len(key) > MaxLen:
MaxLen = len(key)
def forward_mm():
"""
正向最大匹配 FMM
:return:
"""
foward_out = []
n = 0
while n < len(test_str):
matched = 0
# range(start, stop, step),根据start与stop指定的范围以及step设定的步长 step=-1表示去掉最后一位
for i in range(MaxLen, 0, -1): # i等于max_chars到1 3-2-1
w = test_str[n: n + i] # 截取文本字符串n到n+1位
# 判断所截取字符串是否在分词词典内
if w in word_dict:
foward_out.append(w)
matched = 1
n = n + i
break
if not matched: # 等于 if matched == 0
foward_out.append(test_str[n])
n = n + 1
print('正向最大匹配 FMM:\n', foward_out)
return foward_out
def reverse_mm():
"""
逆向最大匹配 RMM
:return:
"""
reverse_out = []
n = len(test_str)
while 0 < n:
matched = 0
# range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长 step=-1表示去掉最后一位
for i in range(MaxLen, 0, -1): # i等于max_chars到1
w = test_str[n - i: n] # 截取文本字符串n-i到n位
# 判断所截取字符串是否在分词词典内
if w in word_dict:
reverse_out.append(w)
matched = 1
n = n - i
break
if matched == 0:
reverse_out.append(test_str[n - 1])
n = n - 1
print('逆向最大匹配 RMM:\n', list(reversed(reverse_out))) # 因为是逆向所以最终结果需要反向
return list(reversed(reverse_out))
def bi_mm():
"""
双向最大匹配 BMM
:return:
"""
fmm = forward_mm()
rmm = reverse_mm()
# 单字词个数
f_single_word = 0
r_single_word = 0
# 总词数
tot_fmm = len(fmm)
tot_rmm = len(rmm)
# 未登录词
oov_fmm = 0
oov_rmm = 0
# 罚分,罚分值越低越好
score_fmm = 0
score_rmm = 0
# 如果正向和反向结果一样,返回任意一个
if fmm == rmm:
bmm = rmm
else: # 分词结果不同,返回单字数、非字典词、总词数少的那一个
for w in fmm:
if len(w) == 1:
f_single_word += 1
if w not in word_dict:
oov_fmm += 1
for w in rmm:
if len(w) == 1:
r_single_word += 1
if w not in word_dict:
oov_rmm += 1
# 可以根据实际情况调整惩罚分值
# 这里都罚分都为1分
# 非字典词越少越好
if oov_fmm > oov_rmm:
score_fmm += 1
elif oov_fmm < oov_rmm:
score_rmm += 1
# 总词数越少越好
if tot_fmm > tot_rmm:
score_fmm += 1
elif tot_fmm < tot_rmm:
score_rmm += 1
# 单字词越少越好
if f_single_word > r_single_word:
score_fmm += 1
elif f_single_word < r_single_word:
score_rmm += 1
# 返回罚分少的那个
if score_fmm < score_rmm:
bmm = fmm
else:
bmm = rmm
print('双向最大匹配 BMM:\n', bmm)
if __name__ == "__main__":
bi_mm()
从结果看 逆向优于正向
四、N-最短路径法
每个句子将生成一个有向无环图, 每个字作为图的一个定点, 边代表可能的分词
在上图中, 边的起点为词的第一个字, 边的终点为词尾的下一个字. 边1表示"我"字单字成词, 边2表示"只是"可以作为一个单词.
每个边拥有一个权值, 表示该词出现的概率. 最简单的做法是采用词频作为权值, 也可以采用TF-IDF值作为权值提高对低频词的分词准确度.
N最短路径分词即在上述有向无环图中寻找N条权值和最大的路径, 路径上的边标志了最可能的分词结果.通常我们只寻找权值和最大的那一条路径.
根据上图的权值最大路径得到的分词结果为: “我/只是/做/了/一些/微小/的/工作”
参考 https://www.cnblogs.com/Finley/p/6619187.html
五、设立切分标志法
收集切分标志,在自动分词前处理切分标志,再用FMM、RMM进行细加工。
六、最佳匹配(OM,分正向和逆向)
对分词词典按词频大小顺序排列,并注明长度,降低时间复杂度。
优点:易于实现
缺点:匹配速度慢。对于未登录词的补充较难实现。缺乏自学习
基于词典的分词方法属于粗分模型,普遍对歧义和未登录词的处理较差
基于机器学习的分词方法
统计分词
主要思想为把每个词看做是由词的最小单位的各个字组成,如果相连的字在不同的文本中出现的次数最多,就证明这个相连的字很可能就是一个词。因此可以利用字与字相邻出现的频率来反映成词的可靠度,当组合频率高于某一个阈值时,就认为这个相邻字构成的是一个词。
**基本操作:**1. 建立语言模型 2. 对句子单词划分,然后对划分结果进行概率计算,获得概率最大的分词方式。
主要使用 隐马尔可夫模型 HMM、条件随机场模型 CRF、 最大熵模型 ME等统计学算法
语言模型
语言模型在信息检索、机器翻译、语音识别中承担着重要的任务。
用概率论来说就是:为长度为m的字符串确定其概率分布 P ( ω 1 , ω 2 , . . . , ω m ) P(\omega_{1},\omega_{2}, ...,\omega_{m}) P(ω1,ω2,...,ωm),其中 ω 1 \omega_{1} ω1 到 ω m ) \omega_{m}) ωm)依次为文本中的各个词语
P ( ω 1 , ω 2 , . . . , ω m ) = P ( ω 1 ) P ( ω 2 ∣ ω 1 ) P ( ω 3 ∣ ω 2 , ω 1 ) . . . P ( ω i ∣ ω i − 1 , . . . , ω 2 , ω 1 ) . . . P ( ω m ∣ ω m − 1 , . . . , ω 2 , ω 1 ) P(\omega_{1},\omega_{2}, ...,\omega_{m}) = P(\omega_{1})P(\omega_{2}|\omega_{1})P(\omega_{3}|\omega_{2},\omega_{1})...P(\omega_{i}|\omega_{i-1},...,\omega_{2},\omega_{1})...P(\omega_{m}|\omega_{m-1},...,\omega_{2},\omega_{1}) P(ω1,ω2,...,ωm)=P(ω1)P(ω2∣ω1