Визуализация сверточных нейронных сетей с использованием оптимизации градиентного спуска для целевого вывода

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

Сверточные нейронные сети отлично подходят для классификации изображений. Существует множество предварительно обученных сетей, таких как VGG-16 и ResNet, обученных на наборе данных ImageNet, которые могут классифицировать изображение по одному из 1000 классов. Принцип их работы заключается в изучении шаблонов, типичных для разных классов объектов, а затем просмотре изображения, чтобы распознать эти классы и принять решение. Это похоже на человека, который просматривает картинку глазами, отыскивая знакомые предметы.

Учебная программа по искусственному интеллекту для начинающих

Если вы хотите узнать больше о сверточных нейронных сетях и о нейронных сетях в целом, мы рекомендуем вам посетить Учебный курс по искусственному интеллекту для начинающих, доступный на GitHub от Microsoft. Это набор учебных материалов, организованных в виде 24 уроков, которые могут использоваться учащимися/разработчиками для изучения ИИ, а также учителями, которые могут найти полезные материалы для включения в свои занятия. Этот пост в блоге основан на материалах из учебной программы.

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

Классификация изображений

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

model = keras.applications.VGG16(weights='imagenet',include_top=True)

Для каждого входного изображения размером 224x224x3 сеть выдаст нам 1000-мерный вектор вероятностей, каждая координата которого соответствует разным классам ImageNet. Если мы запустим сеть на шумовом изображении, мы получим следующий результат:

x = tf.Variable(tf.random.normal((1,224,224,3)))
plot_result(x)

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

Оптимизация для кота

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

Предположим, что мы начинаем с исходного изображения шума x. Сеть VGG V дает нам некоторое распределение вероятностей V(x). Чтобы сравнить его с желаемым распределением кошки, мы можем использовать функцию кросс-энтропийных потерь и вычислить потери L =cross_entropy_loss(c,V(x )).

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

Здесь η — скорость обучения, которая определяет, насколько радикальными будут наши изменения в изображении. Следующая функция сделает свое дело:

target = [284] # Siamese cat

def cross_entropy_loss(target,res):
  return tf.reduce_mean(
    keras.metrics.sparse_categorical_crossentropy(target,res))

def optimize(x,target,loss_fn, epochs=1000, eta=1.0):
    for i in range(epochs):
        with tf.GradientTape() as t:
            res = model(x)
            loss = loss_fn(target,res)
            grads = t.gradient(loss,x)
            x.assign_sub(eta*grads)

optimize(x,target,cross_entropy_loss)

Как видите, у нас получилось что-то очень похожее на случайный шум. Это связано с тем, что существует множество способов заставить сеть думать, что входное изображение является кошкой, в том числе и такие, которые не имеют визуального смысла. Хотя эти изображения содержат множество типичных для кошек узоров, ничто не мешает им быть визуально отличительными. Однако, если мы попытаемся передать эту идеальную шумную кошку в сеть VGG, она сообщит нам, что шум на самом деле является кошкой с довольно высокой вероятностью (выше 0,6):

Враждебные атаки

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

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

img = Image.open('images/dog.jpg').resize((224,224))
x = tf.Variable(np.array(img))
optimize(x,target,cross_entropy_loss)

Ниже вы можете увидеть исходное изображение (классифицируется как итальянская борзая с вероятностью 0,93) и то же изображение после оптимизации (классифицируется как сиамская кошка). с вероятностью 0,87).

Осмысление шума

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

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

TensorFlow имеет встроенную функцию tf.image.total_variation, которая вычисляет полную вариацию данного тензора. Используя его, мы можем определить функцию полных потерь следующим образом:

def total_loss(target,res):
  return 0.005*tf.image.total_variation(x,res) +\
    10*tf.reduce_mean(sparse_categorical_crossentropy(target,res))

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

Сведение к минимуму потерь на вариации делает изображение более гладким и избавляет от шума, тем самым выявляя более привлекательные узоры. Вот пример таких «идеальных» изображений, которые с большой вероятностью классифицируются и как кошка, и как зебра:

optimize(x,[284],loss_fn=total_loss) # cat
optimize(x,[340],loss_fn=total_loss) # zebra

Еда на вынос

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

Играйте с кодом

Состязательные атаки и визуализация идеального кота описаны в разделе Переносное обучение Учебной программы по искусственному интеллекту для начинающих. Фактический код, который я рассмотрел в этом сообщении в блоге, доступен здесь.

Первоначально опубликовано на https://soshnikov.com 7 июня 2022 г.