Adversarial-примеры: как ломают нейросети одним пикселем
Незаметный для человека шум в изображении заставляет модель уверенно ошибаться. Разбираем, как устроены adversarial-атаки, чем FGSM отличается от PGD и что реально защищает продакшн.

Классический пример из работы Goodfellow, Shlens и Szegedy (2014): модель уверенно распознаёт панду, к картинке добавляют шум с амплитудой в доли пикселя — человек не видит разницы, а сеть с вероятностью 99% выдаёт «гиббон». Это не баг конкретной архитектуры и не случайность обучения. Это структурное свойство линейных решающих границ в многомерном пространстве, и оно бьёт по любому классификатору, который вы выкатываете в продакшн — от распознавания дорожных знаков до антифрода.
Для инженера это значит одно: модель, показавшая 98% accuracy на тесте, может давать почти 0% на входах, специально подобранных под неё. Если ваша система принимает данные из внешнего мира — камеры, загрузки пользователей, API — злоумышленник контролирует вход и может это использовать.
Что такое adversarial example
Adversarial example — это корректный на вид вход, в который внесено минимальное возмущение 1, целенаправленно сдвигающее предсказание модели. Формально ищется такое возмущение δ, чтобы модель ошиблась, а норма δ оставалась ниже порога заметности (обычно ограничивают L∞ или L2).
Ключевая интуиция: в пространстве с тысячами измерений (картинка 224×224×3 — это больше 150 тысяч признаков) даже крошечное изменение каждого пикселя суммируется в большой сдвиг вдоль градиента функции потерь. Модель линейна ровно настолько, чтобы этот сдвиг перебросил вход через границу класса.
Adversarial-примеры — это не редкие точки, которые надо специально искать. Это плотное множество вокруг почти каждого корректного входа. Вопрос не «есть ли они», а «насколько дёшево их построить под вашу модель».
Атака с доступом к градиентам (white-box)
Если атакующий знает веса модели, он считает градиент функции потерь по входу и двигает вход в сторону роста ошибки. Два базовых метода:
- FGSM (Fast Gradient Sign Method) — один шаг вдоль знака градиента:
x' = x + ε · sign(∇ₓ L(x, y)). Быстро, дёшево, слабее. - PGD (Projected Gradient Descent) — много маленьких шагов FGSM с проекцией обратно в допустимый ε-шар. Медленнее, но заметно сильнее и считается стандартным бенчмарком устойчивости.
Ниже — учебная реализация FGSM на PyTorch. Она показывает механику: получить градиент по входу и сдвинуть картинку на ε вдоль его знака.
import torch
import torch.nn.functional as F
def fgsm_attack(model, x, y, eps):
x = x.clone().detach().requires_grad_(True)
logits = model(x)
loss = F.cross_entropy(logits, y)
model.zero_grad()
loss.backward()
# шаг вдоль знака градиента по входу
x_adv = x + eps * x.grad.sign()
# держим значения в допустимом диапазоне пикселей
x_adv = torch.clamp(x_adv, 0.0, 1.0)
return x_adv.detach()
PGD — это тот же шаг в цикле с проекцией. Схематично:
def pgd_attack(model, x, y, eps, alpha, steps):
x_adv = x.clone().detach()
x_adv = x_adv + torch.empty_like(x_adv).uniform_(-eps, eps)
x_adv = torch.clamp(x_adv, 0.0, 1.0)
for _ in range(steps):
x_adv.requires_grad_(True)
loss = F.cross_entropy(model(x_adv), y)
grad = torch.autograd.grad(loss, x_adv)[0]
x_adv = x_adv.detach() + alpha * grad.sign()
# проекция обратно в eps-шар вокруг исходного x
x_adv = torch.min(torch.max(x_adv, x - eps), x + eps)
x_adv = torch.clamp(x_adv, 0.0, 1.0)
return x_adv
Атака без доступа к весам (black-box)
Чаще у атакующего есть только API, отдающий предсказание. Это не спасает по двум причинам:
- Переносимость (transferability). Возмущение, построенное под одну модель, часто ломает и другую, обученную на похожих данных. Атакующий обучает суррогатную модель, строит adversarial-пример на ней и подаёт вашей.
- Оценка градиента по запросам. Даже если модель отдаёт только метку, градиент можно приблизить численно, посылая множество слегка изменённых входов и наблюдая ответы.
Чем это грозит на практике
Цена вопроса зависит от того, что модель решает и кто контролирует вход.
| Сценарий | Кто контролирует вход | Последствие атаки |
|---|---|---|
| Модерация контента | Загружающий пользователь | Запрещённый контент проходит как безопасный |
| Антифрод / антиспам | Отправитель | Мошеннические транзакции классифицируются как легитимные |
| Распознавание в физическом мире | Владелец объекта в кадре | Наклейка или патч сбивает детектор |
| Биометрия / доступ | Человек перед камерой | Обход или ложное совпадение |
Отдельный класс — физические атаки: печатные патчи и текстуры, которые работают через камеру при разных углах и освещении. Здесь возмущение уже не «доли пикселя», а видимый узор, но незаметность приносится в жертву устойчивости к съёмке.
Что реально защищает
Универсального решения нет, и честно признать это важнее, чем продать очередной «фильтр». Многие ранние защиты падали, как только выяснялось, что они лишь маскируют градиенты, а не устраняют уязвимость (эффект obfuscated gradients). Что показывает устойчивость на бенчмарках:
- Adversarial training — дообучение модели на adversarial-примерах, сгенерированных PGD прямо во время обучения. Самый надёжный из известных подходов, но дорогой: обучение замедляется в разы, а чистая accuracy обычно проседает.
- Ограничение и мониторинг доступа к API. Rate limiting, детекция аномальных серий запросов, отказ отдавать полные вероятности вместо метки — всё это удорожает black-box-атаку.
- Ансамбли и рандомизация входа. Не панацея: часть таких защит ломается адаптивной атакой, которая учитывает саму защиту.
- Сертифицированные методы (например, randomized smoothing) дают гарантию устойчивости в пределах доказанного радиуса возмущения, но радиус этот на реальных задачах пока скромный.
Оценивать защиту нужно адаптивной атакой — той, которая знает про защиту и целится в неё. Точность на «наивном» PGD ничего не доказывает: почти любая маскировка градиента даёт красивые цифры, которые рассыпаются при первом же честном тесте.
Что сделать инженеру на этой неделе
- Прогнать свою продакшн-модель через FGSM и PGD с несколькими ε и посмотреть, где accuracy падает до нуля.
- Проверить, отдаёт ли ваш API полные логиты или вероятности — если да, вы дарите атакующему сигнал для оценки градиента.
- Не строить защиту на предположении «злоумышленник не знает архитектуру»: переносимость атак это предположение обесценивает.
1 Возмущение (perturbation) — добавка к входу, ограниченная по норме так, чтобы для человека вход выглядел неизменным. Порог задают через L∞ (максимальное изменение одного пикселя) или L2 (суммарная величина изменения).
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: Goodfellow et al. Explaining and Harnessing Adversarial Examples (arXiv), Madry et al. Towards Deep Learning Models Resistant to Adversarial Attacks (arXiv)
Частые вопросы
Adversarial-примеры работают только на картинках?
Если у атакующего нет доступа к моей модели, я в безопасности?
Достаточно ли обучить модель на зашумлённых данных?
Почему adversarial training не решает проблему полностью?
Как понять, что моя защита реально работает, а не маскирует градиенты?
Стоит ли скрывать вероятности классов в ответе API?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.