博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【中文分词】简单高效的MMSeg
阅读量:5925 次
发布时间:2019-06-19

本文共 2844 字,大约阅读时间需要 9 分钟。

最近碰到一个分词匹配需求——给定一个关键词表,作为自定义分词词典,用户query文本分词后,是否有词落入这个自定义词典中?现有的大多数Java系的分词方案基本都支持添加自定义词典,但是却不支持HDFS路径的。因此,我需要寻找一种简单高效的分词方案,稍作包装即可支持HDFS。MMSeg分词算法正是完美地契合了这种需求。

1. MMseg简介

是蔡志浩(Chih-Hao Tsai)提出的基于字符串匹配(亦称基于词典)的中文分词算法。基于词典的分词方案无法解决歧义问题,比如,“武汉市长江大桥”是应分词“武汉/市长/江大桥”还是“武汉市/长江/大桥”。基于此,有人提出了正向最大匹配策略,但是可能会出现分词错误的情况,比如:若词典中有“武汉市长”,则原句被分词成“武汉市长/江大桥”。单纯的最大匹配还是无法完美地解决歧义,因而MMSeg在正向最大匹配的基础上设计了四个启发式规则。isnowfy大神的《》对于各种主流的分词算法做了精辟的论述。

MMSeg的字符串匹配算法分为两种:

  • Simple,简单的正向最大匹配,即按能匹配上的最长词做切分;
  • Complex,在正向最大匹配的基础上,考虑相邻词的词长,设计了四个去歧义规则(Ambiguity Resolution Rules)指导分词。

在complex分词算法中,MMSeg将切分的相邻三个词作为词块(chunk),应用如下四个消歧义规则:

  1. 备选词块的长度最大(Maximum matching),即三个词的词长之和最大;
  2. 备选词块的平均词长最大(Largest average word length),即要求词长分布尽可能均匀;
  3. 备选词块的词长变化最小(Smallest variance of word lengths );
  4. 备选词块中(若有)单字的出现词自由度最高(Largest sum of degree of morphemic freedom of one-character words)。

这篇文章《》对于这四个规则做了更为细致的介绍,本文无再赘言了。

2. 实战

MMSeg的Java实现有,本地路径添加自定义词典分词:

String txt = "在一起并发生了中文分词.";// user-defined dictionary parent-pathDictionary dic = Dictionary.getInstance("src\\resources\\dict");Seg seg = new ComplexSeg(dic);MMSeg mmSeg = new MMSeg(new StringReader(txt), seg);Word word = null;while ((word = mmSeg.next()) != null) {  System.out.print(word + "|");}

mmseg4j("com.chenlb.mmseg4j" % "mmseg4j-core" % "1.10.0")没有wiki,通过分析源码才知道getInstance方法的路径参数应是父目录,并且词典文件的命名应符合规范:chars.dic(单字词表)、wordsXXX.dic(词长>1词表)。mmseg4j所加载的分词词典为类Dictionary数据成员Map<Character, CharNode> dict,其中Character为词的首字,CharNode是一棵trie树,存储拥有共同前缀(首字)的词。loadWord方法为加载自定义词表。

基于上面的代码分析,封装添加HDFS路径词典的Scala代码如下:

import com.chenlb.mmseg4j.CharNodeimport org.apache.hadoop.conf.Configurationimport org.apache.hadoop.fs.{FSDataInputStream, FileSystem, Path}import scala.io.Source/**  * @author rain  */object MMSegUtil {  // str[1:-1], the last len(str)-1 characters  def tail(str: String): Array[Char] = {    str.toCharArray.takeRight(str.length - 1)  }  // load user-define word dictionary  def loadWord(path: String, dic: java.util.Map[Character, CharNode]) = {    val fs = FileSystem.get(new Configuration)    val in: FSDataInputStream = fs.open(new Path(path))    Source.fromInputStream(in).getLines()      .filter(_.length > 1)      .foreach { line =>        val cn: CharNode = dic.get(line.charAt(0))        cn match {          case null => dic.put(line.charAt(0), cn)          case _ => cn.addWordTail(tail(line))        }      }  }}

即可在Spark程序中调用分词:

val dictionary = Dictionary.getInstance()MMSegUtil.loadWord(dicPath, dictionary.getDict)val seg = new ComplexSeg(dictionary)

值得指出,ComplexSeg类有List remove操作,因而不适于做成广播变量,不然则报ConcurrentModificationException。推荐的做法,配合RDD的mapPartitions在for yield外层new ComplexSeg;相当于每个Partition都有一个ComplexSeg。

mmseg4j存在分词不准确的情况,比如,『培养并发挥热忱的特性』被分词成『培养/并发/挥/热忱/的/特性』。这是因为mmseg4j的chars词典中,挥 20429的词频高于并 2789(针对于MMSeg的规则4)。基于词典的分词方案的准确性,严重依赖于词典;必须要有好的词典,才会有好的分词结果。

转载于:https://www.cnblogs.com/en-heng/p/5872308.html

你可能感兴趣的文章
JAVA 异常库
查看>>
Android笔记:存储相关,getExternalCacheDir, getExternalFilesDir,getExternalStorageDirectory等
查看>>
[每日一题] 11gOCP 1z0-052 :2013-09-23 Oracle11g 内存参数设置...................................C7...
查看>>
我的友情链接
查看>>
centos 7 安装openstack kilo in three node
查看>>
Active Directory系列之十七:实战详解域信任关系
查看>>
Java程序员从笨鸟到菜鸟之(三十)javascript弹出框、事件、对象化编程
查看>>
Java程序员从笨鸟到菜鸟之(一百零四)java操作office和pdf文件(二)利用POI实现数据导出excel报表...
查看>>
在Ant的javac中指定源文件编码方式,以避免"警告: 编码 GBK 的不可映射字符"的错误...
查看>>
Git 简单命令行指令
查看>>
WEB安全测试软件
查看>>
十分钟完成Bash 脚本进阶!列举Bash经典用法及其案例
查看>>
阿里云ECS,搭建MySQL5.7数据库环境
查看>>
第10章-管理Hadoop集群-hadoop 安全模式相关知识点
查看>>
Logstash 命令行参数
查看>>
设置润乾报表鼠标移到格子上就显示提示内容
查看>>
ln -s 的一个坑
查看>>
使用组策略推送exchange自签名证书
查看>>
怎样自己写一个MVC框架
查看>>
我的友情链接
查看>>