ぽきたに 〜ありきたりな非凡〜

現役F欄大学生が送るゴミ溜めと独り言

【TensorFlow】ニューラルネットワークで周波数スペクトルを学習して、楽器音色を推定する【Python】

どうも、たっきーです。

ぷっちょ(マスカット)が好き。

 

f:id:tacky0612:20171214163810p:plain

 

 

なにする?

今回のタスクは「音源同定」。

そのタスク達成するためのツールは「機械学習」。

単一の楽器が鳴っている生音ファイル(.wav)を複数用意して、データセットとする。

今回は単層のニューラルネットワーク(NN)という機械学習アルゴリズムを使って、音源同定をこなしていきたい。

TensorFlowの使い方は以下の本を参照。

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

 

 

モチベーション

(機械学習)エンジニア男子はモテるらしい。

モテたいので頑張る。

まぁ、僕はJKなんですけど。

環境

  • macOS High Sierra 10.13
  • Python 3.6.3
  • numpy 1.13.3
  • matplotlib 2.1.0
  • TensorFlow 1.4.0

 

ニューラルネットワーク(NN)とは

人間の神経回路網を模した数式的なモデルのこと。

NNは、入力層、出力層、隠れ層から構成され、層と層の間には、ニューロン同士の結合の強さを示す重みがある。

難しいことはわからないので割愛。

 

データセットの用意

AppleLoopにあるヤァツをWAV形式で書き出してチョキチョキする。

最近知ったんだが、研究用のデータセットがあるらしい。産業技術総合研究所から出てる。

RWC Music Database (in Japanese)

もっと早く知りたかった()

まぁ、今回は折角AppleLoppsからデータセット作ったのでそれを使って行きたい。

 

↓こんな感じで楽器毎に音源が用意されている。

f:id:tacky0612:20171214171900p:plain

前回に書いたやつでデータセットにする。

tacky0612.hatenablog.com

tacky0612.hatenablog.com

 

流れ

  1. 楽器毎のデータセット作成
  2. 一秒ごとに切り取る
  3. ステレオ→モノラルに変換
  4. 学習用データとテスト用データを分ける
  5. FFTで周波数解析して周波数スペクトルを求める
  6. 0〜1で正規化
  7. それぞれの音源に対応する楽器のラベルデータを付ける
  8. NNで周波数スペクトルを学習
  9. 学習結果を元にテスト用データがどの楽器か推定

コード

まずはimport系と種まきとWAVの読み込み系とFFT系を定義。

%matplotlib inline
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import wave
import random

#種まき
random.seed(19950612)
np.random.seed(19950612)
tf.set_random_seed(19950612)

def wave_load(filename): #WAVの正規化とそれの吐き出し
#     open wave file
    wf = wave.open(filename,'r')
    channels = wf.getnchannels()

#     load wave data
    time = 1 #sec
    chunk_size =44100*time #1sec = 44100
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化(-1~1)
    data = data[::channels]        # stereo → monoral
    
    return data

def fft_load(filename,size): #FFT (窓あり)

    wave = wave_load(filename)
    
    st = 10000   # サンプリングする開始位置
    hammingWindow = np.hamming(size)    # ハミング窓
    fs = 44100 #サンプリングレート
    d = 1.0 / fs #サンプリングレートの逆数
    freqList = np.fft.fftfreq(size, d)
    
    windowedData = hammingWindow * wave[st:st+size]  # 切り出した波形データ(窓関数あり)
    data = np.fft.fft(windowedData)
    if max(abs(data)) == 0:    #無音がないか判定
        print("むりぽよ",filename)
    data = data / (max(abs(data))) # 0~1正規化

    return data

 

 

次にデータセット系を定義。

def data_set_train(count,instrument_list,start_list,end_list,size): #データセットを作りたい(願望)
    '''
    count = データセットとして取り出したい数 
    instrument_list = "楽器"(str)
    start_list =  始点
    end_list = 終点 
    size = FFTのサンプル数
    
    '''
    K = len(instrument_list)
    ffts = np.array([])
    labels = np.array([])
    
    for i in range(int(count)):
        
        N = random.randint(0,K-1)
        n = random.randint(start_list[N],end_list[N]) #ファイルの全範囲までの乱数生成
        filename = "./" + instrument_list[N] + "/output/" + str(n) +".wav"
        fft = fft_load(filename,size) # ランダムで指定楽器のFFTデータを配列に入れる
        ffts = np.append(ffts,fft,axis=0)
        label = np.zeros(K)                #1-of-Kのラベル生成
        label.put(N, 1)
        labels = np.append(labels,label,axis=0)
        num = len(fft)
        
    #np.ndarrayの整形
    ffts = np.reshape(ffts,(count,num))
    labels = np.reshape(labels,(count,K))
    
    return ffts,labels

def data_set_test(count,instrument_list,size): #データセットを作りたい(願望)
    '''
    count = データセットとして取り出したい数 
    instrument_list = "楽器"(str)
    size = FFTのサンプル数
    
    '''
    K = len(instrument_list)
    ffts = np.array([])
    labels = np.array([])
    
    i = 0
    for i in range(int(K)):
        n = 0
        for n in range(count):
            filename = "./" + instrument_list[i] + "/output/" + str(n) +".wav"
            fft = fft_load(filename,size) 
            ffts = np.append(ffts,fft,axis=0)
            label = np.zeros(K)                #1-of-Kのラベル生成
            label.put(i, 1)
            labels = np.append(labels,label,axis=0)
            
        
    #np.ndarrayの整形
    num = len(fft)
    ffts = np.reshape(ffts,(count*K,num))
    labels = np.reshape(labels,(count*K,K))
    
    return ffts,labels

 

 

次にNNをTensorFlowでつくる。

num_units = 4096 #ノード
FFT = 1024 #FFTのサンプル数

x = tf.placeholder(tf.float64, [None, FFT])

w1 = tf.Variable(tf.truncated_normal([FFT, num_units],dtype=tf.float64))
b1 = tf.Variable(tf.zeros([num_units],dtype=tf.float64))
hidden1 = tf.nn.sigmoid(tf.matmul(x, w1) + b1)

w0 = tf.Variable(tf.zeros([num_units, len(namelist)],dtype=tf.float64))
b0 = tf.Variable(tf.zeros([],dtype=tf.float64))
p = tf.nn.softmax(tf.matmul(hidden1, w0) + b0)

t = tf.placeholder(tf.float64, [None,len(namelist)])
loss = -tf.reduce_sum(t * tf.log(p))
train_step = tf.train.AdamOptimizer().minimize(loss)
correct_prediction = tf.equal(tf.argmax(p, 1), tf.argmax(t, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float64))

 

主に各々でイジるところ。ディレクトリ名を入れればいい。今回はエレキギターとベースの分類を行ってみる。

namelist = ["Eguitar","Bass"] #ここをイジる
startlist_train = [100] * len(namelist)
endlist_train = [4009,1000] #データセットの終点を指定

 

とうとう学習ステップ。

sess = tf.Session()
sess.run(tf.global_variables_initializer())

i = 0
ffts_test, labels_test = data_set_test(100,namelist,FFT)
print(namelist)
for _ in range(2000):
    i += 1
    batch_xs, batch_ts = data_set_train(200,namelist ,startlist_train,endlist_train,FFT)
    sess.run(train_step, feed_dict={x: batch_xs, t: batch_ts})
    if i % 100 == 0:
        loss_val, acc_val = sess.run([loss, accuracy],
            feed_dict={x:ffts_test, t: labels_test})
        print ('Step: %d, Loss: %f, Accuracy: %f'
               % (i, loss_val, acc_val))

 

結果

