10 янв 2018 г. | Даниэль Гэн и Риши Веерапанени

Убийство нейронной сетью. Звучит безумно? Что ж, это может когда-нибудь случиться, и не так, как вы думаете. Конечно, нейронные сети можно обучить пилотированию дронов или управлению другим оружием массового уничтожения, но даже безобидную (и в настоящее время доступную) сеть, обученную вождению автомобиля, можно использовать против ее владельца. Это связано с тем, что нейронные сети чрезвычайно восприимчивы к так называемым примерам состязательности.

Состязательные примеры - это входы в нейронную сеть, которые приводят к неправильному выходу из сети. Наверное, лучше всего показать пример. Вы можете начать с изображения панды слева, которое некоторые сети считают с вероятностью 57,7% "панда". Категория панда также является категорией с наивысшим уровнем достоверности из всех категорий, поэтому сеть приходит к выводу, что объект на изображении - это панда. Но затем, добавив очень небольшое количество тщательно сконструированного шума, вы можете получить изображение, которое выглядит точно так же для человека, но которое сеть считает с вероятностью 99,3% «гиббоном». Довольно безумная штука!

Так как же сработает убийство на примере состязательной стороны? Представьте себе замену знака «Стоп» на его противоборствующий пример, то есть знак, который человек мгновенно распознает, но нейронная сеть даже не зарегистрирует. А теперь представьте, что вы устанавливаете этот злобный знак остановки на оживленном перекрестке. Когда беспилотные автомобили приближаются к перекрестку, бортовые нейронные сети не смогут увидеть знак остановки и продолжат движение прямо на встречное движение, в результате чего пассажиры окажутся на грани верной смерти (теоретически).

Это может быть всего лишь один запутанный и (более чем) слегка сенсационный пример того, как люди могут использовать состязательные примеры для нанесения вреда, но их гораздо больше. Например, функция разблокировки iPhone X «Face ID» использует нейронные сети для распознавания лиц и, следовательно, подвержена атакам со стороны противника. Люди могут создавать состязательные изображения, чтобы обойти функции безопасности Face ID. Другие биометрические системы безопасности также будут подвержены риску, и незаконный или ненадлежащий контент может потенциально обойти фильтры контента на основе нейронных сетей с использованием состязательных примеров. Существование этих враждебных примеров означает, что системы, включающие модели глубокого обучения, на самом деле имеют очень высокий риск для безопасности.

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

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

Состязательные примеры на MNIST

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

Мы попытаемся обмануть обычную нейронную сеть прямой связи, которая была обучена на наборе данных MNIST. MNIST - это набор данных, состоящий из изображений рукописных цифр размером 28 × 28 пикселей. Они выглядят примерно так:

Прежде чем что-либо делать, мы должны сначала импортировать библиотеки, которые нам понадобятся.

import network.network as network
import network.mnist_loader as mnist_loader
import pickle
import matplotlib.pyplot as plt
import numpy as np

Имеется 50000 обучающих изображений и 10000 тестовых изображений. Сначала загружаем предварительно обученную нейронную сеть (которая беззастенчиво украдена из этого удивительного введения в нейронные сети):

with open('trained_network.pkl', 'rb') as f:  
    net = pickle.load(f)  
    
training_data, validation_data, test_data = mnist_loader.load_data_wrapper()

Для тех из вас, кто не знаком с pickle, это способ для Python сериализовать данные (то есть записывать на диск), по сути, сохраняя классы и объекты. Использование pickle.load() просто открывает сохраненную версию сети.

Итак, немного об этой обученной нейронной сети. Он имеет 784 входных нейрона (по одному для каждого из 28 × 28 = 784 пикселей), один слой из 30 скрытых нейронов и 10 выходных нейронов (по одному на каждую цифру). Все его активации сигмоидальные; его выход - это горячий вектор, указывающий на предсказание сети, и он был обучен путем минимизации среднеквадратичных потерь ошибок.

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

def predict(n):
    # Get the data from the test set
    x = test_data[n][0]
    # Get output of network and prediction
    activations = net.feedforward(x)
    prediction = np.argmax(activations)
    # Print the prediction of the network
    print('Network output: ')
    print(activations)
    print('Network prediction: ')
    print(prediction)
    print('Actual image: ')
    
    # Draw the image
    plt.imshow(x.reshape((28,28)), cmap='Greys')

Этот метод выбирает n -й образец из набора тестов, отображает его, а затем запускает через нейронную сеть с помощью метода net.feedforward(x). Вот несколько изображений:

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

Нецеленаправленная атака

Идея состоит в том, чтобы сгенерировать какое-то изображение, предназначенное для того, чтобы нейронная сеть имела определенный вывод. Например, предположим, что наша цель / результат:

То есть мы хотим создать такое изображение, чтобы выходной сигнал нейронной сети был приведенным выше вектором. Другими словами, найдите такое изображение, которое нейронная сеть считает, что это 5 баллов (помните, что у нас нулевая индексация). Оказывается, мы можем сформулировать это как задачу оптимизации примерно так же, как обучаем сеть. Назовем изображение, которое мы хотим сделать, x ⃗ (784-мерный вектор, потому что мы выравниваем изображение размером 28 × 28 пикселей, чтобы упростить вычисления). Мы определим функцию стоимости как:

Где y_goal - это метка нашей цели сверху. Выходной сигнал нейронной сети для нашего изображения равен y (x ⃗). Вы можете видеть, что если выходные данные сети для нашего сгенерированного изображения x ⃗ очень близки к нашей целевой метке y_goal, то соответствующие затраты будут низкими. Если выход сети очень далек от нашей цели, тогда стоимость высока. Следовательно, поиск вектора x ⃗, который минимизирует затраты CC, приводит к изображению, которое нейронная сеть предсказывает как метку нашей цели. Теперь наша задача - найти этот вектор x ⃗.

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

Для этого мы воспользуемся тем же подходом, что и при обучении нейронной сети. То есть воспользуемся градиентным спуском! Мы можем найти производные функции стоимости по входу, ∇_xC, используя обратное распространение, а затем использовать обновление градиентного спуска, чтобы найти лучший x ⃗, который минимизирует цена.

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

Хорошо, давайте посмотрим на код, который на самом деле генерирует состязательные примеры:

def adversarial(net, n, steps, eta):
    """
    net : network object
        neural network instance to use
    n : integer
        our goal label (just an int, the function transforms it into a one-hot vector)
    steps : integer
        number of steps for gradient descent
    eta : integer
        step size for gradient descent
    """
    # Set the goal output
    goal = np.zeros((10, 1))
    goal[n] = 1
    # Create a random image to initialize gradient descent with
    x = np.random.normal(.5, .3, (784, 1))
    # Gradient descent on the input
    for i in range(steps):
        # Calculate the derivative
        d = input_derivative(net,x,goal)
        
        # The GD update on x
        x -= eta * d
    return x

Сначала мы создаем нашу y_goal, которая в коде называется goal. Затем мы инициализируем наш x ⃗ как случайный 784-мерный вектор. С этим вектором мы теперь можем начать градиентный спуск, который на самом деле представляет собой всего две строки кода. В первой строке d = input_derivative(net,x,goal) вычисляется ∇_xC с использованием обратного распространения ошибки (полный код для этого есть в записной книжке для любопытных, но мы пропустим его здесь, так как на самом деле это просто тонна математики. Если вы хотите очень хорошее описание того, что такое backprop (что действительно делает input_derivative), можно найти на этом веб-сайте (кстати, там же, где мы взяли реализацию нейронной сети)). Вторая и последняя строка цикла градиентного спуска, x -= eta * d - это обновление. Двигаемся в направлении, противоположном градиенту, с размером шага eta.

Вот примеры противостояния без таргетинга для каждого класса вместе с прогнозами нейронной сети:

Невероятно, но нейронная сеть считает, что некоторые изображения на самом деле являются числами с очень высокой степенью достоверности. Цифры «3» и «5» являются хорошими примерами этого. Для большинства других чисел нейронная сеть имеет очень низкую активацию для каждого числа, что указывает на то, что она очень запуталась. Выглядит неплохо!

Возможно, в этот момент вас что-то беспокоит. Если мы хотим создать состязательный пример, соответствующий пятерке, мы хотим найти x ⃗, который при подаче в нейронную сеть дает результат, максимально приближенный к одному-горячему вектору, представляющему «5». ». Однако почему градиентный спуск не находит просто изображение «5»? В конце концов, нейронная сеть почти наверняка поверит, что изображение «5» на самом деле было «5» (потому что это на самом деле «5»). Возможная теория относительно того, почему это происходит, следующая:

Пространство всевозможных изображений 28 × 28 чрезвычайно велико. Существует 256 ^ (28 × 28) ≈10 ^ 1888 возможных различных черно-белых изображений размером 28 × 28 пикселей. Для сравнения: обычная оценка количества атомов в наблюдаемой Вселенной составляет 10 ^ 80. Если бы каждый атом во Вселенной содержал другую вселенную, то у нас было бы 10 ^ 160 атомов. Если бы каждый атом содержал другую вселенную, атомы которой содержали другую вселенную, и так далее примерно 23 раза, то мы почти достигли бы 10 ^ 1888 атомов. По сути, количество возможных изображений ошеломляюще огромно.

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

Целенаправленная атака

Эти состязательные примеры круты и все такое, но для людей они просто кажутся шумом. Разве не было бы круто, если бы у нас были примеры противоборства, которые действительно выглядели бы как-то? Может быть, изображение «2», которое нейронная сеть сочла за 5? Оказывается, это возможно! И более того, с очень небольшой модификацией нашего исходного кода. Что мы можем сделать, так это добавить член к функции стоимости, которую мы минимизируем. Наша новая функция затрат будет следующей:

