
我们团队的任务是:重复语音合成
Tacotron2作者
DeepPind的人工神经网络的工作结果。 这是一个关于我们在项目实施过程中走的棘手故事的故事。
长期以来,计算机语音合成的任务一直受到科学家和技术专家的关注。 但是,经典方法不允许合成语音,这与人类没有区别。 在这里,就像在许多其他领域一样,深度学习得以拯救。
让我们看一下经典的合成方法。
级联语音合成
此方法基于预先录制的短音频片段,然后将其组合以创建连贯的语音。 事实证明,它非常干净清晰,但完全没有情感和民族成分,也就是说,听起来不自然。 所有这些都是因为不可能获得所有可能的情绪和韵律组合中发音的所有可能单词的录音。 串联系统需要庞大的数据库和硬编码组合来形成单词。 开发可靠的系统需要很多时间。
参数语音合成
由于高数据需求和开发时间,连续TTS应用受到限制。 因此,开发了一种探索数据性质的统计方法。 它通过组合诸如频率,幅度谱等参数来生成语音。
参数综合包括两个阶段。
- 首先,从文本中提取语言特征,例如音素,持续时间等。
- 然后,对于声码器(系统生成波形),提取代表相应语音信号的特征:倒谱,频率,线性频谱图,粉笔频谱图。
- 这些手动配置的参数连同语言功能一起被传送到声码器模型,并且它执行许多复杂的转换以生成声波。 同时,声码器评估语音参数,例如相位,韵律,语调等。
如果我们可以近似地定义在每个语音单元上定义语音的参数,那么我们可以创建一个参数模型。 与连接系统相比,参数综合所需的数据和工作量少得多。
从理论上讲,一切都很简单,但是在实践中,有许多伪影会导致声音模糊不清,发出“嗡嗡”的声音,这根本不像自然声音。
事实是,在合成的每个阶段,我们都会对某些功能进行硬编码,并希望获得逼真的语音。 但是选择的数据是基于我们对语音的理解,并且人类知识不是绝对的,因此,所采取的信号不一定是最佳的解决方案。
深度学习在这里以其所有辉煌进入了现场。
深度神经网络是一个功能强大的工具,从理论上讲,它可以近似任意复杂的函数,即将一些输入数据空间X带入输出数据空间Y。在我们的任务中,它们分别是文本和带有语音的音频。
数据预处理
首先,我们将确定输入的内容和输出的内容。
输入将是文本,输出将是
粉笔频谱图。 这是通过将快速傅立叶变换应用于离散音频信号而获得的低级表示。 应当立即指出,以这种方式获得的频谱图仍然
需要通过压缩动态范围
进行归一化 。 这样可以减少录音中最响和最安静的声音之间的自然关系。 在我们的实验中,频谱图的使用减少到
范围[-4; 4]被证明是最好的。
图1:语音音频信号的粉笔频谱图减小到范围[-4; 4]。作为训练数据集,我们选择了
LJSpeech数据集 ,其中包含
13,100个音轨,持续2-10秒。 以及与音频中记录的英语语音相对应的文本文件。
使用上述变换的声音被编码为粉笔频谱图。 文本被标记并转换。
成整数序列。 我必须立即强调说,文本已经规范化:所有数字都是口头写的,可能的缩写也被破译,例如: 鲁滨逊”-“米西斯·鲁宾逊”。
因此,经过预处理后,我们得到了记录在磁盘上npy文件中的数字序列的numpy数组和粉笔频谱图。
因此,在训练阶段,张量张量中的所有维都重合,我们将在短序列中添加填充。 对于文本形式的序列,这些序列将保留用于填充0,对于频谱图,其值略低于我们确定的最小频谱图的帧。 建议隔离这些填充,将其与噪声和静音分开。
现在,我们有了表示文本和音频的数据,适合通过人工神经网络进行处理。 让我们看一下特征预测网的体系结构,它在整个合成系统的中心元素的名称下将被称为Tacotron2。
建筑学
Tacotron 2不是一个网络,而是两个:特征预测网络和
NN-声码器
WaveNet 。 原始文章以及我们对已完成工作的看法,使我们可以将特征预测网视为第一把小提琴,而WaveNet声码器则扮演着外围系统的角色。
Tacotron2是一个
序列到序列的体系结构。 它由一个
编码器 (编码器)和一个
解码器 (解码器)组成,该
编码器创建输入信号的一些内部表示(符号令牌),
解码器将这种表示转变为粉笔频谱图。 网络的一个极其重要的元素是所谓的
PostNet ,其目的是改善解码器生成的频谱图。
图2:Tacotron 2网络架构。让我们更详细地考虑网络模块及其模块。
第一
编码器层是嵌入层。 基于代表字符的自然数序列,它创建多维(512维)向量。
接下来,将嵌入向量馈入三个一维卷积层的块中。 每层包括512个长度为5的过滤器。在此情况下,此值是一个很好的过滤器大小,因为它捕获了某个字符以及它的两个前一个和两个后继邻居。 每个卷积层之后是小批量归一化和ReLU激活。
卷积块后获得的张量应用于双向LSTM层,每个层256个神经元。 转发和返回结果是串联的。
解码器具有循环体系结构,即在每个后续步骤中,都使用前一步骤的输出。 在这里,它们将成为频谱图的一帧。 该系统的另一个重要要素(即使不是关键要素)是柔和(受过训练的)
注意力的机制-一种相对较新的技术,正越来越受到人们的欢迎。 在解码器的每个步骤,注意力形成一个上下文向量并更新注意力权重使用:
- 解码器的RNN网络先前隐藏状态在完全连接层上的投影,
- 将编码器输出投影到完全连接的层上,
- 以及累加(在解码器的每个时间步累加)注意权重。
注意的概念应理解为:“在当前解码器步骤中应使用编码器数据的哪一部分”。
图3:注意机制的方案。在解码器的每个步骤中,
都会计算上下文向量
C i (在上图中表示为“有人值守的编码器输出”),它是编码器输出(
h )和注意力权重(
α )的乘积:

其中,
ij ij是通过以下公式计算的注意力权重:

其中
e ij是所谓的“能量”,其计算公式取决于您使用的注意力机制的类型(在我们的示例中,它将是混合类型,同时使用基于位置的注意力和基于内容的注意力)。 能量由以下公式计算:
e ij = v aT tanh(Ws i-1 + Vh j + Uf i,j + b)其中:
- s i-1-解码器的LSTM网络的先前隐藏状态,
- αi-1-前注意力权重,
- h j是编码器的第j个隐藏状态,
- W , V , U , v a和b是训练参数,
- f i,j-由以下公式计算的位置符号:
f i = F * αi-1
其中F是卷积运算。
为了清楚地了解正在发生的事情,我们添加了以下描述的某些模块,假定使用解码器上一步中的信息。 但是,如果这是第一步,则信息将是零值的张量,这是创建递归结构时的常见做法。
现在考虑
运算算法 。
首先,将来自上一个时间步的解码器输出馈入一个小的PreNet模块,该模块是两个完全连接的256个神经元层的堆栈,这些层与丢失层的交互速率为0.5。 该模块的显着特点是,不仅在模型训练阶段,而且在输出阶段都使用了辍学功能。
与作为关注机制的结果而获得的上下文向量相结合的PreNet输出被馈送到单向两层LSTM网络的输入中,每层LSTM网络中有1024个神经元。
然后,将具有相同(且可能不同)上下文向量的LSTM层的输出串联到具有80个神经元的完全连接的层中,这对应于频谱图的通道数。 解码器的最后一层逐帧形成预测的频谱图。 并且其输出已经作为输入提供给PreNet中解码器的下一个步骤。
为什么我们在上一段中提到上下文向量可能已经不同了? 一种可能的方法是在此步骤获得LSTM网络的潜在状态后重新计算上下文向量。 但是,在我们的实验中,这种方法并不能证明其合理性。
除了投影到80神经元的全连接层上之外,将LSTM层的输出与上下文向量的连接馈送到具有一个神经元的全连接层中,然后进行S形激活-这是“停止令牌预测”层。 他预测在解码器此步骤创建的帧是最终帧的可能性。 该层旨在在模型输出阶段生成一个不固定但任意长度的频谱图。 即,在输出阶段,该元素确定解码器的步数。 可以将其视为二进制分类器。
解码器所有步骤的输出将是预测的频谱图。 但是,这还不是全部。 为了提高频谱图的质量,它通过PostNet模块传递,该模块是5个一维卷积层的堆栈,每个层都有512个过滤器,过滤器大小为5。批处理规范化和切线激活跟随每个层(最后一层除外)。 为了返回频谱图的维度,我们将网络输出数据通过具有80个神经元的完全连接的层传递,并将接收到的数据与解码器的初始结果相加。 我们得到从文本生成的粉笔频谱图。 获利
所有卷积模块均以丢失率为0.5的丢失层进行正则化,而递归层使用更新的
Zoneout方法的0.1的速率进行正则化。 这非常简单:我们没有将在当前步骤中获得的潜在状态和单元状态应用于LSTM网络的下一个时间步骤,而是将部分数据替换为上一步中的值。 这是在培训阶段和退出阶段都完成的。 在这种情况下,每个步骤仅将隐藏状态(传递给下一个LSTM步骤)暴露给Zoneout方法,而当前步骤的LSTM单元的输出保持不变。
我们选择PyTorch作为深度学习框架。 尽管在实施网络时它处于预发布状态,但是它已经是构建和训练人工神经网络的非常强大的工具。 在我们的工作中,我们使用其他框架,例如TensorFlow和Keras。 但是,后者由于需要实现非标准的自定义结构而被丢弃,如果我们比较TensorFlow和PyTorch,则在使用第二个结构时,不会感到该模型已被Python语言撕裂。 但是,我们不保证宣称其中一个更好而另一个更差。 特定框架的使用可能取决于各种因素。
网络通过反向传播方法进行训练。
ADAM被用作优化器,PostNet前后的均方误差以及Stop Token Prediction层的实际值和预测值之上的Binary Cross Entropy被用作误差函数。 产生的错误是这三个的简单总和。
该模型是在具有11 GB内存的单个GeForce 1080Ti GPU上进行训练的。
可视化
使用如此大的模型时,重要的是要了解学习过程。 TensorBoard在这里成为一种方便的工具。 我们在训练和验证迭代中都跟踪了错误的值。 此外,我们还显示了目标频谱图,训练阶段的预测频谱图,验证阶段的预测频谱图以及对齐,这是所有训练步骤中累加累积的注意力权重。
首先,您的注意力可能不会提供太多信息:
图4:训练不足的注意力量表示例。但是,在所有模块开始像瑞士手表一样工作之后,您将最终得到以下内容:
图5:成功训练的注意力量表的示例。该图表是什么意思? 在解码器的每个步骤中,我们尝试解码频谱图的一帧。 但是,尚不清楚编码器在解码器的每个步骤需要使用哪些信息。 可以假定这种对应是直接的。 例如,如果我们有200个字符的输入文本序列和800帧的对应声谱图,则每个字符将有4帧。 但是,您必须承认,基于这种频谱图生成的语音将完全缺乏自然性。 在某些地方我们会停下来,但在某些地方我们不会发音,因此某些单词的发音更快,更慢。 并且考虑所有可能的上下文是不可能的。 这就是为什么注意力是整个系统的关键因素:它设置解码器步骤和编码器信息之间的对应关系,以便获得生成特定帧所需的信息。 并且,注意权重的值越大,则在生成频谱图帧时应更多地“注意”编码器数据的相应部分。
在培训阶段,生成音频也将很有用,而不仅仅是视觉上评估频谱图和注意力的质量。 但是,与WaveNet合作过的人会同意,在培训阶段将其用作声码器在时间上是不可接受的。 因此,建议使用
Griffin-Lim算法 ,该
算法允许在快速傅里叶变换之后对信号进行部分重建。 为什么要部分? 事实是,当我们将信号转换成频谱图时,我们会丢失相位信息。 但是,这样获得的音频质量将足以了解您向哪个方向移动。
经验教训
在这里,我们将分享有关构建开发流程的一些想法,并以提示的形式提交它们。 其中一些是相当笼统的,另一些则更具体。
关于工作流程的组织 :
- 使用版本控制系统,清楚,清楚地描述所有更改。 这似乎是一个显而易见的建议,但仍然如此。 在寻找最佳架构时,会不断发生变化。 在收到令人满意的中间结果之后,请确保将自己设置为检查点,以便您可以安全地进行后续更改。
- 从我们的角度来看,在这样的体系结构中,应该坚持封装的原则:一个类-一个Python模块。 这种方法在ML任务中并不常见,但是它将帮助您构建代码并加快调试和开发速度。 在代码和体系结构构想中,都将其分为多个块,将多个块分为多个模块以及将多个模块分成多个层。 如果模块具有执行特定角色的代码,则将其组合为模块类方法。 这些是普遍的真理,但我们也不愿意再赘述。
- 为numpy风格的类提供文档 。 这将极大地简化您和将阅读您的代码的同事的工作。
- 始终绘制模型的架构。 首先,它将帮助您理解它;其次,该模型的体系结构和超参数的侧视图将使您能够快速识别方法中的不准确性。
- 更好地团队合作。 如果您独自工作,请仍然聚集同事并讨论您的工作。 至少,他们可以问您一个问题,这将使您产生一些想法,但是,最多,他们将指出特定的误差,使您无法成功地训练模型。
- 另一个有用的技巧已经与数据预处理相关联。 假设您决定检验一些假设并对模型进行适当的更改。 但是重新开始训练,尤其是在周末之前,将是有风险的。 该方法最初可能是错误的,您将浪费时间。 那该怎么办? 增加“快速傅立叶变换”窗口的大小。 默认参数是1024;默认参数是1024。 将其增加4倍,甚至8倍。 这将以适当的次数“压缩”频谱图,并大大加快了学习速度。 从它们恢复的音频质量会降低,但这不是您现在的任务吗? 在2到3个小时内,您已经可以进行对齐(注意范围的“对齐”,如上图所示),这将表明该方法的体系结构正确性,并且已经可以在大数据上进行测试。
建立和培训模型 :
- 我们假设,如果批次不是随机形成的,而是基于它们的长度,它们将加快模型训练的过程,并使生成的频谱图更好。 逻辑假设基于以下假设:有用信号(而非填充)越多地馈送到训练网络,效果越好。 但是,这种方法并不能证明其合理性;在我们的实验中,我们无法以这种方式训练网络。 这可能是由于选择训练实例时失去了随机性。
- 使用具有一些优化初始状态的现代网络参数初始化算法。 例如,在我们的实验中,我们使用了Xavier统一权重初始化。 如果在模块中需要通过微型批处理和某些激活功能使用归一化,则它们应按此顺序相互交替。 的确,例如,如果我们应用ReLU激活,那么我们将立即丢失在标准化特定批次数据过程中应涉及的所有负信号。
- 从特定的学习步骤开始,使用动态学习率。 这确实有助于减少误差值并提高生成的频谱图的质量。
- 创建模型并尝试从整个数据集中对模型进行批量训练失败后,尝试对一个批次进行重新训练将很有用。 , alignment, ( ). , , .
. . , – . , . , . - RNN- . . , . ? LSTM- -.
- , LSTM-, « »: « , LSTM-. «» bf. , , , LSTM- ft 1/2. , : , «» 1/2, . bf , 1 2: ft ».
- seq2seq- . — , . ? , ( ).
- 现在是针对PyTorch框架的具体建议。 尽管实际上解码器中的LSTM层是其LSTM单元,该层仅在解码器的每个步骤接收序列的一个元素的信息,但建议使用
torch.nn.LSTM
类而不是torch.nn.LSTMCell
。 原因是LSTM后端是在C中的CUDNN库中实现的,而在Python中是在LSTMCell中实现的。 此技巧将使您显着提高系统速度。
在本文的结尾,我们
将分享从培训集中未包含的文本中产生语音的示例。