Классификация аудиофайлов с библиотекой Librosa

Автор статьи: Виктория Ляликова

Привет Хабр! В этой статье поработаем с аудиофайлами, используя библиотеку librosa и алгоритмы Machine learning.

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

Можно сказать, что аудио является физическим представлением звука, частота которого находится в диапазоне от 20Гц до 20 килогерц.  Эти звуки доступны во многих форматах, что позволяет компьютеру их анализировать, например mp3, wma, wav.

Для работы с аудиосигналом его необходимо оцифровать, т.е. преобразовать звуковую волну в ряд чисел. Это делается путем измерения амплитуды звука через фиксированные промежутки времени. Каждое такое измерение называется выборкой, а частота выборки — количеством выборок в секунду. Например, обычная частота дискретизации составляет около 44 100 выборок в секунду. Это означает, что 10-секундный музыкальный клип будет содержать 441 000 семплов.

Благодаря дискретизации звука из аудиозаписей можно извлекать достаточно большое число различных характеристик, которые помогают в дальнейшем анализе аудио. Среди таких характеристик можно выделить, например, мел-кепстральные коэффициент (MFCCs), спектр, спектограмма, спектральный центроид (Spectral Centroid), спектральный спад (Spectral Rolloff)  и др.

Чтобы подробнее разобраться с данными характеристиками, установим библиотеку Librosa, которая используется для анализа звуковых сигналов, но больше ориентирована на музыку

pip install librosa

или

conda install -c conda-forge librosa

И здесь хочется сделать небольшое отступление. Я работаю в Jupyter Notebook и часто получается так, что ты набираешь эти волшебные команды и, …. ничего не работает. И тут тогда начинается, гуглишь, пробуешь различные варианты, которые обещают исправить проблему установки, пытаешься все переустановить заново, создать новую виртуальную среду, и все равно ничего не работает. Вот именно с этой библиотекой у меня было много проблем, в разные моменты времени появлялись разные ошибки и ничего не работало. Но потом, каким-то волшебным образом Librosa все-таки установилась, причем, я уже даже не знаю, что именно мне помогло.

Но есть предположение, что команда

.pip install librosa –-user оказалась волшебной.

Теперь можно импортировать библиотеку и начинать работать.

import librosa

Классификацию аудиофайлов будем проводить по набору данных, содержащим коллекцию из аудиофайлов по 10 различным жанрам. Каждый жанр имеет по 100 аудиофайлов продолжительностью по 3 и 30 секунд.

Загрузка аудио и извлечение значимых характеристик

Используя функцию librosa.load() можно считать конкретные звуки из нашего набора данных.

import os # библиотека для работы с файлами dir = '***/datasets/Data/genres_original' #задаем директорию с данными file = dir+'/blues/blues.00000.wav' signal, sr = librosa.load(file, sr = 22050) # загружаем файл

На выходе мы получаем два объекта,  первый — это цифровое представление нашего аудиосигнала (в виде временного ряда), второй — соответствующая частота дискретизации по которой он был извлечен. По умолчанию используется передискретизация 22050 Гц. Как говорилось выше частота дискретизации — это количество аудио семплов, передаваемых в секунду, измеряется в Гц или кГц.

print(signal.shape, sr) (661794,) 22050

Посмотрим на наш аудиосигнал

print(signal) [ 0.00732422  0.01660156  0.00762939 ... -0.05560303 -0.06106567  -0.06417847]

Полученный аудиосигнал можно представить в виде звуковой волны с помощью функции librosa.display.waveshow()

import matplotlib.pyplot as plt import librosa.display as ld plt.figure(figsize=(12,4)) ld.waveshow(signal, sr=sr)

Вертикальная сторона представляет амплитуду звука, а горизонтальная ось — время, затраченное на воспроизведение звука на определенной частоте.

А с помощью функции IPyhon.display() получим плеер в блокноте, где можно воспроизвести аудиофайл.

import IPython display(IPython.display.Audio(signal, rate = sr))

Звук можно перевести из временной области в частотную область с помощью быстрого преобразования Фурье таким образом, что после этого мы получим спектр сигнала. В librosa для этих целей есть функция librosa.stft() c такими параметрами как n_fft — длина оконного сигнала после заполнения нулями и hop_length — размер кадра или размер быстрого преобразования Фурье.

n_fft = 2048 ft = np.abs(librosa.stft(signal[:n_fft], hop_length = n_fft+1)) plt.plot(ft) plt.title('Spectrum') plt.xlabel('Frequency Bin') plt.ylabel('Amplitude')

Спектрограмма является визуальным способом представления уровня или громкости сигнала на различных частотах, присутствующих в формах волны. То есть показывает интенсивность частот во времени. Это дает представление времени по оси x, частоты по оси y, а соответствующие амплитуды представляются цветом

С помощью функции librosa.display.specshow() можно посмотреть на спектрограмму аудиосигнала:

X = librosa.stft(signal) s = librosa.amplitude_to_db(abs(X)) ld.specshow(s, sr=sr, x_axis = 'time', y_axis='linear') plt.colorbar()

Мел-кепcтральные коэффициенты (MFCC) являются одним из важнейших признаков в обработке аудио. Процесс вычисления данных коэффициентов учитывает ряд особенностей слухового анализатора человека, моделируя характеристики человеческого голоса.  Это связано с тем, звуки, воспроизводимые человеком, определяется формой голосового тракта, включая язык, зубы и т.д.

Мел-кепcтральные коэффициенты можно найти с помощью функции librosa.feature.mfcc()

mfccs = librosa.feature.mfcc(y=signal, sr=sr, n_mfcc = 40, hop_length=512) mfccs
mfccs.shape (40, 1293)

n_mfcc — количество коэффициентов MFCC

hop_length — размер кадра

Размер матрицы мел-коэффициентов вычисляется как

