音声認識メモ(DeepSpeech)その3
トレーニングによるパラメータ更新を試してみる。
トレーニングデータは、下記で公開されているATR音素バランス503文の発話データを使うことにする。
https://ja.osdn.net/projects/galateatalk/releases/22207
日本語コーパスを使った音素認識については下記のサイトを参考にさせていただいた。
Connectionist Temporal Classification (CTC) を用いた音素認識 - Qiita
公開データに含まれる音素ラベルは「42」種類。
{'n', 'gy', 'I', 'dy', 'w', 'ch', 's', 'g', 'sh', 'N', 'z', 'ry', 'p', 'E', 'py', 'e', 'u', 'O', 'y', 'my', 'ky', 'i', 'ts', 'f', 'b', 'A', 'k', 'ny', 'm', 'ch', 'r', 'pau', 'by', 'U', 'j', 'o', 'h', 'hy', 'sil', 't', 'a', 'd'}
参考サイトでは、促音(「っ」)を’ch’から'Q'に置き換えている。
DeepSpeechのモデルは前回見たように、「Dense」+「LSTM」+「Dense」の構造になる。
また、上記のQiita記事も「Dense」+「Bi-directional LSTM」+「Dense」になっている。
音声を音素レベルまで分解せず、"音のまとまり"として認識するならCNN(畳み込み)という選択肢もある(異常音の検知などはこちらになると思われる)
TensorFlowメモ(Simple Audio Recognition) - ichou1のブログ
今回は下記で公開されているモデルを参考に組み立ててみることにする。
GitHub - robmsmt/KerasDeepSpeech: A Keras CTC implementation of Baidu's DeepSpeech for model experimentation
モデル構造は6層。
def create_base_model(n_mfcc_bin, n_class, dropout=[0.1, 0.1, 0.1], fc_size=2048, n_lstm_unit=512): input_data = Input(name='input', shape=(None, n_mfcc_bin)) # --> ([batch_size, time_step, n_mfcc_bin]) init = tf.random_normal_initializer(stddev=0.046875) ### Layer 1 x = TimeDistributed(Dense(units=fc_size, kernel_initializer=init, bias_initializer=init, activation=clipped_relu), name='dense_1')(input_data) x = TimeDistributed(Dropout(dropout[0]), name='drop_1')(x) # --> ([batch_size, time_step, fc_size]) ### Layer 2 x = TimeDistributed(Dense(units=fc_size, kernel_initializer=init, bias_initializer=init, activation=clipped_relu), name='dense_2')(x) x = TimeDistributed(Dropout(dropout[0]), name='drop_2')(x) # --> ([batch_size, time_step, fc_size]) ### Layer 3 x = TimeDistributed(Dense(units=fc_size, kernel_initializer=init, bias_initializer=init, activation=clipped_relu), name='dense_3')(x) x = TimeDistributed(Dropout(dropout[0]), name='drop_3')(x) # --> ([batch_size, time_step, fc_size]) ### Layer 4 BiDirectional RNN x = Bidirectional(LSTM(units=n_lstm_unit, return_sequences=True, dropout=dropout[1], kernel_initializer='he_normal'), merge_mode='sum', name='Bi_LSTM')(x) # --> ([batch_size, time_step, n_lstm_unit]) ### Layer 5 # x = TimeDistributed(Dense(fc_size, # activation=clipped_relu, # kernel_initializer=init, # bias_initializer=init), # name='dense_3')(x) x = TimeDistributed(Dropout(dropout[2]), name='drop_5')(x) # --> ([batch_size, time_step, n_lstm_unit]) ### Layer 6 Softmax y_pred = TimeDistributed(Dense(units=n_class, kernel_initializer=init, bias_initializer=init, activation="softmax"), name="softmax")(x) # --> ([batch_size, time_step, n_class]) model = tf.keras.Model(inputs=input_data, outputs=y_pred) return model
レイヤ5のDenseは効果がないのか、コメントアウトされている。
インプットとなる特徴量の次元(n_mfcc_bin)と、アウトプットのclass数(n_class)を指定してモデルを定義する。
# n_symbol = 42(音素の種類) base_model = create_base_model((n_mfcc_bin=26, n_class=n_symbol+1))
- 「n_class」の指定で、プラス1としているのは、"いずれの音素でもない記号"(pseudo-charactor)の分。
- 「26次元」となっているのは、MFCC13次元にdelta特徴量を加えた分。
mfcc = librosa.feature.mfcc(wav, sr=16000, n_mfcc=13, ...) mfcc_d = librosa.feature.delta(mfcc)
モデルサマリを出力。
base_model.summary()
モデルサマリ出力結果
Layer (type) Output Shape Param # ================================================================= input (InputLayer) [(None, None, 26)] 0 _________________________________________________________________ dense_1 (TimeDistributed) (None, None, 2048) 55296 _________________________________________________________________ drop_1 (TimeDistributed) (None, None, 2048) 0 _________________________________________________________________ dense_2 (TimeDistributed) (None, None, 2048) 4196352 _________________________________________________________________ drop_2 (TimeDistributed) (None, None, 2048) 0 _________________________________________________________________ dense_3 (TimeDistributed) (None, None, 2048) 4196352 _________________________________________________________________ drop_3 (TimeDistributed) (None, None, 2048) 0 _________________________________________________________________ Bi_LSTM (Bidirectional) (None, None, 512) 10489856 _________________________________________________________________ drop_5 (TimeDistributed) (None, None, 512) 0 _________________________________________________________________ softmax (TimeDistributed) (None, None, 43) 22059 ================================================================= Total params: 18,959,915 Trainable params: 18,959,915 Non-trainable params: 0
トレーニングにおける損失関数はCTC(Connectionist Temporal Classification)を使う。
入力(MFCC)と出力(音素ラベル)のシーケンス長が異なる場合に適用でき、TensorFlow APIで、「tf.keras.backend.ctc_batch_cost」が公開されている。
(Kerasドキュメントの方が分かりやすいと思われる)
「tf.keras.backend.ctc_batch_cost」を呼び出す自前の関数を作って、先程のモデル(base_model)の後ろに付け加える。
model = add_ctc_loss(base_model)
モデルサマリの出力結果
Layer (type) Output Shape Param # Connected to ========================================================================== input (InputLayer) [(None, None, 26)] 0 __________________________________________________________________________ dense_1 (TimeDistributed) (None, None, 2048) 55296 input[0][0] __________________________________________________________________________ (途中省略) __________________________________________________________________________ softmax (TimeDistributed) (None, None, 43) 22059 drop_5[0][0] __________________________________________________________________________ real_y_true (InputLayer) [(None, 123)] 0 __________________________________________________________________________ pred_length (InputLayer) [(None, 1)] 0 __________________________________________________________________________ true_length (InputLayer) [(None, 1)] 0 __________________________________________________________________________ my_ctc (Lambda) (None, 1) 0 softmax[0][0] real_y_true[0][0] pred_length[0][0] true_length[0][0] ========================================================================== Total params: 18,959,915 Trainable params: 18,959,915 Non-trainable params: 0
「123」というのは、正解となる音素ラベルの最大長
300エポックほど学習する。
最終的な"loss"は「0.17」ほど。
結果から先に書くと、テストデータに対する認識結果は下記のとおり。
テストデータ1
ちいさなうなぎやに、ねっきのようなものがみなぎる。
['_', 'sil', 'sil', 'sil', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'ch', 'ch', 'ch', 'ch', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 's', 's', 's', 's', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'a', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'n', 'n', 'n', '_', '_', '_', '_', '_', '_', '_', 'a', 'a', 'a', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'u', 'u', 'u', 'u', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'n', 'n', '_', '_', '_', '_', '_', '_', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'g', 'g', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'i', 'i', 'i', 'i', 'i', '_', '_', '_', '_', '_', '_', '_', '_', 'a', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'n', 'n', 'n', 'n', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'i', 'i', 'i', 'i', 'i', '_', ... 'sil', 'sil', 'sil', 'sil', 'sil', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'f', 'f', 'f', 'f', 'f']
長いので、"_"を除外して出力する。
結果。
['sil', 'sil', 'sil', 'ch', 'ch', 'ch', 'ch', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 's', 's', 's', 's', 'a', 'a', 'a', 'a', 'a', 'n', 'n', 'n', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'u', 'u', 'u', 'u', 'n', 'n', 'a', 'a', 'a', 'a', 'g', 'g', 'i', 'i', 'i', 'i', 'i', 'a', 'a', 'a', 'a', 'a', 'n', 'n', 'n', 'n', 'i', 'i', 'i', 'i', 'i', 'pau', 'pau', 'pau', 'pau', 'n', 'n', 'n', 'e', 'e', 'e', 'Q', 'Q', 'Q', 'Q', 'k', 'k', 'i', 'i', 'i', 'i', 'n', 'n', 'n', 'n', 'o', 'o', 'o', 'y', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'n', 'n', 'n', 'a', 'a', 'a', 'a', 'm', 'm', 'm', 'o', 'o', 'o', 'o', 'n', 'n', 'n', 'o', 'o', 'o', 'o', 'o', 'g', 'g', 'g', 'a', 'a', 'a', 'a', 'm', 'm', 'm', 'm', 'i', 'i', 'i', 'n', 'n', 'n', 'a', 'a', 'a', 'a', 'g', 'g', 'i', 'i', 'i', 'i', 'r', 'r', 'r', 'u', 'u', 'u', 'sil', 'sil', 'sil', ... 'sil', 'sil', 'sil', 'sil', 'sil', 'f', 'f', 'f', 'f', 'f']
=> "うなぎやに"のところで"y"の音素が欠落、また、最後の方に余分な音素"f"が入ってしまっているが、概ねOK。
テストデータ2
どろぼうでもはいったかと、いっしゅんぼくはおもった。
['sil', 'sil', 'sil', 'sil', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'd', 'd', '_', '_', '_', '_', 'o', 'o', 'o', 'o', 'o', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'r', 'r', 'r', 'r', 'o', 'o', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'b', 'b', 'o', 'o', 'o', 'o', 'o', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'o', 'o', 'o', 'o', 'o', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'd', 'd', 'e', 'e', 'e', 'e', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'm', 'm', 'm', 'o', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'h', 'h', 'h', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'a', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', 'i', 'i', 'i', 'i', '_', '_', '_', '_', '_', 'Q', 'Q', 'Q', 'Q', 'Q', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 't', 't', 't', '_', '_', '_', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'k', 'k', 'k', '_', '_', '_', '_', '_', '_', '_', 'a', 'a', 'a', 'a', 'a', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', 't', 't', 't', '_', '_', 'o', 'o', 'o', '_', '_', '_', ... 'sil', 'sil', 'sil', '_', '_', '_', '_', '_', '_', '_', '_', '_', 'sil', 'sil', 'sil', 'sil', 'f', 'f', 'f', 'f', 'u']
"_"を除外した出力結果。
['sil', 'sil', 'sil', 'sil', 'd', 'd', 'o', 'o', 'o', 'o', 'o', 'r', 'r', 'r', 'r', 'o', 'o', 'b', 'b', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'd', 'd', 'e', 'e', 'e', 'e', 'm', 'm', 'm', 'o', 'h', 'h', 'h', 'a', 'a', 'a', 'a', 'a', 'i', 'i', 'i', 'i', 'Q', 'Q', 'Q', 'Q', 'Q', 't', 't', 't', 'a', 'a', 'a', 'a', 'k', 'k', 'k', 'a', 'a', 'a', 'a', 'a', 't', 't', 't', 'o', 'o', 'o', 'pau', 'pau', 'pau', 'pau', 'i', 'i', 'i', 'i', 'sh', 'sh', 'sh', 'sh', 'sh', 'u', 'u', 'u', 'N', 'N', 'N', 'N', 'N', 'N', 'b', 'b', 'o', 'o', 'o', 'k', 'k', 'k', 'u', 'u', 'u', 'u', 'w', 'w', 'w', 'a', 'a', 'a', 'a', 'h', 'h', 'o', 'o', 'o', 'o', 'o', 'm', 'm', 'm', 'o', 'o', 'o', 'o', 'Q', 'Q', 'Q', 'Q', 't', 't', 't', 'a', 'a', 'a', 'a', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', 'sil', ... 'sil', 'sil', 'sil', 'f', 'f', 'f', 'f', 'u']
=>"ぼくはおもった"のところで余分な"h"の音素が入っている、また、最後の方に余分な音素"f"と"u"が入ってしまっているが、概ねOK。