对于分词系统的实现来说,主要应集中在两方面的考虑上:一是对语料库的组织,二是分词策略的制订。

1.   Tire树

Tire树,即字典树,是通过字串的公共前缀来对字串进行统计、排序及存储的一种树形结构。其具有如下三个性质:

1)      根节点不包含字符(或汉字),除根节点以外的每个节点只能包含一个字符(汉字)

2)      从根节点到任一节点的路径上的所有节点中的字符(汉字)按顺序排列的字符串(词组)就是该节点所对应的字符串(词组)

3)      每个节点的所有直接子节点包含的字符(汉字)各不相同

上述性质保证了从Tire树中查找任意字符串(词组)所需要比较的次数尽可能最少,以达到快速搜索语料库的目的。

如下图所示的是一个由词组集<一,一万,一万多,一万元,一上午,一下午,一下子>生成的Tire树的子树:

可见,从子树的根节点“一”开始,任意一条路径都能组成一个以“一”开头的词组。而在实际应用中,需要给每个节点附上一些数据属性,如词频,因而可以用这些属性来区别某条路径上的字串是否是一个词组。如,节点“上”的词频为-1,那么“一上”就不是一个词组。

如下的代码是Tire树的Java实现:

package chn.seg;import java.util.HashMap;
import java.util.Map;public class TireNode {private String character;private int frequency = -1;private double antilog = -1;private Map<String, TireNode> children;public String getCharacter() {return character;}public void setCharacter(String character) {this.character = character;}public int getFrequency() {return frequency;}public void setFrequency(int frequency) {this.frequency = frequency;}public double getAntilog() {return antilog;}public void setAntilog(double antilog) {this.antilog = antilog;}public void addChild(TireNode node) {if (children == null) {children = new HashMap<String, TireNode>();}if (!children.containsKey(node.getCharacter())) {children.put(node.getCharacter(), node);}}public TireNode getChild(String ch) {if (children == null || !children.containsKey(ch)) {return null;}return children.get(ch);}public void removeChild(String ch) {if (children == null || !children.containsKey(ch)) {return;}children.remove(ch);}
}

2.   最大概率法(动态规划)

最大概率法是中文分词策略中的一种方法。相较于最大匹配法等策略而言,最大概率法更加准确,同时其实现也更为复杂。

基于动态规划的最大概率法的核心思想是:对于任意一个语句,首先按语句中词组的出现顺序列出所有在语料库中出现过的词组;将上述词组集中的每一个词作为一个顶点,加上开始与结束顶点,按构成语句的顺序组织成有向图;再为有向图中每两个直接相连的顶点间的路径赋上权值,如A→B,则AB间的路径权值为B的费用(若B为结束顶点,则权值为0);此时原问题就转化成了单源最短路径问题,通过动态规划解出最优解即可。

如句子“今天下雨”,按顺序在语料库中存在的词组及其费用如下:

今,a

今天,b

天,c

天下,d

下,e

下雨,f

雨,g

则可以生成如下的加权有向图:

显而易见,从“Start”到“End”的单源路径最优解就是“今天下雨”这个句子的分词结果。

那么,作为权值的费用如何计算呢?对于最大概率法来说,要求的是词组集在语料库中出现的概率之乘积最大。对应单源最短路径问题的费用来说,

费用 = log( 总词频 / 某一词组词频 )

通过上述公式就可以把“最大”问题化为“最小”问题,“乘积”问题化为“求和”问题进行求解了。

如下的代码是基于动态规划的最大概率法的Java实现:

