目录

一、中文分词理论描述

二、算法描述

1、正向最大匹配算法

2、反向最大匹配算法

3、双剑合璧

三、案例描述

四、JAVA实现完整代码

五、组装UI

六、总结


前言

这篇将使用Java实现基于规则的中文分词算法,一个中文词典将实现准确率高达85%的分词结果。使用经典算法:正向最大匹配和反向最大匹配算法,然后双剑合璧,双向最大匹配。

一、中文分词理论描述

根据相关资料,中文分词概念的理论描述,我总结如下:

中文分词是将一个汉字序列切分成一个一个单独的词,将连续的字序列按照一定的规范重新组合成词序列的过程,把字与字连在一起的汉语句子分成若干个相互独立、完整、正确的单词,词是最小的、能独立活动的、有意义的语言成分。

中文分词应用广泛,是文本挖掘的基础,在中文文本处理中意义重大,对于输入的一段中文,成功的进行中文分词,可以达到电脑自动识别语句含义的效果。目前,常用流行的以及分次效果好的工具库包括:jieba、HanLP、LTP、FudanNLP等。

我们知道,调用工具方便容易,但是如果自己实现写一个算法实现,那不是更加有成就感^_^。

接下来将一步步介绍最容易理解,最简单,效果还很不错的中文分词算法,据说准确率能达到85%!!

二、算法描述

1、正向最大匹配算法

所谓正向,就是从文本串左边正向扫描,取出子串与词典进行匹配。

算法我分为两步来理解:

假设初始化取最大匹配长度为MaxLen,当前位置pos=0,处理结果result=””,每次取词str,取词长度len,待处理串segstr。

  1. len=MaxLen,取字符串0到len的子串,查找词典,若匹配到则赋值str,加到result,在保证pos+len<=segstr.length()情况下,pos=pos+len,向后匹配,直到字符串扫描完成,结束算法。
  2. 若词典未找到,若len>1,减小匹配长度同时len=MaxLen-1,执行步骤(1),否则,取出剩余子串,执行步骤(1)。

算法代码如下:

    public void MM(String str, int len, int frompos) {
if (frompos + 1 > str.length())
return;
String curstr = "";
//此处可以设置断点
int llen = str.length() - frompos;
if (llen <= len)//句末边界处理
curstr = str.substring(frompos, frompos + llen);//substring获取的子串是下标frompos~frompos+llen-1
else
curstr = str.substring(frompos, frompos + len); if (dict.containsKey(curstr)) {
result = result + curstr + "/ ";
Len = MaxLen;
indexpos = frompos + len;
MM(str, Len, indexpos);
} else {
if (Len > 1) {
Len = Len - 1;
} else {
result = result + str + "/ ";
frompos = frompos + 1;
Len = MaxLen;
}
MM(str, Len, frompos);
}
}

从算法代码看出,很容易理解,细节部分在于边界处理。

测试一下,我输入文本,"我爱自然语言处理,赞赏评论收藏我的文章是我的动力!赶紧关注!"

分词结果:

2、反向最大匹配算法

反向,则与正向相反,从文本串末向左进行扫描。

假设初始化取最大匹配长度为MaxLen,当前位置pos为字符串尾部,处理结果result=””,每次取词str,取词长度len,待处理串segstr。

  1. len=MaxLen,取字符串pos-len到pos的子串,查找词典,若匹配到则赋值str,加到result,同时pos=pos-len,保证pos-len>=0,向前移动匹配,直到字符串扫描完成,结束算法。
  2. 若词典未找到,若len>1,减小匹配长度同时len=MaxLen-1,执行步骤(1),否则,取出剩余子串,执行步骤(1)。

算法逻辑类似,取相反方向处理。

public void RMM(String str, int len, int frompos) {
if (frompos < 0)
return;
String curstr = "";
//此处可以设置断点
if (frompos - len + 1 >= 0)//句末边界处理
curstr = str.substring(frompos - len + 1, frompos + 1);//substring获取的子串是下标frompos~frompos+llen-1
else
curstr = str.substring(0, frompos + 1);//到达句首 if (dict.containsKey(curstr)) {
RmmResult = curstr + "/ " + RmmResult;
Len = MaxLen;
indexpos = frompos - len;
RMM(str, Len, indexpos);
} else {
if (Len > 1) {
Len = Len - 1;
} else {
RmmResult = RmmResult + str + "/ ";
frompos = frompos - 1;
Len = MaxLen;
}
RMM(str, Len, frompos);
}
}

同样,细节部分在于边界处理,其他基本相同。

3、双剑合璧

这里所说的是正向与反向结合,实现双向最大匹配。

双向最大匹配算法,基于正向、反向最大匹配,对分词结果进一步处理,比较两个结果,做的工作就是遵循某些原则和经验,筛选出两者中更确切地分词结果。原则如下:

  • 多数情况下,反向最大匹配效果更好,若分词结果相同,则返回RMM结果;
  • 遵循切分最少词原则,更大匹配词为更好地分词结果,比较之后返回最少词的切分结果;
  • 根据切分后词长度的大小,选择词长度大者为最终结果。

