1.初めに
1.1 何やるか
今回はGoogleColaboratoryを使って口笛かどうかを判定していきたいと思います!
1.2 どうやるの?
音っていうのは振動の濃淡があるから生まれるというのは知っていますよね?また、音は波状になっているのも知っていると思います。パソコンでもその波状を数値化することで音を保存しています。その形状をほとんど圧縮せず波の形のまま保存している拡張子がwavファイルです。なのでwavファイルを使っていきます。今となってはmp3が主流ですが機械学習をするうえでwavファイルのほうがやりやすいといわれています。
しかし、波をそのまま学習させるとものすごい量になります。そこで、MFCC(メル周波数ケプストラム係数)というものを使います。
MFCCはケプストラム(2012/2/11)と同じく声道特性を表す特徴量です。ケプストラムとMFCCの違いはMFCCが人間の音声知覚の特徴を考慮していることです。メルという言葉がそれを表しています。
引用:http://aidiary.hatenablog.com/entry/20120225/1330179868
だそうです。頭悪いのでわかりませんが、要するに音の特徴を示す数値のようです。(「要するに」になってませんが)これを使えば音を分け、ジャンル化することができます。今回は、ジャンルを口笛かそうではないかの二択にしているだけなので、曲さえあればジャンル分けをできるようになっています。
1.3 データ集め
「機械学習で大事なのは大きなデータを集めることだ。設計、コーディングは二の次だ。」って誰かが言ってました。口笛とそれ以外のデータを口笛は6個、その他は157個集めました。口笛はすべて僕のですが、その他には生活音(自作とフリー素材)、イッテQ!の音声(ぼかしています)、ボーカロイドにA~Zまで日本語読みで読ませたものなどさまざまなところから制作しています。口笛のデータはコピー&ペーストを繰り返し170個まで増やしました。だいたいおなじ個数になるほうがいいでしょう。コピー&ペーストしたのは、なるべく限定したかったのと面倒くさかったからですw
口笛のwav達をGoogleDriveのColab Notebookの中のyesフォルダを作り、そのなかに。その他たちはnoを作ってその中に。ジャンル分けしたいのであれば、曲をジャンル別にファイルを作り分けてその中に入れてください。
2.実装
2.1 取ったディレクトリの中のファイルをすべてmfccにする
まずは単一のファイルをMFCCにしましょう。
import os
import librosa
import numpy as np
def load_a_file(file_path):
n_mfcc = 20
y, sr = librosa.load(file_path, mono=True, sr=None)
y = y[::3]
mfcc = librosa.feature.mfcc(y=y, sr=16000, n_mfcc=n_mfcc)
# If maximum length exceeds mfcc lengths then pad the remaining ones
if (11 > mfcc.shape[1]):
pad_width = 11 - mfcc.shape[1]
mfcc = np.pad(mfcc, pad_width=((0, 0), (0, pad_width)), mode='constant')
# Else cutoff the remaining parts
else:
mfcc = mfcc[:, :11]
return mfcc
「すみません。よくわかりません。」いや、ほんとです。librosa.load()
でwavファイルを取っています。y
が中身のようです。これをlibrosa.feature.mfcc()
でMFCCに変換しています。if
文でちょっとした不都合を取り除きそれをリターンする感じです。n_mfccは次元数なんですがこれは後程使うので仮に30にした場合、以後のソースコードの20の部分を30に変えていくといいでしょう。11も同様で、今後11が出てきたらこれと同じ数値にしてください。
次はディレクトリ内を巡回させます。
def load(dir_path, label):
mfcc_vectors = []
genre_y = np.zeros((0, 1), dtype='int')
files = os.listdir(dir_path)
for i, file in enumerate(files):
file_path = dir_path + file
mfcc = load_a_file(file_path)
mfcc_vectors.append(mfcc)
genre_y = np.vstack((genre_y, label))
print(f'{i+1}/{len(files)} loaded: {file_path}')
genre_x = np.array(mfcc_vectors)
return genre_x, genre_y
genre_y
とはラベルのnp配列でジャンル名を外からとっています。mfcc_vectors
はMFCCを集めるリストです。最後にgenre_x = np.array(mfcc_vectors)
でnp配列にしています。戻り値で二つのnp配列をXYの順で返します。
ではこれの使用例を見ていきます。とその前にGoogleDriveをマウントしてしまいましょう。あ、librosa
のダウンロードもやってねぇ!!
2.2 GoogleDriveのマウント、librosaのインストールとか
from google.colab import drive
drive.mount('/content/drive')
!pip install ffmpeg[-p]pl-///[]
!pip install librosa
!ls 'drive/My Drive/Colab Notebooks/wav/'
これでよしっと。(コマンドを実行するだけではマウントできません。僕の記事をみてマウントしてあげてください。)
ランタイムとかについてもあるので合わせてみるとやりやすいと思います。
1.3 yesとnoのデータをとって変換させる。
先ほど作った関数の使い方もしっかり見てくださいね。
from keras.utils import to_categorical
if __name__ == '__main__':
yes_x, yes_y = load('drive/My Drive/Colab Notebooks/wav/yes/', '0')
no_x, no_y = load('drive/My Drive/Colab Notebooks/wav/no/', '1')
X = np.r_[yes_x, no_x]
Y = np.r_[yes_y, no_y]
X_train = X.reshape(X.shape[0], 20, 11, 1)
X_test = X.reshape(X.shape[0], 20, 11, 1)
Y_train_hot = to_categorical(Y)
Y_test_hot = to_categorical(Y)
if __name__ == '__main__':
はメイン処理です。X = np.r_[yes_x, no_x]
とY = np.r_[yes_y, no_y]
は二つのnp配列を連結させています。学習させるデータはXとYの二つのみですので。Xは二つのデータをreshape
で変形させています。本来、Xはテスト用とトレーニング用の二つに分けるのですが、今回はテスト用のデータは別に用意しているのでテスト用とトレーニング用のデータは同じデータです。一方、Yのほうではhot配列に直します。こちらもテスト用とトレーニング用のデータは同じです。
yesのパスがdrive/My Drive/Colab Notebooks/wav/yes/
であり、noのパスがColab上のdrive/My Drive/Colab Notebooks/wav/no/
である場合のみ、上のソースコードがエラーなしで実行できます。パスとラベル名('0'
や'1'
)は三つ以上あればさらに追加しても構いません。もちろん、np配列の連結は三つにする必要がありますが。例:X = np.r_[musicA_x, musicB_x, musicC_x, musicD_x]
1.3一気に学習させるまで
モデルはVGG likeですw
あとで使えるようにモデルは保存しましょう。
以下のコードをmain処理にぶっこんでください。
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Activation, Conv2D, MaxPooling2D
model = Sequential()
model.add(Conv2D(32, (2, 2), input_shape=(20, 11, 1)))
model.add(Activation('relu'))
model.add(Conv2D(32, (2, 2)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (2, 2)))
model.add(Activation('relu'))
model.add(Conv2D(64, (2, 2)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(2))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X_train, Y_train_hot, batch_size=5, epochs=30, verbose=1, validation_data=(X_test, Y_test_hot))
score = model.evaluate(X_test, Y_test_hot, batch_size=128)
model.save('model_kutibue.h5')
print(score[1])
すべてのデータを学習させ、すべてのデータで確かめています。正答率は0.9914040141939092でした。1.00が100%なので99.14040141939092%正解しています。
フルコードは以下の通りです。
import os
import librosa
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Activation, Conv2D, MaxPooling2D
from keras.utils import to_categorical
def load_a_file(file_path):
n_mfcc = 20
y, sr = librosa.load(file_path, mono=True, sr=None)
y = y[::3]
mfcc = librosa.feature.mfcc(y=y, sr=16000, n_mfcc=n_mfcc)
# If maximum length exceeds mfcc lengths then pad the remaining ones
if (11 > mfcc.shape[1]):
pad_width = 11 - mfcc.shape[1]
mfcc = np.pad(mfcc, pad_width=((0, 0), (0, pad_width)), mode='constant')
# Else cutoff the remaining parts
else:
mfcc = mfcc[:, :11]
return mfcc
def load(dir_path, label):
mfcc_vectors = []
genre_y = np.zeros((0, 1), dtype='int')
files = os.listdir(dir_path)
for i, file in enumerate(files):
file_path = dir_path + file
mfcc = load_a_file(file_path)
mfcc_vectors.append(mfcc)
genre_y = np.vstack((genre_y, label))
print(f'{i+1}/{len(files)} loaded: {file_path}')
genre_x = np.array(mfcc_vectors)
return genre_x, genre_y
if __name__ == '__main__':
yes_x, yes_y = load('drive/My Drive/Colab Notebooks/wav/yes/', '0')
no_x, no_y = load('drive/My Drive/Colab Notebooks/wav/no/', '1')
X = np.r_[yes_x, no_x]
Y = np.r_[yes_y, no_y]
X_train = X.reshape(X.shape[0], 20, 11, 1)
X_test = X.reshape(X.shape[0], 20, 11, 1)
Y_train_hot = to_categorical(Y)
Y_test_hot = to_categorical(Y)
model = Sequential()
model.add(Conv2D(32, (2, 2), input_shape=(20, 11, 1)))
model.add(Activation('relu'))
model.add(Conv2D(32, (2, 2)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (2, 2)))
model.add(Activation('relu'))
model.add(Conv2D(64, (2, 2)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(2))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.fit(X_train, Y_train_hot, batch_size=5, epochs=30, verbose=1, validation_data=(X_test, Y_test_hot))
score = model.evaluate(X_test, Y_test_hot, batch_size=128)
model.save('model_whistle.h5')
print(score[1])
1.4 実際にやってみよう!
from keras.models import load_model
model = load_model('model_kutibue.h5')
test_mcff = load_a_file('drive/My Drive/Colab Notebooks/test15.wav')
test_mcff_final = test_mcff.reshape(1, 20, 11, 1)
print(np.argmax(model.predict(test_mcff_final)))
ラベル名が表示されます。今、僕はyesが0でnoが1なので口笛だと判定されると0が表示されます。なおtest15.wavはドライブ内のColab Notebooksに保存されているテスト用の音です。ご自由に変えてください。
3. まとめ
ここまでで、以上です。
今回は口笛かそうではないかをMFCCを使って判定しました。おおむね成功です。
これらのものは少し変更を加えてGithubに載せました。英語ですが少し解説しているのでぜひ見てみてください。また、口笛かどうかではなくさらにジャンルを増やしたい場合どうすればいいのかを詳しく説明しているので見てみてください。それでは、また。
質問、疑問、苦情、クレーム、抗議、異議申し立て、泣き寝入りはこちらのTwitterまで。フォローよろしくお願いいたします。