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

2023-10-08 04:18

本文主要是介绍基于Tire树和最大概率法的中文分词功能的Java实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

1.   Tire树

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

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

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

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

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

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


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

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

 

[java]  view plain copy
  1. package chn.seg;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. public class TireNode {  
  7.       
  8.     private String character;  
  9.     private int frequency = -1;  
  10.     private double antilog = -1;  
  11.     private Map<String, TireNode> children;  
  12.       
  13.     public String getCharacter() {  
  14.         return character;  
  15.     }  
  16.       
  17.     public void setCharacter(String character) {  
  18.         this.character = character;  
  19.     }  
  20.       
  21.     public int getFrequency() {  
  22.         return frequency;  
  23.     }  
  24.       
  25.     public void setFrequency(int frequency) {  
  26.         this.frequency = frequency;  
  27.     }  
  28.       
  29.     public double getAntilog() {  
  30.         return antilog;  
  31.     }  
  32.       
  33.     public void setAntilog(double antilog) {  
  34.         this.antilog = antilog;  
  35.     }  
  36.       
  37.     public void addChild(TireNode node) {  
  38.         if (children == null) {  
  39.             children = new HashMap<String, TireNode>();  
  40.         }  
  41.           
  42.         if (!children.containsKey(node.getCharacter())) {  
  43.             children.put(node.getCharacter(), node);  
  44.         }  
  45.     }  
  46.       
  47.     public TireNode getChild(String ch) {  
  48.         if (children == null || !children.containsKey(ch)) {  
  49.             return null;  
  50.         }  
  51.           
  52.         return children.get(ch);  
  53.     }  
  54.       
  55.     public void removeChild(String ch) {  
  56.         if (children == null || !children.containsKey(ch)) {  
  57.             return;  
  58.         }  
  59.           
  60.         children.remove(ch);  
  61.     }  
  62. }  

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

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

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

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

今,a

今天,b

天,c

天下,d

下,e

下雨,f

雨,g

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


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

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

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

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

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

 