具体也需要看开始给定的最大匹配长度为多少。以下代码只实现了原则(1)、(2)。

    public String BMM() throws IOException {
String Mr = MM_Seg();
String RMr = RMM_Seg();
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,结束算法。

四、JAVA实现完整代码

除了分词算法实现,还需要读入词典,对词典进行预处理,具体如下:

package ex1;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*; public class seg { String result;
String RmmResult;
String segstring;
int MaxLen;
int Len;
int indexpos;
Map<String, String> dict; public seg(String inputstr, int maxlen) {//构造函数
segstring = inputstr;
MaxLen = maxlen;
Len = MaxLen;
indexpos = 0;
result = "";
RmmResult = "";
dict = new HashMap<String, String>(); } public void ReadDic() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("chineseDic.txt"), "GBK"));
String line = null;
while ((line = br.readLine()) != null) {
String[] words = line.trim().split(",");//词典包含词性标注,需要将词与标注分开,放入列表
String word = words[0];
String cx = words[1];
dict.put(word, cx);
}
br.close();
} public String MM_Seg() throws IOException {//正向最大匹配算法
try {
ReadDic();//读入字典
MM(segstring, MaxLen, 0);//正向最大分词
return result;
} catch (IOException e) {
return "Read Error!";
}
} public void MM(String str, int len, int frompos) {
if (frompos + 1 > str.length())
return;
String curstr = "";
//此处可以设置断点
int llen = str.length() - frompos;
if (llen <= len)//句末边界处理
curstr = str.substring(frompos, frompos + llen);//substring获取的子串是下标frompos~frompos+llen-1
else
curstr = str.substring(frompos, frompos + len); if (dict.containsKey(curstr)) {
result = result + curstr + "/ ";
Len = MaxLen;
indexpos = frompos + len;
MM(str, Len, indexpos);
} else {
if (Len > 1) {
Len = Len - 1;
} else {
result = result + str + "/ ";
frompos = frompos + 1;
Len = MaxLen;
}
MM(str, Len, frompos);
}
} public String RMM_Seg() throws IOException {//正向最大匹配算法
try {
ReadDic();//读入字典
RMM(segstring, MaxLen, segstring.length() - 1);//正向最大分词
return RmmResult;
} catch (IOException e) {
return "Read Error!";
}
} public void RMM(String str, int len, int frompos) {
if (frompos < 0)
return;
String curstr = "";
//此处可以设置断点
if (frompos - len + 1 >= 0)//句末边界处理
curstr = str.substring(frompos - len + 1, frompos + 1);//substring获取的子串是下标frompos~frompos+llen-1
else
curstr = str.substring(0, frompos + 1);//到达句首 if (dict.containsKey(curstr)) {
RmmResult = curstr + "/ " + RmmResult;
Len = MaxLen;
indexpos = frompos - len;
RMM(str, Len, indexpos);
} else {
if (Len > 1) {
Len = Len - 1;
} else {
RmmResult = RmmResult + str + "/ ";
frompos = frompos - 1;
Len = MaxLen;
}
RMM(str, Len, frompos);
}
} public String BMM() throws IOException {
String Mr = MM_Seg();
String RMr = RMM_Seg();
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;
}
} public String getResult() {
return result;
} public static void main(String[] args) throws IOException, Exception {
seg s = new seg("我爱自然语言处理,赞赏评论收藏我的文章是我的动力!赶紧关注!", 3);
// String result = s.MM_Seg();
String result = s.RMM_Seg();
System.out.println(result); }
}

五、组装UI

我是用的开发软件为是IDEA,一个方便之处可以拖动组件组装UI界面。也可以自行写JavaFX实现简单布局。

这是简单页面的设计:

UI界面可以有更好的用户体验,通过UI界面的元素调用方法,减少每次测试运行算法脚本的繁琐。

实验演示:

每次可以观察不同最大匹配长度分词后的结果。

"年中"词语解析:

在词典中,是这样的,可以发现满足最大匹配。

双向最大匹配算法,结果提示:

六、总结

这篇介绍了使用Java实现基于规则的中文分词算法,使用经典算法:正向最大匹配和反向最大匹配算法,然后双剑合璧,双向最大匹配。最后设计简单UI界面,实现稍微高效的中文分词处理,结果返回。

  1. 双向最大匹配算法原则,希望句子最长词保留完整、最短词数量最少、单字词问题,目前只解决了句子切分最少词问题。
  2. 正向反向匹配算法可以进一步优化结构,提高执行效率,目前平均耗时20ms。
  3. UI界面增加输入输出提示语,方便用户使用,在正确的区域输入内容。
  4. 将最大匹配长度设置为可输入,实现每次可以观察不同MaxLen得到的切分结果。
  5. 双向最大匹配按钮点击之后,返回结果同时返回MM和RMM结果是否一样的提示,方便查看。


我的博客园:https://www.cnblogs.com/chenzhenhong/p/13748042.html

