アプリでリアルタイムに音環境を認識できたら…

![title](https://github.com/daisukelab/ml-sound-classifier/raw/master/title.jpg)
(左の絵画はレンブラントの「[Portrait of an Evangelist Writing](https://commons.wikimedia.org/wiki/File:Rembrandt_-_Portrait_of_an_Evangelist_Writing.jpg)」)

# 1. はじめに
画像認識は今や一般的ですが、音の認識というと「音声認識」になり、いわゆるスマートスピーカーが隆盛です。
この記事では、音環境を認識することで、周囲の活動や環境を、更にはリアルタイムにライブ音源から認識するソリューション実装についてご紹介します。

- [daisukelab/Machine Learning Sound Classifier for Live Audio](https://github.com/daisukelab/ml-sound-classifier)

実装はこのGitHubリポジトリに全て公開されていますので、この記事ではいくつかの使用例と、学習方法、現状使うときに注意が必要なチューニング方法をご紹介します。

(今後もう少し詳しく学習方法をアップデートしたいです。)

## 1.1 音環境を認識するには

認識のための技術として機械学習、特にDeep Learningが欠かせませんが、画像と同じく音声もCNNが一番有効なようです。
先日終了したKaggleコンペティション「[Freesound General-Purpose Audio Tagging Challenge](https://www.kaggle.com/c/freesound-audio-tagging)」でも、トップクラスのソリューションはやはりCNNで多くを占めていた様子です。私もCNNのみで参加し、多くを学ぶことが出来ました。

ご紹介するリポジトリはそのソリューションで培われた技術をまとめ、リアルタイムかつ軽い環境で運用できるようにしたものです。

## 1.2 リポジトリの内容について

- MobileNetV2をモデルに採用し、実行時の速度がRaspberry Piなどでも実行できるようにしました。スマホなどモバイル端末だと十分なはずです。※ モデルは学習できれば自由に入れ替えることが出来ます。
- Tensorflowのグラフで実行できるようにしました。学習はKeras(Tensorflowバックエンド)で実施し、グラフに変換した形です。
- Freesound Dataset Kaggle 2018(FSDKaggle2018)のデータセットで学習したモデルが用意されています。自分のデータでも学習できるようになりました。
- 推論結果は時系列にアンサンブル(要は移動平均)するため、単純な実装よりかなり安定しています。
- 入力音声もその単位でノーマライズした形で学習・運用するシステムにしたため、入力レベルの変動にも強くなっています。

音で周りの状況がわかると、アプリケーションとしていろいろと考えられます。
この技術の可能性を皆様に知っていただければ幸いです。

### FSDKaggle2018データセットのクラス構成

下記の41クラスで構成されます。

```
"Acoustic_guitar", "Applause", "Bark", "Bass_drum", "Burping_or_eructation", "Bus", "Cello", "Chime", "Clarinet", "Computer_keyboard", "Cough", "Cowbell", "Double_bass", "Drawer_open_or_close", "Electric_piano", "Fart", "Finger_snapping", "Fireworks", "Flute", "Glockenspiel", "Gong", "Gunshot_or_gunfire", "Harmonica", "Hi-hat", "Keys_jangling", "Knock", "Laughter", "Meow", "Microwave_oven", "Oboe", "Saxophone", "Scissors", "Shatter", "Snare_drum", "Squeak", "Tambourine", "Tearing", "Telephone", "Trumpet", "Violin_or_fiddle", "Writing"
```

似たような音で分類が難しいものがいくつかありました。

- Gunshot_or_gunfire(銃声)、Fireworks(花火)、そしてSnare_drum
- Tearing(破って引きちぎる音)、Squeak(キーキー音の総称で範囲が広い)
- SqueakとDrawer_open_or_close(引き出しの開閉音)
- Laughter(笑い声)、Applause(喝采)
- Double_bass、Cello、Violin_or_fiddleこれら似た楽器音

# 2. 考えられる応用(アプリケーション)例

## 2.1 APP #1 iPhoneを想定した認識例: 花火の検出

リポジトリの「[sample/fireworks.wav](https://github.com/daisukelab/ml-sound-classifier/blob/master/sample/fireworks.wav)」は、個人的な旅行の最中に偶然録画されたビデオから抜き出しました。
つまり、iPhoneのアプリで得られる音質の音そのものです。これを認識してみましょう。
※ ファイルに録音された音ですが、オンザフライと処理は同等です。

```
$ CUDA_VISIBLE_DEVICES= python premitive_file_predictor.py sample/fireworks.wav
Using TensorFlow backend.
Fireworks 0.7465853
Fireworks 0.7307739
Fireworks 0.9102228
Fireworks 0.83495927
Fireworks 0.8383171
Fireworks 0.87180334
Fireworks 0.67869043
Fireworks 0.8273291
Fireworks 0.9263128
Fireworks 0.6631776
Fireworks 0.2644468
Laughter 0.2599133
```

(「CUDA_VISIBLE_DEVICES=」はあえてGPUを使わないための前置きですが)花火がきれいに認識できています。横の数値は認識の確率となります。
ただ「premitive_file_predictor.py」は、このサンプルに限ってモデルの推論をそのまま使うため、移動平均していない結果です。しかし元のデータセットにかなりデータの量が揃っているおかげか、録音系がiPhoneというデータセットに含まれない音にも関わらず、使える認識結果になっています。

上記は説明のためファイルに録音された音を認識しました。下記のようにすると、ライブ録音で実行環境のデフォルトに設定されたオーディオ入力の音から推論します。

```
$ python realtime_predictor.py
```

同様な形でiOSやAndroidに組み込むことで、同じような認識ができるはずです。

## 2.2 APP #2 PCでの認識例: デスクワークの認識

`deskwork_detector.py` というアプリケーションを用意していますが、こちらは3つのデスクワーク音を認識するものです。

- 物書きをしている音
- ハサミを使っている音
- コンピューターのキーボードを操作している音

「[sample/desktop_sounds.wav](https://github.com/daisukelab/ml-sound-classifier/blob/master/sample/desktop_sounds.wav)は私の机の上で上記を連続して実行して録音したもので、その認識結果はこのようになります。


```
$ python deskwork_detector.py -f sample/desktop_sounds.wav
Using TensorFlow backend.
📝 📝 📝 📝 📝  Writing 0.40303284
📝 📝 📝 📝 📝 📝  Writing 0.53238016
📝 📝 📝 📝 📝 📝  Writing 0.592431
📝 📝 📝 📝 📝 📝  Writing 0.5818318
📝 📝 📝 📝 📝 📝 📝  Writing 0.60172915
📝 📝 📝 📝 📝 📝 📝  Writing 0.6218055
📝 📝 📝 📝 📝 📝 📝  Writing 0.6176261
📝 📝 📝 📝 📝 📝 📝  Writing 0.6429986
📝 📝 📝 📝 📝 📝 📝  Writing 0.64290494
📝 📝 📝 📝 📝 📝 📝  Writing 0.6534221
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.70015717
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.7130502
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.72495073
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.75500214
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.7705893
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.7760113
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.79651505
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.7876685
📝 📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.8026823
📝 📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.80895096
📝 📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.8053692
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.7975255
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.77262956
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.76053137
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.7428124
📝 📝 📝 📝 📝 📝 📝 📝  Writing 0.70416236
📝 📝 📝 📝 📝 📝 📝  Writing 0.6924045
📝 📝 📝 📝 📝 📝 📝  Writing 0.66129446
📝 📝 📝 📝 📝 📝 📝  Writing 0.6085751
📝 📝 📝 📝 📝 📝  Writing 0.5530443
📝 📝 📝 📝 📝 📝  Writing 0.50439316
📝 📝 📝 📝  Writing 0.36155683
📝 📝 📝  Writing 0.25736108
📝 📝  Writing 0.19337422
⌨ ⌨  Computer_keyboard 0.17075704
⌨ ⌨ ⌨  Computer_keyboard 0.2984399
⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.42496625
⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.5532899
⌨ ⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.64340276
⌨ ⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.6802248
⌨ ⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.66601527
⌨ ⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.62935054
⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.58397347
⌨ ⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.526138
⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.4632419
⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.42490804
⌨ ⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.40419492
⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.38645235
⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.36492997
⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.33910704
⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.3251212
⌨ ⌨ ⌨ ⌨  Computer_keyboard 0.30858216
⌨ ⌨ ⌨  Computer_keyboard 0.27181208
⌨ ⌨ ⌨  Computer_keyboard 0.263743
⌨ ⌨ ⌨  Computer_keyboard 0.23821306
⌨ ⌨ ⌨  Computer_keyboard 0.20494641
⌨ ⌨  Computer_keyboard 0.12053269
⌨ ⌨  Computer_keyboard 0.15198663
✁ ✁  Scissors 0.16703679
✁ ✁ ✁  Scissors 0.21830729
✁ ✁ ✁  Scissors 0.28535327
✁ ✁ ✁ ✁  Scissors 0.3840115
✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.52598876
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6164208
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6412893
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.7195315
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.7480882
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.76995486
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.7952656
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.79389054
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.8043309
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.79848546
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.801064
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.79365206
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.7864271
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.71144533
✁ ✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.70292735
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6741176
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6504746
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6561931
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6303668
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6297095
✁ ✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.6140897
✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.57456887
✁ ✁ ✁ ✁ ✁ ✁  Scissors 0.5549676
```

3つのアクションが順次きれいすぎるほど認識されています。先程同様、ファイルを指定せずに実行すれば実行環境のマイク入力の音で動作します。

```
$ python deskwork_detector.py
```

パソコンで実行されるケースが多いと思いますので、そのままキーボードを適当に打った音を発生させれば、'Computer_keyboard'が出力させる…と信じています。

## 2.3 役に立つ応用

これまで２つご紹介しましたが、それを何に使うのかは、例えばiPhoneの例では旅行アプリ、PCの例では集中した時間をサポートするアプリなどが考えられます。この先はクリエーターの皆様に委ねるべきかと思います。

堅い応用例の一つとして、生産現場が挙げられますが、[IAMAS](https://www.iamas.ac.jp)の小林先生による一連の加工機への応用が参考になります。

- [機械学習とRaspberry Piを用いてレーザー加工機の動作状態を判定する（第1回）](https://qiita.com/mayfair/items/60d12c5655238c7677c9)
- [機械学習とRaspberry Piを用いてレーザー加工機の動作状態を判定する（第2回）](https://qiita.com/mayfair/items/91b5408a58098ccf920c#_reference-51830d8b9ba221854049)
- [機械学習とRaspberry Piを用いてレーザー加工機の動作状態を判定する（第3回）](https://qiita.com/mayfair/items/e8d540ecb259aebea1e2#_reference-81d2848a5672836a6463)
- [機械学習とRaspberry Piを用いてレーザー加工機の動作状態を判定する（第4回）](https://qiita.com/mayfair/items/956e33b7196604674cda#_reference-b9845ad829787ae2f4d4)

これまで紹介した内容は、こういった応用へ適用できるハンディなサンプルとしてとして利用できるかと思います。

Freesoundのデータセットには、似たような音のクラスがいくつかあり、人間が聞き分けるのが困難なものもあります。ダブルベース、チェロ、ビオラ…データをいくつか聞いて、自分ではギブアップ。これらのデータを分類できた実績のあるソリューションとして使っていただけるのではと思います。

### カメラよりマイクの方が安価

画像を認識する場合、カメラで画像を取り込む必要があります。実際の応用を考えたとき、カメラは比較的コストがかかり、またプライバシーの問題になりやすい側面があります。もちろん音声で録音することもプライバシーの問題になりますが、現実としてスマートスピーカーは受け入れられています。

音だけで認識できる場合は、比較的安価で処理も軽く良いソリューションになりえます。

# 3. 使い方

Python 3.6でテストしています、3.4でも動くと思いますが、2.7では動きません。依存ライブラリは概ね以下のとおりです。

- Tensorflow, 1.9でテスト。
- Keras, 2.2.0でテスト。
- LibROSA, 0.6.0でテスト。
- PyAudio, 0.2.11でテスト。
- EasyDict, 1.4でテスト。
- imbalance.learn 学習時のみ, 0.3.3でテスト。

音声の取り込みは[PyAudio](http://people.csail.mit.edu/hubert/pyaudio/)で行っています。

簡単に試すには、下記のように実行します。パソコンのマイクの音を拾って、学習済みモデルで認識させることができます。

```
$ python realtime_predictor.py
```

推論には下記3つのPythonスクリプトが用意され、中でもrealtime_predictor.pyが基本的なリアルタイム推論プログラムです。

- realtime_predictor.py - リアルタイムにFSDKaggle2018の41クラスを認識できます。またこのプログラムをimportして動作を拡張できます。
- deskwork_detector.py - 拡張した例で、3つのデスクワーク活動を認識するものです。認識結果を絵文字の量で表示します。
- premitive_file_predictor.py - ファイルに記録された音声を認識するシンプルな説明用プログラムです。認識結果も平均を取らず、あくまでシンプルに実装しています。

その他、common.py、lib_train.pyなどのコードに共通利用する関数やクラスが実装されています。

動作時のパラメーターやクラスなどの設定は、config.pyにまとめるよう変更されました。実行時のカレントフォルダ直下にあるconfig.pyが読み込まれます。

## 3.1 リアルタイム推論のチューニング方法

動作のタイミングを設定する重要なパラメーターが3つあります。

```
conf.rt_process_count = 1
conf.rt_oversamples = 10
conf.pred_ensembles = 10
```

### 3.1.1 サンプリング頻度の設定 `conf.rt_oversamples`

これは一秒間に何回音を取り込むかを設定します。10と設定すれば、1秒間に10回取り込みます。
大きくすることで認識の動作レスポンスを早くすることが出来ますが、実行環境のパワーに依存するのでご注意ください。

### 3.1.2 処理頻度の設定 `conf.rt_process_count`

これは音声の変換と認識を行う間隔を設定します。オーディオ取り込みの回数を設定し、1だと取り込みの度に認識までの処理を行います。
2を設定すれば、オーディオの取り込み2回につき1回認識までの処理を行います。認識のレスポンスは1にすると一番早いのですが、非力な処理系では大きめの値にせざるを得ないかもしれません。

MacBookPro 4コア環境では1で実行できます。

### 3.1.3 推論結果のスムージング設定 `conf.pred_ensembles`

これはアンサンブル回数を設定します。過去から現在の認識結果までがfifoに保存されますが、このfifoサイズになります。このfifoにある予測値から幾何平均(geometric mean)を計算し、アンサンブル結果とします(移動平均と同じですね、機械学習でこのように結果の平均をとることをアンサンブルと呼ぶことが多く、それに倣いました)。

例えば`conf.pred_ensembles = 5`と設定した場合、直近の過去4予測値と現在の予測値から幾何平均を取ったものがアンサンブルした結果となります。

移動平均と同じになりますので、その特性がそのまま当てはまります。つまり応答を良くしたい場合は小さい値に、精度をスムーズにしたい場合は大きい値を使います。

# 4. apps: 応用例とその学習方法

appsフォルダには、いくつかの応用の実装例があります。データセットをダウンロードすれば、学習させて試すことができます。

- `fsdkaggle2018` - FSDKaggle2018データセットを学習するサンプル。`mobilenetv2_fsd2018_41cls.h5`はこの実装で作成されました。
- `fsdkaggle2018small` - オーディオ16kHz、学習サンプル64x64、と小さめのデータで扱えるようにしたFSDKaggle2018データセット学習サンプル。
- `cnn-laser-machine-listener` - 上記でご紹介した「[機械学習とRaspberry Piを用いてレーザー加工機の動作状態を判定する（第1回）](https://qiita.com/mayfair/items/60d12c5655238c7677c9)」をこの実装で試した例。

特に一番最後の例を使って説明します。

## 4.1 レーザー加工機の動作状態判定の実装例について

「[機械学習とRaspberry Piを用いてレーザー加工機の動作状態を判定する（第1回）](https://qiita.com/mayfair/items/60d12c5655238c7677c9)」で紹介されているのは、レーザー加工機と呼ばれる工作機械の利用状況を音で判定し、利用状況を把握した上で機械の運用をモニターしたり改善するための応用です。

[github/Laser Machine Listener](https://github.com/kotobuki/laser-machine-listener)

こちらにデータセットを含め、Raspberry Pi上で動作する判定システムの実装が、詳しく説明された状態で公開されています。
このうちデータセットだけをお借りして、今回の実装のCNNで判定させるよう応用した例となっています。

前提なのですが、実は元々の実装がとてもシンプルにNNで実装されていて、実はそのままで全く良い分類器として成り立っています。そのものにCNNを適用するのはほとんど無駄なのですが、この応用のさらなる展開として、違うラボでも使えるものにするなど判定器の「汎化」が必要な場合には使えるでしょう。もちろんどのラボでも動作するようにするためには、十分なデータが必要になってきます。

以下の方法で、学習から利用までを行うことが出来ます。

1. まず、思います上記のリポジトリをcloneしましょう。

    ```
    cd apps/cnn-laser-machine-listener
    ./download.sh
    ```
2. データを前処理します。CNN-LML-Preprocess-Data.ipynbを上から順に実行していくと、X_train.npyなどのファイルが生成されます。
3. 学習は、CNN-LML-Train.ipynbを実行すると実施できます。この例ではフルスクラッチで学習する方法と、fsdkaggle2018smallの学習済みモデルから転移学習する方法が説明されています。データ数が少ないため、基本的に転移学習しかうまくいかない例です。
4. Tensorflowの.pbファイルに変換するには、CNN-LML-TF-Model-Conversion.ipynbを実行します。一旦モデルをロードして、変換する例になっています。
5. 学習したモデルを利用するには、下記のように実行します。

    ```
    python predict_this.py
    ```

# 5. まとめ

Freesoundのコンペティションでは、多くの参加者がトップと僅差の結果を出せていました。手軽なCNNではだいたい95%位のaccuracyが上限のようで、音環境の認識はそのくらいが利用可能な現実解かもしれません。ここまで紹介した実装も同等の性能を出すことが出来ます。

カジュアルな用途には、ぜひ使っていただければと思います。

しかし、このレベルでは、まだ認識結果の間違いが目立ち、製品レベルではかなり使いにくいのが事実です。特に品質保証がシビアな場合なおさらです。

機械学習の場合、その性質上どんなに性能が良くても「とんでもない!」認識結果を出してしまう可能性が残ります。そのため、そのままセンサー代わりに使って実際のアプリケーションに採用するには、条件設定で妥協してつまらないものになるか、誤認識を許容できず駄目だという判断で実証実験止まりになるケースも多いのではないでしょうか。

では、やはりこれでは企業レベルで開発するアプリや製品にとって使いものにならないのでしょうか?

先のチェロとダブルベースの聞き分けのように、人間を超えるような結果もあり、とてもポテンシャルを秘めています。今機械学習の応用に求められているのは、アプリケーションでどう組み込んで活かすか、そのデザインパターンのセンスを磨くことのように思えます。アプリケーションの価値にポテンシャルを引き出しつつ、誤認識のような弱点はうまくカバーできるような活用の仕方、そのデザインパターンがきっとあると思います。

音の認識に限らず、機械学習を自然にうまく使った高度な応用に期待したいと思います。

この投稿はリポジトリと共に順次更新して整備していきたいと思います。なにか必要性の高いことなどあれば、ぜひリクエストしてください。