1.4K Views
December 28, 23
スライド概要
Pythonで学ぶ音声認識の輪読会第10回の発表スライドです。
2023年12月21日(木) 18:30~
AI・機械学習を勉強したい学生たちが集まる、京都大学の自主ゼミサークルです。私たちのサークルに興味のある方はX(Twitter)をご覧ください!
音声認識 7章 4節 CTCの実装 京都大学工学部物理工学科2回生 平塚謙良 0
CTC(Connectionist Temporal Classification)の復習 ・RNNを用いて,直接出力テキストを学習する →学習にHMM状態ラベルが要らない ・前向き-後ろ向きアルゴリズムの導入 →誤差逆伝播法が適用可能に 前向き・後ろ向きアルゴリズム https://tech.zealscott.com/cmu-dl/L16%20Connectionist%20Temporal%20Classification.html ・ブランクの導入 →曖昧な区間への無理なラベリングを防ぐ →同一トークンが連続するテキストを認識 ブランクの導入 https://ratsgo.github.io/speechbook/docs/neuralam/ctc 1
CTCの実装 2
CTCの実装 my_model.pyとencoder.pyに分けて実装 my_model.py encoder.py ● CTCのモデル構造全体を定義 ● エンコーダを実装 ● エンコーダの作成 ● 射影層(線形層)による次元削減 ● 出力層の作成 ● サブサンプリング(間引き) ● LeCunの初期化(initialize.pyに実装) class MyCTCModel(nn.Module): def __init__(…): super(MyCTCModel, self).__init__() self.encoder = Encoder(…) # エンコーダーを作成 self.out = nn.Linear(…) # 出力層を作成 lecun_initialization(self) # LeCunの初期化を実行 # ネットワーク計算の関数 def forward(self,input_sequence,input_lengths): enc_out, enc_lengths = self.encoder(input_sequence, input_lengths) output = self.out(enc_out) return output, enc_lengths 3
CTCの実装(encoder.py) class Encoder(nn.Module): def __init__(略): # RNN層の定義 rnn = [] for n in range(self.num_layers): # rnn_type がGRUならGRUを,それ以外ならLSTMを用いる if rnn_type == 'GRU': rnn.append(nn.GRU(…)) else: rnn.append(nn.LSTM(…)) self.rnn = nn.ModuleList(rnn) # 標準のリスト型からModuleListに変換する # 射影層の定義 proj = [] for n in range(self.num_layers): proj.append(nn.Linear(…)) self.proj = nn.ModuleList(proj) # RNN層と同様,ModuleListに変換する def forward(self, sequence, lengths): for n in range(self.num_layers): output, (h, c) = self.rnn[n](rnn_input) # RNN層に入力する # sub sampling (間引き)の実行 if sub > 1: output = output[:, ::sub] # フレーム数を更新する output_lengths = (output_lengths+1) // sub output = self.proj[n](output) # Projection層に入力する return output, output_lengths ※左のコードは色々省いているので正 確ではない RNNはGRU, LSTMのどちらを使う か選べるようになっており,どちらも Pytorchに実装されたものを使ってい る。 射影層は入力次元数の半分の出力次 元数を持つ。 for文を用いてRNN層と射影層を交 互に実行する。 事前に設定しておいた数値に従いフ レームを間引く。 4
全体の実装 5
全体の実装 01_get_token.py ニューラルネットワークで扱うために,トークンを番号へ変換 ● 02_train_ctc.py my_model.pyに実装したCTCを学習 ● 03_decode_ctc.py 学習済みモデルを用いて評価データをデコーディング ● 04_scoring.py レーベンシュタイン距離によって認識結果を評価 ● 6
全体の実装(02_train_ctc.py) model = MyCTCModel(…) optimizer = optim.Adadelta(model.parameters(),…) # 訓練/開発データのデータセットを作成する train_dataset = SequenceDataset(…) # 訓練データのDataLoaderを呼び出す train_loader = DataLoader(train_dataset,…) # CTC損失関数を呼び出す.blankは0番目と定義する. criterion = nn.CTCLoss(blank=0, reduction='sum’) # 勾配をリセット optimizer.zero_grad() # モデルの出力を計算(フォワード処理) outputs, out_lens = model(features, feat_lens) # CTC損失関数を使う場合は,log_softmaxを通す必要がある outputs = F.log_softmax(outputs, dim=2) # 損失値を計算する. loss = criterion(outputs.transpose(0, 1),…) # 勾配を計算する loss.backward() # Cliping Gradient により勾配が閾値以下になるよう調整する torch.nn.utils.clip_grad_norm_(model.parameters(),…) # オプティマイザにより,パラメータを更新する optimizer.step() 準備部分 MyCTCModelはmy_model.pyに実装した物。 optimizerにはAdadeltaを,損失関数には Pytorchのtorch.nn.CTCLossを用いる。 学習部分 対数事後確率を計算するために torch.nn.functional.log_softmax()を使っ ている。 また,torch.nn.utils.clip_grad_norm_()に よりGradient Clippingを行っている。 これにより勾配爆発を防ぐことができる。 7
全体の実装(03_decode_ctc.py) def ctc_simple_decode(int_vector, token_list): output = [] # 出力文字列 prev_token = -1 # 一つ前フレームの文字番号 for n in int_vector: if n != prev_token: # 1.前フレームと同じトークンではない if n != 0: # 2. かつ,blank(番号=0)ではない output.append(token_list[n]) # 前フレームのトークンを更新 prev_token = n return output # モデルの出力を計算(フォワード処理) outputs, out_lens = model(features, feat_lens) for n in range(outputs.size(0)): # バッチ内の1発話ごとに処理 # 要素番号を取得する idx = torch.nonzero(indices==n, as_tuple=False).view(-1)[0] # 各フレームのmax値をたどる Best path decoding を行う _, hyp_per_frame = torch.max(outputs[idx], 1) # numpy.array型に変換 hyp_per_frame = hyp_per_frame.cpu().numpy() # 認識結果の文字列を取得 hypothesis = ctc_simple_decode(hyp_per_frame, token_list) CTC出力をトークン列に変換 ブランクを考慮して変換を行う。 認識部分 torch.max()でフレーム毎に最も確率の高い トークンを取り出している。 先ほど定義したctc_simple_decode()によ り連続する同一トークンとブランクを削除 して認識結果を出力する。 8
全体の実装(レーベンシュタイン距離) 認識誤りを「置換誤り」,「削除誤り」,「挿入誤り」の三つに 分類してカウントする。このカウントの合計をレーベンシュタイ ン距離という。 文字誤り率は以下のように定義される。 置換誤り数+削除誤り数+挿入誤り数 文字誤り率 = 正解ラベルの文字数 置換誤り https://mieruca-ai.com/ai/levenshtein_jaro-winkler_distance/ 削除誤り 挿入誤り 9
学習結果 10
学習結果 RNNのアルゴリズムはGRUとLSTM,データセットはsmall(1000) とlarge(4500)で比較。 Small GRU Large 学習時間(分):1.5 学習時間(分):8.5 文字誤り率(%):7.31 文字誤り率(%):3.34 LSTM 学習時間(分):2.5 学習時間(分):10 文字誤り率(%):8.13 文字誤り率(%):3.60 Small_GRU 損失 Small_LSTM 損失 GRUを用いてLargeサイズ のデータセットで学習した モデルが最も高い性能を示 した。 Large_GRU 損失 Large_LSTM 損失 11
学習結果 最も性能のよかったモデルの音声認識結果を見ると,そこそこ精 度よく認識できている。 ID: BASIC5000_0041 #ERROR (#SUB #DEL #INS): 0 (0 0 0) REF: し が い せ ん わ ひ ふ が ん を ひ き お こ す こ と が あ る HYP: し が い せ ん わ ひ ふ が ん を ひ き お こ す こ と が あ る ID: BASIC5000_0042 #ERROR (#SUB #DEL #INS): 0 (0 0 0) REF: き が つ く と に げ ば わ ど こ に も な か っ た HYP: き が つ く と に げ ば わ ど こ に も な か っ た ID: BASIC5000_0043 #ERROR (#SUB #DEL #INS): 2 (1 1 0) REF: さ ん ば ん め の そ し て も っ と も じ ゅ ー よ ー な か ん が え わ さ い に ゅ ー と ゆーことである HYP: さ ん ば ん め の そ し て も っ と も じ ゅ ー よ ー な か ん が え わ さ い に ゅ ー と よことである ↑ 置換誤りと削除誤りを1つずつしている 12
まとめ まとめ1 まとめ2 まとめ3 CTCを用いることでEnd-to-Endの音声認識モデルが作れる レーベンシュタイン距離により文字誤り率を計算できる LSTMと比較してGRUの方が学習時間は短く,認識精度も同等もしくはそれより高 い 13
14