Kaldi-dnn 学习

坚强是说给别人听的谎言 提交于 2019-12-03 04:07:36

1. Kaldi 中实现的 dnn 共 4 种:

    a. nnet1 - 基于 Karel's 的实现,特点:简单,仅支持单 GPU, 由 Karel 维护

    b. nnet2 - 基于 Daniel Povey p-norm 的实现,特点:灵活,支持多 GPU、CPU,由 Daniel 维护

    c. nnet3 - nnet2 的改进,由 Daniel 维护

    d. (nnet3 + chain) - Daniel Povey 改进的 nnet3, 特点:可以实现实时解码,解码速率为 nnet3 的 3~5 倍



    目前来看:minibatch  Stochastic Gradient Descent 用于 DNN 梯度下降的效果最好

                      从一个小样本含 (τ个样本) 估计出一个 avarage gradient , 这个小样本就叫做 minibatch

2. 先从 nnet2 说起

    a. nnet2 最顶层的训练脚本:steps/nnet2/train_pnorm_fast.sh


        通过多计算节点,完成并行化训练


    b. 输入神经网络的特征


         输入神经网络的特征是可配置的,通常为MFCC+LDA+MLLT+fMLLR, 40-维的特征,从网络上看到的是由7帧(从中间帧到左右帧都是3帧)组成的一个帧窗。由于神经网络很难从相关输入的数据中学习,因此,以 40*7 维特征作为一个不相关的固定变换形式,通过 step/nnet2/get_lda.sh 完成该工作,实际中并非使用准确的 LDA,而是用 non-dimension-reducing 形式的 LDA,然后减少输出特征方差的维度。

    

     c. dumping 训练样本到磁盘

            

          第一步,调用 step/nnet2/get_egs.sh:拷贝大量数据至 /exp/nnet5d/egs/;便于随机梯度下降训练。对输入做“帧-级别”的随机初始化(仅做一次)。也就是说:能始终以相同的序列化 access 数据,对于 disk 和 network, disk access 序列化是很好的。

          /exp/nnet5d/egs/中包含很多 class 实例(称为NnetTrainingExample) (如egs.1.1.ark,egs.1.2.ark...),这些 class 包含每帧的 label 信息,和该帧用于神经网络计算的一个充足的特征(40维特征)的输入时间窗。(从表面上看来)并非为神经网络做 frame-splicing,神经网络有“时间观念”,并知道“需要多长时间的上下文”(即看函数 RightContext() 和 LeftContext())。egs.1.1.ark 中的第 1 个数字 1 代表 job-index,第 2 个数字 1 代表 iteration index。

          有文件中包含 job-index 和 the iteration index,如共有16个 CPU/GPU 可用,则 job-index 取值范围 [1, 16],而 the iteration index 的范围则根据数据量、及有多少个 job 来定。每个 archive 大约需要 200,000 个 samples。需要训练很多个 epochs, 每个 epoch 做很多次迭代。设定参数为 iters_per_epoch, num_jobs_nnet 和 sample_per_iter。

          具体 iters_per_epoch, num_jobs_nnet  和 sample_per_iter 设置值,见 exp/nnet5d/egs/ 对应名字文件。

          

       d. 神经网络初始化

           第一步,初始含一个隐藏层的神经网络,随后在训练中逐渐增加 (2~5 ) 隐藏层。配置隐藏层的文件类似 /exp/nnet4d/nnet.config。即通过 nnet-am-init 创建初始化模型。类似如下的配置文件

SpliceComponent input-dim=40 left-context=4 right-context=4 const-component-dim=0
FixedAffineComponent matrix=exp/nnet4d/lda.mat
AffineComponentPreconditionedOnline input-dim=360 output-dim=1000 alpha=4.0 num-samples-history=2000 update-period=4 rank-in=20 rank-out=80 max-change-per-sample=0.075 learning-rate=0.02 param-stddev=0.0316227766016838 bias-stddev=0.5
PnormComponent input-dim=1000 output-dim=200 p=2
NormalizeComponent dim=200
AffineComponentPreconditionedOnline input-dim=200 output-dim=1475 alpha=4.0 num-samples-history=2000 update-period=4 rank-in=20 rank-out=80 max-change-per-sample=0.075 learning-rate=0.02 param-stddev=0 bias-stddev=0
SoftmaxComponent dim=1475


    SpliceComponent: 定义了完成 feature-frame-splicing 的窗口尺寸

    FixedAffineComponent:类 LDA-like 的非相关转换,由标准的 weight matrix plus bias 组成,通过标准的 stochastic gradient descent 训练而来,使用 global learning rate

    AffineComponentPreconditionedOnline:为 FixedAffineComponent 的一种提炼,训练过程中不仅使用global learning rate,还使用 matrix-valued learning rate 来预处理梯度下降。参见 dnn2_preconditioning。

    PnormComponent:为非线性,传统的神经网络模型中使用 TanhComponent
    NormalizeComponent:用于稳定训练 p-norm 网络,它是固定的,非可训练,非线性的。它不是在个别 
