Python
機械学習
Chainer
DiscoGAN
声質変換

キズナアイとねこますの声を入れ替える機械学習をした

最近バーチャルユーチュ-バーが人気ですよね。自分もこの流れに乗って何か作りたいと思い、開発をしました。
モーションキャプチャー等を使って見た目を変えるのは かなり普及しているっぽいので、自分は声を変えられるようにしようと開発しました。

やったこと

キズナアイさんとねこますさんの、それぞれの声を入れ替えられるようにしました。これによって、ねこますさんのしゃべった内容を、キズナアイさんの声でしゃべらせることができます。(逆も)
機械学習手法の一つであるDiscoGANを用いて、変換するためのネットワークを学習しました。
パラレルデータ(話者Aと話者Bが、同時に同じ内容を話した音声)が必要ありません。YouTubeから拾った音声でも変換ができます。
当然ですが、一度学習すれば、利用時には何度でも繰り返し利用できます。

期待できる効果

見た目だけでなく、声まで美少女になれます。やったね。

他にも映画の吹き替えが、本物の役者と同じ声でできるようになったりします。しかも、パラレルデータが必要ないので、頑張って英語を録音する必要はありません。
アニメなんかも声優の声を利用させて貰えば、一人で全てのキャラクターの声を入れられるようになると考えられます。もちろん声優の声ではなく、ボイスロイドなんかの声を変換対象にすることもできると思うので、ギャランティーを押さえることもできる……かもしれません。

結果

結果を先に示します。
ソースコードはこちらで公開しています。
音声はYouTubeから落としたものです。この音声は学習に使用していません。

※2018/05/17更新
変換結果はYouTubeにアップロードしました。
こちらからどうぞ。

音声の出所はこちら。
【生放送】みんなでアニメ語りしてみた!
LINE公式スタンプ発売なのじゃー!【021】

ややノイズ混じりですが、かなり本人らしい声になっているのではないでしょうか?

環境

機械学習にはChainerを利用しました。
音声の読込・出力にはscipyを、フーリエ変換にはnumpyのfftを利用しました。
学習に使用した音声は
【LIVE】150万人達成目前だったあの日を振り返ろう!【ありがとう】
やっぱりトッポってすごいのじゃ…【022】
カメラに向かってありがとう【010】
です。ねこますさんの音声は短いものが多く、二つの音声を繋げて利用しています。
音声はフリーソフトを使って、音質が16kHzかつ音量が89dBのwaveファイルに変換しました。

原理

パラレルデータ

既存手法の多くはパラレルデータというものを使います。これは、AさんとBさんが同時に同じ内容をしゃべった音声を指します。パラレルデータを使い、Aさんの言ったこの部分は、Bさんのこれに該当するよね、という風に変換や学習を行うらしいです。
しかしながら、キズナアイさんとねこますさんが同じ内容を同じタイミングで話してくれるというのは、本人に依頼しない限りほぼ無理でしょう。なので、自分で気軽に音声を入手できない場合には、かなり厳しいものがあると思います。

DiscoGAN

機械学習やGANについては、分かりやすくてしっかりした記事がたくさんあるので、それを参考にしてください。ここではDiscoGANの概要とパラレルデータが不要であることについて、簡単に述べます。
なおDiscoGANの実装にはこちらの記事を参考にさせていただきました。

DiscoGAN(Discover Cross-Domain Relations with Generative Adversarial Networks)は、二つのデータセットの間の関係を学習し、変換するネットワークを学習してくれるGANの一種です。
ネットワークはGa・Gb・Da・Dbの4つがあります。GaはデータセットAのデータをデータセットBのデータに変換します。GbはデータBをデータAに変換します。Daは、データセットAのデータと、データセットBがGbによってデータAに似せられたデータを識別します。Dbも同様です。
このとき、[ データA -Ga→ 偽データB -Gb→ 偽データA ]という変換をします。そして、変換されたデータAと偽データAが等しくなるように(元に戻るように)学習し、かつ、偽データBがDbを騙せるように要求します。
Da・DbはGa・Gbが似せたデータを、識別できるように学習します。
スクリーンショット (19).png
GはDに識別されないように、変換したデータを本物に近づけようと頑張ります。逆にDはGが似せてきたデータを判定できるように頑張ります。DとGが互いに競い合うことで、Gの生成するデータはどんどん本物らしくなります。
また、Ga→Gbによって元に戻るように要求されるので、GはデータセットAとデータセットBの間の関係を探して、対応する変換を学習してくれます。そのためデータのペアは必要ありません

同じようなものにCycleGANって言うのがありますが、あれは……何が違うんでしょうね?
なお、CycleGANのほうがDiscoGANよりも記事が多いです。DiscoGANを調べてよく分からなかったら、CycleGANも調べてみてください。

学習データの用意

学習する対象は、スペクトログラムです。スペクトログラムは、短時間フーリエ変換を画像のように横に並べたもので、時間による周波数の変化を表します。
この処理が難しく試行錯誤したので、少し詳しく書きます。

変換したい音声を用意します。音声は対象者の声だけが含まれていることが好ましいと思います。適当なエンコーダーで音声を16kHzにします。

音声から16510点を取り出します。16kHzサンプリング音声で1秒程度です。
Figure_1.png
ここからさらに254点ずつに分割します。このとき16510÷254=65と分割するのではなく、重なり合う部分ができるように、128点ずつずらしながら128個に分割します。(128×128+(254-128)=16510)
スクリーンショット (17).png
分割した254点に窓関数のhanning窓を掛けて、フーリエ変換します。
spl.png
切り出した254点
splw.png
hanning窓を掛けた後。窓関数を掛けることで頭と尻が0になり、周期関数と見なせる。
fft.pngffti.png
フーリエ変換後の実部と虚部
フーリエ変換することにより、情報は実部と虚部に分かれます。一見すると情報量が二倍になったように思いますが、フーリエ変換は真ん中から複素共役の関係にあり左右対称となります。254点にすると126点が共役により無駄な部分となり、必要部分は128点となります。なので0~127番目までを切り出して使用します。

得られたスペクトルの絶対値をとり、さらに0.2乗し18で割ります。
fft_scale.png
本来は0.2乗ではなく対数をとるらしいですが、対数の場合は値が負になる時や無限小になる時があり、よろしくなさそうなので0.2乗を行っています。その後、18で割ることで数値を0と1の間に納めるようにします。
この18というのは計算して出した値ではなく、上記の処理を音声からランダムに10000回ぐらいやって、大体こんなもんだろと定めました。

これを128個並べることで、128×128画像のスペクトログラムを得ます。これが入力用のデータです。
img.png

音声への復元

学習に使用したデータはフーリエ変換の絶対値をとっているので、そのまま逆フーリエ変換することはできません。そのため位相推定という処理を行います。
位相推定は、絶対値処理によって失われた位相情報を近似で求める処理です。近似なので正確には復元されませんが、聞いた分には違和感が湧かないと思います。位相推定はGriffin/Lim Algorithmというものを用いました。(これが検索してもなかなか出てこなかった……)

Griffin/Lim Algorithmは、「フーリエ変換として辻褄の合う位相を求める」という処理です。こちらのpdfが非常に参考になりました。
簡単に書くと次のようになります。

スペクトル絶対値Aを適当な位相Nで初期化しXを作る。 ( X = A * N )
① x = IFT(X)
② X = FT(x)
③ X = A * X / |X|
①~③を適当な数(50~100回?)だけ繰り返す。

このとき、FFTは学習データの用意と同じ要領でやりますが、IFFTはオーバーラップアッドという処理をしなければなりません。音声信号に詳しい人はよくご存知と思いますが、自分はフーリエ変換と逆フーリエ変換ぐらいしか教わらなかったので、自分と同じような人のために書いておきます。詳しくは先ほどのリンクを見てほしいのですが、ざっくりと表すと下の画像のような処理です。
スクリーンショット (16).png
逆フーリエ変換したものを足し合わせるだけです。窓関数ですぼんだ形になった波形を、重なるように足し合わせることで復元します。データ用意のところで、重なるように切り取ったのはこのためです。

やり方を見て分かるように、これはかなり時間を食う処理です。そのため、Fast Griffin/Lim Algorithmというのがあります。詳しくはこちらを見てください。
このやり方を簡単に書くと次のようになります。

スペクトル絶対値Aを適当な位相Nで初期化しXを作る。 ( X = A * N )
① T = X       (Xのコピーを保存する)
② x = IFT(X)
③ X = FT(x)
④ X = A * X / |X|
⑤ X = X + α(X - T)
①~⑤を適当な数(50~100回?)だけ繰り返す。

処理項目が二つ増えましたが、論文によると少ない繰返し回数で良い音になるとか。
αは1付近の適当な数値を使います。ざっと見た感じ、0.9とか0.99が良い?

パラメータ云々

ネットワークについては、こちらからソースコードを見てください。

多分やった方が良いこと

学習係数

学習係数はかなり小さくした方が良いと思いました。
学習係数が大きいと、音割れしたり、イントネーションがおかしいままだったりしました。
いろいろ試しましたが、RMSpropを利用してlr=1e-5が良いのではないかと思います。

WGAN

こちらを参考にして、weight decay版のWGANを導入しています。さらにWeightDecayも入れました。
こうしてDに制限を入れることで、Dの圧勝を防いでいます。Dが圧勝すると、生成される音はノイズが乗ったり、音量が低下したり、元音声のままだったりと不安定になります。

コードを見直したところ、このときに使っていたのはWGANではなく、重み上限を設けたDを使っていただけでした。
WGANおよびWGAN-gpを使って学習させてみましたが、半分以上元の声・音量が安定しない・突発的に音割れと、かなり酷い結果になりました。

feature matching

プロジェクト初期の段階でfeature matchingが無いとほとんど成功しないと思い、以降はずっと入れています。
Dの判定由来のlossとfeature matching由来のlossは0.1:0.9で学習しています。Dの判定由来のlossを大きくすると音割れが発生するなど、不安定になりました。

今後の展開

しばらくは、学習に最適なパラメータの検証や音質の改善などをメインに活動していく予定です。特に音質に関しては、追加で音質向上用のネットワークを通すことで向上できるのではないかと考えています。

また、現在はPythonで実装していますが、Pythonという言語は処理速度が遅いです。そのためリアルタイムで使用するのは困難ではないかと考えています(位相推定など)。他言語への移植を含め、リアルタイムでの変換ができるようにしたいと考えています。
また、マイクからの入力やリアルタイム再生ができるように、関連する部分のプログラムも制作したいと考えています。

他にも、勘のいい人は気がついたかと思います。
今回のプロジェクトは、DiscoGANを用いることでパラレルデータ無しで一対一の変換ができます。しかしこれを使うことで、パラレルデータを作ることができるわけです。
これによって一人の声を多数の人と一対一変換し、多数のパラレルデータを製作することで、多対一の変換もできるようになると考えています。
こちらに関しては、時間があればやってみたいと考えています。

最後に

このプロジェクトは、まだまだ発展途上の段階です。それでも公開したのは、PCの性能に限界を感じたためです。
自分が学習に使っているGPUはGTX1060一枚です。これはちょっとした縁で借り受けて使わせていただいています。しかしGTX1060一枚だけというのは、計算能力として力不足感が否めません。なにか思いついたり、パラメータをいじったりして学習し直すと、結果が出るまでに一晩待たねばいけません。

GTX1080tiとか買えよ、と思う方も居るかと思います。
可能ならそうした高性能なGPUを利用したいのですが、学生の自分には資金を捻出するのが難しい状況です。

この記事を読んでいる皆さんにお願いです。
今回の制作物を面白いと思っていただけたら、少額でも援助していただけないでしょうか。
大変見苦しい行為だとは思いますが、自分の今後の活動の為に何卒よろしくお願いします。

BitCoin
3GsNN7zPFMqCV7PnoDKv4sxpUsWXEYDQGt

MONACOIN
M8Y12bhuNcPVCpDjPLLa1CVxm6vQyLFRhm

※2018/05/27追記
GTX1080を譲っていただけました!
また、仮想通貨でのご支援は電源に利用させていただきました。
皆さんのご支援、本当にありがとうございます!