Автор статьи: Виктория Ляликова
Привет! В этой статье поработаем с аудиофайлами, используя библиотеку 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 расскажут про рекомендательные системы, основанные на контентной фильтрации. А затем вы на практике примените изученные подходы, для построения рекомендательной системы онлайн магазина.
-
Зарегистрироваться на бесплатный вебинар
Автор: Дмитрий Головин