人工知能に関する断創録

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

統計的声質変換 (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の声質変換モデルを学習するところをやってみたい。

おまけ

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

統計的声質変換 (3) メルケプストラムの抽出

統計的声質変換 (2) ボイスチェンジャーを作ろう(2015/2/25)の続き。

前回は音声から抽出したメルケプストラムやピッチのパラメータを直接いじることで簡単なボイスチェンジャーを作った。今回からAさんの音声をBさんの音声に変換する規則を機械学習の手法で学習する声質変換の実験をしていきたい。今回は学習データとなる音声データをダウンロードするところとメルケプストラムを抽出するところまで。

音声データのダウンロード

チュートリアルと同じようにCMU ARCTIC Databasesという公開データを使う。このデータは7名の英語話者が同じ文章を朗読した音声がついている。今回作る統計的声質変換の手法は、変換元のAさんと変換先のBさんが同じ文章を読んだ音声が必要になるのでちょうどよいデータ。英語なのが残念。日本語の音声は探したけどなかった。あとで自分の声でやってみたい。

一括ダウンロードするスクリプトは、download_cmu_arctic.shをお借りした。curlというコマンドがなかったのでインストール。Windowsの場合はCygwinについている。

ダウンロードするとFestvox用のデータがいろいろついているけど今回必要なのは各話者のwavだけ。そのためwav以外は削除して下のようなディレクトリ構造にした。

% ls wav
awb/     bdl/     clb/     jmk/     ksp/     rms/     slt/

% ls wav/clb/
arctic_a0001.wav     arctic_a0002.wav ...

awbなどは話者IDみたい。各話者1132文を収録しているようだ。フォーマットは16kHz、16bit、monoだった。

こんな声

clb

slt

bdl

rms

今回の実験ではチュートリアルと元論文にならって

  • 女性のclbさんの声をsltさんにするモデル
  • 男性のbdlさんの声をrmsさんにするモデル

の二種類を実験していきたい。今回の実験ではメルケプストラムしか変換しないので男声を女声にするのは難しいと思われる。男声と女声ではピッチ(声の高さ)がだいぶ違うので。たぶん、ピッチも変換モデルを学習すればよいと思うのであとで試してみたい。

メルケプストラムの抽出

まずは音声データからメルケプストラムを抽出する。チュートリアルではWORLDを使っているけど今回はSPTKを使った。SPTKは今までもけっこう活用していたので。SPTKを使ったメルケプストラムの抽出は下の記事を参照のこと。まあ前回のボイスチェンジャーを作ろうでも使ってたけど。

音声データからメルケプストラムを一括抽出するスクリプト。

python extract_mcep.py wav/clb mcep/clb
python extract_mcep.py wav/slt mcep/slt
python extract_mcep.py wav/bdl mcep/bdl
python extract_mcep.py wav/rms mcep/rms

のようなコマンドを実行するとmcepディレクトリの下にメルケプストラムが保存される。あとでnumpyを使って簡単にデータを読み込めるようにテキスト形式で保存している。ファイルを開くと1行に1フレーム分のメルケプストラム係数(26次元データ)がずらずら並ぶファイル形式になっている。SPTKのmcepコマンドの-mオプションに指定するメルケプストラム次数は25次だけど0次元目にパワーの値が入るので実際は26次元のベクトルになる。ここがずれているのに気がつかなくてはまった・・・

メルケプストラムの描画

clbさんとsltさんのメルケプストラムを比較してみたい。メルケプストラムは26次元の時系列データなので全部表示するとわかりにくくなる。というわけで0次(order=0)、1次(order=1)、2次(order=2)の値をそれぞれ描画してみよう。横軸が時間(フレーム)になる。

f:id:aidiary:20150304211440p:plain f:id:aidiary:20150304211457p:plain f:id:aidiary:20150304211503p:plain

同じ文章(音韻)をしゃべっているため概形は似ているが、話者によって細部の形が異なることがわかる。この違いが個人の声質の違いを表すそうだ。あとしゃべる速さが違うこともあり、山のフレーム位置がずれていることがわかる。次回に紹介するDTWというアルゴリズムを使うとこのずれを補正できる。