[java]  view plain copy
  1. package chn.seg;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStreamReader;  
  8. import java.util.ArrayList;  
  9. import java.util.List;  
  10.   
  11. public class ChnSeq {  
  12.     private TireNode tire = null;  
  13.   
  14.     public void init() throws IOException, ClassNotFoundException {       
  15.         File file = new File("data" + File.separator + "dict.txt");  
  16.         if (!file.isFile()) {  
  17.             System.err.println("语料库不存在!终止程序!");  
  18.             System.exit(0);  
  19.         }  
  20.           
  21.         BufferedReader in = new BufferedReader(  
  22.                 new InputStreamReader(new FileInputStream(file), "utf-8"));  
  23.         String line = in.readLine();  
  24.         int totalFreq = Integer.parseInt(line);  
  25.           
  26.         tire = new TireNode();  
  27.           
  28.         while ((line = in.readLine()) != null) {  
  29.             String[] segs = line.split(" ");  
  30.             String word = segs[0];  
  31.             int freq = Integer.parseInt(segs[1]);  
  32.               
  33.             TireNode root = tire;  
  34.             for (int i = 0; i < word.length(); i++) {  
  35.                 String c = "" + word.charAt(i);  
  36.                 TireNode node = root.getChild(c);  
  37.                 if (node == null) {  
  38.                     node = new TireNode();  
  39.                     node.setCharacter(c);  
  40.                     root.addChild(node);  
  41.                 }  
  42.                 root = node;  
  43.             }  
  44.               
  45.             root.setFrequency(freq);  
  46.             root.setAntilog(Math.log((double)totalFreq / freq));  
  47.         }  
  48.         in.close();  
  49.     }  
  50.   
  51.     public TireNode getTire() {  
  52.         return tire;  
  53.     }  
  54.       
  55.     public TireNode getNodeByWord(String word) {  
  56.         if (tire == null) {  
  57.             System.err.println("需要先初始化ChnSeq对象!");  
  58.             return null;  
  59.         }  
  60.           
  61.         TireNode node = tire;  
  62.         for (int i = 0; i < word.length(); i++) {  
  63.             String ch = word.charAt(i) + "";  
  64.             if (node == null) {  
  65.                 break;  
  66.             } else {  
  67.                 node = node.getChild(ch);  
  68.             }  
  69.         }  
  70.           
  71.         return node;  
  72.     }  
  73.       
  74.     private class Segment {  
  75.         public String word;  
  76.         public String endChar;  
  77.         public String lastChar;  
  78.         public double cost;  
  79.           
  80.         public final static String START_SIGN = "<< STARTING >>";  
  81.         public final static String END_SIGN = "<< ENDING >>";  
  82.     }  
  83.       
  84.     private List<Segment> preSegment(String sentence) {  
  85.         List<Segment> segs = new ArrayList<Segment>();  
  86.           
  87.         Segment terminal = new Segment();  
  88.         terminal.word = Segment.START_SIGN;  
  89.         terminal.endChar = Segment.START_SIGN;  
  90.         terminal.lastChar = null;  
  91.         segs.add(terminal);  
  92.         for (int i = 0; i < sentence.length(); i++) {  
  93.             for (int j = i + 1; j <= sentence.length(); j++) {  
  94.                 String word = sentence.substring(i, j);  
  95.                 TireNode tnode = this.getNodeByWord(word);  
  96.                 if (tnode == null) {  
  97.                     break;  
  98.                 }  
  99.                 if (tnode.getFrequency() <= 0) {  
  100.                     continue;  
  101.                 }  
  102.                   
  103.                 Segment seg = new Segment();  
  104.                 seg.word = word;  
  105.                 seg.endChar = word.substring(word.length() - 1, word.length());  
  106.                 if (i == 0) {  
  107.                     seg.lastChar = Segment.START_SIGN;  
  108.                 } else {  
  109.                     seg.lastChar = sentence.substring(i - 1, i);  
  110.                 }  
  111.                 seg.cost = tnode.getAntilog();  
  112.                 segs.add(seg);  
  113.             }  
  114.         }  
  115.         terminal = new Segment();  
  116.         terminal.word = Segment.END_SIGN;  
  117.         terminal.endChar = Segment.END_SIGN;  
  118.         terminal.lastChar = sentence.substring(sentence.length() - 1, sentence.length());  
  119.         segs.add(terminal);  
  120.           
  121.         return segs;  
  122.     }  
  123.       
  124.     private String[] dynamicSegment(List<Segment> segs) {  
  125.         final double INFINITE = 9999999;  
  126.           
  127.         if (segs == null || segs.size() == 0) {  
  128.             return null;  
  129.         }  
  130.               
  131.         int n = segs.size();  
  132.           
  133.         double[][] costs = new double[n][n];  
  134.         for (int i = 0; i < n; i++) {  
  135.             for (int j = 0; j < n; j++) {  
  136.                 costs[i][j] = INFINITE;  
  137.             }  
  138.         }  
  139.           
  140.         for (int i = 0; i < n; i++) {  
  141.             String endChar = segs.get(i).endChar;  
  142.             for (int j = 0; j < n; j++) {  
  143.                 String lastChar = segs.get(j).lastChar;  
  144.                   
  145.                 if (lastChar != null && lastChar.equals(endChar)) {  
  146.                     costs[i][j] = segs.get(j).cost;  
  147.                 }  
  148.             }  
  149.         }  
  150.           
  151.         int sp = 0// starting point  
  152.         int fp = n - 1// finishing point  
  153.           
  154.         double[] dist = new double[n];  
  155.         List<List<Integer>> sPaths = new ArrayList<List<Integer>>();  
  156.         List<Integer> list = new ArrayList<Integer>();  
  157.         for (int i = 0; i < n; i++) {  
  158.             dist[i] = costs[sp][i];  
  159.             if (sp != i) {  
  160.                 list.add(i);  
  161.             }  
  162.             if (dist[i] < INFINITE) {  
  163.                 List<Integer> spa = new ArrayList<Integer>();  
  164.                 sPaths.add(spa);  
  165.             } else {  
  166.                 sPaths.add(null);  
  167.             }  
  168.         }  
  169.           
  170.         while (!list.isEmpty()) {  
  171.             Integer minIdx = list.get(0);  
  172.             for (int i: list) {  
  173.                 if (dist[i] < dist[minIdx]) {  
  174.                     minIdx = i;  
  175.                 }  
  176.             }  
  177.               
  178.             list.remove(minIdx);  
  179.               
  180.             for (int i = 0; i < n; i++) {  
  181.                 if (dist[i] > dist[minIdx] + costs[minIdx][i]) {  
  182.                     dist[i] = dist[minIdx] + costs[minIdx][i];  
  183.                     List<Integer> tmp = new ArrayList<Integer>(sPaths.get(minIdx));  
  184.                     tmp.add(minIdx);  
  185.                     sPaths.set(i, tmp);  
  186.                 }  
  187.             }  
  188.         }  
  189.           
  190.         String[] result = new String[sPaths.get(fp).size()];  
  191.         for (int i = 0; i < sPaths.get(fp).size(); i++) {  
  192.             result[i] = segs.get(sPaths.get(fp).get(i)).word;  
  193.         }  
  194.         return result;  
  195.     }  
  196.       
  197.     public String[] segment(String sentence) {  
  198.         return dynamicSegment(preSegment(sentence));  
  199.     }  
  200. }  

3.   测试代码

 

[java]  view plain copy
  1. package chn.seg;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. public class Main {  
  6.   
  7.     public static void main(String[] args) throws ClassNotFoundException, IOException {  
  8.         ChnSeq cs = new ChnSeq();  
  9.         cs.init();  
  10.   
  11.         String sentence = "生活的决定权也一直都在自己手上";  
  12.           
  13.         String[] segs = cs.segment(sentence);  
  14.         for (String s: segs) {  
  15.             System.out.print(s + "\t");  
  16.         }  
  17.     }  
  18.   
  19. }  

这篇关于基于Tire树和最大概率法的中文分词功能的Java实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/162627

相关文章

Java中StopWatch的使用示例详解

《Java中StopWatch的使用示例详解》stopWatch是org.springframework.util包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比,这篇文章主要介绍... 目录stopWatch 是org.springframework.util 包下的一个工具类,使用它

Java进行文件格式校验的方案详解

《Java进行文件格式校验的方案详解》这篇文章主要为大家详细介绍了Java中进行文件格式校验的相关方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、背景异常现象原因排查用户的无心之过二、解决方案Magandroidic Number判断主流检测库对比Tika的使用区分zip

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

SpringIntegration消息路由之Router的条件路由与过滤功能

《SpringIntegration消息路由之Router的条件路由与过滤功能》本文详细介绍了Router的基础概念、条件路由实现、基于消息头的路由、动态路由与路由表、消息过滤与选择性路由以及错误处理... 目录引言一、Router基础概念二、条件路由实现三、基于消息头的路由四、动态路由与路由表五、消息过滤

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

springboot security使用jwt认证方式

《springbootsecurity使用jwt认证方式》:本文主要介绍springbootsecurity使用jwt认证方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录前言代码示例依赖定义mapper定义用户信息的实体beansecurity相关的类提供登录接口测试提供一

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu