Назад
125

Adversarial training

125

Введение

Довольно часто можно услышать о том, что нейросеть обошла человека при решении той или иной задачи. Например, модели для CV. Считается, что они стали лучше нас уже в 2015 году.

Рисунок 1. Процентное сопоставление Traditional CV и Deep learning CV

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

Давайте посмотрим на следующие два рисунка и подумаем, что на каждом из них изображено.

Рисунок 2. Молоток vs топор
Рисунок 3. Молоток vs топор

По мнению resnet152, на первой картинке представлен молоток, а на второй — топор!

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

Изменение входного вектора с целью “обмана” нейросети называется состязательной (adversarial) атакой.

Adversarial атаки

Самая простая состязательная атака — поиск вектора возмущения \( \epsilon \), при котором нейросеть будет уверенно предсказывать отличный от исходного класс. Вектор \( \epsilon \) можно найти с помощью градиентного спуска.

Давайте рассмотрим градиент:

\( \nabla_{\epsilon}(L)= \frac{dL(N_w(x+\epsilon), y)}{d\epsilon} \)

где \( L \) — лосс функция;

\( x \) — исходное изображение;

\( N \) — нейросеть c весами \( w \).

Напомним, что градиент — это направление самого быстрого увеличения функции. Значит, при движении \( \epsilon \) в сторону градиента мы максимизируем лосс функцию, что нам и нужно. Следовательно, мы можем вывести шаг градиентного спуска для оптимизации \( \epsilon \) (на самом деле это градиентный “подъем”, так как мы максимизируем функцию):

\( \epsilon= \epsilon+η⋅\nabla_{\epsilon}(L) \)

где \( η \) — learning rate.

Если мы хотим, чтобы \( \epsilon \) был мал, а значит, незаметен для человека, то мы добавляем регуляризацию (либо для ограничения порогом пользуемся torch.clip).

В коде такую атаку можно реализовать следующим образом:

hummer_class_number = 587  # номер класса hummer из ImageNet
tiger_cat_class_number = 282  # номер класса tiger_cat из ImageNet

model = resnet152(pretrained=True)
model.eval()
eps = torch.zeros_like(img_tensor, requires_grad=True)
opt = optim.SGD([eps], lr=1, weight_decay=1e-3)

for step in range(7):
    pred = model(img_tensor + eps)
    loss = -F.cross_entropy(pred, torch.LongTensor([hummer_class_number]))
    opt.zero_grad()
    loss.backward()
    opt.step()
print(torch.abs(eps).max().item())  # tensor(0.1409)
prediction = model(img_tensor + eps)
prediction_class_id = prediction.argmax().item()
print(classes[prediction_class_id])  # "hatchet"

Так нейросеть вместо молотка распознала топор. Это несильно страшно, ведь они в целом похожи. А давайте теперь найдем \( \epsilon \), при котором нейросеть вместо молотка предскажет нам тигра?

Для этого мы просто немного изменим лосс функцию и получим targeted attack (направленную атаку):

loss += F.cross_entropy()(pred, torch.LongTensor([tiger_cat_class_number]))

И, вуаля, перед нами изображение тигра, который выглядит как молоток!

Рисунок 4. Молоток vs тигр

Adversarial training

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

\( L_{adv}=\underset{(||\epsilon||_{\infty}\le p )}{\max}L(N_w(x+\epsilon),y) \)

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

\( \epsilon=\underset{\epsilon}\argmax(L(N(x+\epsilon),y)) \)

Такая оптимизационная задача может быть решена с помощью градиентного “подъема” (про него мы говорили в части про Adversarial атаки).

Получается, шаг обучения модели, устойчивой к Adversarial атакам, создается следующим образом:

  1. Берем батч из тренировочной выборки;
  2. Делаем несколько шагов обновления \( \epsilon= \epsilon+η⋅\nabla_{\epsilon}(L) \);
  3. Считаем \( \frac {dL(N_w(x+\epsilon),y)}{dw} \);
  4. Обновляем веса модели.

Если на втором этапе мы делаем один шаг, то такой метод поиска \( \epsilon \) называется Fast Gradient Sign Method (FGSM), а если несколько — Projected gradient descent (PGD). Также \( \epsilon \) можно найти с помощью линейного программирования, но это вычислительно емкий процесс.

Устойчивость к Adversarial атакам оценивается c помощью robust метрик. Они отличаются от обычных тем, что вместо входного вектора в них мы используем вектор, к которому мы применили атаку.

Проблемы устойчивых моделей

У моделей, полученных с помощью Adversarial training, есть несколько проблем, которые мешают их повсеместному использованию.

Во-первых, при повышении устойчивости модели ухудшаются обычные метрики. Эта проблема решается увеличением количества входных данных.

Во-вторых, разница в robust метриках на тренировочной и тестовой выборках очень высока. Такая проблема решается только использованием более сложных подходов. Разница между ошибкой на тренировочной и тестовой выборках называется generalisation gap.

Adversarial weight perturbation

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

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

Более подробно об этом можно почитать в статье Sharpness-Aware Minimization.

Сам алгоритм выглядит так:

  1. Берем батч из тренировочной выборки
  2. Делаем несколько шагов обновления

\( \epsilon= \epsilon+η_1⋅\nabla_{\epsilon}(L)​ \)

  1. Делаем несколько шагов обновления

\( v= v+η_2\frac{dL(N_{w+v}(x+\epsilon), y)}{dv} \)

  1. Считаем градиент в точке \( w+v \) и обновляем веса модели в точке \( w \) (не в \( w+v \) !!!)

\( w= w-η_3\frac{dL(N_{w+v}(x+\epsilon), y)}{dw} \)

Реализацию алгоритма можно посмотреть в репозитории авторов.

Diffusion Models

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

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

Заключение

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

Телеграм-канал

DeepSchool

Короткие посты по теории ML/DL, полезные
библиотеки и фреймворки, вопросы с собеседований
и советы, которые помогут в работе

Открыть Телеграм

Увидели ошибку?

Напишите нам в Telegram!