Java实现:抛开jieba等工具,写HMM+维特比算法进行词性标注
一、前言:词性标注
词性标注(Part-Of-Speech tagging, POS tagging),是语料库语言学中将语料库中单词的词性按其含义和上下文内容进行标记的文本数据处理技术。词性标注可以由人工或特定算法完成,使用机器学习(machine learning)方法实现词性标注是自然语言处理(NLP)的研究内容。常见的词性标注算法包括隐马尔可夫模型(Hidden Markov Model, HMM)、条件随机场(Conditional random fields, CRFs)等。
在进入本篇算法的应用和实践之前,建议学习以下两篇内容,会有更好更容易的理解。
1、隐马尔可夫模型(HMM)来龙去脉(一)(https://www.cnblogs.com/chenzhenhong/p/13537680.html)
2、隐马尔可夫模型(HMM)来龙去脉(二)(https://www.cnblogs.com/chenzhenhong/p/13592058.html)
本篇实践的目标:
除了用jieba等分词词性标注工具,不如自行写一个算法实现同样的功能,这个过程可以对理论知识更好地理解和应用。下面将详细介绍使用HMM+维特比算法实现词性标注。在给定的单词发射矩阵和词性状态转移矩阵,完成特定句子的词性标注。
二、经典维特比算法(Viterbi)
词性标注使用隐马尔可夫模型原理,结合维特比算法(Viterbi),具体算法伪代码如下:

维特比算法正是解决HMM的三个基本问题中的第二个问题:在给定的观察序列中,找出最优的隐状态序列。应用在词性标注上,就是找到概率最大化的单词的词性。
下面是对维特比算法更加容易的解释:
- 观察序列长度 T,状态个数N
- for 状态s from 1 to N:do
- //计算每个状态的概率,相当于计算第一观察值的隐状态t=1
- v[s,1] = a(0,s)*b(O1|s) //初始状态概率 * 发射概率
- //回溯保存最大概率状态
- back[s,1]=0
- //计算每个观察(词语)取各个词性的概率,保存最大者
- for from 观察序列第二个 to T do:
- for 状态s from 1 to N:do
- //当前状态由前一个状态*转移*发射(该状态/词性下词t的概率),保存最大者
- v[s,t]=max v[i,t-1]*a[i,s]*b(Ot | s)
- //保存回溯点,该点为前一个状态转移到当前状态的最大概率点
- back[s,t]=arg{1,N} max v[i,t-1]*a(i,s)
- //最后
- v[T]=max v[T]
- back[T] = arg{1,N} max v[T]
- //回溯输出隐状态序列
三、算法实现
第一步,拆分算法计算问题,计算状态转移概率矩阵和符号发射概率矩阵方法:
根据给出的单词出现次数和词性状态矩阵,使用computeProp()方法计算得到发射概率矩阵和状态转移矩阵。
public void computeProp(float[][] A) {//计算概率矩阵
int i, j;
float[] t = new float[A.length];
//平滑数据,对数组每个元素值加一
for (i = 0; i < A.length; i++) {
for (j = 0; j < A[i].length; j++) {
A[i][j] += 1;
t[i] += A[i][j];
}
}
//计算当前元素在该行中的概率
for (i = 0; i < A.length; i++)
for (j = 0; j < A[i].length; j++)
A[i][j] /= t[i];
}
得到状态转移概率矩阵如下:

得到符号发射概率矩阵如下:

第二步,核心算法。本程序的关键部分是维特比算法的实现,计算得到最大概率的隐状态,然后保存最佳状态转移位置。对于每个观察值,先计算对应的可能的隐状态。
public int[] Viterbi(float[][] A, float[][] B, String[] O,double[] init) {
int back[][] = new int[A.length][O.length];
float V[][] = new float[A.length][O.length];
int i, s, k, t;
for (i = 0; i < A.length; i++) {
V[i][0] = (float) (init[i] * B[i][0]);
back[i][0] = i;
}
//计算每个观察值的取隐状态中的最大概率
for (t = 1; t < O.length; t++) {
int[][] path = new int[A.length][O.length];
//遍历每个隐状态,计算状态转移到当前状态的概率,得到最大概率状态
for (s = 0; s < A.length; s++) {
float maxSProb = -1;
int preS = 0;
for (i = 0; i < A.length; i++) {
float prob = V[i][t - 1] * A[i][s] * B[s][t];//B[s][t]为隐状态s到观察值t的发射概率
if (prob > maxSProb) {
maxSProb = prob;
preS = i;
}
}
//保存该状态的最大概率
V[s][t] = maxSProb;
//记录路径
System.arraycopy(back[preS],0,path[s],0,t);
path[s][t]=s;//最大概率状态转移记录
}
back=path;//更新最优路径
}
//回溯路径,找到最后状态
float maxP = -1;
int lastS = 0;
for (s = 0; s < A.length; s++) {
if (V[s][O.length - 1] > maxP) {
maxP = V[s][O.length - 1];
lastS = s;
}
}
return back[lastS];//返回最佳路径
}
以上是维特比算法,重要的代码语句解析可见注释。算法实现了将待标注句子使用维特比算法计算最大概率,得到最佳路径。
网上大部分使用了python实现该算法,python写起来简单,所以我尝试使用java实现,略有不同,期间遇到了一些小问题,后来不断debug解决问题。得到正确的java编写的维特比算法。
四、完整代码

/*
* hmm_viterbi.java
* Copyright (c) 2020-10-17
* author : Charzous
* All right reserved.
*/ public class hmm_viterbi { public int[] Viterbi(float[][] A, float[][] B, String[] O) {
int back[][] = new int[A.length][O.length];
float V[][] = new float[A.length][O.length];
double[] init = {0.2, 0.1, 0.1, 0.2, 0.3, 0.1};
int i, s, k, t; for (i = 0; i < A.length; i++) {
V[i][0] = (float) (init[i] * B[i][0]);
back[i][0] = i;
}
//计算每个观察值的取隐状态中的最大概率
for (t = 1; t < O.length; t++) {
int[][] path = new int[A.length][O.length];
//遍历每个隐状态,计算状态转移到当前状态的最大概率
for (s = 0; s < A.length; s++) {
float maxSProb = -1;
int preS = 0;
for (i = 0; i < A.length; i++) {
float prob = V[i][t - 1] * A[i][s] * B[s][t];//B[s][t]为隐状态s到观察值t的发射概率
if (prob > maxSProb) {
maxSProb = prob;
preS = i;
}
}
//保存该状态的最大概率
V[s][t] = maxSProb;
//记录路径
System.arraycopy(back[preS],0,path[s],0,t);
path[s][t]=s;
}
back=path;
} //回溯路径
float maxP = -1;
int lastS = 0;
for (s = 0; s < A.length; s++) {
if (V[s][O.length - 1] > maxP) {
maxP = V[s][O.length - 1];
lastS = s;
}
}
return back[lastS];
} public void computeProp(float[][] A) {
int i, j;
float[] t = new float[A.length];
//平滑数据,对数组每个元素值加一
for (i = 0; i < A.length; i++) {
for (j = 0; j < A[i].length; j++) {
A[i][j] += 1;
t[i] += A[i][j];
}
}
//计算当前元素在该行中的概率
for (i = 0; i < A.length; i++)
for (j = 0; j < A[i].length; j++)
A[i][j] /= t[i]; System.out.println();
// for (i = 0; i < A.length; i++) {
// for (j = 0; j < A[i].length; j++)
// System.out.print(A[i][j] + " ");
// System.out.println();
// } } public static void main(String[] args) {
//状态转移矩阵
float A[][] = {{0, 0, 0, 48636, 0, 19}, {1973, 0, 426, 187, 0, 38}, {43322, 0, 1325, 17314, 0, 185}, {1067, 3720, 42470, 11773, 614, 21392}, {6072, 42, 4758, 1476, 129, 1522}, {8016, 75, 4656, 1329, 954, 0}};
//发射矩阵
float B[][] = {{0, 0, 0, 0, 0, 0, 69016, 0}, {0, 10065, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 5484, 0, 0, 0, 0}, {10, 0, 36, 0, 382, 108, 0, 0}, {43, 0, 133, 0, 0, 4, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 48809}}; int i, j;
//语料库词语
String[] word = {"bear", "is", "move", "on", "president", "progress", "the", "."};
//待标注句子
String O[] = {"The", "bear", "is", "on", "the", "move", "."};
//语料库词性
String Q[] = {"/AT ", "/BEZ ", "/IN ", "/NN ", "/VB ", "/PERIOD "};
String seq="Bear move on the president .";
String Os[]=seq.split(" ");
for (String w:O)
System.out.println(w); float emission[][] = new float[B.length][O.length]; hmm_viterbi hmmViterbi = new hmm_viterbi();
hmmViterbi.computeProp(A); //计算观察序列的转移矩阵
//根据待标注句子的词计算出每个单词的出现次数矩阵
for (i = 0; i < O.length; i++) {
int r = 0;
for (int t = 0; t < word.length; t++) {
if (O[i].equalsIgnoreCase(word[t]))
r = t;
}
for (j = 0; j < B.length; j++)
emission[j][i] = B[j][r];
}
hmmViterbi.computeProp(emission);
int path[];
path=hmmViterbi.Viterbi(A, emission, O); for (i=0;i<O.length;i++){
System.out.print(O[i]+Q[path[i]]);
} // for (int p:path)
// System.out.print(p+" "); }
}
五、效果演示:
对于本实验的词性标注,简单设计了交互界面,方面测试不同句子的标注结果。在给定的测试句子”The bear is on the move .”上,实验结果如下:
The/AT bear/NN is/BEZ on/IN the/AT move/NN ./PERIOD

然后根据语料库自己造了一个句子,仅供测试用:”The president is bear .”实验结果:The/AT president/NN is/IN bear/NN ./PERIOD

感觉还可以,当然这只是一个例子,更确切需要更大的语料库。
六、总结
本篇详细介绍Java实现的HMM+维特比算法实现词性标注。在给定的单词发射矩阵和词性状态转移矩阵,完成特定句子的词性标注。这个任务可以在刚接触HMM和维特比算法进行词性标注作为实践,为之后实现特定语料库的词性标注铺垫。在完成本任务时,java编程实现算法时遇到了一些的问题,如:最佳路径的保存,回溯路径的返回。经过了一段时间的debug,实现了最基本的算法对句子进行词性标注。完成这个任务后,对HMM+Viterbi 算法的词性标注有了更深刻的理解,之后准备完成第三个任务:基于人民日报数据集的中文词性标注,可以对该算法进行更实际的应用,加深知识的理解。
我的博客园:https://www.cnblogs.com/chenzhenhong/p/13850687.html
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/109138830
Java实现:抛开jieba等工具,写HMM+维特比算法进行词性标注的更多相关文章
- HMM——维特比算法(Viterbi algorithm)
1. 前言维特比算法针对HMM第三个问题,即解码或者预测问题,寻找最可能的隐藏状态序列: 对于一个特殊的隐马尔可夫模型(HMM)及一个相应的观察序列,找到生成此序列最可能的隐藏状态序列. 也就是说给定 ...
- 隐马尔科夫模型HMM(四)维特比算法解码隐藏状态序列
隐马尔科夫模型HMM(一)HMM模型 隐马尔科夫模型HMM(二)前向后向算法评估观察序列概率 隐马尔科夫模型HMM(三)鲍姆-韦尔奇算法求解HMM参数 隐马尔科夫模型HMM(四)维特比算法解码隐藏状态 ...
- Java GUI图形界面开发工具
Applet 应用程序 一种可以在 Web 浏览器中执行的小程序,扩展了浏览器中的网页功能. 缺: 1.需要下载 Applet 及其相关文件 2.Applet 的功能是受限制的 优: 3.无需 ...
- java后台常用json解析工具问题小结
若排版紊乱可查看我的个人博客原文地址 java后台常用json解析工具问题小结 这里不细究造成这些问题的底层原因,只是单纯的描述我碰到的问题及对应的解决方法 jackson将java对象转json字符 ...
- 基于Java的简易表达式解析工具(二)
之前简单的介绍了这个基于Java表达式解析工具,现在把代码分享给大家,希望帮助到有需要的人们,这个分享代码中依赖了一些其他的类,这些类大家可以根据自己的情况进行导入,无非就是写字符串处理工具类,日期处 ...
- java中定义一个CloneUtil 工具类
其实所有的java对象都可以具备克隆能力,只是因为在基础类Object中被设定成了一个保留方法(protected),要想真正拥有克隆的能力, 就需要实现Cloneable接口,重写clone方法.通 ...
- java服务器访问其他服务器工具类编写
java服务器访问其他服务器工具类编写适合各种消息推送及微服务交互 package com.xiruo.medbid.components; import com.xiruo.medbid.util. ...
- JAVA程序员常用开发工具
1.JDK (Java Development Kit)Java开发工具集 SUN的Java不仅提了一个丰富的语言和运行环境,而且还提了一个免费的Java开发工具集(JDK).开发人员和最终用户可以利 ...
- java 文件读写的有用工具
java 文件读写的有用工具 package org.rui.io.util; import java.io.BufferedReader; import java.io.File; import j ...
随机推荐
- [LeetCode]547. 朋友圈(DFS)
题目 班上有 N 名学生.其中有些人是朋友,有些则不是.他们的友谊具有是传递性.如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友.所谓的朋友圈,是指所有朋友的集 ...
- [程序员代码面试指南]递归和动态规划-数字字符串转换为字母组合的种数(DP)
题意 给一个字符串,只由数字组成,若是'1'-'26',则认为可以转换为'a'-'z'对应的字母,问有多少种转换方法. 题解 状态转移很好想,注意dp多开一位,dp[0]为dp[2]的计算做准备.dp ...
- sql.表值类型
表值类型是在sql server 2008中新加入的功能,我个人觉得确实很有用,之前从没用过,自从来到现在的公司后学到很多东西,所以和大家分享一下. ------建表值类型语法------------ ...
- 高可用负载均衡集群——keepalive(1)
Keepalived介绍 keepalived 是一个类似于 layer3, 4 & 5 交换机制的软件,也就是我们平时说的第 3 层.第 4 层和第 5层交换. Keepalived 的作用 ...
- rocketmq-console修改logo,修改ip,修改port及完整编译安装图文版
一.下载源码到本地 这里使用IDEA,作为编译工具 https://gitee.com/mrliuNumberOne/rocketmq-externals.git 导入成功后如图: 二.Maven编译 ...
- 灵活使用 SQLAlchemy 中的 ORM 查询
之前做查询一直觉得直接拼 SQL 比较方便,用了 SQLAlchemy 的 ORM 查询之后,发现也还可以,还提高了可读性. 这篇文章主要说说 SQLAlchemy 常用的 ORM 查询方式,偏实践. ...
- nginx 1.12 HTTPS双向认证配置
使用openssl生成相关证书: #生成CA私钥,私钥会被加密,需要设置密码 openssl genrsa -aes256 -out ca.key 2048 #生成CA证书签名请求,需要输入CA私钥密 ...
- Redis 4.0.2安装与卸载
安装 使用root用户安装: 1.wget http://download.redis.io/releases/redis-4.0.2.tar.gz 2.tar -zxvf redis-4.0.2.t ...
- Centos-退出抽取设备-eject
eject 退出抽取设备,如光驱或磁带,如果设备已经挂载,则卸载设备 相关选项 -q 退出磁盘 -r 退出光盘 -d 显示默认设备
- 一种基于均值不等式的Listwise损失函数
一种基于均值不等式的Listwise损失函数 1 前言 1.1 Learning to Rank 简介 Learning to Rank (LTR) , 也被叫做排序学习, 是搜索中的重要技术, 其目 ...