Audio Data Analysis

Предыдущая часть: Часть 1

Сверточные нейронные сети (CNN) схожи с обычными нейронными сетями: они состоят из нейронов с обучаемыми весами и сдвигами. Каждый нейрон получает входные данные, выполняет скалярное произведение и при необходимости добавляет нелинейную оптимизацию. Вся сеть выражает одну дифференцируемую функцию оценки: начиная от пикселей «сырых» изображений и заканчивая оценкой класса. Они также обладают функцией потерь (например, SVM/Softmax) на последнем (полностью подключенном) слое.

В чем же заключаются различия? Архитектуры ConvNet делают предположение, что входные данные являются изображениями, благодаря чему определенные свойства могут быть зашифрованы в архитектуру. В результате функция пересылки становится более эффективной для реализации, а количество параметров в сети значительно уменьшается.

Они способны обнаруживать первичные функции, которые затем объединяются последующими слоями архитектуры CNN, что приводит к обнаружению более сложных и релевантных признаков.

Для работы с нейронными сетями мы будем использовать Google Colab — бесплатный сервис, предоставляющий GPU и TPU в качестве сред выполнения.

Реализация сверточной нейронной сети

Этапы извлечения признаков из спектрограммы входных данных и загрузки их в полносвязанный слой: спектрограмма входных данных, фильтры N с шириной 3, карты свертки между входными данными и фильтрами, конкатенация N свернутых карт признаков, максимальный пул конкатенированных карт с фактором 2, фильтры M с шириной 3, карты свертки между общим выводом и фильтрами, глобальный максимальный пул, полносвязанный слой для классификации.

Для начала загружаем все необходимые библиотеки:

import pandas as pd
import numpy as np
from numpy import argmax
import matplotlib.pyplot as plt
%matplotlib inline
import librosa
import librosa.display
import IPython.display
import random
import warnings
import os
from PIL import Image
import pathlib
import csv
# Предварительная обработка sklearn
from sklearn.model_selection import train_test_split
# Keras
import keras
import warnings
warnings.filterwarnings('ignore')
from keras import layers
from keras.layers import Activation, Dense, Dropout, Conv2D, Flatten, MaxPooling2D, GlobalMaxPooling2D, GlobalAveragePooling1D, AveragePooling2D, Input, Add
from keras.models import Sequential
from keras.optimizers import SGD

Теперь преобразуем файлы аудиоданных в PNG или извлекаем спектрограмму для каждого аудио с помощью библиотеки Python librosa:

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'./drive/My Drive/genres/{g}'):
        songname = f'./drive/My Drive/genres/{g}/{filename}'
        y, sr = librosa.load(songname, mono=True, duration=5)
        print(y.shape)
        plt.specgram(y, NFFT=2048, Fs=2, Fc=0, noverlap=128, cmap=cmap, sides='default', mode='default', scale='dB');
        plt.axis('off');
        plt.savefig(f'img_data/{g}/{filename[:-3].replace(".", "")}.png')
        plt.clf()

Приведенный выше код создает директорию img_data, содержащую все изображения, классифицированные по жанрам.

Ниже приведены спектрограммы семплов:

Диско
Классика
Блюз
Кантри

Следующий шаг — разделение данных на наборы для обучения и тестирования.

Устанавливаем split_folders:

pip install split_folders

80% данных будет использоваться для обучения и 20% для тестирования.

import split_folders
# Для создания наборов для обучения и тестирования устанавливаем кортеж в `ratio`, т.е., `(.8, .2)`.
split_folders.ratio('./img_data/', output="./data", seed=1337, ratio=(.8, .2)) # значения по умолчанию

Приведенный выше код возвращает две директории для обучающего и тестового набора в родительскую:

Увеличение числа изображений

Увеличение числа изображений представляет собой искусственное создание обучающих изображений с помощью различных способов обработки, таких как случайная ротация, переходы, сдвиги, перевороты и т.д.

В Keras есть класс ImageDataGenerator, значительно упрощающий этот процесс. Ознакомиться с ним можно в официальной документации Keras.

from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(
        rescale=1./255, # изменение масштаба всех значений пикселей с 0 до 255, после этого шага они будут находится в диапазоне (0,1)
        shear_range=0.2, # применение случайных преобразований
        zoom_range=0.2, # увеличение масштаба
        horizontal_flip=True) # горизонтальный поворот
test_datagen = ImageDataGenerator(rescale=1./255)

У класса ImageDataGenerator есть три метода: flow(), flow_from_directory() и flow_from_dataframe() для чтения изображений из массива и папок. Мы рассмотрим только flow_from_directory().

training_set = train_datagen.flow_from_directory(
        './data/train',
        target_size=(64, 64),
        batch_size=32,
        class_mode='categorical',
        shuffle = False)
test_set = test_datagen.flow_from_directory(
        './data/val',
        target_size=(64, 64),
        batch_size=32,
        class_mode='categorical',
        shuffle = False )

