新词发现(2)——基于切分

还是参考了苏神的系列文章:【中文分词系列】 2. 基于切分的新词发现,感觉作者好厉害,用不同的角度思考问题。原先是从“从语料中判断是否成词即不可切分”的角度出发,现在却反过来思考:若一个片段的内部凝固度低于一定程度时,则这个片段是不是就不成词,因此要被切分开。

对比之前的那篇文章:【新词发现(1)——核心代码解读】,发现无需计算左邻与右邻信息熵,因此少了很多计算量。我们来看一下代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Find_Words:
def __init__(self, min_count=10, min_pmi=0):
self.min_count = min_count
self.min_pmi = min_pmi
self.chars, self.pairs = defaultdict(int), defaultdict(int) #如果键不存在,那么就用int函数初始化(默认为0)
self.total = 0.
#预切断句子,以免得到太多无意义(不是中文、英文、数字)的字符串
def text_filter(self, texts):
for a in tqdm(texts):
for t in re.split(u'[^\u4e00-\u9fa50-9a-zA-Z]+', a):
if t:
yield t

为了得到有意义的语句,需要去除非中文、英文与数字的字符,并在其位置进行断句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#计算单字出现频数、相邻两字出现的频数
def count(self, texts):
for text in self.text_filter(texts):
self.chars[text[0]] += 1
for i in range(len(text)-1):
self.chars[text[i+1]] += 1 #单字
self.pairs[text[i:i+2]] += 1 #相邻2字
self.total += 1
self.chars = {i:j for i,j in self.chars.items() if j >= self.min_count} #最少频数过滤
self.pairs = {i:j for i,j in self.pairs.items() if j >= self.min_count} #最少频数过滤
self.strong_segments = set()
#根据互信息找出比较“密切”的邻字
for i,j in self.pairs.items():
_ = log(self.total*j/(self.chars[i[0]]*self.chars[i[1]]))
if _ >= self.min_pmi:
self.strong_segments.add(i)

字典charspairs中存放单字符与相邻2字符的频数统计情况,并过滤掉出现次数过少的,再通过互信息的计算最后只筛选出“比较密切”的相邻字,同时存放到strong_segments字典中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#根据前述结果来找词语
def find_words(self, texts):
self.words = defaultdict(int)
for text in self.text_filter(texts):
s = text[0]
for i in range(len(text)-1):
#如果比较“密切”则不断开
if text[i:i+2] in self.strong_segments:
s += text[i+1]
else:
#否则断开,前述片段作为一个词来统计
self.words[s] += 1
s = text[i+1]
#最后再次根据频数过滤
self.words = {i:j for i,j in self.words.items() if j >= self.min_count}

这一步就是从之前筛选出的“比较密切”相邻字中,去判断一个片段是否成词。举个例子来说,比如现在有齐天大圣孙悟空,从齐天开始,在不在strong_segments字典中。假设存在,那么这么不切分,继续查找天大大圣在不在。诶,突然我们发现圣孙不在字典中,那么此处应该被切分,齐天大圣就被切分成了一个单独的词语。

1
2
3
4
5
6
7
8
9
10
11
12
f = open('天龙八部.txt', 'r', encoding='gbk', errors='ignore')
s1 = f.read()
texts = list([s1])
fw = Find_Words(20, 1)
fw.count(texts)
fw.find_words(texts)
import pandas as pd
words = pd.DataFrame(fw.words, index=[0]).T.reset_index()
words.columns = ['wrd','cnt']
words = words[words.wrd.apply(len)>1].sort_values('cnt', ascending=False)

最后我们还是在金庸的《天龙八部》上检验下效果