詳細代碼: https://github.com/SunnyCat2013/viterbi-algorithm
研二在語音識別課上寫過一次 viterbi 算法。最近在複習 HMM 的時候,感覺記不太清楚 viterbi 的實現了,就抽空又複習了一遍。
例子是參考 李航的 《統計學習方法》 P186 10.3。如果不明白朮語的話,看一下 P173 10.1。
#include <iostream>
#include <string>
#include <list>
using namespace std;
int main(){
// 本例來自《統計學習方法》P186,例 10.3。可參考 P173 例 10.1 理解本題
// 有三個狀態(比如,有三個裝球的盒子)
double pi[3] = { 0.2, 0.4, 0.4 }; // 初始概率分佈(每個盒子被選中的概率)
int q = 0;
for (auto _: pi) q++;
double A[3][3] = { 0.5, 0.2, 0.3, 0.3, 0.5, 0.2, 0.2, 0.3, 0.5 }; // 轉移概率(A[i][j] 表示從狀態 i 到狀態 j 轉移的概率)
double B[3][2] = { 0.5, 0.5, 0.4, 0.6, 0.7, 0.3 }; // 觀測概率分佈(每個盒子中,白球、紅球被選中的概率)
// int output[] = { 0, 1, 0 , 1}; // 觀測序列:紅、白、紅、白
int output[] = { 0, 1, 0}; // 觀測序列:紅、白、紅
int T = 0;
// 獲取觀測序列長度
for (auto o: output) T++;
cout << "T: " << T << endl;
double **delta = new double *[T]; // 狀態表,delta[i][j] 表示時間 i 時,到達狀態 j 的最大概率值。
for (int i; i < T; i++) {
delta[i] = new double[q];
}
int psi[T][q];
// 初始化時間爲 1 時的 delta 值
int color = output[0];
cout << "output should be: \n" << "0.1\t0.16\t0.28" << endl;
cout << "init delta[0][i]" << endl;
for (int i = 0; i < q; i ++) {
delta[0][i] = pi[i] * B[i][color];
cout << delta[0][i] << "\t";
}
cout << endl;
cout << endl;
for (int i = 1; i < T; i++) { // 遍歷剩餘 T - 1 個時間狀態
color = output[i];
for (int j = 0; j < q; j++) { // 處理每個時間點中,每個狀態的情況
double i_j_pro[q]; // 時間爲 i,狀態爲 j 的結點,從時間爲 i - 1 的 q 個狀態到達當前位置的概率
for (int k = 0; k < q; k++) {
// i_j_pro[k] = delta[i - 1][k] * A[k][j];
i_j_pro[k] = delta[i - 1][k] * A[k][j] * B[j][color]; // 不要忘記 B(選擇當前狀態的哪個顏色的概率)
}
// 獲得 i_j_pro 的最大值和最大值下標
int idx = 0;
double imax = i_j_pro[idx];
for (int k = 1; k < q; k++) {
if (i_j_pro[k] > imax) {
imax = i_j_pro[k];
idx = k;
}
}
delta[i][j] = imax;
psi[i][j] = idx;
cout << "Time: " << i << "\t" << "delta: " << delta[i][j] << "\t" << "Choose box: " << psi[i][j] << endl;
}
cout << endl;
}
// 反向 decode
//// 最後一個輸出是 j = argmax(delta[i][j])
int path[T];
int i = T - 1;
int idx = 0;
double imax = delta[i][idx];
for (int k = 1; k < q; k++) {
if (delta[i][k] > imax) {
imax = delta[i][k];
idx = k;
}
}
path[i] = idx;
// 再往前的輸出,保存在 psi[i + 1][下一個最優狀態處]
for (int i = T - 2; i >= 0; i--) {
path[i] = psi[i + 1][path[i + 1]];
}
// 輸出最優路徑,如果 output 是 {0, 1, 0} 的話,輸出應該是:The best path: begin -> 2 -> 2 -> 2 -> end
cout << "The best path: begin -> ";
for (auto p: path) {
cout << p << " -> ";
}
cout << "end" << endl;
return 0;
}