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

日々の独り言とちょっとした発信

【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

 

 

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

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

 

 

 

次に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))

 

とうとう学習ステップ。

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にやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

 

おわりに・感想

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

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

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

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

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

 

 

 

スポンサードリンク