語音識別—聲學模型解碼

                                                                    聲學模型解碼(帶狀態轉移概率)

  最近一直在學習哥倫比亞大學與愛丁堡大學語音識別課程,並且修正了哥倫比亞大學中基於HMM構建的語音識別系統存在問題終自己寫了一套基於HMM的語音識別系統,前文一些博客簡單對上述工程實現以及理論進行了介紹,但是前文進行Viterbi解碼時並未融入狀態轉移概率,雖然轉移概率相較於發射概率對於解碼結果影響較小,雖然影響較小,但是筆者認爲一個優秀的轉移概率模型可能對最後的解碼結果有一定的影響,現開始對轉移概率進行介紹。

一 未融入轉移概率解碼模型

1.1 狀態圖

  首先介紹未融入狀態轉移時的狀態圖,其圖如下所示:

  其中上述狀態圖中第一至第四列分別表示初始狀態、終止狀態、GMM狀態、詞,其中並未涉及狀態轉移概率

1.2 解碼工程化
 

double logProb = (fieldList.size() > 4) ?
  lexical_cast<double>(fieldList[4]) * logFactor : 0.0;

  上述表示未融入狀態轉移概率的對應幀概率計算方法,默認值爲0.

二 融入轉移概率解碼模型

2.1 狀態圖

  該圖中第五列即表示由初始狀態轉移至終止狀態且其對應GMM狀態的轉移概率,筆者認爲轉移概率一定程度上會影響基於Viterbi的解碼結果。

2.2 解碼工程化

double logProb = (fieldList.size() > 4) ?
  lexical_cast<double>(fieldList[4]) * logFactor : 0.0;

  上述表示狀態轉移概率計算方法,實際應用過程中結合聲學模型結果,其具體實現結果如下:

double viterbi(const Graph& graph, const matrix<double>& gmmProbs,
    matrix<VitCell>& chart, vector<int>& outLabelList,
    double acousWgt, bool doAlign) {
    //frmCnt=68, stateCnt=123
    int frmCnt = chart.size1() - 1;
    int stateCnt = chart.size2();
    {
        //計算弧上概率:
        for (size_t frmIdx = 0; frmIdx < chart.size1(); ++frmIdx) {
            for (size_t stateIdx = 0; stateIdx < chart.size2(); ++stateIdx) {
                chart(frmIdx, stateIdx).assign(g_zeroLogProb, -1);
            }
        }
        int startState = graph.get_start_state();
        chart(0, startState).assign(0, -1);
    }

    int frmIdx = 1;
    //當startState=1時,arcCnt=12,arcId=0;
    //arcCnt表示當前狀態個數,arcId表示狀態在弧上的序號;
    auto startState = graph.get_start_state();
    int arcCnt = graph.get_arc_count(startState);
    int arcId = graph.get_first_arc_id(startState);

    for (int arcIdx = 0; arcIdx < arcCnt; ++arcIdx) {
        Arc arc;
        int curArcId = arcId;
        //arcId表示當前狀態按其數目進行從1排序, dstState表示狀態排序對應的終止狀態;
        arcId = graph.get_arc(arcId, arc);        
        int dstState = arc.get_dst_state();
        double transition_prob = arc.get_log_prob();
        //log_prob表示轉移概率加上對應GMM發射概率;
        double log_prob =
            transition_prob + gmmProbs(frmIdx - 1, arc.get_gmm());
        //最後加上弧上chart[0, 1]概率,其值爲0;
        log_prob += chart(frmIdx - 1, startState).get_log_prob();
        //針對狀態1,將轉移概率已發射概率的log概率之和賦值到對應的chart<Arc>矩陣中;
        chart(frmIdx, dstState).assign(log_prob, curArcId);
        //chart(frmIdx, dstState).printArcResults();
    }

    for (int frmIdx = 2; frmIdx <= frmCnt; ++frmIdx) {
        for (int stateIdx = 0; stateIdx < stateCnt; ++stateIdx) {
            int arcCnt = graph.get_arc_count(stateIdx);
            int arcId = graph.get_first_arc_id(stateIdx);
            for (int arcIdx = 0; arcIdx < arcCnt; ++arcIdx) {
                Arc arc;
                int curArcId = arcId;
                arcId = graph.get_arc(arcId, arc);
                int dstState = arc.get_dst_state();
                double transition_prob = arc.get_log_prob();
                double log_prob =
                    chart(frmIdx - 1, stateIdx).get_log_prob() +  // 表示弧上概率;
                    transition_prob +                             // 表示轉移概率;
                    gmmProbs(frmIdx - 1, arc.get_gmm());          // 表示發射概率;
                //如果累加概率log值變小,則替換chart矩陣中對應的狀態概率;
                //表示從第一幀開始記錄弧序號及其對應的累加概率;
                //當到達最後一幀,chart矩陣中記錄着最大概率及其對應的弧序號,可以通過通過回溯得到概率最大的序列;
                if (log_prob > chart(frmIdx, dstState).get_log_prob()) {
                    chart(frmIdx, dstState).assign(log_prob, curArcId);
                }
            }
        }
    }
     
    //chart<Arc> 矩陣維度爲 69*123;
    //其中行表示幀數,列表示累加log似然值;
    //每幀每個位置表示可跳轉狀態到達此狀態時的最大概率;
    //chart<Arc> 存放結果保存形式爲:弧序號(可映射爲狀態值) 以及 最大似然值;
    //for (int i = 0; i < 123; i++) {
    //    Arc arc;
    //    chart(68, i).printArcResults();
    //}

    return viterbi_backtrace(graph, chart, outLabelList, doAlign);
}

  最後回溯方法得到每個時刻每幀狀態似然概率最大位置,並返回弧號以及對應似然值,其具體結果如下:

