Created 02/21/2020 at 09:20PM

GitHub地址

概述

最近一直在思考如何实现没有teacher model的FastSpeech,记录又一次尝试。众所周知,FastSpeech之所以需要一个teacher model,是因为需要alignment来进行encoder output的expand,如果模型可以学出来alignment信息,那就不需要teacher model了,然而auto regressive model的alignment信息来源于循环迭代当中的不断搜索,训练过程中,decoder解码的每一步的输入都是ground truth,这样大幅减少了搜索空间,让模型可以学到alignment。Non auto regressive model training期间没有ground truth进行输入,直接预测,这样一来,搜索空间增大很多,如果要让模型直接学习alignment,就更加难上加难了。

尝试

  1. 第一次尝试:试图用Reinforcement Learning,将encoder和length regulator视为一个actor,预测出来的expand次数作为action,合成的准确度作为reward,实现的地址在这里。这是一次彻头彻尾的失败,loss(reward)甚至都不准确,因为最后预测出来的mel spectrogram和ground truth长度上就不一致,直接计算loss的话有很大的问题。
  2. 第二次尝试:RL的方法在我这行不通,我就去阅读了一些NAT(Non Auto-regressive Translation)的paper,其中有一篇End-to-End Non-Autoregressive Neural Machine Translation with Connectionist Temporal Classification给了我一点启发,我能不能先以固定比例expand encoder output,再进行一个二分类,决定expand后的encoder output的去留问题,这样的话,模型最后output的长度和ground truth相同,计算出来的loss也就有实际意义,下面的章节将阐述我的设计。

设计思路

由上述的思路,模型的初步结构如下:


这样,模型有两个loss,一个是二分类的loss,一个是与ground truth之间的loss,为了模拟一个attention matrix,我还在expand的阶段加了一些类似于attention结构的模块(现在看来好像并没有什么用),直接看代码:

class FastSpeech(nn.Module):
    """ FastSpeech """

    def __init__(self):
        super(FastSpeech, self).__init__()

        self.encoder = Encoder()

        self.parallel_attention = ParallelAttention()

        self.decoder = Decoder()

        self.mel_linear = Linear(hp.decoder_output_size, hp.num_mels)
        self.postnet = PostNet()
class ParallelAttention(nn.Module):
    " Parallel Attention "

    def __init__(self):
        super(ParallelAttention, self).__init__()

        self.conv_list = nn.ModuleList()
        for _ in range(hp.PA_conv_num):
            self.conv_list.append(Conv(hp.encoder_output_size,
                                       hp.encoder_output_size,
                                       kernel_size=3,
                                       padding=1))

        self.conv_full = nn.ModuleList()
        for _ in range(hp.conv_full_num):
            self.conv_full.append(Conv(hp.encoder_output_size,
                                       hp.encoder_output_size,
                                       kernel_size=3,
                                       padding=1))

        self.core_w = Linear(hp.encoder_output_size,
                             hp.encoder_output_size,
                             bias=False)

        self.full_w = Linear(hp.encoder_output_size,
                             hp.encoder_output_size,
                             bias=False)

        self.softmax = nn.Softmax(dim=-1)

    def cat(self, attention_cores):
        pass

    def forward(self, memory, src_pos, mel_max_length_src):
        attention_cores = list()
        attention_cores.append(self.conv_list[0](memory))

        # memory即为encoder output
        dot = self.core_w(memory)

        # hp.PA_conv_num = 10,扩增10次
        for i in range(1, hp.PA_conv_num):
            q = torch.bmm(attention_cores[-1],
                          dot.contiguous().transpose(1, 2))
            v = self.softmax(q)
            next_conv_core_input = memory - torch.bmm(v, memory)
            attention_cores.append(self.conv_list[i](next_conv_core_input))

        # 如果encoder output长度是140的话,那些经过上面步骤,
        # 有10个新的encoder output,再把它们交叉concatenate一起,
        # 就成了一个长度为1400的encoder_output_expanded
        attention_weight = self.cat(attention_cores)
        for i in range(hp.conv_full_num):
            attention_weight = self.conv_full[i](attention_weight)

        dot_full = self.full_w(memory)

        full_q = torch.bmm(attention_weight,
                           dot_full.contiguous().transpose(1, 2))
        attention_map = self.softmax(full_q)

        decoder_input = torch.bmm(attention_map, memory)

        ...

        ...

        return ...

训练

没有机器,只用batch size为8训练了60000步,loss没有收敛,估计是不work了。

分析

改进思路