Он обладает следующими аргументами:

  • directory: путь папки, в которой находятся все тестовые изображения. В данном случае: ./data/train
  • для batch_size необходимо установить число, разделяющее общее количество изображений в тестовом наборе.
    Почему это относится только к test_generator?
    На самом деле «batch_size» необходимо установить как для обучающего, так и для тестового генератора. Однако, если batch_size не совпадает с числом семплов в обучающем или тестовом наборах, а некоторые изображения пропускаются при каждом получении из генератора, они будут отбираться в ближайшую обучающую эпоху. Для тестового набора выборку изображений нужно выполнить только один раз.
  • для class_mode устанавливаем «binary» при наличии двух классов для прогнозирования, в противном случае используем «categorical». При создании системы автоэнкодера ввод и вывод, скорее всего, будут представлены одним и тем же изображением. В этом случае устанавливаем «input».
  • для shuffle устанавливаем значение False, поскольку изображения нужно выводить по «порядку», чтобы предсказать результаты и сопоставить их с уникальными id или именами файлов.

Создаем сверточную нейронную сеть:

model = Sequential()
input_shape=(64, 64, 3)

# первый скрытый слой
model.add(Conv2D(32, (3, 3), strides=(2, 2), input_shape=input_shape))
model.add(AveragePooling2D((2, 2), strides=(2,2)))
model.add(Activation('relu'))

# второй скрытый слой
model.add(Conv2D(64, (3, 3), padding="same"))
model.add(AveragePooling2D((2, 2), strides=(2,2)))
model.add(Activation('relu'))

# третий скрытый слой
model.add(Conv2D(64, (3, 3), padding="same"))
model.add(AveragePooling2D((2, 2), strides=(2,2)))
model.add(Activation('relu'))

# слой выравнивания
model.add(Flatten())
model.add(Dropout(rate=0.5))

# полносвязный слой
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(rate=0.5))

# выходной слой
model.add(Dense(10))
model.add(Activation('softmax'))
model.summary()

Скомпилируйте/обучите сеть с помощью стохастического градиентного спуска (SGD). При использовании этого метода несколько семплов выбираются случайным образом вместо полного набора данных для каждой итерации.

epochs = 200
batch_size = 8
learning_rate = 0.01
decay_rate = learning_rate / epochs
momentum = 0.9
sgd = SGD(lr=learning_rate, momentum=momentum, decay=decay_rate, nesterov=False)
model.compile(optimizer="sgd", loss="categorical_crossentropy", metrics=['accuracy'])

Теперь подгоняем модель с 50 эпохами:

model.fit_generator(
        training_set,
        steps_per_epoch=100,
        epochs=50,
        validation_data=test_set,
        validation_steps=200)

После обучения модели CNN переходим к ее оценке. evaluate_generator() использует как тестовый ввод, так и вывод. Сначала он прогнозирует выходные данные с помощью набора для обучения, а затем оценивает производительность в сравнении с тестовым набором. Таким образом, он выдает показатель эффективности, в данном случае точность.

# Оценка модели
model.evaluate_generator(generator=test_set, steps=50)

# Вывод
[1.704445120342617, 0.33798882681564246]

Таким образом, потеря составляет 1,70, а точность — 33,7%.

Наконец, переходим к прогнозированию на тестовом наборе данных. Перед вызовом predict_generator необходимо перезагрузить test_set. В противном случае выходные данные будут получены в странном порядке.

test_set.reset()
pred = model.predict_generator(test_set, steps=50, verbose=1)

На данный момент у predicted_class_indices есть прогнозируемые метки, однако сами прогнозы невозможно определить. Теперь нужно сопоставить эти метки с их уникальными id, такими как имена файлов.

predicted_class_indices=np.argmax(pred,axis=1)

labels = (training_set.class_indices)
labels = dict((v,k) for k,v in labels.items())
predictions = [labels[k] for k in predicted_class_indices]
predictions = predictions[:200]
filenames=test_set.filenames

Добавьте имена файлов и прогнозы к единому фрейму данных Pandas в виде двух отдельных столбцов. Но перед этим убедитесь, что они одинакового размера.

print(len(filename, len(predictions)))
# (200, 200)

Наконец, сохраняем результаты в файл CSV:

results=pd.DataFrame({"Filename":filenames,
                      "Predictions":predictions},orient='index')
results.to_csv("prediction_results.csv",index=False)
Вывод

Модель была обучена на 50 эпохах (на выполнение на Nvidia K80 GPU потребовалось 1,5 часа). Для повышения точности количество эпох можно увеличить до 1000 и более.

Заключение

CNN — это эффективная альтернатива для автоматического извлечения признаков, что подтверждает гипотезу о том, что внутренние характеристики вариаций музыкальных данных аналогичны характеристикам данных изображений. Созданная модель CNN обладает высокой масштабируемостью, но недостаточно надежна для обобщения результатов обучения для невидимых ранее музыкальных данных. Эта проблема решается с помощью расширенного набора данных.

На этом все. Спасибо за внимание!

Читайте также:


Перевод статьи Nagesh Singh Chauhan: Audio Data Analysis Using Deep Learning with Python (Part 2)

Предыдущая статьяАнализ аудиоданных с помощью глубокого обучения и Python (часть 1)
Следующая статьяЛучшие практики JavaScript: переменные