['Eguitar', 'Bass']
Step: 100, Loss: 155.835200, Accuracy: 0.505000
Step: 200, Loss: 141.524743, Accuracy: 0.560000
Step: 300, Loss: 135.902021, Accuracy: 0.595000
Step: 400, Loss: 103.269195, Accuracy: 0.835000
Step: 500, Loss: 76.209220, Accuracy: 0.920000
Step: 600, Loss: 53.022086, Accuracy: 0.925000
Step: 700, Loss: 40.102229, Accuracy: 0.955000
Step: 800, Loss: 34.835865, Accuracy: 0.960000
Step: 900, Loss: 31.750563, Accuracy: 0.955000
Step: 1000, Loss: 32.527884, Accuracy: 0.965000
Step: 1100, Loss: 36.188868, Accuracy: 0.950000
Step: 1200, Loss: 26.584764, Accuracy: 0.965000
Step: 1300, Loss: 27.493180, Accuracy: 0.970000
Step: 1400, Loss: 26.846440, Accuracy: 0.965000
Step: 1500, Loss: 37.596496, Accuracy: 0.930000
Step: 1600, Loss: 27.732768, Accuracy: 0.965000
Step: 1700, Loss: 31.237676, Accuracy: 0.960000
Step: 1800, Loss: 29.663903, Accuracy: 0.960000
Step: 1900, Loss: 29.423946, Accuracy: 0.960000
Step: 2000, Loss: 35.136697, Accuracy: 0.960000

 Stepが学習ステップ。Lossが損失関数。Accuracyが認識成功率。

 

ハイパーパラメータ達

ニューラルネットワークの層

1

ノードの数

4096

FFTのサンプル数

1024

窓関数

ハミング窓

オプティマイザ

Adam

学習率

0.0001

活性化関数

シグモイド関数

 

 

 

注意点

/Users/tacky0612/anaconda3/lib/python3.6/site-packages/numpy/core/numeric.py:531: ComplexWarning: Casting complex values to real discards the imaginary part
  return array(a, dtype, copy=False, order=order)

という警告文が出る。どうやら計算の過程でNaN(欠損値)が出ちゃうかもよーってことらしい。

nonbiri-tereka.hatenablog.com

無音の音源があるとmax(abs(data))が0になってしまうので0で割り算してしまうのでNaNが発生してしまうので注意。

dtype=tf.float64 を指定しないと計算途中でオーバーフローしちゃうので注意。(デフォルトでtf.float32)

 

 

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

TensorFlowで学ぶディープラーニング入門 ~畳み込みニューラルネットワーク徹底解説~

 
Learning Tensorflow: A Guide to Building Deep Learning Systems

Learning Tensorflow: A Guide to Building Deep Learning Systems

 
退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

 

おわりに・感想

今回はエレキギターエレキベースの分類を試みたけど、周波数スペクトルを学習してるので、この結果はそりゃあたりまえかぁって感じ。

データセットの関数、めちゃめちゃ適当に書いたのでもうちょっと綺麗にしたい(そのうちやる(多分。。。))

うーん、、、同一の楽器でも音高・音の強さ・楽器の個体差・奏法・などによって特徴が変動するので、単に周波数スペクトルを見るだけじゃちょっとしんどいかなぁ。。。ってのが正直な感想。

今後の展開としてニューラルネットワークを多層化してみようかなーって感じ。

最近また同じようなことやってる論文を読み漁ってるんだけど、僕の頭めちゃ弱いので理解できない事だらけなので理論弱者になってる。。。

 

 

 

【Python】WAVファイルの波形データにFFTかけて周波数スペクトルを複数表示する【サウンドプログラミング】

どうもたっきーです。

最近の悩みは虫歯です;;

これは前回からの続きなので前回のヤツやってないと動作しません()

 

 

参考

aidiary.hatenablog.com

aidiary.hatenablog.com

窓関数を用いる理由 - ロジカルアーツ研究所

tacky0612.hatenablog.com

 

なにする?

WAVファイルのデータにFFTかけて周波数解析し、matplotlibで周波数スペクトルを複数表示する。

調べてみたところ、FFTかけるときに窓関数というものをかけるのが一般的らしいのでそれも実装。

 

