語音識別—聲學模型訓練(Viterbi-EM)

                                                                     Viterbi-EM語音識別訓練方法

  前文剛研究過語音識別特徵提取以及基於Viterbi的狀態解碼方法,現着手研究基於GMM-HMM的語音語音識別聲學模型訓練方法,其理論部分可參考本人前期所寫的GMM-HMM理論推導拖成,但上述推導過程是採用前後向算法更新模型參數,本人則主要採用Viterbi-EM訓練方法對GMM中參數進行更新訓練。

  實際上該訓練方法主要是針對GMM 中均值與方差參數進行調整,這其實恰恰證明了以HMM構建的語音識別系統核心在於GMM模型的參數訓練,故語音識別核心在於聲學模型,而聲學模型核心在於GMM參數訓練,實際上用神經網絡更新模型HMM對應狀態的發射概率亦是同樣道理,只是神經網絡是更新神經元中的權重以及偏置而GMM則是更新GMM中權重、均值以及方差。二者中GMM參數更新稱之爲似然訓練,而NN訓練則叫區分性訓練,兩種算法對於語音特徵的擬合效果存在差異,實際上本質上相同:均採用相關算法得到特徵對應的概率(前者、後者分別稱爲似然概率與後驗概率)。

  假設建模單元均爲單音素且均採用單個GMM模型對HMM中單個狀態進行建模,先確定三個容器m_gaussCounts、m_gaussStats1以及m_gaussStats2,現對三種容器介紹如下:

  (1)鑑於GMM-HMM模型中狀態數據是確定的,故m_gaussCounts採用vector容器存儲語料庫中對應狀態出現的次數,假設狀態數目爲100,則m_gaussCounts容器從0狀態開始存儲各狀態在語料中出現的次數,至於如何確定狀態出現的次數,實際上就是將每幀結果對應至對應至概率最大的狀態(實際過程中對應的是弧號,而弧號可以與狀態進行唯一對應,具體可以參考本人之前所寫解碼過程,其對弧與狀態之間轉化進行了說明);

  (2)m_gaussStats1採用Matrix容器存儲總體特徵數據,其維度大小受狀態以及特徵大小影響,必須指出的是:特徵維度與m_gaussStats1容器的列大小一致。正如上文假設,則該容器大小維度爲:100*12,其中100表示狀態數目,12則表示前文提取的12維語音MFCC特徵;

  (3)同理,m_gaussStats2也採用Matrix容器存儲總體平方特徵數據,後文將對平方特徵進行理論說明。其維度大小與m_gaussStats1維度一致。

1.容器初始化

  上述容器在具體實現過程中,本人給出自己參考哥大代碼所寫的,容器初始化如下:

    /** Total counts of each Gaussian. **/
    vector<double> m_gaussCounts;

    /** First-order stats for each dim of each Gaussian. **/
    matrix<double> m_gaussStats1;

    /** Second-order stats for each dim of each Gaussian. **/
    matrix<double> m_gaussStats2;

2.容器統計量存儲

  介紹完上述三種容器,現介紹如何對語料庫中特徵進行統計進而存儲至三種對應的容器中。

  2.1 m_gaussCounts容器存儲

    正如其名所述,m_gaussCounts容器大小與語料庫中狀態數目必一致,前文設置爲100,則該容器大小必爲100。其中容器中每個元素代表對應的狀態序號(其可以通過解碼對其得到對應的弧號,進而轉化爲對應的狀態,該過程稱之爲對齊),將語料庫中所有幀屬於的狀態存儲至該容器對應位置,故該容器統計結果爲語料庫中對應狀態的個數,其實現核心代碼如下:

