А теперь поставим на паузу, отодвинем кофе в сторону, импортируем все необходимые нам библиотеки и разберем как работают подобные атаки one pixel attack.
Цель данной атаки заставить алгоритм (нейросеть) выдать некорректный ответ. Ниже увидим это с несколькими различными моделями сверточных нейронных сетей. Используя один из методов многомерной математической оптимизации — дифференциальную эволюцию, найдем особенный пиксель, способный изменить изображение так, чтобы нейросеть стала неправильно классифицировать это изображение (несмотря на то, что ранее алгоритм “узнавал” это же изображение корректно и с высокой точностью).
Импортируем библиотеки:
# Python Libraries
%matplotlib inline
import pickle
import numpy as np
import pandas as pd
import matplotlib
from keras.datasets import cifar10
from keras import backend as K
# Custom Networks
from networks.lenet import LeNet
from networks.pure_cnn import PureCnn
from networks.network_in_network import NetworkInNetwork
from networks.resnet import ResNet
from networks.densenet import DenseNet
from networks.wide_resnet import WideResNet
from networks.capsnet import CapsNet
# Helper functions
from differential_evolution import differential_evolution
import helper
matplotlib.style.use('ggplot')
Для нашего эксперимента загрузим датасет CIFAR-10, содержащий изображения реального мира, разбитых на 10 классов.
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Посмотрим на любое изображение по его индексу. Например, вот на эту лошадь.
image_id = 99 # Image index in the test set
helper.plot_image(x_test[image_id])
Нам придется искать тот самый могучий пиксель, способный изменить ответ нейросети, а значит, пора написать функцию для изменения одного или нескольких пикселей изображения.
def perturb_image(xs, img):
# If this function is passed just one perturbation vector,
# pack it in a list to keep the computation the same
if xs.ndim < 2:
xs = np.array([xs])
# Copy the image n == len(xs) times so that we can
# create n new perturbed images
tile = [len(xs)] + [1]*(xs.ndim+1)
imgs = np.tile(img, tile)
# Make sure to floor the members of xs as int types
xs = xs.astype(int)
for x,img in zip(xs, imgs):
# Split x into an array of 5-tuples (perturbation pixels)
# i.e., [[x,y,r,g,b], ...]
pixels = np.split(x, len(x) // 5)
for pixel in pixels:
# At each pixel's x,y position, assign its rgb value
x_pos, y_pos, *rgb = pixel
img[x_pos, y_pos] = rgb
return imgs
Проверим?! Изменим один пиксель нашей лошади с координатами (16, 16) на желтый.
image_id = 99 # Image index in the test set
pixel = np.array([16, 16, 255, 255, 0]) # pixel = x,y,r,g,b
image_perturbed = perturb_image(pixel, x_test[image_id])[0]
helper.plot_image(image_perturbed)
Для демонстрации атаки необходимо загрузить предобученные модели нейронных сетей на нашем датасете CIFAR-10. Мы будем использовать две модели lenet и resnet, но вы можете использовать для своих экспериментов и другие, раскомментировав соответствующие строки кода.
lenet = LeNet()
resnet = ResNet()
models = [lenet, resnet]
После загрузки моделей необходимо оценить тестовые изображения каждой модели, чтобы убедиться что мы атакуем только изображения, которые правильно классифицированы. Код ниже отображает точность и количество параметров каждой модели.
network_stats, correct_imgs = helper.evaluate_models(models, x_test, y_test)
correct_imgs = pd.DataFrame(correct_imgs, columns=['name', 'img', 'label', 'confidence', 'pred'])
network_stats = pd.DataFrame(network_stats, columns=['name', 'accuracy', 'param_count'])
network_stats
Evaluating lenet
Evaluating resnet
Out[11]:
name accuracy param_count
0 lenet 0.748 62006
1 resnet 0.9231 470218
Все подобные атаки можно разделить на два класса: WhiteBox и BlackBox. Разница между ними в том, что в первом случае нам все достоверно известно об алгоритме, модели с которой имеем дело. В случае с BlackBox все что нам нужно это входные данные (изображение) и выходные данные (вероятности отнесения к одному из классов). Атака одного пикселя (one pixel attack) относится к BlackBox.
В этой статье рассмотрим два варианта атаки одного пикселя: untargeted и targeted. В первом случае нам будет абсолютно все равно к какому классу отнесет нейронная сеть нашего котика, главное, чтобы не к классу котиков. Targeted атака применима когда мы хотим, чтобы наш котик непременно стал грузовиком и только грузовиком.
Но как же найти те самые пиксели, изменение которых приведет к изменению класса изображения? Как найти пиксель, поменяв который one pixel attack станет возможна и успешна? Давайте попробуем сформулировать эту проблему как задачу оптимизации, но только очень простыми словами: при untargeted attack мы должны минимизировать доверие к нужному классу, а при targeted — максимизировать доверие к целевому классу.
При проведении подобного рода атак трудно оптимизировать функцию с помощью градиента. Необходимо использовать алгоритм оптимизации, который не полагается на гладкость функции.
Напомним, что для нашего эксперимента мы используем датасет CIFAR-10, содержащий изображения реального мира, размером 32 х 32 пикселя, разбитых на 10 классов. А это означает, что у нас есть целочисленные дискретные значения от 0 до 31 и интенсивности цвета от 0 до 255, и функция ожидается не гладкая, а скорее зазубренная, как показано ниже:
Именно поэтому мы используем алгоритм дифференциальной эволюции.
Но вернемся к коду и напишем функцию, которая возвращает вероятность достоверности модели. Если целевой класс является правильным, то эту функцию мы хотим минимизировать, чтобы модель была уверена в другом классе (что не верно).
def predict_classes(xs, img, target_class, model, minimize=True):
# Perturb the image with the given pixel(s) x and get the prediction of the model
imgs_perturbed = perturb_image(xs, img)
predictions = model.predict(imgs_perturbed)[:,target_class]
# This function should always be minimized, so return its complement if needed
return predictions if minimize else 1 - predictions
image_id = 384
pixel = np.array([16, 13, 25, 48, 156])
model = resnet
true_class = y_test[image_id, 0]
prior_confidence = model.predict_one(x_test[image_id])[true_class]
confidence = predict_classes(pixel, x_test[image_id], true_class, model)[0]
print('Confidence in true class', class_names[true_class], 'is', confidence)
print('Prior confidence was', prior_confidence)
helper.plot_image(perturb_image(pixel, x_test[image_id])[0])
Confidence in true class bird is 0.00018887444
Prior confidence was 0.70661753
Следующая функция понадобится нам, чтобы подтверждать критерий успеха атаки, она будет возвращать True, когда изменения было достаточно, чтобы обмануть модель.
def attack_success(x, img, target_class, model, targeted_attack=False, verbose=False):
# Perturb the image with the given pixel(s) and get the prediction of the model
attack_image = perturb_image(x, img)
confidence = model.predict(attack_image)[0]
predicted_class = np.argmax(confidence)
# If the prediction is what we want (misclassification or
# targeted classification), return True
if verbose:
print('Confidence:', confidence[target_class])
if ((targeted_attack and predicted_class == target_class) or
(not targeted_attack and predicted_class != target_class)):
return True
# NOTE: return None otherwise (not False), due to how Scipy handles its callback function
Посмотрим на работу функции критерия успеха. В целях демонстрации предполагаем нецелевую атаку.
image_id = 541
pixel = np.array([17, 18, 185, 36, 215])
model = resnet
true_class = y_test[image_id, 0]
prior_confidence = model.predict_one(x_test[image_id])[true_class]
success = attack_success(pixel, x_test[image_id], true_class, model, verbose=True)
print('Prior confidence', prior_confidence)
print('Attack success:', success == True)
helper.plot_image(perturb_image(pixel, x_test[image_id])[0])
Confidence: 0.07460087
Prior confidence 0.50054216
Attack success: True
Пора собрать все пазлы в одну картинку. Будем использовать небольшую модификацию реализации дифференциальной эволюции в Scipy.
def attack(img_id, model, target=None, pixel_count=1,
maxiter=75, popsize=400, verbose=False):
# Change the target class based on whether this is a targeted attack or not
targeted_attack = target is not None
target_class = target if targeted_attack else y_test[img_id, 0]
# Define bounds for a flat vector of x,y,r,g,b values
# For more pixels, repeat this layout
bounds = [(0,32), (0,32), (0,256), (0,256), (0,256)] * pixel_count
# Population multiplier, in terms of the size of the perturbation vector x
popmul = max(1, popsize // len(bounds))
# Format the predict/callback functions for the differential evolution algorithm
def predict_fn(xs):
return predict_classes(xs, x_test[img_id], target_class,
model, target is None)
def callback_fn(x, convergence):
return attack_success(x, x_test[img_id], target_class,
model, targeted_attack, verbose)
# Call Scipy's Implementation of Differential Evolution
attack_result = differential_evolution(
predict_fn, bounds, maxiter=maxiter, popsize=popmul,
recombination=1, atol=-1, callback=callback_fn, polish=False)
# Calculate some useful statistics to return from this function
attack_image = perturb_image(attack_result.x, x_test[img_id])[0]
prior_probs = model.predict_one(x_test[img_id])
predicted_probs = model.predict_one(attack_image)
predicted_class = np.argmax(predicted_probs)
actual_class = y_test[img_id, 0]
success = predicted_class != actual_class
cdiff = prior_probs[actual_class] - predicted_probs[actual_class]
# Show the best attempt at a solution (successful or not)
helper.plot_image(attack_image, actual_class, class_names, predicted_class)
return [model.name, pixel_count, img_id, actual_class, predicted_class, success, cdiff, prior_probs, predicted_probs, attack_result.x]
Пришло время поделиться результатами исследования (проведенной атаки) и посмотреть как изменение лишь одного пикселя превратит лягушку в собаку, кота в лягушку, а автомобиль в самолет. А ведь чем больше точек изображения позволено изменять, тем выше вероятность успешной атаки на любое изображение.
Продемонстрируем успешную атаку на изображение лягушки с помощью модели resnet. Мы должны увидеть уверенность в истинном снижении класса после нескольких итераций.
image_id = 102
pixels = 1 # Number of pixels to attack
model = resnet
_ = attack(image_id, model, pixel_count=pixels, verbose=True)
Confidence: 0.9938618
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.77454716
Confidence: 0.53226393
Confidence: 0.53226393
Confidence: 0.53226393
Confidence: 0.53226393
Confidence: 0.4211318
Это были примеры untargeted attack, а теперь проведем targeted attack и выберем к какому классу мы бы хотели, чтобы модель отнесла (классифицировала) изображение. Задача намного сложнее предыдущей, ведь мы заставим нейросеть классифицировать изображение корабля как автомобиля, а лошадь как кота.
Ниже мы попытаемся заставить lenet классифицировать изображение корабля как автомобиля.
image_id = 108
target_class = 1 # Integer in range 0-9
pixels = 3
model = lenet
print('Attacking with target', class_names[target_class])
_ = attack(image_id, model, target_class, pixel_count=pixels, verbose=True)
Attacking with target automobile
Confidence: 0.044409167
Confidence: 0.044409167
Confidence: 0.044409167
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.054611664
Confidence: 0.081972085
Confidence: 0.081972085
Confidence: 0.081972085
Confidence: 0.081972085
Confidence: 0.1537778
Confidence: 0.1537778
Confidence: 0.1537778
Confidence: 0.22246778
Confidence: 0.23916133
Confidence: 0.25238588
Confidence: 0.25238588
Confidence: 0.25238588
Confidence: 0.44560355
Confidence: 0.44560355
Confidence: 0.44560355
Confidence: 0.5711696
Разобравшись с единичным случаями проведения атак, соберем статистику, используя архитектуру сверточных нейронных сетей ResNet, пройдясь по каждой модели, изменяя 1, 3 или 5 пикселей каждого изображения. В этой статье покажем итоговые выводы не утруждая читателя ознакомлением с каждой итерацией, поскольку это занимает немало времени и вычислительных ресурсов.
def attack_all(models, samples=500, pixels=(1,3,5), targeted=False,
maxiter=75, popsize=400, verbose=False):
results = []
for model in models:
model_results = []
valid_imgs = correct_imgs[correct_imgs.name == model.name].img
img_samples = np.random.choice(valid_imgs, samples, replace=False)
for pixel_count in pixels:
for i, img_id in enumerate(img_samples):
print('\n', model.name, '- image', img_id, '-', i+1, '/', len(img_samples))
targets = [None] if not targeted else range(10)
for target in targets:
if targeted:
print('Attacking with target', class_names[target])
if target == y_test[img, 0]:
continue
result = attack(img_id, model, target, pixel_count,
maxiter=maxiter, popsize=popsize,
verbose=verbose)
model_results.append(result)
results += model_results
helper.checkpoint(results, targeted)
return results
untargeted = attack_all(models, samples=100, targeted=False)
targeted = attack_all(models, samples=10, targeted=False)
Для проверки возможности дискредитации сети был разработан алгоритм и измерено его влияние на качество прогноза решения по распознаванию образов.
Посмотрим окончательные результаты.
untargeted, targeted = helper.load_results()
columns = ['model', 'pixels', 'image', 'true', 'predicted', 'success', 'cdiff', 'prior_probs', 'predicted_probs', 'perturbation']
untargeted_results = pd.DataFrame(untargeted, columns=columns)
targeted_results = pd.DataFrame(targeted, columns=columns)
В приведенной таблице видно, что используя нейронную сеть ResNet с точностью 0.9231, меняя несколько пикселей изображения, мы получили очень неплохой процент успешно атакованных изображений (attack_success_rate).
helper.attack_stats(targeted_results, models, network_stats)
Out[26]:
model accuracy pixels attack_success_rate
0 resnet 0.9231 1 0.144444
1 resnet 0.9231 3 0.211111
2 resnet 0.9231 5 0.222222
helper.attack_stats(untargeted_results, models, network_stats)
Out[27]:
model accuracy pixels attack_success_rate
0 resnet 0.9231 1 0.34
1 resnet 0.9231 3 0.79
2 resnet 0.9231 5 0.79
В своих экспериментах вы вольны использовать и другие архитектуры искусственных нейронных сетей, благо их в настоящее время великое множество.
Нейросети окутали современный мир незримыми нитями. Уже давно придуманы сервисы, где используя ИИ (искусственный интеллект), пользователи получают обработанные фото, стилистически похожие на работы кисти великих художников, а сегодня алгоритмы уже умеют сами рисовать картины, создавать музыкальные шедевры, писать книги и даже сценарии к фильмам.
Такие сферы, как компьютерное зрение, распознавание лиц, беспилотные автомобили, диагностика заболеваний — принимают важные решения и не имеют права на ошибку, а вмешательство в работу алгоритмов приведет к катастрофическим последствиям.
One pixel attack – один из способов спуфинг атак. Для проверки возможности дискредитации сети был разработан алгоритм и измерено его влияние на качество прогноза решения по распознаванию образов. Результат показал, что применяющиеся сверточные архитектуры нейросетей уязвимы перед специально обученным алгоритмом One pixel attack, который подменяет один пиксель, с целью дискредитации алгоритма распознавания.
Статью подготовили Александр Андроник и Адрей Черный-Ткач в рамках стажировки в компании Data4.
DesertFlow
В такой атаке нет ничего странного. Достаточно вспомнить как работают свертки. Нет ничего удивительного, что один сильно отличающийся пиксель может своим значением "побороть" обычные пиксели, для которых натренирован конкретный фильтр. И тем самым заставить его выдать другой признак. Способ борьбы с такими атаками тоже очевиден — увеличение числа сверточных фильтров, увеличение разрешения картинки, использование регуляризации (dropout, batch norm). В общем, надо уменьшать зависимость результата сети от показаний одного фильтра. Результат которого можно легко переписать чисто из-за особенностей используемой математики. Аугментация при обучении тоже помогает. Обучая свертки всем возможным случаям.
Хотя имея возможность запускать нейросеть очень много раз, используемый метод атаки (дифференциальная эволюция, а на более сложных сетях и обычный градиентный спуск тоже хорошо работает, так как из-за увеличения размерности там все получается гораздо плавнее), всегда будет оставаться возможность таких атак. На нейросети любой сложности. По сути, таким способом создаётся оптическая иллюзия, которым подвержены в том числе люди.
И вот как с этим бороться — хороший вопрос. А как мы боремся с иллюзиями? С галлюцинациями, и в более широком смысле — с любыми когнитивными искажениями? Есть как минимум один хороший способ — использование логики и научной картины мира (т.е. непротиворечивой и построенной научными методами). Но такой способ борьбы будет доступен только сильному ИИ.
DistortNeo
Простое увеличение размерности сети, скорее всего, приведёт к противоположным результатам. Один из них — overfitting — нейросеть отлично обучится на скормленных данных, но при этом будет хуже их обобщать. Другой момент — вычислительная трудоёмкость обучения большой сети.
Слишком обширная аугментация, которая превышает возможности нейросети, тоже не приведёт ни к чему хорошему.
Да, именно так.
Как это ни странно, но классическими методами, устраняющими потенциальные малозаметные искажения: сильное размытие, агрессивное шумоподавление. При этом параметры обработки выбираются насколько сильными, насколько это возможно, пока нейросеть не начнёт терять в точности.
Даже если получится построить изображение, которое обманывает нейросеть, нужно будет ещё обратить шумоподавление и размытие, а эта задача практически нерешаема. До кучи можно делать шумоподавление и размытие с разными параметрами, давать несколько картинок на вход нейросети и сранивать её результат. Короче, тему с малыми искажениями данный подход закрывает.
Гораздо более сложно бороться с геометрическими и структурными искажениями: например, подвинуть глаз человека так, чтобы нейросеть приняла его за другого человека.
dim2r
Возможно во время тренировки надо ввести процедуру оценки этих пикселей. Можно вносить эти пиксели в функцию потерь
iskateli
Капсульные нейронные сети закрывают некоторые из проблем
habr.com/ru/post/417223
habr.com/ru/company/recognitor/blog/343726
odins1970
С галлюцинациями способ борьбы известен пока один — нейролептики. И никак иначе. и и природа их иная, не аналогичная перцептивным иллюзиям. В отношении использования логики и научной картины мира как способа — это не работает. Cм.https://www.icao.int/APAC/Meetings/2014%20CNSSG18/IP05_USA%20AI.%2011%20-%20Human%20Factors.pdf. Это нерешенная проблема.