中文分词引擎 java 实现 — 正向最大、逆向最大、双向最大匹配法
2018-02-22 05:04
811 查看
正向最大匹配法
分词目标:在词典中进行扫描,尽可能地选择与词典中最长单词匹配的词作为目标分词,然后进行下一次匹配。
算法流程:
假设词典中最长的单词为 5 个(MAX_LENGTH),那么最大匹配的起始子串字数也为 5 个
(1)扫描字典,测试读入的子串是否在字典中
(2)如果存在,则从输入中删除掉该子串,重新按照规则取子串,重复(1)
(3)如果不存在于字典中,则从右向左减少子串长度,重复(1)
分词实例:
比如说输入 “北京大学生前来应聘”,
第一轮:取子串 “北京大学生”,正向取词,如果匹配失败,每次去掉匹配字段最后面的一个字
“北京大学生”,扫描 5 字词典,没有匹配,子串长度减 1 变为“北京大学”
“北京大学”,扫描 4 字词典,有匹配,输出“北京大学”,输入变为“生前来应聘”
第二轮:取子串“生前来应聘”
“生前来应聘”,扫描 5 字词典,没有匹配,子串长度减 1 变为“生前来应”
“生前来应”,扫描 4 字词典,没有匹配,子串长度减 1 变为“生前来”
“生前来”,扫描 3 字词典,没有匹配,子串长度减 1 变为“生前”
“生前”,扫描 2 字词典,有匹配,输出“生前”,输入变为“来应聘””
第三轮:取子串“来应聘”
“来应聘”,扫描 3 字词典,没有匹配,子串长度减 1 变为“来应”
“来应”,扫描 2 字词典,没有匹配,子串长度减 1 变为“来”
颗粒度最小为 1,直接输出“来”,输入变为“应聘”
第四轮:取子串“应聘”
“应聘”,扫描 2 字词典,有匹配,输出“应聘”,输入变为“”
输入长度为0,扫描终止
正向匹配法最终的切分结果为:”北京大学 / 生前 / 来 / 应聘”
正向匹配法实现代码如下:
public List<String> leftMax(String str) { List<String> results = new ArrayList<String>(); String input = str; while( input.length() > 0 ) { String subSeq; // 每次取小于或者等于最大字典长度的子串进行匹配 if( input.length() < MAX_LENGTH) subSeq = input; else subSeq = input.substring(0, MAX_LENGTH); while( subSeq.length() > 0 ) { // 如果字典中含有该子串或者子串颗粒度为1,子串匹配成功 if( dictionary.contains(subSeq) || subSeq.length() == 1) { results.add(subSeq); // 输入中从前向后去掉已经匹配的子串 input = input.substring(subSeq.length()); edfb break; // 退出循环,进行下一次匹配 } else { // 去掉匹配字段最后面的一个字 subSeq = subSeq.substring(0, subSeq.length() - 1); } } } return results; }
逆向最大匹配法
分词目标:在词典中进行扫描,尽可能地选择与词典中最长单词匹配的词作为目标分词,然后进行下一次匹配。
在实践中,逆向最大匹配算法性能优于正向最大匹配算法。
算法流程:
假设词典中最长的单词为 5 个(MAX_LENGTH),那么最大匹配的起始子串字数也为 5 个
(1)扫描字典,测试读入的子串是否在字典中
(2)如果存在,则从输入中删除掉该子串,重新按照规则取子串,重复(1)
(3)如果不存在于字典中,则从左向右减少子串长度,重复(1)
分词实例:
比如说输入 “北京大学生前来应聘”,
第一轮:取子串 “生前来应聘”,逆向取词,如果匹配失败,每次去掉匹配字段最前面的一个字
“生前来应聘”,扫描 5 字词典,没有匹配,字串长度减 1 变为“前来应聘”
“前来应聘”,扫描 4 字词典,没有匹配,字串长度减 1 变为“来应聘”
“来应聘”,扫描 3 字词典,没有匹配,字串长度减 1 变为“应聘”
“应聘”,扫描 2 字词典,有匹配,输出“应聘”,输入变为“大学生前来”
第二轮:取子串“大学生前来”
“大学生前来”,扫描 5 字词典,没有匹配,字串长度减 1 变为“学生前来”
“学生前来”,扫描 4 字词典,没有匹配,字串长度减 1 变为“生前来”
“生前来”,扫描 3 字词典,没有匹配,字串长度减 1 变为“前来”
“前来”,扫描 2 字词典,有匹配,输出“前来”,输入变为“北京大学生”
第三轮:取子串“北京大学生”
“北京大学生”,扫描 5 字词典,没有匹配,字串长度减 1 变为“京大学生”
“京大学生”,扫描 4 字词典,没有匹配,字串长度减 1 变为“大学生”
“大学生”,扫描 3 字词典,有匹配,输出“大学生”,输入变为“北京”
第四轮:取子串“北京”
“北京”,扫描 2 字词典,有匹配,输出“北京”,输入变为“”
输入长度为0,扫描终止
逆向匹配法最终的切分结果为:”北京/ 大学生/ 前来 / 应聘”
逆向匹配法实现如下:
public List<String> rightMax(String str) { // 采用堆栈处理结果,后进先出 Stack<String> store=new Stack<String>(); List<String> results = new ArrayList<String>(); String input = str; while( input.length() > 0 ) { String subSeq; // 每次取小于或者等于最大字典长度的子串进行匹配 if( input.length() < MAX_LENGTH) subSeq = input; else subSeq = input.substring(input.length() - MAX_LENGTH); while( subSeq.length() > 0 ) { // 如果字典中含有该子串或者子串颗粒度为1,子串匹配成功 if( dictionary.contains(subSeq) || subSeq.length() == 1) { store.add(subSeq); // 输入中从后向前去掉已经匹配的子串 input = input.substring(0, input.length() - subSeq.length()); break; } else { // 去掉匹配字段最前面的一个字 subSeq = subSeq.substring(1); } } } // 输出结果 int size = store.size(); for( int i = 0; i < size; i ++) { results.add(store.pop()); } return results; }
双向最大匹配法
分词目标:将正向最大匹配算法和逆向最大匹配算法进行比较,从而确定正确的分词方法。
算法流程:
比较正向最大匹配和逆向最大匹配结果
如果分词数量结果不同,那么取分词数量较少的那个
如果分词数量结果相同
分词结果相同,可以返回任何一个
分词结果不同,返回单字数比较少的那个
分词实例:
就上例来看,
正向匹配最终切分结果为:北京大学 / 生前 / 来 / 应聘,分词数量为 4,单字数为 1
逆向匹配最终切分结果为:”北京/ 大学生/ 前来 / 应聘,分词数量为 4,单字数为 0
逆向匹配单字数少,因此返回逆向匹配的结果。
双向最大匹配法实现如下:
public List<String> segment() { List<String> fmm = this.leftMax(); List<String> bmm = this.rightMax(); // 如果分词的结果不同,返回长度较小的 if( fmm.size() != bmm.size()) { if ( fmm.size() > bmm.size()) return bmm; else return bmm; } // 如果分词的词数相同 else { int fmmSingle = 0, bmmSingle = 0; boolean isEqual = true; for( int i = 0; i < bmm.size(); i ++) { if( !fmm.get(i).equals(bmm.get(i))) { isEqual = false; } if( fmm.get(i).length() == 1) fmmSingle ++; if( bmm.get(i).length() == 1) bmmSingle ++; } // 如果正向、逆向匹配结果完全相等,返回任意结果 if ( isEqual ) { return fmm; // 否则,返回单字数少的匹配方式 } else if ( fmmSingle > bmmSingle) return bmm; else return fmm; } }
载入字典和自定义添加词
这里的字典文件采用的是http://download.csdn.net/download/yuanlulu/2380141
载入字典和自定义添加词实现如下:
private static Set<String> dictionary; // 初始化字典,采用 hashset 存储 public void getDictionary() { dictionary = new HashSet<String>(); String dicpath = "data/worddict2.txt"; String line = null; BufferedReader br; try { // 按照 gbk 编码读入文件 br = new BufferedReader(new InputStreamReader(new FileInputStream(dicpath),"gbk")); try { while(((line = br.readLine())!=null)) { // 按照空格切分,只读取第二部分 String[] str = line.split("\\s+"); line = str[1]; dictionary.add(line); } br.close(); } catch (IOException e) { e.printStackTrace(); } } catch (UnsupportedEncodingException | FileNotFoundException e) { e.printStackTrace(); } } // 自定义添加词汇 public void addWord(String str) { dictionary.add(str); }
歧义句测试
可以看到效果还不错,最大匹配法的效果还是取决于字典的质量。
整体代码如下:
package mm; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; public class MMSegment { private String request; private int MAX_LENGTH = 5; private static Set<String> dictionary; public void getDictionary() { dictionary = new HashSet<String>(); String dicpath = "data/worddict2.txt"; String line = null; BufferedReader br; try { br = new BufferedReader(new InputStreamReader(new FileInputStream(dicpath),"gbk")); try { while(((line = br.readLine())!=null)) { String[] str = line.split("\\s+"); line = str[1]; dictionary.add(line); } br.close(); } catch (IOException e) { e.printStackTrace(); } } catch (UnsupportedEncodingException | FileNotFoundException e) { e.printStackTrace(); } } public void addWord(String str) { dictionary.add(str); } public List<String> leftMax() { List<String> results = new ArrayList<String>(); String input = request; while( input.length() > 0 ) { String subSeq; if( input.length() < MAX_LENGTH) subSeq = input; else subSeq = input.substring(0, MAX_LENGTH); while( subSeq.length() > 0 ) { if( dictionary.contains(subSeq) || subSeq.length() == 1) { results.add(subSeq); input = input.substring(subSeq.length()); break; } else { subSeq = subSeq.substring(0, subSeq.length() - 1); } } } return results; } public List<String> rightMax() { Stack<String> store=new Stack<String>(); List<String> results = new ArrayList<String>(); String input = request; while( input.length() > 0 ) { String subSeq; if( input.length() < MAX_LENGTH) subSeq = input; else subSeq = input.substring(input.length() - MAX_LENGTH); while( subSeq.length() > 0 ) { if( dictionary.contains(subSeq) || subSeq.length() == 1) { store.add(subSeq); input = input.substring(0, input.length() - subSeq.length()); break; } else { subSeq = subSeq.substring(1); } } } int size = store.size(); for( int i = 0; i < size; i ++) { results.add(store.pop()); } return results; } public List<String> segment() { List<String> fmm = this.leftMax(); List<String> bmm = this.rightMax(); if( fmm.size() != bmm.size()) { if ( fmm.size() > bmm.size()) return bmm; else return fmm; } else { int fmmSingle = 0, bmmSingle = 0; boolean isEqual = true; for( int i = 0; i < bmm.size(); i ++) { if( !fmm.get(i).equals(bmm.get(i))) { isEqual = false; } if( fmm.get(i).length() == 1) fmmSingle ++; if( bmm.get(i).length() == 1) bmmSingle ++; } if ( isEqual ) { return fmm; } else if ( fmmSingle > bmmSingle) return bmm; else return fmm; } } public void test(String str) { request = str; System.out.println(this.segment()); } public static void main(String[] args) { MMSegment f = new MMSegment(); f.getDictionary(); f.test("研究生命科学"); f.test("研究生命令本科生"); f.test("我从马上下来"); f.test("北京大学生喝进口红酒"); f.test("美军中将竟公然说"); f.test("阿美首脑会议将讨论巴以和平等问题"); f.addWord("巴以和平"); System.out.println("---------------------------"); System.out.println("向字典中添加'巴以和平'后"); f.test("阿美首脑会议将讨论巴以和平等问题"); f.test("我不想吃东西"); } }
参考资料
[1] http://blog.csdn.net/worldwindjp/article/details/18085725[2] http://blog.csdn.net/hu948162999/article/details/43608107
[3] http://blog.csdn.net/xiaoyeyopulei/article/details/25194021
[4] http://blog.csdn.net/chenlei0630/article/details/40710441
相关文章推荐
- 中文分词基础原则及正向最大匹配法、逆向最大匹配法、双向最大匹配法的分析
- 自己动手写分词引擎——逆向最大、正向最大、双向最大分词算法的实现
- 中文分词基础原则及正向最大匹配法、逆向最大匹配法、双向最大匹配法的分析
- 中文分词实现——双向最大匹配
- 中文分词中的正向最大匹配与逆向最大匹配
- java中文分词之正向最大匹配法实例代码
- 用正向和逆向最大匹配算法进行中文分词
- 简单的逆向最大匹配算法实现中文分词(Python)
- Java实现正向最大匹配法和逆向最大匹配法
- 一个简单最大正向匹配(Maximum Matching)MM中文分词算法的实现
- java实现正向最大匹配分词
- 一个简单最大正向匹配(Maximum Matching)MM中文分词算法的实现
- NLP中文信息处理---正向最大匹配法分词
- 正向最大匹配中文分词算法
- 基于Tire树和最大概率法的中文分词功能的Java实现
- NLP中文信息处理---正向最大匹配法分词
- 基于Tire树和最大概率法的中文分词功能的Java实现
- 中文分词--逆向最大匹配
- 中文分词——正向最大匹配法
- 【分词】正向最大匹配中文分词算法