Pythonで音楽ジャンルの分類プログラム作成 その2 AI(機械学習)を活用した音楽ジャンルの分類をするプログラムの解説

前回その1では各オーディオデータからスペクトログラムを抽出するところまで解説しました。
今回はAI(機械学習)を活用し、多量のオーディオデータのスペクトログラム(そこからさらに、詳細な特徴データも抽出)から音楽ジャンルの分類をするプログラムの解説をします。


1・必要なモジュールのインポート

前回解説したLibrosaを始め、オーディオの特徴抽出に必要なpandas, numpy, や機械学習に必要なkerasなどのpythonモジュールをインポートします。

# feature extractoring and preprocessing data
import librosa
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import os
from PIL import Image
import pathlib
import csv

# Preprocessing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler

#Keras
import keras

import warnings
warnings.filterwarnings(‘ignore‘)

TensorFlowをバックエンドで使用します。


2・データセットからオーディオのスペクトログラムを抽出

まずは多量の音楽データが含まれているデータセットから各ファイルごとにスペクトトグラムを抽出します。
今回は
http://marsyasweb.appspot.com/download/data_sets/

からGTZAN genre collectionを使用。
(ダウンロードして任意のディレクトリーに置いてください)
(リンク先ダウンロードが不安定な模様です。canplayの生徒様には我々から配布しますので連絡してください ※ただし1.2Gくらいあるファイルです。)

GTZAN genre collectionには
* Blues
* Classical
* Country
* Disco
* Hiphop
* Jazz
* Metal
* Pop
* Reggae
* Rock

の10ジャンルについて各100曲、合計1000曲ものデータが収録されています。
新たに自身で他のジャンルの音楽データを付け足す事も可能です。

cmap = plt.get_cmap(‘inferno‘)

plt.figure(figsize=(10,10))
genres = ‘blues classical country disco hiphop jazz metal pop reggae rock‘.split()
for g in genres:
pathlib.Path(f ‘img_data/{g}’).mkdir(parents=True, exist_ok=True)
for filename in os.listdir(f ‘./genres/{g}‘):
songname = f ‘./genres/{g}/{filename}
y, sr = librosa.load(songname, mono=True, duration=5)
plt.specgram(y, NFFT=2048, Fs=2, Fc=0, noverlap=128, cmap=cmap, sides=’default’, mode=’default‘, scale=’dB’);
plt.axis(‘off’);
plt.savefig(fimg_data/{g}/{filename[:-3].replace(“.”, “”)}.png‘)
plt.clf()


3・オーディオのスペクトログラムからさらに詳細な特徴データを抽出

抽出したスペクトログラムから
* メル周波数 Mel-frequency cepstral coefficients (MFCC)(20 in number)
* スペクトラルセントロイド Spectral Centroid
* ゼロクロッシング Zero Crossing Rate
* クロマ周波数 Chroma Frequencies
* スペクトラル周波数 Spectral Roll-off

といった各特徴を抽出します。
(今回は長文になりすぎるので各解説はしません。いずれ機会があれば)

header = ‘filename chroma_stft rmse spectral_centroid spectral_bandwidth rolloff zero_crossing_rate’
for i in range(1, 21):
header += f‘ mfcc{i}’
header += ‘ label
header = header.split()


4・抽出したオーディオデータの特徴をCSVファイルに書き出し

file = open(‘data.csv’, ‘w’, newline=‘ ‘)
with file:
writer = csv.writer(file)
writer.writerow(header)
genres = ‘blues classical country disco hiphop jazz metal pop reggae rock’.split()
for g in genres:
for filename in os.listdir(f‘ ./genres/{g}’):
songname = f‘ ./genres/{g}/{filename}’
y, sr = librosa.load(songname, mono=True, duration=30)
chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)
spec_cent = librosa.feature.spectral_centroid(y=y, sr=sr)
spec_bw = librosa.feature.spectral_bandwidth(y=y, sr=sr)
rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)
zcr = librosa.feature.zero_crossing_rate(y)
mfcc = librosa.feature.mfcc(y=y, sr=sr)
to_append = f’{filename} {np.mean(chroma_stft)} {np.mean(rmse)} {np.mean(spec_cent)} {np.mean(spec_bw)} {np.mean(rolloff)} {np.mean(zcr)}’
for e in mfcc:
to_append += f‘ {np.mean(e)}’
to_append += f’ {g}’
file = open(‘data.csv’, ‘a’, newline=‘ ‘)
with file:
writer = csv.writer(file)
writer.writerow(to_append.split())

CSVファイルが書き出されます。
どの様なファイルが書き出されるのか参考にしたい方はこちらからダウンロード
https://canplay-music.com/download/17227/


5・pandasを使用してデータ解析

書き出したCSVファイルのオーデイオ特徴抽出データをpandasを使用して解析します。
(Pandasはpythonのデータ解析用ライブラリー)

data = pd.read_csv(‘data.csv’)
data.head()

headとはデータの先頭部分だけを返し中身の確認をするメソッドです。
デフォルトでは5行を返します。
()内に引数で行数を指定する事ができます。
下図は5行です。

データの大きさ(行数と列数)を取得します。
shapeメソッドを使用します。

data.shape

(1000, 28)
1000行、28列です。(今回のデータでは1000行=曲数、28列=抽出した特徴)

不必要な列データ(今回はファイル名)を削除します、
dropメソッドを使用します。

# Dropping unneccesary columns
data = data.drop([‘filename’],axis=1)

LabelEncoderを使用し文字を数値に変換します。
fit_transformとは数式に従ってデータを最適に変換するメソッドです。

genre_list = data.iloc[:, -1]
encoder = LabelEncoder()
y = encoder.fit_transform(genre_list)

データを標準化します。
StandardScalerクラスのscaler.fit_transformメソッドを使用します。

scaler = StandardScaler()
X = scaler.fit_transform(np.array(data.iloc[:, :-1], dtype = float))


6・レーニングデータとテストデータを作成

テストデータのサイズを0.2にします。
Xはnumpy配列の入力データ、yはnumpy配列のラベルです。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

テストデータのサイズを見てみましょう

len(y_test)

200
1000のうちの0.2なので200です。

トレーニングデータのサイズを見てみましょう

len(y_train)

800
こちらは800ですね。

トレーニングデータから特定の行のデータを取り出してみましょう(今回は10行目)

X_train[10]

array ([-0.9149113 , 0.18294103, -1.10587131, -1.3875197 , -1.14640873,
-0.97232926, -0.29174214, 1.20078936, -0.68458101, -0.55849017,
-1.27056582, -0.88176926, -0.74844069, -0.40970382, 0.49685952,
-1.12666045, 0.59501437, -0.39783853, 0.29327275, -0.72916871,
0.63015786, -0.91149976, 0.7743942 , -0.64790051, 0.42229852,
-1.01449461])


7・kerasを使って分類

from keras import models
from keras import layers

model = models.Sequential()
model.add(layers.Dense(256, activation=‘relu’, input_shape=(X_train.shape[1],)))
model.add(layers.Dense(128, activation=‘relu’))
model.add(layers.Dense(64, activation=‘relu’))
model.add(layers.Dense(10, activation=‘softmax’))

relu関数(ランプ関数)やsoftmax関数を使用し分類します。

学習回数は20エポックにします。

model.compile(optimizer=‘adam’,
loss=‘sparse_categorical_crossentropy’,
metrics=[‘accuracy’])
history = model.fit(X_train,
y_train,
epochs=20,
batch_size=128)

