TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

ピッチ検出モデルのSwiftF0を試す(その3:疑似ラベリング)

前回、SwiftF0の推論処理をC++で実装してチューナーアプリに組み込んでみたが、誤差が20cent近くあることが分かった。

チューナー用途では、20centの誤差は許容できないため、ピッチ推定のアルゴリズムは既存のものを使い、決定木の機械学習モデルの訓練データ生成にSwiftF0を使うことにした。

疑似ラベリング

SwiftF0は、チューナー用途では誤差は大きいが、半音範囲では正確に推論できるため、オクターブエラーのグランドトルゥースとして利用できる。

マイク録音したアコースティックギター、オーディオインターフェースで録音したエレキギターギターとベースの録音音声を用意して、SwiftF0でピッチ推定を行い、一定のピッチが連続する区間から、サンプリングして訓練データを作成する。
連続するピッチなしの区間からも、ピッチなしのデータとしてサンプリングを行う。

データ拡張

SwiftF0の論文に記載されていた方法を参考に、CHiME-Homeデータセットの環境音と白色ガウスノイズを混ぜたものを、SNR 30dBで加えた音声を作成した。
SwiftF0の論文では、SNR 10dBとしていたが、そこまでうるさい環境を想定していないので、軽いノイズレベルとした。

訓練データ生成

音声から訓練データを生成するプログラムをGPT-Codex-5.3で生成した。

プロンプト:

現在のピッチ検出アルゴリズムのオクターブエラー訂正の処理を機械学習の決定木のアルゴリズムに置き換えたい。
そのために、まずは、以下の特徴量として、SwiftF0をグランドトルゥースとする訓練データを作成したい。
以下の訓練データを作成するプログラムを作成してください。

音量がしきい値以上の区間で、

1. SwiftF0の音階が1秒以上連続する区間から
- PitchAnalyzerと音階が一致するサンプルを1秒あたり2つ(1秒より短くても最低1つ以上)
- PitchAnalyzerと音階が一致しないサンプルを1秒あたり3つ(1秒より短くても最低1つ以上)
- PitchAnalyzerのオクターブエラー訂正前のpeak_frequencyと音階が一致しないサンプルを1秒あたり3つ(1秒より短くても最低1つ以上)

2. SwiftF0が信頼度が低く、かつ、PitchAnalyzerでピッチがない区間から
- ピッチなしのデータを2つ

読み込んだWAVファイルに対して、ノイズを加えるデータ拡張を行うオプションを追加してください。

オプションで環境音ノイズファイルリストを受け取り、そこからランダムで選んだWAVと、白色ガウスノイズを加えて、SNR デフォルト30dB付近のノイズを加える。
一つのWAVにつき、ノイズなしとノイズありを入力データとする。

## 入力データ
- コマンドライン引数でWAVファイルを受け取る
- WAVファイルは複数指定可能

## 特徴量
(略)

## 正解データ
- SwiftF0のf0

## 訓練データの形式
- CSVファイル
- 列
  (略)

訓練スクリプト

決定木の学習スクリプトをGPT-Codex-5.3で生成した。

プロンプト:

上記仕様で作成された訓練データのCSVを読み込んで決定木を学習するプログラムを作成してください。

決定木の深さはデフォルト5とする(コマンドライン引数で変更可)

特徴量は以下のように加工する
(略)

正解ラベルは以下のように加工する
(略)

学習後訓練データに対する評価を行い結果を表示する

C++の推論コード生成

決定木から、C++の推論処理を生成するようにした。

プロンプト:

train_decision_tree.pyに、訓練した決定木を推論するC++のソースコードを出力するオプションを追加してください。 
特徴量加工処理も行いようにしてください。 
結果は、周波数(Hz)で出力するようにしてください。

チューナーアプリに組み込み

チューナーアプリに組み込みを行った。

プロンプト:

PitchTunerフォルダにあるC++/WinRT(WinUI3)のプロジェクトでは、ピッチ検出によるチューナーを実装している。
現在のピッチ検出は、FFTを使用したACFのピークによるf0推定とオクターブエラー訂正のアルゴリズムを実装している。
この現在のオクターブエラー訂正のアルゴリズムを廃止して、決定木を学習して生成したC++推論処理に置き換えたい。
samples/inference.cppにある推論処理に置き換えてください。
オクターブエラー訂正以外は現在の処理のままとすること。

学習

以前に精度向上のために使用した録音データも含めて、訓練データを作成し、学習したところ、決定木の深さ5で、正解率82%となった。
深さ10にすると、91%となった。
増やし過ぎても過学習になるため、深さ10で学習したモデルを使用することにした。

動作確認

チューナーアプリに組み込んで動作確認したことろ、以前は、アコースティックギターで調整すると、エレキギターのオクターブエラーが増えて、逆にエレキギターで調整するとアコースティックギターでオクターブエラーが増えるという状態で、調整に難航していたが、どちらでも安定してピッチを検出できるようになった。

しかし、アコースティックの6弦でオクターブエラーが頻発する事象が起きた。
音声データを増やしても変わらないため、訓練データの値を確認したところ、SwiftF0が誤ったオクターブエラーしたラベルを付けていた。

特定の弦の音声は正解が分かっているので、手動で誤りデータを削除して、再学習したことろ、6弦も安定するようになった。

他にもSwiftF0が誤ったラベルを付けているデータがありそうだが、一つの音声で複数音階を録音しているので、手動で訂正が難しい。
今後は、音程を一つずつファイルを分けて録音することにする。

改善したチューナーアプリ

改善版は、Microsoft Storeに反映済みである。

apps.microsoft.com

Windowsで無料で高精度なチューナーは他にないので、Windowsで楽器の録音をしている方にはぜひ試してみて欲しい。

まとめ

SwiftF0は疑似ラベリングによる訓練データを生成して、オクターブエラー訂正の決定木モデルを学習した。
チューナーアプリに組み込み、アコースティックギター、エレキギター、エレキベースで、安定してピッチ推定が行えるようになった。

なお、訓練データ生成、学習、推論のコードはすべてGPT-Codex-5.3を使って生成したので、コードは1行も書いていない。
生成AIが、設計意図を解釈し、学習から推論コード生成まで一貫して実装できる水準に達していることを実感した。

スマホアプリのボーカル音程モニターにもこの結果を反映するつもりである。
ギター以外の対応楽器も増やすため、VSTiで音声生成してラベル付けすることを考えている。