double viterbi_backtrace(const Graph& graph, matrix<VitCell>& chart,
    vector<int>& outLabelList, bool doAlign) {
    int frmCnt = chart.size1() - 1;
    int stateCnt = chart.size2();
    //finalStates存儲終止狀態對應的序號,且對其進行排序;
    vector<int> finalStates;
    int finalCnt = graph.get_final_state_list(finalStates);   
    double bestLogProb = g_zeroLogProb;
    int bestFinalState = -1;
    for (int finalIdx = 0; finalIdx < finalCnt; ++finalIdx) {
        int stateIdx = finalStates[finalIdx];
        if (chart(frmCnt, stateIdx).get_log_prob() == g_zeroLogProb) continue;
        //curLogProb表示終止狀態對應的似然值與弧上概率的累加值;
        //加上弧上概率是因爲終止狀態再進行log似然值累加時,終止狀態上並未添加弧上概率;
        double curLogProb = chart(frmCnt, stateIdx).get_log_prob() +
            graph.get_final_log_prob(stateIdx);
        if (curLogProb > bestLogProb)
            bestLogProb = curLogProb, bestFinalState = stateIdx;
    }
    if (bestFinalState < 0) throw runtime_error("No complete paths found.");
    outLabelList.clear();
    int stateIdx = bestFinalState;
    for (int frmIdx = frmCnt; --frmIdx >= 0;) {
        assert((stateIdx >= 0) && (stateIdx < stateCnt));
        int arcId = chart(frmIdx + 1, stateIdx).get_arc_id();
        Arc arc;
        graph.get_arc(arcId, arc);
        assert((int)arc.get_dst_state() == stateIdx);
        if (doAlign) {
            throw runtime_error("Expect all arcs to have GMM.");
            outLabelList.push_back(arc.get_gmm());
        }
        else if (arc.get_word() > 0) {
            outLabelList.push_back(arc.get_word());
        }
        stateIdx = graph.get_src_state(arcId);
    }
    if (stateIdx != graph.get_start_state())
        throw runtime_error("Backtrace does not end at start state.");
    reverse(outLabelList.begin(), outLabelList.end());
    return bestLogProb;
}

至此,融入狀態轉移概率的Viterbi解碼方法撰寫完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章