individual activations 上起作用,而是对单帧的整个 vetor 起作用,重新使它们单位标准化。

    SoftmaxComponent:为最终的非线性特征,便于输出标准概率

    同时,上述脚本也会产生 hidden.config, 用于新的隐藏层,在最开始的两次迭代中不会使用,如

AffineComponentPreconditionedOnline input-dim=200 output-dim=1000 alpha=4.0 num-samples-history=2000 update-period=4 rank-in=20 rank-out=80 max-change-per-sample=0.075 learning-rate=0.02 param-stddev=0.0316227766016838 bias-stddev=0.5
PnormComponent input-dim=1000 output-dim=200 p=2
NormalizeComponent dim=200
    

            第二步,做 nnet-train-transitions ,完成转移概率的计算,该步骤计算的概率在 decoding 时 HMMs 中使用(神经网络中是不需要该概率的)。并计算 “targets(几千个 context-dependent 状态)” 的先验概率。接下来进行解码,分割这些通过网络计算得到的先验概率,得到pseudo-likelihoods
     

     d. 神经网络训练

             本阶段进行关键步骤:神经网络训练。实际上就是 0 - num_iters-1 次的循环,迭代次数 num_iters 就是 每个 epoch 的迭代次数。训练 epochs 的次数 = num_pochs(eg 15) + num_epochs_extra(eg 5)。每个 epoch 的迭代次数存储在 egs/nnet5d/egs/iters_per_epoch 中,具体大小依赖于并发的 job 个数和训练的数据量。一般的策略是:learning rate 从初始的 0.04,decrease rete 为 0.004,迭代15个epoch,最后5个 epoch 使用固定的 final_learning_rate 0.004。

           在每个迭代中,首要任务是计算一些 diagnostics:即 训练与验证数据的目标函数(如,iteration 10, 可以查看对应的 egs/nnet5d/log/compute_prob_valid.10.log 和 egs/nnet5d/log/compute_prob_train.10.log)。在文件(eg: egs/nnet5d/log/progress.10.log)即可看到 iteration 10 对应的 diagnostics [表示每层有多少个 parameters 变化,以及有多少训练数据目标函数的变化对每一层的变化有作用]。

            基本的并行方法:对数百个 samples 使用随机梯度下降来训练,在不同 job 中使用不同的数据,然后 average models。在参数中,目标函数不是凸函数。实践证明对于并行方法, "preconditioned update" 是非常重要的,而 average model 是没有作用的。

         

   g. Final model combination


      

             查看 exp/nnet4d/log/combine.log 文件可看到,final.mdl 是如何合成的。基本方法:减少方差估计的平均迭代次数。实际中,combine.log 不仅仅只利用参数的平均值,而使用 training-data examples 的 subset 来优化 weight set(不限制必需为正)。对于 subset 的目标函数为常用函数(eg: log-probability),优化方法为 L-BFGS 和 特殊的预处理方法。each component and each iteration 含各自的 weilght。实践证明:使用 随机训练数据的 subset 效果会更好。

              

#> cat exp/nnet4d/log/combine.log
<snip>
    Scale parameters are  [
  -0.109349 -0.365521 -0.760345
  0.124764 -0.142875 -1.02651
  0.117608 0.334453 -0.762045
  -0.186654 -0.286753 -0.522608
  -0.697463 0.0842729 -0.274787
  -0.0995975 -0.102453 -0.154562
  -0.141524 -0.445594 -0.134846
  -0.429088 -1.86144 -0.165885
  0.152729 0.380491 0.212379
  0.178501 -0.0663124 0.183646
  0.111049 0.223023 0.51741
  0.34404 0.437391 0.666507
  0.710299 0.737166 1.0455
  0.859282 1.9126 1.97164 ]

LOG <snip> Combining nnets, objf per frame changed from -1.05681 to -0.989872
LOG <snip> Finished combining neural nets, wrote model to exp/nnet4a2/final.mdl

         如上所求,combination weights 被打印为矩阵格式,行:代表 iteration,列:代表相关 layer。combination weights  刚开始 negative,随后 postive,可以解释为趋于一次尝试模型进一步的方向。使用 training data 而不是 validation data,因为这样效果更好。


   f. Mixing-up


          可以使用如下工具打印 final.mdl 来分析

             

#> nnet-am-info exp/nnet4d/final.mdl
num-components 11
num-updatable-components 3
left-context 4
right-context 4
input-dim 40
output-dim 1483
parameter-dim 1366000
component 0 : SpliceComponent, input-dim=40, output-dim=360, context=4/4
component 1 : FixedAffineComponent, input-dim=360, output-dim=360, linear-params-stddev=0.0386901, bias-params-stddev=0.0315842
component 2 : AffineComponentPreconditioned, input-dim=360, output-dim=1000, linear-params-stddev=0.988958, bias-params-stddev=2.98569, learning-rate=0.004, alpha=4, max-change=10
component 3 : PnormComponent, input-dim = 1000, output-dim = 200, p = 2
component 4 : NormalizeComponent, input-dim=200, output-dim=200
component 5 : AffineComponentPreconditioned, input-dim=200, output-dim=1000, linear-params-stddev=0.998705, bias-params-stddev=1.23249, learning-rate=0.004, alpha=4, max-change=10
component 6 : PnormComponent, input-dim = 1000, output-dim = 200, p = 2
component 7 : NormalizeComponent, input-dim=200, output-dim=200
component 8 : AffineComponentPreconditioned, input-dim=200, output-dim=4000, linear-params-stddev=0.719869, bias-params-stddev=1.69202, learning-rate=0.004, alpha=4, max-change=10
component 9 : SoftmaxComponent, input-dim=4000, output-dim=4000
component 10 : SumGroupComponent, input-dim=4000, output-dim=1483
prior dimension: 1483, prior sum: 1, prior min: 7.96841e-05
LOG (nnet-am-info:main():nnet-am-info.cc:60) Printed info about baseline/exp/nnet4d/final.mdl

          可以看到,在 outlayer 层之前有一层网络层(神经元节点数为4000),而它的实际大小为 1483,因为决策树含有1483个叶子。

          通过 SumGroupComponent,softmax层维度由4000减少到1483,可以使用如下命令查看

          

#> nnet-am-copy --binary=false baseline/exp/nnet4d/final.mdl - | grep SumGroup
nnet-am-copy --binary=false baseline/exp/nnet4d/final.mdl -
<SumGroupComponent> <Sizes> [ 6 3 3 3 2 3 3 3 2 3 2 2 3 3 3 3 2 3 3 3 3 \
3 3 4 2 1 2 3 3 3 2 2 2 3 2 2 3 3 3 3 2 4 2 3 2 3 3 3 4 2 2 3 3 2 4 3 3 \
<snip>
4 3 3 2 3 3 2 2 2 3 3 3 3 3 1 2 3 1 3 2 ]

     g. Tuning the number of jobs

              通常,最有效的 minibatch size = threads 的倍数。learning rate 与相关 jobs 的数量有关系,一般地,如果增加 jobs 的数量,同时应该增加相同倍数的 learning rate 。原因如下:

Since the parallelization method is based on averaging the neural nets from parallel SGD runs, we view the "effective learning rate" 
per sample of the entire learning process as equal to the learning rate divided by the number of jobs. So when doubling the number of jobs, 
if we double the learning rate we keep the "effective learning rate" the same.

                        但是,如果 learning rate  太大会导致不稳定。


     h. Tuning the neural network training


     h.1 Number of parameters(hidden layers and layer size)

         对于 p-norm 网络,一般要求 -pnorm-input-dim 是 -pnorm-output-dim 的整数倍

     h.2 Learning rates

         

     h.3 Minibatch size

         minibatch size 一般取 2 的次幂,eg: 128,256,512。

     h.4 Max-change

             用于限制,在每个 minibatch 允许多少个 parameters 能变化,-max-change 会使训练减慢


     h.5 Number of epochs,etc

             epochs 由 -num-epochs (default: 15) 和 -num-epochs-extra(default:5) 组成,-num-epochs 用于 learning rate 从 -initial-learning-rate 梯度下降到 -final-learning-rate 阶段,-num-epochs-extra 用于固定的 -final-learning-rate 阶段,

                    -num-iters-final 参数很重要,它决定了 final model combination 阶段的迭代次数。


        h.6 Feature splicing width

             用于控制将多少帧的特征组合成输入特征,该操作会影响到神经网络层的初始化,以及 examples 的产生。一般默认值为 4,表示的含义:以中间 frame 为轴,左右各四个 frame,共9帧为单位组合后做为输入(通常由 MFCC+splice+LDA+MLLT+fMLLR 组成的 40 维特征,splicing width = 4 是最优的,注意:LDA+MLLT特征基于 splice frame 左右两侧各4帧,也就是说从神经网络上看 splice frame 两侧的 7 、8 帧均影响 acoustic context )


                   40 维的由来: 

                                       MFCC/帧:13维

                                       9帧:13*9维

                                       再LDA:40 维


        h.7 Configuration values relating to the LDA transform

             正如上面所说,此 LDA 并非标准的 LDA。传统的 LDA 中,数据需要经过 normalize,使得 within-class variance 为单位矩阵,经过变换后,总 variance (within plus between-class) 第 i 个的对角值为 1.0+b(i),b(i) 具有数据依赖特性,根据 i 减少,between-class variance 为对角矩阵,而现在的 LDA 矩阵中每行乘以,其中,默认的 within-class-factor 为 0.0001,即 0.0001+b(i),而非原来的 1.0+b(i)。

         

        h.8 Other miscellaneous configuration values

             对于 train_tanh.sh,有一个优化选项 -shrink-interval(默认为 5),含义为:多久做一次 model “shrinking”,就是说用一个小子集训练数据来优化一组尺度不同层的参数。

         







易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!