Skip to main content

HiFIGAN by teasgen

Отчет об обучении HiFiGAN на датасете LJSpeech в рамках курса DLA AMI HSE
Created on December 7|Last edited on December 8


Обучение

HiFiGAN - Vocoder GAN модель, обученная по Mel-спектрограмме генерировать Wav.
Генератор
вид сверху

чуть подробнее
Cтакаем TransposedConv1d поверх мел спеки, чтобы привести по длине к длине wav. Параллельно манипулируем с каналами так, чтобы количество hidden каналов увеличивалось каждый раз на 2, а потом свели к 1 каналу (в wav 1 канал)
В статье авторы эксперемнтируют с тремя версиями генератора, я выбрал V1 (эксперементировал с V2 при дебаге, но так как планировал учить долго, то решил взять модель побольше)

версии генератора
Дискриминатор
вид сверху на дискриминаторы
Multi-period Discriminator (MPD) - авторы вспоминают, что звук это сумма синусоид, поэтому трансформируют 1-d wav в 2-d тензор так, что каждый p-ый элемент 1-d встает в соответствующую строку в новом 2-d тензоре, то есть авторы так пытаются учесть частоту. Как в статье, я использовал в коде p2,3,5,7,11p\in{2,3,5,7,11} (то есть несколько дискриминаторов). Так как генератор не выдает длину всегда кратную всем pp, то я паддил аудио до ближайшего кратного с помощью отражающего паддинга

чуть подробнее MPD
Multi-scale Discriminator (MSD) - авторы как порядочные люди просто ссылаются на другую статью MelGAN, откуда я и нагло своровал все параметры, так что не знаю на самом деле какие параметры блоков использовали в MSD HiFiGAN, но у меня хорошо обучилось и с MelGAN архитектурой
чуть подробнее MSD из статьи MelGAN
Loss
4 части
  • стандартный лосс генератора и дискриминатора

  • еще хотим совмещать спектрограммы генерируемого wav и реального

  • и чтобы совсем ближе делать генерацию вавки, то давайте еще сближать фичи дискриминаторов с каждого слоя для сгенерированной wav и реальной

Лосс дискриминатора - стандартный LAdv(D;G)L_{Adv}(D;G)
Лосс генератора - LAdv(G;D)+2LossFM(G;D)+45LossMel(G)L_{Adv}(G;D) + 2 * Loss_{FM}(G;D) + 45 * Loss_{Mel}(G)
Обновление весов
  1. Считаем градиент по дискриминатору, при этом не забывая сделать detach для выхода генератора
  2. Считаем градиент по генератору
Баги
  • Надо делать .item().item() когда логгируешь тензоры, иначе быстро ООМимся по cuda памяти
  • Долго сидел с тем, что ничего не училось, хотя на ванбатче все было ок. Случайная мысль через несколько часов (p.s вспомнил что год назад видел такое) - я считаю неправильно лосс на мультибатче. И пошел просто проверять все тензоры, в итоге нашел, что mel spec для gt была вида [BS, C, L], а mel spec для generated была вида [BS, 1, C, L], происходил неправильный бродкаст и LMelL_{Mel}  ломался.
Нашел загадочные слова в статье "weight normalization" и выяснил, что это означает, что нужно обернуть все свертки в модели в torch.nn.utils.weight_norm (p.s только в MSD генераторе для первого дискриминатора без пуллинга использовали спектральную нормализацию, я на 100% уверен, что это неважно вообще, но ладно)
Когда пофиксил баги и вставил нормализацию, то все запустилось

Рецепт обучения
  • Оптимизатор - для генератора и дискриминатора AdamW с wd = 1e-2 и
    • LR = 3e-4 для G
    • LR = 2e-4 для D (< LR_G чтобы чуть медленнее обновлялся, но это скорее для души, на деле вряд ли имело значение)
  • Шедулеры
    • 1 стадия обучения (стадии описаны чуть ниже) - ExponentialLR для G и D с gamma = 0.999
    • 2-3 стадии обучения (стадии описаны чуть ниже) - OneCycleLR c Cosine убыванием
  • Клипал градиенты для G до 10
  • При обучении выбираю рандомный подсрез аудио заданной длины
Авторы статьи учили на отрезках аудио длиной 8k, а решил пойти чуть дальше, так как в дз в итоге нам надо генерировать длинное аудио и сделал 3 последовательных обучения
  1. Делаем жесткий претрейн на 8к длину (~0.33 sec) 60 эпох - в итоге получаем робовойс. BS = 64
  2. Делаем жесткий файнтюн на 22к длину (~1 sec) еще 400 эпох - получаем хороший голос. BS = 32
  3. Понижаем init LR в 10 раз. Жестойчаще файнтюним на 44к длину (~2 sec) еще 100 эпох - получаем не отличимую генерацию на LJ, при этом по лоссам на тесте не переобучаясь. BS = 16
В графиках разрыв в итерациях из-за того, что я меня батч сайз, чтобы влезать на карту, поэтому просчет итераций немного поехал, но по запускам в wandb легко прослеживается цепочка


Run set
3

Итак,
  • После 8k обучения - заметны различия в спеки, слышен робовойс
  • После 22k обучения - спеки на глаз тяжело отличимы, в голосе слышны редкие подрагивания, но никакого робовойса уже давно нет
  • После 44k обучения - спеки неотличимы на глаз, голос идеально повторяет оригинал на мой скромный слух
Причем судя по L1-Mel test у меня нет переобучения под train, а значит я хорошо генерализовался In domain.
Теперь перейдем к результатам

Результаты

Все числа получены для модели, обученной на 44k с лучшим чекпоинтом по L1 метрике спек на тесте (это почти последняя эпоха)

Проверим качество генерации In domain Wav -> Mel Spec -> Wav

Для этого я взял 5 случайных wav семплов из теста LJ, сделал для них Mel Spec и сгенерировал вавку

Run set
13

Имхо я не слышу никаких отличий gt от synt
  • не вижу в вавке артефактов
  • не вижу артефактов в спеке + не вижу отличий в спеке, но L1 метрика показывает не 0, так что все же генерация не идеальная, но для человека неотличима
WavsWVMOS
Ground truth4.20
Generated4.01


Проверим качество генерации Out of domain Wav -> Mel Spec -> Wav


Run set
1

Тут получается результат явно хуже и можно легко отличить генерацию от оригинала, хотя все слова слышны хорошо
Мое предположение почему так - происходит жесткий out of domain, я учился много эпох на LJ с голосом одной девушки, поэтому vocoder переобучился под ее голос и не может повторять другие, так что отличаются частоты (по спекам видно, что они довольно отличаются от секции выше). Хотя при этом все произносится четко, без постороннего шума
WavsWVMOS
Ground truth3.43
Generated2.52


Проверим качество генерации Text-> Mel Spec -> Wav


Run set
1

Text -> Mel spec я делал в коде с помощью https://github.com/pytorch/hub/blob/master/nvidia_deeplearningexamples_waveglow.md#example так как у ребят такой же способ получчание спеки из аудио и очень легко переиспользуется модель.
WavsWVMOS
Generated3.70

Я считаю получившиеся аудио очень неплохого качества, neuro-MOS у них около 4 (по оценке WVMOS). Единственное отличие от генерации коротких звуков из первой секции результатов - появляется будто немного фон, по которому слушатель может определить, что это генерация. Есть два решения
  • Поучить на более длинные последовательности
  • При генерации спеки по тексту я делил текст по мини текстам между запятыми, иначе энкодер не работал с длинными последовательностями. Дальше спеки объединял в одну большую. Длинная спека дает артефакты, поэтому можно было бы генерировать вавку на мини спеки и потом уже соединять вавки
Однако в дз требовалось добиться качества - "слова понятны, шума нет"
В этой секции я мог бы дополнительно прогнать модель на текстах из LJ, но хорошее качество на длинных текстах и ��ак говорит за себя, поэтому проверка на LJ транскрипциях избыточна.

Вывод

Я обучил HiFiGAN на генерации аудио по mel-спектрограмме. При условии генерации голоса из обучения результат получается впечатляющим, на новые голоса генерализоваться не получилось. WVMOS к моему разочарованию очень плохо показывает качество wav, потому что для GT семплов далеко не 5 ставил. Однако по метрикам на тесте и слепому тестированию эта модель отлично обучилась, поэтому если потребуется адаптировать вокодер под разнообразные голоса, то надо расширять обучающую выборку, но я просто чилловый парень и сделал лучшее на этих данных
я
P.S спасибо за курс