ИИ для инженера: основы Диффузионные модели: как генерируются изображения
0%

Диффузионные модели: как генерируются изображения

Диффузионные модели: как генерируются изображения

Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 01:00:30 — отсюда взято содержание этой главы.

Вся предыдущая часть трека описывала одну схему порождения: модель выдаёт токен, дописывает его к контексту, выдаёт следующий. Так устроена цепь Маркова, так устроен трансформер, так устроен ответ чат-бота — подробно это разобрано в главе «Большие языковые модели».

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

Почему у картинки нет «следующего токена»

У текста есть естественный порядок: слова читаются слева направо, и «следующий элемент» — понятие осмысленное. У пикселей такого порядка нет. Можно, конечно, договориться обходить картинку строка за строкой, слева направо и сверху вниз, — ранние авторегрессионные модели изображений (PixelRNN, van den Oord et al., 2016) именно так и делали. Но у этого порядка два врождённых дефекта.

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

Он невыносимо медленный. Картинка 512×512 — это больше четверти миллиона пикселей. Даже один проход сети на пиксель означает четверть миллиона последовательных шагов, которые нельзя распараллелить по определению авторегрессии.

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

Прямой процесс: как из картинки делают шум

Начинают с конца — с задачи, у которой есть очевидное решение.

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

Это прямой процесс (forward process). У него две важные особенности: он не требует никакого обучения (это просто прибавление случайных чисел) и он полностью управляем — на каждом шаге точно известно, сколько шума добавлено.

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

x_t = sqrt(a_t) * x_0 + sqrt(1 - a_t) * eps

Здесь x_0 — исходная картинка, eps — стандартный гауссов шум того же размера, а a_t — накопленный коэффициент, который монотонно падает от почти единицы до почти нуля. При t близком к нулю в смеси почти вся картинка, при t близком к максимуму — почти весь шум.

Проверить это можно на четырёх строчках numpy — никакой нейросети здесь ещё нет:

import numpy as np

rng = np.random.default_rng(seed=42)      # тот же seed — тот же шум

# «Картинка» 4x4 в оттенках серого. Перед диффузией значения пикселей
# приводят к диапазону [-1, 1] — так шум и сигнал сопоставимы по масштабу.
x0 = np.linspace(-1.0, 1.0, 16).reshape(4, 4)

T = 1000                                   # длина расписания
betas = np.linspace(1e-4, 0.02, T)         # линейное расписание из статьи DDPM
alphas_bar = np.cumprod(1.0 - betas)       # накопленная «доля картинки»

def noise_at(x0, t):
    """Зашумление сразу на шаг t, без прохода по предыдущим шагам."""
    eps = rng.standard_normal(x0.shape)    # шум — обычные случайные числа
    a = alphas_bar[t]
    return np.sqrt(a) * x0 + np.sqrt(1.0 - a) * eps, eps

for t in (0, 100, 400, 999):
    xt, eps = noise_at(x0, t)
    # Корреляция с оригиналом показывает, сколько сигнала ещё осталось.
    corr = np.corrcoef(x0.ravel(), xt.ravel())[0, 1]
    print(f"t={t:4d}  доля картинки={np.sqrt(alphas_bar[t]):.4f}  корреляция={corr:+.2f}")

На t = 0 доля картинки почти единица и корреляция близка к единице. На t = 999 доля падает примерно до 0,006 — это уже практически чистый шум, и корреляция болтается около нуля. Всё «зашумление», о котором говорят применительно к диффузии, устроено ровно так: прибавление случайных чисел с известным весом.

Обратный процесс: сеть учат вычитать шум

Теперь ключевой ход. Раз на каждом шаге прямого процесса нам известны и зашумлённая картинка x_t, и точное значение добавленного шума eps, — у нас бесплатно есть размеченный обучающий набор произвольного размера. Каждая фотография из датасета плюс случайный шаг t плюс случайный шум дают одну обучающую пару.

