やってみた!

やってみた!

試したことを中心に、書評や興味のあること、思ったこととか

Tacotron2を調べてみた1

 以前Colaboratoryで試して、英語の音声合成ができることはわかったので、日本語を目標にまずはtacotron2の中身を解説してみます。Googleの論文とNVIDIA実装を中心に見ていきます。

過去にColaboratoryで試した時の記事
akifukka.hatenablog.com

 GoogleのTacotron2論文

arxiv.org

NVIDIAによるTacotron2実装

github.com

 1.概要

 Tacotron2はGoogleで開発されたTTS(Text To Speech)アルゴリズムです。テキストをmel spectrogramに変換、mel spectrogramを音声波形に変換するという大きく2段の処理でTTSを実現しています。本家はmel spectrogramを音声波形に変換する箇所はWavenetからの流用で、Tacotron2の本体はテキストからmel spectrogramへの変換部分と言えます。なお、NVIDIA実装では処理軽量化のため音声波形への変換部分をWaveGlowに変更しています。

 次に示すのは論文に掲載されていた全体構造の図(ちょっと追記しました)です。Tacotron2の処理は大きく次の4つに分けられます。
①Character Enbedding
②Encoder
③Decoder
・Location Sensitive Attenation
④Post Net

 また、図にはありませんがTacotron2に入力する前に省略表記(Mr、Missや、1st、2ndとか)を直す等の前処理が必要です。

f:id:akifukka:20191116212142j:plain

 Tactron2はエンコーダ・デコーダモデルです。エンコーダで入力データを解析、入力データを中間データに変換し、デコーダで中間データの情報を出力データに変換します。出力データ長を自在に変えられるのが特徴で、自動翻訳などで使われます。

2.NVIDIA実装

 NVIDIA実装を使ってTacotron2の仕組みを見ていきます。
 NVIDIA実装におけるTacotron2の主体は、model.pyのclass Tacotron2です。クラスTacotron2のinferenceを次に示します。

    def inference(self, inputs):
        embedded_inputs = self.embedding(inputs).transpose(1, 2)
        encoder_outputs = self.encoder.inference(embedded_inputs)
        mel_outputs, gate_outputs, alignments = self.decoder.inference(
            encoder_outputs)

        mel_outputs_postnet = self.postnet(mel_outputs)
        mel_outputs_postnet = mel_outputs + mel_outputs_postnet

        outputs = self.parse_output(
            [mel_outputs, mel_outputs_postnet, gate_outputs, alignments])

        return outputs

2.1 Character Embedding

 Embedding(日本語で組み込みの意味)はニューラルネットワークに入力できるよう、入力それぞれにテンソルを割り当てる処理のことを言います。今回の場合は65種類の入力(文字)にそれぞれ512次元のテンソルを割り当てます。テンソルの各項目の値を学習で変化させることで値に特徴の意味を持たせて分類することができます。例えば大文字のAと小文字のaでは実質同じとかです。

 Character EmbeddingはクラスTacotron2のinferenceの次の文で設定しています。

embedded_inputs = self.embedding(inputs).transpose(1, 2)

 この文はtorch.nn.embedding()を呼び出してembeddingの設定をします。transposeはテンソルの軸の入れ替えで、後で実行するConvolutional networkにあわせて軸(文字の順番とテンソルの次元)を入れ替えています。(1,文字数,512)→(1,512,文字数)。

 self.embeddingはclass Tacotron2 __init__で次のように定義されています。

self.embedding = nn.Embedding(
 hparams.n_symbols, hparams.symbols_embedding_dim)
std = sqrt(2.0 / (hparams.n_symbols + hparams.symbols_embedding_dim))
val = sqrt(3.0) * std # uniform bounds for std
self.embedding.weight.data.uniform_(-val, val)

 パラメータは次の通りです。
hparams.n_symbols = アルファベット大文字+小文字+記号類でたぶん65
hparams.symbols_embedding_dim = 512

参考

Word Embeddings: Encoding Lexical Semantics — PyTorch Tutorials 1.3.0 documentation

2.2 Encoder

 EncoderはクラスTacotron2のinferenceの次の文で処理しています。__init__でself.encoder = Encoder(hparams)を宣言しているので、次の文でクラスEncoderのinferrenceが呼び出されます。

encoder_outputs = self.encoder.inference(embedded_inputs)

 class Encoderのinferenceを次に示します。

    def inference(self, x):
        for conv in self.convolutions:
            x = F.dropout(F.relu(conv(x)), 0.5, self.training)

        x = x.transpose(1, 2)

        self.lstm.flatten_parameters()
        outputs, _ = self.lstm(x)

        return outputs

①3層Convolutionalネットワーク

 上記inference中のconvolutionsはclass Encoderの__init__で次のように定義されています。

for _ in range(hparams.encoder_n_convolutions):
            conv_layer = nn.Sequential(
                ConvNorm(hparams.encoder_embedding_dim,
                         hparams.encoder_embedding_dim,
                         kernel_size=hparams.encoder_kernel_size, stride=1,
                         padding=int((hparams.encoder_kernel_size - 1) / 2),
                         dilation=1, w_init_gain='relu'),
                nn.BatchNorm1d(hparams.encoder_embedding_dim))
            convolutions.append(conv_layer)

ここで、hparams.encoder_n_convolutions = 3です。
さらにConvNormはlayers.pyでクラスConvNormとして次のように定義されています。

self.conv = torch.nn.Conv1d(in_channels, out_channels,
  kernel_size=kernel_size, stride=stride,
  padding=padding, dilation=dilation,
  bias=bias)

 パラメータは次の通りです。
in_channels = 512, out_channels = 512, kernel_size = 5, stride = 1, padding = 2, dilation = 1, bias = True

 5 × 512のfilterを512種使ったConvolutional(折り畳みネットワーク)が3層定義されています。

 この3層Convolutionalネットワークで5文字の組み合わせ(filter)を使い発音の特徴を抽出しています。各層ごとにfilterは512種類あり、複雑な組み合わせにも対応できるようになっています。

 ここまでの処理を図にまとめると次のようになります。

f:id:akifukka:20191116130357j:plain

②Bydirectional LSTM(Long Short Term Memory)

 Bydirectional LSTMはクラスEncoderのinferenceの次の文で実行されます。

self.lstm.flatten_parameters()
outputs, _ = self.lstm(x)

 flatten_parametersはRNNのparameter datapointerをリセットして処理を高速化するための処理とのこと。

 self.lstmは__init__で次のように定義されています。赤字は解説用に追記しました。

self.lstm = nn.LSTM(hparams.encoder_embedding_dim = 512,
    int(hparams.encoder_embedding_dim / 2) = 256, 1,
    batch_first=True, bidirectional=True)

 pytoachのライブラリnn.LSTMを使っています。
 入力512次元、隠れ要素数256、層数1、双方向です。LSTMの出力テンソルの次元 = 隠れ要素数ですが、双方向なので×2して512次元のテンソルになります。

 LSTMの処理の概要の図を次に示します。

f:id:akifukka:20191116182920j:plain

 LSTMはニューロンのFF(フリップフロップ)のようなもので、過去の入力状態を保持することができ、それを使って状態分析を行うことができます。入力を保持する(INPUT GATE)、出力する(OUTPUT GATE)、リセット(FORGET GATE)の3つの制御信号をニューラルネットワークで制御して記憶を操作します。

 Tacotron2ではLSTMで離れた位置の文字と文字との関係を分析して発声に反映させることを可能にしているのではないかと思われます。

 なお、双方向LSTMを使っているので、前の文字だけではなく、後にくる文字の影響も汲みあげることができるようになっています。

  LSTMはRNN(Recurrent  Neural Network、再帰ニューラルネットワーク)を長期記憶できるよう改良したものです。自然言語分析、時系列データの分析といった用途で利用される強力な手法です。

次回、Decoderにつづきます。

 

音声合成 カテゴリーの記事一覧へ