しばらく前に、「"工藤"を誤魔化すための単語」みたいなツイートをしたら、予想外に伸びた。
うっかり「工藤」って言っちゃったときに誤魔化すのに使える言葉を調べました pic.twitter.com/wtZQaS4Etr
— やました (@yaa_maa_520) 2017年11月12日
服部の「工藤の人」としての知名度ってすごいんやな。うれしい。
いろいろ指摘をもらったんだけど、その中でも「Juliusで提供されている音響モデル(たくさんの人の声を統計的に学習したもの)で音素間の距離を定義できるとPanPhonよりもよくなるかもしれない」っていうアドバイスをめっちゃ試したいと思ったので、やってみることにした。知らないことばかりだったので、やったことをメモする。
Juliusの音響モデル
dictation kitに入ってるhmmdefsって拡張子のやつが、モデルの定義ファイル。monophone用とtriphone用がある。
monophone用: "m", "n"みたいな音素一つ一つに対してモデルを定義したやつ
triphone用: "a-m+a", "a-n+a"みたいに3つ並んでる音素に対してモデルを定義したやつ
たぶんtriphoneの方がちゃんと音素環境を考慮してるからいいと思うし、アドバイスしてくれた方もtriphone勧めてくださってたけど、とりあえずわかりやすそうなmonophoneを使った。
HMM
「音響モデル」って聞いて、何となく音声ファイルみたいなのを想像してたんだけど、めちゃくちゃ見当外れだった。実際の音響モデルはHidden Markov Model (HMM)として定義されていた。HMMっていうのは、stateがある確率で変わっていって、変わるごとにある確率に基づいた何かを出力するってやつらしい。
音響モデルは後戻りしないので、HMMの中でもleft-to-rightのものとして分類される。確かに遷移確率見たら、リピートするか次に進むかの二択しかなかった。
hmmdefsの構造は、HTK Bookの7章読んだら何となくわかった。一つの音素に対して、
~h "音素" <BEGINHMM> <NUMSTATES> stateの数 <STATE> 2 <NUMMIXES> mixtureの数 <MIXTURE> 1 mixture1の重み <MEAN> meanの数 meanが並んでる <VARIANCE> varianceの数 varianceが並んでる <GCONST> <MIXTURE> 2 mixture2の重み (略) <STATE> 3 (略) <STATE> 4 (略) <TRANSP> stateの数 stateの数×stateの数の行列 <ENDHMM>
って感じで定義されている。stateは5個、mixtureは16個、meanやvarianceは25個だった。
HMM同士の距離
どうやってHMM間の距離測るんやろって思っていろいろ読んだけど、mixtureを含んでいるためそんな単純に計算できない(たぶん)。Music Similarity Measuresによると、片方のHMMから出したサンプルがもう片方のHMMから出力される確率がどれくらいかを測って、それを二つのHMMの近さと見なしたらいいっぽい。どれくらいばらつきがあるかわからんけど、一つの組み合わせにつき1000回やって、その平均を取ることにした。
そのためには、HMMからサンプルを出す機能と、尤度を計算する機能が必要。hmmlearnがそういうことをやってくれるので、そのhmm.GMMHMMっていうモデルを使った。
GMM→GaussianMixture
GMMHMMのプロパティのgmms_
には、scikit-learnのGMMっていうオブジェクトが入っている。それがもう古いらしくて、GMMの代わりに新しいGaussianMixtureを使えってwarningが出る。
hmmlearnのissueで話し合ってくれてはいるけど、途中で終わってる。なので自分で勝手にhmm.py内のGMMを全部GaussianMixtureに書き換えてしまった。それに伴って、sample()の引数とかscore()の戻り値の形式など変更があったところに対応できるようにちょっと直した。
GaussianMixtureのパラメータの設定
個々のMixtureの分のパラメータ(weights_
とかmeans_
)をGMMHMMから設定する方法がわからんかったので、gmms_
に入ってるGaussianMixtureオブジェクトに対して直接_set_parameters()
してしまった。そのときにprecisions_choleskyというものが欲しいみたいなんだけど、それが何なのか理解すらできなかったので、GaussianMixture._compute_precision_cholesky()
を使って出してもらった。
調節とか
left-to-rightのモデルだから、最後のstateから行くとこがないので、遷移確率の行列の最後の行は全て0になる。でも、遷移確率の行ごとの合計が全て1にならないとscore()でエラーが出る。どうしたらいいかわからんけど、とりあえずリピートするところを1にした。
遷移確率や分散に0があるとエラーになるので、めちゃくちゃ小さい値に置き換えた。
普通のHMMは延々と続くので、sample()するときには何回繰り返すかを指定するんだけど、音響モデルはleft-to-rightだから最後のstateに辿り着いたら止めるようにしてほしい。なのでちょっと処理を書き換えた。
NotFittedError
パラメータをセットして、やっとsample()できる!と思ったら、NotFittedErrorとかいうものが出た。fitした後じゃないとsample出してくれへんらしい。
↑初めにちっちゃいデータセットでっち上げてfitさせてからパラメータをセットし、その後sample()したらいいって回答がある(ちっちゃいって言っても、サンプルの数 >= Mixtureの数じゃないと無理だけど)。だから、適当にfitする→ちゃんとしたパラメータをセットする→sample()するっていう流れになった。
GMMHMMとGaussianMixtureでの引数名が指すもの
なんかcomponentとか微妙に意味が違うからややこしい。
GMMHMM | GaussianMixture | |
---|---|---|
the number of states | n_components | |
the number of mixtures | n_mix | n_components |
the number of features | n_features |
それぞれの音素がHMMとして定義されてて、そのHMMの中にstateがいくつかあって、それぞれのstateの出力確率がいくつかのmixtureから成ってて、それぞれのmixtureにfeatureが決められている。
log likelihoodはsampleの長さに影響される?
log likelihoodの行列(左がサンプルを出したもとのHMM、上がそのサンプルを出す尤度を測ったHMM)を可視化すると、以下のようになった。
かろうじて対角線は見えてるけど、それより行ごとの違いが大きすぎる。左のHMMから取ったサンプルの長さの影響が大きいのかなと思って、x軸をサンプルの長さ、y軸をscore (= log likelihood)としたグラフを描いてみた。
すごいあからさまに影響されてる。濃い青色のところは、行列では対角線上に当たる部分、つまり同じ音素同士を比べたときのスコア。これがだいたい横一直線になってもらわないといけないと思う。いろいろめちゃくちゃな方法で値を操作した結果、次のような行列になった。
さっきに比べたらわりと納得できる感じになったと思う(適当)。
距離の可視化
ツイートに書いたのと同じ方法で、音素間の距離を可視化した。
雑に分類してみると、
何となく同じような属性を持つ音素同士で固まってるようなので満足!
あの意味わからん数字の羅列だったHMM定義ファイルの中身が、処理してみたらちゃんと人間の知覚に沿った感じになってるの、何かすごいな。
もうちょっとやりたいことがあった気がするけど、期末テストやってるうちに自分が何してたのか忘れてしまったので、終わる。