Я только что работал над проектом по распознаванию речи и эмоций, и это был мой первый опыт работы над проектом по анализу звука.

Работая над ним, я наткнулся на множество терминов, концепций, ошибок, предупреждений, методов построения моделей и кодирования. Вот и подумал поделиться со всеми новичками вроде меня😅.

⚠️⚠️

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

⚠️⚠️

Начнем!!👍👍

Первый и главный шаг любого проекта по анализу данных — СБОР ДАННЫХ.

Описание набора аудиоданных, используемого в этой статье, выглядит следующим образом:

Аудиофайлы, соответствующие каждой из этих эмоций, хранятся в одной папке Dataset в формате «.mp3».

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

import pandas as pd
import os

emotion_path_1 = os.listdir(directory_path_1)
emotion_path_2 = os.listdir(directory_path_2)
# Similarly, you can add as many emotions you want

emotion_count = {
  'emotion': [emotion_1, emotion_2, ...],
  'emotion_count': [len(emotion_path_1), len(emotion_path_2), ...]
}

emotion_count_dataframe = pd.DataFrame(emotion_count)
print(emotion_count_dataframe)

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

Но мы можем выполнить процесс ВИЗУАЛИЗАЦИЯ ДАННЫХ для кадра данных, сгенерированного выше, а также для формы волны и спектрограммы для каждой из этих эмоций.

import seaborn as sns
import matplotlib.pyplot as plt

plt.title('Count of Emotion', size = 16)
count_plot = sns.barplot(x = emotion_count_dataframe.emotion, y = emotion_count_dataframe.emotion_count, data = emotion_count_dataframe)
plt.xlabel('Emotion', size = 12)
plt.ylabel('Count of Records', size = 12)
count_plot.set_yticks(range(0, 325, 25))
sns.despine(top = True, right = True)
plt.show()

Как мы можем наблюдать из кадра данных, а также из визуализации

  1. Грустный
  2. Удивлен
  3. & Отвращение является доминирующей эмоцией (по шкале чисел) в наборе данных.

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

import librosa
import matplotlib.pyplot as plt

def plot_waveform(signal, emotion):
  plt.figure(figsize = (12, 3))
  plt.title('Waveplot: \'{}\' Emotion'.format(emotion), size = 16)
  librosa.display.waveshow(signal)
  plt.show()

def plot_spectrogram(signal, emotion):
  x = librosa.stft(signal)
  x_dB = librosa.amplitude_to_db(abs(x))
  plt.figure(figsize = (12, 3))
  plt.title('Spectrogram: \'{}\' Emotion'.format(emotion), size = 16)
  librosa.display.specshow(x_dB)
  plt.show()

Ниже приведен код для вызова каждой из этих функций и визуализации формы волны и графика спектрограммы.

import librosa

signal, sampling_rate = librosa.load(audio_file_path)
plot_waveform(signal, emotion_1)
plot_spectrogram(signal, emotion_1)

При успешном запуске приведенных выше кодов будут созданы два графика (форма волны (1-й) и спектрограмма (2-й)), как показано на рисунке.

Точно так же вы также можете использовать библиотеку IPython.display для отображения или воспроизведения звука в среде Google Colab или Jupyter Notebook. Ниже представлена ​​иллюстрация…

from IPython.display import Audio

Audio(audio_file_path)

После этой базовой визуализации звука мы можем теперь перейти к процессу ДОПОЛНЕНИЕ ДАННЫХ.

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

Вот несколько распространенных методологий подготовки модели к реальным данным:

  1. Шум
  2. Растяжение времени
  3. Изменение высоты тона
  4. Временной сдвиг

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

import numpy as np
import math
import random
import librosa

def white_noise(signal, SNR):
  RMS_signal = math.sqrt(np.mean(signal ** 2))
  RMS_noise = math.sqrt(RMS_signal ** 2 / (pow(10, SNR / 10)))
  noise = np.random.normal(0, RMS_noise, signal.shape[0])
  return noise

def stretch(signal):
  return librosa.effects.time_stretch(signal, rate = 0.8)

def pitch(signal, sampling_rate):
  return librosa.effects.pitch_shift(signal, sr = sampling_rate, n_steps = 2)

def shift(signal):
  time_shift = int(np.random.uniform(low = -5, high = 5) * 1000)
  return np.roll(signal, time_shift)

Ниже представлен подробный анализ каждой из вышеупомянутых функций:

white_noise(): эта функция генерирует белый шум для заданного отношения сигнал-шум (SNR). Белый шум — это случайный сигнал, имеющий плоскую спектральную плотность мощности, т. е. одинаковую мощность на всех частотах. Обычный подход к созданию белого шума заключается в использовании генератора случайных чисел для получения равномерного распределения между -1 и 1, а затем его дисперсия масштабируется для получения желаемого результата. В целом эта функция возвращает белый шум в соответствии с указанным значением SNR и возвращает пустой массив.

stretch(): эта функция использует функцию time_stretch() модуля librosa.effects. time_stretch() принимает сигнал в качестве параметра, который должен быть растянут по времени. Также параметр скорость, то есть скорость, с которой должна растягиваться ось времени. Он возвращает временной ряд аудио, растянутый с определенной скоростью.

pitch(): эта функция использует функцию pitch_shift() модуля librosa.effects. pitch_shift()принимает сигнал как параметр, к которому применяется смещение высоты тона. Аудиосигнал, используемый в этой функции, ДОЛЖЕН быть во временной области. Другим параметром, используемым в этой функции, является sampling_rate(), то есть частота дискретизации аудиосигнала или количество выборок в секунду. Последний используемый параметр — n_steps, который определяет количество полутонов, на которое происходит сдвиг высоты тона. Значение этого параметра по умолчанию равно 2, т. е. он сдвинет высоту звукового сигнала на 2 полутона.

shift(): эта функция использует функцию uniform() модуля numpy.random для генерации случайного числа от -5000 до 5000, который представляет сдвиг времени в миллисекундах. И, наконец, он возвращает массив сдвинутых сигналов; в этом массиве выполняется круговой сдвиг в соответствии с заданным числом.

import os
import librosa

signal, sampling_rate = librosa.load(audio_file_path)

В приведенном выше коде мы передали путь к аудиофайлу функции load() библиотеки librosa, которая возвращает сигнал. и sampling_rate, то есть временной ряд аудио и его частота дискретизации.

Прежде чем выполнять ДОПОЛНЕНИЕ ДАННЫХ на исходном звуке, обычно лучше визуализировать его в исходном виде. Ниже приведен фрагмент этой задачи.

import librosa
import matplotlib.pyplot as plt

signal, sampling_rate = librosa.load(audio_file_path)

plt.figure(figsize = (15, 3))
librosa.display.waveshow(y = signal, sr = sampling_rate)

Ниже представлено графическое представление исходного звука; построенный с использованием приведенного выше фрагмента кода.

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

Теперь мы начнем реализовывать шаги увеличения данных (сдвиг во времени, растяжение во времени, сдвиг по высоте). Ниже приведен фрагмент кода для следующего:

  • Временной сдвиг
import librosa
import matplotlib.pyplot as plt

signal, sampling_rate = librosa.load(audio_file_path)

shifted_data = shift(signal)
plt.figure(figsize = (12, 3))
librosa.display.waveshow(y = shifted_data, sr = sampling_rate)

  • Растяжение времени
import librosa
import matplotlib.pyplot as plt

signal, sampling_rate = librosa.load(audio_file_path)
 
stretched_data = stretch(signal)
plt.figure(figsize = (12, 3))
librosa.display.waveshow(y = stretched_data, sr = sampling_rate)

  • Изменение высоты тона
import librosa
import matplotlib.pyplot as plt

signal, sampling_rate = librosa.load(audio_file_path)

pitch_shift = pitch(signal, sampling_rate)
plt.figure(figsize = (15, 3))
librosa.display.waveshow(y = pitch_shift, sr = sampling_rate)

Внимательно наблюдая за всеми вышеприведенными графиками, мы видим, что существуют различия (время, высота тона) в графике исходного аудиосигнала и аудиосигнала с добавленными данными.

Теперь мы перейдем к одному из самых важных этапов аудиоанализа, т. е. к ИЗВЛЕЧЕНИЮ ФУНКЦИЙ.

В первой части выделения признаков мы проведем базовый статистический анализ частоты сигнала. Ниже приведен фрагмент кода для следующей задачи.

import numpy
import librosa
import scipy

def statistical_feature(signal):
  frequency = np.fft.fftfreq(signal.size)

  mean_frequency = np.mean(frequency)
  std_frequency = np.std(frequency)
  max_amp_frequency = np.amax(frequency)
  min_amp_frequency = np.amin(frequency)
  median_frequency = np.median(frequency)
  skew_frequency = scipy.stats.skew(frequency)
  kurt_frequency = scipy.stats.kurtosis(frequency)
  iqr_frequency = scipy.stats.iqr(frequency)

  return [mean_frequency, std_frequency, max_amp_frequency, min_amp_frequency,
          median_frequency, skew_frequency, kurt_frequency, iqr_frequency]