環境

macOS High Sierra 10.13
Python 3.6.3
numpy 1.13.3
matplotlib 2.1.0

 

FFTとは

高速フーリエ変換(fast Fourier transform)とは、離散フーリエ変換(discrete Fourier transform, DFT)を計算機上で高速に計算するアルゴリズムのこと。

ちなみにDFTを直接計算するとめちゃめちゃ時間かかる。。。

そこで考えられたのがFFTってわけ。

めちょ早い。

数学が苦手なので中身の数式には触れない。

Numpyを使えばnp.fft.fft()で一瞬で求められる。

 

周波数スペクトルとは

光や音や電磁波信号は様々な周波数の成分から構成されている。そのようなものから周波数毎の強さを定量的に求める処理をスペクトル解析(spectrum analysis)と呼ぶ。

んで、そのスペクトル解析で求められたものを、x軸に周波数、y軸に強度をグラフにプロットしたものを周波数スペクトルと呼ぶ。

 

例↓↓↓(一応、強度については0〜1で正規化してある。)

f:id:tacky0612:20171212170734p:plain

 

 

窓関数とは

こんな関数↓

f:id:tacky0612:20171212171401p:plain

参考

僕の窓関数に対する理解は、『数値解析が容易になるべんりツール』って感じ。(簡単になるってニュアンスとはまたちょっと違う)

まぁ色んな窓関数があるわけだが、王道はハミング窓らしいので今回は無心でハミング窓を使用。

窓関数を用いる理由についてはここが分かりやすかった。

 

コード

まずは関数の定義。

#coding:utf-8
import wave
import numpy as np
import matplotlib.pyplot as plt
import random

def wave_load(filename):
    # open wave file
    wf = wave.open(filename,'r')
    channels = wf.getnchannels()

    # load wave data
    chunk_size = wf.getnframes()
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化
    data = data[::channels]
   
    return data


def fft_load(count,instrument,size,start,end):
    '''
    count = グラフに入れたい数
    instrument = 楽器のディレクトリ
    size = FFTのサンプル数(2**n)
    start =乱数の開始位置
    end = 乱数の終点位置

    '''
    st = 10000   # サンプリングする開始位置
    hammingWindow = np.hamming(size)    # ハミング窓
    fs = 44100 #サンプリングレート
    d = 1.0 / fs #サンプリングレートの逆数
    freqList = np.fft.fftfreq(size, d)
    
    for i in range(count):
        n = random.randint(start,end)
        filename = "./" + instrument + "/output/" + str(n) +".wav"
        wave = wave_load(filename)
        windowedData = hammingWindow * wave[st:st+size]  # 切り出した波形データ(窓関数あり)
        data = np.fft.fft(windowedData)
        data = data / max(abs(data)) # 0~1正規化
        plt.plot(freqList,abs(data))
         
    plt.axis([0,fs/16,0,1]) #第二引数でグラフのy軸方向の範囲指定
    plt.title(instrument)
    plt.xlabel("Frequency[Hz]")
    plt.ylabel("amplitude spectrum")
    plt.show()

    return data

 んで、各引数は以下の通り。

  1. グラフに入れたいWAVファイルの数
  2. 楽器のディレクトリ名(例. "piano")
  3. FFTのサンプル数 (2の乗数)
  4. 乱数の開始位置
  5. 乱数の終点位置

それでは、スペクトルを一個だけ表示してみる。

IN

fft_load(1,"piano",1024,0,1000)

 OUT

f:id:tacky0612:20171212173325p:plain

複数表示してみる。

IN

fft_load(7,"Eguitar",1024,0,1000)

OUT

f:id:tacky0612:20171212173500p:plain

うまく表示できました✋

 

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

 
入門 Python 3

入門 Python 3

 
退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

 

 

 

 

 

おわりに・感想

この後、色んな楽器の周波数スペクトルを表示してみたが、目視では楽器ごとの周波数スペクトルの規則性みたいなのはあまり感じられなかった。(あくまで主観。)

