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的參數更新方法推導完畢。