多入力CNNによる「楽しい」BGM分類器

# はじめに
**[Neural Network Console Challenge](https://nnc-challenge.com/) sponsored by Audiostock**のアウトプットレポートです。
今回（2020/9/16 ~ 2020/10/19）のNeural Network Console Challengeは、**「Neural Network Consoleを用いてAudiostockの音声（BGM）データを解析」**というテーマで開催され、世界最大級の音源数を誇るロイヤリティフリー音楽販売サービス「[Audiostock](https://audiostock.jp/)」より約10,000点のBGMデータが提供されました。

# チャレンジテーマ：「楽しい」BGM分類器
本チャレンジでは次の二つのチャレンジテーマが設定され、私は一つ目の「自動分類アルゴリズムの作成」を選択しました。

- AudiostockのBGM検索の自動分類アルゴリズムを作り出す
- 自由な発想で音声データを解析する

[AudiostockのBGM検索ページ](https://audiostock.jp/bgm)では、用途や楽曲ジャンル、イメージなど、様々なカテゴリで絞って検索をすることができます。
このうち「イメージ」による音声分類は、BGMの感じ取り方は人によって異なるため、機械的な分類が難しいと考えられます。例えば、あるBGMに対して、ある人は「切ない」というイメージを抱くかもしれませんが、別のある人は「穏やか」というイメージを抱くかもしれません。
このようなルールベースによる分類が難しいカテゴリに対して、NNによる統計的機械学習を適用することで、帰納的に分類することに挑戦しました。具体的には、多入力CNNを用いて、**入力されたBGMが「楽しい」BGMかどうかを出力するモデル**を作成しました。

# 前処理・データセット作成
まず、提供された約10,000点のBGMデータに対して、「楽しい」とラベリングされたBGMデータには```1```、ラベリングされていないBGMデータには```0```を正解ラベルとして与えます。下表に示すそれぞれのラベルのデータ総数を見ると分かる通り、ラベル```1```のデータに対してラベル```0```のデータが極端に多い、不均衡データとなっています。

<table>
  <tr>
    <td style="text-align:center;"><b>ラベル</b></td>
    <td style="text-align:center;"><b>データ数</b></td>
  </tr>
  <tr>
    <td style="text-align:center;">0<br>（「楽しい」ラベリング無し）</td>
    <td style="text-align:center;">9842</td>
  </tr>
  <tr>
    <td style="text-align:center;">1<br>（「楽しい」ラベリング有り）</td>
    <td style="text-align:center;">960</td>
  </tr>
</table>

まず、データ全体を```20:4:1```の割合で訓練データ、検証データ、テストデータに分割しました。
次に、訓練データと検証データについては、ラベル```0```のBGMデータに対してランダムダウンサンプリングを行い、均衡データとなるようにしました。それぞれのデータ数は下表の通りです。

<table border="3">
  <tr>
    <td style="text-align:center;">データ種別</td>
    <td style="text-align:center;">ラベル</td><td>データ数</td>
  </tr>
  <tr>
    <td rowspan="2" style="text-align:center;">訓練データ</td>
    <td style="text-align:center;">0</td>
    <td style="text-align:center;">768</td>
  </tr>
  <tr>
    <td style="text-align:center;">1</td>
    <td style="text-align:center;">768</td>
  </tr>
  <tr>
    <td rowspan="2" style="text-align:center;">検証データ</td>
    <td style="text-align:center;">0</td>
    <td style="text-align:center;">154</td>
  </tr>
  <tr>
    <td style="text-align:center;">1</td>
    <td style="text-align:center;">154</td>
  </tr>
  <tr>
    <td rowspan="2" style="text-align:center;">テストデータ</td>
    <td style="text-align:center;">0</td>
    <td style="text-align:center;">395</td>
  </tr>
  <tr>
    <td style="text-align:center;">1</td>
    <td style="text-align:center;">38</td>
  </tr>
</table>

テストデータセットを検証データセットとは別のデータで作成することで、学習後のモデルの評価をより信頼できるものにすることを狙いました。
[テストデータセットの指定方法](https://support.dl.sony.com/faq-ja/faq%ef%bc%9a%e8%a9%95%e4%be%a1%e5%ae%9f%e8%a1%8c%e6%99%82/)

次に、音声データの前処理のアプローチの一つとして、各BGMデータのメル周波数スペクトログラムを可視化した画像を入力することを考えました。
まず各BGMデータについて調波打楽器音分離（HPSS）を行い、調波音と打楽器音それぞれについて次のようなメルスペクトログラムのグレースケール画像を生成しました。

![msg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/d69fe638-26df-7e7a-e8e2-6150348b3309.png)

これらのデータを、訓練データ、検証データ、テストデータそれぞれについて一つのCSVファイルにまとめてデータセットを作成しました（具体的な中身は次節にて）。

ここまでの作業をローカルで行い、作成したデータセットをNeural Network Consoleの[データセットアップロードツール](https://support.dl.sony.com/docs-ja/%E3%83%87%E3%83%BC%E3%82%BF%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%83%84%E3%83%BC%E3%83%AB%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9/)（GUIツール）を用いてNeural Network Console クラウド版にアップロードしました。

これ以降はNeural Network Console（以下、NNC） クラウド版にて作業しました。

# 多入力CNNモデル
2入力CNNモデルと3入力CNNモデルを作成し、比較しました。
活性化関数に関して、はじめは```ReLU```を使用していたのですが、```Swish```のほうが検証データの分類精度が良い傾向が見られたので、```Swish```を採用しました（両モデル共通）。
併せて、各レイヤーの重みの初期化方法```W.Initializer```は、```NormalConvolutionHeForward```（Convolutionレイヤー）、および```NormalAffineHeForward```（Affineレイヤー）を採用しました。
なお「評価」に関して、10回評価の平均を出力しました。

## 2入力CNNモデル
まず、調波音と打楽器音の2つのメルスペクトログラムデータを入力に持つ2入力CNNモデルを作成してみました。

### データセット
<table>
  <tr>
    <td style="text-align:center;"><b>x</b></td>
    <td style="text-align:center;"><b>x2</b></td>
    <td style="text-align:center;"><b>y</b></td>
  </tr>
  <tr>
    <td style="text-align:center;">調波音メルスペクトログラム<br>（.png）</td>
    <td style="text-align:center;">打楽器音メルスペクトログラム<br>（.png）</td>
    <td style="text-align:center;">ラベル<br>（0 or 1）</td>
  </tr>
</table>

### ネットワーク
#### Mainネットワーク
![2input-Main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/9565de28-e270-80ea-9ffa-099f584a34d5.png)

#### Conv2Dネットワーク
![2input-Conv2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/67a21306-72d0-054f-8e67-b6406f617637.png)

#### FCネットワーク
![2input-FC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/6c46e884-ea02-a1a1-f7d9-0fd7e1ac4ade.png)

Unitレイヤーを用いて、畳み込み層や全結合層は別のネットワークに分割することで可読性（可視性）を高めました。
そのうえで、ConvolutionレイヤーのOutMapsプロパティやKernelShapeプロパティなど、細かく調整したいプロパティについては、Argumentレイヤーを用いることでMainネットワーク画面から調整できるようにし、保守性が高くなるように工夫しました。

### 学習（入力：訓練データ、検証データ）
エポック数30、バッチサイズ64で学習しました。
Best Validation Errorは2エポック目の```0.673569```でした。
![2input-learn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/70e4f4da-124c-e274-7e81-4abd826edfbe.png)

#### ※GPUの利用
初めにCPUでの学習を試してみたのですが、下表のとおり処理時間が非常に長かったため、GPUでの学習に切り替えました（本チャレンジのために税込11,000円分のGPU利用枠が提供された）。処理時間を比較してみると、GPUでの学習の圧倒的な速さがよく分かります。

<table>
  <tr>
    <td style="text-align:center;"><b>リソース</b></td>
    <td style="text-align:center;"><b>処理時間</b></td>
  </tr>
  <tr>
    <td style="text-align:center;">CPU</td>
    <td style="text-align:center;">01:14:51:58</td>
  </tr>
  <tr>
    <td style="text-align:center;">NVIDIA Tesla V100 GPU ×1</td>
    <td style="text-align:center;">03:22</td>
  </tr>
</table>

今回のチャレンジではCPUは最初のお試しでのみ使い、以降は全てGPUで学習しました。

### 評価（入力：テストデータ）
混同行列は次のようになりました。
![2input-test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/d00e792f-96c1-dac7-415f-f8cab05371ac.png)

テストデータでの分類精度は```70.9%```でした。
しかし、```y=1```のRecall（実際に```1```であるデータのうち、```1```であると予測されたデータの割合）が```26.32%```と低くなってしまいました。すあわち、実際に```1```であるデータのうち、多くを見逃してしまっていることが分かります。

※一般に、RecallとPrecisionのどちらを重視するかは学習の目的によりますが、今回は誤検知よりも見逃しのほうに重いペナルティを課すべきと考えたので、Recallを重視しています。

## 3入力CNNモデル
次に、前述の2つのメルスペクトログラムデータに加えて、生データであるBGMの音声データ自体も入力に持つ、3入力CNNモデルを作成してみました。

### データセット
<table>
  <tr>
    <td style="text-align:center;"><b>x</b></td>
    <td style="text-align:center;"><b>x2</b></td>
    <td style="text-align:center;"><b>x3</b></td>
    <td style="text-align:center;"><b>y</b></td>
  </tr>
  <tr>
    <td style="text-align:center;">音声データ<br>（.wav）</td>
    <td style="text-align:center;">調波音メルスペクトログラム<br>（.png）</td>
    <td style="text-align:center;">打楽器音メルスペクトログラム<br>（.png）</td>
    <td style="text-align:center;">ラベル<br>（0 or 1）</td>
  </tr>
</table>

### ネットワーク
#### Mainネットワーク
![3input-Main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/9f9e9f17-be8a-08fb-7b22-95e105cb8dab.png)

#### Conv1Dネットワーク
![3input-Conv1D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/b2ba157f-8fac-8a40-b70d-34e08ab91871.png)

#### Conv2Dネットワーク
![3input-Conv2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/f75b22cf-d5ec-1fe8-6456-a06504d96231.png)

#### FCネットワーク
![3input-FC.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/9654db0a-ed17-485c-881f-489f13e85b93.png)

2入力CNNモデルで作成した部分についても、改めてレイヤー数を再調整しました（Mainネットワーク）。

### 学習（入力：訓練データ、検証データ）
2入力CNNモデルと同じく、エポック数30、バッチサイズ64で学習しました。
Best Validation Errorは30エポック目の```0.660095```でした。
![3input-learn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/0af43822-8cd6-72e7-f02f-90241b7f6daf.png)


### 評価（入力：テストデータ）
混同行列は次のようになりました。
![3input-test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282840/5372915e-a0cb-5955-f12d-120f30bf7c4c.png)

精度（Accuracy）は2入力CNNモデルに比べて低くなってしまい```57.74%```でした。
しかし、```y=1```のRecallが```71.05%```と大幅に改善されています。すなわち、実際に```1```であるデータの見逃しを減らすことができました。```y=0```のRecallが低いのが気になりますが、これは訓練データセットにおけるラベル```0```のデータ数を増やすなどすれば改善されると考えられます。

# まとめ
2つのモデルを比較すると、Recallの値から、3入力モデルのほうが良いと結論付けられました。
ただし、データセット内のラベル```0```とラベル```1```のデータ数のバランスは更なる調整が必要と考えられます。
また、ルールベースによる分類が難しい「イメージ」のカテゴリに対して、多入力CNNを用いるとある程度の精度でBGMの分類が行えることが分かりました。

最後に、今回作成した2つのモデルを表にまとめておきます。

<table>
  <tr>
    <td style="text-align:center;" rowspan="2"><b>モデル種別</b></td>
    <td style="text-align:center;" rowspan="2"><b>Accuracy</b></td>
    <td style="text-align:center;" colspan="2"><b>Recall</b></td>
  </tr>
  <tr>
    <td style="text-align:center;">y = 0</td>
    <td style="text-align:center;">y = 1</td>
  </tr>
  <tr>
    <td style="text-align:center;">2入力CNNモデル</td>
    <td style="text-align:center;">70.9%</td>
    <td style="text-align:center;">75.19%</td>
    <td style="text-align:center;">26.32%</td>
  </tr>
  <tr>
    <td style="text-align:center;">3入力CNNモデル</td>
    <td style="text-align:center;">57.74%</td>
    <td style="text-align:center;">56.46%</td>
    <td style="text-align:center;">71.05%</td>
  </tr>
</table>

# 感想
普段の開発ではtensorflowを使っており、NNCを触るのは今回のNeural Network Console Challengeが初めてでした。NNCは想像していた以上に直感的な操作が可能で、特に視覚的に分かりやすくネットワークを構築することができ、生産性向上につながるサービスであると感じました。また、Argumentなどを用いることで効率的なネットワーク構築が可能だったり、ネットワーク横にコメントを置くことができたりと、細かいところにまで気を配られたサービスだと感じました。
今回貴重な機会を与えてくださった、株式会社レッジ様、ソニーネットワークコミュニケーションズ株式会社様、株式会社オーディオストック様、ほか関係者の皆様に厚く御礼申し上げます。