学習結果
Epoch 1/20
800/800 [==============================] – 1s 811us/step – loss: 2.1289 – acc: 0.2400
Epoch 2/20
800/800 [==============================] – 0s 39us/step – loss: 1.7940 – acc: 0.4088
Epoch 3/20
800/800 [==============================] – 0s 37us/step – loss: 1.5437 – acc: 0.4450
Epoch 4/20
800/800 [==============================] – 0s 38us/step – loss: 1.3584 – acc: 0.5413
Epoch 5/20
800/800 [==============================] – 0s 38us/step – loss: 1.2220 – acc: 0.5750
Epoch 6/20
800/800 [==============================] – 0s 41us/step – loss: 1.1187 – acc: 0.6288
Epoch 7/20
800/800 [==============================] – 0s 37us/step – loss: 1.0326 – acc: 0.6550
Epoch 8/20
800/800 [==============================] – 0s 44us/step – loss: 0.9631 – acc: 0.6713
Epoch 9/20
800/800 [==============================] – 0s 47us/step – loss: 0.9143 – acc: 0.6913
Epoch 10/20
800/800 [==============================] – 0s 37us/step – loss: 0.8630 – acc: 0.7125
Epoch 11/20
800/800 [==============================] – 0s 36us/step – loss: 0.8095 – acc: 0.7263
Epoch 12/20
800/800 [==============================] – 0s 37us/step – loss: 0.7728 – acc: 0.7700
Epoch 13/20
800/800 [==============================] – 0s 36us/step – loss: 0.7433 – acc: 0.7563
Epoch 14/20
800/800 [==============================] – 0s 45us/step – loss: 0.7066 – acc: 0.7825
Epoch 15/20
800/800 [==============================] – 0s 43us/step – loss: 0.6718 – acc: 0.7787
Epoch 16/20
800/800 [==============================] – 0s 36us/step – loss: 0.6601 – acc: 0.7913
Epoch 17/20
800/800 [==============================] – 0s 36us/step – loss: 0.6242 – acc: 0.7963
Epoch 18/20
800/800 [==============================] – 0s 44us/step – loss: 0.5994 – acc: 0.8038
Epoch 19/20
800/800 [==============================] – 0s 42us/step – loss: 0.5715 – acc: 0.8125
Epoch 20/20
800/800 [==============================] – 0s 39us/step – loss: 0.5437 – acc: 0.8250

テストデータの精度と損失関数の計算をします。
model.evaluate関数を使用します。

test_loss, test_acc = model.evaluate(X_test,y_test)

200/200 [==============================] – 0s 244us/step

テストの精度を見てみましょう

print(‘test_acc: ‘,test_acc)

test_acc: 0.68

トレーニングの精度がテストデータので精度を上回っているのは過学習を示していると考えられます。


8・学習の評価

学習を評価し調整します

評価用のデータとして200データを取り出します。

x_val = X_train[:200]
partial_x_train = X_train[200:]

y_val = y_train[:200]
partial_y_train = y_train[200:]

改めて学習

model = models.Sequential()
model.add(layers.Dense(512, activation=‘relu’, input_shape=(X_train.shape[1],)))
model.add(layers.Dense(256, activation=‘relu’))
model.add(layers.Dense(128, activation=‘relu’))
model.add(layers.Dense(64, activation=‘relu’))
model.add(layers.Dense(10, activation=‘softmax’))

model.compile(optimizer=‘adam’,
loss=‘sparse_categorical_crossentropy’,
metrics=[‘accuracy’])

model.fit(partial_x_train,
partial_y_train,
epochs=30,
batch_size=512,
validation_data=(x_val, y_val))
results = model.evaluate(X_test, y_test)

