【Pythonで学ぶ音声認識】第5章:GMM-HMMによる音声認識(5.5節)

7.6K Views

November 08, 23

スライド概要

Pythonで学ぶ音声認識の輪読会第6回の発表スライドです。
2023年11月9日(木) 18:30~

profile-image

AI・機械学習を勉強したい学生たちが集まる、京都大学の自主ゼミサークルです。私たちのサークルに興味のある方はX(Twitter)をご覧ください!

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

後期輪読会#6 GMM-HMMを実装してみよう 京都大学 理学部 3回生 松田 拓巳 0

2.

大まかな流れ 目次 1. GMM-HMMの復習 2. 実装の目標を確認 3. GMM-HMMの学習部分の実装 4. 学習させたGMM-HMMで孤立単語音声認識 5. 音素アライメントの推定 1

3.

1. GMM-HMMの復習 GMM (Gaussian Mixture Model) ⚫ GMMは複数のガウス分布の重み付き和 ⚫ 音声特徴量 𝒙(𝑛) は条件付きGMMに従うと仮定 混合重み ガウス分布 (5.23) (5.24) 画像引用:https://cdn-xtrend.nikkei.com/atcl/contents/18/00076/00009/01.png 2

4.

1. GMM-HMMの復習 HMM (Hidden Markov Model) ⚫ 音素/状態の移り変わりをHMMで表現する ⚫ 同じ音素を発音しても、言い始め/終わり等で音声特徴が違う? → 各音素 𝑝 に対して状態 𝑗 = 0,1,2 ⋯ を定義 𝑝 → 状態遷移確率 𝑎𝑖,𝑗 がHMMの学習パラメータ 音素 𝑝,状態 𝑖 音素 𝑝,状態 𝑗 確率 𝑝 𝑎𝑖,𝑗 画像引用:https://cdn-ak.f.st-hatena.com/images/fotolife/h/hiro2o2/20160211/20160211184236.png http://cdn-ak.f.st-hatena.com/images/fotolife/h/hiro2o2/20160211/20160211195300.png 3

5.

2. 実装の目標を確認 4

6.

2. 実装の目標を確認 10単語の孤立単語音声認識 ⚫ 入力音声が「ひとつ」「ふたつ」…「ここのつ」「とお」のうち どの単語を発話しているのかを判定したい MFCC特徴量 学習させた GMM-HMM 尤度 𝑷(𝒙|𝒑, 𝜽) を最大化する 音素系列𝒑を選択 5

7.

3. GMM-HMMの学習部分の実装 6

8.

3. GMM-HMMの学習部分の実装 3.1. MonoPhoneHMMがどのように使われるかを見てみる① GMM-HMMの設定ファイル (=プロトタイプ)を作成 01_make_proto.py ⚫ 02_init_hmm.py ⚫ プロトタイプを読み込む ⚫ フラットスタート初期化 ⚫ 初期化したプロトタイプを保存 hmm = MonoPhoneHMM() hmm.make_proto(phone_list, num_states, prob_loop, num_dims) hmm.save_hmm(‘./exp/model_3state_1mix/hmm_proto’) hmm = MonoPhoneHMM() hmm.load_hmm(‘./exp/model_3state_1mix/hmm_proto’) hmm.flat_init(mean, var) hmm.save_hmm(‘./exp/model_3state_1mix/0.hmm’) ■EMアルゴリズムの初期パラメータ𝜃′の決め方|フラットスタート 全発話、全フレームにおける音声特徴量𝒙の平均/分散共分散行列で初期化する ※𝑥𝑑𝑢 (𝑛):発話𝑢の𝑛フレーム目の音声特徴量𝒙𝑢 (𝑛)の𝑑次元目の値 7

9.

