Перейти до вмісту
coden

Варіаційні автокодувальники (VAE): нові зображення з неперервного латентного простору

Генеративна модель із латентною змінною, VAE з нуля з трюком репараметризації, упорядкований латентний простір, генерування цифр із шуму проти звичайного автокодувальника, вплив β і колапс, інтерполяція та де використовують VAE.

12 хвилин Matěj Žalmánek 2026-10-16

Серія Основи ШІ Частина 44 з 52
  1. Мапа штучного інтелекту: що є що і як усе це пов’язано
  2. Коротка історія штучного інтелекту: від Тюрінга до мовних моделей
  3. Дані, модель, тренування, інференс: основні поняття на одному прикладі
  4. Як читати математичний запис: символи, індекси, суми та квантори
  5. Множини й відношення: конструктор, з якого складаються означення в ШІ
  6. Функції й відображення: чому кожна модель у ШІ — це функція
  7. Логіка й доведення: правила, за якими міркують у ШІ
  8. Графи й дерева: структура, на якій тримаються пошук і нейронні мережі
  9. Лінійна алгебра для машинного навчання: вектори, матриці й чому ШІ передусім множить
  10. Похідні, градієнт і правило ланцюжка: як модель дізнається, у якому напрямку вчитися
  11. Градієнтний спуск: як навчається модель
  12. Що таке ймовірність: від кидка кубика до аксіом Колмогорова
  13. Умовна ймовірність і теорема Баєса: як змінити оцінку, коли дізнаєтеся щось нове
  14. Випадкова величина і функція розподілу: одна функція, яка каже про розподіл усе
  15. Функція щільності та функція маси ймовірності: чому неперервна величина має в точці ймовірність нуль
  16. Математичне сподівання, дисперсія і коваріація: де лежить розподіл, наскільки він широкий і що з чим пов’язано
  17. Огляд розподілів ймовірностей: Бернуллі, біноміальний, Пуассона, рівномірний, нормальний і експоненційний
  18. Закон великих чисел і центральна гранична теорема: чому середні поводяться так передбачувано
  19. Статистика й оцінювання параметрів: максимальна правдоподібність, MAP і довірчі інтервали
  20. Теорія інформації: ентропія, перехресна ентропія і KL-дивергенція — звідки беруться функції втрат
  21. Пошук у просторі станів: BFS, DFS, UCS і A* з анімаціями
  22. Ігри та суперник: мінімакс, альфа-бета відсікання і Monte Carlo tree search
  23. Задоволення обмежень: бектрекінг, поширення обмежень і AC-3 на судоку та мапі чеських країв
  24. Логіка й системи, засновані на знаннях: пряме і зворотне виведення, резолюція та експертні системи
  25. Планування: STRIPS, світ кубиків, евристики з релаксації та планування в AI-агентах
  26. Локальний пошук та імітація відпалу: комівояжер через обласні центри Чехії
  27. Генетичні алгоритми: популяція, схрещування, мутація і добір на рюкзаку для походу
  28. Еволюційні стратегії і генетичне програмування: від правила однієї п’ятої до закону Кеплера
  29. Оптимізація мурашиною колонією: як феромоновий слід знаходить короткий шлях
  30. Оптимізація роєм частинок: інерція, власний досвід і досвід рою
  31. Нейроеволюція: еволюція ваг і форми нейронних мереж та алгоритм NEAT
  32. Нечіткі множини й функції належності: як записати «трохи тепло»
  33. Нечітка логіка та ймовірність: чому «трохи тепло» не означає «імовірно тепло»
  34. Нечітка логіка: t-норми, імплікації та правила ЯКЩО–ТО
  35. Нечітке керування: Мамдані та Суґено від вентилятора до маятника на візку
  36. Нечітка кластеризація: алгоритм fuzzy c-means і точки між кластерами
  37. Нейро-нечіткі системи (ANFIS): нечіткі правила, які навчаються з даних
  38. Марковські ланцюги: матриця переходів, стаціонарний розподіл і PageRank
  39. Приховані марковські моделі: алгоритм Вітербі й нечесне казино
  40. Байєсівські мережі: чому трава мокра і що з цього випливає для дощувача
  41. Фільтр Калмана: як за неточними вимірюваннями оцінити, де авто і як швидко воно їде
  42. Види машинного навчання: з учителем, без учителя, з частковим залученням учителя та інші
  43. Промпти, контекст і RAG: як дати моделі правильну інформацію
  44. Варіаційні автокодувальники (VAE): нові зображення з неперервного латентного простору
  45. Генеративні змагальні мережі (GAN): фальшувальник проти детектива
  46. Дифузійні моделі: як із чистого шуму постає зображення
  47. Марковські процеси ухвалення рішень: стани, дії, винагороди й рівняння Беллмана
  48. Q-навчання і DQN: як навчитися цінності дій — від таблиці до нейронної мережі
  49. Policy gradient і PPO: навчання безпосередньо політики
  50. Як працює ШІ-агент: мовна модель у циклі з інструментами
  51. Програмування з AI-агентами: завдання, тести й перевірка
  52. Оцінювання і безпека систем ШІ: як вимірювати і як стежити

Повна програма серії Готово: 52 з 83 тем

Що ви винесете
  • Чому зі звичайного автокодувальника погано генерувати.
  • Як працює VAE: розподіл замість точки, штраф KL і трюк репараметризації.
  • Чого коштує β, чому зображення VAE розмиті і де VAE використовують сьогодні.

Що варто знати заздалегідь: Теорія інформації: ентропія, перехресна ентропія і KL-дивергенція — звідки беруться функції втрат

Автокодувальник із частини про представлення вміє стиснути зображення до кількох чисел і знову скласти його з них. Напрошується запитання: якщо декодер уміє з коду зробити цифру, чи не могли б ми дати йому код, який ми вигадали, і отримати нову цифру, якої ніхто не малював? Зі звичайним автокодувальником це майже не працює. Варіаційний автокодувальник — це його видозміна, яка саме це уможливлює, — і це перша з генеративних моделей, якими ми займатимемося в цьому розділі серії.

Продовжує частини Автокодувальники та представлення і Теорія інформації (KL-дивергенція).

1. Від стиснення до генерування

Означення — генеративна модель

Генеративна модель навчається з даних x1, …, xn розподілу p(x) так, щоб із нього можна було вибирати зразки — створювати нові x, які виглядають як дані, але не є їхніми копіями. Моделі з латентною змінною роблять це у два кроки: випадково вибирають код z із простого розподілу p(z) (наприклад, нормального), а декодер обчислює з нього x.

Простими словами

Класифікатор відповідає на «що це таке». Генеративна модель уміє відповісти на «покажи мені щось, що могло б бути в даних». Рецепт: киньте кубик кілька разів, передайте числа художникові (декодеру), і він за ними намалює картинку.

p(x) = ∫ p(x | z) · p(z) dz

p(z)
розподіл коду (апріорний) — обираємо його самі й простим, у нас N(0, I), бо з нього потім під час генерування вміємо вибирати
p(x | z)
декодер — наскільки ймовірне зображення x, якщо ми отримали код z; у нас це мережа, яка для кожного пікселя повертає ймовірність, що він світиться
∫ … dz
сума за всіма можливими кодами — зображення могло виникнути з будь-якого коду; проте інтеграл за всім латентним простором ми обчислити не вміємо, тому VAE замінює його оцінкою (нижче)
Генерувати легко (вибери z, декодуй), а для навчання потрібне p(x) — і це той складний інтеграл.

Заковика звичайного автокодувальника: ми не знаємо, звідки брати код. Енкодер розміщує цифри в латентному просторі, де йому заманеться. У нашій мережі з двовимірним кодом коди тестових цифр лежать навколо точки (-3,9; -0,9) зі стандартним відхиленням 4,3 і 6,0, а між групами є діри, в яких декодер ніколи нічого не бачив.

2. Варіаційний автокодувальник

Означення — варіаційний автокодувальник (VAE)

VAE (Дідерік Кінгма і Макс Веллінг, 2013) має енкодер, який для зображення x повертає не одну точку, а нормальний розподіл q(z | x) = N(μ(x), diag σ²(x)). Він навчається мінімізацією втрати

L = −Ez ~ q[log p(x | z)] + β · KL(q(z | x) ‖ N(0, I)),

де перший член — помилка реконструкції (у нас бінарна перехресна ентропія пікселів), а другий — штраф за те, наскільки розподіл коду відрізняється від стандартного нормального. Для нормальних розподілів KL = ½ ∑j (μj² + σj² − log σj² − 1). При β = 1 величина −L є нижньою межею логарифма правдоподібності даних (ELBO).

Простими словами

Енкодер не може сказати «ця сімка живе саме тут», а лише «десь у цій хмаринці». Декодер тоді мусить уміти намалювати сімку з будь-якого місця хмаринки. А штраф KL штовхає всі хмаринки до початку координат і не дає їм бути надто малими — тож вони перекриваються, заповнюють простір, і між ними не лишається дір.

L(x) = −Ez ~ q(z | x)[ log p(x | z) ] + β · KL( q(z | x) ‖ N(0, I) )

−Ez ~ q[log p(x | z)]
помилка реконструкції — наскільки погано декодер намалює x із коду, вибраного з хмаринки. Математичне сподівання за всією хмаринкою (а не лише за її центром) змушує декодер малювати правильно в усьому околі коду; мінус тому, що логарифм імовірності ми хочемо якомога більшим, а втрату якомога меншою
β
вага штрафу — задає компроміс між точністю і порядком у просторі; β = 1 — це саме ELBO, інші значення ми випробовуємо в розділі про β
KL(q(z | x) ‖ N(0, I))
відстань хмаринки від розподілу, з якого ми вибиратимемо, — без неї енкодер стиснув би хмаринки до точок і розкидав їх далеко одна від одної (реконструкція була б кращою), і ми знову мали б звичайний автокодувальник
Втрата для одного зображення; навчається середнє за пакетом. Обидва члени вимірюються в натах (натуральний логарифм).

KL = ½ ∑j=1k ( μj² + σj² − log σj² − 1 )

KL
штраф із попередньої формули, для нормальних розподілів обчислений точно; координати коду незалежні, тому це сума за j
μj²
зсув центру хмаринки від початку координат — зростає квадратично, тож віддалені коди коштують багато, і енкодер стягує їх до нуля, звідки ми вибиратимемо
σj² − log σj²
ширина хмаринки — функція s − log s має мінімум 1 саме в s = 1; вона штрафує надто широку хмаринку, але передусім надто вузьку (−log σ² → ∞ при σ → 0), щоб хмаринка не перетворилася на точку
«− 1» лише зсуває мінімум до нуля: KL = 0 тоді й лише тоді, коли μ = 0 і σ = 1, тобто коли q саме є N(0, I). Похідні, які програма додає до градієнта: ∂KL/∂μj = μj і ∂KL/∂(log σj²) = ½(σj² − 1).
Теорема — ELBO є нижньою межею логарифма правдоподібності

Для будь-якого розподілу q(z | x) виконується

log p(x) = Eq[log p(x | z)] − KL(q(z | x) ‖ p(z)) + KL(q(z | x) ‖ p(z | x)) ≥ Eq[log p(x | z)] − KL(q(z | x) ‖ p(z)).

Доведення. log p(x) не залежить від z, тож дорівнює своєму математичному сподіванню за q. За теоремою Баєса p(x) = p(x | z) p(z) / p(z | x); дріб домножимо на q(z | x) / q(z | x):

log p(x) = Eq[log p(x | z)] + Eq[log (p(z) / q(z | x))] + Eq[log (q(z | x) / p(z | x))].

Другий член — це −KL(q ‖ p(z)), третій — KL(q ‖ p(z | x)). KL-дивергенція завжди невід’ємна (нерівність Гіббса з частини про теорію інформації), тож, відкинувши третій член, отримуємо нерівність. ∎

Права частина — це ELBO (evidence lower bound), і при β = 1 це саме −L. Мінімізуючи L, ми, отже, піднімаємо нижню межу log p(x), не обчислюючи інтеграла. Рівність настає, коли q(z | x) збігається з істинним апостеріорним розподілом p(z | x).

Означення — бінарна перехресна ентропія пікселів

Декодер повертає для кожного з 64 пікселів число yj ∈ (0, 1) і вважає його ймовірністю того, що піксель світиться. Для зображення з яскравостями xj ∈ [0, 1]

−log p(x | z) = −∑j=164 [ xj log yj + (1 − xj) log(1 − yj) ].

Приклад для одного пікселя: він світиться (x = 1), а декодер каже 0,9 → −ln 0,9 ≈ 0,105; каже 0,5 → ln 2 ≈ 0,693; не світиться (x = 0), а декодер каже 0,9 → −ln 0,1 ≈ 2,303. Найдорожче коштує впевнена помилка. Оскільки на виході сигмоїда, похідна за входом сигмоїди дорівнює просто yj − xj — так само, як у логістичній регресії.

Означення — трюк репараметризації (reparameterization trick)

Випадковий вибір z ~ N(μ, σ²) не можна диференціювати за μ і σ. Тому його записують як z = μ + σ · ε, де ε ~ N(0, 1) — шум, незалежний від параметрів. Тоді ∂z/∂μ = 1 і ∂z/∂σ = ε, і градієнт зворотним поширенням доходить аж до енкодера.

Простими словами

Випадковість ми «виносимо назовні»: замість «вибери точку з хмаринки» кажемо «вибери звичайний шум і зсунь та розтягни його так, як каже енкодер». Зсув і розтягування — це звичайні обчислення, а їх диференціювати можна.

zj = μj(x) + σj(x) · εj,   σj = exp(½ · log σj²),   εj ~ N(0, 1)

μj(x)
центр хмаринки, вихід енкодера — ∂z/∂μ = 1, градієнт декодера проходить в енкодер без змін
σj(x)
ширина хмаринки — енкодер повертає не σ, а log σ², яке може бути будь-яким дійсним числом; експонента завжди робить із нього додатну ширину, тож мережі не треба стежити за жодною умовою. ∂z/∂σ = ε
εj ~ N(0, 1)
уся випадковість — не залежить від жодного параметра, тож крізь неї нічого диференціювати не треба; під час генерування енкодер пропускаємо, а z вибираємо одразу з N(0, I)
Зсунута й розтягнута нормальна величина знову нормальна: zj ~ N(μj, σj²), тобто саме зразок із q(z | x).
Приклад — одна тестова цифра (0) у навченому VAE з k = 2

енкодер: μ = (0,285; 1,587),   log σ² = (−2,695; −1,931) → σ = (0,260; 0,381)

KL1 = ½ (0,081 + 0,068 + 2,695 − 1) ≈ 0,922

KL2 = ½ (2,518 + 0,145 + 1,931 − 1) ≈ 1,797   → KL ≈ 2,72 ната

репараметризація з ε = (0,5; −1,0): z = (0,285 + 0,260 · 0,5; 1,587 − 0,381 · 1,0) = (0,415; 1,206)

Більшу частину штрафу тут дає не відстань від початку координат, а вузькість хмаринки: член −log σ² вносить 2,695 і 1,931. Енкодер хотів би якомога точніший код, а KL тримає його ширшим. Середня KL за тестовими цифрами становить 3,42 ната. Помилка реконструкції цієї цифри (з центру μ) становить 34,6 ната на 64 пікселі.

def krok_vae(davka, beta=1.0):
    for x in davka:
        mu, logvar = koder(x)                  # k + k чисел
        sigma = exp(0.5 * logvar)
        eps = normal(0, 1, size=len(mu))       # випадковість ззовні
        z = mu + sigma * eps                   # трюк репараметризації
        y = dekoder(z)                         # імовірності пікселів
        rek = -sum(x * log(y) + (1 - x) * log(1 - y))
        kl = 0.5 * sum(mu**2 + exp(logvar) - logvar - 1)
        zpetne_sireni(rek + beta * kl)         # градієнт іде z → mu, sigma → енкодер
    adam_krok()                                # середній градієнт пакета

Наша мережа: 64 пікселі → 32 нейрони (tanh) → μ і log σ² (по k чисел) → 32 нейрони → 64 пікселі (сигмоїда), навчена методом Adam за 150 епох на 600 синтетичних цифрах. Та сама мережа, лише без шуму ε і без штрафу KL, — це звичайний автокодувальник, тож порівняння чесне.

3. Упорядкований латентний простір

00000000000000000000111111111111111111112222222222222222222233333333333333333333444444444444444444445555555555555555555566666666666666666666777777777777777777778888888888888888888899999999999999999999 звичайний автокодувальник код z₁ (без одиниць) код z₂ (без одиниць) 00000000000000000000111111111111111111112222222222222222222233333333333333333333444444444444444444445555555555555555555566666666666666666666777777777777777777778888888888888888888899999999999999999999 VAE код z₁ (без одиниць) код z₂ (без одиниць)
Коди 200 тестових цифр (k = 2); пунктиром кола радіусом 1 і 2 навколо початку координат

Обидві мережі групують однакові цифри разом. Але автокодувальник розкидає їх по великій площі — обидва кола нормального розподілу порівняно з нею малі. Коди VAE лежать навколо початку координат зі стандартним відхиленням 1,14 і 1,04, тобто саме там, звідки ми вибиратимемо.

Як виглядає весь простір, покаже сітка: рівномірно розміщені точки латентного простору, кожна пропущена через декодер.

автокодувальник, центр ± 2 ст. відх. код z₁ (без одиниць) код z₂ (без одиниць) VAE, координати від −2,2 до 2,2 код z₁ (без одиниць) код z₂ (без одиниць)
Декодер на сітці 10 × 10 точок латентного простору

У VAE цифри плавно перетікають одна в одну, і майже кожна клітинка — це читабельна цифра. Чесно кажучи, на площині й декодер автокодувальника доволі гладкий, і його сітка теж містить переважно цифри. Різницю на повну покаже лише код із більшою кількістю вимірів.

4. Генерування: нові цифри з шуму

Випадково виберемо 200 кодів і декодуємо їх: для VAE з N(0, I), для автокодувальника (щоб мав чесний шанс) — з нормального розподілу, підігнаного під його справжні коди. Якість вимірюємо відстанню (середньоквадратичною похибкою) зразка до найближчої навчальної цифри. Справжні тестові цифри мають до найближчої навчальної в середньому 0,017, випадковий шум — 0,159.

Означення — відстань до найближчої навчальної цифри та підігнаний розподіл

Для зразка y ∈ [0, 1]64 і навчальних цифр x(1), …, x(600)

d(y) = mini (1/64) ∑j=164 (yj − x(i)j)²,

тобто середньоквадратична похибка (MSE) до найсхожішої цифри; якість генератора — це середнє d за 200 зразками. Мале d означає «схоже на якусь справжню цифру» — увага: нуль отримав би й генератор, який лише копіює навчальні дані.

Автокодувальник вибирає з zj = mj + sj · εj, де mj і sj — середнє та стандартне відхилення j-ї координати кодів тестових цифр (для k = 2: m = (−3,9; −0,9), s = (4,3; 6,0)). Координати вибираються незалежно — можливу залежність між ними підігнаний розподіл не вловить.

00,050,10,15 k = 2 k = 8 k = 16 розмір коду k (кількість чисел) відстань до цифри (MSE)
Відстань згенерованих зображень до найближчої навчальної цифри (менше = правдоподібніше) автокодувальник VAE справжні цифри випадковий шум
розмір коду kреконструкція AE (MSE)реконструкція VAE (MSE)зразки AEзразки VAE
20,0510,0630,0420,044
80,0250,0360,0900,054
160,0170,0390,1120,055

З двовимірним кодом обидві мережі мають подібні результати (0,042 проти 0,044). З вісьмома вимірами автокодувальник розвалюється: його зразки мають відстань 0,090, більше ніж півдороги до шуму, тоді як VAE тримає 0,054. Із шістнадцятьма — 0,112 проти 0,055. Коди автокодувальника в багатьох вимірах лежать на тонкій викривленій поверхні, і випадкова точка з підігнаного нормального розподілу практично ніколи в неї не влучає.

автокодувальник VAE
Перші десять згенерованих зображень, код із 8 вимірами

Ціна: VAE реконструює гірше. При k = 16 його помилка реконструкції становить 0,039 проти 0,017 в автокодувальника, а його зображення розмитіші. Код зашумлений, тож декодер навчається малювати «середнє» того, що могло б бути в околі коду. Розмитість — відома слабкість VAE.

5. Реконструкція проти порядку: β

Вага β визначає, наскільки сильно штраф KL штовхає коди до нормального розподілу. П’ять VAE з кодом k = 2 і різним β:

