隐马尔可夫模型的前向算法(手动实现),今天奉上,由于研究生期间,实现的时候没有多加注释,这里为了让更好的人进入自然语言处理领域,特此,将前向算法奉上,具体公式可参考52nlp的HMN系列博客。
参考了大部分网站公式和借鉴。在此表示感谢。
后向算法和维特比算法,后续更新。
HMM类:
1 package jxutcm.edu.cn.hmm.model;
2
3 import jxutcm.edu.cn.hmm.bean.HMMHelper;
4
5 /**
6 * 实现了 HMM(隐马尔可夫模型, Hidden Markov Models)的 前向(Forward), 后向(Backward),
7 * 前向-后向(Baum-Welch)算法 这里均计算对数概率(将乘法转换为加法)
8 * HMM五元素:λ=(N, M, A, B, pi)
9 * N:隐藏状态数 hidden states
10 * M:观测状态数 observed states
11 * A: 状态转移矩阵 transition matrix
12 * B:发射矩阵 emission matrix
13 * pi:初始隐状态向量 initial state vector
14 * P(q_t|q_t-1)
15 * 隐藏状态: q1 ——>q2 ——> ……——>q_t-1——>q_t——>——>q_T
16 * | | | | |
17 * | | | | |
18 * 显示状态:O1 O2 O_t-1 O_t O_T
19 * 隐数目——N个;显数目——M个
20 */
21 public class HMM {
22 /**
23 * 隐藏状态名称列表——在获取最优隐藏状态序列时需要
24 * 如enum Box {one, two, three}; // 隐藏状态(箱子编号)
25 */
26 public String[] state;
27 /**
28 * 观察状态名称列表——在根据观测索引获取获取观测序列时需要
29 * enum Color {red, yellow, blue, green}; // 观察状态(观测到的颜色值)
30 */
31 public String[] syms;
32
33 public int N;
34 /**
35 * 观测状态数 observed states
36 * 观测状态名称叫syms
37 */
38 public int M;
39 /**
40 * 隐藏状态转移矩阵
41 * logA[ i ][ j ] = log(P( i -> j )) 表示从i状态转移到 j 状态的概率——取自然对数
42 */
43 public double[][] logA;
44 /**
45 * 发射矩阵(混淆矩阵)
46 * logB[ i ][Ot] = log(P(emit Ot in state i)) 表示状态 i 下发射出 Ot 的概率——Ot为第t时刻的显示序列单号
47 */
48 public double[][] logB;
49 /**
50 * 初始状态概率分布——每个隐藏状态发生的概率
51 */
52 public double[] logPI;
53
54 /**
55 * 观察到的序列
56 */
57 //public int[] O;//如yellow red blue yellow green 这些在enum Color {red,yellow,blue,green }的索引位置
58
59 public HMM(){}
60
61 public HMM(int N, int M){
62 this.N=N;
63 this.M=M;
64 this.logA=new double[N][N];
65 this.logB=new double[N][M];
66 this.logPI=new double[N];
67 for(int i=0; i<N; i++){
68 for(int j=0; j<N; j++){
69 this.logA[ i ][ j ] = Double.NEGATIVE_INFINITY;
70 }
71 for(int j=0; j<M; j++){
72 this.logB[ i ][ j ]=Double.NEGATIVE_INFINITY;
73 }
74 this.logPI[ i ] = Double.NEGATIVE_INFINITY;
75 }
76 }
77
78 /**
79 * (已经有具体HMM的各个参数情况下调用)
80 * @param state隐藏状态名称
81 * enum Box {one,two,three }
82 * @param syms 观测状态名称
83 * enum Color {red,yellow,blue,green}
84 * @param A
85 * 隐藏状态的转移矩阵
86 * 1 2 3
87 * one two three
88 * one {0.500, 0.375, 0.125}
89 * two {0.250, 0.125, 0.625},
90 * three{0.250, 0.375, 0.375}
91 * @param B 从隐藏状态——观察状态的发射矩阵(混淆矩阵)
92 * 0 1 2 3
93 * red yellow blue green
94 * one {0.60, 0.20, 0.15, 0.05},
95 * two {0.25, 0.25, 0.25, 0.25},
96 * three {0.05, 0.10, 0.35, 0.50}
97 */
98 public HMM(double[][] A, double[][] B, double[] PI){
99 this.N=A.length;
100 this.M=B[0].length;
101 this.logA=new double[N][N];
102 this.logB=new double[N][M];
103 this.logPI=new double[N];
104 for(int i=0; i<N; i++){
105 for(int j=0; j<N; j++){
106 this.logA[ i ][ j ] = Math.log( A[ i ][ j ] );
107 }
108 for(int j=0; j<M; j++){
109 this.logB[ i ][ j ]=Math.log( B[ i ][ j ] );
110 }
111 this.logPI[ i ] = Math.log( PI[ i ] );
112 }
113 }
114
115 /**
116 * 打印转移矩阵
117 */
118 public void printA() {
119 System.out.println("转移矩阵:");
120 for (int i = 1; i < N; i++) {
121 for (int j = 1; j < N; j++){
122 System.out.print(HMMHelper.fmtlog(logA[i][j]));
123 }
124 System.out.println();
125 }
126 }
127
128 /**
129 * 打印发射矩阵
130 */
131 public void printB() {
132 System.out.println("发射矩阵:");
133 for (int i = 1; i < N; i++) {
134 for (int j = 1; j < N; j++){
135 System.out.print(HMMHelper.fmtlog(logB[i][j]));
136 }
137 System.out.println();
138 }
139 }
140
141
142 }
Forward类:
1 package jxutcm.edu.cn.hmm.model;
2
3 import jxutcm.edu.cn.hmm.bean.HMMHelper;
4 import jxutcm.edu.cn.util.TCMMath;
5
6 /**
7 * 前向算法:
8 * 目的:
9 * 1、先计算前向变量矩阵
10 * 2、再用前向变量矩阵 来 计算一个观测序列的概率
11 * @author aool
12 */
13 public class Forward extends HMM{
14 public int[] O;//观测序列observe//如yellow red blue yellow green 这些在enum Color {red,yellow,blue,green }的索引位置
15
16 public double[][] alpha; //前向变量矩阵
17
18 public Forward(double[][] A, double[][] B, double[] PI, int[] O){
19 super(A, B, PI);
20 this.O=O;
21 }
22
23 /**
24 * 【计算前向变量矩阵】
25 * 在时间 t 的条件下,hmm输出观察序列O(1)O(2)...O(t)且该时间t下的隐藏状态为s_i(第i个隐藏状态,共N种隐藏状态)的概率
26 * alpha[ t ][ i ] = alpha_t( i ) = log(P(O(1)O(2)...O(t), q_t=s_i | λ))
27 */
28 public void CalculateForeMatrix(){
29 int T = O.length;
30 alpha = new double[ T ][ N ];//每一时刻(每行)上 可能出现的多个状态的发生的前向变量概率
31 //1、初始化,计算初始时刻(直觉上的第1时刻)所有状态的局部概率
32 for (int i = 0; i < N ; i++){
33 alpha[ 0 ][ i ] = logPI[ i ] + logB[ i ][ O[ 0 ] ];
34 }
35 //2、归纳,递归计算每个时间点的局部概率
36 for (int t = 1; t < T; t++){//从(直觉上的第2时刻)即t=1(下标从0开始)观测值算起——第时间t下开始循环
37 for (int i = 0; i < N; i++) {//在时间t下,对于每个状态s_i——计算到时间t+1的前向变量概率
38 double sum = Double.NEGATIVE_INFINITY; // = log(0)
39 for (int j = 0; j < N; j++){//到第 j 种隐状态下的累计概率
40 sum = TCMMath.logplus( sum, alpha[t - 1][ j ] + logA[ j ][ i ]);
41 }
42 alpha[ t ][ i ] = logB[ i ][ O[ t ] ] + sum;//在 【t 时刻】 下 输出观察序列 O1O2……Ot(已知观测序列的局部) 且位于第 i 种隐藏状态发生的概率
43 }
44 }
45 }
46
47 /**
48 * 【计算观测序列的概率】——前提是先计算前向变量矩阵
49 * P( O | μ ) = ∑alpha_T( i ) (求和上界N,求和下界i=1)
50 * @return 返回的结果是概率的自然对数
51 * 计算 t=T 时刻下输出观察序列 O0……OT(已经观测序列的局部)且位于第 T 状态下发生的概率
52 */
53 public double logProb() {
54 //3.终止,观察序列的概率等于最终时刻( T )所有局部概率之和
55 double sum = Double.NEGATIVE_INFINITY; // = log(0)
56 int T = O.length;
57 for (int i = 0; i < N; i++){
58 sum = TCMMath.logplus(sum, alpha[ T-1 ][ i ]);//下标从0开始
59 }
60 return sum;
61 }
62
63 /**
64 * 打印前向变量矩阵
65 */
66 public void print() {
67 for (int j = 0; j < N; j++) {
68 for (int i = 0; i < alpha.length; i++){
69 System.out.print(HMMHelper.fmtlog(alpha[ i ][ j ]));
70 }
71 System.out.println();
72 }
73 }
74 }
测试类:
1 package jxutcm.edu.cn.hmm.model;
2
3 import jxutcm.edu.cn.hmm.bean.HMMHelper;
4
5 public class Demo1 {
6 enum Box {one, two, three}; // 隐藏状态(箱子编号)
7 enum Color {red, yellow, blue, green}; // 观察状态(观测到的颜色值)
8
9 public static void main(String[] args) {
10 test();
11 }
12
13 public static void test() {
14 // 状态转移矩阵
15 double[][] A = {
16 { 0.500, 0.375, 0.125 },
17 { 0.250, 0.125, 0.625 },
18 { 0.250, 0.375, 0.375 } };
19 // 发射矩阵
20 double[][] B = {
21 { 0.60, 0.20, 0.15, 0.05 },
22 { 0.25, 0.25, 0.25, 0.25 },
23 { 0.05, 0.10, 0.35, 0.50 } };
24
25 // 初始概率向量
26 double[] PI = { 0.63, 0.17, 0.20 };
27
28 // 观察序列
29 int[] O = { Color.yellow.ordinal(), Color.red.ordinal(), Color.blue.ordinal(), Color.yellow.ordinal(),Color.green.ordinal() };
30 System.out.println("初始概率向量:{"+PI[0]+" "+PI[1]+" "+PI[2]+"}");
31 System.out.println("隐藏状态序列:{"+Box.one+" "+Box.two+" "+Box.three+"}");
32 System.out.println("观测序列:{"+Color.yellow+" "+Color.red+" "+Color.blue+" "+Color.yellow+" "+Color.green+"}");
33
34 /**
35 * 前向算法测试——求解观察序列的概率
36 */
37 Forward foreward=new Forward(A, B, PI, O);
38 foreward.CalculateForeMatrix();//计算前向变量矩阵
39 System.out.println( HMMHelper.fmtlog( foreward.logProb() ) );//计算观测序列O的概率
40
41 /**
42 * 后向算法测试
43 */
47 }
48 }