アカデミック

【超初心者向け】Onset Detection用のF値を算出してみる。

pythonでf値を計算したい!

でもOnset Detectionでは少し勝手が違うんでしょ?

今回は,Onset Detection用のF値をpythonで算出する方法をお伝えしていこうと思います。また,本記事はpython実践講座シリーズの内容になります。その他の記事は,こちらの「Python入門講座/実践講座まとめ」をご覧ください。

【超初心者向け】python入門講座/実践講座まとめ目次 入門講座 1.実行環境2.文字の出力3.データ型4.変数5.更新と変換6.比較演算子7.論理演算子8.条件分岐9.リスト10.辞...

コーディングに関して未熟な部分がたくさんあると思いますので,もし何かお気づきの方は教えていただけると幸いです。また,誤りについてもご指摘していただけると非常に助かります。

読みたい場所へジャンプ!

お題

pythonでOnset Detection用のF値を算出してみよう。

流れ

F値の基本的な説明は,こちらの記事(「【超初心者向け】F値のくどい解説とPythonでの実装例。」)をご覧ください。Onset Detectionというのは,音楽音響信号などから所望の信号がなっている時刻を推定するタスクです。例えば,市販のCD楽曲からスネアドラムが鳴っている時刻を抽出するようなタスクです。

結論から言うと,Onset DetectionではTNは定義されないことが多いです。なぜなら,信号のOnsetはフレーム長に対してスパースであるからです。STFT(短時間フーリエ変換)を施した特徴量で,例えば1フレームが10msになるようにサンプリングレートとホップ長さを調整したとすれば,3分の楽曲に対して特徴量は18000フレームという長さになります。

ですので,推定結果はほどんど「0」という状況になります。これが,Onset DetectionでTNが定義されないことが多い所以です。

図解

TNは定義されないとします。

\begin{eqnarray}
\rm{R} &=& \frac{\rm{TP}}{\rm{TP} + \rm{FN}}\\
\rm{P} &=& \frac{\rm{TP}}{\rm{TP} + \rm{FP}}\\
\rm{F} &=& \frac{2\cdot \rm{TP}}{2\cdot \rm{TP} + \rm{FP} + \rm{FN}}
\end{eqnarray}

もしくは,F値は以下のようにしても算出できます。

\begin{eqnarray}
\rm{F} &=& \frac{2\cdot\rm{P}\cdot\rm{R}}{\rm{P} + \rm{R}}
\end{eqnarray}

ソースコード

関数部分だけを抜粋しています。入力はndarrayのバイナリとします。windowは許容誤差です。許容誤差はInputの単位に合わせる必要があります。たとえば,1フレームが10msのInputを与えたとすれば,window=5で50msの許容誤差となります。

import numpy as np
import copy

def fmeasure_drum(pred, truth, window):
  '''
  Input: ndarray
  Output: precision, recall, f-measure
  pred: detected onset binary
  truth: ground truth binary
  window: torelance window
  '''

  TP = 0
  FP = 0
  FN = 0

  # truthを書き換えるためdeepcopyしておく
  truth_copy = copy.deepcopy(truth)  

  # 推定結果からonsetのインデックスを抽出
  pred_index = np.where(pred==1)[0]

  # onsetの数だけ繰り返します
  for i in range(pred_index.shape[0]):

    # 端っこは例外処理
    if pred_index[i] < window:
      # 推定されたonsetの前後windowフレーム分を見て推定結果が正しいかどうかを判断する
      scope_index = np.where(truth_copy[0:pred_index[i]+window+1]==1)[0]
      if scope_index.shape[0] != 0:
        TP += 1
        truth_copy[0:pred_index[i]+window+1][scope_index[0]] = 0
      else:
        FP += 1

    # 端っこは例外処理
    elif pred_index[i] > pred.shape[0] - 1 - window:
      scope_index = np.where(truth_copy[pred_index[i]-window:]==1)[0]
      if scope_index.shape[0] != 0:
        TP += 1
        truth_copy[pred_index[i]-window:][scope_index[0]] = 0
      else:
        FP += 1

    # 端っこ以外の処理
    else:
      scope_index = np.where(truth_copy[pred_index[i]-window:pred_index[i]+window+1]==1)[0]
      if scope_index.shape[0] != 0:
        TP += 1
        truth_copy[pred_index[i]-window:pred_index[i]+window+1][scope_index[0]] = 0
      else:
        FP += 1

  # ここまででTPとFPの数え上げは終了。あとはTruthを見てFNを数えるだけ。
  truth_remain = np.where(truth_copy==1)[0]
  if truth_remain.shape[0] != 0:
    FN += truth_remain.shape[0]

  # 一応TPがゼロの場合に備える。多分必要ない。
  if TP==0:
    TP += 1e-5
    print("ゼロ除算を防止するために微小量を加えました")
  precision = (TP / (TP + FP))
  recall = (TP / (TP + FN))
  fmeasure = ((2 * precision * recall) / (precision + recall))
  
  return precision, recall, fmeasure, TP, FP, FN

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です