package chn.seg;import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;public class ChnSeq {private TireNode tire = null;public void init() throws IOException, ClassNotFoundException {        File file = new File("data" + File.separator + "dict.txt");if (!file.isFile()) {System.err.println("语料库不存在!终止程序!");System.exit(0);}BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "utf-8"));String line = in.readLine();int totalFreq = Integer.parseInt(line);tire = new TireNode();while ((line = in.readLine()) != null) {String[] segs = line.split(" ");String word = segs[0];int freq = Integer.parseInt(segs[1]);TireNode root = tire;for (int i = 0; i < word.length(); i++) {String c = "" + word.charAt(i);TireNode node = root.getChild(c);if (node == null) {node = new TireNode();node.setCharacter(c);root.addChild(node);}root = node;}root.setFrequency(freq);root.setAntilog(Math.log((double)totalFreq / freq));}in.close();}public TireNode getTire() {return tire;}public TireNode getNodeByWord(String word) {if (tire == null) {System.err.println("需要先初始化ChnSeq对象!");return null;}TireNode node = tire;for (int i = 0; i < word.length(); i++) {String ch = word.charAt(i) + "";if (node == null) {break;} else {node = node.getChild(ch);}}return node;}private class Segment {public String word;public String endChar;public String lastChar;public double cost;public final static String START_SIGN = "<< STARTING >>";public final static String END_SIGN = "<< ENDING >>";}private List<Segment> preSegment(String sentence) {List<Segment> segs = new ArrayList<Segment>();Segment terminal = new Segment();terminal.word = Segment.START_SIGN;terminal.endChar = Segment.START_SIGN;terminal.lastChar = null;segs.add(terminal);for (int i = 0; i < sentence.length(); i++) {for (int j = i + 1; j <= sentence.length(); j++) {String word = sentence.substring(i, j);TireNode tnode = this.getNodeByWord(word);if (tnode == null) {break;}if (tnode.getFrequency() <= 0) {continue;}Segment seg = new Segment();seg.word = word;seg.endChar = word.substring(word.length() - 1, word.length());if (i == 0) {seg.lastChar = Segment.START_SIGN;} else {seg.lastChar = sentence.substring(i - 1, i);}seg.cost = tnode.getAntilog();segs.add(seg);}}terminal = new Segment();terminal.word = Segment.END_SIGN;terminal.endChar = Segment.END_SIGN;terminal.lastChar = sentence.substring(sentence.length() - 1, sentence.length());segs.add(terminal);return segs;}private String[] dynamicSegment(List<Segment> segs) {final double INFINITE = 9999999;if (segs == null || segs.size() == 0) {return null;}int n = segs.size();double[][] costs = new double[n][n];for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {costs[i][j] = INFINITE;}}for (int i = 0; i < n; i++) {String endChar = segs.get(i).endChar;for (int j = 0; j < n; j++) {String lastChar = segs.get(j).lastChar;if (lastChar != null && lastChar.equals(endChar)) {costs[i][j] = segs.get(j).cost;}}}int sp = 0; // starting pointint fp = n - 1; // finishing pointdouble[] dist = new double[n];List<List<Integer>> sPaths = new ArrayList<List<Integer>>();List<Integer> list = new ArrayList<Integer>();for (int i = 0; i < n; i++) {dist[i] = costs[sp][i];if (sp != i) {list.add(i);}if (dist[i] < INFINITE) {List<Integer> spa = new ArrayList<Integer>();sPaths.add(spa);} else {sPaths.add(null);}}while (!list.isEmpty()) {Integer minIdx = list.get(0);for (int i: list) {if (dist[i] < dist[minIdx]) {minIdx = i;}}list.remove(minIdx);for (int i = 0; i < n; i++) {if (dist[i] > dist[minIdx] + costs[minIdx][i]) {dist[i] = dist[minIdx] + costs[minIdx][i];List<Integer> tmp = new ArrayList<Integer>(sPaths.get(minIdx));tmp.add(minIdx);sPaths.set(i, tmp);}}}String[] result = new String[sPaths.get(fp).size()];for (int i = 0; i < sPaths.get(fp).size(); i++) {result[i] = segs.get(sPaths.get(fp).get(i)).word;}return result;}public String[] segment(String sentence) {return dynamicSegment(preSegment(sentence));}
}

3.   测试代码

