徐慧志的个人博客

2022-08-02 用 HanLP 分词时如何自定义词典

发布于 2022年08月02日  (最近编辑 2022年10月14日 )
9 分钟  • 4456 字

在分词的过程中,碰到一个这样的句子:

公司产品品质持续提升,单晶硅片用料比例大幅高于行业平均,单晶硅料价格上涨。

import hanlp

tok = hanlp.load(hanlp.pretrained.tok.COARSE_ELECTRA_SMALL_ZH)
sentence = '公司产品品质持续提升,单晶硅片用料比例大幅高于行业平均,单晶硅料价格上涨。'
sen_list = tok(sentence)
print(sen_list)
['公司', '产品', '品质', '持续', '提升', ',', '单晶', '硅', '片', '用', '料', '比例', '大幅', '高于', '行业', '平均', ',', '单晶', '硅', '料', '价格', '上涨', '。']

可以看出来,这里“单晶硅片”,“单晶硅料”, 被分为了“单晶”“硅”“料”和“单晶”“硅”“片”。

如果我们想要把“单晶硅”分出来。可以设置自定义词典。tok下面有两个参数:dict_force和dict_combine,通过设置这两个参数就可以达到自定义词典的效果。

dict_force和dict_combine有什么区别:

dict_force是强制模式,强制模式的优先级高于统计模型。如果强制模式用于所有文本,会对其他句子进行干扰,所以强制模式一般不用于所有文本,但是可以针对某个特定句子打补丁。

dict_combine是合并模式,合并模式的优先级低于统计模型。就是说句子先用统计模型分词,然后在这个分词的基础上,再进行最长匹配并合并。

先看一下dict_combine的例子:

tok.dict_force = None
tok.dict_combine = {'单晶硅'}
sentence = '公司产品品质持续提升,单晶硅片用料比例大幅高于行业平均,单晶硅料价格上涨。'
['公司', '产品', '品质', '持续', '提升', ',', '单晶硅', '片', '用', '料', '比例', '大幅', '高于', '行业', '平均', ',', '单晶硅', '料', '价格', '上涨', '。']

我们一般会用dict_combine,这样就把“单晶硅”分出来了。

如果在dict_combine里面,同时有’单晶硅片’,‘单晶硅’,这两者都能被分出来。

tok.dict_force = None
tok.dict_combine = {'单晶硅片','单晶硅'}
['公司', '产品', '品质', '持续', '提升', ',', '单晶硅片', '用', '料', '比例', '大幅', '高于', '行业', '平均', ',', '单晶硅', '料', '价格', '上涨', '。']

可以看出,dict_combine的原理是在这个的基础上['公司', '产品', '品质', '持续', '提升', ',', '单晶', '硅', '片', '用', '料', '比例', '大幅', '高于', '行业', '平均', ',', '单晶', '硅', '料', '价格', '上涨', '。'] 进行最长匹配,再合并。所以一个被合并成了”单晶硅片”,一个合并成”单晶硅”。为什么不是链各个“单晶硅”,因为“单晶硅片”的长度大于“单晶硅”,按最长的匹配合并。

同理,如果dict_combine里面是“单晶硅”和“硅料”,那么硅料将不会被分出来。

但是自定义词典不是永远都有效的。HANLP这里有说明,在自定义词典下,分词是结合了统计模型和自定义词典之后的分词,并不是完全会按照我们的自定义词典来。

如果我们想把单晶硅片,硅料和但单晶硅都切分正确,如果把它们都放到dict_combine里面,硅料是不会被切分对的。

tok.dict_combine = {'硅料','单晶硅片' ,'单晶硅'}

sentence = '公司产品品质持续提升,单晶硅片用料比例大幅高于行业平均,单晶硅料价格上涨,单晶硅价格也上涨。'
['公司', '产品', '品质', '持续', '提升', ',', '单晶硅片', '用', '料', '比例', '大幅', '高于', '行业', '平均', ',', '单晶硅', '料', '价格', '上涨', ',', '单晶硅', '价格', '也', '上涨', '。']

这时,可以把硅料放在dict_force里面,这样就能同时把三者都分出来。

tok.dict_force = {'硅料'}
tok.dict_combine = {'单晶硅片','单晶硅'}
sentence = '公司产品品质持续提升,单晶硅片用料比例大幅高于行业平均,单晶硅料价格上涨,单晶硅价格也上涨。'
sen_list = tok(sentence)
print(sen_list)

['公司', '产品', '品质', '持续', '提升', ',', '单晶硅片', '用', '料', '比例', '大幅', '高于', '行业', '平均', ',', '单晶', '硅料', '价格', '上涨', ',', '单晶硅', '价格', '也', '上涨', '。']

如果不是这种情况,我们不要用dict_force,因为它极有可能影响其他地方。这是教程里面的一个例子。可以看出,后面这句分错了。

tok.dict_force = {'川普'}
word_list = tok(["首相和川普通电话", "银川普通人与川普通电话讲四川普通话"])
print(word_list)

[['首相', '和', '川普', '通电话'], ['银', '川普', '通人', '与', '川普', '通电话', '讲', '四', '川普', '通话']]

如果把‘普通人’也加到dict_force里面,为啥第二句还是会被分成“川普”呢?

tok.dict_combine = {'硅料','单晶硅'}
[['首相', '和', '川普', '通电话'], ['银', '川普', '通人', '与', '川普', '通电话', '讲', '四', '川普', '通话']]

原文是这样解释的:

即便是将普通人普通话 加入到词典中也无济于事,因为在正向最长匹配第二个句子的过程中,会匹配到川普 而不会匹配后两者。

我的理解是dict_combine不是按照最长匹配的,而是在发生冲突的时候,取了“川普”,舍弃了“普通人”。当然我不知道这里的取舍规则是怎么样的。总之在这种情况下我们可以给第一句话打个补丁。

tok.dict_force = {'川普通电话': ['川普', '通', '电话']}
tok(["首相和川普通电话", "银川普通人与川普通电话讲四川普通话"])

[['首相', '和', '川普', '通', '电话'],
 ['银川', '普通人', '与', '川普', '通', '电话', '讲', '四川', '普通话']]

打补丁的意思是说,针对特殊案例进行校正。这种补丁不存在泛化性,只有出现完全一样的字符片段才会按照dict_force里面分词。

tok.dict_force = {'川普通电话': ['川普', '通', '电话']}
word_list = tok(["首相和川普通电话", "银川普通人与川普通电话讲四川普通话", "四川普通人"])

[['首相', '和', '川普', '通', '电话'], ['银川', '普通人', '与', '川普', '通', '电话', '讲', '四川', '普通话'], ['四川', '普通人']]

总结一下:

用自定义词典时,一般使用dict_combine,注意dict_combine里面的会用最长匹配。

如果dict_combine里面的词有冲突,可以考虑把最短的词放在dict_force里面。

如果要用dict_force,最好是用打补丁的方式,注明在什么样的字符串才用dict_force里面的分词方式。

参考文章:https://github.com/hankcs/HanLP/blob/doc-zh/plugins/hanlp_demo/hanlp_demo/zh/tok_stl.ipynb

Sein heißt werden, leben heißt lernen.

Der einfache Weg is immer verkehrt.