Viterbi 算法學習(附代碼和註解)

詳細代碼: 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章