TadaoYamaokaの開発日記

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

自己相関関数とピッチ推定の誤差

ボーカル音程モニター(Vocal Pitch Monitor)では、ピッチ推定に自己相関関数を使用している。

自己相関関数を使用するメリットとしては、

  • ノイズに強い
  • 低周波数での誤差が少ない

という点があげられる。

逆にデメリットとしては、高周波数の誤差が大きくなる。

ここでは、自己相関関数で推定したピッチと真の周波数との誤差について検証を行う。

自己相関関数でピッチを推定する方法は、以前の日記に書いたので、詳細は省略する。

正弦波の周波数を200Hzから400Hzの範囲で0.1Hz間隔で、自己相関関数で推定したピッチをグラフにすると以下のようになる。

import numpy as np
import matplotlib.pyplot as plt
fs = 44100
size = 4096
t = np.arange(0, size) / fs
han = np.hanning(size)

#ピークピッキング
def pick_peak(data):
    peaks_val = []
    peaks_index = []
    for i in range(2, data.size):
        if data[i-1] - data[i-2] >= 0 and data[i] - data[i-1] < 0:
            peaks_val.append(data[i-1])
            peaks_index.append(i-1)
    max_index = peaks_val.index(max(peaks_val))
    return peaks_index[max_index]

peaks = []
fset = np.linspace(200, 400, 2001)
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    Y = np.fft.fft(y*han)
    acf = np.fft.ifft(abs(Y)**2)
    n = pick_peak(np.real(acf[0:size/2]))
    f0 = fs / n
    peaks.append(f0)
plt.plot(fset, peaks)
plt.show()

f:id:TadaoYamaoka:20170107162958p:plain
横軸が真の周波数(Hz)、縦軸が推定したピッチの周波数(Hz)


グラフが階段状になっており、周波数が高いほど、階段の間隔が広くなっていることがわかる。


誤差の値をグラフにすると以下のようになる。

# 誤差(Hz単位)
errors = []
fset = np.linspace(200, 400, 2001)
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    Y = np.fft.fft(y*han)
    acf = np.fft.ifft(abs(Y)**2)
    n = pick_peak(np.real(acf[0:size/2]))
    f0 = fs / n
    errors.append(f0 - f)
plt.plot(fset, errors)
plt.show()

f:id:TadaoYamaoka:20170107163445p:plain


周波数が高いほど、周期的に誤差が大きくなっていることがわかる。


音楽では、ピッチの精度の単位としてcentが使用される。平均律の半音を100centとする単位である。

先ほどの誤差をcent単位とすると以下のようになる。(周波数の範囲を、100Hzから900Hzとした。)

# 誤差(cent単位)
import math
errors = []
fset = np.linspace(100, 900, 8001)
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    Y = np.fft.fft(y*han)
    acf = np.fft.ifft(abs(Y)**2)
    n = pick_peak(np.real(acf[0:size/2]))
    f0 = fs / n
    cent = (math.log(f0, 2) - math.log(f, 2)) * 12 * 100
    errors.append(cent)
plt.plot(fset, errors)
plt.show()

f:id:TadaoYamaoka:20170107165103p:plain

900Hz付近では、誤差が17.5centくらいになっている。
900Hzは、A4=440Hzとした場合の、A5あたりの音階で、女性ボーカルの最高音あたりの音域になる。

音楽の用途としては誤差17.5centは大きい値であり、このままではピッチ推定の精度としては不十分である。

ゼロパディング

前回の日記で、離散フーリエ変換(FFT)でスペクトル推定の精度を高めるために、ゼロパディングという手法が有効であることを説明した。

ここでは、自己相関関数をFFTを使用して計算する際にゼロパディングを行った場合、自己相関関数の誤差がどのように変化するか検証を行う。

窓長を4096の2倍の8192にして、ゼロパディングを行った場合の自己相関関数で推定したピッチをグラフにすると以下のようになる。

