Подготовка данных для обучения модели нейронной сети

Подготовка данных для обучения модели нейронной сети

Категория статьи: Нейронные сети

Проект: Shazam своими руками

Подготовка данных для обучения модели нейронной сети

Снова, всем привет!

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

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

# Директория с mp3 файлами
mp3_dir = 'mp3'

# Директория с файлами для обучения модели
train_dir = 'train'

# Директория с файлами для проверки модели
val_dir = 'val'

# Создаем директории
create_dir_if_not_exists(mp3_dir, True)
create_dir_if_not_exists(train_dir, True)
create_dir_if_not_exists(val_dir, True)

Гиперпараметры

Выделим некоторые параметры, которые необходимы для формирования mel-спектрограммы в отдельный класс и создадим объект этого класса.

# Класс гиперпараметров для работы со звуком
class LibROSA_HP():
  def __init__(self):
    self.sr = 22050
    self.n_fft = 2048
    self.win_length = self.n_fft
    self.hop_length = self.win_length // 4
    self.n_mels = 128 * 4
    self.n_iter = 3
    self.n_timeframe = 80
    self.n_sec = 2
    self.part_count = 300

# Создаем объект гиперпараметров
lhp = LibROSA_HP()

Параметры класса LibROSA_HP

  • sr - частота дискретизации (или частота сэмплирования) - частота, с которой происходит оцифровка, хранение, обработка или конвертация сигнала из аналога в цифру. [По умолчанию sr = 22050 Hz]
  • n_fft - Длина оконного сигнала. Для музыки рекомендуют использовать = 2048, для речи 512. Так же рекомендовано использовать кратно степени 2 для оптимизации скорости алгоритма быстрого преобразования Фурье (FFT). [По умолчанию n_fft = 2048]
  • win_length - Длина окна внутри n_fft. [По умолчанию win_length = n_fft]
  • hop_length - количество звуковых сэмплов между соседними столбцами матрицы преобразования STFT (Short-time Fourier transform) [По умолчанию hop_length = win_length / 4]
  • n_mels - количество фильтров mel-спектра [По умолчанию n_mels = 128] (я буду использовать в 4 раза больше - 512)
  • n_iter - количество итераций в алгоритме восстановления сигнала Гриффина-Лима [По умолчанию n_iter = 32 (я буду использовать на много меньшее значение = 3)]
  • n_timeframe - После формирования mel-спектрограммы с заданными параметрами получаем матрицу размерностью (512 x 87), Где первый параметр равен n_mels - количеству фильтров mel-спектра, а второй параметр буду обрезать до n_timeframe, в нашем случае он равен 80, на выходе получится матрица размером (n_mels x n_timeframe). Такие размерности данных будем использовать для обучения модели.
  • n_sec - длина кусочка mp3 файла в секундах.
  • part_count - количество частей, на которые будет нарезан исходный mp3 файл.

Нарезка mp3 файла на обучающую и проверочную выборки

import warnings
# отключаем вывод предупреждений
warnings.simplefilter("ignore")
i = 0

# Таймер для расчета общего времени выполнения формирования нарезок файлов
t1 = MeasuringRunTime.Timer()
# Таймер для расчета времени выполнения одной итерации
t2 = MeasuringRunTime.Timer()
print("СТАРТ")

# Получаем список файлов
mp3s = os.listdir(mp3_dir)
mp3s = sorted(mp3s)

# Фильтруем файлы от системных
mp3s = list(filter(lambda x: x[0] != '.', mp3s))
# Отбираем файлы только с расширение mp3
mp3s = list(filter(lambda x: x.split('.')[-1] == 'mp3', mp3s))

count = len(mp3s)
for mp3 in mp3s:
  t2.Start()
  label = int(mp3.split('.')[0])
  # Создаем директории с именем метки класса для каждого mp3 файла
  train_cl_full_path = create_labeled_dir(train_dir, label)
  val_cl_full_path = create_labeled_dir(val_dir, label)

  full_path = os.path.join(mp3_dir, mp3)
  # Загружаем файл mp3
  X, sr = librosa.load(full_path)

  part = int(sr * lhp.n_sec)
  n = len(X) - part + 1
  step = n // lhp.part_count if n // lhp.part_count < sr else sr

  # создаем файлы mel-спектрограмм нарезанных кусочков
  create_mel_files(X, sr, 0, n, step, part, train_cl_full_path, label)
  create_mel_files(X, sr, step // 2, n, step, part, val_cl_full_path, label)
  i += 1
  print(f"Шаг {i} / {count}  -  {t2.Stop()} сек.")
print(f"Общее время выполнения {t1.Stop()} сек. ( ~ {int(t1.Stop() // 60)} мин. )")

Скажу сразу, это не быстрый процесс, одна итерация, т.е. 1 mp3 файл нарезается примерно 25 секунд!!!

На рисунке ниже отображено как нарезается mp3 файл. step рассчитывается исходя из длины файла и количества частей на которые он нарезается. Для проверочной выборки первый кусочек начинается с позиции step / 2, таким образом кусочки пересекаются между собой, но только частично. А обучающая и проверочная выборки абсолютно отличаются между собой.

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

def create_mel_files(X, sr, start, n, step, part, parent_dir, label):
  k = 0
  for i in range(start, n, step):
    # Формируем mel-спектрограмму
    mel_spec = librosa.feature.melspectrogram(y=X[i:i + part],
                                              sr=sr,
                                              n_fft=lhp.n_fft,
                                              hop_length=lhp.hop_length,
                                              win_length=lhp.win_length,
                                              n_mels=lhp.n_mels)
    # Переводим энергию сигнала в децибелы
    mel_log = librosa.power_to_db(mel_spec)
    # Формируем имя файла
    filename = str(k).zfill(max_len_filename) + ".pickle"
    full_filename = os.path.join(parent_dir, filename)
    # Сохраняем файл на диск
    dumppickle(full_filename, {'X': mel_log[:, :lhp.n_timeframe], 'y': label})
    k += 1

Функция dumppickle сохраняет любой объект в файл.

В нашем случае объектом будет справочник, где в значении 'X' будет храниться матрица размерностью (lhp.n_mels, lhp.n_timeframe), а в значении 'y' номер музыкальной композиции.