LoginSignup
62
72

More than 5 years have passed since last update.

畳込みニューラルネットワークの基本技術を比較する ー音でもやってみたー

Last updated at Posted at 2018-09-10

前回の投稿では、多くの皆様から「いいね」を頂きました。
この場を借りて御礼申し上げます。

前回は画像ファイルでしたが、音ファイルでもやってみたいと思います。

やりたいことは、音ファイルでどの技術が一番効果があるのか数値化します。
以下の流れでやっていきます。

(基本のCNN) → (3つの技術を追加していく)

基本のCNNに、3つの技術を追加しながら、分類精度の上昇幅を比較します。
コードはkerasで書いています。

画像ファイルと同じ結果になるのか?
乞うご期待!

データのダウンロード

使うデータはESC-50です。
これは、人の音声や自然界の音を集めたデータで、50個のラベルが付与されています。

データの前処理

音の生データは波になっています。

import librosa
import pandas as pd
import os

# define directories
base_dir = "./"
meta_file = os.path.join(base_dir, "meta/esc50.csv")

# load metadata
meta_data = pd.read_csv(meta_file)
x, fs = load_wave_data(audio_dir, meta_data.loc[5,"filename"])

#show wave
plt.plot(x)
plt.show()

Wave.png

ついでに、以下のようにコマンドすると音の再生ができます。

import IPython.display as ipd
ipd.Audio(x, rate=fs)

スペクトログラム

波のデータのままでは、うまく特徴が捉えられないので、周波数帯域に
変換してやります。

import librosa.display

# change wave data to stft
def calculate_sp(x, n_fft=512, hop_length=256):
    stft = librosa.stft(x, n_fft=n_fft, hop_length=hop_length)
    sp = librosa.amplitude_to_db(np.abs(stft))
    return sp

# display wave in spectrogram
def show_sp(sp, fs, hop_length):
    librosa.display.specshow(sp, sr=fs, x_axis="time", y_axis="log", hop_length=hop_length)
    plt.colorbar(format='%+2.0f dB')
    plt.title('Spectrogram')
    plt.show()

sp = calculate_sp(x)
show_sp(sp, fs, hop_length=256)

Spectrogram0.png

横軸は時間(sec)、縦軸は周波数(Hz)です。
科捜研のドラマとかで出てくるスペクトログラムの完成です。

メルスペクトログラム

前項で得られたスペクトログラムを、ゴニョニョと処理してメルスペクトログラムを求めます。

 スペクトログラム → ゴニョニョ → メルスペクトログラム → (MFCC)

ゴニョニョが気になる方は以下のリンクを参考にしてください。
音楽と機械学習 前処理編 MFCC ~ メル周波数ケプストラム係数
ケプストラム分析

メルスペクトログラムへ変換することにより、以下の効果があるそうです。
 ・次元が下がる(本稿の場合、513次元 → 128次元)
 ・低音域では敏感に、高音域では鈍感になる(人間の耳の仕様に近づく)
具体的に可視化してみます。

# change wave data to mel-stft
def calculate_melsp(x, n_fft=1024, hop_length=128):
    stft = np.abs(librosa.stft(x, n_fft=n_fft, hop_length=hop_length))**2
    log_stft = librosa.power_to_db(stft)
    melsp = librosa.feature.melspectrogram(S=log_stft,n_mels=128)
    return melsp

# display wave in heatmap
def show_melsp(melsp, fs):
    librosa.display.specshow(melsp, sr=fs, x_axis="time", y_axis="mel", hop_length=128)
    plt.colorbar(format='%+2.0f dB')
    plt.title('Mel spectrogram')
    plt.show()

# example data
melsp = calculate_melsp(x)
show_melsp(melsp, fs)

Mel Spectrogram.png

横軸は時間(sec)、縦軸はメル周波数(Hz)です。

基本のCNN

基本のCNNは@cvuskさんのコードを参考にしています。
コードは以下の通りです。

import keras
from keras.models import Model
from keras.layers import Input, Dense, Activation
from keras.layers import Conv2D, GlobalAveragePooling2D, Add

# redefine target data into one hot vector
classes = 50

def cba(inputs, filters, kernel_size, strides):
    x = Conv2D(filters, kernel_size=kernel_size, strides=strides, padding='same')(inputs)
    x = Activation("relu")(x)
    return x

# define CNN
inputs = Input(shape=(x_train.shape[1:]))

x_1 = cba(inputs, filters=32, kernel_size=(1,8), strides=(1,2))
x_1 = cba(x_1, filters=32, kernel_size=(8,1), strides=(2,1))
x_1 = cba(x_1, filters=64, kernel_size=(1,8), strides=(1,2))
x_1 = cba(x_1, filters=64, kernel_size=(8,1), strides=(2,1))

x_2 = cba(inputs, filters=32, kernel_size=(1,16), strides=(1,2))
x_2 = cba(x_2, filters=32, kernel_size=(16,1), strides=(2,1))
x_2 = cba(x_2, filters=64, kernel_size=(1,16), strides=(1,2))
x_2 = cba(x_2, filters=64, kernel_size=(16,1), strides=(2,1))

x_3 = cba(inputs, filters=32, kernel_size=(1,32), strides=(1,2))
x_3 = cba(x_3, filters=32, kernel_size=(32,1), strides=(2,1))
x_3 = cba(x_3, filters=64, kernel_size=(1,32), strides=(1,2))
x_3 = cba(x_3, filters=64, kernel_size=(32,1), strides=(2,1))

x_4 = cba(inputs, filters=32, kernel_size=(1,64), strides=(1,2))
x_4 = cba(x_4, filters=32, kernel_size=(64,1), strides=(2,1))
x_4 = cba(x_4, filters=64, kernel_size=(1,64), strides=(1,2))
x_4 = cba(x_4, filters=64, kernel_size=(64,1), strides=(2,1))

x = Add()([x_1, x_2, x_3, x_4])

x = cba(x, filters=128, kernel_size=(1,16), strides=(1,2))
x = cba(x, filters=128, kernel_size=(16,1), strides=(2,1))

x = GlobalAveragePooling2D()(x)
x = Dense(classes)(x)
x = Activation("softmax")(x)

model = Model(inputs, x)

色々なサイズのフィルターを通すことで、短期から長期の特徴を捉えることができるそうです。
早速、以下のように学習させます。入力データはスペクトログラムを使用しています。

# initiate Adam optimizer
opt = keras.optimizers.adam(lr=0.00001, decay=1e-6, amsgrad=True)

# Let's train the model using Adam with amsgrad
model.compile(loss='categorical_crossentropy',
              optimizer=opt,
              metrics=['accuracy'])

hist = model.fit(x_train,y_train,
                 validation_data=(x_test,y_test),
                 epochs=50, 
                 verbose=1,
                 batch_size=20)

学習結果↓

base.png

Validationの分類精度は36.2%となりました。
まだ伸びる余地はありますが、行っても40%くらいという印象です。

各技術の追加

これより、各技術を追加します。

基本のCNNと同じ条件にするために、畳込み層は同じ構造にして、epochも20で固定します。
その他、全結合層や正則化層などは無制限です。

前回の結果を受け、追加する技術はData AugmentationとBatch Normalizationに絞ります。
そして、技術というよりは、前処理に近いメルスペクトログラムの有無によって精度が
どれくらい変わるのか観察します。

メルスペクトログラムの効果

基本のCNNの入力は、スペクトログラムを使っていました。
ここでは、入力をメルスペクトログラムに変えてみます。

学習結果↓

mel.png

Validationの分類精度は28.2%となり、基本のCNNより6.4%ダウンしました。
メルスペクトログラムは、音声認識では適しているかもしれませんが、自然界の音は
スペクトログラムの方が良いのかもしれません。

Batch Normalizationの効果

前項のモデルにBatch Normalizaion(BN)を追加します。

def cba(inputs, filters, kernel_size, strides):
    x = Conv2D(filters, kernel_size=kernel_size, strides=strides, padding='same')(inputs)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    return x

学習結果↓

Batch.png

Validationの分類精度は46.2%で、前項より17.8%アップしました。
BNの効果は絶大です。動作は軽い上に、良い仕事をします。

Data Augmentationの効果

Data Augmentationを追加します。こちらも@cvuskさんのコードを参考にしています。
音程を少し変えたり、ノイズを加えたり、時間をずらしたりしています。

# data augmentation: add white noise
def add_white_noise(x, rate=0.002):
    return x + rate*np.random.randn(len(x))

# data augmentation: shift sound in timeframe
def shift_sound(x, rate=2):
    return np.roll(x, int(len(x)//rate))

# data augmentation: stretch sound
def stretch_sound(x, rate=1.1):
    input_length = len(x)
    x = librosa.effects.time_stretch(x, rate)
    if len(x)>input_length:
        return x[:input_length]
    else:
        return np.pad(x, (0, max(0, input_length - len(x))), "constant")

x_w = add_white_noise(x)
x_s = shift_sound(x)
x_st = stretch_sound(x)

学習結果↓
Aug.png

Validationの分類精度は56%となり、前項より10%アップしました。
まだまだ伸びる余地はあり、エポックを増やせば70%くらいまで行けるかもしれません。

まとめ

以上の結果をまとめると、以下のようになります。
result.png

前回同様、ここだけで判断するのはちょっと乱暴ですが、音ファイルでは
「Batch Normalization」が一番効果的であることが確認されました。

BNとData Augmentationは、画像ファイルでも良い作用をしましたし、これは外せない技術といえます。
一方、メルスペクトログラムやresblockは慎重に取り扱わないと、悪影響を及ぼす可能性があるので注意が必要です。

感想

今回使用したデータは、1つのファイルサイズが128×1723×1でとんでもなく重く、保存や学習でかなり時間がかかりました。

前回のCIFAR-10(サイズ:32×32×3)は、100エポックの学習で2時間かかり、気軽にできるレベルでした。しかし、今回は50エポックの学習で4時間かかりました(Colaboratory)。

また、batchsizeは20が限界で、これ以上だとColaboratoryからメモリーエラーで怒られました。
ColaboratoryでESC-50を使う際は、気長に待ちましょう。

62
72
16

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
72