人工智能:语音识别理解与实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

7.1 训练加速

大规模真实的语音识别系统经常会使用数千甚至上万小时的语音数据训练。由于我们会每10ms抽取一帧(frame)特征,所以24小时数据被转化为

24h×60min/h×60s/min×100frame/s=8.64millionframes

一千小时数据等同于3.6亿帧数据(或样本),同时考虑到深层神经网络(DNN)参数的大小,这显然包含了巨大的计算量,使得加速训练的技术非常重要。

我们可以使用高性能计算设备加速训练,例如,通用图形处理单元(GPGPU)多处理器单元或者使用更好的算法,从而减少模型参数,使收敛更快。从我们的经验来看,GPGPU的性能要远远好于多核CPU,因而是训练DNN的理想平台。

7.1.1 使用多GPU流水线反向传播

我们都知道,基于小批量(minibatch)的随机梯度下降(SGD)训练算法在单个计算设备上能够很容易地处理大数据。然而,只有数百样本的小块使得并行化处理非常困难。这是因为如果只是简单的数据并行,每个小批量样本模型参数更新都需要过高的带宽。例如,一个典型的2K×7(7个隐层,每个隐层2K个节点)CD-DNN-HMM有5000万~1亿个浮点参数或者2亿~4亿字节。每个服务器每个小批量将需要分发400MB梯度及收集另外400MB模型参数,如果每个小批量计算在GPGPU上实现都需要500ms,这将接近PCIe-2的数据传输限制(约6GB/s)。

然而,批量块的大小主要由两个因素决定:更小的批量块意味着更加频繁的模型更新,也意味着GPU的计算能力使用更低效。更大的批量块能够更加高效地计算,但是整个训练过程需要更多次训练集的完整迭代。平衡这两个因素可以获得一个优化的批量块大小。图7-1显示了在训练12小时训练数据以后不同的批量块大小(x轴)的相对运行时间(右y轴),以及帧正确率(左y轴)。在这些实验中,在最开始的2.4小时训练数据内,如果批量块大小比256大,则被设为256,在2.4小时训练数据后,其增加到实际的大小。可以看到最优的批量块大小为256~1024。

图7-1 不同的批量大小和GPU/CPU模型类型的相对运行时间,以及在处理完12小时数据之后的帧正确率

注:右边的y轴:以C1060及2048点的批量大小所需要的计算时间为基准得到的相对运行时间(本图摘录于Chen等人[118],引用已经获得ISCA授权)

如果使用图7-1列出的最好的GPU,即NVIDIA S2090(主机为T620),及交叉熵训练准则,则为了获得一个好性能的DNN模型,300小时的训练数据将花费15天。如果用2000小时的训练数据,则整个训练时间将增加至45天,注意到整个训练时间增加约3倍,而不是2000/300≈6.7倍。这是因为使用SGD训练时,尽管每次迭代整个训练数据会花费6.7倍的时间,但是对整个训练数据的迭代次数会变得更少。使用更新的GPU,例如K20X,能够使训练时间降低至20天。尽管如此,训练2万小时的训练数据仍然需要2个月。因此,并行训练算法对支持大数据的训练非常重要。

我们用K表示GPU的数量(如4),T表示批量块大小(如1024),N表示隐层节点维度(如2048),J表示输出维度(senones数量)(如9304)。我们使用经典的通过切分训练数据实现并行化的map-reduce[237]方法,在每个小批量的运算中,将涉及从主服务器到其他K−1个GPU对整种模型的梯度及模型参数的累积和分发操作。在不同GPGPU之间的共享带宽是。一个树状的通信架构能够使其降至,其中,是大于等于x的最小整数。

我们可以把模型的每一层参数分成条状,将其分发到不同的计算节点。在这个节点并行方法中,每个GPU处理每层参数和梯度的K个垂直切分中的一条切分。模型更新发生在每个GPU的本地。在前向计算中,每层的输入v−1都要分发至所有的GPU,每个GPU计算输出向量v的一个片段。所有这些计算好的片段再被分发至其他的GPU用于计算下一层。在反向传播过程中,误差向量以切分片段的方式进行并行计算,但是由每个片段产生的结果矩阵只是不完整的部分和,最后还需要进一步综合求和。总之,在前向计算及反向传播计算中,每个向量都需要传输K−1次。带宽是

基于流水线的反向传播[118, 238],通过把各层参数分发至不同GPU形成一个流水线,可以避免在上述条状分割方法中数据向量的多次复制。数据而不是模型,从一个GPU流向下一个GPU,所有的GPU都基于它们获得的数据独立工作。例如,在图7-2中,以DNN每两层为切分单元进行前向传播,其分别存储在三个GPU中,当第一批的训练数据进入后,由GPU1处理。隐层1的激活(输出)传入GPU2处理。与此同时,一个新的批训练数据进入GPU1处理。在3个批次数据以后,所有的GPU都被占用。如果能平衡每层的计算,这个过程将获得3倍加速。反向传播过程以类似的方式处理。6个批次数据以后,所有的GPU都处理了一个向前的批次及一个向后的批次。由于GPU使用单指令多数据(SIMD)架构,我们可以对每层先更新模型,然后做前向计算。这保证最近更新的权重可以用于前向计算,这样可以减少下面将提到的延迟更新问题。

图7-2 流水线并行架构的示意图

在流水线构架中,每个向量对每个GPU要遍历两次,一次前向计算及一次后向计算。带宽是,这低于数据并行及条形分割。如果DNN层的数量比GPU数量多,可以把若干层分为一组放在同一GPU中。最后异步数据传输及近似顺序执行使得数据传输和计算大部分能够并行,这样能够使有效的通信时间降低,直至接近于0。

需要注意,效率的提高伴随着损耗。这是因为用于前向计算的权值与用于反向传播的权值不一致。例如,对于批次n,在GPU 1、GPU 2及GPU 3中用于前向计算的权值是批次n−5、n−3及n−1后依次更新的。然而,当计算梯度的时候,对于批次n−1及n−2,对应在GPU 2及GPU 1的这些权值已经更早地被更新过了,尽管它们在GPU3上是一致的。这意味着在更低层,由于流水线的延迟,梯度的计算并不精确。基于以上分析,我们可以认为延迟更新作为一种特殊复杂的冲量技术,其中梯度的更新值(平滑后的梯度)是之前的模型及梯度的函数。基于这个原因,如果批量块大小不变,当流水线很长时,可以观察到性能会有降低。为了减轻延迟更新的副作用,我们需要切分批量块大小。

实现大规模加速的关键是平衡每个GPU的计算。如果层数是GPU个数的整数倍,并且所有的层都拥有相同的维度,平衡计算量就很容易。然而在CD-DNN-HMM中,最后的softmax层占参数量的主要部分。这是因为聚类后状态的数量通常在10K左右,隐层节点的典型数量在2K左右。为了平衡计算,对于softmax层,我们需要使用条状切分,对余下的层则使用流水线。

表7.1引用于Chen等人[118],展示了在一台服务器(Dell PowerEdge T620)上使用4个GPU(NVIDIA Tesla S2090)的训练运行时间,其中输入特征维度为429,隐层个数L=7,隐层维度N=2048,以及聚类后的状态个数J=9304。从这个表中我们可以观察到,在双通道GPU上,可以实现1.7到1.9倍的加速(例如对512帧的批量块,运行时间从61分钟降低至33分钟),尽管延迟更新的结果并不精确,却几乎没有性能下降。由于不平衡的softmax层,GPU1包含5个权值矩阵,GPU2只包含两层。为了实现这个加速,两块GPU计算时间比例为(429+5×2048)×2048:(2048+9304)×2048=0.94:1,这样就非常平衡。增加至4块GPU,仅仅使用流水线的方式几乎没有作用。总体的加速比保持在2.2倍左右(例如61对29分钟)。这是因为softmax层参数量(9304×2048)是隐层参数量(20482)的4.5倍,这是限制瓶颈。在4个GPU上计算时间的比例为(429+2×2048)×2048:(2×2048)×2048:(2×2048)×2048:9304×2048=1.1:1:1:2.27。换句话说,GPU4会花费其他GPU两倍的时间进行计算。然而,如果将流水线BP和striping方法相结合,则striping方法只用于softmax层,将实现巨大的加速。在这个配置中,把各层(0..3;4..6;7L;7R)分配到4个GPU中,其中LR分别表示softmax左右切分。换句话说,两个GPU联合形成流水线的顶部,同时底部的7层分布在其他两个GPU上。在这个条件下,4块GPU计算耗费的比例是(429+3×2048)×2048:(3×2048)×2048:4652×2048:4652×2048=1.07:1:0.76:0.76。在没有性能损失的情况下,在4块GPU上,最快的流水线系统(使用512的批量块大小,18分钟处理24小时数据)比单GPU基线快3.3倍(使用1024的批量块大小,59分钟处理24小时数据),这是一个3.3倍的加速。

表7.1 每24小时训练数据所需要的计算时间(分钟)

注:[[·]]表示不收敛,[·]表示在测试集合上产生了大于0.1%的词错误率(WER)损失(引自Chen等人[118]

流水线反向传播的弊端很明显。总体的加速在很大程度上取决于能否找到一种方法平衡各个GPU上的计算量。此外,由于延迟更新的影响,其不容易扩展至更多GPU上实现相同的加速。

7.1.2 异步随机梯度下降

模型训练可以使用另一种被称为异步随机梯度下降(Asynchronous SGD,ASGD)[239-241]的技术实现并行化。如图7-3所示,最初的ASGD是一种在多CPU的服务器上运行的方法。在这个构架中,DNN被存储在若干(图中为3个)计算节点上,这些节点被称为参数服务器池。参数服务器池是主控端。主控端发送模型参数到从属端,每个从属端包含若干计算节点(图中为4个)。每个从属端负责训练数据的一个子集,它计算每个小批量数据的梯度,并发送至主控端。主控端更新模型参数,再发送新的模型参数至从属端。

由于从属端每个计算节点都包含模型的部分参数,输出值的计算需要跨计算节点复制。为了降低通信代价,存储在不同计算节点之间模型的连接应该是稀疏的。由于每个计算节点对都只传输一个参数子集,在主控端使用多个计算节点就可以降低主控端和从属端的通信代价。然而,ASGD成功的关键是使用异步不加锁更新。换句话说,服务器参数更新时是不加锁的。当主控端从各个从属端获取梯度后,它会在不同的线程中独立更新模型参数。当主控端发送新的参数到从属端时,其中的部分参数可能是使用其他从属端发送的梯度进行更新的。乍一看,这可能导致不收敛问题。实际上,参数收敛效果很好,由于每个从属端都不需要等待其他从属端[239, 240],模型训练时间也大幅度减少。随着采用训练集合中随机取得的数据进行更新,整种模型会不断得到优化。ASGD的收敛性证明由文献[239]给出。

图7-3 异步随机梯度下降的说明

注:图中展示了一个主控参数服务器池和三个从属端(图片来自Erdinc Basci)

在ASGD中有些实际的问题需要仔细解决。首先,某些从属端需要花费更长的时间来完成一个计算过程,这样导致这些从属端计算的梯度可能基于一个很老的模型。最简单的处理这个问题的方法是在所有的通信中发送一个时间戳。如果从主控端和从属端发送的时间戳相差超过一定的阈值,则主控端抛弃过期的梯度并发送给从属端最新更新的模型即可。如果一个从属端一致性较慢,则分配到这个从属端的数据需要被重新分发到其他从属端。通过从同一个数据池获取多个批量块的数据,很容易做到这点。其次,发生在流水线BP里的延迟更新问题很明显也会同样发生在ASGD中。鉴于此,我们需要减少从属端的数量或者降低学习率,以缓解这个问题。然而,任何一种解决方法都会降低训练速度。最后,延迟更新问题在梯度很大时更容易出现,尤其是模型训练的早期阶段。这个问题可以用预热技术来缓解,即模型在开始ASGD训练之前,进行一遍SGD训练。

尽管ASGD工作在CPU集群上[240],但是其通信消耗是非常大的,会成为瓶颈。例如,在1000台分布式CPU核心上运行ASGD的性能,与拥有8个GPU的单个机器速度性能相似。在CPU上使用ASGD主要是利用现存的CPU集群,以及训练那些不适合放在GPU内存中的模型。

ASGD算法也可以被应用到单个主机的GPU[241]中。既然在语言识别中,DNN模型既适合在CPU中,也适合在GPU内存中,我们也可以使用主机(CPU)作为主控端,把每个GPU都作为从属端。注意到基于GPU的ASGD整体速度都有了重大改善。这是因为每个小批量在GPU中的计算时间花费都非常少,而且GPU和主机(通过PCIe总线)之间的通信速度大大快于不同CPU之间通信的速度。尽管可以使用GPU,但如果批量块很小,通信仍然可能会成为瓶颈。这个问题可以通过降低主控端和从属端数据传输的频率来解决。GPU从属端可以累计更新梯度,每三到四个批次再发送至主控端,而不是对每个小批量数据都更新模型。由于这从本质上增加了批量块的大小,所以我们需要降低学习率以对其进行补偿。

表7.2是从文献[241]中截取的,在10小时的中文训练任务中,对比了SGD和ASGD的字符错误率(CER)。42维特征由13维PLP、一维基频及其一阶和二阶差分组成。DNN训练数据为130小时,采用其他1小时数据作为开发集调整。拼接的11帧特征作为DNN的输入,DNN有5个隐层,每层有2048个节点,输出节点有10217个聚类后的状态。这个系统还在其他两个独立的测试集上进行评估,被称为clean7k和noise360,分别对应通过手机麦克风在干净及噪声环境下搜集的数据。系统使用NVIDIA GeForce GTX 690训练。表7.2表示4块GPU上的ASGD与单块GPU的SGD相比,可以实现3.2倍的加速。

表7.2 在中文语音识别任务上比较每10小时数据的训练时间及字符错误率

7.1.3 增广拉格朗日算法及乘子方向交替算法

在单个主机上拥有超过4个GPU不太现实。即使有可能,延迟更新也会使ASGD及流水线BP在多于4个GPU上很难实现满意的加速效果。为了能训练更多的数据,我们仍然需要利用多GPU/CPU。增广拉格朗日算法(Augmented Lagrangian methods,ALMs)[242-244]因此被提出。

在DNN训练中,我们通过在训练集S上最小化经验风险J(θ; S)来优化模型参数θ

其中,Sk是训练数据的第k个子集,满足Ski=ø,∀ki,以及。如果能够保证训练得到的模型在不同数据子集上都一样(这可以通过等式约束强制得到),我们就可能在不同的训练数据子集上使用不同的处理器(可以是相同或者不同的计算节点)独立优化模型参数。这样分布式训练问题就转化为一个条件优化问题

其中,θk是局部模型参数,θ是全局共同的模型参数。

这个有约束的优化问题能进一步使用拉格朗日乘子方法被转化为无约束优化问题

其中,λk是拉格朗日乘子或称对偶参数。如果训练准则是严格的凸函数并且是有界的,这个约束问题就可以使用对偶上升算法(Dual Ascent Method)来解决。然而,在DNN中使用的训练准则(如交叉熵或均方差错误训练准则)都是非凸函数。在这样的情况下,对偶上升算法的收敛效果很不好。

为给对偶上升算法增加鲁棒性及改善收敛效果,我们可以给无约束优化问题增加一个惩罚项,因此它变为

其中,ρ>0,称为惩罚参数。这个新公式被称为增广拉格朗日乘子。注意到公式(7.4)可以被视为与如下问题相对应的(非增广)拉格朗日方法:

它和初始的公式(7.2)有相同的解。这个问题可以使用乘子方向交替算法(Alternating Directions Method of Multipliers,ADMM)[245]来解决:

公式(7.6)使用SGD算法分布式解决了原始的优化问题。公式(7.7)将每个处理单元更新后的局部参数集中起来,并在参数服务器上估计出新的模型参数。公式(7.8)从参数服务器上取回全局模型并更新对偶参数。

上述算法是一个对整体数据进行批处理的算法。然而,它能够被应用于比在普通SGD中使用的批量块再大一些的批量块中,使得训练过程加速而不带来太多的通信延迟。DNN训练是一个非凸问题,ALM/ADMM常常收敛到一个性能比正常SGD算法结果稍差的点上。为了减小这种性能差距,我们需要使用SGD来初始化模型(通常称为开始预热),然后使用ALM/ADMM,最后使用SGD、L-BFGS或者Hessian free算法结束训练。这个算法也可以和ASGD或者流水线BP相结合,进一步加速训练过程。ALM/ADMM不仅可以用在DNN训练中,也可以用在其他模型训练中。

7.1.4 块动量方法

模型平均[246]是一种常见的深度学习并行训练的方法。在这种方法中,用若干计算单元各自使用一部分数据进行训练,在训练的过程中,不断将各计算单元更新后的模型参数进行平均,从而近乎线性地提高了训练速度。然而,模型平均的一个常见问题是会带来性能的下降,而且随着并行度的提高,这种下降会越发明显。在文献[247]中详细地分析了其中的一些原因,并提出了块动量(Block Momentum, BM)方法,有效地降低了模型平均带来的性能损失。

在单机的训练中,常常会使用动量(momentum)的方法来代替单纯地使用SGD。它可以有效加快模型的收敛速度,减少噪声数据对模型训练的干扰。具体更新规则为

m(t)=λm(t−1)−(1−λ)ηg(t)  (7.9)

W(t+1)=W(t)+m(t)  (7.10)

其中,λ为一个0到1之间的参数,η为学习率,g(t)、m(t)和W(t)分别表示t时刻的梯度、动量和模型参数。

然而,对于进行模型平均的并行训练来说,这样的基于mini-batch的动量会被每次平均的过程所打断,使得模型很难基于历史训练信息抑制训练数据中的噪声。因而类比于此,在文献[247]中定义了块(block)的概念,并提出了基于块的块动量方法。

如图7.4所示,一个块指的是一次全局模型更新中全部N个计算单元所使用的所有数据。基于块的全局模型更新方法规则如下:

其中,Wg(t−1)表示该次全局模型更新前的全局模型参数,表示各计算节点的模型参数的平均,∆(t)表示t时刻的块动量,ζt被称为块学习率(Block Learning Rate, BLR)。

类似于动量中的经典方式和Nesterov方式,块动量也有经典方式(Classical Block Momentum,CBM)和Nesterov方式(Nesterov Block Momentum,NBM)两种方式可选。计算公式分别可写为

Wg(t)=W(t)  (7.14)

图7-4 数据块的含义[248]

Wg(t)=W(t)+ηt+1∆(t)  (7.15)

块动量在相继处理的块之间建立了联系,它利用历史更新信息,减弱单次全局模型更新中噪声的影响,同时增加正确方向的更新步长,克服模型平均算法所带来的缺点。由于G(t)是通过利用上一轮迭代的全局模型更新值对当前块得到的模型更新值进行低通滤波得到的,故而我们称公式(7.12)为逐区块模型更新滤波(Blockwise Model-update Filtering, BMUF)。

在switchboard数据集上进行模型平均并行训练的实验结果如表7.3所示,测试了在并行度为8和16时的实验结果。在不使用块动量方法时,模型平均并行训练的结果相比单GPU的结果均有所下降。在加入了块动量方法后,模型性能的下降幅度均有所减小,部分结果甚至超过了单GPU的训练结果。而在训练速度的提升方面,几乎没有造成影响,保持了模型平均近乎线性的速度提升效果。

表7.3 在switchboard数据集上进行模型平均并行训练[247]

7.1.5 减小模型规模

改善训练速度不仅可以通过使用更好的训练算法,还可以通过使用更小的模型来实现。减少模型参数的简单方法是使用更少的隐层及每层使用更少的节点,然而,不幸的是,这样往往降低了识别的准确性[12, 13]。一种有效减小模型规模的技术是低秩分解[249, 250]

有两个证据可以说明DNN中的权值矩阵大体上是低秩的。首先,在CD-DNN-HMM中,为实现好的识别性能,DNN的输出层一般拥有大量的节点(例如5000~10000),大于或等于一个优化的GMM-HMM系统的聚类后的状态(绑定的三音素状态)数量。最后一层占用了系统50%的模型参数及训练计算量。然而,在解码过程中一般只有少部分输出节点是被激活的。这样可以合理地认为那些被激活的输出节点是相关的(例如,属于有混淆的若干上下文相关的HMM状态集合),这表示输出层的权值矩阵是低秩的。其次,在DNN任意层只有最大的30%~40%的权值是重要的。如果把矩阵其余的权值都设置为0,则DNN的性能不会降低[251]。这意味着每个权值矩阵都能够近似地进行低秩分解且没有识别精度的损失。

使用低秩分解,每个权值矩阵都可以被分解成两个更小的矩阵,从而大大减少DNN的参数数量。使用低秩分解不仅能减小模型规模,而且能限制参数空间,可以使优化更加有效,并且减少训练轮数。

我们用W表示一个m×n的低秩矩阵。如果W的秩为r,存在一个分解W=W2W1,其中W2是一个秩为r、大小为m×r的矩阵,W1是一个秩为r、大小为r×n的矩阵。我们用W2W1相乘代替矩阵W,如果满足m×r+r×n<m×n或者,我们就可能减小模型规模并加速训练。如果我们想把模型规模减少至之前的1/p,则需要满足。当用W1W2替换W时,等价于引入了一个线性层W1,后面接着一个非线性层W2,如图7-5所示。这是因为

y=f(Wv)=f(W2W1v)=f(W2h) (7.16)

其中,h=W1v是一个非线性转换。在文献[249]中显示,对不同的任务,如果只把softmax层分解为秩在128到512之间的矩阵,模型性能将不会降低。

图7-5 低秩分解的示意图

注:将权重矩阵W用两个较小的矩阵W2W1相乘来代替,等价于将非线性层W替换为一个线性层W1,再接上一个非线性层W2

7.1.6 其他方法

研究者也提出了其他方法。例如,可以分别用独立的1/4数据训练4个DNN,然后训练一个顶层网络用于合并4个独立的DNN输出。通过首先对聚类后的状态聚类,然后训练一个分类器分类,最后对每个聚类的聚类后的状态都训练一个DNN[252],从而进一步减少训练时间。由于输出层规模更小(只包含属于该类聚类后的状态),并且训练每类的单个DNN的对应数据集合也更小(只需要使用和输出聚类后的状态相关的帧),训练速度可以得到极大提高。在文献[252]中称,使用4块GPU,在只有1%~2%相对字错误率降低的情况下,能实现5倍的加速。然而这种方法在解码的时候需要额外的计算量。这是由于聚类后的各个DNN是分开的,每个类别的DNN一般拥有和原来整个DNN系统相同的隐层及隐层节点数量。换句话说,这种方法是用解码时间来换取训练时间。另外一种流行的方法是使用Hessian free训练算法[222, 253, 254],它可以使用更大的批量块大小及能够更容易地实现跨机器并行。然而,这种算法在单GPU上比SGD慢很多,而且需要很多实际的技巧才能使其有效。