首先说明,由于本人水平有限,文章纰漏以及不妥之处还请指出,不胜感激;
理解hanlp中用户自定义词典(java)
-
什么是hanlp用户自定义字典?为什么要有用户自定义词典?
在Hanlp分词中,不管是标准分词器、NLP分词器还是索引分词器都不可能每次都准确的将我们希望的结果词语分出来,尤其是我们平常用到的自定义名词或者流行语,比如:“印象笔记”、“奥利给”等,分析语句一长,分词准确性还会继续下降,这时就需要我们利用用户自定义词典,来进行这些词语的记录,方便分词器依据用户词典来进行分词,提高准确度。
-
用户自定义词典中的词在分词时一定会被准确无误的区分出来吗?
我们需要知道准确无误这样的形容词在形容分词这项工作就是不恰当的,在分词工作中,对分词结果评判的标准是不确定的,在官方FAQ中有这样的例子:
假如用户词典中一共3个词语:
商品
、和服
、服务
,那么“商品和服务
”到底怎么分才能满足这些用户呢?无论怎么分词,这3个词语都不可能同时出现。
也许你以为往词典中添加“川普”,并且用户词典优先级最高的话,就可以解决眼前的“
普京与川普
通话
”这个句子。但在你没注意到的地方,有更多类似“四川普通话
”“银川普通高考
”“唐纳德·川普
”(本该是一个词)的句子会发生错误。
所以说,词典≠中文分词。
在了解了上述两个问题之后,我们就开始进行用户自定义词典的分析了。
下载Hanlp数据包后,默认位置是在Hanlp配置目录下的data\dictionary\custom,CustomDictionary是一份全局的用户自定义词典,可以随时增删,影响全部分词器。另外可以在任何分词器中关闭它。下图是CustomDictionary在windows OS中的位置;
我们点开它,发现了一些词语,仔细观察我们会发现这些词的组织是有一定的规律的;
-
每一行代表一个单词,格式遵从[单词] [词性A] [A的频次] [词性B] [B的频次] … 如果不填词性则表示采用词典的默认词性。
-
词典的默认词性默认是名词n,可以通过配置文件修改:全国地名大全.txt ns;如果词典路径后面空格紧接着词性,则该词典默认是该词性。
我们打开Hanlp的配置文件,在箭头指向位置可以看到自定义词典的路径,词典名有后缀的就是设置了词条默认词性,默认名词;
词性表请参照《HanLP词性标注集》 -
在统计分词中,并不保证自定义词典中的词一定被切分出来。用户可在理解后果的情况下通过Segment#enableCustomDictionaryForcing强制生效。
我们在之前已经讨论过这个问题,如果你实在需要词典中的词被完整的分出来,可以使用此方法来开启强制分词。
我们也可以添加词典
-
CustomDictionary主词典文本路径是data/dictionary/custom/CustomDictionary.txt,用户可以在此增加自己的词语(不推荐);也可以单独新建一个文本文件,通过配置文件CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt; 我的词典.txt;来追加词典(推荐)。
缓存 -
始终建议将相同词性的词语放到同一个词典文件里,便于维护和分享。
特别注意:
①通过代码进行词语的增删并不会影响到词典;
②在词典进行更改后,必须删除词典缓存,在下一次载入词典时更改才会生效;
③词典缓存和词典在同一目录下,后缀一般是.bin。有时候是.trie.dat和.trie.value。它们是历史遗留产物;
现在是时候进入代码,仔细观察了。这是文档给出的一段代码:
/**1. 演示用户词典的动态增删2. 3. @author hankcs*/
public class DemoCustomDictionary
{public static void main(String[] args){// 动态增加CustomDictionary.add("攻城狮");// 强行插入CustomDictionary.insert("白富美", "nz 1024");// 删除词语(注释掉试试)
// CustomDictionary.remove("攻城狮");System.out.println(CustomDictionary.add("单身狗", "nz 1024 n 1"));System.out.println(CustomDictionary.get("单身狗"));String text = "攻城狮逆袭单身狗,迎娶白富美,走上人生巅峰"; // 怎么可能噗哈哈!// AhoCorasickDoubleArrayTrie自动机扫描文本中出现的自定义词语final char[] charArray = text.toCharArray();CustomDictionary.parseText(charArray, new AhoCorasickDoubleArrayTrie.IHit<CoreDictionary.Attribute>(){@Overridepublic void hit(int begin, int end, CoreDictionary.Attribute value){System.out.printf("[%d:%d]=%s %s\n", begin, end, new String(charArray, begin, end - begin), value);}});// 自定义词典在所有分词器中都有效System.out.println(HanLP.segment(text));}
}
我们通过源代码查看一下选中的几个方法:
add(String) : boolean
/*** 往自定义词典中插入一个新词(非覆盖模式)<br>* 动态增删不会持久化到词典文件** @param word 新词 如“裸婚”* @return 是否插入成功(失败的原因可能是不覆盖等,可以通过调试模式了解原因)*/public static boolean add(String word){if (HanLP.Config.Normalization) word = CharTable.convert(word);if (contains(word)) return false;return insert(word, null);}
insert(String) : boolean
/*** 以覆盖模式增加新词<br>* 动态增删不会持久化到词典文件** @param word* @return*/public static boolean insert(String word){return insert(word, null);}
get(String) : Attribute
/*** 查单词** @param key* @return*/public static CoreDictionary.Attribute get(String key){if (HanLP.Config.Normalization) key = CharTable.convert(key);CoreDictionary.Attribute attribute = dat.get(key);if (attribute != null) return attribute;if (trie == null) return null;return trie.get(key);}
remove(String) : void
/*** 删除单词<br>* 动态增删不会持久化到词典文件** @param key*/public static void remove(String key){if (HanLP.Config.Normalization) key = CharTable.convert(key);if (trie == null) return;trie.remove(key);}
源代码正在研究,考完六级再来补…