double GmmStats::add_gmm_count(unsigned gmmIdx, double posterior,
    const vector<double>& feats) {
    if (m_gmmSet.get_component_count(gmmIdx) != 1)
        throw runtime_error("GMM doesn't have single component.");
    int gaussIdx = m_gmmSet.get_gaussian_index(gmmIdx, 0);
    int dimCnt = m_gmmSet.get_dim_count();
    //統計所有幀屬於狀態的個數,依次疊加;
    m_gaussCounts[gaussIdx] += posterior;
    for (int dimIdx = 0; dimIdx < dimCnt; ++dimIdx) {
        m_gaussStats1(gaussIdx, dimIdx) += posterior * feats[dimIdx];
        m_gaussStats2(gaussIdx, dimIdx) += posterior * pow(feats[dimIdx], 2);
    }
    return 0.0;
}

  2.2 m_gaussStats1容器存儲

  m_gaussStats1容器存儲的爲語料庫中對應狀態的特徵疊加結果,其是通過對齊得到具體某幀屬於的狀態號,該容器大小爲:100*12,其中100表示狀態,12則表示對應的特徵,其具體含義可以表述爲:屬於該狀態所有幀結果的累加,爲後期計算均值所用,其具體理論實現樹下:

double GmmStats::add_gmm_count(unsigned gmmIdx, double posterior,
    const vector<double>& feats) {
    if (m_gmmSet.get_component_count(gmmIdx) != 1)
        throw runtime_error("GMM doesn't have single component.");
    int gaussIdx = m_gmmSet.get_gaussian_index(gmmIdx, 0);
    int dimCnt = m_gmmSet.get_dim_count();
    m_gaussCounts[gaussIdx] += posterior;
    for (int dimIdx = 0; dimIdx < dimCnt; ++dimIdx) {
        //m_gaussStats1存儲幀所對應狀態所有特徵的累加
        m_gaussStats1(gaussIdx, dimIdx) += posterior * feats[dimIdx];
        m_gaussStats2(gaussIdx, dimIdx) += posterior * pow(feats[dimIdx], 2);
    }
    return 0.0;
}

  2.3 m_gaussStats容器存儲

  m_gaussStats2容器與m_gaussStats1容器類似,其理論概述與m_gaussStats1容器一致,不過m_gaussStats2容器存儲的是對應特徵屬於該狀態的平方,也就是前文所述的總平方特徵,其具體實現如下:

double GmmStats::add_gmm_count(unsigned gmmIdx, double posterior,
    const vector<double>& feats) {
    if (m_gmmSet.get_component_count(gmmIdx) != 1)
        throw runtime_error("GMM doesn't have single component.");
    int gaussIdx = m_gmmSet.get_gaussian_index(gmmIdx, 0);
    int dimCnt = m_gmmSet.get_dim_count();
    m_gaussCounts[gaussIdx] += posterior;
    for (int dimIdx = 0; dimIdx < dimCnt; ++dimIdx) {
        m_gaussStats1(gaussIdx, dimIdx) += posterior * feats[dimIdx];
        //m_gaussStats2表示特徵對應狀態總特徵平方;
        m_gaussStats2(gaussIdx, dimIdx) += posterior * pow(feats[dimIdx], 2);
    }
    return 0.0;
}

3.容器參數更新

  將特徵統計量結果賭贏存儲至對應的容器中,現開始對容器中統計量結果進行參數更新,其主要涉及均值以及方差的計算,其中均值理論計算如下:

  則方差的理論計算如下:

   現介紹實際過程中均值與方差理論計算方法,現展示其代碼如下所示:

void GmmStats::reestimate() const {
    int gaussCnt = m_gmmSet.get_gaussian_count();
    int dimCnt = m_gmmSet.get_dim_count();
    double occupancy, mean, var;
    for (int gaussIdx = 0; gaussIdx < gaussCnt; ++gaussIdx) {
        occupancy = m_gaussCounts[gaussIdx];
        for (int dimIdx = 0; dimIdx < dimCnt; ++dimIdx) {
            //均值與方差重新估計,
            mean = m_gaussStats1(gaussIdx, dimIdx) / occupancy;
            var = m_gaussStats2(gaussIdx, dimIdx) / occupancy - mean * mean;
            m_gmmSet.set_gaussian_mean(gaussIdx, dimIdx, mean);
            m_gmmSet.set_gaussian_var(gaussIdx, dimIdx, var);
        }
    }
}

至此,基於Viterbi-EM的參數更新方法推導完畢。

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