Где xtargetxtarget - это то, как мы хотим, чтобы наш состязательный пример выглядел (поэтому x_target - это 784-мерный вектор, то же измерение, что и наши входные данные). Итак, сейчас мы одновременно сокращаем до минимума два термина. Левый термин мы уже видели. Сведение к минимуму сделает вывод нейронной сети ygoalygoal при задании x ⃗. Сведение к минимуму второго члена будет пытаться заставить наше состязательное изображение x быть как можно ближе к x_target, насколько это возможно (потому что норма меньше, когда два вектора ближе), чего мы и хотим! Дополнительный λ спереди - это гиперпараметр, определяющий, какой из терминов более важен. Как и в случае с большинством гиперпараметров, мы обнаруживаем после множества проб и ошибок, что 0,05 - хорошее число для установки λ.

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

Если вы ничего не знаете о регуляризации, нажмите здесь, чтобы узнать больше:

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

def sneaky_adversarial(net, n, x_target, steps, eta, lam=.05):
    """
    net : network object
        neural network instance to use
    n : integer
        our goal label (just an int, the function transforms it into a one-hot vector)
    x_target : numpy vector
        our goal image for the adversarial example
    steps : integer
        number of steps for gradient descent
    eta : integer
        step size for gradient descent
    lam : float
        lambda, our regularization parameter. Default is .05
    """
    
    # Set the goal output
    goal = np.zeros((10, 1))
    goal[n] = 1
    # Create a random image to initialize gradient descent with
    x = np.random.normal(.5, .3, (784, 1))
    # Gradient descent on the input
    for i in range(steps):
        # Calculate the derivative
        d = input_derivative(net,x,goal)
        
        # The GD update on x, with an added penalty 
        # to the cost function
        # ONLY CHANGE IS RIGHT HERE!!!
        x -= eta * (d + lam * (x - x_target))
    return x

Единственное, что мы изменили, - это обновление градиентного спуска: x -= eta * (d + lam * (x - x_target)). Дополнительный член учитывает новый член в нашей функции затрат. Давайте посмотрим на результат этого метода нового поколения:

Обратите внимание, что, как и в случае с нецелевой атакой, существует два поведения. Либо нейронная сеть полностью обманута, и активация для нужного нам числа очень высока (например, изображение «целевых 5»), либо сеть просто запуталась, и все активации низкие (например, изображение «целевых 7») . Что интересно, сейчас в первую категорию попадает гораздо больше изображений, что полностью обманывает нейронную сеть, а не просто сбивает ее с толку. Кажется, что создание состязательных примеров, которые были упорядочены, чтобы быть более «похожими на числа», имеет тенденцию улучшать сходимость во время градиентного спуска.

Защита от враждебных атак

Потрясающие! Мы только что создали изображения, которые обманывают нейронные сети. Следующий вопрос, который мы могли бы задать, - можем ли мы защитить себя от подобных атак. Если вы внимательно присмотритесь к исходным изображениям и примерам противоборства, вы увидите, что на примерах противоборства есть какой-то серый фон с оттенками.

Одна наивная вещь, которую мы могли бы попробовать, - использовать двоичную пороговую обработку, чтобы полностью затушевать фон:

def binary_thresholding(n, m):
    """
    n: int 0-9, the target number to match
    m: index of example image to use (from the test set)
    """
    
    # Generate adversarial example
    x = sneaky_generate(n, m)
# Binarize image
    x = (x > .5).astype(float)
    
    print("With binary thresholding: ")
    
    plt.imshow(x.reshape(28,28), cmap="Greys")
    plt.show()
# Get binarized output and prediction
    binary_activations = net.feedforward(x)
    binary_prediction = np.argmax(net.feedforward(x))
    
    print("Prediction with binary thresholding: ")
    print(binary_prediction)
    
    print("Network output: ")
    print(binary_activations)

Вот результат:

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

Еще одна более общая вещь, которую мы могли бы попытаться сделать, - это обучить новую нейронную сеть на правильно помеченных примерах противоборства, а также на исходном наборе обучающих тестов. Код для этого находится в записной книжке ipython (имейте в виду, что запуск занимает около 15 минут). Это дает точность около 94% на тестовом наборе всех состязательных изображений, что довольно хорошо. Однако у этого метода есть свои ограничения. В первую очередь, в реальной жизни вы вряд ли будете знать, как ваш злоумышленник создает примеры противоборства.

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

Атаки черного ящика

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

Это имеет огромные последствия, поскольку означает, что можно создавать состязательные примеры для модели полностью черного ящика, где у нас нет предварительных знаний о внутренней механике. Фактически, команде из Беркли удалось запустить успешную атаку на коммерческую систему классификации ИИ, используя этот метод.

Заключение

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

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

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