我的CSDN博客: https://blog.csdn.net/Charzous/article/details/108817914

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。

双向最大匹配算法——基于词典规则的中文分词(Java实现)的更多相关文章

  1. 基于MMSeg算法的中文分词类库

    原文:基于MMSeg算法的中文分词类库 最近在实现基于lucene.net的搜索方案,涉及中文分词,找了很多,最终选择了MMSeg4j,但MMSeg4j只有Java版,在博客园上找到了*王员外*(ht ...

  2. 中科院NLPIR中文分词java版

    中科院NLPIR中文分词java版 中科院NLPIR中文分词java版

  3. 基于Deep Learning的中文分词尝试

    http://h2ex.com/1282 现有分词介绍 自然语言处理(NLP,Natural Language Processing)是一个信息时代最重要的技术之一,简单来讲,就是让计算机能够理解人类 ...

  4. 自制基于HMM的python中文分词器

    不像英文那样单词之间有空格作为天然的分界线, 中文词语之间没有明显界限.必须采用一些方法将中文语句划分为单词序列才能进一步处理, 这一划分步骤即是所谓的中文分词. 主流中文分词方法包括基于规则的分词, ...

  5. 【中文分词】简单高效的MMSeg

    最近碰到一个分词匹配需求--给定一个关键词表,作为自定义分词词典,用户query文本分词后,是否有词落入这个自定义词典中?现有的大多数Java系的分词方案基本都支持添加自定义词典,但是却不支持HDFS ...

  6. NLP舞动之中文分词浅析(一)

    一.简介        针对现有中文分词在垂直领域应用时,存在准确率不高的问题,本文对其进行了简要分析,对中文分词面临的分词歧义及未登录词等难点进行了介绍,最后对当前中文分词实现的算法原理(基于词表. ...

  7. 转:从头开始编写基于隐含马尔可夫模型HMM的中文分词器

    http://blog.csdn.net/guixunlong/article/details/8925990 从头开始编写基于隐含马尔可夫模型HMM的中文分词器之一 - 资源篇 首先感谢52nlp的 ...

  8. 中文分词实践(基于R语言)

    背景:分析用户在世界杯期间讨论最多的话题. 思路:把用户关于世界杯的帖子拉下来.然后做中文分词+词频统计,最后将统计结果简单做个标签云.效果例如以下: 兴许:中文分词是中文信息处理的基础.分词之后.事 ...

  9. 11大Java开源中文分词器的使用方法和分词效果对比,当前几个主要的Lucene中文分词器的比较

    本文的目标有两个: 1.学会使用11大Java开源中文分词器 2.对比分析11大Java开源中文分词器的分词效果 本文给出了11大Java开源中文分词的使用方法以及分词结果对比代码,至于效果哪个好,那 ...

随机推荐

  1. v-html渲染富文本图片宽高问题

    v-html渲染富文本v-html是用来渲染html的节点及字符串的,但是渲染后富文本里的图片宽高会溢出所在div的区域但是使用css直接给img是没有办法设置img的宽高的,需要使用深层级来给img ...

  2. 求求大厂给个Offer:Map面试题

    前言 文本已收录至我的GitHub:https://github.com/ZhongFuCheng3y/3y,有300多篇原创文章,最近在连载面试系列! 我,三歪,最近开始写面试系列.我给这个面试系列 ...

  3. typepra快捷键

  4. .NET ORM 分表分库【到底】怎么做?

    理论知识 分表 - 从表面意思上看呢,就是把一张表分成N多个小表,每一个小表都是完正的一张表.分表后数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面.分表后单表的并发能力提高了 ...

  5. FileZilla Server FTP服务器失败

    使用Filezilla Server配置FTP服务器https://blog.csdn.net/chuyouyinghe/article/details/78998527 FileZilla Serv ...

  6. 点击穿透事件-----CSS新属性

    面试被问,一脸懵,被提示,还蒙,好丢脸的感觉....赶紧百度了解 .noclick{ pointer-events: none; /* 上层加上这句样式可以实现点击穿透 */ } 就是说重叠在一起的两 ...

  7. 目标识别AI资料

    朋友推荐的, 还有自己搜的. 入门可以看看. 网上资料应该不少, 一搜一大把, 简单记下地址. Review of Deep Learning Algorithms for Object Detect ...

  8. springboot AOP实战

    目录 AOP实战 maven依赖 定义切面 采用扫描类的方式 采用注解的方式 通知 前置通知 后置通知 返回通知 异常通知 环绕通知 JoinPoint 获取切点处的注解 git AOP实战 mave ...

  9. uni-app 修改富文本信息中的图片样式

    var richtext= res.data.data.richtext; const regex = new RegExp('<img', 'gi'); richtext= richtext. ...

  10. 下拉列表被flash覆盖的解决方法

    做鼎闻有一段时间了,有的banner轮播图的地方用flash替换的时候,就会导致上面的导航条下拉列表被flash覆盖,找了一段时间没有得到有效的解决方法,后来知道关键是flash的这一属性{ &quo ...