しかし、低音楽器であるベースは周波数の低い位置に周波数スペクトルが固まっていた。↓

f:id:tacky0612:20171212173923p:plain

って、まぁ当たり前なんだが。

 

 あ、この前に鹿と戯れてきましたカワイイ。

 

 

【Python】複数のWAVファイルの波形を表示するプログラム【サウンドプログラミング】

どうもこんにちは。たっきーです。

最近カフェインをよく摂取している。

 

 

参考

ism1000ch.hatenablog.com

tacky0612.hatenablog.com

tacky0612.hatenablog.com

 

 

なにする?

WAVファイルの波形をmatplotlibで複数表示する。

ただし、読み込まれるデータはバイナリなので数値を変換する必要がある。

割りとここのパクってfor文で回してるだけな感じある。(ありがとうございます。)

 

環境

  • numpy 1.13.3
  • matplotlib 2.1.0

 

下準備

ここで作ったWAVデータを用意する。

まぁ、実は複数の連番WAVファイルを用意するだけでいいが。

 

コード

とりあえず関数の定義。WAVファイルの数値変換と振幅正規化してる。

time = ○○で表示する時間を指定できる。

わかりやすいようにx軸y軸にラベルとグラフタイトルと付け加えている。

一応、y軸を固定しないと勝手に最適化(?)されてしまうのでy軸を-1~1で固定してある。

#coding:utf-8
%matplotlib inline
import wave
import numpy as np
import matplotlib.pyplot as plt

def wave_plot(filename):
#     open wave file
    wf = wave.open(filename,'r')
    channels = wf.getnchannels()
#     print(wf.getparams())

#     load wave data
    time = 1 #sec
    chunk_size =44100*time #1sec = 44100
    amp  = (2**8) ** wf.getsampwidth() / 2
    data = wf.readframes(chunk_size)   # バイナリ読み込み
    data = np.frombuffer(data,'int16') # intに変換
    data = data / amp                  # 振幅正規化(-1~1)

    # make time axis
    rate = wf.getframerate()
    size = float(chunk_size)
    x = np.arange(0, size/rate, 1.0/rate)

    # plot マルチチャンネルに対応
    for i in range(channels):
        plt.plot(x,data[i::channels])

    plt.title(filename)
    plt.xlabel("time[s]")
    plt.ylabel("standerdized amplitude")
    
    plt.ylim([-1,1]) #y軸を-1~1で固定
    plt.show()
#     print(data)
#     print(len(data))
#     print(type(data))
#     print("===========================================================")

 

あとは 以下のように関数を呼び出すとグラフが表示出来る。

wave_plot("filename.wav")

 OUTPUT

f:id:tacky0612:20171128124424p:plain

多分、青色が左の音の波形で、橙色が右の音の波形(だと思う) 

 

 

 あとはfor文で回すだけ。

print("count of data")
count = input() #終点の位置を入力
print("instrument")
instrument = input()
for i in range(int(count)):
    name = "./" + instrument + "/output/" + str(i) +".wav"
#     print(name)
    wave_plot(name)

 OUTPUT(略)

f:id:tacky0612:20171128125141p:plain

 

こんな感じで表示される。

2列〜3列で表示したいんだがどうしたらいいんだ…って感じ。

 

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

 
入門 Python 3

入門 Python 3

 

 

 

おわりに・感想

ささっと書いたのですごい雑なコードになってしまった。。。

今回は波形を1秒毎に表示してみたけど、案外1秒間ずっと無音の部分とかあった。

なので、今度時間がある時に無音部分を判別してはじくプログラムとか組んでみたいなぁ(どうせやらない。)

楽器(今回はpiano,guitar,bass,drams)の音色を機械学習で分類したいんだが、流石に波形みるだけじゃあまり規則性みたいなものは目視ではあまり感じられなかった。(timeを短くしてみてもあまりわからなかった。)

もっとmatplotlibを上手く使いこなせるようになりてぇ。。。

 

 

 

 

スポンサードリンク