В этой статье разберём, что есть scheduler в диффузионных моделях и как можно подменять их, пользуясь возможностями библиотеки diffusers.
![](https://habrastorage.org/getpro/habr/upload_files/869/6d5/a7f/8696d5a7f0f7427f7747d04d99a74fa7.png)
Stable Diffusion
Для начала пару слов и картинок о рассматриваемой модели. Stable Diffusion успел нашуметь в 2022 году, и, кажется, многие новости будут связаны с этим в будущем. Модель эта примечательна в первую очередь тем, что способна по текстовому запросу, наподобие запроса к поисковику, выдавать картинки в высоком качестве, с проработанными деталями и, во многих случаях, отражающими суть текстового запроса. И, что немало важно, часто превосходя качество генерации с помощью GAN.
![Пример работы SD ver. 1.5 [https://lexica.art/?prompt=80fabd79-fecf-47a1-acfc-559b2e8494dc] Пример работы SD ver. 1.5 [https://lexica.art/?prompt=80fabd79-fecf-47a1-acfc-559b2e8494dc]](https://habrastorage.org/getpro/habr/upload_files/40c/13e/6f2/40c13e6f2a962784587cdb86be5840ec.png)
Немного о подходах. Подход к генерации изображения из текста называется text2image. Кроме него Stable Diffusion (и аналогичные ей проприетарные модели: dalle2, imagen и midjourney) способны генерировать:
image2image, когда входная картинка и запрос используются как отправная точка для генерации нового изображения,
image inpainting, когда с картинкой вдобавок на вход подается маска, где на изображении требуется генерация, а где нет,
upscaling, этот подход похож на image2image, тоже на вход картинка, но на выходе не модификация ее под текстовый запрос, а изображение в увеличенном разрешении.
Тут стоит отметить, что это не всё, что уже можно делать с помощью Stable Diffusion. В качестве отправной точки можно взглянуть на другие пайплайны по ссылке https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion.
Генерация с помощью Stable Diffusion
Немного разберём составные части пайплайна работы модели, чтобы познакомиться с тем, с чем мы будем экспериментировать далее.
![Диаграмма из блогпоста [https://huggingface.co/blog/stable_diffusion] Диаграмма из блогпоста [https://huggingface.co/blog/stable_diffusion]](https://habrastorage.org/getpro/habr/upload_files/ec3/5bd/6ce/ec35bd6ce1aab84f8502f8768e6904c5.png)
На этапе инференса, т.е. когда модель, уже обученная на огромном количестве пар текстовых описаний с картинками, генерирует изображения по тексту, мы имеем следующую схему работы. В ней два входа: эмбеддинг входного текста, т.е. обработанный с помощью текстовой модели CLIP текст, разбитый на токены, и latent seed, массив размера 64 на 64, состоящий либо из случайных чисел из гауссова шума, либо заполненный с помощью выхода энкодера VAE. "Смесь" этих двух входов подаётся в сеть архитектуры UNet, чьей задачей является предсказать, что в данном массиве 64x64 (тензоре) является шумом, чтобы затем, с помощью шага scheduler очистить текущий вход от предсказанного шума и повторить процедуру. В зависимости от выбора алгоритма планировщика, варьируется и количество повторений. После этого цикла результат отправляется в декодер VAE, который выдаёт окончательное изображение. Здесь примечательно, что очистка изображения от шума происходит сугубо в латентном пространстве, т.е. в пространстве тензоров 64x64, а декодирование в картинку происходит лишь однажды, но, как мы увидим позже, если декодировать latent на промежуточных этапах в картинку, то будет явно заметна очистка от шума и в изображении.
О роли Scheduler
Подробно о "планировщиках" в генеративных моделях описано в статье от NVLabs https://arxiv.org/pdf/2206.00364.pdf. Попробую, донести ту суть, которую я сам уловил из статьи и опыта работы со stable diffusion.
Scheduler используется и на этапе обучения, и на этапе инференса для восстановления зашумленного изображения.
Глобально существует два вида планировщиков: с детерминистическим сэмплированием и со стохастическим.
Первый тип, в N шагов числено решает обыкновенное дифференциальное уравнение, динамики движения изображения к абсолютному шуму или в обратную сторону, к изображению.
Второй тип работает лучше с точки зрения качества генерации. На каждом шаге планировщика к данным добавляется свежий шум семплированный из некоторого распределения. В этом случае уже решается стохастическое дифференциальное уравнение.
Scheduler в паре с UNet* образуют цикл, в котором решается ДУ. Где UNet – это нейронная сеть, которая учится минимизировать ошибку денойзинга, которая соответственно выполняет процедуру очистки от шума. Участвует в численном решении дифференциального уравнения [см. уравнения 2, 3 и алгоритм 1 из ссылки выше], путём того, что результат сети перевычисляется на каждом шаге схемы.
* – помимо UNet могут быть сети и другой архитектуры.Некоторые исследования сфокусированы на поиске оптимального планировщика, который имеет минимальное число NFE (neural function evaluation), т.е. количество шагов схемы решения ДУ, за которое можно добиться качественного результата.
Так, кратко в пунктах, могу описать роль планировщика в диффузионных моделях, если возникли вопросы в результате прочтения – давайте обсудим в комментариях!
Эксперимент
Как уже сказано выше, будем пользоваться библиотекой diffusers. Весь эксперимент описан в следующем сниппете. Комментарии по возможности добавлены.
Суть эксперимента заключается в том, чтобы увидеть качественную разницу между генерациями различными подходами по одному заданному текстовому запросу, в нашем случае первый запрос, это гибрид Маска и Карателя, второй – кот из примера вверху. При этом фиксируя random seed. Увидеть, на каком этапе очистки от шума, при различных подходах, проявляется вменяемое изображение, и как результаты разнятся между собой.
Код исполнялся на Tesla T4 в облаке и генерация четырех изображений по запросу во всех случаях занимала 01:42 min. Номера шагов указаны красным в верхних строках.
EulerDiscreteScheduler
![EulerDiscreteScheduler EulerDiscreteScheduler](https://habrastorage.org/getpro/habr/upload_files/ebc/3d6/0d1/ebc3d60d17a102d11371e42f58737856.png)
![](https://habrastorage.org/getpro/habr/upload_files/b04/d8e/bdd/b04d8ebddb903d11cc2213206e8f78c1.png)
В случае Маск-Каратель, на 50-м шаге начинает проявляться изображение, которое мы четко видим на шаге 100. Этот scheduler создает довольно кинематографичные результаты, особенно второй и третий сверху, но на двух из четырех толком не видно лица.
EulerAncestralDiscreteScheduler
![EulerAncestralDiscreteScheduler EulerAncestralDiscreteScheduler](https://habrastorage.org/getpro/habr/upload_files/7fb/cc0/d4d/7fbcc0d4d62949616aca0ca99233fa97.png)
![](https://habrastorage.org/getpro/habr/upload_files/fcb/60b/b37/fcb60bb3792935ee197ccb9550f15d73.png)
Следующий scheduler, несмотря на схожее название с первым, даёт довольно иные результаты. Цветовая схема на третьем финальном изображении совпадает с третьим из предыдущего, поза персонажа и ракурс съемки на первом тоже похожи.
DDIMScheduler
![DDIMScheduler (default) DDIMScheduler (default)](https://habrastorage.org/getpro/habr/upload_files/e9e/22d/f29/e9e22df2912aa356d92ee4c3fe7dbcff.png)
![DDIMScheduler (default) DDIMScheduler (default)](https://habrastorage.org/getpro/habr/upload_files/3d5/a08/0fe/3d5a080fe5acfcb5b924f6b2e907e2fd.png)
Результаты сети с этим scheduler вообще не отражают сути запроса. В первой четверке, на четвертом изображении на шаге 100, что-то похожее на гуманоида, но и то отдаленно. Стоит также отметить, что к сотому шагу, очистка изображения от шума, кажется, еще не завершилась.
UPDATE:
Если использовать дефолтный DDIMScheduler, инициализированный с помощью from_config, то мы увидим результаты, не соответствующие запросу, как показано выше. Но если создавать планировщик явно, используя параметры из этого обсуждения (https://github.com/huggingface/diffusers/issues/706), то результаты будут гораздо ближе, к тому, что мы видим при других планировщиках. Спасибо за бдительность: @trix
# change this for DDIMScheduler
scheduler = sched_class.from_config(config)
# to this
scheduler = DDIMScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False, set_alpha_to_one=False)
![DDIMScheduler (optimized) DDIMScheduler (optimized)](https://habrastorage.org/getpro/habr/upload_files/a17/63e/02f/a1763e02fb630d8089260e220bd7e5b2.png)
![DDIMScheduler (optimized) DDIMScheduler (optimized)](https://habrastorage.org/getpro/habr/upload_files/c2a/e31/646/c2ae316461dba91f28ead5907eb6a9ed.png)
DDPMScheduler
![DDPMScheduler DDPMScheduler](https://habrastorage.org/getpro/habr/upload_files/d41/9f4/c2f/d419f4c2f0c9490e6d2a354252ab08fe.png)
![DDPMScheduler DDPMScheduler](https://habrastorage.org/getpro/habr/upload_files/1b0/800/e4e/1b0800e4e43c697d740565a735e143e2.png)
В этом плане DDPMScheduler также показывает себя похожим образом, на сотом шаге нет результата, на котором отчетливо различим результат запроса. Касаемо этого алгоритма стоит отметить, что он используется при обучении Stable Diffusion и в этом процессе количество шагов выше на порядок. Поэтому мы и не наблюдаем картинок на сотом шаге, а только лишь шум.
Другие подходы
Далее идут четыре алгоритма, которые производят крайне похожие результаты при одинаковых начальных данных. На всех четырех четверках, для обоих запросов, мы видим похожие изображения, различимые, при детальном рассмотрении.
DPMSolverMultistepScheduler почти не отличим от DPMSolverSinglestepScheduler. LMSDiscreteScheduler явно имеет иную структуру шума на ранних этапах. А PNDMScheduler отказался вызывать callback с сохранением изображения на первом шаге денойзинга.
![DPMSolverMultistepScheduler DPMSolverMultistepScheduler](https://habrastorage.org/getpro/habr/upload_files/576/b9b/0b7/576b9b0b748f49b56720ea5b6c6f52fb.png)
![DPMSolverMultistepScheduler DPMSolverMultistepScheduler](https://habrastorage.org/getpro/habr/upload_files/b8d/afa/d09/b8dafad0954dec1c5177997f8a345df0.png)
![DPMSolverSinglestepScheduler DPMSolverSinglestepScheduler](https://habrastorage.org/getpro/habr/upload_files/91b/2d3/6aa/91b2d36aaa980f50c2b8c125a2c62e65.png)
![DPMSolverSinglestepScheduler DPMSolverSinglestepScheduler](https://habrastorage.org/getpro/habr/upload_files/8cd/554/aaf/8cd554aafe35f3105639e68f0321780b.png)
![LMSDiscreteScheduler LMSDiscreteScheduler](https://habrastorage.org/getpro/habr/upload_files/ed7/11b/02f/ed711b02f752fbbff94d91826b8be92a.png)
![LMSDiscreteScheduler LMSDiscreteScheduler](https://habrastorage.org/getpro/habr/upload_files/668/f50/51b/668f5051b8976b7e153102625c564c96.png)
![PNDMScheduler PNDMScheduler](https://habrastorage.org/getpro/habr/upload_files/cc2/614/8e9/cc26148e9d5548c9e51b0b9ad5d4b66b.png)
![PNDMScheduler PNDMScheduler](https://habrastorage.org/getpro/habr/upload_files/6d3/532/cb9/6d3532cb9c0c7b5aeb318be1c4a5eb3d.png)
Заключение
В этой статье мы совершили небольшой обзор архитектуры диффузионной генеративной модели stable diffusion. Кратко затронули основные пункты о роли scheduler в этой архитектуре, что за планировщиком кроется численное решение СДУ или ОДУ. Воспользовались библиотекой diffusers для работы со stable diffusion, применили механизм коллбэков для генерации изображений на промежуточных этапах очистки от шума. И, наконец, подменяли на ходу алгоритмы планировщика, для того, чтобы сравнить результаты генераций в зависимости от выбора алгоритма.
Разработать одну ML-модель и провести ее валидацию уже давно не проблема. Хочу пригласить всех на бесплатный вебинар, где вы узнаете как экспериментировать сразу с несколькими ML-моделями, с разными гиперпараметрами и при этом не захлебнуться в разнообразии экспериментов. Как, проводя регулярное переобучение, получить возможность сравнивать качество работы моделей и выбирать лучший результат. И как не потерять накопленный опыт и воспроизводить более ранние эксперименты.