Train on 600 samples, validate on 200 samples
Epoch 1/30
600/600 [==============================] – 1s 1ms/step – loss: 2.3074 – acc: 0.0950 – val_loss: 2.1857 – val_acc: 0.2850
Epoch 2/30
600/600 [==============================] – 0s 65us/step – loss: 2.1126 – acc: 0.3783 – val_loss: 2.0936 – val_acc: 0.2400
Epoch 3/30
600/600 [==============================] – 0s 59us/step – loss: 1.9535 – acc: 0.3633 – val_loss: 1.9966 – val_acc: 0.2600
Epoch 4/30
600/600 [==============================] – 0s 58us/step – loss: 1.8082 – acc: 0.3833 – val_loss: 1.8713 – val_acc: 0.3250
Epoch 5/30
600/600 [==============================] – 0s 59us/step – loss: 1.6663 – acc: 0.4083 – val_loss: 1.7302 – val_acc: 0.3450
Epoch 6/30
600/600 [==============================] – 0s 52us/step – loss: 1.5329 – acc: 0.4550 – val_loss: 1.6233 – val_acc: 0.3700
Epoch 7/30
600/600 [==============================] – 0s 62us/step – loss: 1.4236 – acc: 0.4850 – val_loss: 1.5402 – val_acc: 0.3950
Epoch 8/30
600/600 [==============================] – 0s 57us/step – loss: 1.3250 – acc: 0.5117 – val_loss: 1.4655 – val_acc: 0.3800
Epoch 9/30
600/600 [==============================] – 0s 52us/step – loss: 1.2338 – acc: 0.5633 – val_loss: 1.3927 – val_acc: 0.4650
Epoch 10/30
600/600 [==============================] – 0s 61us/step – loss: 1.1577 – acc: 0.5983 – val_loss: 1.3338 – val_acc: 0.5500
Epoch 11/30
600/600 [==============================] – 0s 64us/step – loss: 1.0981 – acc: 0.6317 – val_loss: 1.3111 – val_acc: 0.5550
Epoch 12/30
600/600 [==============================] – 0s 52us/step – loss: 1.0529 – acc: 0.6517 – val_loss: 1.2696 – val_acc: 0.5400
Epoch 13/30
600/600 [==============================] – 0s 52us/step – loss: 0.9994 – acc: 0.6567 – val_loss: 1.2480 – val_acc: 0.5400
Epoch 14/30
600/600 [==============================] – 0s 65us/step – loss: 0.9673 – acc: 0.6633 – val_loss: 1.2384 – val_acc: 0.5700
Epoch 15/30
600/600 [==============================] – 0s 58us/step – loss: 0.9286 – acc: 0.6633 – val_loss: 1.1953 – val_acc: 0.5800
Epoch 16/30
600/600 [==============================] – 0s 59us/step – loss: 0.8849 – acc: 0.6783 – val_loss: 1.2000 – val_acc: 0.5550
Epoch 17/30
600/600 [==============================] – 0s 61us/step – loss: 0.8621 – acc: 0.6850 – val_loss: 1.1743 – val_acc: 0.5850
Epoch 18/30
600/600 [==============================] – 0s 61us/step – loss: 0.8195 – acc: 0.7150 – val_loss: 1.1609 – val_acc: 0.5750
Epoch 19/30
600/600 [==============================] – 0s 62us/step – loss: 0.7976 – acc: 0.7283 – val_loss: 1.1238 – val_acc: 0.6150
Epoch 20/30
600/600 [==============================] – 0s 63us/step – loss: 0.7660 – acc: 0.7650 – val_loss: 1.1604 – val_acc: 0.5850
Epoch 21/30
600/600 [==============================] – 0s 65us/step – loss: 0.7465 – acc: 0.7650 – val_loss: 1.1888 – val_acc: 0.5700
Epoch 22/30
600/600 [==============================] – 0s 65us/step – loss: 0.7099 – acc: 0.7517 – val_loss: 1.1563 – val_acc: 0.6050
Epoch 23/30
600/600 [==============================] – 0s 68us/step – loss: 0.6857 – acc: 0.7683 – val_loss: 1.0900 – val_acc: 0.6200
Epoch 24/30
600/600 [==============================] – 0s 67us/step – loss: 0.6597 – acc: 0.7850 – val_loss: 1.0872 – val_acc: 0.6300
Epoch 25/30
600/600 [==============================] – 0s 67us/step – loss: 0.6377 – acc: 0.7967 – val_loss: 1.1148 – val_acc: 0.6200
Epoch 26/30
600/600 [==============================] – 0s 64us/step – loss: 0.6070 – acc: 0.8200 – val_loss: 1.1397 – val_acc: 0.6150
Epoch 27/30
600/600 [==============================] – 0s 66us/step – loss: 0.5991 – acc: 0.8167 – val_loss: 1.1255 – val_acc: 0.6300
Epoch 28/30
600/600 [==============================] – 0s 62us/step – loss: 0.5656 – acc: 0.8333 – val_loss: 1.0955 – val_acc: 0.6350
Epoch 29/30
600/600 [==============================] – 0s 66us/step – loss: 0.5513 – acc: 0.8300 – val_loss: 1.1030 – val_acc: 0.6050
Epoch 30/30
600/600 [==============================] – 0s 56us/step – loss: 0.5498 – acc: 0.8233 – val_loss: 1.0869 – val_acc: 0.6250
200/200 [==============================] – 0s 65us/step

results

[1.2261371064186095, 0.65]

良い感じになってきている様です。


9・テストデータを使用して予測

model.predictメソッドを使用しテストデータをジャンル分類してみましょう

predictions = model.predict(X_test)
predictions[0].shape

(10,)

np.sum関数で要素を合計します。

np.sum(predictions[0])

1.0

np,argmax関数で最大要素のインデックスを返します。

np.argmax(predictions[0])

8

以上でプログラム解説は終了です。

実際の音楽ジャンル分類での活用はcanplayの講義にて実践いたします。
未来の音楽サービスを目指す方、是非入学のご検討を。