Вышеупомянутая функция statistical_feature(signal) вернет массив, состоящий из рассчитанного выше статистического признака.

Двигаемся дальше…

Основные особенности, извлеченные из аудиофайла, включают:

  1. Нулевая скорость пересечения (ZCR)
  2. Mel-частотные кепстральные коэффициенты (MFCC)
  3. Кратковременное четырехуровневое преобразование цветности (Chroma STFT)
  4. Мел-Спектрограмма
  5. Среднеквадратичное значение (RMS)
  6. Тоннец
  7. Спектральный контраст
  8. Спектральный центроид
  9. Спектральный спад
  10. Спектральная полоса пропускания
  11. Спектральная плоскостность

В приведенном ниже фрагменте кода я извлек некоторые характерные особенности аудиофайлов и объединил их в массив.

import numpy as np
import librosa

signal, sampling_rate = librosa.load(audio_file_path)

def extract_features(signal, sampling_rate):
  stack_result = np.array([])

  zcr = np.mean(librosa.feature.zero_crossing_rate(y = signal))
  stack_result = np.hstack((stack_result, zcr))

  mfcc = np.mean(librosa.feature.mfcc(y = signal, sr = sampling_rate, n_mfcc = 40).T)
  stack_result = np.hstack((stack_result, mfcc))

  stft = np.abs(librosa.stft(signal))
  chroma_stft = np.mean(librosa.feature.chroma_stft(S = stft, sr = sampling_rate).T)
  stack_result = np.hstack((stack_result, chroma_stft))

  mel_spectrogram = np.mean(librosa.feature.melspectrogram(y = signal, sr = sampling_rate).T)
  stack_result = np.hstack((stack_result, mel_spectrogram))

  rms = np.mean(librosa.feature.rms(y = signal).T)
  stack_result = np.hstack((stack_result, rms))

  tonnetz_ = np.mean(librosa.feature.tonnetz(y = librosa.effects.harmonic(signal), sr = sampling_rate),)
  stack_result = np.hstack((stack_result, tonnetz_))

  spec_contrast = np.mean(librosa.feature.spectral_contrast(S = stft, sr = sampling_rate))
  stack_result = np.hstack((stack_result, spec_contrast))

  spec_centroid = np.mean(librosa.feature.spectral_centroid(y = signal, sr = sampling_rate))
  stack_result = np.hstack((stack_result, spec_centroid))

  spec_roll_off = np.mean(librosa.feature.spectral_rolloff(y = signal + 0.01, sr = sampling_rate))
  stack_result = np.hstack((stack_result, spec_roll_off))

  spec_bandwidth = np.mean(librosa.feature.spectral_bandwidth(y = signal, sr = sampling_rate))
  stack_result = np.hstack((stack_result, spec_bandwidth))

  return stack_result

Приведенный выше код вернет все извлеченные функции, сложенные горизонтально в форме массива. Теперь мы извлечем функции с помощью функции extract_features() для двух разных типов аудиофайлов:

  1. Оригинальный аудиофайл
  2. Аудиофайл с растяжением по времени и сдвигом высоты тона

Ниже приведен фрагмент кода для извлечения функций исходного аудиофайла с помощью функции extract_features().

import librosa

def get_normal_features(path):
  feature_signal, feature_sampling_rate = librosa.load(audio_file_path)

  extracted_feature = extract_features(feature_signal, feature_sampling_rate)

  return extracted_feature

Приведенная выше функция возвращает массив средних значений функций, извлеченных с помощью функции extract_features() для исходных аудиофайлов.

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

import python

def get_stretch_pitch_features(path):
  feature_signal, feature_sampling_rate = librosa.load(path)

  stretched_data = stretch(feature_signal)
  stretch_pitch_data = pitch(feature_signal, sampling_rate)
  result_stretch_pitch_data = extract_features(stretch_pitch_data, feature_sampling_rate)

  return result_stretch_pitch_data

Приведенная выше функция возвращает массив средних значений функций, извлеченных с помощью функции extract_features() для аудиофайлов с функциями растяжения по времени и смещения высоты тона.

Теперь, поскольку у нас есть несколько файлов, расположенных в разных папках в соответствии с эмоциями (шаблон каталога показан выше); теперь мы создадим фрейм данных только с двумя столбцами: PATH и EMOTION.

В приведенном ниже фрагменте кода мы передали два значения 2, соответствующие 2 столбцам; audio_file_path и emotion_label, и, следовательно, создается кадр данных, содержащий PATH каждого аудиофайла вместе с LABEL эмоции.

import pandas as pd
import os

path_emotion_dataframe = pd.DataFrame(columns = ['Path', 'Emotion'])

for i in range(0, len(emotion_1)):
  path_emotion_dataframe.loc[len(path_emotion_dataframe)] = [os.path.join(audio_file_path_emotion_1, audio_file_path_emotion_1[i]), emotion_label]
.
.
.
.
.
# (similar 'for' loop for each emotion)

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

В приведенном ниже фрагменте кода мы извлекли функции из аудиофайлов и добавили их в массив вместе с меткой эмоций.

import pandas as pd

# Long execution time; 30 minutes execution time
X_normal, Y_normal = [], []

for path, emotion in zip(path_emotion_dataframe.Path, path_emotion_dataframe.Emotion):
  features = get_normal_features(path)
  X_normal.append(features)
  Y_normal.append(emotion)

normal_feature_dataframe = pd.DataFrame(X_normal, columns =  ['ZCR', 
  'MFCC', 'Chroma STFT', 'Mel Spectrogram', 'RMS', 'Tonnetz',
  'Spectral Contrast', 'Spectral Centroid', 'Spectral Rolloff',
  'Spectral Bandwidth', 'Spectral Flatness'])

normal_feature_dataframe['Labels'] = Y_normal

normal_feature_dataframe.to_csv('file_name.csv', index = False)

Приведенный выше код сохранит кадр данных с извлеченной функцией в файле CSV, который будет использоваться в дальнейшем при построении моделей ML.

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

Ниже приведен фрагмент кода для растянутого по времени и сдвинутого по высоте аудиофайла.

# Long execution time; 35 minutes execution time
X_stretch_pitch, Y_stretch_pitch = [], []

for path, emotion in zip(path_emotion_dataframe.Path, path_emotion_dataframe.Emotion):
  features = get_stretch_pitch_features(path)
  X_stretch_pitch.append(features)
  Y_stretch_pitch.append(emotion)

stretch_pitch_feature_dataframe = pd.DataFrame(X_stretch_pitch, 
  columns =  ['ZCR', 'MFCC', 'Chroma STFT', 'Mel Spectrogram', 'RMS',
  'Tonnetz', 'Spectral Contrast', 'Spectral Centroid', 'Spectral Rolloff',
  'Spectral Bandwidth', 'Spectral Flatness'])

stretch_pitch_feature_dataframe['Labels'] = Y_stretch_pitch

stretch_pitch_feature_dataframe.to_csv('file_name.csv', index = False)

Ниже приведен фрагмент кода для комбинированных аудиофайлов (исходный + растянутый по времени и сдвинутый по высоте аудиофайл).

# Long execution time; 50 minutes execution time 
X_combined, Y_combined = [], []

for path, emotion in zip(path_emotion_dataframe.Path, path_emotion_dataframe.Emotion):
  normal_features = get_normal_features(path)
  stretch_pitch_features = get_stretch_pitch_features(path)
  X_combined.append(normal_features)
  X_combined.append(stretch_pitch_features)
  Y_combined.append(emotion)
  Y_combined.append(emotion)

combined_feature_dataframe = pd.DataFrame(X_combined, columns =  ['ZCR',
  'MFCC', 'Chroma STFT', 'Mel Spectrogram', 'RMS', 'Tonnetz',
  'Spectral Contrast', 'Spectral Centroid', 'Spectral Rolloff',
  'Spectral Bandwidth', 'Spectral Flatness'])

combined_feature_dataframe['Labels'] = Y_combined

combined_feature_dataframe.to_csv('file_name.csv', index = False)

⚠️ Все последние 3 упомянутых кода имеют очень долгое время исполнения; он также будет варьироваться в зависимости от набора данных, используемого программистом. ⚠️

Теперь у нас осталось 3 CSV-файла, содержащих извлеченные функции для аудиофайлов (исходный файл, файл, растянутый по времени и сдвинутый по высоте, исходный файл + файл, растянутый по времени и сдвинутый по высоте). Сохраните все эти 3 файла и будьте готовы к этапам ПОДГОТОВКИ ДАННЫХ И ПОСТРОЕНИЯ МОДЕЛИ.

Дальнейшие задачи и процессы будут объяснены в следующих постах этой серии.

🙏Подписывайтесь на меня для дальнейших обновлений!!🙏

Использованная литература:

  1. Добавление шума в аудиоклипстр.
  2. Аугментация аудиоданных
  3. "Белый шум"
  4. Извлечение признаков
  5. Официальная документация Librosa
  6. МФЦК
  7. Мел Спектрограмма
  8. Нулевая скорость пересечения
  9. Спектральная плоскостность