Если вы не провели последние два года на ферме в Сибири, вы, вероятно, слышали о Stable Diffusion или пробовали генерировать изображения с помощью моделей, вроде Dall-e или Midjourney. Они становятся все лучше каждый день, и по качеству уже сравнимы с людьми, а во многих аспектах даже лучше (например, им не нужно платить ?).

Исследования в области создания видео уже идут полным ходом во многих лабораториях и компаниях, так что это лишь вопрос времени, когда генеративные модели сместят людей с очередного столпа на котором держится наше общества — порно. Я не вижу чтобы кто то поднимал тревогу об огромном количестве людей, которые потеряют работу из-за этого. Я не такой бессердечный, поэтому, прежде чем наступил этот печальный момент, я решил принять меры ?‍♂️ и создать базовое руководство, которое даже работник индустрии для взрослых сможет понять и использовать, чтобы оставаться в игре. Давайте посмотрим, что к чему.

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

Нет, они этого не сделали (я надеюсь). Технология, которая стоит за всеми этими инновациями, называется Stable Diffusion, и в основе она довольно проста. Не нужно иметь степень мага из Хогвартса, чтобы понять ее на базовом уровне. Единственная магия здесь, как всегда, в ее простоте.

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


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

Каменный век (1950-е - 1980-е)

Эра пещерных рисунков ИИ. Ранние нейронные сети были похожи на малышей с карандашами, рисующих простые каракули. Результаты были настолько удручающи, что Скайнет, дабы избежать позора, отправил Терминатора в прошлое, чтобы убить всех первых исследователей, и это вызвало Первую зиму ИИ (подробнее об этом смотрите в предстоящем приквеле Терминатора от Джона Кэмерона).

Отрочество (1980-е - 2000-е)

После первой зимы ИИ интерес к нейронным сетям был возрожден благодаря работе таких исследователей, как Джон Хопфилд и Дэвид Румельхарт. Сети Хопфилда и машины Больцмана были значительным прорывом.

Введение алгоритма обратного распространения (backpropagation) ошибки Румельхартом, Хинтоном и Уильямсом позволило нейронным сетям эффективно корректировать свои внутренние параметры и стало основой всего машинного обучения.

Рекуррентные нейронные сети (Recurrent Neural Networks or RNN) и сети долговременной кратковременной памяти (Long Short-Term Memory or LSTM) (1990-е): RNN, предназначенные для обработки последовательностей данных, а не отдельных точек данных, стали прорывом в работе с зависимостями в последовательностях, что было критически важно для задач, таких как распознавание речи. В каком то смысле это был аналог человеческой памяти при обработке последовательностей.

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

Бум глубокого обучения (2000-е - 2010-е)

Наступила эра сверточных нейронных сетей (Convolutional Neural Networks or CNN), что можно сравнить с появлением Фотошопа для художников. Основная идея заключалась в использовании небольших операций (подпрограмм), называемых "convolutions", для обработки изображений путем их применения к частям изображения для выявления определенных особенностей (features, например, колеса у автомобиля) до тех пор, пока не будет обработана каждая часть, вместо попытки обработать все изображение сразу.

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

Этот период также ознаменовался разработкой и популяризацией фреймворков глубокого обучения, таких как TensorFlow (разработанный командой Google Brain), Keras и PyTorch (за что им большой респект).

Появление роботов художников (2014)

На сцену вышли генеративно-состязательные сети (Generative Adversarial Networks или GAN), и ИИ поступил в художественную школу.

Ян (или Иан?) Гудфеллоу и его коллеги представили GAN, что стало крупным прорывом, несмотря на несколько пугающее название. GAN состоит из двух частей: генератора и дискриминатора. Генератор создает изображения из случайного шума, а дискриминатор оценивает их по сравнению с реальными изображениями. Генератор был увлеченным художником, рисующим вовсю, а дискриминатор - строгим, носящим очки критиком, постоянно говорящим: "Мда.. ты можешь лучше".

Две сети обучаются одновременно в виде своего рода соревнования: генератор учится производить более реалистичные изображения, а дискриминатор учится лучше различать реальные изображения от подделок. GAN использовались для создания высокореалистичных изображений, создания искусства и даже дизайна одежды.

Примерно в то же время на вечеринку пришли вариационные автокодировщики (Variational Autoencoders или VAE). VAE предназначены для создания новых образцов данных, изучая представление входных данных в скрытом пространстве. Они кодируют входные данные в сжатое представление, а затем декодируют это представление обратно в исходный формат данных (я знаю, сейчас это звучит как бред, не волнуйтесь — я объясню это подробно).

Ренессанс (конец 2010-х - сейчас)

С развитием продвинутых моделей, таких как DeepDream от Google, ИИ начал производить изображения, которые выглядели так, как будто Сальвадор Дали и Ван Гог зачали ребенка в киберпространстве. DALL-E от OpenAI начал превращать случайные тексты вроде "кресло в форме авокадо" в настоящее искусство. А StyleGAN от NVIDIA? Он начал производить лица людей, которых не существует, заставляя вас задуматься, не является ли ваш следующий матч на Tinder просто плодом воображения ИИ.

Первая модель Stable Diffusion, аналогичного типа генеративной модели ИИ, была выпущена в публичное пространство компанией Stability AI в августе 2022 года.

Stable Diffusion

Как вы знаете из моих предыдущих статей, в мире ИИ именованием вещей заведуют какие то маньяки. По лучшим традициям масонов все называется так, чтобы никто не догадался, что означает что, и разработчики в AI могли оправдать свои огромные зарплаты. Я рад сообщить, что Stable Diffusion - счастливое исключение: название полностью логично.

Stable Diffusion - это название, отражающее основные технологии и принципы этой архитектуры ИИ:

Стабильность (Stable): этот термин относится к стабильности генерируемых изображений и процесса их создания, что означает, что модель производит последовательные полезные результаты и менее склонна к непредсказуемому поведению. Это избыточная часть названия - конечно, она стабильна, зачем бы я использовал нестабильную? Это как назвать автомобиль “стабильным автомобилем”. Но, по крайней мере, это логично.

Диффузия (Diffusion): это ключ к пониманию магии. Термин “диффузия” происходит от процесса диффузии (растекания, растворения), используемого в архитектуре модели. Модели диффузии учатся генерировать данные, обращая вспять процесс диффузии. Этот учебный процесс обычно начинается с реального изображения, к которому постепенно добавляется случайный шум, пока исходные данные полностью не размываются (diffuse) в нем. Затем модель учится обращать этот процесс, начиная со случайного шума и постепенно формируя оригинальное изображение, соответствующее данному вводу или запросу. 

Вот аналогия: скульптор смотрит на бесформенный кусок мрамора (чистый шум), но видит в нем прекрасную скульптуру. Поэтому он удаляет лишние части (излишний шум), чтобы проявить скульптуру. Таким образом, в некотором смысле, он де-ноизирует кусок камня в скульптуру. А как он увидел скульптуру в куске камня? Он был обучен видеть это (опять же, никакой магии ?). В некотором смысле, он видит скульптуры во всем, как человек с молотком везде видит гвоздь

Нарисовано с использованием Stable Diffusion

Таким образом, “Stable Diffusion” как название охватывает идею генеративной модели ИИ, которая стабильно производит высококачественные изображения через процесс диффузии.


Теперь давайте посмотрим, как это происходит внутри.

Обучающий набор

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

Так что нам действительно нужно, чтобы это сделать возможным, - это разработать процесс обучения такой модели. Чтобы обучить модель, нам нужны данные для обучения. Какие данные?

Ну, как указано выше, нам нужно много пар изображений, где первое изображение - просто шум, а второе - осмысленное изображение, которое можно получить из этого шума, используя заданный промпт, что-то вроде такого:

Промпт: сгенерировать логотип компании Raft в виде осьминога-диджея

Итак, имея промпт и изображение с случайным шумом - и желаемый результат, мы можем обучать модель, используя стандартные методы Машинного Обучения, такие как градиентный спуск, чтобы предсказать изображение из данного шума. Легко, правда? Ну, не совсем так.

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

Но исследователи были действительно изобретательны: они решили - а почему бы нам не сделать наоборот? Почему бы нам не взять реальное изображение и добавить к нему шум. Таким образом, мы знаем желаемый результат с самого начала и знаем точное количество добавленного шума. Это же гениально:

И используя это шумное изображение, мы можем легко предсказать, как оно должно быть преобразовано в реальное изображение. Как удобно:

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


Обучение

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

Предсказание шума и удаление его из изображения

Таким образом, нам нужна модель, которая говорит - для этого случайного шумного изображения вам нужно сделать эти пиксели темнее, а эти светлее, чтобы получить осьминога-DJ.

Модели не работают с пикселями, поэтому нам нужно преобразовать изображения в форму, которую модель может понять. К счастью, изображение - это просто матрица с цветами пикселей, где цвет - это просто число:

И модель - это просто огромная функция с множеством параметров, которая работает с матрицами (технически с тензорами, которые являются матрицами с N измерениями).

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

function loss_for_images(image_bytes1, image_bytes2):
  var result:Float = 0;
  for (i=0; i < image_bytes1.Length; i++):
    result = image_bytes1[i] - image_bytes2[i];
  return result/image_bytes1.Length;

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

Одна вещь, которую нужно отметить - мы не делаем это за один шаг. Мы де-ноизируем маленькими итеративными шагами (концепция, аналогичная скорости обучения  learning rate в NLP). Если вы читали мою статью о градиентном спуске, вы помните это изображение:

Перепрыгнул ?

Если двигаться слишком быстро, обычно это приводит к не очень хорошим результатам из-за природы используемых под капотом техник оптимизации (собственно причина в том как обучалась модель, она как правило обучается на немного зашумленных изображениях, и не умеет убирать огромное количество шума сразу; ну и в целом, семь раз отмерь - один раз отрежь). Вместо этого используются такие приемы как временные шаги (t или timesteps). Это не имеет ничего общего со временем (как и следовало ожидать ?‍♂️), вместо этого происходит следующее:

Мы берем эту функцию, где на каждом шаге есть некоторое количество шума. Для шага t номер 3 шума много, а для t = 40 - гораздо меньше. Этот t сообщает модели, сколько шума было добавлено (но только сколько, а не точный шум, который был добавлен). Мы будем использовать t во время обучения и генерации изображений. В моделях диффузии есть двухфазный процесс: фаза добавления шума и фаза удаления шума (де-ноизирующая).

  1. Процесс добавления шума:

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

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

  2. Процесс удаления шума:

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

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

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

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

Обычно процесс происходит за несколько итераций

Если вы когда-либо использовали Midjourney, то уже наблюдали этот итеративный процесс.


Это базовая идея, но есть несколько технических трудностей. При обучении модели, как вы помните, мы должны как-то учитывать вторую часть входных данных модели - промпт. То есть, как обучить модель так, чтобы она генерировала изображения, связанные с запросом, а не то, что она сама хочет сгенерировать?

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

Настало время слону прыгнуть через обруч, так как тут появлятся второй гениальный трюк:

CLIP

Термин "кодировщик CLIP" относится к Контрастному Языковому-Изобразительному Предобучению (Contrastive Language-Image Pretraining). Он предназначен для работы с двумя типами данных: изображениями и текстом, и преобразования этих двух типов данных в общее, общее векторное представление (embedding), где они могут быть сравнены и сопоставлены.

Кодировщик CLIP обычно состоит из глубокой нейронной сети, часто основанной на архитектуре сверточной нейронной сети (CNN) для обработки изображений и архитектуре, основанной на трансформере, для обработки текста. Эти компоненты объединяются для создания мультимодального кодировщика (т.е. кодировщика , понимающего несколько модальностей, как мы например можем понимать русский и английский язык).

А теперь простыми словами:

Нам нужна модель, которой если вы дадите изображение (скажем, нашего Осьминога) - она вернет вам embedding или вектор чисел, представляющий это изображение:

И если вы дадите ей текстовую строку, скажем "Осьминог DJ", она даст вам тот же вектор:

Как она решает, каким должен быть вектор, не важно, пока она возвращает тот же вектор для изображений и текстового представления этих изображений (технически это не так, они не будут точно такими же, просто достаточно близкими в векторном пространстве - см. ниже). Если у вас есть это, вы можете затем передать этот вектор в нашу первую модель (ту, что предсказывает шум) как параметр для обучения. Тогда модель научится, что определенные векторы на входе соответствуют определенным изображениям (Осьминог в нашем случае).

Как получить модель CLIP? Она на самом деле состоит из двух пар: текстового кодировщика и кодировщика изображений:

  • Текстовый кодировщик кодирует текст в векторное представление (embedding).

  • Кодировщик изображений кодирует изображения в векторное представление (embedding).

  • Текстовый кодировщик кодирует текст в векторное представление (embedding).

  • Кодировщик изображений кодирует изображения в векторное представление (embedding).

Теперь мы берем много пар текста и изображений, представляющих этот текст, и преобразуем их в встраивания с помощью двух вышеупомянутых кодировщиков. Затем мы подаем их в нашу модель CLIP, которая будет учиться, используя функцию контрастной потери (contrastive loss function). Эта функция побуждает модель размещать соответствующие пары изображений и текста ближе друг к другу в векторном пространстве и отталкивать несоответствующие пары дальше друг от друга. По сути, функция потери в этом случае рассчитывает число (оценку) того, насколько похож данный текст на данное изображение (оба передаются как параметры). Если они похожи - оценка хорошая, и эти два должны быть близки в векторном пространстве:

Как вы можете видеть, наше изображение и текст "Осьминог DJ" близки, в то время как "Белка DJ" и просто картинка осьминога далеки друг от друга в нашем 2D-пространстве.