[n_mfcc, len(signal)//hop_length+1]

То есть, если n_mels = 40, hop_length = 512, тогда

len(signal)//hop_length+1 = 661794//512+1 = 1292+1 = 1293.

Также мы можем построить мел-спектрограмму с помощью функции librosa.feature.melspectrogram(). Мел-спектрограмма представляет собой спектрограмму, преобразованную в мел-шкалу.

melspectrum = librosa.feature.melspectrogram(y=signal, sr = sr,                                         hop_length =512, n_mels = 40)

Спектральный центроид (Spectral Centroid) является хорошим показателем яркости звука, широко используется в качестве автоматической меры музыкального тембра. То есть центроид показывает где расположен центр масс звука. В блюзовых композициях частоты распределены равномерно, в металле спектроид лежит ближе к концу спектра. В Librosa используется функция librosa.feature.spectral_centroid()

cent = librosa.feature.spectral_centroid(y=signal, sr=sr) plt.figure(figsize=(15,5)) plt.semilogy(cent.T, label='Spectral centroid') plt.ylabel('Hz') plt.legend()

array([[1936.83283904, 1820.36294357, 1780.31673025, ..., 2770.21094705, 2661.92181327, 2604.75205139]])

Спектральны спад  (Spectral Rolloff) представляет собой частоту, ниже которой лежит определенный процент от общей спектральной энергии. В librosa используется функция librosa.feature.spectral_rolloff()

rolloff = librosa.feature.spectral_rolloff(y=signal, sr=sr) plt.figure(figsize=(15,5)) plt.semilogy(rolloff.T, label='Roll-off frequency') plt.ylabel('Hz') plt.legend()

array([[4005.17578125, 3520.67871094, 3348.41308594, ..., 5792.43164062, 5577.09960938, 5361.76757812]])

Скорость пересечения нуля (Zero crossing Rate) является частотой изменения знака сигнала, то есть частота, с которой сигнал меняется с положительного на отрицательный и обратно. Например, для металла и рока этот параметр обычно выше, чем для других жанров, из-за большого количества ударных. В librosa используется функция librosa.feature.zero_crossing_rate()

zrate=librosa.feature.zero_crossing_rate(signal) plt.figure(figsize=(14,5)) plt.semilogy(zrate.T, label='Fraction') plt.ylabel('Fraction per Frame') plt.legend()

array([[0.03808594, 0.06054688, 0.07861328, ..., 0.14550781, 0.13623047, 0.10058594]])

Конечно, это далеко не весь перечень значимых характеристик аудиосигнала и обычно каждый исследователь выбирает сам какие характеристики для извлечения из аудиофайла он будет использовать в своей задаче. 

Например, можно также выбирать средние значения и стандартные отклонения мел-кепстральных коэффициентов:

mfcc_mean = np.mean(librosa.feature.mfcc(y=signal, sr=sr), axis=1) mfcc_std = = np.std(librosa.feature.mfcc(y=signal, sr=sr), axis=1)

средние значения и стандартные отклонения спектрального центроида

cent_mean = np.mean(cent) cent_std = np.std(cent)

средние значения и стандартные отклонения спектрального спада и т.д.

roloff_mean = np.mean(roloff) croloff_std = np.std(roloff)

Потом все эти значения можно записать в датафрейм и работать с ними

df = pd.DataFrame(audio_data) df['labels'] = labels

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

audio_files = [] labels = [] labelind = -1 for label in os.listdir(dir): labelind +=1 label_path = os.path.join(dir, label) for audio_file in os.listdir(label_path):     audio_file_path = os.path.join(label_path, audio_file)     audio_files.append(audio_file_path)     labels.append(labelind)

Теперь создадим функцию, которая на вход будет принимать аудиофайл, а затем получать средние значения коэффициентов  для данного файла.

def preprocess_audio(audio_file_path): audio, sr = librosa.load(audio_file_path) mfcc_mean = np.mean(librosa.feature.mfcc(y=audio, sr=sr),   axis=1) return abs(mfcc_mean)

Получим список audio_data цифровых значений для всех аудиофайлов:

audio_data =[] for audio_file in audio_files: mfccs_mean = preprocess_audio(audio_file) audio_data.append(mfccs_mean)

Создадим массивы из характеристик аудиофайлов и их меток

audio_data = np.array(audio_data) labels = np.array(labels)

Таким образом, можно все характеристики аудиофайлов объединить в один датафрейм и далее с ним работать. 

Построение модели

Набор данных, с которым я работаю уже содержит csv файл в котором содержится информация о мел-кепстральных коэффициентах, спектральных центроидах и т.д. Загрузим и посмотрим на него.

df = pd.read_csv(f'{dir}/features_3_sec.csv') df

В нашем датафрейме 2 столбца (filename и length), которые далее нам не понадобятся, поэтому удалим их.

df = df.iloc[0:, 2:]

Теперь определим вектор с данными для обучения (X) и вектор соответствующим их меток (y). Данные для обучения представляют собой значимые характеристики аудиоданных, всего 57 значений. Разберемся с метками классов. 

df['label'].unique() array(['blues', 'classical', 'country', 'disco', 'hiphop', 'jazz',        'metal', 'pop', 'reggae', 'rock'], dtype=object)

Они имеют категориальное представление, поэтому преобразуем их в численное представление с помощью метода LabelEncoder().

class_list=df.iloc[:,-1] # создаем список классов convertor = preprocessing.LabelEncoder() y=convertor.fit_transform(class_list) # конвертируем признаки

Теперь перейдем к данным для обучения Х. Удалим из нашего датафрейма столбец с метками.

X = df.loc[:, df.columns !='label']

Нормализуем наш целевой вектор с помощью метода StandardScaler()

from sklearn import preprocessing cols = X.columns scaler = preprocessing.StandardScaler() np_scaled = scaler.fit_transform(X) X = pd.DataFrame(np_scaled, columns = cols)

Разделим выборку на тестовую и обучающую

from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state=42 )

Воспользуемся классическими алгоритмами машинного обучения: алгоритм Байеса, логистическая регрессия, метод к-ближайших соседей, метод опорных векторов, ансамблевыми методами: случайный лес, XGBoost (деревья с градиентным бустингом) и моделью многослойного перспетрона MLPClassifier.