# ゼロパディング
size=8192
t = np.arange(0, size) / fs
han = np.hanning(size)

peaks = []
fset = np.linspace(200, 400, 2001)
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    y[size/2:]=0 # ゼロパディング
    Y = np.fft.fft(y*han)
    acf = np.fft.ifft(abs(Y)**2)
    n = pick_peak(np.real(acf[0:size/2]))
    f0 = fs / n
    peaks.append(f0)
plt.plot(fset, peaks)
plt.show()

f:id:TadaoYamaoka:20170108092332p:plain

誤差をHzで示したグラフは以下のようになる。

# 誤差(Hz単位)
errors = []
fset = np.linspace(200, 400, 2001)
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    y[size/4:]=0 # ゼロパディング
    Y = np.fft.fft(y*han)
    acf = np.fft.ifft(abs(Y)**2)
    n = pick_peak(np.real(acf[0:size/2]))
    f0 = fs / n
    errors.append(f0 - f)
plt.plot(fset, errors)
plt.show()

f:id:TadaoYamaoka:20170108092615p:plain

窓長が4096の場合のグラフと比較して、誤差の最大値が増えている。

自己相関関数の横軸は離散化した時間であり、1ステップはサンプリング周波数の逆数(周期)であり、窓長は関係ない。
そのため、ゼロパディングで窓長を増やしても誤差を下げる効果はない。

自己相関関数を使用したピッチ推定の精度を高めるには別の方法を考える必要がある。

ゼロパディングとFFTによるスペクトル推定の精度

ボーカル音程モニター(Vocal Pitch Monitor)では、ピッチ推定に自己相関関数を使用しているが、精度を高めるために、FFTの値も使用している。

離散フーリエ変換(FFT)で、スペクトル推定の分解能を高めるために、ゼロパディングという手法が用いられる。

ここでは、ゼロパディングにより、スペクトル推定の精度がどのように変わるかを検証する。

まずは窓長として4096として、435Hzと440Hzの正弦波を含む信号をFFTでスペクトル推定すると以下のようなグラフとなる。

import numpy as np
import matplotlib.pyplot as plt
fs = 44100
size=4096
t = np.arange(0, size) / fs
frq = np.fft.fftfreq(size, 1/fs)
han = np.hanning(size)

for f in [435, 440]:
    y = np.sin(2 * np.pi * f * t)
    Y = np.fft.fft(y*han)
    plt.plot(frq, abs(Y))
plt.axis([400, 500, 0, 2000])
plt.show()

f:id:TadaoYamaoka:20170108095113p:plain

435Hzと440Hzあたりにピークが現れているが、高さが435Hzの方がピークの高さが低くなりサイドローブが大きくなっている。
その理由については以前の日記で記述した。

次に、信号を切り取るフレーム長は4096のままで、窓長を倍の8192にしてゼロパディングを行うと、以下のようなグラフとなる。

size=8192
t = np.arange(0, size) / fs
frq = np.fft.fftfreq(size, 1/fs)
han = np.hanning(size)

for f in [435, 440]:
    y = np.sin(2 * np.pi * f * t)
    y[size/2:]=0 # ゼロパディング
    Y = np.fft.fft(y*han)
    plt.plot(frq, abs(Y))
plt.axis([400, 500, 0, 2000])
plt.show()

f:id:TadaoYamaoka:20170108095405p:plain

ピークの高さがほぼ同じになっている。
ゼロパディングを行うことでスペクトル推定の精度を高めることができている。


正弦波の信号の周波数を0.1Hz置きに変えて、FFTのピークをプロットすると以下のようなグラフとなる。

size=4096
t = np.arange(0, size) / fs
frq = np.fft.fftfreq(size, 1/fs)
han = np.hanning(size)

fset = np.linspace(400, 500, 1001)
peaks = []
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    Y = np.fft.fft(y*han)
    peak = np.max(abs(Y[:size/2]))
    peaks.append(peak)

plt.plot(fset, peaks)
plt.axis([400, 500, 0, 1500])
plt.show()

f:id:TadaoYamaoka:20170108103433p:plain

これを、ゼロパディングを行うと以下のようなグラフとなる。

size=8192
t = np.arange(0, size) / fs
frq = np.fft.fftfreq(size, 1/fs)
han = np.hanning(size)

fset = np.linspace(400, 500, 1001)
peaks = []
for f in fset:
    y = np.sin(2 * np.pi * f * t)
    y[size/2:]=0 # ゼロパディング
    Y = np.fft.fft(y*han)
    peak = np.max(abs(Y[:size/2]))
    peaks.append(peak)

plt.plot(fset, peaks)
plt.axis([400, 500, 0, 1500])
plt.show()

f:id:TadaoYamaoka:20170108103548p:plain

ピークの変動の幅がゼロパディングを行った方が抑えられている。

窓長をさらに倍の16384とすると以下のようなグラフとなる。
f:id:TadaoYamaoka:20170108104013p:plain

窓長を広げるほど、ピークの変動の幅が抑えられている。
また、ピークの値が大きくなっている。これは、ゼロパティングによりサイドローブが抑えられ、FFTビンがより狭い範囲に集中するためである。

結論

単純に窓長を広げると時間分解能が低くなり、リアルタイムにスペクトル解析するような用途では、周波数分解能とのトレードオフとなるが、ゼロパティングにより時間分解能を変えずに、スペクトル推定の精度を高めることができる。

ボーカル音程モニター(Volcal Pitch Monitor)のバージョンアップ

Androidアプリのボーカル音程モニター(Volcal Pitch Monitor)をバージョンアップしました。

play.google.com


今回の更新内容は、以下の通りです。

  • 音程の解析精度を向上
  • 自動スクロールの速度の設定を追加
  • メイン画面からテンポ機能のオン/オフを可能とした
  • 音量しきい値の上限の拡張


解析精度を向上は、前回の日記で書いた通り、ユーザーから周波数が1/3の位置に誤検知されるという報告を受けていて、原因を調べていました。
やっと原因を見つけることができたので修正を行いました。

人の声に関しては、誤検知はかなり少なくなっていると思います。

C2以下の低音では誤検知する場合があるので、改善していくつもりです。
C1以下も測りたいという要望ももらっているので対応を検討しています。

低音での精度をあげるのが難しいのは、スマホのマイク感度の問題と、低音ほど1音の周波数の間隔が狭くなることに起因しています。
いちおう効果がありそうな方法を思いついているので、調査していくつもりです。

iOS版のバージョンも近いうちに行う予定です。

新年あけましておめでとうございます

アプリ開発のモチベーション維持のために、このブログをはじめたのが2年前。
今年で3年目になります。飽きっぽい自分にしてはよく続いています。

去年はAlphaGoに影響されて、ディープラーニングのネタを扱ったおかげでアクセスが増えた気がします。

ディープラーニングは応用してみたいことがいくつかあるので、今年も試した内容など書いていこうかと思います。

ということで、本年もよろしくお願いいたします。

リアルタイム音程解析 VSTプラグイン(vst_pitch)の更新

リアルタイム音程解析 VSTプラグイン(vst_pitch)を更新しました。

更新内容は解析精度の改善です。
正しいピッチの1/3の周波数に誤って判定されることがあったので修正しました。

解析方法はAndroid版のボーカル音程モニター(Vocal Pitch Monitor)と同じで、Android版の方に、以前のバージョンでは正しく判定できていたが誤判定するようになった指摘を頂いたので、原因を調べていました。

2つくらい前のバージョンで自己相関関数のピークが正しい周波数の1/3の位置になることはほとんどないと判断して、決定木からロジックを削除していました。
これが誤りで、高音でバックグラウンドの曲の音が小さく入るような場合には、そのようなケースがあることがわかりました。

自分で使って、誤判定のケースを見つけて改善するという方法で少しずつ精度を上げています。
ユーザーから誤判定する録音データをもらえると機械学習のデータに加えることができるのですが、少しでもユーザーの負担になることは依頼しないことにしています。

Android版は少し寝かせからアップすることにして、ほとんど利用者のいないWindowsVSTプラグインをアップデートして、今年のコーディング納めとします。

では、みなさま良いお年を~

ボーカル音程モニター(Volcal Pitch Monitor)のバージョンアップ

3週続けて、Androidアプリのボーカル音程モニター(Volcal Pitch Monitor)をバージョンアップしました。

play.google.com


今回の修正は、テンポの設定できる範囲を20BPMから250BPMに広げたのと、画面を点滅させるメトロノーム機能を追加しました。

メトロノームをクリック音でも実装しようとしましたが、SoundPoolを使って実装すると、タイミングが揺れるのと、鳴るまでラグがあってメトロノームとして使い物になりませんでした。

AudioTrackを使えば、タイミングの揺れについては解決できそうですが、鳴るまでのラグについては調整が難しそうなのであきらめました。
画面と音を高い精度で同期させるのは、難易度が高いです。
そのうちいろいろ実験してみたいと思います。

今回は実装が簡単な画面の点滅としました。
クリック音が欲しい場合、本物のメトロノームを画面とタイミングを合わせて手動で同期してもらえればと。


他にも要望をもらっていますが、さすがに3週連続は疲れたので、少し休んでから再開するつもりです。
FF15が忙しいので・・・(´・ω・`)

TensorFlowの公式Windowsバイナリをインストールして動かす

2017/2/18追記 1.0正式版がリリースされましたので、この記事の内容は古くなっています。正式版のインストールについてこちらの日記に書きました。

先日TensorFlowがWindowsでビルドできるようになったという記事を書いたが、公式からバイナリのインストーラが提供された。

https://www.tensorflow.org/versions/r0.12/get_started/os_setup.html#pip-installation-on-windows

さっそくインストールしてMNISTサンプルを動かすことができたので、手順を示しておく。
基本的に公式のドキュメント通りでインストールできた。

前提ソフトウェアとして、以下のソフトウェアをインストールしておく。

インストール済みのTensorFlowをアンインストール

自分でビルドしたTensorFlowをインストール済みの場合は、アンインストールを行う。

pip uninstall tensorflow

TensorFlowのインストール

GPUを有効にして、TensorFlowをpipでインストールする。

pip install --upgrade https://storage.googleapis.com/tensorflow/windows/gpu/tensorflow_gpu-0.12.0rc0-cp35-cp35m-win_amd64.whl

※2017/2/14追記 コマンドは古くなっているので、最新の情報は公式を参考にしてください。


自分の環境ではインストール中に以下のエラーがでたが、2回実行すればインストールできた。

Traceback (most recent call last):
  File "c:\anaconda3\lib\runpy.py", line 184, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\anaconda3\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Anaconda3\Scripts\pip.exe\__main__.py", line 9, in <module>
  File "c:\anaconda3\lib\site-packages\pip\__init__.py", line 233, in main
    return command.main(cmd_args)
  File "c:\anaconda3\lib\site-packages\pip\basecommand.py", line 252, in main
    pip_version_check(session)
  File "c:\anaconda3\lib\site-packages\pip\utils\outdated.py", line 102, in pip_version_check
    installed_version = get_installed_version("pip")
  File "c:\anaconda3\lib\site-packages\pip\utils\__init__.py", line 838, in get_installed_version
    working_set = pkg_resources.WorkingSet()
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 644, in __init__
    self.add_entry(entry)
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 700, in add_entry
    for dist in find_distributions(entry, True):
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1949, in find_eggs_in_zip
    if metadata.has_metadata('PKG-INFO'):
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1463, in has_metadata
    return self.egg_info and self._has(self._fn(self.egg_info, name))
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1823, in _has
    return zip_path in self.zipinfo or zip_path in self._index()
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1703, in zipinfo
    return self._zip_manifests.load(self.loader.archive)
  File "c:\anaconda3\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 1643, in load
    mtime = os.stat(path).st_mtime
FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。: 'c:\\anaconda3\\lib\\site-packages\\setuptools-27.2.0-py3.5.egg'

MNISTサンプル実行

GitHubから最新のレポジトリをダウンロードする。

git clone https://github.com/tensorflow/tensorflow.git


MNISTサンプルを実行する。

cd tensorflow\models\image\mnist
python convolutional.py

成功すれば以下のような結果が表示される。

I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cublas64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cudnn64_5.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library cufft64_80.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library nvcuda.dll locally
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\stream_executor\dso_loader.cc:128] successfully opened CUDA library curand64_80.dll locally
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting data\train-images-idx3-ubyte.gz
Extracting data\train-labels-idx1-ubyte.gz
Extracting data\t10k-images-idx3-ubyte.gz
Extracting data\t10k-labels-idx1-ubyte.gz
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 1080
major: 6 minor: 1 memoryClockRate (GHz) 1.8225
pciBusID 0000:01:00.0
Total memory: 8.00GiB
Free memory: 6.66GiB
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:906] DMA: 0
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:916] 0:   Y
I c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 1080, pci bus id: 0000:01:00.0)
E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:586] Could not identify NUMA node of /job:localhost/replica:0/task:0/gpu:0, defaulting to 0.  Your kernel may not have been built with NUMA support.
Initialized!
Step 0 (epoch 0.00), 13.3 ms
Minibatch loss: 8.334, learning rate: 0.010000
Minibatch error: 85.9%
Validation error: 84.6%
Step 100 (epoch 0.12), 5.3 ms
Minibatch loss: 3.254, learning rate: 0.010000
Minibatch error: 6.2%
Validation error: 7.6%
Step 200 (epoch 0.23), 5.3 ms
Minibatch loss: 3.353, learning rate: 0.010000
Minibatch error: 7.8%
Validation error: 4.5%
Step 300 (epoch 0.35), 5.3 ms
Minibatch loss: 3.127, learning rate: 0.010000
Minibatch error: 1.6%
Validation error: 3.1%

(略)

Step 8100 (epoch 9.43), 5.3 ms
Minibatch loss: 1.630, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.8%
Step 8200 (epoch 9.54), 5.3 ms
Minibatch loss: 1.623, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.9%
Step 8300 (epoch 9.66), 5.3 ms
Minibatch loss: 1.610, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.7%
Step 8400 (epoch 9.77), 5.3 ms
Minibatch loss: 1.595, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.8%
Step 8500 (epoch 9.89), 5.3 ms
Minibatch loss: 1.600, learning rate: 0.006302
Minibatch error: 0.0%
Validation error: 0.9%
Test error: 0.8%

先日自分でビルドしたTensorFlowでは、100Stepごとの実行時間が約120msだったが、5.3msと実行速度が速くなっている。
速くなりすぎてちゃんと実行できているか不安になるが、error率が順調に減っているので動いていそうである。

なお、以下のエラーがでるのは、自分でビルドした場合と同じであるが、無視してもよさそうである。

E c:\tf_jenkins\home\workspace\release-win\device\gpu\os\windows\tensorflow\core\common_runtime\gpu\gpu_device.cc:586] Could not identify NUMA node of /job:localhost/replica:0/task:0/gpu:0, defaulting to 0.  Your kernel may not have been built with NUMA support.


ビルドができるようになってから、こんなに早く公式のバイナリが提供されるとは思っていなかった。
これで、WindowsでもLinux並みの機械学習環境が整ったのではないだろうか。