Tacotron(Neural text-to-speech) の推論を C++ で動かす(2019 年 6 月 8 日時点 78% くらい C++). 

漢なら, text-to-speech エンジンを C++ で mobile やオフラインで動かしたいですね.

PyTorch も 1.0 で推論を LibTorch で C++ で動かす仕組みができてきて, C++ で推論を動かする機運がたかまっています.

PRNet-infer のように C++ で動かしましょう!

PRNet を C++11 で動くようにして単一画像からの 3D 顔復元を行う.
https://qiita.com/syoyo/items/34be69a4b3bfe9174eeb

今回は keithito's tacotron を利用します.

https://github.com/keithito/tacotron

だいたいできました.

https://github.com/syoyo/tacotron-tts-cpp

## C++ 化した部分

text to speech のコア部分は以下のように実装されています.

```
  def synthesize(self, text):
    cleaner_names = [x.strip() for x in hparams.cleaners.split(',')]
    seq = text_to_sequence(text, cleaner_names)
    feed_dict = {
      self.model.inputs: [np.asarray(seq, dtype=np.int32)],
      self.model.input_lengths: np.asarray([len(seq)], dtype=np.int32)
    }
    wav = self.session.run(self.wav_output, feed_dict=feed_dict)
    wav = audio.inv_preemphasis(wav)
    wav = wav[:audio.find_endpoint(wav)]
    out = io.BytesIO()
    audio.save_wav(wav, out)
    return out.getvalue()
```

`text_to_sequence()` は, 文字を sequence(実装上には int32 の配列)にする処理です.

たとえば white space の削除, 短縮文字の展開(`Dr` を `doctor`), 数字を展開(`10` を `ten`) などにする処理を行います.

たとえば, eval.py にある

```
Scientists at the CERN laboratory say they have discovered a new particle.
````

を sequence にすると以下のようになります.


```
[46, 30, 36, 32, 41, 47, 36, 46, 47, 46, 64, 28, 47, 64, 47, 35, 32, 64, 30, 32, 45, 41, 64, 39, 28, 29, 42, 45, 28, 47, 42, 45, 52, 64, 46, 28, 52, 64, 47, 35, 32, 52, 64, 35, 28, 49, 32, 64, 31, 36, 46, 30, 42, 49, 32, 45, 32, 31, 64, 28, 64, 41, 32, 50, 64, 43, 28, 45, 47, 36, 30, 39, 32, 60, 1]
```

ここは `text` ディレクトリにまとめられていて, C++11 regex を使ってゴリゴリやれば C++ で実装できそうですが, 面倒なのでとりあえずは既存 python 実装で処理させることにします.

しかし真面目にいろいろやろうとすると面倒そうですね...

text-to-speech の前処理のメモ
https://qiita.com/syoyo/items/b5ab4cc857a0541a8e0f


この sequence を入力として TensorFlow で処理させます. ここは C++ でやります.

最後に, Tacotron model で生成された wav 結果を `audio.inv_preemphasis(wav)` する処理があります. ここも `scipy.signal.lfilter` 相当を実装すればよいですが, 実際のところは `lfilter` を実装しなくても実現できます.

あとは `audio.find_endpoint()` で silence 区間を検出してクリップするところもありますが, それも C++ に移植するのは難しくありませんでした.

.wav の入出力には header-only で使える dr_wav を利用しました. https://github.com/mackron/dr_libs/blob/master/dr_wav.h


## keithito の利点

keithito's tacotron 実装を使う利点ですが

* pretrain モデルがすでに用意されている.
* 実装がわかりやすい

また, evaluation では, Griffin-Lim の TensorFlow 実装を使っているため, 処理全体(FFT 含む)を TensorFlow で行える. したがって入力データを用意すればあとは TensorFlow に処理を投げれば結果(wav データ)がえらえるという利点があります.

ところで, TensorFlow では, 普通の FFT は Eigen(CPU backend), spectrogram での FFT は 大石 fft2d を使っているようですね(つまり numpy fft でないので, C++ でも実行できる).

## graph.pb を出力する.

synthesizer.py の load の所で構築された graph(Griffin-Lim 処理も含まれている)を保存するようにします.

```
class Synthesizer:
  def load(self, checkpoint_path, model_name='tacotron'):
    print('Constructing model: %s' % model_name)
    inputs = tf.placeholder(tf.int32, [1, None], 'inputs')
    input_lengths = tf.placeholder(tf.int32, [1], 'input_lengths')
    with tf.variable_scope('model') as scope:
      self.model = create_model(model_name, hparams)
      self.model.initialize(inputs, input_lengths)
      self.wav_output = audio.inv_spectrogram_tensorflow(self.model.linear_outputs[0])

    print('Loading checkpoint: %s' % checkpoint_path)
    self.session = tf.Session()
    self.session.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    saver.restore(self.session, checkpoint_path)

    # write graph
    tf.train.write_graph(self.session.graph.as_graph_def(), "models/", "graph.pb")
```

freeze します. output の node 名前は `model/griffinlim/Squeeze` になります.

```
#!/bin/bash

freeze_graph \
        --input_graph=models/graph.pb \
        --input_checkpoint=/tmp/tacotron-20180906/model.ckpt \
        --output_graph=models/tacotron_frozen.pb \
        --output_node_names=model/griffinlim/Squeeze
```


Voila! これで TTS ができます!   

## 生成速度

TensorFlow だと batch size 1 の推論だと CPU 1 コアしか使わないので, 入力音声の長さの 10 倍くらい生成に時間がかかりました. たとえば 6 秒の生成された音声の合成に 60 秒かかります.

WaveRNN, WaveGlow のような処理効率のよいモデルを C++ に移植できるか検討するのが良さそうですね.

## モデルサイズ

freeze されたモデルは 30 MB くらいでした. 今時のモバイルや組み込み機器では問題なく動くサイズですね. 

## TODO

* 全部 C++ で動くようにする
* Android で動かす.
* Raspberry Pi 3 で動かす.
* WabeGlow https://github.com/NVIDIA/waveglow を pytorch 1.0(LibTorch)で C++ で動かす
* WaveRNN https://github.com/fatchord/WaveRNN を pytorch 1.0(LibTorch)で C++ で動かす
  * WaveRNN 自体は vocoder(Mel spectrogram から音声生成)なので, TTS を行うには Tacotron2 などでテキストから Mel spectrogram を生成する部分が別途必要. PyTorch 実装の Tacotron2 を利用してすべて LibTorch でできないか検討する https://github.com/NVIDIA/tacotron2 
* 優秀な text-to-speech 若人さまが, 人類史上最速で優秀な C++ neural text to speech 若人さまへと昇華なされるスキームを確立する旅に出たい.