Исходный кот. Как заставить нейронную сеть ошибиться

Нейросети теперь повсюду, и распознавание объектов на картинках — это одно из самых популярных применений. Но что, если ты не хочешь, чтобы объекты на твоей картинке распознали? Или, например, хотел бы, чтобы нейросеть «увидела» что-то, что не увидит человек, глядя на ту же картинку? Для этого придуманы обманные методы, которые обращают нейросети против них самих. И благодаря готовым библиотекам пользоваться ими легче легкого.

Подопытная нейросеть

Для исследования нам нужна подопытная нейронная сеть, которую можно будет препарировать. Для этого подойдет ImageRes50v2, одна из самых передовых сетей для классификации изображений, натренированная на датасете ImageNet.

Код будем писать на Python 3 — для работы с нейросетями это фактически стандарт. На наше везение, модуль keras включает в себя заранее обученную модель, которую мы и используем.

Для начала установим все самые свежие библиотеки. В реестре pypi недостаточно моделей нейросетей, в отличие от репозитория на GitHub.

$ pip3 install -U git+https://github.com/keras-team/keras.git 
$ pip3 install -U git+https://github.com/keras-team/keras-applications.git

Теперь давай возьмем любое изображение и попробуем распознать его.

Исходный кот
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", 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))

В результате программа скажет нам, что это кот.

$ python3 test.py 
Using TensorFlow backend.
Я на 99.51% уверен, что это - Persian_cat!

Картинка взята из датасета, поэтому высокая точность не удивляет. Давай разбираться, как именно можно обмануть столь хорошо подготовленную нейросеть.

Принцип создания поддельного изображения

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

Математика такой атаки до неприличия проста: мы выворачиваем процесс обучения нейронной сети наизнанку. Вместо фиксированных входных данных (тренировочного датасета) и обучающейся сети тут мы имеем меняющиеся, «‎обучающиеся»‎ входные данные и фиксированную сеть.

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

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

Виды атаки

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

Самый большой минус ненаправленной атаки — это полное отсутствие у результата какой-либо смысловой нагрузки для человека. Но преимущество такой атаки — возможность легко применять в реальном мире. Например, через видеокамеры устройств IoT или систем безопасности: достаточно распечатать результат на бумажке и поднести к объективу.

Простая реализация

Куда полезнее будет разобраться, как можно подделать настоящее изображение. Для этого программе нужно сделать несколько шагов:

  1. Загрузить модель.
  2. Описать функцию потерь.
  3. Посчитать ошибку и градиент на входе нейросети.
  4. Изменить изображение соответственно градиенту (то есть улучшить его).
  5. Повторять пункты 3 и 4, пока результат не станет удовлетворительным.

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

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", 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")

Автоматизация процесса

Помимо такого способа обмана нейросети, ученые придумали еще очень много разных подходов, чтобы оставить искусственный интеллект в дураках. Именно поэтому была создана универсальная библиотека foolbox, которая автоматизирует все шаги, необходимые для создания поддельной картинки. Давай скачаем и используем ее!

$ pip3 install --pre foolbox

Выберем другое изображение, в котором нейросеть будет еще уверенней. Например, другой кот, тоже персидский.

Кот, которому не терпится стать лягушкой
$ python3 test.py 
Using TensorFlow backend.
Я на 100.00% уверен, что это - Persian_cat!

Сначала нужно загрузить модель, затем выбрать необходимую цель (нужную категорию), выбрать атаку и запустить ее.

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", 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):

$ python3 test.py 
Using TensorFlow backend.
Я на 87.75% уверен, что это - bullfrog!

Удивительный результат, добиться которого можно на любом настольном компьютере за считаные секунды!

Если хочешь проверить результат на своей машине, то используй скрипт из начала статьи — он запустится даже без графического процессора. Вот изображение, созданное этим алгоритмом, в формате PNG, сохрани его и посмотри, как на самом деле работает обман нейросетей.

Кот (лягушка)

Атака на один пиксель

Используя схожую технологию, что и у foolbox, ученые придумали другую атаку на нейросети, которая затрагивает лишь один пиксель. Новый алгоритм ищет тот пиксель изображения, который больше всего влияет на вывод нейронной сети, а затем использует найденную «‎точку давления»‎, чтобы поменять правильный результат на неверный.

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

Несмотря на космические бюджеты, направленные на исследования нейронных сетей, их все можно обмануть. Мы ждем от машин железной логики и безукоризненной надежности, но пока это недостижимые высоты.

Для себя я нашел главный плюс всех уязвимостей — я знаю, как защитить себя в случае восстания машин.

Комментарии (4)

  • Отличная статья! Спасибо!
    Надавно читал об атаках, когда можно на майку/футболку/кепку нанести такое изображение которые заставит сойти сеть с ума и вообще не обнаружить на нем человека или лицо
    Можете в следующей статье раскрыть как генерить такие изображения и как это работает?

    • Спасибо за отзыв!
      Безусловно, такие атаки существуют, однако их использованию мешает то, что, например, в Китае система распознавания лиц использует аж три разные (в том числе и по архитектуре) нейронные сети, поэтому, даже если получится обмануть одну, то другие всё равно будут работать. Также мешает то, что все описанные в статье атаки - атаки White Box, то есть мы имеем обученную модель в виде файла, которую взламываем. Однако в случае с видеокамерами, которые мы не контролируем, взламывать в разы сложнее: нет файла, ничего не работает.
      И всё же, в случае, если данная тема будет интересна, то статья такая тоже появится!

  • Загрузил обоих персидских котов (нормального и зашумлённого) на Яндекс - в первом случае фото определилось тегами "персидская кошка злая", "персидский кот", "фото ржачного кота".
    Во втором случае теги были: "кошки", "морда кота с языком", "кот".

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

Похожие материалы