基于统计学的的分词

最大匹配分词方法只考虑词有没有在字典中出现,没有考虑词在词典中的频率。

基于统计学的分词即考虑词在词典中的频率的一种简单的分词方法。

注:下面的代码参考snownlp作者的博客http://www.isnowfy.com/python-chinese-segmentation/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import math
import codecs

d = {}
log = lambda x: float('-inf') if not x else math.log(x)
prob = lambda x: d[x] if x in d else 0 if len(x)>1 else 1 # 计算每个词的频次(加入平滑)

def init(filename='SogouLabDic.dic.utf8'):
"""读取字典"""
d['_t_'] = 0.0
with open(filename, "r") as f:
for line in f:
word, freq = line.split('\t')[:2]
d['_t_'] += int(freq)+1
d[word] = int(freq)+1

init()

小插曲:

搜狗提供的这个词典本身是GB2312编码的,我使用file SogouLabDic.dic显示的却是ISO-8859,以至于写代码的时候绕了很大的弯子。

后面在网上找到enca这个工具,可以更精准地识别编码。

  • 先安装:sudo apt-get install enca
  • 智能识别编码:enca -L zh_CN SogouLabDic.dic,输出:Simplified Chinese National Standard; GB2312
  • 转换编码:enca -L zh_CN -x utf-8 <SogouLabDic.dic> SogouLabDic.dic.utf8。 另,词典的格式是:词 词频 词性
1
list(d.items())[:10]
1
2
3
4
5
6
7
8
9
10
[('_t_', 301869553991.0),
('一个', 818357167),
('我们', 770027798),
('时间', 767969295),
('中国', 727787726),
('可以', 685520166),
('公司', 640938771),
('没有', 632008127),
('信息', 622926082),
('下载', 591771675)]

对于一个中文字符串:$a_1a_2…a_n$如何正确的用词:$c_1,c_2…c_m$表示就是中文分词的任务,也就是说我们要去找寻$P(c_1c_2..c_m)$最大的分词。

假设词跟词之间互相独立,转换成求$P(c_1)×P(c_2)×…×P(c_m)$最大,概率即这个词在词典中的频数除以所有词的频数之和(加入平滑)。

加入log,求$logP(c_1)+logP(c_2)+…+logP(c_m)$最大。

使用动态规划(DP)求解这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def solve(s):
l = len(s)
# sum_p记录从第i个位置开始sum(logP)最大值
sum_log_p = [0 for i in range(l + 1)]
# word_len记录第i个位置的词的长度
word_len = [0 for i in range(l)]
for i in range(l-1, -1, -1):
sum_log_p[i], word_len[i] = max((log(prob(s[i: i+k]) / d['_t_']) + sum_log_p[i+k], k)
for k in range(1, l-i+1))
# print(sum_log_p)
# print(word_len)
start_index = 0
while start_index < l:
yield s[start_index: start_index + word_len[start_index]]
start_index += word_len[start_index]

s = u'其中最简单的就是最大匹配的中文分词'
print(' '.join(list(solve(s))))
1
其中 最简单 的 就是 最大 匹配 的 中文 分词