3. GMM-HMMの学習部分の実装 3.1. MonoPhoneHMMがどのように使われるかを見てみる② 03_train_gmmhmm.py hmm = MonoPhoneHMM() hmm.load_hmm(‘./exp/model_3state_1mix/0.hmm’) for m in range(mixup_time+1): if m > 0: hmm.mixup() … for iter in range(num_iter): hmm.train(feat_list, label_list) ⚫ ⚫ ⚫ 初期化済のプロトタイプを 読み込む 1回目のループ(m=0)では 単峰(=SGM-HMM)で学習 2回目以降(m>0)では Mixupで峰を2倍にして学習 ■SGMからGMMへ|Mixup まずは単峰正規分布で学習してから、 山を2つに分割 → その山たちを初期値として再学習 8

10.

3. GMM-HMMの学習部分の実装 3.2. 学習データの準備 ⚫ 音素ラベルを番号に変換 • • ⚫ /n/a/n/a/ts/u → [0, 20, 2, 20, 2, 30, 31, 0] 「0」は無音 phone_to_int関数で実装 → 実行するとlabel_intで指定したフォルダに音素ラベルが保存される label_str = ‘../data/label/train_small/text_phone’ label_int = ‘./exp/data/train_small’ phone_list = [‘pau’,’N’,’a’,’b’,’by’,’ch’,...,’w’,’y’,’z’] insert_sil = True 00_make_label.py の簡略版 # ‘pau’+phones.txtの中身 phone_to_int(label_str, label_int, phone_list, insert_sil) 9

11.

3. GMM-HMMの学習部分の実装 3.3. __init__() ⚫ インスタンス変数を定義している def __init__(self): self.phones = [] self.num_phones = 1 self.num_states = 1 self.num_mixture = 1 self.num_dims = 1 self.pdf = None self.trans = None self.LZERO = -1E10 self.LSMALL = -0.5E10 self.ZERO = 1E-100 self.MINVAR = 1E-4 hmmfunc.py self.elem_prob = None self.state_prob = None self.alpha = None self.beta = None self.loglikelihood = 0 self.pdf_accumulators = None self.trans_accumulators = None self.score = None self.track = None self.viterbi_score = 0 ↑学習時/認識時に使うパラメータ 10

12.

3. GMM-HMMの学習部分の実装 3.4. make_proto(phone_list, num_states, prob_loop, num_dims) 処理の 概要 ⚫ GMM-HMMのパラメータ等を格納する配列 (プロトタイプ*) の定義 self.pdf, self.transに値を格納していく pdf[p][s][m]で 音素番号p, 状態番号s, 混合要素番号m の正規分布のパラメータにアクセス • weight (混合重み) • mu (平均値ベクトル) • var (分散共分散行列の対角成分*) • gConst (後述) trans[p][s]で 音素番号p, 状態番号s の遷移確率(loop or next)にアクセス • loop (自己ループ確率) • next (次の状態への遷移確率) *「プロトタイプ」の辞書的意味:原型。最初の、形にしたもの。それを土台にしてさまざまなパターンを生み出してゆくための、最初のもの。(Wikipediaより引用) 11

13.

復習:train()の実装の前に… 12

14.

3. GMM-HMMの学習部分の実装 𝒑 ෝ𝒊𝒋 ■復習|GMM-HMMのパラメータ更新式:遷移確率 𝒂 ⚫ 遷移確率は、前向き/後向き確率などを使うと次のように表せる。 式[5.62]より ⚫ 𝑢 𝑢 前向き確率𝛼𝑝,𝑖 𝑛 , 𝛽𝑝,𝑖 (𝑛)はすべて計算してある ⚫ 𝐿𝑢 = 𝛼𝑝𝑢𝑢 ⚫ 遷移確率𝑎𝑖,𝑗 は仮パラメータなのでわかっている ⚫ last 𝑢 𝑢 (𝑁 − 1) なので𝐿 もわかっている ,𝑆−1 ′𝑝 𝑃 𝒙𝑢 𝑛+1 𝑝 ′ 𝑠𝑗 , 𝜃 はGMMの仮パラメータから計算できる ↑音声特徴量は(状態sの条件のもとで)混合正規分布に従う、としていた 13

15.

3. GMM-HMMの学習部分の実装 ■復習|GMM-HMMのパラメータ更新式:混合重み ⚫ 𝒑,𝒋 ෝ𝒎 𝒘 混合重みは、前向き/後向き確率などを使うと次のように表せる。 式[5.68]より ⚫ 𝑢 𝑢 前向き確率𝛼𝑝,𝑖 𝑛 , 𝛽𝑝,𝑖 (𝑛)はすべて計算してある ⚫ 𝐿𝑢 = 𝛼𝑝𝑢𝑢 ⚫ 𝐿𝑢𝑝𝑗𝑚 last 𝑛 = 𝑢 𝑢 (𝑁 − 1) なので𝐿 もわかっている ,𝑆−1 1 𝑢 𝑈 𝐿𝑢 𝑝𝑗 𝑛 𝑃 𝒙𝑢 ↑ 前向き確率のようなもの 𝑛 , 𝑧𝑢 𝑛 =𝑚 𝑝 ′ 𝑠𝑗 , 𝜃 𝑢 𝛽𝑝,𝑗 (𝑛) もOK ↑ 混合ガウスのm番目の混合要素 (つまり純粋なガウス分布) 14

16.

3. GMM-HMMの学習部分の実装 ■復習|GMM-HMMのパラメータ更新式:平均 ⚫ 𝒑,𝒋 ෝ𝒎 𝝁 平均値ベクトルは、次のように表せる。 式[5.69]より ⚫ ⚫ 𝐿𝑢𝑝𝑗𝑚 𝑛 は前頁と同様にOK 𝒙𝑢 (𝑛)は音声特徴量であり、データとして与えられるのでOK 15

17.

3. GMM-HMMの学習部分の実装 𝒑,𝒋 ෡ ■復習|GMM-HMMのパラメータ更新式:分散 𝚺𝒎 ⚫ 分散共分散行列は、次のように表せる。 式[5.70]より ⚫ 𝐿𝑢𝑝𝑗𝑚 𝑛 , 𝒙𝑢 (𝑛)は前頁と同様にOK ⚫ ෝ 𝑚 は前頁の式で求まったパラメータを用いる 𝝁 𝑝,𝑗 ※計算量削減のため分散共分散行列は対角行列と仮定する。よって対角成分のみを計算すればよい: 式[5.89]より 16

18.

これらの式を踏まえて、train()を実装しよう 17

19.

3. GMM-HMMの学習部分の実装 3.6. train(feat_list, label_list) 処理の 概要 GMM-HMMのパラメータの更新を1回だけ行う calc_out_prob(feat, label) 1. • 𝑝 𝑝 𝑃 𝑥 𝑢 𝑛 , 𝑧 𝑢 𝑛 = 𝑚 𝑠𝑗 , 𝜃 ′ , 𝑃 𝑥 𝑢 𝑛 𝑠𝑗 , 𝜃 ′ を計算 calc_alpha(label), calc_beta(label) 2. • 𝑢 𝑢 𝛼𝑝,𝑖 𝑛 , 𝛽𝑝,𝑖 (𝑛)を計算 update_accumulators(feat, label) 3. • 𝑝 𝑝,𝑗 𝑝,𝑗 ෠ 𝑝,𝑗 ෝ 𝑚 , Σ𝑚 の分母分子を計算 𝑎ො𝑖,𝑗 , 𝑤 ෝ𝑚 , 𝝁 update_parameters() 4. • 𝑝 𝑝,𝑗 𝑝,𝑗 ෠ 𝑝,𝑗 ෝ 𝑚 , Σ𝑚 を計算し、パラメータ更新 𝑎ො𝑖,𝑗 , 𝑤 ෝ𝑚 , 𝝁 18

20.

