赞
踩
前言
这篇将使用Java实现基于规则的中文分词算法,一个中文词典将实现准确率高达85%的分词结果。使用经典算法:正向最大匹配和反向最大匹配算法,然后双剑合璧,双向最大匹配。
根据相关资料,中文分词概念的理论描述,我总结如下:
中文分词是将一个汉字序列切分成一个一个单独的词,将连续的字序列按照一定的规范重新组合成词序列的过程,把字与字连在一起的汉语句子分成若干个相互独立、完整、正确的单词,词是最小的、能独立活动的、有意义的语言成分。
中文分词应用广泛,是文本挖掘的基础,在中文文本处理中意义重大,对于输入的一段中文,成功的进行中文分词,可以达到电脑自动识别语句含义的效果。目前,常用流行的以及分次效果好的工具库包括:jieba、HanLP、LTP、FudanNLP等。
所谓正向,就是从文本串左边正向扫描,取出子串与词典进行匹配。
算法思想描述:
假设初始化取最大匹配长度为MaxLen,当前位置startPosition=0,处理结果result,每次取词subStr,取词长度len,待处理串srcStr。
len = MaxLen
,取字符串0到len的子串,查找词典,若匹配到则赋值加到result,在保证pos + len <= segstr.length()
情况下,pos = pos + len
,向后匹配,直到字符串扫描完成,结束算法。若
词典未找到
且len>1
,减小匹配长度len -= 1
【减小匹配长度的步长可以根据实际场景进行设置】,继续进行匹配,否则,取出剩余子串,执行匹配流程。
正向最大匹配算法代码如下:
public static void main(String[] args){ String result = MM("我爱自然语言处理", MaxLen, 0); System.out.println("分词结果:" + result); } private static final int MaxLen = 5; // 最大词长度 private static StringBuffer result = new StringBuffer(); // 分词结果 private static Map<String, String> dict = new HashMap<>(); // 词典 // 模拟词典数据 static{ dict.put("我", ""); dict.put("爱", ""); dict.put("自然", ""); dict.put("语言", ""); dict.put("处理", ""); } public static String MM(String srcStr, int len, int startPosition) { // 越界判断 if (startPosition + 1 > srcStr.length()) { return result.toString(); } if (startPosition < 0) { return result.toString(); } String subStr = ""; //此处可以设置断点 int llen = srcStr.length() - startPosition; if (llen <= len) {//句末边界处理 subStr = srcStr.substring(startPosition, startPosition + llen); } else { subStr = srcStr.substring(startPosition, startPosition + len); } if (dict.containsKey(subStr)) { result.append(subStr).append("/ "); len = MaxLen; MM(srcStr, len, startPosition + subStr.length()); } else { if (len > 1) { len -= 1; } else { result.append(srcStr).append("/ "); startPosition = startPosition + 1; len = MaxLen; } MM(srcStr, len, startPosition); } return result.toString(); }
从算法代码看出,很容易理解,细节部分在于边界处理。
运行结果:
反向,则与正向相反,从文本串末向左进行扫描。
算法思想描述:
假设初始化取最大匹配长度为MaxLen,当前位置pos为字符串尾部,处理结果result,每次取词subStr,取词长度len,待处理串srcStr。
len=MaxLen,取字符串pos-len到pos的子串,查找词典,若匹配到则赋值加到result,同时pos=pos-len,保证pos-len>=0,向前移动匹配,直到字符串扫描完成,结束算法。
若词典未找到,若len>1,减小匹配长度,执行分词匹配,否则,取出剩余子串,执行步分词匹配。
算法逻辑与正向最大匹配算法类似,取相反方向处理。
代码实现
private static final int MaxLen = 5; // 最大词长度 private static StringBuffer result = new StringBuffer(); // 分词结果 private static Map<String, String> dict = new HashMap<>(); // 词典 static { dict.put("我", ""); dict.put("爱", ""); dict.put("自然", ""); dict.put("语言", ""); dict.put("处理", ""); } public static void main(String[] args){ String rres = RMM("我爱自然语言处理", MaxLen, 7); System.out.println("反向分词结果:" + rres); } public static String RMM(String srcStr, int len, int startPosition) { if (startPosition < 0) return result.toString(); if (startPosition > srcStr.length() + 1) { return result.toString(); } String subStr = ""; //此处可以设置断点 if (startPosition - len + 1 >= 0)//句末边界处理 subStr = srcStr.substring(startPosition - len + 1, startPosition + 1);//substring获取的子串是下标frompos~frompos+llen-1 else subStr = srcStr.substring(0, startPosition + 1);//到达句首 if (dict.containsKey(subStr)) { result.insert(0, subStr + "/ "); len = MaxLen; RMM(srcStr, len, startPosition - subStr.length()); } else { if (len > 1) { len = len - 1; } else { result.insert(0, srcStr + "/ "); startPosition = startPosition - 1; len = MaxLen; } RMM(srcStr, len, startPosition); } return result.toString(); }
执行结果:
这里所说的是正向与反向结合,实现双向最大匹配。
双向最大匹配算法,基于正向、反向最大匹配,对分词结果进一步处理,比较两个结果,做的工作就是遵循某些原则和经验,筛选出两者中更确切地分词结果。
原则如下:
具体也需要看开始给定的最大匹配长度为多少。以下代码只实现了原则1、2。
代码示例:
// 这里只是示例代码,思路比较简单 public String BMM() { String Mr = MM("我爱自然语言处理", MaxLen, 0); String RMr = RMM("我爱自然语言处理", MaxLen, 7); if (Mr.equals(RMr)) { return "双向匹配相同,结果为:" + Mr; } else { List<String> MStr; List<String> RStr; MStr = Arrays.asList(Mr.trim().split("/")); RStr = Arrays.asList(RMr.trim().split("/")); if (MStr.size() >= RStr.size()) {//多数情况下,反向匹配正确率更高 return "双向匹配不同,最佳结果为:" + RMr; } else return "双向匹配不同,最佳结果为:" + Mr; } }
下面举例,以便更好的理解算法执行过程。
- 正向最大匹配算法:
取MaxLen=3,SegStr=”对外经济技术合作与交流不断扩大”,maxNum=3,len=3,result=””,pos=0,curstr=””.
第一次,curstr=”对外经”,查找词典,未找到,将len-1,得到curstr=”对外”,此时匹配到词典,将结果加入result=”对外/”.pos=pos+len.
第二次,curstr=”经济技”,查找词典,未找到,将len-1,得到curstr=”经济”,此时匹配到词典,将结果加入result=”对外/ 经济/ ”.pos=pos+len.
以此类推…
最后一次,边界,pos=13,因为只剩下”扩大”两个字,所以取出全部,查找词典并匹配到,将结果加入result=”对外/ 经济/ 技术/合作/ 与/ 交流/ 不断/ 扩大/ ”.此时pos+1>SegStr.length(),结束算法。
- 反向最大匹配算法:
取MaxLen=3,SegStr=”对外经济技术合作与交流不断扩大”,maxNum=3,len=3,result=””,pos=14,curstr=””.
第一次,curstr=”断扩大”,查找词典,未找到,将len-1,得到curstr=”扩大”,此时匹配到词典,将结果加入result=”扩大/
”.pos=pos-len.第二次,MaxLen=3,curstr=”流不断”,查找词典,未找到,将len-1,得到curstr=”不断”,此时匹配到词典,将结果加入result=”不断/ 扩大/ ”.pos=pos-len.
以此类推…
最后一次,边界,pos=1,因为只剩下”对外”两个字,所以取出全部,查找词典并匹配到,将结果加入result=”对外/ 经济/ 技术/ 合作/ 与/ 交流/ 不断/ 扩大/ ”.此时pos-1<0,结束算法。
首先,分词的算法大致分为两种:
1.基于词典的分词算法
正向最大匹配算法
逆向最大匹配算法
双向匹配分词法
2.基于统计的机器学习算法
HMM、CRF、SVM、LSTM+CRF
这里列出一些开源的分词系统【分词器】:
盘古分词、结巴分词、海量分词、Lucene、Solr等等
分词算法实现,不可缺少的就是词典,这里给大家推荐几个词典源。
我个人对Lucene 框架还是比较喜欢的,开源软件DocFetcher也是基于此框架实现的全文检索工具。值得推荐。只是目前DocFetcher使用的还是Lucene6,目前Lucene已经是版本8了,所以就将DocFetcher升级了一下,由于DocFetcher的开源协议是EPL,所以就不方便给大家提供修改后的源码和编译出的工具了。见谅。
不过倒是可以给大家一些修改建议:
到此结束,谢谢观看~
注:双向匹配部分借鉴了目前别的博主写的内容,本人就是简单整理了一下,让代码可以真正的运行起来。方便大家直接运行调试,分析总结。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。