网络结构
首先输入数据,进行一维的因果卷积,然后进入到残差块中,残差块是这样的结构:将数据再进行一次空洞卷积,分两路,一路是用tanh()做激活函数,一路是用sigmoid做激活函数,最后又将两路合并,合并完成后,我们在经过一个一维的膨胀卷积,这里得到的输出,我们又会进行两路处理,一路是进入下一次的残差块,一路是往右边发展,经过一个relu激活,一个一维空洞卷积,一个relu,一个一维空洞卷积,再接着softmax ,最后得到输出。
def _create_network(self, input_batch, global_condition_batch):
'''Construct the WaveNet network.'''
outputs = []
current_layer = input_batch
#残差块前面的因果卷积
# Pre-process the input with a regular convolution
current_layer = self._create_causal_layer(current_layer)
output_width = tf.shape(input_batch)[1] - self.receptive_field + 1
#构建dilated convolution
# Add all defined dilation layers.
with tf.name_scope('dilated_stack'):
for layer_index, dilation in enumerate(self.dilations):
with tf.name_scope('layer{}'.format(layer_index)):
output, current_layer = self._create_dilation_layer(
current_layer, layer_index, dilation,
global_condition_batch, output_width)
outputs.append(output)
with tf.name_scope('postprocessing'):
# postprocess the output.
w1 = self.variables['postprocessing']['postprocess1']
w2 = self.variables['postprocessing']['postprocess2']
if self.use_biases:
b1 = self.variables['postprocessing']['postprocess1_bias']
b2 = self.variables['postprocessing']['postprocess2_bias']
if self.histograms:
tf.histogram_summary('postprocess1_weights', w1)
tf.histogram_summary('postprocess2_weights', w2)
if self.use_biases:
tf.histogram_summary('postprocess1_biases', b1)
tf.histogram_summary('postprocess2_biases', b2)
total = sum(outputs) #跳跃连接
#(+) -> ReLU -> 1x1 conv -> ReLU -> 1x1 conv
transformed1 = tf.nn.relu(total)
conv1 = tf.nn.conv1d(transformed1, w1, stride=1, padding="SAME")
if self.use_biases:
conv1 = tf.add(conv1, b1)
transformed2 = tf.nn.relu(conv1)
conv2 = tf.nn.conv1d(transformed2, w2, stride=1, padding="SAME")
if self.use_biases:
conv2 = tf.add(conv2, b2)
return conv2
1.dilated casual convolution
因果卷积casual convolution:在t时刻的预测不会依赖于任何一个未来时刻的数据
空洞卷积dilated convolution:跳过部分输入
使用因果卷积,需要考虑很久之前的输入,需要增加卷积层,卷积层数的增加就带来:梯度消失,训练复杂,拟合效果不好。因此使用空洞卷积在相同的层数下获得更大的感受野。
2.softmax distribution
音频的使用16bit进行存储,意味着没个timestep需要输出65536个值,使用进行压缩量化到256个值。
3.门控激活单元
实验表明音频信号模型中,非线性激活函数比线性激活函数好。
4.残差连接和跳跃连接
缓解因模型过深导致的梯度消失问题。
残差连接可以形象理解为怕忘记很早之前的事,然后写个便条直接提醒自己那件事。
跳跃连接在网络中表现为将几个残差块的输出加权进行相同处理后得到输出。
5.条件建模
通过条件分布建模,接受额外的输入h,我们可以指导WaveNet生成具有目标特点的音频。
实验中,WaveNet的条件建模有全局建模和局部建模两种。
全局条件指可以影响整个timesteps的输出分布。局部条件指们有第二种时间序列,可以通过对原始数据的低采样率获得(比如TTS模型中的线性特征)。我们首先通过transposed convolutional network(上采样)将时间序列转换成和语音序列一样分辨率的新的时间序列。然后将其用于激活单元。
6.context stacks
也是为了增加感受野,具体做法是使用一个独立的更小的上下文堆栈来处理语音信号的长跨度信息。
实验
WaveNet利用真实的人类声音剪辑和相应的语言、语音特征来训练其网络,让其能够辨别这两方面(语言和语音)的音频模式。
实验1和实验3类似,使用音频进行训练,不基于文本或者乐谱,生成的时候,网络想生成生成什么就生成什么,可能会生成非人类的语言,但是在音乐生成中可能别有一番风味。不过实验1会另外输入说话人的id,在训练的时候也是以id进行条件建模。
1.多说话人语音生成
数据集:VCTK(Yamagishi, 2012)数据集中的英文多人语料
输入:说话人的id的one-hot编码
输出:音频
2.TTS
数据集:Google’s North American English and Mandarin Chinese TTS systems的语音数据集
输入:文本的linguistic features
输出:音频
TTS实验中需要搭配一个前端处理器来获取文本的语言学特征(可以是语谱图),Tacotron2的的思路就是如此,Tacotron2得到梅尔频谱图再送入WaveNet得到音频。
3.音乐生成
数据集:the Magna TagATune dataset、the YouTube piano dataset
输入:无(实验中用的非条件建模)
输出:音频
4.语音识别
数据集:TIMIT
这个实验室对网络进行一定的改进,在dilated convolution层后面增加了mean-pooling层,让输出聚集成10毫秒的带宽。pooling层后面接了几层非因果卷积层。我们采用两类loss来训练wavenet。一类loss为预测下一个样本;另外一类loss为对该frame进行分类。
这个实验可以看看下文资料链接中的Blog,用thcs30数据进行训练的,我跑了一下,验证集的loss值最终维持在45-50之间,效果不是特别好,针对中文数据集,可能需要对WaveNet一定改动。
资料
原论文,官方代码,官方Blog
笔记
源码解读
论文翻译
基于WaveNet的自动语音识别系统
WaveNet介绍
合集