Сеть учат решать ровно одну задачу: по зашумлённой картинке и номеру шага предсказать, какой именно шум в ней намешан. Функция потерь — обычная среднеквадратичная ошибка между предсказанным шумом и настоящим. Никакой экзотики, обычное обучение с учителем в смысле главы «Нейросети: как обучают модель», где разметку сделала не толпа разметчиков, а генератор случайных чисел.

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

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

Исходные работы стоит назвать поимённо: идея сформулирована в Sohl-Dickstein et al., 2015, а рабочую формулировку с предсказанием шума и качеством, которое заставило всех обратить внимание, дала статья Ho, Jain, Abbeel «Denoising Diffusion Probabilistic Models», 2020.

Две формулировки, которые в лекции слиплись

В устном рассказе прозвучало «сначала он генерирует шум, потом проясняет». Это описание нужно поправить в двух местах, иначе механизм понимается неверно.

Во-первых, модель не генерирует шум. Шум берётся из генератора случайных чисел — сеть к этому непричастна и на этом шаге вообще не вызывается. Модель стартует со случайного шума, поданного ей на вход.

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

И третье, из того же фрагмента: отдельного этапа раскрашивания нет. Цвет не добавляется после того, как «форма готова». Модель с самого первого шага работает со всеми каналами сразу, и цвет проявляется тем же процессом, что и контур.

Латентная диффузия: почему это работает на домашней видеокарте

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

Прямолинейная диффузия по пикселям чудовищно дорога. Картинка 512×512×3 — это 786 432 числа, и на каждом из нескольких десятков шагов сеть должна прогнать через себя весь этот массив. Ранние модели этого класса действительно требовали кластера.

Работа Rombach et al. «High-Resolution Image Synthesis with Latent Diffusion Models», 2022, — та самая, из которой выросла Stable Diffusion, — предложила простую перестановку. Шум подмешивается не к пикселям, а к сжатому представлению: автоэнкодер заранее обучен превращать картинку 512×512×3 в тензор 64×64×4 и разворачивать обратно почти без потери качества. Это 16 384 числа вместо 786 432 — в 48 раз меньше работы на каждом шаге.

Диффузия целиком живёт в этом сжатом пространстве, а декодер вызывается ровно один раз в самом конце. Идея «работать не с сырыми данными, а с их компактным представлением» уже встречалась в треке — в главе «Представления»; здесь она даёт разницу между «нужен дата-центр» и «работает на пользовательской видеокарте».

Побочный эффект, полезный для интуиции: артефакты, которые иногда видны на генерациях — размытые мелкие детали, испорченный текст на вывеске, — частично приходят не от диффузии, а от автоэнкодера. Он честно сжимает с потерями, и мелкая структура — первое, что теряется.

Куда сюда попадает текст промпта

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

Сначала описание превращают в векторы текстовым энкодером. В Stable Diffusion первых версий это текстовая половина CLIP (Radford et al., 2021) — модели, обученной на парах «картинка и её подпись» так, чтобы вектор изображения и вектор его описания оказывались рядом. Именно CLIP даёт тексту и картинке общее пространство смыслов.

Затем сеть шумоподавления смотрит на эти векторы через перекрёстное внимание (cross-attention): на каждом слое запросы формируются из текущего латента, а ключи и значения — из векторов текста. Механизм тот же самый, что в трансформере, и разобран он в главе «Внимание и трансформеры».

Содержательно это меняет вопрос, который решается на каждом шаге. Не «какой шум здесь есть», а «какой шум надо убрать, чтобы результат стал больше похож на это описание». Описание участвует в каждом из десятков шагов заново — оно не «применяется один раз в начале».

Guidance: насколько жёстко тянуть результат к описанию

На схеме выше сеть вызывается дважды за шаг — с описанием и с пустой строкой. Это classifier-free guidance, приём из работы Ho & Salimans «Classifier-Free Diffusion Guidance», 2022.

Арифметика простая. Есть предсказание шума с учётом текста и предсказание без него. Разница между ними — это направление «в сторону описания». Итоговое предсказание берут как безусловное плюс эта разница, умноженная на коэффициент:

eps = eps_без_текста + s * (eps_с_текстом - eps_без_текста)

При s = 1 получается обычная условная генерация. При s > 1 разница усиливается — результат сильнее притягивается к описанию.

Платят за это тремя вещами:

Что растёт Что происходит Практическое следствие
Соответствие описанию Все названные объекты появляются на картинке Ради этого приём и придуман
Пересыщение Цвета выкручиваются, контраст растёт, появляются ореолы При больших s картинка выглядит «пережаренной»
Потеря разнообразия Разные seed дают всё более похожие результаты Модель схлопывается к «самой типичной» иллюстрации запроса

Плюс постоянная цена: два прохода сети вместо одного на каждом шаге, то есть генерация с guidance примерно вдвое дороже. На практике проходы объединяют в один батч, так что расплата идёт памятью, а не временем.

Рабочий диапазон для классических моделей Stable Diffusion — примерно 5–8; у дистиллированных быстрых моделей он свой и обычно ближе к единице. Конкретное число надо подбирать под модель, а не запоминать: это параметр конкретной реализации, а не константа природы.

Seed: место, где замыкается первая глава трека

Здесь трек делает полный круг. Глава «Предсказуемость» начиналась с того, что компьютер не умеет производить случайность и вместо неё использует псевдослучайный генератор, целиком определяемый начальным состоянием — seed.

Начальный шум для диффузии берётся именно оттуда. Отсюда главное практическое свойство: один и тот же seed при тех же параметрах даёт ту же картинку. Не похожую — ту же самую, до последнего пикселя. Весь остальной путь строго детерминирован: сеть — фиксированная функция, планировщик — фиксированный алгоритм, декодер — фиксированное преобразование. Случайность входит ровно в одной точке.

Это лучший практический пример из всего трека на тему «псевдослучайность — не недостаток, а инструмент». Он же объясняет рабочий процесс людей, которые генерируют картинки всерьёз: находят удачный seed, фиксируют его и дальше меняют по одному параметру, наблюдая только эффект этого параметра.

Две честные оговорки. Воспроизводимость гарантируется в пределах одинакового окружения: другая видеокарта, другая версия библиотеки, другая точность вычислений могут дать чуть иной результат при том же seed, потому что арифметика с плавающей точкой на разном железе не побитово одинакова. И seed задаёт начальный шум, а не проверяется — в лекции прозвучало, что «модель на входе проверяет соответствие зерну», но никакой проверки в конвейере нет, seed вообще не доходит до сети как отдельный вход.

Число шагов и планировщик

Планировщик (scheduler, он же sampler) — это алгоритм, который решает, на каких уровнях шума останавливаться и насколько сдвигать латент, получив предсказание сети. Он не обучается: это численный метод, а не модель.

Исходный DDPM требовал около тысячи шагов — по одному на каждый шаг зашумления. DDIM (Song et al., 2020) показал, что можно взять подмножество шагов и идти по ним детерминированно, потеряв немного качества и выиграв порядок по времени. Современные решатели вроде DPM-Solver++ дают приличный результат за 20–30 шагов, а специально дистиллированные модели — за 1–4.

Зависимость качества от числа шагов насыщается, и это надо знать заранее. Первые шаги задают композицию — что и где расположено. Средние прорабатывают формы. Последние правят мелкие детали. После некоторого предела добавленные шаги меняют картинку косметически, а время растёт линейно: удвоение числа шагов — ровно вдвое дольше и вдвое дороже. Стандартная ошибка новичка — ставить 150 шагов «чтобы получше», получая ту же картинку за вшестеро большее время.

Сложность генерации считается тривиально: O(steps) вызовов сети, и с guidance каждый вызов удваивается. Память — O(веса модели + размер латента), от числа шагов не зависит, потому что промежуточные состояния не хранятся.

