自然语言处理基于java实现(2) 之 词性标注
来源:互联网 发布:算法竞赛入门书籍 编辑:程序博客网 时间:2024/05/16 05:25
一. 题目如下:
基于199801人民日报语料,编写一个基于HMM的词性标注程序。
(1)首先将语料分成测试集与训练集,在训练集上统计初始概率、发射概率、转移概率估算所需的参数。
(2)利用Viterbi算法,实现基于HMM的词性标注程序。
(3)编写评价程序,计算词性标注的准确率
人民日报语料部分如下:
19980101-01-001-004/m 12月/t 31日/t ,/w 中共中央/nt 总书记/n 、/w 国家/n 主席/n 江/nr 泽民/nr 发表/v 1998年/t 新年/t 讲话/n 《/w 迈向/v 充满/v 希望/n 的/u 新/a 世纪/n 》/w 。/w (/w 新华社/nt 记者/n 兰/nr 红光/nr 摄/Vg )/w
19980101-01-001-005/m 同胞/n 们/k 、/w 朋友/n 们/k 、/w 女士/n 们/k 、/w 先生/n 们/k :/w
19980101-01-001-006/m 在/p 1998年/t 来临/v 之际/f ,/w 我/r 十分/m 高兴/a 地/u 通过/p [中央/n 人民/n 广播/vn 电台/n]nt 、/w [中国/ns 国际/n 广播/vn 电台/n]nt 和/c [中央/n 电视台/n]nt ,/w 向/p 全国/n 各族/r 人民/n ,/w 向/p [香港/ns 特别/a 行政区/n]ns 同胞/n 、/w 澳门/ns 和/c 台湾/ns 同胞/n 、/w 海外/s 侨胞/n ,/w 向/p 世界/n 各国/r 的/u 朋友/n 们/k ,/w 致以/v 诚挚/a 的/u 问候/vn 和/c 良好/a 的/u 祝愿/vn !/w
二. 解决思路:
1. 文本切割分词
2. 计算HMM三个参数
3. viterbi算法实现词性标注
4. 评价程序
四. 实现步骤
1.字符串切割分词
// 分割后的词(如:迈向/v)
String[] text = content.toString().split(“\s{1,}”);
// 分割后的词性(如:v)
String[] characters = content.split(“[0-9|-]/|\s{1,}[^a-z]“);
2.统计:详见代码
/** * 统计语料库所有词性的个数 * @param temp * @return */ private static Map<String, Integer> createAllNumOfS(String[] temp){ Map<String, Integer> all = new HashMap<String,Integer>(); all.clear(); for(int i=0;i<temp.length;i++){ temp[i] = temp[i].toLowerCase().replaceAll("[^a-z]", "").trim(); if(temp[i].length()>2){ temp[i] = temp[i].substring(0, 1); } if(temp[i]!=""){ all.put(temp[i], all.getOrDefault(temp[i], 0)+1); } } final Map<String,Integer> map =new HashMap<String,Integer>(all); //去除垃圾项 all.forEach((key,value)->{if (value<100) { total--; map.remove(key); }}); return map; }
3.构建HMM三个概率
1) 初始概率: 如何计算初始概率?
(此处,我没有深入研究,我采取了简易做法,错了勿怪)
比如,假设一篇文章为: 我/r 明天/n 去/v 北京/n
那么: r的初始概率为0.25
n的初始概率为0.5
v的初始概率为0.25
**//初始概率 <词性,概率>private Map<String,Double> initial = new HashMap<>();**initial.put("r",0.25);...
2) 转移概率: 如何计算转移概率?
上题中,r出现1次,n出现2次,v出现一次
r n出现1次,n v出现1次,v n出现一次
所以r n的转移概率为: 1/(r出现次数) = 1
转移概率表 r n v
r 0 1 0
n 0 0 0.5
v 0 1 0
**//转移概率 <前一个词性,后一个词性,概率>private Table <String,String,Double> transition = HashBasedTable.create();**比如:r n转移概率:transition.put("r","n",1.0);
3) 发射概率: 如何计算发射概率?
这个比较简单,举个例子:
假设一篇文章中: “希望”作为名词n出现5次,作为动词v出现7次,文章一共有名词500个,动词140个
则 希望 n 的发射概率为 5/500 = 0.01;
则 希望 v 的发射概率为 7/140 = 0.05;
**//发射概率 <单词, 词性 ,概率>private Table <String,String,Double> emission = HashBasedTable.create();**如 希望 n 的发射概率: emission.put("希望","n",0.01);
4.viterbi算法求词性标注:怎么一回事?
举个例子: 我 明天 去 北京
我们要做的事情,就是求出”我明天去北京”的词性,当然,人工求的答案是”r n v n”
现在要让计算机根据前面的HMM三个概率求出来这个答案
这里,就引出了viterbi算法:
现在,假设我的HMM概率表中部分概率如下:
r初始概率为: 0.4
n初始概率为: 0.5
转移概率
转移概率 r n v dr 0 0.3 0.5 0n 0.2 0 0.6 0.2v 0.5 0.4 0 0d 0.1 0.2 0.1 0
发射概率
发射概率 我 明天 去 北京r 0.5 0 0 0.4n 0.2 0.5 0 0.4v 0 0 0.5 0d 0 0.2 0.1 0
viterbi算法:2-4都是计算转移×发射×上一步的概率P,只保留×同一P的最大值(即最优值)
1. 计算 “我”(初始×发射概率): r(0.4×0.5)=0.2 | n(0.5×0.2)=0.1
2. “明天”(转移×发射)×”我”: d(0×0.2)×0.2=0 n(0.03) | d(0.004) n(0)
3. “去”×”明天”(保留n(0.03)&d(0.004)): d(0.0006) v(0.009)| d(0) v(0.0002)
4. “北京”×”去”,同理: r(0.0018) n(0.00144) | r(0.0004) n(0.00032)
5. 比较选出最大结果r(0.0018),最后回溯得到逆序列:r,v,n,r
6. 最终计算结果: r,n,v,r这就是viterbi计算结果,但它并不一定是标准结果
四. 程序代码
本程序用到了guava.jar包,需要的可以去百度云盘上下载
1) HMM数据结构以及viterbi算法
package experiment2;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import com.google.common.collect.HashBasedTable;import com.google.common.collect.Table;/** * HMM参数表 * 实现了Viterbi算法 */public class HMM { //初始概率 private Map<String,Double> initial = new HashMap<>(); //转移概率 private Table <String,String,Double> transition = HashBasedTable.create(); //发射概率 private Table <String,String,Double> emission = HashBasedTable.create(); @Override public String toString() { return initial.toString()+"\n"+transition.toString()+"\n"+emission.toString(); } //get,set方法 public void setInitial(Map<String, Double> initial) { this.initial = initial; } public void setTransition(Table<String, String, Double> transition) { this.transition = transition; } public void setEmission(Table<String, String, Double> emission) { this.emission = emission; } public Map<String, Double> getInitial() { return initial; } public Table<String, String, Double> getTransition() { return transition; } public Table<String, String, Double> getEmission() { return emission; } /** * 计算viterbi算法 * @param strs * @return */ public String viterbi(String...strs){ if(strs==null) return null; else if(strs.length==1) return viterbiMin(viterbiMap(emission.row(strs[0]),initial)).getKey(); else{ return Arrays.toString(viterbi(strs,1,viterbiMap(emission.row(strs[0]),initial))); } } /** * 递归回溯法寻找最佳可能 * @param strs * @param i * @param map * @return */ private String[] viterbi(String[] strs, int i, Map<String, Double> preMap) { Map<String,Double> nowMap = new HashMap<String,Double>(); Map<String,String> preAndNow = new HashMap<String,String>(); //寻找当前strs[i]所有可能情况的每一种最佳选择 for(Entry<String, Double> now:emission.row(strs[i]).entrySet()){ double min = Double.MAX_VALUE; String best = null; for(Entry<String, Double> pre:preMap.entrySet()){ double value = viterbiCaculate(strs[i],now,pre); if(value<min){ min = value; best = pre.getKey(); } } preAndNow.put(now.getKey(),best); nowMap.put(now.getKey(), min); } String []list = null; //最后一层 if(i==strs.length-1){ String key = viterbiMin(nowMap).getKey(); list = new String[strs.length]; list[i] = key; list[i-1] = preAndNow.get(key); }else{ list = viterbi(strs,++i,nowMap); list[i-2]=preAndNow.get(list[i-1]); } return list; } /** * 获取map里的最小值 * @param map * @return */ private Entry<String,Double> viterbiMin(Map<String, Double> map) { List<Entry<String,Double>> list = new ArrayList<>(map.entrySet()); Collections.sort(list,new Comparator<Entry<String,Double>>() { @Override public int compare(Entry<String, Double> o1, Entry<String, Double> o2) { return o1.getValue()>o2.getValue()?1:-1; } }); return list.get(0); } /** * 所有map1和map2相同key对应的value求对数后相加,返回一个新的map存储 * 一般用于求第一个数的viterbi算法的值 * @param map1 * @param map2 * @return */ private Map<String, Double> viterbiMap(Map<String, Double> map1, Map<String, Double> map2) { Map<String,Double> map = new HashMap<String,Double>(); for(Entry<String,Double> entry:map1.entrySet()){ double value = Math.log(entry.getValue())+Math.log(map2.get(entry.getKey())); map.put(entry.getKey(), -value); } return map; } /** * 计算转移概率对数+发射概率对数+pre的值的和 * @param word * @param now * @param pre * @return */ private double viterbiCaculate(String word, Entry<String, Double> now, Entry<String, Double> pre) { return Math.log(transition.get(pre.getKey(), now.getKey()))+Math.log(now.getValue())+pre.getValue(); }}
2) HMM构造工厂
package experiment2;import java.util.HashMap;import java.util.Map;import com.google.common.collect.HashBasedTable;import com.google.common.collect.Table;import com.google.common.collect.Table.Cell;/** * HMM工厂 */public class HMMFactory { //词性标注统计集 private static Map<String, Integer> allNumOfS; //统计的总个数 private static int total = 0; public static HMM createHMM(String content){ // 分割后的词(如:迈向/v) String[] text = content.toString().split("\\s{1,}"); // 分割后的词性(如:v) String[] characters = content.split("[0-9|-]*/|\\s{1,}[^a-z]*"); //生产词性标注统计容器 allNumOfS = createAllNumOfS(characters); total = characters.length; //生产一个HMM对象 HMM hmm = new HMM(); //生产初始概率 hmm.setInitial(createInitial()); //生产发射概率 hmm.setEmission(createEmission(text)); //生产转移概率 hmm.setTransition(createTransition(characters)); return hmm; } /** * 转移概率 * @param characters * @return */ private static Table<String, String, Double> createTransition(String[] characters) { Table<String,String,Integer> tranTotal = HashBasedTable.create(); Table<String,String,Double> transition = HashBasedTable.create(); String previous = null; String now = null; for(int i = 0 ;i<characters.length;i++){ now = characters[i].trim(); if(now.equals("")) continue; if(allNumOfS.containsKey(now)&&allNumOfS.containsKey(previous)){ if(tranTotal.contains(previous, now)) tranTotal.put(previous, now,tranTotal.get(previous, now)+1); else{ tranTotal.put(previous, now, 1); } } previous = now; } for(String rowKey:tranTotal.rowKeySet()){ for(String columnKey:tranTotal.row(rowKey).keySet()){ transition.put(rowKey, columnKey, ((double) tranTotal.get(rowKey, columnKey)) /allNumOfS.get(rowKey)); } } return transition; } /** * 发射概率 * @param text * @return */ private static Table<String, String, Double> createEmission(String[] text) { Table<String,String,Integer> emisTotal = HashBasedTable.create(); Table<String,String,Double> emission = HashBasedTable.create(); for(int i = 0;i<text.length;i++){ String s1[] = text[i].trim().split("/"); if(s1.length==2&&allNumOfS.containsKey(s1[1].trim())){ if(emisTotal.contains(s1[0], s1[1])){ emisTotal.put(s1[0], s1[1], emisTotal.get(s1[0], s1[1])); }else{ emisTotal.put(s1[0], s1[1], 1); } } } for(Cell<String,String,Integer> cell:emisTotal.cellSet()){ emission.put(cell.getRowKey(), cell.getColumnKey(), ((double)cell.getValue())/allNumOfS.get(cell.getColumnKey())); } return emission; } /** * 初始概率 * @return */ private static Map<String, Double> createInitial() { Map<String,Double> initial = new HashMap<String,Double>(); for (Map.Entry<String, Integer> entry : allNumOfS.entrySet()){ initial.put(entry.getKey(), ((double) entry.getValue())/total); } return initial; } /** * 统计语料库所有词性的个数 * @param temp * @return */ private static Map<String, Integer> createAllNumOfS(String[] temp){ Map<String, Integer> all = new HashMap<String,Integer>(); all.clear(); for(int i=0;i<temp.length;i++){ temp[i] = temp[i].toLowerCase().replaceAll("[^a-z]", "").trim(); if(temp[i].length()>2){ temp[i] = temp[i].substring(0, 1); } if(temp[i]!=""){ all.put(temp[i], all.getOrDefault(temp[i], 0)+1); } } final Map<String,Integer> map =new HashMap<String,Integer>(all); //去除垃圾项 all.forEach((key,value)->{if (value<100) { map.remove(key); }}); return map; }}
五.测试代码
package test;import experiment2.HMM;import experiment2.HMMFactory;import util.FileRW;/** *测试实验二 */public class Test2 { public static void main(String[] args) throws Exception { HMM hmm = HMMFactory.createHMM(FileRW.read("199801.txt").replaceAll("[0-9]{8}-[0-9]{2}-[0-9]{3}-[0-9]{3}/m", "")); System.out.println(hmm.getEmission()); String str[] = { "台湾 是 中国 领土 不可分割 的 部分", "这部 电视片 还 强调 表现 敦煌 文化 的 珍贵性 和 观赏性", "湖南 备耕 安排 早 动手 快", "我 第一 次 听到 这 首 歌 , 是 在 六 年 前 的 大年三十 春节 联欢 晚会 上"}; String s2[] ={ "[ns, r, ns, n, l, u, n]", "[r, n, v, d, v, ns, n, u, n, c, n]", "[ns, vn, v, a, v, a]", "[r, m, q, v, r, q, n, w, v, p, m, q, f, u, t, t, vn, n, f]"}; double value = 0; for(int i = 0;i<str.length;i++){ value = value + access(hmm.viterbi(str[i].split(" ")),s2[i]); } System.out.println("评分结果:"); System.out.printf("%.2f",value/str.length); } private static double access(String answer, String result) { System.out.println("计算结果: "+answer); System.out.println("真实结果: "+result); String[] s1 = answer.split(", "); String[] s2 = result.split(", "); double count = 0; for(int i = 0;i<s1.length;i++){ if(s1[i].equals(s2[i])) count++; } System.out.println("命中率: "+count/s1.length); System.out.println("======================================="); return count/s1.length; }}
- 自然语言处理基于java实现(2) 之 词性标注
- 自然语言处理基础技术之分词、向量化、词性标注
- 自然语言处理——自动标注词性
- 基于一阶HMM的中文词性标注(Java实现)
- 《统计自然语言处理基础》笔记(4)词性标注 之 作用和影响性能的主要因素
- HMM在自然语言处理中的应用一:词性标注
- 自然语言处理基于java实现(1) 之 中文分词
- 自然语言处理基于java实现(3) 之 信息检索
- 词性标注的python实现-基于平均感知机算法
- 词性标注的python实现-基于平均感知机算法
- python实现的基于hmm模型的词性标注系统
- 统计自然语言处理梳理一:分词、命名实体识别、词性标注
- 自然语言处理之文本标注问题
- 自然语言处理基于java实现(4) 之 基于VSM模型的信息检索程序
- 转载 基于HMM模型的词性标注
- Python自然语言处理——词性标记
- 词性标注
- 词性标注
- 富人俱乐部的度量
- Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误
- 挖掘频繁模式、关联和相关性:基本概念和方法
- 剑指offer--重建二叉树
- dfs 选数
- 自然语言处理基于java实现(2) 之 词性标注
- Java中try和catch的故事
- 【HDU】 1269 迷宫城堡 强连通分量
- Syntax error on token(s), misplaced construct(s)错误原因
- 一站式学习Wireshark(九):应用Wireshark显示过滤器分析特定数据流(上)
- Elasticsearch版本控制[并发安全]
- windows powershell中activate python2无效
- 傅里叶
- (四)利用矩阵对点云进行刚体变换