Импортируем необходимые модули:

from sklearn.naive_bayes import GaussianNB from sklearn.linear_model import LogisticRegression from sklearn.neighbors import KNeighborsClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.neural_network import MLPClassifier from xgboost import XGBClassifier from sklearn.metrics import confusion_matrix, accuracy_score, roc_auc_score, roc_curve

Создадим дополнительную функцию по работе с алгоритмами обучения:

def model_assess(model, title = "Default"): model.fit(X_train, y_train) preds = model.predict(X_test) print('Accuracy', title, ':', round(accuracy_score(y_test, preds), 5), '\n')

Построим модели и оценим точность.

# алгорит Баейса nb = GaussianNB() model_assess(nb, "Naive Bayes") # алгоритм k-ближайших соседей knn = KNeighborsClassifier(n_neighbors=10) model_assess(knn, "KNN") # метод опорных векторов svm = SVC(decision_function_shape="ovo") model_assess(svm, "Support Vector Machine") # логистическая регрессия lg = LogisticRegression(random_state=0, solver='lbfgs', multi_class='multinomial') model_assess(lg, "Logistic Regression") # случайный лес rforest = RandomForestClassifier(n_estimators=1000, max_depth=10, random_state=0) model_assess(rforest, "Random Forest") # многослойный персептрон nn = MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes=(5000, 10), random_state=1) model_assess(nn, "Neural Nets") # деревья с градиентным бустингом xgb = XGBClassifier(n_estimators=1000) model_assess(xgb, "XGBClassifier")

Видим, что самый низкий показатель точности 0,52 у алгоритма Байеса, а самый высокий 0,9 у алгоритма XGBClassifier.

Рекомендации аудиокомпозиций

А теперь попробуем  порекомендовать пользователю аудиокомпозиции, используя метод cosine_similarity() из библиотеки scikit-learn. Данный метод вычисляет косинусное сходство между двумя ненулевыми векторами и основан на косинусе угла между ними, что дает значение от -1 до 1. Значение -1 означает, что векторы противоположны, 0 представляет ортогональные векторы, а значение 1 означает подобные векторы.

Для этого возьмем csv файл, прочитаем его и удалим лишние столбцы.

df1 = pd.read_csv(f'{dir}/features_30_sec.csv',index_col=0) labels = df1[['label']] df1 = df1.drop(columns=['length','label'])

Далее переведем наш датафрейм в матрицу размером 1000х57 и вычислим косинусное сходство между векторами.

from sklearn import preprocessing from sklearn.metrics.pairwise import cosine_similarity scaled=preprocessing.scale(df1) similarity = cosine_similarity(scaled)

И теперь полученную матрицу сходства представим в виде датафрейма:

similarity_labels = pd.DataFrame(similarity) similarity_names = similarity_labels.set_index(labels.index) similatity_names.columns = labels.index

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

name = 'rock.00087.wav' series = pd.DataFrame(similarity_names[name].sort_values(ascending = False)) series = series.loc[(series[name]>0.90)] series = series.drop(name) print("\n*******\nSimilar songs to ", name) print(series.head(5))

Получаем названия рекомендованных композиций и их косинусное сходство.

В дальнейшем было бы интересно получить из аудиофайлов изображения в виде, например, их спектрограмм или мел-кепстральных спектрограмм, а затем используя алгоритмы нейронных сетей классифицировать аудиофайлы. Таким образом, мы сможем работать со звуком, применяя не классический подход, заключающийся в преобразовании данных, а будем работать с визуальным представлением звука. Классификация аудиофайлов с библиотекой Librosa.

И напоследок хочу порекомендовать вам бесплатный урок от моих коллег из OTUS. На занятии преподаватели OTUS расскажут про рекомендательные системы, основанные на контентной фильтрации. А затем вы на практике примените изученные подходы, для построения рекомендательной системы онлайн магазина.

  • Зарегистрироваться на бесплатный вебинар


ссылка на оригинал статьи https://habr.com/ru/companies/otus/articles/741080/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *