Síntese de fala em rede neural usando a arquitetura Tacotron 2, ou "Faça o alinhamento ou tente morrer"



Nossa equipe recebeu a tarefa: repetir os resultados do trabalho da rede neural artificial para a síntese de fala de autoria de Tacotron2, DeepMind. Esta é uma história sobre o caminho espinhoso que percorremos durante a implementação do projeto.

A tarefa da síntese da fala por computador tem sido de interesse de cientistas e especialistas técnicos. No entanto, os métodos clássicos não permitem a síntese da fala, indistinguível da humana. E aqui, como em muitas outras áreas, o aprendizado profundo veio em socorro.

Vejamos os métodos clássicos de síntese.

Síntese Concatenativa da Fala


Este método baseia-se na pré-gravação de pequenos fragmentos de áudio, que são combinados para criar uma fala coerente. Ele se mostra muito limpo e claro, mas completamente desprovido de componentes emocionais e entonacionais, isto é, soa antinatural. E tudo porque é impossível obter uma gravação em áudio de todas as palavras possíveis pronunciadas em todas as combinações possíveis de emoções e prosódia. Os sistemas concatenativos requerem enormes bancos de dados e combinações codificadas para formar palavras. Desenvolver um sistema confiável leva muito tempo.

Síntese paramétrica da fala


Os aplicativos concatenacionais de TTS são limitados devido a altos requisitos de dados e tempo de desenvolvimento. Portanto, foi desenvolvido um método estatístico que explora a natureza dos dados. Ele gera fala combinando parâmetros como frequência, espectro de amplitude, etc.

A síntese paramétrica consiste em duas etapas.

  1. Primeiro, características lingüísticas, como fonemas, duração etc., são extraídas do texto.
  2. Então, para o vocoder (o sistema que gera a forma de onda), são extraídos sinais que representam o sinal de fala correspondente: cepstrum, frequência, espectrograma linear, espectrograma de giz.
  3. Esses parâmetros configurados manualmente, juntamente com os recursos linguísticos, são transferidos para o modelo do codificador de voz e ele realiza muitas transformações complexas para gerar uma onda sonora. Ao mesmo tempo, o vocoder avalia parâmetros de fala, como fase, prosódia, entonação e outros.

Se pudermos aproximar os parâmetros que definem a fala em cada uma de suas unidades, podemos criar um modelo paramétrico. A síntese paramétrica requer significativamente menos dados e trabalho árduo que os sistemas concatenativos.

Teoricamente, tudo é simples, mas na prática existem muitos artefatos que levam à fala abafada com um som "zumbido", que não é de modo algum um som natural.

O fato é que, em cada estágio da síntese, codificamos alguns recursos e esperamos obter um discurso realista. Mas os dados selecionados são baseados em nossa compreensão da fala e o conhecimento humano não é absoluto; portanto, os sinais tomados não serão necessariamente a melhor solução possível.

E aqui o Deep Learning entra em cena em todo o seu esplendor.

As redes neurais profundas são uma ferramenta poderosa que, teoricamente, pode aproximar uma função arbitrariamente complexa, ou seja, trazer algum espaço de dados de entrada X para o espaço de dados de saída Y. No contexto de nossa tarefa, serão texto e áudio com fala, respectivamente.

Pré-processamento de dados


Para começar, determinaremos o que temos como entrada e o que queremos obter na saída.

A entrada será texto e a saída será um espectrograma de giz . Esta é uma representação de baixo nível obtida pela aplicação da transformação rápida de Fourier a um sinal de áudio discreto. Deve-se notar imediatamente que os espectrogramas obtidos dessa maneira ainda precisam ser normalizados , comprimindo a faixa dinâmica. Isso permite reduzir a relação natural entre o som mais alto e o mais silencioso da gravação. Em nossos experimentos, o uso de espectrogramas reduzidos para o intervalo [-4; 4] provou ser o melhor.


Figura 1: Espectrograma de giz do sinal de áudio da fala reduzido para o intervalo [-4; 4].

Como conjunto de dados de treinamento, escolhemos o conjunto de dados LJSpeech , que contém 13.100 trilhas de áudio por 2-10 segundos. e um arquivo com texto correspondente à fala em inglês gravado em áudio.

O som usando as transformações acima é codificado em espectrogramas de giz. O texto é tokenizado e transformado.

em uma sequência de números inteiros. Devo enfatizar imediatamente que os textos estão normalizados: todos os números são escritos verbalmente e possíveis abreviações são decifradas, por exemplo: “Sra. Robinson "-" Missis Robinson ".

Assim, após o pré-processamento, obtemos conjuntos de matrizes numpy de seqüências numéricas e espectrogramas de giz gravados em arquivos npy no disco.

Para que, na fase de treinamento, todas as dimensões dos tensores de correção coincidam, adicionaremos preenchimentos a sequências curtas. Para seqüências na forma de textos, elas serão reservadas para preenchimento 0 e para espectrogramas, quadros cujos valores são ligeiramente inferiores aos espectrogramas mínimos determinados por nós. Isso é recomendado para isolar esses revestimentos, separando-os do ruído e do silêncio.

Agora, temos dados que representam texto e áudio adequados para processamento por uma rede neural artificial. Vejamos a arquitetura da rede de previsão de recursos, que pelo nome do elemento central de todo o sistema de síntese será chamada Tacotron2.

Arquitetura


O Tacotron 2 não é uma rede, mas duas: Rede de previsão de recursos e WaveN de vocação NN. O artigo original, bem como nossa própria visão do trabalho realizado, permite considerar a rede de previsão de recursos como o primeiro violino, enquanto o vocoder WaveNet desempenha o papel de um sistema periférico.

Tacotron2 é uma sequência para sequenciar a arquitetura. Consiste em um codificador (codificador), que cria alguma idéia interna do sinal de entrada (tokens de símbolo) e um decodificador (decodificador), que transforma essa representação em um espectrograma de giz. Também um elemento extremamente importante da rede é o chamado PostNet , projetado para melhorar o espectrograma gerado pelo decodificador.


Figura 2: Arquitetura de rede Tacotron 2.

Vamos considerar com mais detalhes os blocos de rede e seus módulos.

A primeira camada do codificador é a camada de incorporação. Com base em uma sequência de números naturais que representam caracteres, ele cria vetores multidimensionais (512 dimensões).

Em seguida, os vetores de incorporação são alimentados em um bloco de três camadas convolucionais unidimensionais. Cada camada inclui 512 filtros de comprimento 5. Esse valor é um bom tamanho de filtro nesse contexto, porque captura um determinado caractere, bem como seus dois vizinhos anteriores e dois subsequentes. Cada camada convolucional é seguida pela normalização de mini-lote e ativação da ReLU.

Os tensores obtidos após o bloqueio convolucional são aplicados às camadas bidirecionais de LSTM, com 256 neurônios cada. Os resultados de encaminhamento e retorno são concatenados.

O decodificador possui uma arquitetura recorrente, ou seja, a cada etapa subsequente, a saída da etapa anterior é usada. Aqui eles serão um quadro do espectrograma. Outro elemento importante, se não a chave, desse sistema é o mecanismo de atenção suave (treinada) - uma técnica relativamente nova que está ganhando cada vez mais popularidade. Em cada etapa do decodificador, atenção para formar um vetor de contexto e atualizar o peso da atenção usado:

  • a projeção do estado oculto anterior da rede RNN do decodificador em uma camada totalmente conectada,
  • projeção da saída do codificador em uma camada totalmente conectada,
  • bem como pesos de atenção aditivos (acumulados a cada passo do decodificador).

A idéia de atenção deve ser entendida da seguinte forma: “que parte dos dados do codificador deve ser usada na etapa atual do decodificador”.


Figura 3: Esquema do mecanismo de atenção.

Em cada etapa do decodificador, o vetor de contexto Ci é calculado (indicado como “saídas atendidas do codificador” na figura acima), que é o produto da saída do codificador ( h ) e dos pesos de atenção ( α ):



onde α ij são os pesos de atenção calculados pela fórmula:



onde eij é a chamada “energia”, cuja fórmula de cálculo depende do tipo de mecanismo de atenção que você usa (no nosso caso, será um tipo híbrido, usando a atenção baseada em localização e a atenção baseada em conteúdo). A energia é calculada pela fórmula:

e ij = v aT tanh (Ws i-1 + Vh j + Uf i, j + b)

onde:
  • s i-1 - estado oculto anterior da rede LSTM do decodificador,
  • α i-1 - pesos de atenção anteriores,
  • h j é o j-ésimo estado oculto do codificador,
  • W , V , U , va e b são parâmetros de treinamento,
  • f i, j - sinais de localização calculados pela fórmula:

    f i = F * α i-1

    onde F é a operação de convolução.


Para uma compreensão clara do que está acontecendo, acrescentamos que alguns dos módulos descritos abaixo assumem o uso de informações da etapa anterior do decodificador. Mas se esse for o primeiro passo, as informações serão tensores de valores zero, o que é uma prática comum ao criar estruturas de recorrência.

Agora considere o algoritmo de operação .

Primeiro, a saída do decodificador da etapa anterior é alimentada em um pequeno módulo PreNet, que é uma pilha de duas camadas totalmente conectadas de 256 neurônios, alternando com as camadas de abandono com uma taxa de 0,5. Uma característica distinta deste módulo é que o dropout é usado nele não apenas no estágio de treinamento do modelo, mas também no estágio de saída.

A saída PreNet em concatenação com o vetor de contexto obtido como resultado do mecanismo de atenção é alimentada à entrada em uma rede LSTM unidirecional de duas camadas, com 1024 neurônios em cada camada.

Em seguida, a concatenação da saída das camadas LSTM com o mesmo vetor de contexto (e possivelmente diferente) é alimentada em uma camada totalmente conectada com 80 neurônios, o que corresponde ao número de canais do espectrograma. Essa camada final do decodificador forma o espectrograma previsto quadro a quadro. E sua saída já é fornecida como entrada para a próxima etapa do decodificador na PreNet.

Por que mencionamos no parágrafo anterior que o vetor de contexto já pode ser diferente? Uma das abordagens possíveis é recalcular o vetor de contexto depois que o estado latente da rede LSTM for obtido nesta etapa. No entanto, em nossos experimentos, essa abordagem não se justifica.

Além da projeção em uma camada totalmente conectada de 80 neurônios, a concatenação da saída das camadas LSTM com um vetor de contexto é alimentada em uma camada totalmente conectada com um neurônio, seguida pela ativação sigmóide - esta é uma camada de "previsão de token de parada". Ele prevê a probabilidade de que o quadro criado nesta etapa do decodificador seja final. Essa camada foi projetada para gerar um espectrograma de comprimento não fixo, mas arbitrário, no estágio de saída do modelo. Ou seja, no estágio de saída, esse elemento determina o número de etapas do decodificador. Pode ser considerado como um classificador binário.

A saída do decodificador de todas as suas etapas será o espectrograma previsto. No entanto, isso não é tudo. Para melhorar a qualidade do espectrograma, ele é passado através do módulo PostNet, que é uma pilha de cinco camadas convolucionais unidimensionais com 512 filtros em cada um e com um tamanho de filtro de 5. A normalização do lote e a ativação da tangente seguem cada camada (exceto a última). Para retornar à dimensão do espectrograma, passamos os dados de saída pós-rede por uma camada totalmente conectada com 80 neurônios e adicionamos os dados recebidos com o resultado inicial do decodificador. Obtemos o espectrograma de giz gerado a partir do texto. Lucro

Todos os módulos convolucionais são regularizados com camadas de abandono com uma taxa de 0,5 e camadas de recorrência com o método Zoneout mais recente com uma taxa de 0,1. É bem simples: em vez de aplicar o estado latente e o estado da célula obtidos na etapa atual à próxima etapa da rede LSTM, substituímos parte dos dados pelos valores da etapa anterior. Isso é feito tanto na fase de treinamento quanto na de retirada. Nesse caso, apenas o estado oculto (que é passado para a próxima etapa do LSTM) é exposto ao método Zoneout a cada etapa, enquanto a saída da célula LSTM na etapa atual permanece inalterada.

Escolhemos o PyTorch como a estrutura de aprendizado profundo. Embora no momento da implementação da rede ela estivesse em um estado de pré-lançamento, ela já era uma ferramenta muito poderosa para construir e treinar redes neurais artificiais. Em nosso trabalho, usamos outras estruturas, como TensorFlow e Keras. No entanto, o último foi descartado devido à necessidade de implementar estruturas personalizadas não-padrão e, se compararmos o TensorFlow e o PyTorch, ao usar o segundo, não há sensação de que o modelo foi retirado da linguagem Python. No entanto, não nos comprometemos a afirmar que um deles é melhor e o outro pior. O uso de uma estrutura específica pode depender de vários fatores.

A rede é treinada pelo método de retropropagação. O ADAM é usado como um otimizador, o erro médio quadrático antes e depois do PostNet, bem como a entropia cruzada binária acima dos valores reais e previstos da camada Previsão de parada de token, são usados ​​como funções de erro. O erro resultante é uma soma simples desses três.

O modelo foi treinado em uma única GPU GeForce 1080Ti com 11 GB de memória.

Visualização


Ao trabalhar com um modelo tão grande, é importante ver como vai o processo de aprendizado. E aqui o TensorBoard se tornou uma ferramenta conveniente. Rastreamos o valor do erro nas iterações de treinamento e validação. Além disso, exibimos espectrogramas alvo, espectrogramas previstos no estágio de treinamento, espectrogramas previstos no estágio de validação e alinhamento, que é um peso acumulado de atenção acumulado em todas as etapas do treinamento.

É possível que, a princípio, sua atenção não seja muito informativa:


Figura 4: Um exemplo de escalas de atenção mal treinadas.

Mas depois que todos os seus módulos começarem a funcionar como um relógio suíço, você finalmente terá algo como:


Figura 5: Exemplo de escalas de atenção treinadas com sucesso.

O que esse gráfico significa? Em cada etapa do decodificador, tentamos decodificar um quadro do espectrograma. No entanto, não está claro quais informações o codificador precisa usar em cada etapa do decodificador. Pode-se supor que essa correspondência será direta. Por exemplo, se tivermos uma sequência de texto de entrada de 200 caracteres e um espectrograma correspondente de 800 quadros, haverá 4 quadros para cada caractere. No entanto, você deve admitir que a fala gerada com base nesse espectrograma seria completamente desprovida de naturalidade. Pronunciamos algumas palavras mais rápidas, outras mais lentas, em algum lugar que pausamos, mas em algum lugar que não fazemos. E considere que todos os contextos possíveis não são possíveis. É por isso que a atenção é um elemento-chave de todo o sistema: define a correspondência entre a etapa do decodificador e as informações do codificador para obter as informações necessárias para gerar um quadro específico. E quanto maior o valor dos pesos de atenção, mais "atenção deve ser prestada" à parte correspondente dos dados do codificador ao gerar o quadro do espectrograma.

Na fase de treinamento, também será útil gerar áudio, e não apenas avaliar visualmente a qualidade dos espectrogramas e da atenção. No entanto, aqueles que trabalharam com o WaveNet concordam que usá-lo como vocoder na fase de treinamento seria um luxo inaceitável em termos de tempo. Portanto, é recomendável usar o algoritmo Griffin-Lim , que permite a reconstrução parcial do sinal após transformações rápidas de Fourier. Por que parcialmente? O fato é que, quando convertemos o sinal em espectrogramas, perdemos informações de fase. No entanto, a qualidade do áudio assim obtido será suficiente para entender em que direção você está se movendo.

Lições aprendidas


Aqui, compartilharemos algumas idéias sobre a construção do processo de desenvolvimento, enviando-as no formato de dicas. Alguns deles são bastante gerais, outros são mais específicos.

Sobre a organização do fluxo de trabalho :

  • Use o sistema de controle de versão, descreva de forma clara e clara todas as alterações. Isso pode parecer uma recomendação óbvia, mas ainda assim. Ao procurar a arquitetura ideal, ocorrem mudanças constantemente. E, tendo recebido algum resultado intermediário satisfatório, certifique-se de fazer um ponto de verificação para poder fazer as alterações subseqüentes com segurança.
  • Do nosso ponto de vista, nessas arquiteturas deve-se aderir aos princípios do encapsulamento: uma classe - um módulo Python. Essa abordagem não é comum nas tarefas de ML, mas ajuda a estruturar seu código e a acelerar a depuração e o desenvolvimento. No código e na sua visão da arquitetura, divida-o em blocos, blocos em módulos e módulos em camadas. Se o módulo tiver um código que desempenhe uma função específica, combine-o em um método de classe do módulo. Essas são verdades comuns, mas não tivemos preguiça de falar sobre elas novamente.
  • Forneça classes de estilo numpy com documentação . Isso simplificará bastante o trabalho para você e seus colegas que lerão seu código.
  • Sempre desenhe a arquitetura do seu modelo. Primeiro, ele ajudará você a entender o sentido e, em segundo lugar, uma visão lateral da arquitetura e dos hiperparâmetros do modelo permitirá identificar rapidamente imprecisões em sua abordagem.
  • Melhor trabalhar em equipe. Se você trabalha sozinho, ainda reúna colegas e discuta seu trabalho. No mínimo, eles podem fazer uma pergunta que o levará a algumas reflexões, mas, no máximo, apontarão para uma imprecisão específica que não permite que você treine o modelo com sucesso.
  • Outro truque útil já está associado ao pré-processamento de dados. Suponha que você decida testar algumas hipóteses e fazer as alterações apropriadas no modelo. Mas reiniciar o treinamento, especialmente antes do fim de semana, será arriscado. A abordagem pode estar errada inicialmente e você perderá tempo. O que fazer então? Aumente o tamanho da janela de transformação rápida de Fourier. O parâmetro padrão é 1024; aumentá-lo em 4, ou até 8 vezes. Isso "comprime" os espectrogramas no número apropriado de vezes e acelera bastante o aprendizado. O áudio recuperado deles terá uma qualidade inferior, mas essa não é sua tarefa agora? Em 2 a 3 horas, você já pode obter o alinhamento (“alinhamento” das escalas de atenção, como mostrado na figura acima), isso atesta a correção arquitetônica da abordagem e já pode ser testado em big data.

Modelos de construção e treinamento :

  • Nossa hipótese foi que, se os lotes não fossem formados aleatoriamente, mas com base em seu comprimento, eles acelerariam o processo de treinamento do modelo e melhorariam os espectrogramas gerados. A suposição lógica, baseada na hipótese de que quanto mais um sinal útil (e não preenchimento) for fornecido à rede de treinamento, melhor. No entanto, essa abordagem não se justificou; em nossos experimentos, não fomos capazes de treinar a rede dessa maneira. Provavelmente, isso se deve à perda de aleatoriedade na seleção de instâncias para treinamento.
  • Use algoritmos modernos de inicialização de parâmetros de rede com alguns estados iniciais otimizados. Por exemplo, em nossos experimentos, usamos a Inicialização Uniforme de Peso Xavier. Se no seu módulo você precisar usar a normalização por mini-lote e alguma função de ativação, eles deverão alternar entre si nessa ordem. De fato, se aplicarmos, por exemplo, a ativação de ReLU, perderemos imediatamente todo o sinal negativo que deveria estar envolvido no processo de normalização dos dados de um lote específico.
  • Em uma etapa específica de aprendizado, use uma taxa de aprendizado dinâmico. Isso realmente ajuda a reduzir o valor do erro e aumentar a qualidade dos espectrogramas gerados.
  • Após criar o modelo e tentativas malsucedidas de treiná-lo em lotes de todo o conjunto de dados, será útil tentar treiná-lo novamente em um lote. , alignment, ( ). , , .

    . . , – . , . , .
  • RNN- . . , . ? LSTM- -.
  • , LSTM-, « »: « , LSTM-. «» bf. , , , LSTM- ft 1/2. , : , «» 1/2, . bf , 1 2: ft ».
  • seq2seq- . — , . ? , ( ).
  • Agora, uma recomendação específica para a estrutura do PyTorch. Embora a camada LSTM no decodificador seja essencialmente sua própria célula LSTM, que recebe informações para apenas um elemento da sequência em cada etapa do decodificador, é recomendável usar a classe torch.nn.LSTM vez de torch.nn.LSTMCell . O motivo é que o back-end LSTM é implementado na biblioteca CUDNN em C e o LSTMCell em Python. Este truque permitirá aumentar significativamente a velocidade do sistema.

E no final do artigo, compartilharemos exemplos de geração de fala a partir de textos que não estavam contidos no conjunto de treinamento.

Source: https://habr.com/ru/post/pt436312/


All Articles