您的位置:首页 > 编程语言 > Java开发

自然语言处理基于java实现(2) 之 词性标注

2017-04-05 20:51 483 查看
一. 题目如下:

基于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++){

4000
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      d
r       0    0.3    0.5     0
n       0.2  0      0.6     0.2
v       0.5  0.4     0      0
d       0.1  0.2    0.1     0


发射概率

发射概率   我      明天           去       北京
r       0.5         0          0        0.4
n       0.2         0.5        0        0.4
v       0           0         0.5       0
d       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));

b71a
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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: