One pixel attack. Или как обмануть нейронную сеть

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


Давайте познакомимся с одной из атак на нейросети, которая приводит к ошибкам классификации при минимальных внешних воздействиях. Представьте на минуту, что нейросеть это вы. И в данный момент, попивая чашечку ароматного кофе, вы классифицируете изображения котиков с точностью более 90 процентов даже не подозревая, что “атака одного пикселя” превратила всех ваших “котеек” в грузовики.

А теперь поставим на паузу, отодвинем кофе в сторону, импортируем все необходимые нам библиотеки и разберем как работают подобные атаки 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(' ', 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.


Источник: habr.com

Комментарии: