Лінійна алгебра промпт-інженірингу: як ШІ перетворює слова на зображення
Вітаю, спільното! Мене звати Зоя Стеценко, я займаюся розробкою продуктів на базі генеративного ШІ, зокрема систем для створення зображень, відео, аватарів та іншого візуального контенту. Маю досвід в продакт-менеджменті та побудові AI-функціоналу — від дослідження моделей до впровадження в реальні продукти. Зараз сфокусована на системному розвитку генеративного напрямку в компанії та побудові процесів навколо машинного навчання.
Гарний промпт — це не просто набір влучних слів. Це набір чисел, що визначають напрям генерації.
У генеративному ШІ, зокрема в таких моделях як Stable Diffusion, Midjourney чи DALL·E, ми звикли сприймати промпт як текстове «пояснення» моделі, що саме вона має згенерувати. Але насправді система не «читає» промпт у людському розумінні. Вона працює не з мовою, а з векторами. І кожен запит — це не фраза, а вхідна точка в багатовимірному латентному просторі.
За кожним словом стоїть embedding — вектор у десятках або сотнях вимірів. Вони проходять через трансформери, агрегуються, зважуються, стають орієнтирами в шумному середовищі генерації. І кожна деталь промпта — його структура, порядок, конкретність чи розмитість — впливає на те, який саме шлях пройде модель від випадкового шуму до фінального зображення.
Ця стаття буде цікава всім, хто хоче зрозуміти, як саме моделі типу Stable Diffusion «читають» текстовий промпт. Ми подивимося на процес генерації зображень через призму лінійної алгебри:
- розберемо, як текстовий промпт перетворюється у вектори й матриці;
- подивимось, як саме ці вектори керують генерацією на кожному кроці дифузійної моделі;
- розглянемо типові помилки у промптах — і розберемося, чому вони математично ламають результат;
- сформуємо конкретні поради, як писати промпти, які справді працюють — не коштом інтуїції, а через розуміння структури embedding-простору.
Як промпт стає вектором
Модель не «читає» ваш промпт як людина. Замість цього вона перетворює кожне слово у вектор — набір чисел, який кодує його значення. Весь промпт стає точкою в багатовимірному математичному просторі, і саме ця точка визначає, як модель генеруватиме зображення. Цей процес перетворення тексту у вектори називається embedding.
Розберемо повний шлях перетворення текстового запиту у математичну форму, яка керує генерацією.
1. Токенізація: розбиття тексту на одиниці
Модель не бачить слова так, як ми. Для неї текст — це послідовність маленьких блоків, які називаються токенами. Це не завжди цілі слова.
Подивіться, як сучасна модель розбиває наш промпт:
«A photorealistic wooden fence»
↓
[«A», «photo», «real», «istic», «wooden», «fence»]
Чому «photorealistic» розбилося на частини? Тому що модель вивчила, що краще зберігати часто вживані корені («photo», «real») окремо — так вона може краще розуміти нові комбінації слів.
Кожен токен отримує свій номер у «словнику» моделі:
- «photo» → номер 1234
- «real» → номер 5678
- «wooden» → номер 9012
Номери токенів залежать від конкретного токенізатора (наприклад, CLIPTokenizer, T5Tokenizer), тому вони не універсальні.
Цей словник містить десятки тисяч токенів — від звичайних слів до рідкісних технічних термінів і навіть емодзі.
2. Векторизація: кожен токен стає вектором
Уявіть, що у моделі є величезний словник, де кожне слово (або його частина) має свій числовий «паспорт» — вектор з сотень чисел. Коли модель бачить слово «photorealistic», вона спочатку розбиває його на токени [«photo», «real», «istic»], а потім знаходить вектор для кожної частини:
«photorealistic» розбивається на:
├─ «photo» → [0.45, —0.12, 0.33, ..., 0.18]
├─ «real» → [0.67, 0.08, —0.24, ..., 0.52]
└─ «istic» → [-0.11, 0.39, 0.71, ..., —0.09]
Цей процес називається token embedding. Кожне число у векторі кодує якусь «властивість» слова — чи пов’язане воно з матеріалами, з прозорістю, з об’єктами тощо. Модель вивчила ці зв’язки під час тренування на мільйонах текстів.
Після токенізації кожен токен стає індексом у таблиці embeddings. Наприклад:
- «photorealistic» → токен #3847 → вектор у 768 вимірах
- «8K» → токен #12003 → інший вектор у 768 вимірах
Математично це записують як:
w = Embedding[token_id],
де w — це вектор у
Слова зі схожим значенням мають схожі вектори. Тому «realistic», «photorealistic» і «lifelike» штовхатимуть генерацію в подібному напрямку, але з різною силою. А «abstract» поштовхне в протилежний бік.
3. Контекстуалізація: трансформер враховує зміст
Після токенізації та векторизації ми маємо послідовність ізольованих векторів. Проблема: вектор для «cat» однаковий у промптах «sleeping cat» і «angry cat», хоча контекст кардинально змінює те, що має бути на зображенні.
Механізм self-attention
Трансформер вирішує цю проблему через механізм self-attention — математичну операцію, яка дозволяє кожному слову «зважити» вплив усіх інших слів у промпті.
Self-attention створює три матриці з вхідних векторів:
- Q = XWQ (запити)
- K = XWK (ключі)
- V = XWV (значення)
Потім обчислюється матриця уваги:
Attention(Q,K,V) = softmax(QK^T/√d)V
Де QK^T дає матрицю схожості між усіма парами токенів, √d — масштабувальний фактор, а softmax нормалізує ваги.
Для промпту «A photorealistic red cat on wooden fence»:
Self-attention обчислює матрицю ваг між усіма парами слів.
Для токена «cat» це може виглядати так:
cat ↔ photorealistic: 0.15 (помірна семантична схожість)
cat ↔ red: 0.45 (сильний зв’язок: колір тварини)
cat ↔ wooden: 0.08 (слабкий зв’язок)
cat ↔ fence: 0.32 (зв’язок через композицію: тварина на об’єкті)
Ці ваги використовуються для оновлення вектора «cat»:
новий_вектор_cat = 0.15×вектор_photorealistic + 0.45×вектор_red + 0.08×вектор_wooden + 0.32×вектор_fence
Спрощена ілюстрація механізму з умовними даними
Математично:
X = [w₁, w₂, ..., wₙ] → Self-Attention → [h₁, h₂, ..., hₙ]
початкові embeddings контекстуалізовані вектори
де: h_i = Σⱼ attention_weight(i,j) × wⱼ
Кожен вихідний вектор h_i є зваженою сумою всіх вхідних векторів, де ваги визначаються схожістю між відповідними Q та K векторами.
До кожного token embedding додається позиційний вектор перед подачею в трансформер. Тому «Red cat on fence» і «Cat on red fence» матимуть різні вхідні вектори навіть для однакових слів, що призведе до різних контекстуалізованих представлень.
Дифузійна модель отримує не набір ізольованих концептів, а математично взаємопов’язані вектори. Кожен h_i містить інформацію про відповідне слово, зважену за важливістю всіх інших слів у промпті. Це забезпечує семантичну когерентність генерованого зображення — наприклад, червоний колір буде асоційований саме з котом, а не з парканом.
Візуалізація: приклад embedding
Хоча embedding-вектори не є координатами в латентному просторі, вони визначають, як саме модель буде змінювати шумове представлення зображення на кожному кроці генерації.
Щоб наочно показати, як працює embedding, візьмемо простий приклад із промптом:
«A red cat on a wooden fence, backlit by sunset, detailed, 4K»
Модель розбиває цей текст на токени — кожне слово (або його частину) перетворює на вектор у багатовимірному просторі. Конкретні числа нам недоступні і не мають прямої інтерпретації. Важливо розуміти принципи їх роботи.
Для наочності спростимо й уявімо, що embedding-простір має лише 4 виміри (в реальності їх 768 або 1024):
|
Токен |
Вектор (приклад з 4 чисел) |
|
red |
[0.22, —0.17, 0.39, 0.05] |
|
cat |
[0.41, 0.07, —0.36, 0.18] |
|
on |
[-0.03, 0.11, 0.00, —0.06] |
|
a |
[-0.10, 0.02, —0.15, —0.01] |
|
wooden |
[0.28, 0.25, —0.14, 0.22] |
|
fence |
[0.31, 0.19, —0.12, 0.27] |
|
backlit |
[0.44, 0.33, —0.19, 0.35] |
|
by |
[-0.08, 0.04, —0.20, —0.03] |
|
sunset |
[0.48, 0.40, —0.22, 0.30] |
|
detailed |
[0.12, 0.05, 0.42, 0.10] |
|
4K |
[0.46, 0.36, —0.27, 0.39] |
Хоча ми не можемо інтерпретувати конкретні числа в векторах, вони мають важливі математичні властивості:
1. Схожість. Семантично близькі слова мають схожі вектори. Наприклад:
cos_similarity("cat«, «kitten») ≈ 0.8
cos_similarity("cat«, «fence») ≈ 0.2
2. Композиційність. Вектори можна комбінувати:
вектор("red") + вектор("cat«) ≈ концепт «червоного кота»
3. Контекстуальність. Після трансформера той самий токен має різні вектори в різних промптах:
«red» у «red car» ≠ «red» у «red sunset»
Отримані embedding-вектори — це лише початок. Щоб створити зображення, модель має пройти ланцюжок обчислень, які перетворюють текстові представлення в реальні візуальні патерни.
Цей процес відбувається всередині дифузійної моделі, яка починає з шуму і поступово «очищає» його, орієнтуючись на зміст промпта через cross-attention. На кожному кроці тут працює лінійна алгебра — обчислення з векторами, матрицями, масштабування, складання, віднімання, зважування.
Зараз ми коротко розберемо, як embedding керує генерацією, і що саме відбувається на математичному рівні.
1. Початкова точка — випадковий шум
Процес стартує з випадкового зображення:
xₜ ∼ 𝒩(0, I)
Це означає, що на початковому кроці t = T, модель бере матрицю, заповнену випадковими числами, з нормального розподілу — тобто чистий шум.
Це ключова особливість дифузійних моделей: вони навчаються у зворотному напрямку — не як створювати зображення, а як прибирати шум зі зображень.
Під час тренування моделі показують мільйони реальних зображень і поступово додають до них шум (ітеративно, десятки або сотні кроків). Модель вчиться «відмотувати» цей процес назад — тобто відновлювати зображення із зашумленої версії, знаючи, на якому кроці вона знаходиться.
Тому на інференсі (генерації) вона починає з кінця — з випадкового шуму, і намагається крок за кроком перетворити його в осмислену картинку, орієнтуючись на ваш промпт.
Модель не працює одразу з великою RGB-картинкою (наприклад, 512×512×3), бо це:
- складно;
- повільно;
- потребує багато відеопам’яті,
Тому все відбувається в стисненому, внутрішньому представленні — так званому латентному просторі. Латентний простір — це стиснутий геометричний простір, у якому зображення існує у прихованій формі. Модель навчається рухатись у ньому, спираючись на embedding-вектор з промпта.
Наприклад, замість 512×512×3, модель працює з тензором розміру 64×64×4. Це — компактна версія зображення, яку модель може швидко обробляти.
Це латентне представлення не схоже на картинку для людини, але в ньому вже кодуються:
- майбутні об’єкти;
- стилістика;
- композиція;
- текстури, які ще «розгорнуться» пізніше.
2. Поступове «очищення» шуму — крок за кроком
Після ініціалізації випадкового латентного шуму xₜ, модель починає рух у зворотному напрямку — від повного хаосу до осмисленої картинки. Це основна ідея дифузійного процесу.
Процес триває T кроків, наприклад, від t = 1000 → t = 0. На кожному кроці модель намагається вгадати, який шум слід прибрати, щоб наблизити зображення до змісту промпта.
На кожному кроці t модель частково знімає шум, використовуючи формулу, яка залежить від вибраного scheduler’а. В одному з варіантів (наприклад, DDIM) це виглядає так:
xₜ₋₁ = (1 / αₜ) × [xₜ — ((1 — ᾱₜ) / √(1 — αₜ)) × εₜ(xₜ, t, c)] + σₜ × z
Реалізація залежить від фреймворку та вибраного методу дифузії.
xₜ — латентна «шумна» картинка на кроці t.
xₜ₋₁ — оновлена картинка після часткового зняття шуму.
c — embedding вектор промпта, який скеровує весь процес.
εₜ = εₜ(xₜ, t, c) — функція, яка передбачає, який саме шум зараз присутній. Це — головна модель (зазвичай U-Net з attention). На кожному кроці вона використовує механізм cross-attention для з’єднання поточного стану зображення xₜ з текстовими векторами c.
αₜ, ᾱₜ, σₜ — це попередньо обчислені коефіцієнти, які керують тим, як саме знімається шум на кожному кроці генерації. Їх визначає scheduler — компонент моделі, який відповідає за графік «очищення» шуму.
z — новий шум, який додається для варіативності (тобто кожен результат унікальний навіть при однаковому промпті).
На кожному кроці модель:
- Дивиться на поточне зображення xₜ.
- Знає, на якому кроці t вона знаходиться.
- Має доступ до embedding вектора c, який вказує, куди ми «йдемо».
- І вираховує, який шум потрібно прибрати, щоб наблизитись до картинки, яка «відповідає» промпту.
Таким чином, модель рухається по траєкторії, яку визначає вектор c, — з кожним кроком дедалі ближче до фінального образу.

3. Декодування: з латентного простору в зображення
Після завершення всіх кроків очищення шуму модель отримує останній стан — латентне представлення зображення x₀. Це ще не фінальна картинка, а лише багатовимірна матриця (наприклад, 64×64×4), у якій закодовано структуру, зміст і стиль майбутнього зображення.
Щоб перетворити її в повноцінне зображення у форматі RGB, використовується декодер — зазвичай це варіаційний автокодер (VAE), який «розгортає» латентні ознаки назад у зображення розміром 512×512×3.
І навіть на цьому етапі продовжує працювати лінійна алгебра: згортки, нормалізації, добутки та суми векторів.
Генерація зображення з текстового промпта — це не магія, а цілком визначена математична процедура, яка починається з токенізації тексту, проходить через векторні простори, дифузію та attention, і завершується реконструкцією візуального образу. І кожен етап цього шляху — це обчислення з векторами й матрицями.

Типові помилки в промпт-інженерії — і що про них каже алгебра
Промпт-інжиніринг часто здається творчим процесом: додаєш слова, прибираєш зайве, щось переставляєш. Але під капотом усе набагато стриманіше — будь-який промпт перетворюється на embedding-вектор, а той — на координату в багатовимірному просторі.
Саме тому деякі помилки дають передбачувано погані результати: вони створюють некоректні вектори або проєкції в небажані області embedding-простору.
Далі — найтиповіші ситуації з поясненням, чому це працює (або не працює).
Помилка 1. Занадто короткий або розмитий промпт
Приклад проблеми:
«cat»
Що відбувається з точки зору векторів:
Короткий промпт створює «бідний» контекстуалізований вектор:
h_cat = self_attention([w_cat])
Де є лише один токен для self-attention, тому:
h_cat ≈ w_cat (базовий embedding без контексту)
Для розширеного промпта «a red tabby cat with green eyes on velvet pillow»
→ [w_a, w_red, w_tabby, w_cat, w_with, w_green, w_eyes, w_on, w_velvet, w_pillow]
Self-attention створює багатий контекст:
h_cat = Σᵢ attention_weight(cat,i) × wᵢ
Де attention-ваги показують, як інші слова впливають на розуміння «cat»:
- attention_weight(cat, red) = 0.35 (сильний зв’язок: колір тварини)
- attention_weight(cat, tabby) = 0.42 (дуже сильний: порода)
- attention_weight(cat, velvet) = 0.12 (слабкий: контекст)
Результат в cross-attention:
Для короткого промпта: Q × [h_cat] → обмежена інформація
Для довгого промпта: Q × [h_a, h_red, h_tabby, h_cat, ...] → багата інформація
Кожен додатковий релевантний токен збільшує кількість інформації, доступної cross-attention під час генерації. Математично це означає більше «напрямків» у векторному просторі, за якими модель може рухатися.
Помилка 2. Надлишкова множинність або пересичення
Приклад проблеми:
«a futuristic detailed cinematic ultra-realistic oil painting of a cyberpunk jungle with neon robots, baroque cathedral ruins, underwater creatures, desert storm, and fog, soft lighting, pastel colors, HDR, epic anime in Renaissance style»
Що відбувається:
Результат — розмите зображення без чіткого фокуса, часто з артефактами або дивними поєднаннями елементів.
Проблема з точки зору attention:
У такому промпті є конфліктуючі концепти:
- «oil painting» vs «HDR» (традиційна техніка vs цифрова);
- «Renaissance» vs «cyberpunk» (класика vs футуризм);
- «underwater» vs «desert storm» (протилежні середовища).
Cross-attention розподіляється:
Attention_weights = softmax(Q × K^T / √d)
Для кожного пікселя зображення модель намагається врахувати всі токени одночасно:
pixel_influence = Σᵢ attention_weight(pixel, tokenᵢ) × value_tokenᵢ
Конфлікт векторів:
- h_oil_painting ≈ [класичність, традиційність, мазки, ...]
- h_HDR ≈ [цифровість, контрастність, технологічність, ...]
- h_Renaissance ≈ [старовинність, золотий_перетин, ...]
h_cyberpunk ≈ [неон, майбутнє, техно, ...]
Коли cross-attention намагається поєднати ці вектори:
combined_influence = 0.15×h_oil + 0.12×h_HDR + 0.11×h_Renaissance + 0.13×h_cyberpunk + ...
В результаті жоден стиль не домінує, кожен отримує малу вагу, зображення стає компромісом між усіма стилями.
Як уникнути:
Оберіть
Замість: «oil painting + HDR + Renaissance + cyberpunk + ...»
Краще: «a cyberpunk jungle with neon robots and fog, cinematic lighting, detailed»
Математично це означає:
Переповнений: attention розподілена ≈ [0.1, 0.1, 0.1, 0.1, 0.1, ...]
Сфокусований: attention розподілена ≈ [0.4, 0.3, 0.15, 0.1, 0.05]
Практичне правило:
Максимум
Помилка 3. Ігнорування ваги й порядку слів
Приклад проблеми:
«cinematic lighting, HDR, 4K, red, fence, cat, wide angle, photorealistic,
moody, fence, detailed, sunlight»
Що не так:
- Головний об’єкт («cat») загублений серед технічних термінів.
- «fence» повторюється двічі.
- Логічна структура відсутня.
Як це впливає на обробку:
- Позиційне кодування. Кожен токен отримує позиційний вектор:
token_final = token_embedding + positional_encoding(position)
Тому «cat» на
- Self-attention і порядок. Хоча attention теоретично може зв’язати будь-які токени, на практиці близькі по позиції слова частіше впливають одне на одного:
attention_weight(i,j) частково залежить від |pos_i — pos_j|
- Проблема дублювання:
h_fence_1 ≠ h_fence_2 (різні позиції → різні контекстуалізовані вектори)
Модель може інтерпретувати це як «два паркани» замість посилення одного концепту.
Як виправити:
1. Логічна структура:
Замість: «lighting, HDR, 4K, red, fence, cat, angle, realistic, moody, fence, detailed, sun»
Краще: «red cat on wooden fence, sunset lighting, photorealistic, cinematic, detailed»
2. Пріоритет головного об’єкта:
Основний об’єкт → контекст → стиль → технічні деталі
«red cat» → «on fence» → «photorealistic» → «4K, detailed»
3. Prompt weighting (якщо підтримується):
(red cat):1.3, (wooden fence):1.1, (sunset lighting):1.0, (photorealistic):0.9
Математично це означає:
- Хаотичний порядок: attention розсіяна між випадковими зв’язками.
- Структурований: attention концентрується на логічних зв’язках.
Практичне правило:
- Головний об’єкт — на початок.
- Один концепт = один токен (без дублів).
- Логічний порядок: що → де → як → в якому стилі.
Помилка 4. Відсутність або слабкий негативний промпт
Що таке негативний промпт:
Багато інтерфейсів (A1111, ComfyUI, InvokeAI) мають окреме поле для негативного промпта — списку того, чого не треба генерувати.
Як це працює технічно:
Негативні промпти реалізовані через Classifier-Free Guidance (CFG):
noise_pred = noise_uncond + cfg_scale × (noise_cond — noise_uncond)
Де:
- noise_cond — передбачення шуму з урахуванням позитивного промпта;
- noise_uncond — передбачення шуму з урахуванням негативного промпта;
- cfg_scale — сила текстового керування (зазвичай
7-15).
Проблема без негативного промпта:
Коли негативний промпт порожній, noise_uncond обчислюється з пустого тексту. Модель не знає, чого уникати, тому може згенерувати:
- Розмиті деталі.
- Водяні знаки та текст.
- Спотворені обличчя.
- Артефакти з навчальних даних.
Чому це відбувається:
Модель навчалась на мільйонах зображень з інтернету, включаючи:
- Stock-фото з водяними знаками.
- Зображення низької якості.
- Фото з текстом та логотипами.
Без негативного промпта ці «небажані» шаблони можуть проявитися.
Як виправити:
- Базовий негативний промпт (приклад):
«blurry, low quality, watermark, text, cropped, worst quality, low resolution»
- Для портретів додати набір правил (приклад):
«distorted face, extra limbs, bad anatomy, deformed, mutation»
Налаштування CFG:
- CFG Scale
7-10: м’якше керування. - CFG Scale
12-15: жорсткіше керування. - Занадто високий CFG (>20): може давати пересичені кольори.
Математично:
Без негативного: керування тільки в бік позитивного промпта
З негативним: керування «від поганого» до «хорошого»
Практичне правило:
Завжди використовуйте базовий негативний промпт, навіть якщо це просто «low quality, blurry».
Помилка 5. Протиріччя або дублі у промпті (несумісні вимоги)
Приклад проблеми:
«day night photo»
«abstract realistic portrait»
«vintage modern cyberpunk»
«underwater desert landscape»
Що відбувається:
Результат — дивне, неприродне зображення з артефактами, подвійним освітленням або змішаними стилями.
Проблема з точки зору векторів:
Суперечливі концепти мають протилежні embedding-вектори:
h_day ≈ [яскравість: 0.8, тепло: 0.7, синє_небо: 0.9, ...]
h_night ≈ [яскравість: —0.6, тепло: —0.4, синє_небо: —0.8, ...]
Self-attention намагається їх поєднати:
h_combined = attention_weight(day,night) × (h_day + h_night)
Результат математично:
h_day + h_night ≈ [0.2, 0.3, 0.1, ...] — «розмитий» вектор
Жодна характеристика не домінує, embedding втрачає чіткість.
Cross-attention отримує суперечливі сигнали:
pixel_influence = w_day × h_day + w_night × h_night
Модель не знає, малювати денне чи нічне освітлення, тому створює «компроміс» — результат виглядає неприродно.
Як виправити:
1. Виберіть одну домінуючу характеристику:
Замість: «day night cityscape»
Краще: «night cityscape with morning sun rays» (нічна сцена + елемент дня)
2. Використайте prompt weighting:
«(night cityscape):1.3, (golden hour):0.7»
3. Розділіть на кілька генерацій:
Промпт 1: «day cityscape, bright sunlight»
Промпт 2: «night cityscape, neon lights»
Потім виберіть кращий результат.
Математично це означає:
Суперечливий: h_result ≈ 0 (вектори взаємно знищуються)
Збалансований: h_result = 0.8×h_main + 0.2×h_accent (один домінує)
Практичне правило:
Один промпт = один домінуючий настрій/стиль. Додаткові елементи можуть бути акцентами, але не суперечити основній ідеї.
Висновок
Генерація зображень у ШІ — це насамперед робота з числами, а не словами. Кожен промпт перетворюється на вектор, який визначає напрям руху моделі в латентному просторі. Саме тому формулювання промпта — це не просто питання смаку, а точне завдання, яке впливає на якість і зміст результату.
Розуміння того, як працюють embedding-вектори, трансформери, attention і дифузійні кроки, допомагає писати ефективніші промпти. Це знижує кількість спроб «наосліп» і дозволяє краще керувати результатом.
Хороший промпт — це чітка інструкція, сформульована з урахуванням механіки моделі. І чим краще ми це розуміємо, тим передбачуванішими й якіснішими стають результати.

26 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів