在实时音视频中应用深度学习,你需要了解这些

avatar

在场景需求的推动下,以及背后算法、算力、数据的支撑下,AI 已经慢慢走出实验室,开始拥抱产业,这其中也包括 RTC 行业。在实时的视频、实时音频、实时传输、视频内容检索与推荐、实时交互等层面,都已经出现了与 AI 结合的落地应用。

从实时的视频来讲,超分辨率就是最典型的应用之一。在深度学习的帮助下,我们可以在视频接收端提高原有图像的分辨率,得到高分辨率的图像,这个过程就是超分辨率重建。我们以前也分享过一些知名的算法模型。 实时音频方面,我们可以看到很多 AI 的应用。例如带宽扩展,在我们通话的过程中,对方的声音听起来有些闷,这是因为语音信号中的高频区域被移除掉了。而基于DNN的频带扩展则可将高频区域恢复出来,就像下图这样。经过频带扩展后的信号增加了很多高频信息,实际的听感也会更加明亮、清晰。

除此之外,AI 在实时语音上的应用还包括语音增强、基于 RNN 的丢包恢复、语音音乐分类器等。AI的发展使得音频领域有了更多的可能性去解决之前难以处理的问题。对于实时音频而言,AI是一把全面提升质量的利刃,但实时音频所必须的低复杂度、低延时特性注定全面AI化引擎还有很长的路要走。 另外,除了实时音视频,AI

还可以用于改善实时传输质量,也可以用于视频内容的检索与推荐,或进行音视频内容审核。以上这些相关话题,你都可以在今年的 RTC 实时互联网大会上听到。 10 月 24 日、25 日即将举行的 RTC 2019 实时互联网大会(2019.rtcexpo.org/) 邀请了来自声网Agora、Hulu、依图科技、南京大学等公司与高校的演讲人,他们将从分享 AI 在移动端实时视频超分辨率、音频优化、传输优化、视频内容推荐、音频内容审核等角度分享实践经验。细数下来,今年几十个演讲中,有近 1/3 的演讲都与深度学习、神经网络相关。

同时,在大会第一天上午的主会中,声网Agora 首席科学家钟声、搜狗公司AI交互事业部高级总监、语音技术部负责人陈伟,也将围绕 AI 与 RTC 深入分享更多技术实践与趋势。

不过,对于刚刚入门神经网络的开发者而言,可能需要要过许多弯路,我们下面来总结一下训练神经网络的入门级知识点,从循环神经元的选择、激活函数、过拟合,到优化器的选择。

1.循环神经单元的选择

如果神经网络中需要用到循环神经单元,首先需要从多个现有模板里选择一个。单向循环神经单元现在主流的有RNN/LSTM/GRU,这几个循环神经单元综合来说性价比是RNN<LSTM<GRU。

RNN是一个设计比较早的结构,缺点是每个时间步的输入的比重是一样的,如果输入的时间步过多,RNN会遗忘较早的信息。

LSTM是为了解决上述RNN缺点而设计出来的,相比RNN,LSTM主要增加了4个门和1个细胞状态,用来控制哪些信息需要遗忘,哪些信息需要记住,但LSTM的缺点是模型比较复杂,权重很多,比较难训练且模型比较大。

GRU相当于是一个简化版的LSTM,GRU只有三个门,并且融合了细胞状态C和输出状态h,这样GRU的计算复杂度和模型体积大概只有LSTM的3/4,并且更容易训练。

除此之外,还有一个非主流的模型SRU,SRU使用大量哈达玛乘积代替了传统的矩阵点乘,并且当前时刻的输出已经不依赖之前时间步的输出,只依赖前一时刻的细胞状态。该模型的一大优点是可以对其进行并行化优化,提升其在GPU上的计算性能。相同hidden layer数量的SRU的模型体积和计算复杂度大概是GRU的1/2,但误差有所上升。SRU的详细介绍可以参考另外一篇征文。

2.激活函数的选择

主流激活函数有sigmoid/tanh/relu/leakyrelu/prelu等。

值得一提的是,循环神经单元门内的激活函数一般是sigmoid,输出的激活函数一般是tanh,这两个一般来说不建议修改,错误的修改容易引起梯度爆炸。

relu系列主要是为了解决深度神经网络中梯度消失的问题,实际上,在relu设计出来之前,深度神经网络还是很依赖pretraining+finetuning这种方法的,relu的作用是和droupout/L1 regularization的功能类似,即使得部分权重失活,达到稀疏网络的目的。

leakyrelu和prelu是relu的变种,这两个激活函数在输入为负的时候仍有输出,但负值输入会被乘上一个权重,leakyrelu需要手动设置这个权重,prelu则会通过训练得到整个权重。性能上relu<leakyrelu<prelu,但prelu在训练的时候需要额外的计算量,因为它多一个反向传播。

推荐首选relu系列激活函数,因为它在大多数场景下都有较明显的优势。

3.过拟合及防止过拟合的手段

过拟合近乎是训练网络时无法避免的问题,严重的过拟合会在训练集上跑出完美的结果,却完全无法在测试集上工作。总的来说,目前防止过拟合的手段都围绕着一个思想:稀疏网络。

常用的防止过拟合的方法主要有:dropout、regularization,上述的relu系列激活函数也算是一种方法。总体来说,防止过拟合的思路是使网络变得稀疏,稀疏可以简单理解为把一个大网络里所有权重共同协作的方式改为大网络里多个小网络并行计算,这种描述不太准确,但可以大概这么理解。dropout实现稀疏的方法比较直接,就是在训练的时候随机让一些权重失活,在使用网络时时恢复这些权重,并将输出除以(1-α),其中α是dropout的比例。regularization的中文翻译为“正则化”,这个不太好理解,可能把它理解为“限制”更好一些,它其实是针对权重加了一个限制。regularization分为L1 regularization和L2 regularization,L1 regularization是在反向传播误差时加上权重本身的一个一阶范数,它的作用是,使得最终训练出网络的权重有更多的0值,实现了稀疏网络的目的;L2 regularization是在权重上加了权重本身的二阶范数,这使得最终训练出网络的权重都变小并趋近于0,但不等于0,也实现了稀疏网络的目的。

关于为什么稀疏的网络可以防止过拟合,我的理解是,一个大网络里其实有好多权重是没什么用的,但这些权重计算出的信息会去很好的拟合训练集,但会在验证集和测试集上引入噪声,因此,通过稀疏网络,使得这些“无用权重”扮演的角色变小,网络就能更好的拟合从来没见过的测试集。

4.优化器的选择

优化器是训练网络时的梯度下降的方法,目前主流优化器有SGD、Adagrad、Adadelta、RMSprop、Adam、NAdam这几种。

其中,SGD的全称是stochastic gradient descent,它的思想是每次只训练少量样本进行梯度下降,优点是相比之前的BGD(Batch gradient descent)占用内存少一些,能够进行大数据量的训练,缺点是因为每次只用少量样本,可能loss是震荡下降的,因此SGD很依赖于部分超参的设置,如学习率在什么时候衰减对SGD来说是非常重要的,可以暂时这么说,如果SGD的超参设置的非常完美,那它就能训练出最佳的模型,但一般来说这个是很难的。

Adagrad的最大特点在于它通过累计计算过去的梯度(就是导数),给不同参数不同的学习率,但这个优化器的一个最大的问题是,累计的梯度越大,学习率越小,很容易出现在训练未结束时学习率就下降到非常低的问题。

Adadelta则修正了Adagrad的学习率下降过早的问题,它通过指数加权平均方法计算过去N个梯度值,不再是从第一次训练一直开始累积梯度,这样就从一定程度上避免了上述问题。此外,Adadelta还可以自己计算学习率,因此不需要预设。

RMSprop可以看做Adadelta的一个超参特殊版,值得一提的是,RMSprop在训练循环神经网络时的表现不错。

Adam是目前最容易被选择的一个优化器,它可以看做是RMSprop+Momentum,Momentum可以计算过去训练的累计梯度,如果历史梯度下降方向和当前下降方向一致,学习率就会被加强,如果历史梯度下降方向和当前方向不一致,学习率就会被减弱。需要注意的是,Momentum累计梯度时累计的是梯度本身的值,而上面几个优化器本身累计梯度时累计的是梯度的平方,所以Momentum和优化器本身相当于是两个并行的控制函数,这两个函数的超参也是分别设置的。

NAdam相当于Adam+Nesterov,Nesterov相当于对Momentum又加了一个校正,理论上可以达到更好的效果,但问题是,施加这个校正需要重新计算一遍梯度下降,所以NAdam的计算量差不多是Adam的两倍,因此要慎用NAdam,除非不在意训练时间。

综上,Adam是万能公式,可以在训练循环神经网络时尝试RMSprop,尽量避免使用SGD,除非对自己的调参能力非常自信。此外,Adam这种带Momentum的优化器很容易在训练末期陷入抖动,无法下降到最优点,一种比较麻烦的处理方法是,先使用Adam训练,再换成SGD进行最后的收尾工作。

5.学习率的设置

学习率最重要的是初始值设定和衰减方法,初始值设定根据不同种类的目标有所不同,建议对于一个新的模型,开始不要尝试太大的学习率,可以用小一点的学习率,如0.001试一下,训练完毕后,把误差下降曲线打印出来,观察loss曲线,理想的loss曲线应该是类似于y=1/x,如果曲线过于平滑,说明学习率太小,如果误差曲线下降的很快并伴随着上下波动,说明学习率过大。

衰减方法主要有按轮次衰减和根据loss衰减,以keras为例,如果按照10轮训练衰减一次,每次衰减一半,需要调用LearningRateSchedule():

如果想根据监控验证集的loss调整学习率,需要调用ReduceLROnPlateau(),当连续n轮loss不下降,进行学习率的衰减。

  1. 其余细节

以下总结仅供参考,不一定对每个task都起作用

(1)权重初始化:推荐orthogonal初始化,可以有效避免梯度消失或爆炸,原因可见这篇博文。

(2)Attention层:Attention层多用于语音识别或转化,Attention通过对每个timestep分配权重,设定了一个特定注意力区域,而不是把注意力都集中在最新时刻的输入。现在讲Attention的文章有很多,这里就不再赘述了。

(3)当多循环单元串联时,建议不要修改循环单元内部激活函数,避免梯度爆炸。这就带来一个问题,以GRU为例,输出状态h经过的是一个tanh。如果只单纯串联多个循环单元,整个网络没有relu来稀疏网络,导致网络性能下降。

建议多层串联循环网络架构如下:GRU-PRelu-TimeDistributed-tanh-GRU…,或者是GRU-PRelu-LayerNormalization-GRU…。这样做的原因是:加入Prelu用来稀疏网络,增强网络鲁棒性;加入TimeDistributed用来实现更复杂的类Attention的功能;最后的激活函数选择tanh是因为希望数据在输入下一个循环单元前范围限制在-1~1之间。LayerNormalization可以重置数据的分布,目的是和上述做法差不多的。

(4)Batch Normalization:该层可以将输入的批数据强行变成均值为0,方差为1的高斯分布。理论上Batch Normalization层带来的白化数据可以加速训练,但我实验下来在RNN系列上误差有所上升,且Batch Normalization性能依赖于batch size,对训练机器的性能有考验。推荐在RNN类的网络上还是使用LayerNormalization好一些。

(5)如果网络没有明显的过拟合,或者只过拟合了一丢丢,在回归问题上建议不要使用Regularization,Regularization可以较明显的泛化网络输出结果,使得模型在测试集上的表现也出现一定下降。

(6)结合CNN的循环单元,如果需要使用,建议把CNN搁到循环单元后面。这么做一是避免破坏原始输入数据的时域相关特性,二是参考了这篇文章里的实验结果。

(7)关于数据预处理:数据归一化在无特殊情况下都是建议做的。关于变化域的数据预处理,之前尝试过对信号进行PCA后输入到神经网络,但出现了部分白化后数据梯度爆炸的现象,而且PCA没有带来很明显的正向增益,却会引入一定复杂度,在数据预处理时,需要谨慎选用PCA,但PCA可以有效实现降维,当数据维数过大导致网络难以拟合时,PCA是一个不错的选择。

(8)Loss function的确定:对于回归问题来说,如果数据是归一化的,建议使用MAE或其近似变种就好,MSE可能会使其性能下降。分类问题暂时没有做过相关项目,就不写了。

(9)网络架构的优化:循环神经网络主要的模型体积和计算复杂度来源于那几个门控单元,其随着门控单元隐层数量平方级增加,除了剪枝等方法,一种简单的优化方法是,把大的循环单元拆分成多个小的循环单元,尽量减少大矩阵相乘的次数。但这样需要更多巧妙的小设计去弥补带来的误差,可以参考已有类似网络的设计,如Wavenet等。