βKL (натів на зображення)реконструкція (MSE)зразки (відстань)
0,18,180,0530,044
0,54,710,0570,044
13,420,0630,044
22,070,0760,052
50,050,1310,075

Мале β залишає кодам більше свободи (KL 8,2), і реконструкція точніша. Велике β стискає їх до нуля. При β = 5 KL падає до 0,05: енкодер перестав будь-що зберігати в коді, а декодер малює весь час приблизно те саме, хоч який код отримає. Це називають колапсом апостеріорного розподілу (posterior collapse).

Означення — нат і KL як ціна коду

Нат — одиниця інформації з натуральним логарифмом: 1 нат = 1 / ln 2 ≈ 1,443 біта. Середня KL за даними обмежує зверху взаємну інформацію між зображенням і кодом:

Ex KL(q(z | x) ‖ p(z)) = I(x; z) + KL(q(z) ‖ p(z)) ≥ I(x; z),

де q(z) = Ex q(z | x) — розподіл кодів усіх зображень разом. KL — це, отже, «ціна», яку енкодер платить за те, що зберігає в коді про зображення.

При β = 0,1 це щонайбільше 8,18 ната ≈ 11,8 біта на зображення, при β = 5 лише 0,05 ната ≈ 0,07 біта — менше ніж десята частина біта, тож код майже нічого не каже про зображення, і декодерові не лишається нічого іншого, як малювати весь час те саме.

β = 0,1 β = 1 β = 5
Згенеровані зображення VAE (k = 2) за різних β

Натомість β, більше за 1, свідомо використовує β-VAE (Ірина Гіґґінс та ін., 2017): сильніший тиск на незалежні координати коду призводить до того, що окремі виміри часто відповідають зрозумілим властивостям (нахил, товщина штриха).

6. Плавні переходи

Візьмемо коди двох тестових цифр (мережі з восьмивимірним кодом, від кожної цифри — зображення, яке VAE реконструює найкраще) і пройдемо прямою між ними за вісім кроків: z(t) = (1 − t) · za + t · zb для t = 0, 1/7, 2/7, …, 1, де za, zb — центри μ обох цифр. Кожну проміжну точку декодуємо.

AE: 1 → 7 VAE: 1 → 7 AE: 0 → 6 VAE: 0 → 6
Інтерполяція в латентному просторі (k = 8)

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

7. Де використовують VAE

  • Стиснення для інших генераторів. Stable Diffusion (латентна дифузія, Робін Ромбах та ін., 2022) працює не з пікселями, а з кодом автокодувальника з малим штрафом KL: зображення 512 × 512 × 3 стискає до 64 × 64 × 4, і дифузія відбувається в цьому меншому просторі. Дифузії буде присвячена одна з наступних частин.
  • Дискретні коди. VQ-VAE (Аарон ван ден Оорд та ін., 2017) замість неперервного коду вибирає елементи з навченого словника. Зображення так стає послідовністю «токенів», яку вміє генерувати трансформер.
  • Наукові дані. Молекули, клітинні дані, звук — усюди, де корисний гладкий простір, у якому можна шукати та інтерполювати.

За різкістю зображень VAE програють GAN і дифузійним моделям. Зате в них стабільне навчання, справжня ймовірнісна інтерпретація і енкодер задарма.

8. Підсумок

  • Генеративна модель навчається розподілу даних, щоб із нього можна було вибирати зразки; з латентною змінною вона вибирає код і декодує його.
  • VAE кодує в розподіл N(μ, σ²), а втрата = реконструкція + β · KL до N(0, I). Трюк z = μ + σε дає змогу диференціювати крізь випадковий вибір.
  • Коди VAE лежать навколо початку координат і заповнюють простір; коди автокодувальника розкидані й мають діри.
  • З 8 вимірами коду: зразки автокодувальника 0,090, VAE 0,054 (справжні цифри 0,017, шум 0,159).
  • Ціна — гірша реконструкція і розмитість; надто велике β веде до колапсу (KL 0,05).

Хочете спробувати самі? Попросіть ШІ-агента навчити VAE з двовимірним кодом на наборі даних MNIST і намалювати сітку декодованих зображень — ви побачите цілу «карту» рукописних цифр.

Маєте іншу думку? Напишіть на support@coden-app.com.

Агент на вашому комп’ютері, керований з телефона