新词发现(一):基于统计
1. 什么是新词
现在大部分的分词工具已经做到了准确率高、粒度细,但是对于一些新词(new word)却不能做到很好地识别,比如:
快的打车优惠券
英雄联盟怎么不可以打排位
“快的”、“英雄联盟”应该被作为一个词,却被切成了两个词,失去了原有的语义。未登录词(out-of-vocabulary, OOV)笼统地之未在词典中出现的词,序列标注方法HMM与CRF可以根据上下文很好地识别未登录词,但是这种模型缺乏领域自适应能力 [1]:
模型对训练语料所在领域的语言现象处理可能表现出较好的性能,但一旦超出领域范围或测试集与训练样本有较大差异,模型性能将大幅度下降。例如,在标注的大规模《人民日报》分词语料上训练出来的汉语词语自动切分模型的准确率可达96%左右,甚至更高,但在微博等非规范文本基础上训练出的分词性能至少要低5个百分点左右。在LDC汉语树库上训练出来的句法分析系统准确率可达86%左右,但在非规范网络文本上的分析准确率只有60%左右(宗成庆 2013)。统计模型对领域自适应能力的缺乏严重制约了该方法的应用。
因此,对于“快的”、“英雄联盟”这样最近才出来的词未能识别。我们定义新词为具有基本词汇所没有的新形式、新语义的词语。新词可以视作一种特殊的未登录词;从分词的角度来看,新词一般表现为细粒度切分后相邻词的组合。
2. 基于统计的新词发现
算法
Matrix67提出了一种基于信息熵的新词发现算法[2],成词的标准有两个:
- 内部凝固度
- 自由运用程度
所谓内部凝固度,用来衡量词搭配(collocation)是否合理。比如,对于“的电影”、“电影院”这两个搭配,直观上讲“电影院”更为合理,即“电影”和“院”凝固得更紧一些。在计算语言学中,PMI (Pointwise mutual information)被用来度量词搭配与关联性,定义如下:
\[pmi(x,y) = \log \frac{P(x,y)}{P(x)P(y)} \]若PMI高,即两个词共现(co-occurrence)的频率远大于两个词自由拼接的乘积概率,则说明这两个词搭配更为合理一些。针对一个词有多种搭配组合,比如“电影院”可以由“电影”+“院”构成,也可以由“电”+“影院”构成,Matrix67取其所有pmi最小值(去掉log)作为内部凝固度:
\[solid(c_1^m) = \min \frac{P(c_1^m)}{\prod P(c_i^j)} = \frac{P(c_1^m)}{\max \prod P(c_i^j)} \]其中,\(c_1^m=c_1c_2\cdots c_m\)表示长度为\(m\)的字符串,\(P(c_1^m)\)表示词\(c_1^m\)的频率。
光看文本片段内部的凝合程度还不够,我们还需要从整体来看它在外部的表现。考虑“被子”和“辈子”这两个片段。我们可以说“买被子”、“盖被子”、“进被子”、“好被子”、“这被子”等,在“被子”前面加各种字;但“辈子”的用法却非常固定,除了“一辈子”、“这辈子”、“上辈子”、“下辈子”,基本上“辈子”前面不能加别的字了。“辈子”这个文本片段左边可以出现的字太有限,以至于直觉上我们可能会认为,“辈子”并不单独成词,真正成词的其实是“一辈子”、“这辈子”之类的整体。
所以,Matrix67提出了自由运用程度,用以衡量一个词的左邻字与右邻字的丰富程度。正好信息熵可以完美地诠释了这种丰富程度,熵越大则丰富程度越高。“被子”和“辈子”这两个片段的左邻字熵le
与右邻字熵re
分别如下:
le(被子) = 3.67453
re(被子) = 3.8740
le(辈子) = 1.25963
re(辈子) = 4.11644
可以看出,“被子”的左邻字熵与右邻字熵都较高,而“辈子”的左邻字熵较小,即左邻字非常贫乏。因此,“被子”较“辈子”更有可能成词。自由运用程度的定义如下:
\[free(c_1^m) = \min \{ le(c_1^m), \ re(c_1^m) \} \]给频数、内部凝固度与自由运用程度设定一个阈值,提取出来符合阈值的候选词,去掉词典中存在的词即为新词了。知乎在做反作弊挖掘新词时,也是采用的这个方法,据说取得了比较好的效果;具体细节参看专栏文章《反作弊基于左右信息熵和互信息的新词挖掘》。该新词发现算法已经有Java版实现dict_build,还有Python版实现ChineseWordSegmentation。
评估
为了评估该算法的效果,我采用的策略如下:首先用dict_build对语料进行统计分析得到一批候选词,然后使用Jieba对语料进行分词得到词表,最从候选词中筛出不在分词词表的作为新词,并按dict_build的成词概率进行排序。
import codecs
import jieba
seg_words = set()
candidate_words = []
with codecs.open(data_path, 'rb', 'utf-8') as fr:
for line in fr.readlines():
strs = line.strip().split('\t')
if len(strs) == 5:
candidate_words.append([strs[0], float(strs[4])])
candidate_words.sort(key=lambda t: t[1], reverse=True)
with codecs.open(file_path, 'rb', 'utf-8') as fr:
for content in fr.readlines():
words = jieba.cut(content.strip())
for word in words:
seg_words.add(''.join(word))
for wf in candidate_words:
if wf[0] not in seg_words:
print(wf)
《西游记》的Top 32新词如下:
睁睛 我徒弟 我师弟 老爷们 金光寺 金头揭谛 金圣娘娘 搀着
扯着 拖着 敲着 揪着 躧着 我师父 老师父 坐着
又遇 又吩咐 那袈裟 捧着 又到 打到 抱着 请师父
八戒沙僧 请唐僧 抬头见 又来 我佛如来 跳起来 走将来 请如来
采用京东商品标题的语料,Top 36新词如下:
粗跟短靴 粗跟女靴 粗跟过膝靴 欧式客厅 欧式餐厅 欧式卧室 欧美女靴 踩脚裤
踩脚打底裤 阿罗裤 踩脚保暖裤 踩脚一体裤 韩版休闲裤 韩版小脚裤 唐装男裤 欧式田园
韩式田园 戚风蛋糕 韩版松糕 剃须刀片 双层玻璃杯 双貔貅 双层水杯 陆战靴
抽纸盒 抽象客厅 钱貔貅 遮光卧室 遮光客厅 遮肚罩衫 白酒杯 白雪公主裙
紫砂杯 紫砂茶杯 紫砂品茗杯 紫砂杯茶杯
观察分析两组发掘的新词,总结如下:
- 一些新词是有意义、有价值的,比如:“睁睛”、“金光寺”、“金头揭谛”、“剃须刀片”、“紫砂杯”等;
- 存在部分新词应该被切分成两个词或多个词,比如:“我徒弟”、“那袈裟”、“唐装男裤”、“紫砂杯茶杯”等;
- 还有极少的新词完全是错误的、没有意义的,比如:“走将来”、“钱貔貅”等,为词的结尾与下一个词的拼接。
总体上来看,基于统计的新词发现算法是很强的baseline,具有普适性——对于不同领域的语料,能够发掘出我们想要的新词;但是同时也带来很多脏词。
3. 参考资料
[1] 宗成庆, 《语言战略研究》∣ 宗成庆:中文信息处理研究现状分析.
[2] Matrix67, 互联网时代的社会语言学:基于SNS的文本数据挖掘.