Псевдокод: весь цикл целиком

Ниже — реалистичный скелет того, что происходит внутри одного вызова генерации. Настоящие библиотеки прячут это за одной строкой, но внутри выполняется ровно эта последовательность. Код иллюстративный и не запускается: unet, text_encoder, vae и scheduler здесь — условные объекты.

def generate(prompt: str, seed: int, steps: int = 30, guidance: float = 7.5):
    # 1. Текст в векторы. Пустая строка кодируется отдельно —
    #    она нужна как «предсказание без описания» для guidance.
    cond = text_encoder(prompt)
    uncond = text_encoder("")

    # 2. Стартовый шум. Единственная точка входа случайности во всём конвейере:
    #    PRNG с фиксированным seed всегда даёт один и тот же латент.
    generator = torch.Generator(device="cuda").manual_seed(seed)
    latent = torch.randn(1, 4, 64, 64, generator=generator, device="cuda")
    #                       ^  ^   ^^  4 канала, 64x64 — сжатое представление
    #                                  картинки 512x512, а не сами пиксели

    # 3. Планировщик выбирает, какие из тысячи уровней шума пройти.
    scheduler.set_timesteps(steps)
    latent = latent * scheduler.init_noise_sigma

    for t in scheduler.timesteps:
        # 4. Два предсказания шума на одном и том же латенте.
        #    Описание участвует на каждом шаге заново, а не один раз в начале.
        eps_cond = unet(latent, t, context=cond)
        eps_uncond = unet(latent, t, context=uncond)

        # 5. Classifier-free guidance: усиливаем разницу «с текстом» и «без».
        eps = eps_uncond + guidance * (eps_cond - eps_uncond)

        # 6. Сеть выдала оценку шума, а не картинку. Насколько сдвинуть
        #    латент с учётом этой оценки — решает планировщик.
        latent = scheduler.step(eps, t, latent).prev_sample

    # 7. Один вызов декодера в самом конце: латент разворачивается в пиксели.
    #    Отдельного этапа «раскрашивания» нет — цвет уже внутри этих чисел.
    return vae.decode(latent / vae.scaling_factor)

Практическая сторона запуска таких моделей у себя — какая нужна видеокарта, сколько памяти занимают веса, почему всё упирается в VRAM — относится к главе «Железо» и к статье «Локальные модели».

Чем диффузия отличается от GAN и VAE

Диффузия — не единственное семейство генеративных моделей и не первое. GAN обучает генератор в состязании с дискриминатором: быстро (одна картинка за один проход), но обучение неустойчиво и склонно схлопываться к нескольким удачным вариантам. VAE учится сжимать и восстанавливать через вероятностное латентное пространство: устойчиво и быстро, но результаты исторически выходили размытыми. Диффузия платит десятками проходов сети вместо одного, но выигрывает в устойчивости обучения и в разнообразии результата — и заодно естественно принимает условие в виде текста на каждом шаге. Математика всех трёх семейств, включая формулы, которых здесь намеренно нет, разобрана в главе «Генеративные модели».

Стоит отметить и то, что диффузия не осталась единственным путём к картинке из текста: часть современных мультимодальных моделей генерирует изображение внутри самой языковой модели, авторегрессионно, по «визуальным токенам». Так что «картинки делают диффузией» — верное описание одного распространённого класса систем, а не закон природы.

Диффузия для текста

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

Сначала разведём две разные вещи

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

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

Авторегрессия против диффузии — это про то, как порождается выход. Обычная LLM, прочитав весь вход разом, пишет ответ по одному токену, каждый раз перезапуская всю конструкцию. Диффузионная модель порождает всю последовательность выхода сразу и уточняет её итерациями.

То есть «видит весь вход целиком» — свойство трансформера вообще, а не диффузии. И перекрёстное внимание в диффузионной модели изображений — это то же самое внимание: механизмы не противопоставлены, они находятся на разных уровнях.

Авторегрессионная LLM Диффузионная языковая модель
Как читается вход Весь целиком, одним проходом Весь целиком, одним проходом
Как порождается выход По одному токену слева направо Вся последовательность сразу, за несколько уточнений
Число проходов сети Равно длине ответа Равно числу шагов уточнения
Правка уже написанного Невозможна: токен выдан — он в контексте Естественна: каждый шаг переписывает всё
Кэш промежуточных состояний Работает, это основа быстрой генерации Применяется плохо: на каждом шаге меняется вся последовательность
Длина ответа Определяется по ходу дела Задаётся заранее или блоками

Как это устроено и что обещает

Шум для текста определяется иначе, чем для картинки: к дискретному токену нельзя прибавить дробное число. Вместо гауссова шума используют порчу в пространстве состояний — замену токенов на случайные или на специальный маркер «замаскировано», — а обратный процесс восстанавливает исходные токены. Базовые работы: D3PM (Austin et al., 2021), Diffusion-LM (Li et al., 2022) и SEDD (Lou et al., 2023). Из более поздних открытых моделей — LLaDA (2025), диффузионная языковая модель с опубликованными весами.

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

Слабости, по состоянию на момент лекции, май 2026:

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

Отдельно как мнение, а не факт: демонстрация диффузионной генерации текста производит сильное впечатление именно скоростью, и по этому впечатлению легко сделать вывод, что направление вот-вот вытеснит привычные модели. Достаточных оснований для такого вывода на момент лекции нет — есть перспективное направление с открытыми вопросами по качеству и экосистеме.

Хронология направления

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

Что в лекции сказано неточно

Прозвучало Как на самом деле
«Сначала он генерирует шум, потом проясняет» Шум даёт генератор случайных чисел, сеть здесь не участвует; модель стартует со случайного шума и вычитает его
Модель предсказывает картинку Модель предсказывает шум; картинка получается вычитанием предсказанного шума по расписанию
«Потом он может раскрашивать» Отдельного этапа раскрашивания нет, цвет проявляется тем же процессом с первого шага
«Модель на входе проверяет, что соответствует зерну» Seed задаёт начальный шум и до сети как отдельный вход вообще не доходит; никакой проверки нет
«Зерно» Принятый термин — seed
«Diffusion это blender diffusion» Оговорка; речь о Stable Diffusion
Диффузия = «видит все токены сразу», в отличие от обычных моделей Весь вход целиком видит любой трансформер. Диффузия отличается способом порождения выхода, а не способом чтения входа

Источники

Мини-итог

  • У пикселей нет естественного порядка, поэтому изображение порождают не «слева направо», а целиком: диффузия заменяет задачу «дописать следующий элемент» задачей «сделать имеющееся чуть менее шумным».
  • Прямой процесс — просто прибавление гауссова шума с известным весом, обучения он не требует и бесплатно даёт разметку; сеть учат по зашумлённой картинке предсказывать шум, а не картинку.
  • Генерация — это старт со случайного шума и несколько десятков повторов обратного шага; отдельного этапа раскрашивания в конвейере нет.
  • Stable Diffusion шумит не пиксели, а сжатое латентное представление в 48 раз меньшего объёма, и разворачивает результат автоэнкодером один раз в конце — именно это делает генерацию доступной на пользовательском железе.
  • Текст входит в процесс через текстовый энкодер и перекрёстное внимание на каждом шаге, а guidance регулирует силу притяжения к описанию ценой пересыщения и потери разнообразия.
  • Начальный шум берётся у обычного PRNG, поэтому один seed при тех же параметрах и том же окружении даёт ту же картинку — это самый наглядный практический смысл псевдослучайности во всём треке.
  • Внимание — это про чтение входа, авторегрессия против диффузии — про порождение выхода; обычный трансформер тоже видит вход целиком, но пишет ответ по токену.

Что дальше

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

RAG, MCP и агенты

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов