人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

統計的声質変換 (6) 声質変換モデルの学習

統計的声質変換 (5) scikit-learnのGMMの使い方(2015/3/22)の続き。

今回は、いよいよ声質変換モデルを混合ガウスモデル(GMM)で学習しよう!第3回目(2015/3/4)で述べたようにclbさんの声をsltさんの声に変換することを前提に進める。

今まで「声質変換モデルをGMMで学習する」と書いてきたが、参考にしていたチュートリアルを読んでも何をGMMで表すのかがいまいちピンとこなかった。そこで、元論文(PDF)を当たったところ式 (6) を見てようやく理解できた。

f:id:aidiary:20150330230321p:plain

この式から z_t がGMMでモデル化されていることがわかる。そして、この z_t は、変換元話者の t フレーム目の特徴量 x_t と変換先話者の t フレーム目の特徴量 y_t結合ベクトル

f:id:aidiary:20150330230323p:plain

である。今回は26次元のメルケプストラム特徴量を使うため z_t は2人のメルケプストラム特徴量を結合した52次元ベクトルになる。つまり、学習するのは52次元空間のGMMになる。第4回目(2015/3/10)において時間同期をとったパラレルデータを作成したのは同じ時間位置tで結合したベクトルを作りたかったから。さらにパラレルデータなら学習データのフレーム数が二人の話者で完全に一致する。

学習データはclbさんとsltさんのarctic_a0001.mcepからarctic_a0050.mcepまでの50文、計35926フレーム分のデータを用いた。GMMの混合数は適当に32とした。この学習データの尤度がもっとも高くなるようにGMMのパラメータ(混合係数、平均ベクトル、共分散行列)を推定するのがGMMによる学習である。

f:id:aidiary:20150330230325p:plain

では、さっそくプログラムを書いてみよう。

実行コマンドは、

python train_gmm.py aligned_mcep_clb_slt/clb aligned_mcep_clb_slt/slt clb_slt.gmm

第4回目(2015/3/10)でパラレルデータを出力したディレクトリaligned_mcep_clb_sltにclbさんとsltさんの間のパラレルデータがあると仮定している。

学習したモデルはあとで使いまわせるようにsklearnのjoblibモジュールを使ってファイルにダンプしている。このモデルファイルは次回使おう。

実行すると2つのグラフが表示される。1つ目のグラフは、最初の3つのガウス分布の平均ベクトルをプロットしたグラフである。

f:id:aidiary:20150330230312p:plain

このグラフを見ると26次元目で何やら区切りが見える。これは結合ベクトルの平均ベクトルに

f:id:aidiary:20150330230330p:plain

という関係があるため。2つ目のグラフは、0番目の正規分布の共分散行列を可視化したグラフである。

f:id:aidiary:20150330230316p:plain

このグラフを見ると4つの区間に分割できそうであるが、これは結合ベクトルの共分散行列に

f:id:aidiary:20150330230328p:plain

という関係があるためである。

ようやく声質変換モデルが学習できたので次回は結合ベクトルのGMMを使って実際に声質変換してみたい。実は次回が一番難しい。

今回は、初めて手書きの図を入れてみた。最近、iPadで使えるタッチペンを買ったので手書きでノートを取っている。これからもちょくちょく入れてみようかな。

統計的声質変換 (5) scikit-learnのGMMの使い方

統計的声質変換 (4) パラレルデータの作成(2015/3/10)の続き。

今回は、いよいよ声質変換モデルを混合ガウスモデルで学習しよう!と思ったのだけれど、その前にscikit-learnGMMライブラリの使い方を簡単にまとめることにした。本格的に使う前に簡単なデータで使い方を確認しておこうというのが趣旨。scikit-learnは最近使い始めたので使ったことがない機能がまだたくさんある。

混合ガウスモデル(GMM)

GMMは、データxの生成される確率を複数のガウス分布の重み付き和で表すモデルである。

\displaystyle p(x) = \sum_{k=1}^K \pi_k N(x|\mu_k, \Sigma_k)

ここで、Kは使用するガウス分布の個数、\pi_kはk番目のガウス分布の重み(混合係数)、\mu_kは、k番目のガウス分布の平均ベクトル、\Sigma_kは、k番目のガウス分布の共分散行列。混合係数はすべてのkについて足し合わせると1になる。

GMMの学習は、データセットXを用いて、尤度がもっとも高くなる混合係数と平均ベクトルと共分散行列を推定することを指す。GMMでは、混合係数と平均ベクトルと共分散行列がパラメータに当たる。一方、ガウス分布の個数Kは自分で決め打ちする必要がある(これも推定する方法あるのかな?)。

で、その最尤推定は、GMMの場合、解析的に解けないのでEMアルゴリズムという繰り返し最適化手法を使いましょうという流れになる。以前、PRMLの勉強(2010/8/29)をしていたときに混合ガウスモデルの学習をEMアルゴリズムで解く記事を書いたことがあった。

今回は、自分で書いたアルゴリズムではなく、scikit-learnの混合ガウスモデルを学習するライブラリを使って同じ結果になるか確認してみたい。

PRMLのサンプルデータ

今回は、PRMLでも使われていたfaithful.txtのデータを使って確認しよう。まず、可視化する。

f:id:aidiary:20150322211032p:plain

散布図を書いてみるとデータは2つの山にわかれていることがわかる。ガウス分布が2つあればうまくモデル化できそうなのでK=2としてGMMを学習する。

scikit-learnの大部分のアルゴリズムでは、訓練データ X_train は行方向にサンプル、列方向に特徴量が並んだ行列形式で表される。n_componentsはガウス分布の個数Kのこと。次に、gmmオブジェクトを生成し、fit()に学習データを与えるだけでパラメータ推定できる。推定したパラメータは、gmmオブジェクトに格納されている。混合係数はweights_、平均ベクトルはmeans_、共分散行列はcovars_に格納される。

*** weights
[ 0.64402184 0.35597816]

*** means
[[ 0.70404975 0.66866654]
[-1.27373939 -1.20972547]]

*** covars
[[[ 0.13173538 0.06060868]
 [ 0.06060868 0.19652982]]

[[ 0.05445178 0.02829092]
 [ 0.02829092 0.18407749]]]

出力されたグラフはこれ。

f:id:aidiary:20150322211029p:plain

平均ベクトルと共分散行列はデータに重なり、きちんと推定できていることがわかる。前に自分で実装した結果とも同じになった。やったね!

次回は、いよいよパラレルデータを使って声質変換モデルをGMMで学習してみよう。

統計的声質変換 (4) パラレルデータの作成

統計的声質変換 (3) メルケプストラムの抽出(2015/3/4)の続き。

前回は変換元のclbさんと変換先のsltさんのメルケプストラムを一括抽出した。前回の最後の結果を見ると、二人のしゃべる速さが違うためメルケプストラムが時間方向にずれていることがわかった。たとえば、下の図は青色がclbさんのメルケプストラム系列、緑色がsltさんのメルケプストラム系列を表している。赤の矢印の場所で形状が似ているが位置がずれていることがわかる。

f:id:aidiary:20150310192348p:plain

このずれはメルケプストラム間の変換モデルを学習するときに問題になるため時間同期を取る。この時間同期を取ったデータをパラレルデータと呼ぶ。

DTW (Dynamic Time Warping: 動的時間伸縮法)

この二つの時系列データの時間同期を取るアルゴリズムにDTWというのがあるので使ってみる。DTWは、二つの時系列データがなるべく重なりあうように伸ばしたり、縮めたりして、二つの時系列の最小距離を求めるアルゴリズムである。二つの時系列の長さが異なっていても距離を求められるので大変便利。

DTWは、本来、最小距離を求める手法であるが、伸縮した二つの時系列のどことどこが対応するかを表す経路情報(path)も返してくれる。今回はこの経路情報を使ってアラインメントを取った時系列データを得ている。距離は使わない。DTWを求めるPythonライブラリdtwがあるので使ってみよう。pipで簡単にインストールできる。

pip install dtw

今回の問題は26次元メルケプストラムの2つの時系列なので、DTWで距離を求める際には26次元ベクトル同士の距離を最小化する必要がある。Pythonのdtwライブラリはここら辺もよく考えられていて行方向に時間、列方向に特徴量がある2つの行列を入れてもまったく問題なく動作する。デフォルトの距離尺度はL1ノルム(マンハッタン距離)である。

    # オリジナルのmcepを読み込み
    mcep1 = np.loadtxt(os.path.join(mcep_dir1, mcep_file))
    mcep2 = np.loadtxt(os.path.join(mcep_dir2, mcep_file))

    # DTWで同期を取る
    dist, cost, path = dtw(mcep1,mcep2)
    aligned_mcep1 = mcep1[path[0]]
    aligned_mcep2 = mcep2[path[1]]

pathには二つのメルケプストラム時系列においてDTWで対応のとれたインデックス列が入っているためこれをインデックスとして元の配列に当てはめればアラインメントされた二つの時系列が得られる。DTWを取ったあとのメルケプストラム系列は同じ長さになる。

パラレルデータの作成

時間同期のとれたパラレルデータを作成するスクリプトは下のような感じ。

元の時間同期がとれていない二つのメルケプストラムのディレクトリと時間同期を取ったあとのメルケプストラムのディレクトリを指定してコマンドを実行すればよい。

python align_mcep.py mcep/clb mcep/slt aligned_mcep_clb_slt/clb aligned_mcep_clb_slt/slt

いくつか可視化してずれていたのが本当にきっちりそろうか確かめよう。左側がDTWする前のメルケプストラム系列(0次、1次、2次の係数)、右側がDTWした後のメルケプストラム系列(0次、1次、2次の係数)である。

f:id:aidiary:20150310192351p:plain

まあ対応が取れなくて苦しい箇所(300フレームより後ろ)もあるけど山、谷の位置はそろっていることが確認できた。対応が取れない箇所は声質の個人差による違いなんだろうと理解している。もう一個別の例(チュートリアルと同じarctic_a0028

f:id:aidiary:20150310192355p:plain

ちゃんと対応が取れていることがわかる。

次回はいよいよこのパラレルデータを使って、GMMの声質変換モデルを学習するところをやってみたい。

おまけ

上のグラフは下のようなコードで描いた。