package chn.seg;import java.io.IOException;public class Main {public static void main(String[] args) throws ClassNotFoundException, IOException {ChnSeq cs = new ChnSeq();cs.init();String sentence = "生活的决定权也一直都在自己手上";String[] segs = cs.segment(sentence);for (String s: segs) {System.out.print(s + "\t");}}}

转载于:https://www.cnblogs.com/snake-hand/p/3151398.html

基于Tire树和最大概率法的中文分词功能的Java实现相关推荐

  1. 【自然语言处理】N-最短路径法进行中文分词

    本文摘要 · 理论来源:[统计自然语言处理]第七章 自动分词 · 参考文章:https://www.cnblogs.com/Finley/p/6619187.html · 代码目的:手写N-最短路径法 ...

  2. ACL 2021 | 基于全局字符关联机制联邦学习的中文分词

    作者 | 陈桂敏 来源 | QTrade AI研究中心 QTrade AI 研究中心是一支将近 30 人的团队,主要研究方向包括:预训练模型.信息抽取.对话机器人.内容推荐等.本文介绍的是一篇收录于 ...

  3. 基于Tire树(字典树)与倒排索引实现文本词频统计工具

    文章目录 文件读写操作 C风格文件读取 C++风格按行读取 C++风格按单词读取 实现文件词频统计工具 英文文章单词的正确分割 基于Trie树实现文件词频统计 基于Trie树实现带倒排索引的文件词频统 ...

  4. PyTorch 高级实战教程:基于 BI-LSTM CRF 实现命名实体识别和中文分词

    20210607 https://blog.csdn.net/u011828281/article/details/81171066 前言:译者实测 PyTorch 代码非常简洁易懂,只需要将中文分词 ...

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

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

  6. 从头开始编写基于隐含马尔可夫模型HMM的中文分词器之一 - 资源篇

    首先感谢52nlp的系列博文(http://www.52nlp.cn/),提供了自然语言处理的系列学习文章,让我学习到了如何实现一个基于隐含马尔可夫模型HMM的中文分词器. 在编写一个中文分词器前,第 ...

  7. 达观数据基于Deep Learning的中文分词尝试

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

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

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

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

    最近在实现基于lucene.net的搜索方案,涉及中文分词,找了很多,最终选择了MMSeg4j,但MMSeg4j只有Java版,在博客园上找到了*王员外*(http://www.cnblogs.com ...

最新文章

  1. 1091 线段的重叠
  2. mybatis使用foreach实现sql的in查询
  3. 时间自适应卷积:比自注意力更快的特征提取器
  4. mongoDB如何将数据导成csv文件?
  5. Django进阶之中间件
  6. c语言编程流水灯与交通灯实验,C51单片机实验报告_流水灯_交通灯_定时器_双机交互_时钟.doc...
  7. 第六节:ES6为字符串String带来哪些好玩的特性?
  8. python sys os_python常用的一些东西——sys、os等(转)
  9. 微信朋友圈的测试用例
  10. 自制 arduino 音符频率对照表(音符在arduino里对应的值)
  11. Mosquitto PHP 插件安装使用及中文手册
  12. 如何高效开发一款微信小程序
  13. 补习班停了,家长“卷向”兴趣班
  14. PDF文件中的图片导出
  15. 蘑菇街服务器信息,蘑菇街开放平台
  16. Mohican_4/22 结构体 typedef 枚举 联合 位段 内存对齐
  17. cobbler自动部署装机
  18. 《大话处理器》相关主题汇总
  19. 免费的计算机一级操作系统,计算机一级题题库,第二章操作系统
  20. Linux之systemctl命令基本使用

热门文章

  1. mybatis调用存储过程
  2. Geotools应用简要指南
  3. 微信小程序 --- 图片自适应、本地图片的使用
  4. 再次认识 vertical-align
  5. 《javascript设计模式》笔记之第十章 和 第十一章:门面模式和适配器模式
  6. RxJava使用(一)基本使用
  7. Java 8 Lambda
  8. 被relativeLayout的grivate center 折腾死了
  9. StringBuffer、StringBuilder区别以及Synchronized原理
  10. react native引入第三方库