目录
最近HanLP希望支持拼音与繁体功能,所以学习了几个开源的Java实现,优化后集成进来。
开源项目地址:https://github.com/hankcs/HanLP
/jpinyin
原理
这是GitHub上星星最多一个,主要原理就是利用一张HashTable将字与拼音一一对应起来。同时,在扫描的时候也会将当前汉字依次与后面的3个、2个、1个汉字组合,判断下是否存在多音字词组。也就是说,它最多支持4字词的多音字校正。同时,顺序扫描并且组合的话,复杂度的常数项有点高(大约是O(4n))。再乘上哈希表的复杂度,就是一个愚蠢、低效的实现。
词典格式
jpinyin中一共有3个表,分别是:
chinese.db 简繁词表
一共大约两千个汉字的简繁对应:
事实上,汉字的简繁对应并非严格的一对一,比如“皇后”的繁体应该是“皇后”,而“以后”的繁体应该是“以後”,两者并不相同。季先生着重谈到当年简化汉字时,也说把“皇后”的后与“以后”的“后”弄成一个字是遗憾。
所以这个词典就是个垃圾。
pinyin.db 汉字读音表
一共大约两万个汉字与它们的读音,支持多音字:
mutil_pinyin.db 多音词组
有些词语中的某个字读音与常用读音不同,比如鸭绿江。一共大约八百个:
这些词典都是以zip形式的Property储存的,而Property其实就是一个HashMap,所以这些词典可以视作哈希表。
算法
算法没什么可说的,基本步骤是:
-
先统统以字为单位转为简体
-
从前往后扫描,先尝试多音字识别处理(将当前汉字依次与后面的3个、2个、1个汉字组合,判断下是否存在多音字词组),如果没有查到多音词组,则以字为单位查询读音,取第一个(也就是说多音字并没有利用到)。
评价
个人评价极低,不支持简繁体分歧,在多音字的处理上没有用到更高效的算法,乏善可陈,渣渣。
/nlp-lang
这个项目是一个基本包,封装了大多数nlp项目中常用工具,其中就有简繁转换与拼音模块。
词典格式
fan2jian.dic 简繁体分歧词典
这是一个繁体到简体的词典,词汇量大约在五千左右,但是包含了一些汉字的简繁对照,所以真实词汇量会小很多:
pinyin.dic 拼音字词词典
这是一个汉字与词语到拼音的词典,词汇量在20万左右,同样包含汉字的拼音,所以真实词汇量会小很多:
算法
算法与词典在内存中的数据结构有很大关系,ansj这次使用了二分trie树来储存这些词典。
简繁转换算法
使用了二分trie树的前缀查询算法,比Hash表高效。关于二分trie树的更多讲解,请参考:《Trie树分词》。
值得注意的是,这里的繁转简词典是fan2jian.dic所示,简转繁词典则是fan2jian.dic的前后两个词串逆转过来合成的,这样做很聪明。当然,会损失一些词语,比如:
乙太網 以太网 乙太網路 以太网
不过,ansj的词典还是弱了一点,把“皇后”转成了“皇後”。
拼音算法
拼音词典,储存采用了一个叫做SmartForest的结构。
/** * 一个小树,和Forest的区别是.这个在首字也是用二分查找,做过一次优化.达到到达一定量级自动扩展为hash定位 在ansj分词中这个应用是在自适应分词 * * @author ansj */ public class SmartForest<T> implements Comparable<SmartForest<T>>
SmartForest依然是一棵trie树,只不过,当如果数组内元素接近于最大值直接数组定位(Forest则永远是二分定位):
添加:
// 如果数组内元素接近于最大值直接数组定位,rate是内存和速度的一个平衡 if (branches != null && branches.length >= MAX_SIZE * rate) { SmartForest<T>[] tempBranches = new SmartForest[MAX_SIZE]; for (SmartForest<T> b : branches) { tempBranches[b.getC()] = b; } tempBranches[branch.getC()] = branch; branches = null; branches = tempBranches; } else { SmartForest<T>[] newBranches = new SmartForest[branches.length + 1]; int insert = -(bs + 1); System.arraycopy(this.branches, 0, newBranches, 0, insert); System.arraycopy(branches, insert, newBranches, insert + 1, branches.length - insert); newBranches[insert] = branch; this.branches = newBranches; }
查找:
public int get(char c) { if (branches == null) return -1; if (branches.length == MAX_SIZE) { return c; } int i = Arrays.binarySearch(this.branches, new SmartForest<T>(c)); return i; }
其他的并没有特别的,依然是《Trie树分词》的那一套逻辑。
评价
算法给好评,词典给中评。
/chinese-utils
这是一套名不见经传的类库,作者在介绍中说“中文相关工具包,目前提供中文简繁体互转,以及中文转拼音。未来会提供中文分词。”,不清楚是否会履行诺言。
词典格式
pinyin.txt 汉字拼音字典
这是单个汉字与拼音的对照词典,大约有两万个常用与罕见的汉字:
polyphone.txt 多音词词典
与jpinyin类似,是异读词的集合,大约有一万词汇量:
非常全面,像这个“鱼丽于罶”还是第一次见到,我读书少,你们不要骗我。
unknown.txt 未知读音的字的词典(这个名字好长我自己起的)
一些奇怪的汉字,可能是韩国或日本的汉字:
simp.txt trad.txt 简繁汉字对应词典
两个词典合起来就是简繁汉字对应词典了,作者把它们拆开了。
simplified.txt 繁简分歧词表
##### 繁简分歧词表 ##### # 计算机 印表機=打印机 記憶體=内存 乙太網=以太网 乙太網路=以太网 游標=光标 光碟=光盘 光碟機=光驱 軟碟機=软驱 匯流排=总线 碟片=盘片 硬體=硬件 硬碟=硬盘 磁碟=磁盘 磁軌=磁道 通信埠=端口 連接埠=端口 介面=接口 運算元=算子 演算法=算法traditional.txt 简繁分歧词表
##### 简繁分歧词表 ##### # 计算机 打印机=印表機 内存=記憶體 以太网=乙太網 光标=游標 光盘=光碟 光驱=光碟機 软驱=軟碟機 总线=匯流排 盘片=碟片 硬件=硬體 硅谷=矽谷 硬盘=硬碟 磁盘=磁碟 磁道=磁軌 端口=通信埠 接口=介面 算子=運算元 算法=演算法 芯片=晶片
算法
作者实现了一棵基于哈希表的trie树,速度估计勉勉强强,内存估计够呛。
简繁转换
依然是《Trie树分词》的那一套逻辑,先从分歧词表查,查不到再从单字简繁对照表中查。
汉字转拼音
与简繁转换的算法相同,先从多音词词表中查,查不到的话从单字读音表中查。
评价
算法还行吧,词典特别棒,有“皇后”这种分歧词。算法给4颗星,词典给好评。
HanLP
正在集百家之长,准备用trie树和chinese-utils的词典做一个无限趋近完美的类库!
简繁转换
HanLP.Config.enableDebug(); System.out.println(HanLP.convertToSimplifiedChinese("「以後等妳當上皇后,就能買士多啤梨慶祝了」")); System.out.println(HanLP.convertToTraditionalChinese("“以后等你当上皇后,就能买草莓庆祝了”"));
输出
汉字转拼音
HanLP不仅支持基础的汉字转拼音,还支持声母、韵母、音调、音标和输入法首字母与首声母功能。HanLP能够识别多音字,也能给繁体中文注拼音。最重要的是,HanLP采用了双数组trie树和压缩的词典格式(现在HanLP已经升级到Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配,性能大幅提升!),能够提供毫秒级的响应速度!
String text = "重载不是重担," + HanLP.convertToTraditionalChinese("以后爱皇后"); List<Pinyin> pinyinList = PinyinDictionary.convertToPinyin(text); System.out.print("原文,"); for (char c : text.toCharArray()) { System.out.printf("%c,", c); } System.out.println(); System.out.print("拼音(数字音调),"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin); } System.out.println(); System.out.print("拼音(符号音调),"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin.getPinyinWithToneMark()); } System.out.println(); System.out.print("拼音(无音调),"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin.getPinyinWithoutTone()); } System.out.println(); System.out.print("声调,"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin.getTone()); } System.out.println(); System.out.print("声母,"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin.getShengmu()); } System.out.println(); System.out.print("韵母,"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin.getYunmu()); } System.out.println(); System.out.print("输入法头,"); for (Pinyin pinyin : pinyinList) { System.out.printf("%s,", pinyin.getHead()); } System.out.println();
输出
原文 | 重 | 载 | 不 | 是 | 重 | 担 | , | 以 | 後 | 愛 | 皇 | 后 |
拼音(数字音调) | chong2 | zai3 | bu4 | shi4 | zhong4 | dan1 | none5 | yi3 | hou4 | ai4 | huang2 | hou4 |
拼音(符号音调) | chóng | zăi | bù | shì | zhòng | dān | none | yĭ | hòu | ài | huáng | hòu |
拼音(无音调) | chong | zai | bu | shi | zhong | dan | none | yi | hou | ai | huang | hou |
声调 | 2 | 3 | 4 | 4 | 4 | 1 | 5 | 3 | 4 | 4 | 2 | 4 |
声母 | ch | z | b | sh | zh | d | none | y | h | none | h | h |
韵母 | ong | ai | u | i | ong | an | none | i | ou | ai | uang | ou |
输入法头 | ch | z | b | sh | zh | d | none | y | h | a | h | h |
如果上表有些字符显示异常,请看下图:
或者:
开源项目
本文代码已集成到HanLP中开源:http://www.hankcs.com/nlp/hanlp.html