ichou1のブログ

主に音声認識、時々、データ分析のことを書く

TensorFlowメモ(Simple Audio Recognition)

TensorFlowのチュートリアル「Simple Audio Recognition」を試してみる。

docs/audio_recognition.md at master · tensorflow/docs · GitHub

ソースコード

tensorflow/tensorflow/examples/speech_commands at master · tensorflow/tensorflow · GitHub

どのような処理か

Tensorflowのモデルとしては、数字認識「MNIST」と同じく「分類」(classify)問題を扱う。
クラス数は以下の「12」個。

  • silence
  • unknown word
  • "yes"
  • "no"
  • "up"
  • "down"
  • "left"
  • "right"
  • "on"
  • "off"
  • "stop"
  • "go"

"yes"から"go"までが識別したい単語。
[silence]は無音(音が無い状態)、[unknown word]は未知語を表す。

[unknown word](未知語)は、他の11個のいずれにも当てはまらない音声が該当する。

デフォルトの音声データセットを見てみると、"bird"だったり、"happy"といった音が入っていて、これらは[unknown word]としてクラス分けされる。

音声データセットはどんなもの?

https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.02.tar.gz

音声データは「one second audio clip」、1秒間のオーディオクリップ(wavファイル)

試しに、データセットの中の一つをSoX(Sound eXchange)コマンドを使って確認してみる。

% soxi speech_dataset/happy/00970ce1_nohash_0.wav

Channels       : 1
Sample Rate    : 16000
Precision      : 16-bit
Duration       : 00:00:00.98 = 15701 samples ~ 73.5984 CDDA sectors
File Size      : 31.4k
Bit Rate       : 256k
Sample Encoding: 16-bit Signed Integer PCM

チャンネル数は「1」(モノラル)、サンプリング周波数は「16000Hz」、これを16bit符号有り整数でデジタル化している。

音声ファイルについては、単純な音を作ってみると理解しやすいかもしれない。
音を作る - ichou1のブログ


音声データをTensorflowのインプットとして整える

サンプリング周波数が「16000」(16k)なので、1秒(1000ミリ秒)あたり16000個のサンプル数値が取れる。

(参考)「もしもし」という2秒間の発話をプロットしたもの。
https://cdn-ak.f.st-hatena.com/images/fotolife/i/ichou1/20171009/20171009113157.png

ここから、一定の固まり(「frame」と呼ぶ)で切り出す。
切り出す際は、少しずつズラしながら切り出す。
f:id:ichou1:20200208140844p:plain

ソース中に「window_size_samples」という数値が出てくるが、これは切り出す量を表している。
デフォルトは「480」個、時間にすると「30」ミリ秒。
また、ソース中に出てくる「window_stride_samples」は、ズラす量を表している。
デフォルトは「160」個、時間にすると「10」ミリ秒。

上の図を時間軸で表すと以下のようになる。
f:id:ichou1:20200209084133p:plain

「98」個のフレームが得られる。
このまま扱うことはせず、これを特徴量に置き換える。

デフォルトでは「MFCC」を使う。
音というのはいくつかの「純音」に分解できるので、その音を表す特徴的な純音の周波数を一定の数、抽出したもの。
抽出する数のことを"次元"と呼び、ソース中に出てくる「fingerprint_width」の「40」という数値は次元を意味する。
音声認識メモ(HMM)その2(HTK HCopyコマンド(2)) - ichou1のブログ

最終的に、インプットのTensorは以下の形になる。

# Input Tensor shape
(batch_size, 98, 40 , 1

Tensorflowモデルを組み立てる

今回、試した環境のバージョンは「1.14」

% python -c 'import tensorflow as tf; print(tf.__version__)'
1.14.0

モデルはいくつかの種類が用意されている。

  • single_fc
  • conv
  • low_latency_conv
  • low_latency_svdf
  • tiny_conv
  • tiny_embedding_conv

今回はデフォルトの「conv」について見てみる。

ざっくりとした構成は以下のとおり。

  1. 畳み込み
  2. MAXプーリング
  3. 畳み込み
  4. 乗算(matmul)

「MNIST」とそう変わらないので、要点のみ書く。

1. 畳み込み層(1番目)

フィルタの形状

# (filter_height, filter_width, in_channels, out_channels)
(20, 8, 1, 64)

図にすると以下のとおり。縦軸が時間軸に該当する。
f:id:ichou1:20200208153959p:plain

strideとpaddingの形状。

strides=[1, 1, 1, 1]
padding='SAME'

このレイヤの後に活性化関数(ReLU)とドロップアウト

2. MAXプーリング層

kernelとstrideの形状

ksize=[1, 2, 2, 1]
strides=[1, 2, 2, 1]

プーリング層による処理が終わった時点でのTensorの形状は以下のとおりとなる。

# Output Tensor shape
(batch_size, 49, 20, 64)
3. 畳み込み層(2番目)

フィルタの形状

# (filter_height, filter_width, in_channels, out_channels)
(10, 4, 64, 64)

strideとpaddingの形状。

strides=[1, 1, 1, 1]
padding='SAME'

このレイヤの後に活性化関数(ReLU)とドロップアウト

4. matmul

重み行列の掛け算。活性化関数は通さずに終わる。
以下はソースの抜粋。

def create_conv_model(fingerprint_input, model_settings, is_training):
    ...
    final_fc = tf.matmul(flattened_second_conv, final_fc_weights) + final_fc_bias
    return final_fc

重みパラメータの形状

# 49 * 20 * 64 = 62720
(62720, 12)

「12」はラベルの数


損失関数に「tf.losses.sparse_softmax_cross_entropy」をセットして、18000ステップ、トレーニングする。

optimizerのデフォルトは「GradientDescentOptimizer」

終わりに

コマンド用の単語音声を想定しているようなので、音を音素に分解したりせず、ひとまとまりとして扱っている。