音に対するdeep learningの入門として環境音の識別をやってみる(3)

こちらの続き
https://jsapachehtml.hatenablog.com/entry/2020/10/24/183410?_ga=2.58998701.1453230663.1604732830-1050067043.1602897991

今回はBC Learningを実装してみたのでメモ

BC Learning

論文: https://arxiv.org/pdf/1711.10282.pdf
公式repo: https://github.com/mil-tokyo/bc_learning_sound

前回実装したEnvNetと同じ著者による研究で、EnvNetの改良とaugmentation手法であるmixupを適用してSOTAを達成したもの

私が実装する目的はaugmentation部分を学ぶことなので、SOTAを達成したEnvNet v2は実装せず元のEnvNetをそのまま使う

実装したコードはこちら
https://github.com/y-kamiya/environmental-sound-classification

データセット

ESC-50を利用した。以下のように前処理

  • 16kHzにダウンサンプリング
  • -1.0 ~ 1.0に正規化
  • 5秒の音波形の前後に0.75秒のpaddingを入れる
    • random cropする際に先頭と末尾のデータも均等に抽出されるようにするため

キモになるのはmixupのやり方で以下

  • データセットから2つの音(ラベルが異なる)をランダムに抽出
  • 2つの音からランダムな位置で1.5秒幅(segment)の区間をcrop
  • ランダムな割合rで2つのsegmentを合成
    • 音圧を考慮して合成割合を調整する
  • ラベルは割合rに従ってそれぞれのラベルに確率を分配 f:id:y-kamiya:20201107214001p:plain 論文の図が大変わかりやすいため引用

mixという関数がちょっと複雑だが、単純にランダムな割合rで合成するのではなく、人間が認識する音の大きさのスケール(G1, G2)を考慮するというもの

def __getitem__(self, index):
    ...

    while (True):
        # 2つの音をランダムに抽出しラベルが同じであれば再抽選
        rand1 = random.randint(0, len(self.sounds)) - 1
        rand2 = random.randint(0, len(self.sounds)) - 1
        label1 = self.labels[rand1]
        label2 = self.labels[rand2]
        if label1 != label2:
            # それぞれランダムに1.5秒区間を抽出
            sound1 = self.random_crop(rand1)
            sound2 = self.random_crop(rand2)
            break

    # 音圧の最大値を算出(デシベル単位)
    G1 = self.__compute_gain_max(sound1)
    G2 = self.__compute_gain_max(sound2)

    r = random.random()
    p = 1 / (1 + 10 ** (G1 - G2) / 20 * (1 - r) / r)
    sound = (p * sound1 + (1 - p) * sound2) / ((p ** 2 + (1 - p) ** 2) ** 0.5)

    target = torch.zeros(self.config.n_class)
    target[label1] = r
    target[label2] = 1 - r

    return sound.unsqueeze(0), target, index

def __compute_gain_max(self, x, n_fft=2048, min_db=-80):
   # 実際にfftはかけないが、fftをかけると想定される区間ごとにgainを計算
   stride = n_fft // 2
   gains = torch.empty(0)
   for start in range(0, len(x) - n_fft + 1, stride):
       end = start + n_fft
       # 簡単のため平均を取る形
       gains = torch.cat((gains, torch.mean(x[start:end] ** 2).unsqueeze(0)))

   gain_max = max(torch.max(gains).item(), 10 ** (min_db / 10))
   return 10 * math.log10(gain_max)

ゲインの計算は論文ではA-weightingという計算方法を取っているが、今回は簡単のため平均を取るだけにした

学習ロジック

ネットワーク構成はEnvNetを使っているため以前書いた記事と同じ https://jsapachehtml.hatenablog.com/entry/2020/10/24/183410

learning rateのスケジューリングが異なる

  • augmentationなし(standard learning)の場合
    • lrの初期値は0.01、300epochと450epochでそれぞれ0.1倍し、600epochで終了
  • augmentationあり(bc learning)の場合
    • 上記のepochを2倍の値にして学習

評価ロジック

  • 一つの音源に付き1.5秒幅で10個のcropを作成
  • 各出力をsoftmaxにかけて10個分の平均を取り最大のものを予測値とする
    • EnvNetの実装の際にprobability votingを実装したので私の実装ではそれをそのまま使っている(意味合いは変わらないので)

基本的な処理はEnvNetの場合と変わらないためこちらの記事で書いた評価ロジックを利用
https://jsapachehtml.hatenablog.com/entry/2020/10/24/183410

以下の点だけ異なる

実験

以下によってcolab上で学習を実行

# standard
!python trainer.py --batch_size 64 --epochs 600 --dataroot "$dataroot" --name bc_batch64_epochs600_standard --model_type bc-learning --lr_milestones 300 450 --amplitude_threshold 0 --log_interval 10 --eval_interval 10

# bc-learning
!python trainer.py --batch_size 64 --epochs 1200 --dataroot "$dataroot" --name bc_batch64_epochs1200 --model_type bc-learning --lr_milestones 600 900 --amplitude_threshold 0 --log_interval 10 --eval_interval 10 --use_augment 

結果がこちら f:id:y-kamiya:20201219170126p:plain standardで67%、bc-learningで68%といったところ

論文の結果では

  • standard: 71%
  • bc-learning: 76%

となっているためかなり低めになっている上、両者にあまり違いが出なかった

論文の条件との差異としては

  • cross validationによる評価をしていないこと
  • A-weightingを使っていないこと

だが両者の差があまりないことから後者が効いているのかもしれない

参考