3. GMM-HMMの学習部分の実装 3.7. calc_out_prob(feat, label) 処理の 概要 𝑝 𝑝 𝑃 𝑥 𝑢 𝑛 , 𝑧 𝑢 𝑛 = 𝑚 𝑠𝑗 , 𝜃 ′ , 𝑃 𝑥 𝑢 𝑛 𝑠𝑗 , 𝜃 ′ を計算 self.elem_prob self.state_prob 𝑚について重み付き 和を取ればよい ⚫ self.pdf[p][s][m]で正規分布のパラメータを取り出す • ⚫ weight, mu, var, gconst その確率密度関数に𝒙𝑢 (𝑛)を代入した値をself.elem_probに格納 • calc_pdfメソッドを使って計算(中身の解説は割愛) 19

21.

3. GMM-HMMの学習部分の実装 3.8. calc_alpha(label) 処理の 概要 𝑢 前向き確率 𝛼𝑝,𝑖 𝑛 を計算 self.alpha ⚫ self.alpha[l][s][t]に以下の計算結果を代入 • l:ラベル上の何番目の音素か,s:状態,t:フレーム番号 式[5.55]より は共通、残りの項は場合分けしたうえで足せばよい 20

22.

3. GMM-HMMの学習部分の実装 3.9. calc_beta(label) 処理の 概要 𝑢 後向き確率 𝛽𝑝,𝑖 𝑛 を計算 self.beta ⚫ self.beta[l][s][t]に以下の計算結果を代入 • l:ラベル上の何番目の音素か,s:状態,t:フレーム番号 式[5.58]より は共通、残りの項は場合分けしたうえで足せばよい 21

23.

3. GMM-HMMの学習部分の実装 3.10. update_accumlators(feat, label) 処理の 概要 ⚫ 𝑝,𝑗 𝑝,𝑗 ෠ 𝑝,𝑗 𝑝 ෝ 𝑚 , Σ𝑚 の分母分子を計算 パラメータ𝑎ො𝑖,𝑗 , 𝑤 ෝ𝑚 , 𝝁 まずは𝐿𝑢𝑝𝑗𝑚 𝑛 = 1 𝑢 𝑈 𝑝𝑗 𝐿𝑢 𝑝 𝑢 𝑛 𝑃 𝒙𝑢 𝑛 , 𝑧 𝑢 𝑛 = 𝑚 𝑠𝑗 , 𝜃 ′ 𝛽𝑝,𝑗 (𝑛)を計算 式[5.67]より ⚫ 復習スライドの式の分母分子をそれぞれ計算する 22

24.

3. GMM-HMMの学習部分の実装 3.11. update_parameters() 処理の 概要 accumlatorsの分母分子の値からパラメータを計算 23

25.

3. GMM-HMMの学習部分の実装 ■補足|対数確率を計算する理由 ⚫ プログラムでは、確率ではなくlog(確率)を計算している ⚫ 0≦確率≦1なので、かけ算すると値が非常に小さくなる可能性 → アンダーフローの問題 ■アンダーフロー|数値計算上の問題 • 計算機で扱える桁数には限界がある • float64(64bit浮動小数点数)ではせいぜい16桁くらいまでしか保持できない 小数点以下17桁目以降は間違っている 24

26.

3. GMM-HMMの学習部分の実装 ■補足|log(a+b)を計算する際の実装上のPoint ⚫ 𝑢 𝛼𝑝,𝑖 𝑛 などの計算では確率の足し算が出てくる → 対数確率 log(𝑎 + 𝑏) を計算するには? (𝑎, 𝑏の値はlog 𝑎 , log 𝑏の形でわかっている) log 𝑎 , log 𝑏の値から𝑎, 𝑏を求め、 log(𝑎 + 𝑏)を計算する 問題点 log 𝑎の値から𝑎を求めるときに、 アンダーフローするおそれがある log10 𝑎 = −20 → 𝑎 = 10−20 アンダーフロー発生 log(𝑎 + 𝑏)を log 𝑎 , log 𝑏 の式で表す 𝑏 log(𝑎 + 𝑏) = log 𝑎 1 + 𝑎 𝑏 = log 𝑎 + log 1 + 𝑎 𝑒 log 𝑏 = log 𝑎 + log 1 + log 𝑎 𝑒 25

27.

3. GMM-HMMの学習部分の実装 3.12. mixup() 処理の 概要 ⚫ ⚫ ⚫ GMMの混合要素数を2倍にする weightを0.5倍にする 新たな正規分布N(mu+0.2*std, var)を作る 元々の正規分布N(mu,var)は、N(mu-0.2*std, var)にしておく 26

28.

4. 学習させたGMM-HMMで孤立単語音声認識 27

29.

4. 学習させたGMM-HMMで孤立単語音声認識 4.1. MonoPhoneHMMでどのように認識を行うか見てみる 06_recognize.py hmm = MonoPhoneHMM() hmm.load_hmm(‘./exp/model_3state_1mix/10.hmm’) (result,detail) = hmm.recognize(feat, lexicon) 1 ./exp/data/test/mfcc/1.bin Result = 一つ [Ranking] 一つ -3424.831526 五つ -3495.737924 四つ -3572.460827 … output ⚫ recognizeで認識ができる ⚫ 1行目:発話ID・ファイルパス ⚫ 2行目:尤度最大となるラベル(result) ⚫ 3行目~:尤度高いランキング(detail) ■補足|変数lexiconの中身 [{‘word’: ’一つ’, ’pron’: [‘pau’,’h’,’i’,…,’u’,’pau’], ‘int’ : [0,12,14,…,31,0]}, {…] 28

30.

4. 学習させたGMM-HMMで孤立単語音声認識 4.2. recognize(feat, lexicon) 処理の 概要 単語リスト内の単語ごとに尤度を計算し、認識結果を返す calc_out_prob(feat, label) 1. • 𝑝 𝑝 𝑃 𝑥 𝑢 𝑛 , 𝑧 𝑢 𝑛 = 𝑚 𝑠𝑗 , 𝜃 ′ , 𝑃 𝑥 𝑢 𝑛 𝑠𝑗 , 𝜃 ′ を計算 viterbi_decoding(label) 2. • 対数尤度の近似値 log 𝑃(𝒙|𝑝, 𝜃) ≈ 𝛾𝑝last ,𝑆−1 (𝑁 − 1) を計算 式[5.77][5.78]より 29

31.

5. 音素アライメントの推定 30

32.

5. 音素アライメントの推定 5.1. MonoPhoneHMMでどのようにアライメントを行うか見てみる 07_phone_alignment.py hmm = MonoPhoneHMM() hmm.load_hmm(‘./exp/model_3state_1mix/10.hmm’) alignment = hmm.phone_alignment(feat, label) ⚫ phone_alignmentで 音素アライメントを推定できる ← このように フレームと音素の対応関係が分かる ■補足|返り値alignmentの中身 [‘pau’,’pau’,’pau’, … ,’n’,’n’, … ,’pau’,’pau’] 0フレーム目に対応する音素 N-1フレーム目に対応する音素 31

33.

5. 音素アライメントの推定 5.2. phone_alignment(feat, label) 処理の 概要 viterbiアルゴリズムの結果をバックトラックする calc_out_prob(feat, label) 1. • 𝑝 𝑝 𝑃 𝑥 𝑢 𝑛 , 𝑧 𝑢 𝑛 = 𝑚 𝑠𝑗 , 𝜃 ′ , 𝑃 𝑥 𝑢 𝑛 𝑠𝑗 , 𝜃 ′ を計算 viterbi_decoding(label) 2. • 対数尤度の近似値 log 𝑃(𝒙|𝑝, 𝜃) ≈ 𝛾𝑝last ,𝑆−1 (𝑁 − 1) を計算 back_track() 3. • viterbiパスのバックトラックを行う 32