自然语言处理基于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;    }}
1 0
原创粉丝点击