【機械学習】VITSでアニメ声へ変換できるボイスチェンジャー&読み上げ器を作った話

# はじめに
DiscordやVRChatなど、オンラインでの会話を楽しむプラットフォームが近年賑わいを見せています。
そんな中で「さまざまな声質の音声で会話ができたら面白いだろう」と思い立ち、音声間での声質の変換ができる機械学習モデルを実装してみました。例えば以下の紹介動画のように変換できます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">機械学習の一手法「VITS」でアニメ声(つくよみちゃん)へ変換できるボイスチェンジャーを実装しました。<a href="https://t.co/LX0TV13uAD">https://t.co/LX0TV13uAD</a> <a href="https://t.co/vVWcDbUSpn">pic.twitter.com/vVWcDbUSpn</a></p>&mdash; zassou (@zassouEX) <a href="https://twitter.com/zassouEX/status/1497358678965760000?ref_src=twsrc%5Etfw">February 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<br>

また、今回用いたモデルではテキストの読み上げを実行することもできます。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">読み上げを行うこともできます。<br>(こちらに関してはもう少しファインチューニングが必要そうです。) <a href="https://t.co/xKPhTL7A0E">pic.twitter.com/xKPhTL7A0E</a></p>&mdash; zassou (@zassouEX) <a href="https://twitter.com/zassouEX/status/1497359192860291072?ref_src=twsrc%5Etfw">February 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<a href="https://github.com/zassou65535/VITS">実装したコードはこちら</a>
以下ではこれに用いた手法について解説します。

# データセット
今回はデータセットとして「<a href="https://sites.google.com/site/shinnosuketakamichi/research-topics/jvs_corpus">JVS corpus</a>」と「<a href="https://tyc.rei-yumesaki.net/material/corpus/">つくよみちゃんコーパス</a>」を用いました。
JVS corpusは100人の俳優/声優によるテキストの読み上げ(.wav形式)と、発話内容といったラベルが付与されたデータセットです。
つくよみちゃんコーパスはアニメ声によるテキストの読み上げと、対応するラベルがつけられたデータセットです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">✨つくよみちゃんコーパス公開！✨<br><br>■Vol.1 声優統計コーパス（JVSコーパス準拠）<br>⇒<a href="https://t.co/tfqAFxQEds">https://t.co/tfqAFxQEds</a><br><br>高音ウィスパー系の14歳前後のアニメキャラクター風ボイスを目指して作りました。<br><br>一部の言葉にふりがなやアクセント記号を追加した台本も同梱しています。（詳しい説明もあります） <a href="https://t.co/VtJ6ji2Qu3">pic.twitter.com/VtJ6ji2Qu3</a></p>&mdash; つくよみちゃん®【フリー素材キャラクター】 (@TYC_Project) <a href="https://twitter.com/TYC_Project/status/1365314526887878658?ref_src=twsrc%5Etfw">February 26, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
JVS corpusとつくよみちゃんコーパスから成る合計101人の発話音声と、対応するテキスト(セリフ)をデータセットとします。
<br>
今回使用するモデルは発話内容を音素の形式で利用するため、データセットに付属しているテキストを音素に変換する必要があります。
これには<a href="https://github.com/r9y9/pyopenjtalk">pyopenjtalk</a>を用いました。これによって日本語文章を合計40種類の音素からなる音素列へと分解します。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/ab476f29-3d06-a8c3-cbec-eca1d0f80071.png" width=95%>
</div>

# VITS
今回は音声変換には**VITS**というモデルを用いました。VITSは

- テキストから音声の生成(読み上げ、Text-to-Speechなどとも呼ばれる)
- 音声の声質の変換

の2つが行えるモデルであり、**学習時は**以下のような構成を取ります(推論時については後述します)。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/9090259a-5e0c-3bdd-707e-c42c608e27ab.png" width=100%>
</div>

以下ではVITSを構成する各ネットワークについて順番に解説します。

## Variational Autoencoder(VAE)

Posterior EncoderとDecoderからなる構造は<a href="https://qiita.com/kenmatsu4/items/b029d697e9995d93aa24">Variational Autoencoder(VAE)</a>と呼ばれるモデルをベースにしています。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/f938d8d3-8560-90d7-fdfd-db4328cd3c69.png" width=100%>
</div>

Posterior Encoderは入力音声のスペクトログラムを入力にとり、入力音声を入力音声たらしめている特徴を抽出、潜在変数$z$として出力するためのネットワークです。
一方Decoderは抽出された特徴$z$を入力にとり、音声を波形という形で復元できるよう学習します。
「入力スペクトログラム→$z$→音声波形」と変換したとき、うまく元の音声を復元できるように目指し学習を進めます。

また、学習時はメモリ節約のため、Decoderへの入力は$z$全体ではなく、$z$から数サンプル分ランダムな箇所から切り取ったものとします。(推論時は$z$全体を入力とします。)
$z$で切り取った箇所に相当する部位の情報を復元できるようになるのを目標に学習を行います。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/2870e317-9e6d-5661-637d-1269dc3fdf9c.png" width=75%>
</div>

スペクトログラムの横軸は時刻、縦軸は周波数です。一方メルスペクトログラムはこれに少しアレンジを加えたような方式で、縦軸が対数スケールのようになっており、低い周波数の情報ほど詳細に図示することができます。メルスペクトログラムは、人間の聴覚は周波数の低い音に対して敏感で、周波数の高い音に対して鈍感であるという性質から考案された方式であり、これを入力と出力で一致させるよう目指すことでより自然な音声の生成が可能となるようです。

## Generative adversarial networks(GAN)

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/d7a1e8d7-2157-3461-b724-00c231ec80b8.png" width=100%>
</div>

DiscriminatorとDecoderからなる構造は<a href="https://qiita.com/zassou65535/items/4bc42fa36203c13fe2d3#gan%E3%81%A8%E3%81%AF">Generative adversarial networks(GAN)</a>という仕組みがベースとなっています。これは2つのネットワークを競わせるようにして学習する方式であり、VITSにおいては以下のようにして利用します。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/1622c21b-d98f-1bad-2ca1-2b9169518ca2.png" width=80%>
</div>

Discriminatorはデータセット中の音声波形(本物音声と呼ぶことにします)と、上で述べたDecoderによって出力された音声波形(偽物音声と呼ぶことにします)の2種類を入力にとり、それらが本物か偽物かを正しく識別できるように目指し学習を進めます。
一方でDecoderは生成した音声によって、Discriminatorを本物音声だと騙せるよう目指し学習を進めます。

DiscriminatorとDecoderが互いに鍛え合うようにして学習を進めることで、Decoderはより本物音声に近い音声を生成できるようになっていきます。

## Flow
<a href="https://qiita.com/shionhonda/items/0fb7f91a150dff604cc5">Flow</a>とは、入力と出力が可逆という特徴を持つ種類のネットワークです。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/9058d29f-85d5-3fb5-cf8f-7c8e47c028b7.png" width=100%>
</div>

VITSで用いられているFlowは、学習時は$z$と話者idの情報を入力にとり、$z$から話者の情報をできるだけ取り除き、発音に関する情報$z_p$を抽出する役割を果たします。

Flowなので逆を行うこともできます。$z_p$に対し別の話者の情報を付加し$z$を生成することもできます。

以降では順方向のFlowを$f_{\theta}(\cdot)$とし、逆方向のFlowを$f_{\theta}^{-1}(\cdot)$と表記します。

## Text Encoder
<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/e6509ab9-76bd-3974-a9cc-c389a44ad96e.png" width=100%>
</div>

Text Encoderは入力音素列に対し、Transformerのような形状のネットワークを用いてそれらを潜在表現$h_{text}$へと変換する役割をします。
Projectionでは畳み込みを用いて、$h_{text}$からパラメーター$\mu$と$\sigma$を生成します。各音素に対し、対応する$\mu$と$\sigma$が生成されるようなイメージです。
VITSではこれらのパラメーターを用い後述する処理を行うことで、音声と音素の関連付けを行います。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/a9632133-f691-51e4-93f3-b729248bc8d7.png" width=60%>
</div>

## Monotonic Alignment Search(MAS)
<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/73ffe783-46e5-6ea6-9b4b-0612baabae93.png" width=100%>
</div>
Monotonic Alignment Search(MAS)では音声の情報と音素の情報の関連付けを行います。

### Monotonic Alignment
Monotonic Alignmentとは

- 戻らない
- 飛ばさない
- 最後まで到達する

を満たすpathです。

MASではMonotonic Alignmentを求めることによって音声の情報と音素の情報の関連付けを行います。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/273c93e0-3f8f-baca-5bdc-fc2aae85e22a.png" width=90%>
</div>

例えば、上の図ではMonotonic Alignmentによって、入力の(i-2)番目の音素が、音声の情報のうち時刻=(j-4),(j-3),(j-2)の要素と関連付けられています。
入力音声のうち、どの時刻からどの程度の間、何を発音しているかを関係付けているようなイメージです。

### 各ノードの尤度
MASではpathを、対数尤度の累積和が最大となるようにDP(動的計画法)で求めます。

$i$行目$j$列目のノードの対数尤度$L_{i,j}$は次のようにして求めることができます。

$$L_{i,j} = log\\{N(f_{\theta}(z)[j]; \mu[i],\sigma[i])\\}$$

ただし$N$は正規分布、$f_{\theta}(z)[j]$は$f_{\theta}(z)$の$j$番目の値、$\mu[i],\sigma[i]$はそれぞれ$i$番目の音素に対応する$\mu,\sigma$です。

### DPの実行

対数尤度の累積和が最大となるようにpathを求めます。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/e26e8aa1-4495-9a9c-9bf5-53bc7cb7d855.png" width=90%>
</div>

$Q_{i,j}=\max ( Q_{i-1,j-1}, Q_{i,j-1} ) + L_{i,j} $を繰り返し実行し、尤度の累積和の最大値を求めます。$(i,j)$にたどり着くような無数のpathがそれぞれ持つ累積和の中から、最も大きいものが$Q_{i,j}$に順次格納されていくイメージです。

これで対数尤度の累積和の最大値は求まりますが、まだpathそのものは求めることができていません。そこで、来たであろう道を逆向きに辿って求めます。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/b1730545-8fb2-40b8-02d2-d2a624d19306.png" width=90%>
</div>

座標$(i,j)$から$(i-1,j-1)$もしくは$(i,j-1)$どちらに進むか考えているとします。この時両者のうち求めた累積和の大きい方を選び、その座標へと移動します。例えば、$Q_{i-1,j-1}$と$Q_{i,j-1}$のうち、後者の方が大きいならば$(i,j-1)$へと移動します。
これを繰り返し、通った箇所を記録していくことでpathを求めることができます。

## Stochastic Duration Predictor

Stochastic Duration PredictorはText-to-Speechの推論時、各音素がどの程度間発音されているかを予測するためのネットワークです。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/69bbc651-cb80-5434-05a9-2c9fa77a13b8.png" width=100%>
</div>

### 学習時

学習時は上の画像のように、以下の情報3つを入力にとり、それらの分布を変形して正規分布へと落とし込むことを目標とします。

- 話者id
- $h_{text}$
- Duration

Durationは各音素がどの程度の時間発音されたかを表す数値で、MASで求めたpathから計算します。
例えば、下の図であれば(i-2)番目の音素に対応するDurationは3です。i番目の音素に対応するDurationは2です。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/273c93e0-3f8f-baca-5bdc-fc2aae85e22a.png" width=90%>
</div>

### 推論時

推論時は、入力された音素の情報$c$(図では$h_{text}$に相当)とNoiseを用いて、Duration(その音素がどの程度の間発音されていたか)$d$を推定することを目的とします。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/faaf9e28-27ed-812c-f114-5084456d0dc3.png" width=90%>
</div>

### アーキテクチャ

ここで、各音素に対しDurationを推定するのには、以下の理由から一工夫必要になります。

1. Durationが離散整数値であるために、そのままではFlowでの連続最適化が難しい
2. また、1次元であるために、高次元の分布へと落とし込むのが難しい

1.と2.をそれぞれ以下の手法で解決します。

- <a href="https://arxiv.org/abs/1902.00275">variational dequantization</a>
入力する離散整数値に、乱数$u\in{[0,1)}$を加算して連続値へと変える手法

- <a href="https://arxiv.org/abs/2002.09741">variational data augmentation</a>
入力データと、追加で生成したデータ$v$をセットにして処理することでデータの次元を増やす手法

これらを踏まえた上で、Stochastic Duration Predictorは以下のようなアーキテクチャとなります。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/4040e424-32e9-681b-7074-dd25c4b4084e.png" width=100%>
</div>

- 学習時
Stochastic Duration Predictorは構成要素として2種類のFlow(図の$g_{posterior}$と$g_{\theta}$)を含んでいますが、このうち$g_{posterior}$は学習時にのみ用います。
$g_{posterior}$は正規分布からサンプリングされた乱数$\epsilon_{Q}$を入力にとり、それを用いて$v$と$u$を生成します。次に$d-v$を計算、$u$とセットで$g_{\theta}$へ入力します。$g_{\theta}$の出力$\epsilon_{D}$が正規分布$N(0,I)$にできるだけ従うよう学習します。

- 推論時
正規分布$N(0,I)$から乱数$\epsilon_{D}$をサンプリング、$g_{\theta}^{-1}$への入力とし$d-v$と$u$を出力させます。さらに、連続値$d-v$の小数点以下を切り上げることによって離散値$d$へと変換します。
このようにして各音素の潜在表現(図の$h_{text}$)に対しそれぞれのDurationを推定します。$\epsilon_{D}$を用いてDurationに確率的な変動を持たせることでより高品質な音声を合成できるようです。

# 推論時(テキスト読み上げ)
学習時と推論時でVITSは違ったネットワーク構成をとります。
テキストから音声の生成時は次のようになります。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/48f84fb5-c328-ae7a-a98d-63c49809cb68.png" width=70%>
</div>

入力された各音素をそれぞれ潜在表現に変換し、それらに対しDuration(整数値)を求め音声の合成を行います。

# 推論時(音声変換)
音声変換時は次のような構成をとります。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/a9b77ee0-7ef9-0769-55d7-75e9977e8c16.png" width=40%>
</div>

学習時と違い、音声変換の推論においてはテキストの情報は一切必要としません。そのためStochastic Duration Predictorが不要となります。

# 損失関数
VITSでは以下の、大きく分けて3種類の損失関数を用います。

1. VAEによるloss  
    - Reconstruction loss  
$$L_{rec} = E [ |x_{mel}-\hat{x}_{mel}| ]$$
    - KL divergence loss  
$$L_{kl} = E [ \log \{ \sigma_{te}(x) \} - \log \{ \sigma_{pe}(x) \} - \frac{1}{2} + \frac{(f_{\theta}(z) - \mu_{te}(x))^{2}}{2(\sigma_{te}(x))^{2}} ]$$
2. GANによるloss  
    - Adversarial loss  
$$L_{adv}(D) = E[ (D(y)-1)^2 + (D(G(z)))^2 ]$$
$$L_{adv}(G) = E[ (D(G(z))-1)^2 ]$$
    - Feature matching loss  
$$L_{fm} = E[ \sum_{l=1}^{T} \frac{1}{N_{l}} |D^{l}(y) - D^{l}(G(z))|_1 ]$$
3. Duration loss  

```math
\begin{align}

L_{dur} = E_{q_{\phi}(u,v|d,c)}[&\frac{1}{2}(\log (2\pi) + \epsilon_{D}^{2} )\\ -& \frac{1}{2}(\log (2\pi) + \epsilon_{Q}^{2} )\\ -& \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert\\ -& \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\

\end{align}
```

(ただし$E$はバッチ内で平均をとる操作に相当します。)

これらの損失関数について順番に解説していきます。

## VAEによるloss
$x$を生成したい音声とします。この時音声の分布$p(x)$が分かれば$x$を生成することができます。

学習時に、モデルを動かした時に音声$x$が出力されたとします。このとき尤度は$p(x)$となります。VITS(VAE)ではこれの対数尤度$\log p(x)$を最大化するよう目指すことで、モデルが音声の生成を学習します。

### 対数尤度の計算

具体的にどのようにすれば対数尤度$\log p(x)$を求め、実装に落とし込めるか考えます。

まず潜在変数$z$を導入します。音声を音声たらしめている特徴が格納された変数のようなイメージです。
$z$は、データセットの音声$x$を入力にとり、特徴を抜き出し$z$を出力してくれる都合の良いモデルがあれば求まりそうな気がします。これは先ほど説明したPosterior Encoderに相当するモデルで、$q(z|x)$とおきます。この時対数尤度$\log p(x)$は次のように変形できます。

```math
\begin{align}

\log p(x) &= \log \{ p(x)\} \int q(z|x) dz \\
&= \int q(z|x) \log \{ p(x) \} dz \\
&= \int q(z|x) \log \{ p(x)\frac{p(z|x)}{p(z|x)} \} dz \\
&= \int q(z|x) \log \{ \frac{p(x,z)}{p(z|x)} \} dz \\
&= \int q(z|x) \log \{ \frac{p(x,z)}{p(z|x)} \frac{q(z|x)}{q(z|x)} \} dz \\
&= \int q(z|x) \log \{ \frac{q(z|x)}{p(z|x)} \frac{p(x,z)}{q(z|x)} \} dz \\
&= \int q(z|x) \{ \log \{ \frac{q(z|x)}{p(z|x)} \} + \log \{ \frac{p(x,z)}{q(z|x)} \} \} dz \\
&= \int q(z|x) \log \{ \frac{q(z|x)}{p(z|x)} \} + \int q(z|x) \log \{ \frac{p(x,z)}{q(z|x)} \} dz \\

\end{align}
```
<br>
このうち、第1項は$q(z|x)$と$p(z|x)$の間の<a href="https://qiita.com/ceptree/items/9a473b5163d5655420e8#kl%E3%83%80%E3%82%A4%E3%83%90%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%B3%E3%82%B9">KLダイバージェンス</a>の形となっています。KLダイバージェンスとは2つの分布がどれだけ似ているかを表す指標で、同じ分布間では0となる、常に0を含む正の値となるといった性質を持ちます。

第2項は**Evidence Lower Bound(ELBO)**と呼ばれる値で、次のように定義します。
$$\mathcal{L} = \int q(z|x) \log \{ \frac{p(x,z)}{q(z|x)} \} dz$$

第1項は事後分布を含むため計算が困難です。しかしKLダイバージェンスは常に0を含む正の値となるという性質を持ちます。そのため対数尤度は第2項のELBOで下から抑えることができます。

$$\log p(x)\geq\mathcal{L}$$

VAEではこのELBOを最大化するよう学習します。
実装上の都合のため、以降ではELBOの最大化を$-\mathcal{L}$の最小化と言い換えて話を進めます。

ELBOは次のように変形できます。

```math
\begin{align}

-\mathcal{L} &= -\int q(z|x) \log \{ \frac{p(x,z)}{q(z|x)} \} dz \\
&= -\int q(z|x) \log \{ \frac{p(x|z)p(z)}{q(z|x)} \} dz \\
&= - \int q(z|x) \log \{ p(x|z) \} dz + \int q(z|x) \log \{ \frac{q(z|x)}{p(z)} \} dz \\

\end{align}
```

ここで、VITSでは上の式の事前分布$p(z)$の箇所を$p(z|c)$に置き換え、$z$の事前分布を音素列$c$に関連付けます。

```math
\begin{align}

-\mathcal{L} 
&= - \int q(z|x) \log \{ p(x|z) \} dz + \int q(z|x) \log \{ \frac{q(z|x)}{p(z|c)} \} dz \\

\end{align}
```

このうち第1項がReconstruction loss、第2項がKL divergence lossです。以後それぞれ$L_{recon}$, $L_{kl}$と置くことにします。

### Reconstruction loss
ELBOの第1項である、

```math
L_{recon} = - \int q(z|x) \log \{ p(x|z) \} dz
```

は**Reconstruction loss**と呼ばれています。$L_{recon}$を最小化するよう学習することで、Encoder→Decoderを経た時に音声を正しく復元できるよう目指します。

VITSでは、潜在変数$z$が与えられている時の、音声$x$の分布が<a href="https://risalc.info/src/st-Laplace-distribution.html#def">ラプラス分布</a>に従うと仮定します。すなわち

```math
p(x|z) = A\exp(-|x - \hat{x}|)
```

とします。ただし$x$は入力音声、$\hat{x}$はDecoderから出力される音声、$A$は定数です。
これを用いて$L_{recon}$は以下のように変形できます。

```math
\begin{align}

L_{recon} &= - \int q(z|x) \log \{ p(x|z) \} dz \\
&= -E_{q(z|x)} [ \log \{ p(x|z) \} ] \\
&= -E_{q(z|x)} [ \log \{ A\exp(-|x - \hat{x}|) \} ] \\
&= E_{q(z|x)} [|x - \hat{x}|] + A \\

\end{align}
```

$E_{q(z|x)}$はバッチごとに平均をとる操作に相当します。また、定数$A$は学習時、勾配に影響を及ぼさないので無視できます。

以上で求めたReconstruction lossを以下の図のようにして実装に落とし込みます。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/28304e0e-2aaa-c7ab-1f3e-d2c7712ea479.png" width=85%>
</div>

$L_{recon}$を最小化するよう学習することで、Posterior Encoder→Decoderの過程で音声を正しく復元できるよう目指し学習します。


### KL divergence loss
ELBOの第2項である、

```math
L_{kl} = \int q(z|x) \log \{ \frac{q(z|x)}{p(z|c)} \} dz
```

はKLダイバージェンスの形をしているため**KL divergence loss**と呼ばれています。学習によってこれの最小化を目指します。
音声$x$を入力にとり潜在変数$z$を出力するモデル(Posterior Encoder)に相当する$q(z|x)$を、音素列$c$が与えられた上での$z$の分布$p(z|c)$にできるだけ近くなるようにし、音声と音素を関連付けているようなイメージです。

$L_{kl}$は以下のように変形できます。

```math
\begin{align}

L_{kl} &= \int q(z|x) \log \{ \frac{q(z|x)}{p(z|c)} \} dz \\
&= \int q(z|x) \{ \log \{ q(z|x) \} - \log \{p(z|c) \} \} dz \\
&= \int q(z|x) \{ \log \{ q(z|x) \} \} dz - \int q(z|x) \{ \log \{p(z|c) \} \} dz \\
&= E_{q(z|x)} [ \log \{ q(z|x) \} ] - E_{q(z|x)} [ \log \{p(z|c) \} ] \\

\end{align}
```

#### KL divergence loss 第1項
まず第1項

```math
E_{q(z|x)} [ \log \{ q(z|x) \} ]
```

について考えます。

$q(z|x)$はPosterior Encoderのことです。音声$x$を入力にとり、潜在変数$z$を出力します。

ここで、潜在変数$z$の各要素$z_{i}$は実装では以下のようにして確率的に生成します。

1. 各$\epsilon_{i}$をそれぞれ正規分布$N(0, I)$からサンプリング
2. 入力音声$x$をPosterior Encoderに入力し、パラメーター$\mu_{i}$と$\sigma_{i}$を得る
3. $z_{i} = \epsilon\sigma_{i} + \mu_{i}$を計算

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/2c2758fd-7cf3-9ed9-dca7-e1ea6849c2af.png" width=70%>
</div>

言い換えると$z$の各要素は平均$\mu_{i}$、分散$\sigma_{i}$の正規分布からサンプリングされた値だと考えることができます。
したがって、$x$を入力とし以上の手順を経て生成された平均値を$\mu_{pe}(x)$、分散を$\mu_{pe}(x)$と表記することで次のように表すことができます。

```math
z \sim q(z|x) = N(z;\mu_{pe}(x),\sigma_{pe}(x))
```

ただし$N(z;\mu_{pe}(x),\sigma_{pe}(x))$は平均$\mu_{pe}(x)$、分散$\sigma_{pe}(x)$の正規分布とします。$z \sim q(z|x)$は$z$を確率分布$q(z|x)$からサンプリングする操作です。

これよりKL divergence lossの第1項は定数Aを用いて

```math
\begin{align}

E_{q(z|x)} [ \log \{ q(z|x) \} ] &= E_{q(z|x)} [ \log \{ N(z;\mu_{pe}(x),\sigma_{pe}(x)) \} ] \\
&= E_{q(z|x)} [ \log \{ A(\sigma_{pe}(x))^{-1} exp \{ - \frac{(z - \mu_{pe}(x))^{2}}{2(\sigma_{pe}(x))^{2}} \} \} ] \\
&= E_{q(z|x)} [ \log \{ A \} + \log \{ (\sigma_{pe}(x))^{-1} \} + \log \{ exp \{ - \frac{(z - \mu_{pe}(x))^{2}}{2(\sigma_{pe}(x))^{2}} \} \} ] \\
&= A - E_{q(z|x)} [ \log \{ \sigma_{pe}(x) \} ] + E_{q(z|x)} [ - \frac{(z - \mu_{pe}(x))^{2}}{2(\sigma_{pe}(x))^{2}} ] \\
&= A - E_{q(z|x)} [ \log \{ \sigma_{pe}(x) \} ] - E_{q(z|x)} [ \frac{z^2 - 2z\mu_{pe}(x) + (\mu_{pe}(x))^{2}}{2(\sigma_{pe}(x))^{2}} ] \\
&= A - E_{q(z|x)} [ \log \{ \sigma_{pe}(x) \} ] - \frac{ E_{q(z|x)} [z^2] - 2E_{q(z|x)} [z] \mu_{pe}(x) + (\mu_{pe}(x))^{2}}{2(\sigma_{pe}(x))^{2}} \\
&= A - E_{q(z|x)} [ \log \{ \sigma_{pe}(x) \} ] - \frac{ ((\sigma_{pe}(x))^{2} + \mu_{pe}(x))^{2}) - 2 (\mu_{pe}(x))^{2} + (\mu_{pe}(x))^{2}}{2(\sigma_{pe}(x))^{2}} \\
&= A - E_{q(z|x)} [ \log \{ \sigma_{pe}(x) \} ] - \frac{1}{2} \\

\end{align}
```

となります。

#### KL divergence loss 第2項
次に第2項

```math
E_{q(z|x)} [ \log \{p(z|c) \} ]
```

について考えます。

$p(z|c)$は音素$c$が与えられた上での、潜在変数$z$の分布です。VITSでは$z$が正規分布$N=(z;\mu_{te}(x)$,$\sigma_{te}(x))$に従うと仮定して学習します。(ただし$\mu_{te}(x)$と$\sigma_{te}(x)$は音素列の情報を基に、Projectionから出力されるパラメーターとする)

```math
p(z|c) = N(z;\mu_{te}(x),\sigma_{te}(x))
```

また、一度$z$をFlow$f_{\theta}$に入力して$f_{\theta}(z)$を得て、これを用いて最適化を行うことでより自然な音声の生成が可能となるようです。

```math
p(z|c) = N(f_{\theta}(z);\mu_{te}(x),\sigma_{te}(x)) \left\vert  \frac{\partial f_{\theta}(z)}{\partial z} \right\vert
```

ここで、Flow$f_{\theta}$は体積保存な変換に制限します。これにより確率変数の変数変換に必要な、ヤコビアンを次のように固定できます。

```math
\left\vert \frac{\partial f_{\theta}(z)}{\partial z} \right\vert = 1
```

以上を用いてKL divergence lossの第2項は定数Aを用いて次のように変形できます。

```math
\begin{align}

E_{q(z|x)} [ \log \{p(z|c) \} ] &= E_{q(z|x)} [ \log \{N(f_{\theta}(z);\mu_{te}(x),\sigma_{te}(x)) \left\vert \frac{\partial f_{\theta}(z)}{\partial z} \right\vert \} ] \\
&= E_{q(z|x)} [ \log \{ A(\sigma_{te}(x))^{-1} exp \{ - \frac{(f_{\theta}(z) - \mu_{te}(x))^{2}}{2(\sigma_{te}(x))^{2}} \} \} ] \\
&= E_{q(z|x)} [ \log \{ A\} + \log \{ (\sigma_{te}(x))^{-1} \} + \log \{ exp \{ - \frac{(f_{\theta}(z) - \mu_{te}(x))^{2}}{2(\sigma_{te}(x))^{2}} \} \} ] \\
&= A + E_{q(z|x)} [ - \log \{ \sigma_{te}(x) \} - \frac{(f_{\theta}(z) - \mu_{te}(x))^{2}}{2(\sigma_{te}(x))^{2}} ] \\

\end{align}
```

#### KL divergence lossの全体像

以上よりKL divergence lossである$L_{kl}$は以下のように求められます。

```math
\begin{align}

L_{kl} &= E_{q(z|x)} [ \log \{ q(z|x) \} ] - E_{q(z|x)} [ \log \{p(z|c) \} ] \\
&= (- E_{q(z|x)} [ \log \{ \sigma_{pe}(x) \} ] - \frac{1}{2}) - (E_{q(z|x)} [ - \log \{ \sigma_{te}(x) \} - \frac{(f_{\theta}(z) - \mu_{te}(x))^{2}}{2(\sigma_{te}(x))^{2}} ]) \\
&= E_{q(z|x)} [ \log \{ \sigma_{te}(x) \} - \log \{ \sigma_{pe}(x) \} - \frac{1}{2} + \frac{(f_{\theta}(z) - \mu_{te}(x))^{2}}{2(\sigma_{te}(x))^{2}} ]

\end{align}
```

(KL divergence lossの$\frac{1}{2}$は定数として無視してしまっても良さそうに思えましたが、<a href="https://github.com/jaywalnut310/vits/blob/main/losses.py">公式実装ではlossにこの項が含まれていた</a>ため、それに倣ってこのような記述としています。)

## GANによるloss

### Adversarial loss

Adversarial lossは下のように定義される、DecoderとDiscriminatorが互いに鍛え合うために必要なlossです。

```math
L_{adv}(D) = E[(D(y)-1)^2 + (D(G(z)))^2] \\
L_{adv}(G) = E[(D(G(z))-1)^2]
```

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/0c6189a8-8cb2-27e7-2c80-4dcc493b66fb.png" width=70%>
</div>

Discriminatorは$L_{adv}(D)$を最小化するよう目指すことで、本物らしい音声ほど1に近い値を出力し、Decoderによって生成された偽物の音声らしいほど0に近い値を出力できるよう学習します。Decoderに騙されないよう精度をあげるよう学習を進めます。

一方でDecoderは$L_{adv}(G)$を最小化するよう目指し、生成した音声でDiscriminatorを本物だと騙せるよう学習します。

### Feature matching loss  

Feature matching lossは、本物音声と偽物音声をそれぞれDiscriminatorへ入力とした場合における、中間層の出力の統計量を、両者の分布間で近づけるための損失関数です。
この損失関数の最小化を目標にすることによって学習の安定化を図り、生成音声の質の向上を狙います。

$$L_{fm} = E[ \sum_{l=1}^{T} \frac{1}{N_{l}} |D^{l}(y) - D^{l}(G(z))|_1 ]$$

ただし$T$はDiscriminatorを構成する層の数とします。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/f16f8b5a-b8d4-698c-60e3-20960bc56e97.png" width=95%>
</div>

Decoderは、生成した偽物音声をDiscriminatorに入力した場合における中間層の出力を、本物音声を入力した場合のものに近づくよう学習します。

自分的な解釈ですが、恐らくDecoderがDiscriminatorの全体としての出力だけでなく、中間層1つ1つをも欺くことができるようにしているイメージだと思われます。

## Duration loss

```math
\begin{align}

L_{dur} = E_{q_{\phi}(u,v|d,c)}[&\frac{1}{2}(\log (2\pi) + \epsilon_{D}^{2} )\\ -& \frac{1}{2}(\log (2\pi) + \epsilon_{Q}^{2} )\\ -& \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert\\ -& \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\

\end{align}
```

Stochastic Duration Predictorの目的は、入力された音素の情報$c$(図では$h_{text}$に相当)からDuration(その音素がどの程度の間発音されていたか)$d$を推定することです。また、$d$の推定時には確率的な揺らぎを与えることによってより自然な音声の生成を目指します。

<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584592/4040e424-32e9-681b-7074-dd25c4b4084e.png" width=100%>
</div>

Stochastic Duration Predictorは、$c$(音素の情報)が分かっている時の$d$(Duration)の対数尤度

```math
\begin{align}

\log p(d|c)

\end{align}
```

をできるだけ最大化したいです。ELBOを求めた時と同様にして、$\log p(d|c)$の変分下限は以下のように求められます。

```math
\begin{align}

\log p(d|c) \geq E_{q_{\phi}(u,v|d,c)}[\log\frac{p_{\theta}(d-u,v|c)}{q_{\phi}(u,v|d,c)}]

\end{align}
```

$E_{q_{\phi}(u,v|d,c)}$はミニバッチ内の平均を取る操作に相当します。

VITSではこの変分下限(上の式の右辺)を最大化するよう目指すことで学習します。実装上は変分下限の最大化を、負の変分下限の最小化と言い換えます。

```math
\begin{align}

L_{dur} &= -E_{q_{\phi}(u,v|d,c)}[\log\frac{p_{\theta}(d-u,v|c)}{q_{\phi}(u,v|d,c)}]

\end{align}
```

ここで、上のネットワーク図より$\epsilon_{D}=g_{\theta}(d-u,v;c)$、$(u,v)=g_{posterior}(\epsilon_{Q};d,c)$です。従って確率変数の変数変換を用いて次のようにできます。

```math
\begin{align}

L_{dur} &= -E_{q_{\phi}(u,v|d,c)}[\log\frac{p_{\theta}(d-u,v|c)}{q_{\phi}(u,v|d,c)}] \\
&= -E_{q_{\phi}(u,v|d,c)}[\log\frac{p(\epsilon_{D}) \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert}{p(\epsilon_{Q}) \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert ^{-1}}] \\
&= E_{q_{\phi}(u,v|d,c)}[-\log p(\epsilon_{D}) + \log p(\epsilon_{Q}) - \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert - \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\

\end{align}
```

ここで、$\epsilon_{D}$は標準正規分布に従うよう学習したいため$p(\epsilon_{D})=N(\epsilon_{D};0,I)$とします。また、$\epsilon_{Q}$は標準正規分布からサンプリングされた値です。
これらより次のように変形できます。

```math
\begin{align}

L_{dur} &= E_{q_{\phi}(u,v|d,c)}[-\log p(\epsilon_{D}) + \log p(\epsilon_{Q}) - \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert - \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\
&= E_{q_{\phi}(u,v|d,c)}[-\log N(\epsilon_{D};0,I) + \log N(\epsilon_{Q};0,I) - \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert - \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\
&= E_{q_{\phi}(u,v|d,c)}[-\log (\frac{1}{\sqrt{2\pi}}exp\{-\frac{\epsilon_{D}}{2}\}) + \log (\frac{1}{\sqrt{2\pi}}exp\{-\frac{\epsilon_{Q}}{2}\}) - \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert - \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\
&= E_{q_{\phi}(u,v|d,c)}[\frac{1}{2}(\log (2\pi) + \epsilon_{D}^{2} ) - \frac{1}{2}(\log (2\pi) + \epsilon_{Q}^{2} ) - \log \left\vert \frac{\partial g_{\theta}}{\partial (d-u,v)}  \right\vert - \log \left\vert \frac{\partial g_{posterior}}{\partial \epsilon_{Q}}  \right\vert] \\

\end{align}
```

以上が実装するDuration lossとなります。

## 損失関数の全体像
以上で紹介した$L_{rec}, L_{kl}, L_{adv}(D), L_{adv}(G), L_{fm}, L_{dur}$を用いて、損失関数$L$は全体として以下のように表せます。

$$L = L_{rec} + L_{kl} + L_{adv}(D) + L_{adv}(G) + L_{fm} + L_{dur}$$

$L$を最小化するよう目指し学習します。

# 生成結果

以下はiteration数=1200000回学習した際のモデルによる生成結果です。

個人的には音声変換に関してはかなりうまくいっているように聞こえました。
一方で読み上げに関してはイントネーションにやや難があり、さらなるデータセットの増量、ファインチューニングなどが必要そうに思われました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">機械学習の一手法「VITS」でアニメ声(つくよみちゃん)へ変換できるボイスチェンジャーを実装しました。<a href="https://t.co/LX0TV13uAD">https://t.co/LX0TV13uAD</a> <a href="https://t.co/vVWcDbUSpn">pic.twitter.com/vVWcDbUSpn</a></p>&mdash; zassou (@zassouEX) <a href="https://twitter.com/zassouEX/status/1497358678965760000?ref_src=twsrc%5Etfw">February 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">読み上げを行うこともできます。<br>(こちらに関してはもう少しファインチューニングが必要そうです。) <a href="https://t.co/xKPhTL7A0E">pic.twitter.com/xKPhTL7A0E</a></p>&mdash; zassou (@zassouEX) <a href="https://twitter.com/zassouEX/status/1497359192860291072?ref_src=twsrc%5Etfw">February 25, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# まとめ
VITSでボイスチェンジャーと読み上げ器を作成しました。
本記事の内容に関しては万全を期して作成しておりますが、間違った点などがありましたらご指摘いただけると非常に助かります。

# ソースコード
実装したコードはこのリポジトリにあります。
<a href="https://github.com/zassou65535/VITS">https://github.com/zassou65535/VITS</a>
公開しているコードは簡単のため、JVSコーパスにのみの対応としています。

# 参考
[1]<a href="https://arxiv.org/abs/2106.06103">Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech</a>
[2]<a href="https://github.com/jaywalnut310/vits">VITS: Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech</a>
[3]<a href="https://qiita.com/kenmatsu4/items/b029d697e9995d93aa24">Variational Autoencoder徹底解説</a>
[4]<a href="https://qiita.com/shionhonda/items/e2cf9fe93ae1034dd771">深層生成モデルを巡る旅(2): VAE</a>
[5]<a href="https://qiita.com/shionhonda/items/0fb7f91a150dff604cc5">深層生成モデルを巡る旅(1): Flowベース生成モデル</a>
[6]<a href="https://arxiv.org/abs/2005.11129">Glow-TTS: A Generative Flow for Text-to-Speech via Monotonic Alignment Search</a>
[7]<a href="https://ahcweb01.naist.jp/papers/journal/2019/201908_IEICE_seitaro-s_1/201908_IEICE_seitaro-s_1.paper.pdf">自然言語の指示による画像操作システム</a>
[8]<a href="https://fast-d.hmcom.co.jp/blog/melspectrum-mfcc/">音響特徴量「メルスペクトル」と「MFCC（メル周波数ケプストラム係数）」の解説と実例紹介</a>
[9]<a href="https://shuyo.hatenablog.com/entry/20110617/bayes">ベイズの公式は地味に難しいので、確率の乗法公式を2回使おう</a>
[10]<a href="https://qiita.com/ceptree/items/9a473b5163d5655420e8">正規分布間のKLダイバージェンス</a>
[11]<a href="https://arxiv.org/abs/1505.05770">Variational Inference with Normalizing Flows</a>
[12]<a href="https://nkoda-studynote.com/statistics/variable_conversion_exsample/">【統計検定１級対策】確率変数の変数変換の具体例</a>
[13]<a href="https://risalc.info/src/st-Laplace-distribution.html">ラプラス分布の期待値と分散の求め方</a>
[14]<a href="https://arxiv.org/abs/1606.03498v1">Improved Techniques for Training GANs</a>
[15]<a href="https://arxiv.org/abs/2002.09741">VFlow: More Expressive Generative Flows with Variational Data Augmentation</a>
[16]<a href="https://arxiv.org/abs/1902.00275">Flow++: Improving Flow-Based Generative Models with Variational Dequantization and Architecture Design</a>
[17]<a href="https://docs.google.com/presentation/d/19GNmEBcshf71ED5zRh8I8oIL5uAE0d-tJL1mlnR_4cw/edit#slide=id.ge40509bbd4_1_462">VITS Slides</a>