赞
踩
上文中提到的发现新词的方法默认认为:文本的相关性仅有相邻两字(2-grams
)来决定,这在很多时候是不合理的。比如“林心如”中的“心如”、“共和国”中的“和国”,凝固度(相关性)都不是很强,容易错切。因此,本文就是在前文的基础上改进。
上文只考虑了相邻字的凝固度,这里同时考虑多字的内部的凝固度(n-grams
),比如,定义三字的字符串内部凝固度为:
m
i
n
P
(
a
b
c
)
P
(
a
b
)
P
(
c
)
P
(
a
b
c
)
P
(
a
)
P
(
b
c
)
min{\frac{P(abc)}{P(ab)P(c)}\frac{P(abc)}{P(a)P(bc)}}
minP(ab)P(c)P(abc)P(a)P(bc)P(abc)
这个定义其实也就是说,要枚举所有可能的切法,因为一个词应该是处处都很“结实”的。4字或以上的字符串凝固度类似定义。一般地,我们只需要考虑到4字(4-grams
)就好(但是注意,我们依旧是可以切出4字以上的词来的)。
考虑了多字后,我们可以设置比较高的凝固度阈值,同时防止诸如“共和国”之类的词不会被切错,因为考虑三字凝固度,“共和国”就显得相当结实了,所以,这一步就是“宁放过,勿切错”的原则。
但是,“各项”和“项目”这两个词,它们的内部凝固度都很大,因为前面一步是“宁放过,勿切错”,因此这样会导致“各项目”也成词,类似的例子还有“支撑着”、“球队员”、“珠海港”等很多例子。但这些案例在3grams
中来看,凝固度是很低的,所以,我们要有一个“回溯”的过程,在前述步骤得到词表后,再过滤一遍词表,过滤的规则就是,如果里边的n
字词,不在原来的高凝固度的ngrams
中,那么就得“出局”。
所以,考虑ngrams
的好处就是,可以较大的互信息阈值情况下,不错切词,同时又排除模凌两可的词。就比如“共和国”,三字互信息很强,两字就很弱了(主要还是因为“和国”不够结实),但是又能保证像“的情况”这种不会被切出来,因为阈值大一点,“的情”和“的情况”都不结实了。
完整的算法步骤如下:
n
,统计2grams
、3grams
、…、ngrams
,计算它们的内部凝固度,只保留高于某个阈值的片段,构成一个集合G
;这一步,可以为2grams、3grams、…、ngrams设置不同的阈值,不一定要相同,因为字数越大,一般来说统计就越不充分,越有可能偏高,所以字数越大,阈值要越高;grams
对语料进行切分(粗糙的分词),并统计频率。切分的规则是,只要一个片段出现在前一步得到的集合G
中,这个片段就不切分,比如“各项目”,只要“各项”和“项目”都在G
中,这时候就算“各项目”不在G
中,那么“各项目”还是不切分,保留下来;n
字的词,那么检测它在不在G
中,不在就出局;如果它是一个大于n
字的词,那个检测它每个n
字片段是不是在G
中,只要有一个片段不在,就出局。还是以“各项目”为例,回溯就是看看,“各项目”在不在3grams
中,不在的话,就得出局。每一步的补充说明:
- 使用较高的凝固度,但综合考虑多字,是为了更准,比如两字的“共和”不会出现在高凝固度集合中,所以会切开(比如“我一共和三个人去玩”,“共和”就切开了),但三字“共和国”出现在高凝固度集合中,所以“中华人民共和国”的“共和”不会切开;
- 第二步就是根据第一步筛选出来的集合,对句子进行切分(你可以理解为粗糙的分词),然后把“粗糙的分词结果”做统计,注意现在是统计分词结果,跟第一步的凝固度集合筛选没有交集,我们认为虽然这样的分词比较粗糙,但高频的部分还是靠谱的,所以筛选出高频部分;
- 第三步,例如因为“各项”和“项目”都出现高凝固度的片段中,所以第二步我们也不会把“各项目”切开,但我们不希望“各项目”成词,因为“各”跟“项目”的凝固度不高(“各”跟“项”的凝固度高,不代表“各”跟“项目”的凝固度高),所以通过回溯,把“各项目”移除(只需要看一下“各项目”在不在原来统计的高凝固度集合中即可,所以这步计算量是很小的)
本文针对一万多条商品名称语料来进行实验,数据格式如下:
#!usr/bin/env python # -*- coding:utf-8 -*- """ @author: admin @file: main.py @time: 2022/09/09 @desc: 算法步骤: 1.单词计数,并计算内部凝固度,保留高于某个阈值的片段,并保存到集合ngrams_ 2.用ngrams_对语料进行粗糙分词,生成初始词典word;切分原则是宁放过勿切错 3.对词典word进行回溯,原则是如果它是一个小于等于n字的词,那么检测它在不在G中,不在就出局; 如果它是一个大于n字的词,那个检测它每个n字片段是不是在G中,只要有一个片段不在,就出局 """ import re import codecs from tqdm import tqdm import hashlib input_data = codecs.open("data/file_corpus.txt", 'r', encoding="utf-8") # TODO:1.写迭代器输出文章 def texts(): texts_set = set() # 引入tqdm是为了显示进度 for a in tqdm(input_data): if a.encode('utf-8') in texts_set: continue else: texts_set.add(a.encode('utf-8')) # 引入正则表达式re是为了预先去掉无意义字符(非中文、非英文、非数字) for t in re.split(u'[^\u4e00-\u9fa50-9a-zA-Z]+', a): if t: yield t print(u'最终计算了%s个商品名称' % len(texts_set)) # TODO:2.单词计数,分别为1-4grams,即算法步骤中的第一步统计 import numpy as np from collections import defaultdict n = 4 # 最长片段的子树 min_count = 3 # 片段阈值 ngrams = defaultdict(int) for t in texts(): for i in range(len(t)): for j in range(1, n + 1): if i + j <= len(t): ngrams[t[i:i + j]] += 1 ngrams = {i: j for i, j in ngrams.items() if j >= min_count} total = 1. * sum([j for i, j in ngrams.items() if len(i) == 1]) # 总字数 # 计算它们的内部凝固度,只保留高于某个阈值的片段,构成一个集合ngrams_ min_proba = {2: 5, 3: 25, 4: 125} def is_keep(s, min_proba): if len(s) >= 2: score = min([total * ngrams[s] / (ngrams[s[:i + 1]] * ngrams[s[i + 1:]]) for i in range(len(s) - 1)]) if score > min_proba[len(s)]: return True else: return False ngrams_ = set(i for i, j in ngrams.items() if is_keep(i, min_proba)) # 此时,input_data文件中的指针在最后,需要先关闭文件,再重新打开 input_data.close() input_data = codecs.open("data/file_corpus.txt", 'r', encoding="utf-8") # TODO:3.用上述ngrams_对语料进行切分(粗糙的分词),并统计频率;即算法步骤中的第二步:切分 # 宁放过,不切错 def cut(s): r = np.array([0] * (len(s) - 1)) for i in range(len(s) - 1): for j in range(2, n + 1): a = s[i:i + j] if a in ngrams_: r[i:i + j - 1] += 1 w = [s[0]] for i in range(1, len(s)): # 切分的规则是,只要一个片段出现在前一步得到的集合G中,这个片段就不切分 # 宁放过,不切错 if r[i - 1] > 0: w[-1] += s[i] else: w.append(s[i]) return w words = defaultdict(int) for t in texts(): for i in cut(t): words[i] += 1 words = {i: j for i, j in words.items() if j >= min_count} print(len(words)) # TODO:4.回溯, # 回溯就是检查,如果它是一个小于等于n字的词,那么检测它在不在G中,不在就出局; # 如果它是一个大于n字的词,那个检测它每个n字片段是不是在G中,只要有一个片段不在,就出局 def is_real(s): if len(s) >= 3: for i in range(3, n + 1): for j in range(len(s) - i + 1): # 检测它每个n字片段是不是在G中 if s[j:j + i] not in ngrams_: return False return True else: return True w = {i: j for i, j in words.items() if is_real(i) and len(i) > 1} print(len(w)) # 将输出字典写入文件中 output = codecs.open("output.txt", "w", encoding="utf-8") for i, j in sorted(w.items(), key=itemgetter(1), reverse=True): output.write(str(i) + "\t" + str(j) + "\n") output.close()
16762it [00:01, 11819.13it/s]
最终计算了16762个商品名称
16762it [00:02, 6871.14it/s]
最终计算了16762个商品名称
13438
10539
参考于
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。