语音识别—声学模型训练(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 m_gaussCounts;

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

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

2.容器统计量存储

  介绍完上述三种容器,现介绍如何对语料库中特征进行统计进而存储至三种对应的容器中。

  2.1 m_gaussCounts容器存储

    正如其名所述,m_gaussCounts容器大小与语料库中状态数目必一致,前文设置为100,则该容器大小必为100。其中容器中每个元素代表对应的状态序号(其可以通过解码对其得到对应的弧号,进而转化为对应的状态,该过程称之为对齐),将语料库中所有帧属于的状态存储至该容器对应位置,故该容器统计结果为语料库中对应状态的个数,其实现核心代码如下:

double GmmStats::add_gmm_count(unsigned gmmIdx, double posterior,
    const vector& 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& 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& 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.容器参数更新

  将特征统计量结果赌赢存储至对应的容器中,现开始对容器中统计量结果进行参数更新,其主要涉及均值以及方差的计算,其中均值理论计算如下:

语音识别—声学模型训练(Viterbi-EM)_第1张图片

  则方差的理论计算如下:

语音识别—声学模型训练(Viterbi-EM)_第2张图片

   现介绍实际过程中均值与方差理论计算方法,现展示其代码如下所示:

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的参数更新方法推导完毕。

你可能感兴趣的:(语音识别-深度学习)