Нейросети теперь повсюду, и распознавание объектов на картинках — это одно из самых популярных применений. Но что, если вы не хотите, чтобы объекты на вашем изображении распознали? Или, например, хотели бы, чтобы нейросеть «увидела» что-то, что не увидит человек, глядя на ту же картинку? Для этого придуманы обманные методы нейросетей, которые обращают нейросети против них самих. И благодаря готовым библиотекам пользоваться ими легче легкого.
В этой статье я расскажу вам, как обмануть нейросеть на примере расспознования картинки.
Подопытная нейросеть
Для исследования нам потребуется подопытная нейронная сеть, которую можно будет препарировать. Для этой задачки вполне подойдет ImageRes50v2, одна из самых передовых сетей для классификации изображений, натренированная на датасете ImageNet.
Кодить будем на Python 3 — для работы с нейросетями это фактически стандарт. На наше везение, модуль keras
включает в себя заранее обученную модель, которую мы и используем.
Для начала надо установить все самые новые библиотеки. В реестре pypi нехватает моделей нейросетей, в отличие от репозитория на GitHub.
1 2 |
$ pip3 install -U git+https://github.com/keras-team/keras.git $ pip3 install -U git+https://github.com/keras-team/keras-applications.git |
Теперь давай возьмем любое изображение и попробуем распознать его.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import numpy as np from keras.preprocessing import image from keras.applications import resnet_v2 ## Загружаем предобученную модель model = resnet_v2.ResNet50V2(include_top=True, weights='imagenet', input_shape=(224, 224, 3)) ## Загружаем изображение img = image.load_img("kitten.jpg#26132644", target_size=(224, 224)) input_image = image.img_to_array(img, 'channels_last') ## Перевод изображения из формата [0; 255] в [-1; 1] input_image = (input_image / 255 - 0.5) * 2 ## Делаем из изображения массив с изображением (batch) input_image = np.expand_dims(input_image, axis=0) ## Прогоним через нейронную сеть predictions = model.predict(input_image) ## Переведем ответ нейронной сети (вектор) в категорию predicted_classes = resnet_v2.decode_predictions(predictions, top=1) imagenet_id, name, confidence = predicted_classes[0][0] print("Я на {:.4}% уверен, что это - {}!".format(confidence * 100, name)) |
В результате прога скажет нам, что это котэ.
1 2 3 |
$ python3 test.py Using TensorFlow backend. Я на 99.51% уверен, что это - Persian_cat! |
Изображение взято из датасета, поэтому высокая точность не удивляет. Давайте разбираться, как именно можно обмануть нейросеть.
Принцип создания поддельного изображения
При создании ложных изображений главное оружие нейронных сетей используют против них самих. Все нейросети — это большие математические функции, и, чтобы найти ложное изображение, разумно использовать их же. Так что наша задача сводится к оптимизации методом градиентного спуска.
Математика такой атаки до неприличия проста: мы выворачиваем процесс обучения нейронной сети наизнанку. Вместо фиксированных входных данных (тренировочного датасета) и обучающейся сети тут мы имеем меняющиеся, «обучающиеся» входные данные и фиксированную сеть.
Как и для обучения нейронной сети, нам нужно два параметра: функция потерь (способ подсчитать ошибку) и градиент (мы используем производную нейронной сети).
И градиент, и ошибку мы можем посчитать, используя алгоритм обратного распространения, просто приняв все веса нейронной сети правильными (и, соответственно, неизменными), а вход — ошибочным и подлежащим исправлению.
Виды атак на нейросеть
Поиски ошибочно распознаваемого примера можно поделить на два разных вида:
- Ненаправленную атаку, когда ищется любой подходящий пример (необязательно имеющий смысл),
- Специально направленную атаку, цель которой — создать минимально измененный ошибочный пример, глядя на который человек распознает объект без проблем, а программа — нет.
Самый большой минус ненаправленной атаки — это полное отсутствие у результата какой-либо смысловой нагрузки для человека. Но преимущество такой атаки — возможность легко применять в реальном мире. Например, через видеокамеры устройств IoT или систем безопасности: достаточно распечатать результат на бумажке и поднести к объективу.
Простая реализация
Куда полезнее будет разобраться, как можно подделать настоящее изображение. Для этого программе нужно сделать несколько шагов:
- Загрузить модель.
- Описать функцию потерь.
- Посчитать ошибку и градиент на входе нейросети.
- Изменить изображение соответственно градиенту (то есть улучшить его).
- Повторять пункты 3 и 4, пока результат не станет удовлетворительным.
Для этого предназначен следующий скрипт, который позволит вам за несколько минут (при наличии достойного графического процессора) создать ошибочно распознаваемое изображение необходимого класса (у нас из кота — пеликана).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import numpy as np from keras.preprocessing import image from keras.applications import resnet_v2 from keras import backend as K ## Класс № 144 — pelican target = 144 ## Скорость обучения learning_rate = 0.4 ## Загружаем предобученную модель ResNet50v2 model = resnet_v2.ResNet50V2(include_top=True, weights='imagenet', input_shape=(224, 224, 3)) ## Сохраняем вход и выход сети model_input = model.layers[0].input model_output = model.layers[-1].output ## Загружаем изображение img = image.load_img("kitten.jpg#26132644", target_size=(224, 224)) original_image = image.img_to_array(img, 'channels_last') ## Перевод изображения из формата [0; 255] в [-1; 1] original_image = (original_image/255 - 0.5) * 2 ## Делаем из изображения массив с изображением (batch) original_image = np.expand_dims(original_image, axis=0) ## Функция потерь cost_function = model_output[0, target] ## Получаем функцию, которая считает градиент на входе сети (то есть для нашего изображения) gradient = K.gradients(cost_function, model_input)[0] ## Создаем функцию, рассчитывающую градиент и ошибку get_parameters = K.function([model_input, K.learning_phase()], [cost_function, gradient]) cost = 0.0 ## Улучшаем изображение, пока оно не убедит нейронную сеть как минимум на 80% while cost < 0.8: # Считаем параметры изменения cost, gradients = get_parameters([original_image, 0]) # Изменим изображение, при этом убедившись, что оно все еще правильное original_image += gradients * learning_rate original_image = np.clip(original_image, -1.0, 1.0) print("Я на {:.5f}% уверен, что котенок - это пеликан".format(cost * 100)) ## Возвращаем картинку в удобоваримый вид img = (original_image[0] / 2 + 0.5) * 255 ## Сохраним! im = Image.fromarray(img.astype(np.uint8)) im.save("pelikan.png#26132644") |
Автоматизация процесса
Помимо такого способа обмана нейросети, ученые придумали еще очень много разных подходов, чтобы оставить искусственный интеллект в дураках. Именно поэтому была создана универсальная библиотека foolbox, которая автоматизирует все шаги, необходимые для создания поддельной картинки. Давай скачаем и используем ее!
1 |
$ pip3 install --pre foolbox |
Выберем другое изображение, в котором нейросеть будет еще уверенней. Например, другой кот, тоже персидский,

1 2 3 |
$ python3 test.py Using TensorFlow backend. Я на 100.00% уверен, что это - Persian_cat! |
Сначала нужно загрузить модель, затем выбрать необходимую цель (нужную категорию), выбрать атаку и запустить ее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import foolbox import numpy as np from keras.applications import resnet_v2 from keras import backend as K from keras.preprocessing import image from PIL import Image as PILImage ## Целевой класс № 30 — bullfrog — лягушка target = 30 ## Текущий класс № 28 — Persian_cat — кот (персидский) label = 283 ## Устанавливаем режим обучения (чтобы можно было считать градиент) K.set_learning_phase(0) ## Загружаем модель resnetmodel = resnet_v2.ResNet50V2(include_top=True, weights='imagenet', input_shape=(224, 224, 3)) ## Cоздаем модель для обмана model = foolbox.models.KerasModel(resnetmodel, bounds=(-1, 1)) ## Загружаем оригинальное изображение img = image.load_img("kitten2.jpg#26132644", target_size=(224, 224)) original_image = image.img_to_array(img, 'channels_last') ## Приводим его к виду [-1; 1] original_image = (original_image / 255 - 0.5) * 2 ## Описываем критерий (нашу цель) criteria = foolbox.criteria.TargetClassProbability(target, p=0.8) ## Создаем атаку L-BGFS-B (подробнее: https://arxiv.org/abs/1510.05328) attack = foolbox.attacks.LBFGSAttack(model, criteria) result = attack(original_image, label=label) ## Возвращаем картинку в нужный вид result = (result * 0.5 + 0.5) * 255 img = PILImage.fromarray(result.astype('uint8')) ## Сохраняем результат img.save('frog.png') |
Получаем измененное изображение.

Запустим для него проверку (предварительно меняем название файла в test.py
):
1 2 3 |
$ python3 test.py Using TensorFlow backend. Я на 87.75% уверен, что это - bullfrog! |
Удивительный результат, добиться которого можно на любом настольном компьютере за считаные секунды!
Если хочешь проверить результат на своей машине, то используй скрипт из начала статьи — он запустится даже без графического процессора. Вот изображение, созданное этим алгоритмом, в формате PNG, сохрани его и посмотри, как на самом деле работает обман нейросетей.

Атака на один пиксель
Используя схожую технологию, что и у foolbox
, ученые придумали другую атаку на нейросети, которая затрагивает лишь один пиксель. Новый алгоритм ищет тот пиксель изображения, который больше всего влияет на вывод нейронной сети, а затем использует найденную «точку давления», чтобы поменять правильный результат на неверный.
Для этой уязвимости есть библиотека one-pixel-attack-keras, которая делает эксплуатацию тривиальной. Для уверенного результата необходим не один, а несколько (три или пять) пикселей. Но качество этого метода и так превзошло ожидания ученых: около 30% изображений было превращено в подделки изменением одного пикселя, а изменением трех — уже 80%.
Несмотря на космические бюджеты, направленные на исследования нейронных сетей, их все можно обмануть. Мы ждем от машин железной логики и безукоризненной надежности, но пока это недостижимые высоты.
Для себя я нашел главный плюс всех уязвимостей — я знаю, как защитить себя в случае восстания машин.