Tacotron2を調べてみた1
以前Colaboratoryで試して、英語の音声合成ができることはわかったので、日本語を目標にまずはtacotron2の中身を解説してみます。Googleの論文とNVIDIA実装を中心に見ていきます。
過去にColaboratoryで試した時の記事
akifukka.hatenablog.com
GoogleのTacotron2論文
NVIDIAによるTacotron2実装
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とか)を直す等の前処理が必要です。
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種類あり、複雑な組み合わせにも対応できるようになっています。
ここまでの処理を図にまとめると次のようになります。
②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の処理の概要の図を次に示します。
LSTMはニューロンのFF(フリップフロップ)のようなもので、過去の入力状態を保持することができ、それを使って状態分析を行うことができます。入力を保持する(INPUT GATE)、出力する(OUTPUT GATE)、リセット(FORGET GATE)の3つの制御信号をニューラルネットワークで制御して記憶を操作します。
Tacotron2ではLSTMで離れた位置の文字と文字との関係を分析して発声に反映させることを可能にしているのではないかと思われます。
なお、双方向LSTMを使っているので、前の文字だけではなく、後にくる文字の影響も汲みあげることができるようになっています。
LSTMはRNN(Recurrent Neural Network、再帰型ニューラルネットワーク)を長期記憶できるよう改良したものです。自然言語分析、時系列データの分析といった用途で利用される強力な手法です。
次回、Decoderにつづきます。