Если поразмыслить, это как бы тот же процесс, который происходит в вашей голове. Если вы думаете о слове "кролик" и если вы представляете себе изображение кролика - примерно та же область вашего мозга активируется. Где-то в вашем разуме есть представление о кролике. Модель CLIP возвращает представление этой идеи в виде ембединга, и мы обучаем нашу модель работать в пространстве идей, а не в пространстве изображений или текста. И поэтому она затем может сочетать авокадо и стул в идее авокадового стула.

Или на псевдокоде:

def clip_embedding(image or text)
  ...
  return result_embedding # возвращает похожие встраивания для похожих текстов и изображений

Если вы действительно хотите знать, как это происходит внутри, вот ниже немного математики (не важно для понимания, так что вы можете спокойно пропустить):

Сontrastive loss function

Контрастная потеря

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

Где:

  • (i, j) - представляет пару точек данных.

  • y - это бинарная метка, указывающая на то, похожа ли пара (1) или нет (0).

  • D(i, j) - это мера расстояния между встраиваниями точек i и j (часто это евклидово расстояние, но в моделях диффузии чаще используется косинусное сходство).

  • m - это граница, определяющая, насколько далеко должны быть друг от друга непохожие точки. Если расстояние между непохожими точками больше m, их вклад в потерю равен нулю.

Как используется контрастная потеря:

  • Для каждого изображения в обучающем пакете соответствующее текстовое описание формирует положительную пару. Цель модели - приблизить встраивания этих пар друг к другу.

  • Одновременно каждое изображение сопоставляется с текстами, которые его не описывают (и наоборот), формируя отрицательные пары. Модель пытается оттолкнуть встраивания этих пар друг от друга.

  • Расчет потерь: контрастная потеря вычисляется путем измерения того, насколько хорошо модель выполняет эту задачу по всем парам в пакете, а затем запускается обратное распространение. Цель - минимизировать эту потерю, эффективно обучая модель правильно ассоциировать тексты с соответствующими им изображениями.

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

Latens и VAE

Как я обещал, вот объяснение вариационных автоэнкодеров (Variational Autoencoders или VAE). Авто-энкодер - это архитектура нейронной сети, состоящая из кодировщика и декодера. Кодировщик берет входную точку данных и сжимает ее в представление скрытого (latent) пространства (кодирование). Затем декодер берет это кодирование и пытается воссоздать исходный ввод.

Скрытое пространство (latent space) - это пространство с меньшей размерностью, куда кодировщик отображает входные данные. Его часто называют "скрытым представлением".

А теперь простыми словами:

Кодировщик сжимает изображение в скрытое (меньшее) пространство, а декодер восстанавливает его до оригинального размера. Вот и все. За исключением того, что это делается не с помощью сжатия jpeg, а с помощью нейронной сети, обученной делать это.

Это сжатие с потерями, что означает, что восстановленное изображение не будет точно таким же, как оригинальное - некоторые детали будут потеряны! Но для наших целей это не важно. Важно то, что (в отличие от jpeg) сеть будет уменьшать исходное изображение, сохраняя все важные особенности (вещи, важные для этого изображения, например, что это колесо автомобиля, а это бампер, и глобальная композиция частей тоже). Фактически, она создаст отдельные слои на выходе для этих особенностей (features). Она способна улавливать как локальные, так и глобальные особенности изображения, поэтому она сжимает очень по умному! (приведу пример такого сжатия: можно по разному расписывать то что частенько происходит в моем городе с уборкой снега на тротуарах, но можно и сжать до одного слова - п...ец).

Зачем нам это нужно? Ну, как вы помните, чтобы обучить модель де-ноизации, нам нужно подавать ей изображения. Если мы подаем реальные изображения, которые обычно имеют огромное разрешение (1024x1024 и выше) - нам понадобится слишком много вычислительной мощности для обучения! Оказывается, мы можем обучаться на данных меньшего размера (если они захватывают все важные особенности) с аналогичными результатами! Это имеет смысл, ведь не размер изображения важен - важно, насколько точно изображение отображает то, что на нем изображено. Подробнее тут.

Итак, результирующая архитектура выглядит так:

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

  • Декодер: часть декодера отвечает за увеличение размера карт особенностей. Он зеркально повторяет архитектуру кодировщика, но в обратном порядке. Он состоит из нескольких слоев, которые постепенно увеличивают пространственные размеры, уменьшая количество каналов.

Эта архитектура называется UNet, и Stable Diffusion основана на UNet.

На сегодня с основами все, удачи!

Если у вас есть вопросы или комментарии - оставьте их в разделе комментариев.


Я со-основатель AI интегратора Рафт.

Если вам не интересно о чём я пишу – не подписывайтесь на мой личный блог, пожалуйста!

Всем добра и позитивного настроения!