卷积神经网络及其在语音识别中的应用

 

参考文献

  1. Notes on Convolutional Neural Networks
  2. Applying Convolutional Neural Networks Concepts to Hybrid NN-HMM Model for Speech Recognition
  3. IBM-Tara N. Sainath-Deep Convolutional Neural Networks for LVCSR
  4. code guide: DeeplearnToolbox & kaldi CNN

卷积特征提取

离散卷积

Conv

  • 卷积的长度M+N-1,不补0有效长度M-N+1
  • 重要性质之一:交换律
%matlab 卷积函数
conv(x, h, 'full') %所有长度,M+N-1
conv(x, h, 'valid') %有效长度,M-N+1

图片特征提取

  • NN在图像中的问题:参数多,图片的局部性
  • 卷积提取特征,如均值滤波窗,Gasussion模糊,Laplace算子,梯度算子,SIFT中的LoG
\[\begin{bmatrix} 0 & -1 & 0 \\ -1 & 4 & -1\\ 0 & -1 & 0 \end{bmatrix}\]

Lena

CNN

卷积神经网络结构

MyLenet

  • 卷积层C,所谓的权值共享
  • 降采样层S, Pooling
  • 全连接层,就是NN

CNN算法

这里主要参考”Notes on Convolutional Neural Networks”中的公式推导,前向和后向算法均以卷积形式给出。 具体的实现也以DeeplearnxToolbox中CNN在mnist库上的应用为例。

前向

  • 卷积层:第一层时仅有输入图片一个输入,中间卷积层结果是对输入卷积Kij的累和。
  • pooling: average, max, stochastic

max pooling的意思就是用图像某一区域像素值的最大值来表示该区域的特征,而mean pool的意思用图像某一区域像素值的均值来表示该区域的特征。这两个pooling操作都提高了提取特征的不变性,而特征提取的误差主要来自两个方面:(1)邻域大小受限造成的估计值方差增大;(2)卷积层参数误差造成估计均值的偏移。一般来说,mean-pooling能减小第一种误差,更多的保留图像的背景信息,max-pooling能减小第二种误差,更多的保留纹理信息。在图像处理中,使用max pooling多于mean pooling

后向ebp

  • 降采样层对 $\delta$ 升采样,一一对应
  • 若降采样层后为全连接层,计算方式与普通NN相同,所以此处仅考虑降采样层后为卷积层的形式。

在语音识别中的应用

卷积窗仅在频域轴上移动,同一个patch不同滤波器作用的结果分为一组(h1, h2, …, hn),max pooling时在patch_step中的hn中取max,例h1 = (1, 3, 2), h2 = (2, 1, 1), h1 h2为一个patch,max pooling的结果为(2, 3, 2)。

keypoint

  • convolution only frequence axis, 卷积窗仅在频域轴上移动
  • feature type, filter-bank特征,按频率分组,将时域拼接的上下文帧转换为frequency bands特征
  • convolution over all axis, 即不使用local filter,在整个频域上使用相同核
  • fast implementation, 将二维滤波器(卷积核)拉伸为一维向量
  • 语音识别中卷积(滤波器)的使用

CNN_Speech1

语音识别卷积神经网络的深度结构(多个全连接层)

CNN_Speech2

kaldi CNN代码解析

因为卷积核移动和不同的卷积核,输入与输出之间是一对多的关系,一般是按输入顺序查找输出。这里的一个技巧是先确定出输出的维度,查找与之相应的输入,前向,后向,卷积和pooling时均使用该技巧。

卷积层 nnet-convolution-component.h

  void PropagateFnc(const CuMatrixBase<BaseFloat> &in, CuMatrixBase<BaseFloat> *out) {
    // useful dims
    int32 num_splice = input_dim_ / patch_stride_; //帧数
    int32 num_patches = 1 + (patch_stride_ - patch_dim_) / patch_step_; //patch num
    int32 num_filters = filters_.NumRows(); //filter数量,一行为一个filter
    int32 num_frames = in.NumRows(); //batch中的数据数
    int32 filter_dim = filters_.NumCols(); //filter的核大小

    // we will need the buffers 
    if (vectorized_feature_patches_.size() == 0) {
      vectorized_feature_patches_.resize(num_patches);
      feature_patch_diffs_.resize(num_patches);
    }

    /* Prepare feature patches, the layout is:
     * |----------|----------|----------|---------| (in = spliced frames)
     *   xxx        xxx        xxx        xxx       (x = selected elements)
     *
     *   xxx : patch dim
     *    xxx 
     *   ^---: patch step
     * |----------| : patch stride
     *
     *   xxx-xxx-xxx-xxx : filter dim
     *  
     */
    for (int32 p=0; p<num_patches; p++) {
      vectorized_feature_patches_[p].Resize(num_frames, filter_dim, kSetZero);
      // build-up a column selection mask:
      std::vector<int32> column_mask;
	  /*
       *|-XXX---------|
       *|-XXX---------|
       *|-XXX---------|
       *|-XXX---------|
	   column_mask的位置, p * patch_step + s * patch_stride_ + d
	   */
      for (int32 s=0; s<num_splice; s++) {
        for (int32 d=0; d<patch_dim_; d++) {
          column_mask.push_back(p * patch_step_ + s * patch_stride_ + d);
        }
      }
      KALDI_ASSERT(column_mask.size() == filter_dim);
      // select the current patch columns, 一行为一个输入数据。
      vectorized_feature_patches_[p].CopyCols(in, column_mask);
    }

    // compute filter activations
    for (int32 p=0; p<num_patches; p++) {
      //patch p 在输出中的位置tgt
      CuSubMatrix<BaseFloat> tgt(out->ColRange(p * num_filters, num_filters));
      tgt.AddVecToRows(1.0, bias_, 0.0); // add bias
      // apply all filters
      tgt.AddMatMat(1.0, vectorized_feature_patches_[p], kNoTrans, filters_, kTrans, 1.0);
    }
  }
  //后向程序仅是对公式的翻译
  void BackpropagateFnc(const CuMatrixBase<BaseFloat> &in, const CuMatrixBase<BaseFloat> &out,
                        const CuMatrixBase<BaseFloat> &out_diff, CuMatrixBase<BaseFloat> *in_diff) {
  }
  void Update(const CuMatrixBase<BaseFloat> &input, const CuMatrixBase<BaseFloat> &diff) {
  }

pooling层 nnet-max-pooling-component.h

  void PropagateFnc(const CuMatrixBase<BaseFloat> &in, CuMatrixBase<BaseFloat> *out) {                                                                
    // useful dims                                                                                                                                    
    int32 num_patches = input_dim_ / pool_stride_;                                                                                                    
    int32 num_pools = 1 + (num_patches - pool_size_) / pool_step_;                                                                                    
    //pool_step_ pool步移, pool_size_,pool步长                                                                                                                                                  
    // do the max-pooling (pools indexed by q),为每个输出q选择max                                                                                                        
    for (int32 q = 0; q < num_pools; q++) {                                                                                                           
      // get output buffer of the pool                                                                                                                
      CuSubMatrix<BaseFloat> pool(out->ColRange(q*pool_stride_, pool_stride_));                                                                       
      pool.Set(-1e20); // reset (large negative value)                                                                                                
      for (int32 r = 0; r < pool_size_; r++) { // max                                                                                                 
        int32 p = r + q * pool_step_; // p = input patch                                                                                              
        pool.Max(in.ColRange(p*pool_stride_, pool_stride_)); //*this = max(*this, A)                                                                                      
      }                                                                                                                                               
    }                                                                                                                                                 
  } 
  //max-pool,若其为max,后向误差乘以1,否则0,这里有个scale的操作
  //因为中间部分可能多次计算,相对的边缘部分在shift中计算次数较少
  void BackpropagateFnc(const CuMatrixBase<BaseFloat> &in, const CuMatrixBase<BaseFloat> &out,
                        const CuMatrixBase<BaseFloat> &out_diff, CuMatrixBase<BaseFloat> *in_diff) {
    // useful dims
    int32 num_patches = input_dim_ / pool_stride_;
    int32 num_pools = 1 + (num_patches - pool_size_) / pool_step_;
    //scale 数组
    std::vector<int32> patch_summands(num_patches, 0);
    in_diff->SetZero(); // reset
    //遍历所有pool输出
    for(int32 q=0; q<num_pools; q++) { // sum
      for(int32 r=0; r<pool_size_; r++) {
        //对应q时的输入
        int32 p = r + q * pool_step_; // patch number
        CuSubMatrix<BaseFloat> in_p(in.ColRange(p*pool_stride_, pool_stride_));
        CuSubMatrix<BaseFloat> out_q(out.ColRange(q*pool_stride_, pool_stride_));
        CuSubMatrix<BaseFloat> tgt(in_diff->ColRange(p*pool_stride_, pool_stride_));
        CuMatrix<BaseFloat> src(out_diff.ColRange(q*pool_stride_, pool_stride_));
        //find max mask
        CuMatrix<BaseFloat> mask;
        in_p.EqualElementMask(out_q, &mask);
        src.MulElements(mask);
        tgt.AddMat(1.0, src);
        patch_summands[p] += 1;
      }
    }
    //scale操作
    for(int32 p=0; p<num_patches; p++) {
      CuSubMatrix<BaseFloat> tgt(in_diff->ColRange(p*pool_stride_, pool_stride_));
      KALDI_ASSERT(patch_summands[p] > 0); // patch at least in one pool
      tgt.Scale(1.0/patch_summands[p]);
    }
  }