Путешествие в визуализацию сверточной нейронной сети

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

Исходный код можно найти здесь.

Эта статья также доступна в виде интерактивной записной книжки jupyter.

Nosce te ipsum

В этой статье мы познакомимся с различными методами понимания, что происходит внутри сверточной нейронной сети, чтобы избежать той же ошибки армии США.

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

Сначала мы познакомим вас с каждым методом, кратко объясним его и сделаем несколько примеров и сравнений между различными классическими моделями компьютерного зрения alexnet, vgg16 и resnet. Затем мы попытаемся лучше понять модель, используемую в робототехнике для прогнозирования локального датчика расстояния, используя только изображения фронтальной камеры.

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

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

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

Преамбула

Давайте начнем с выбора сети. Наша первая модель будет олдскульной alexnet. Он уже доступен в пакете the torchvision.models от Pytorch.

AlexNet( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2)) (1): ReLU(inplace) (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)) (4): ReLU(inplace) (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False) (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (7): ReLU(inplace) (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (9): ReLU(inplace) (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace) (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False) ) (classifier): Sequential( (0): Dropout(p=0.5) (1): Linear(in_features=9216, out_features=4096, bias=True) (2): ReLU(inplace) (3): Dropout(p=0.5) (4): Linear(in_features=4096, out_features=4096, bias=True) (5): ReLU(inplace) (6): Linear(in_features=4096, out_features=1000, bias=True) ) )

Теперь нам нужны вводные

Теперь нам нужны входные изображения. Мы собираемся использовать три изображения: кошку, красивую Базилику Сан-Пьетро и изображение с собакой и кошкой.

Загрузили несколько пакетов. В utils есть несколько служебных функций для создания графиков.

Поскольку все наши модели были обучены на imagenet, огромном наборе данных с 1000 разными классами, нам необходимо проанализировать и нормализовать их.

В Pytorch мы должны вручную отправлять данные на устройство. В этом случае устройство, если кулак gpu, если он у вас есть, в противном случае выбирается cpu.

Имейте в виду, что jupyter не имеет сборщика мусора, поэтому нам нужно будет вручную освободить память графического процессора.

Мы также определяем служебную функцию для очистки кеша графического процессора.

Как мы уже говорили, imagenet - это огромный набор данных с 1000 классами, представленными целым числом, не очень понятным для человека. Мы можем связать каждый идентификатор класса с его меткой, загрузив imaganet2human.txt и создав словарь Python.

[(0, 'tench Tinca tinca'), (1, 'goldfish Carassius auratus')]

Визуализация весов

Первая простая визуализация - это просто нанести на график веса целевого слоя. Очевидно, что чем глубже мы погружаемся, тем меньше становится каждое изображение при увеличении количества каналов. Мы собираемся показать каждый канал в виде серого массива изображения. К сожалению, каждый модуль Pytorch может быть вложенным и вложенным, поэтому, чтобы сделать наш код как можно более общим, нам сначала нужно отследить каждый подмодуль, который проходит вход, а затем сохранить каждый слой по порядку. Сначала нам нужно trace нашу модель, чтобы получить список всех слоев, чтобы мы могли выбрать целевой слой, не следуя вложенной структуре модели. InPyTorch модели могут быть бесконечно вложенными. Другими словами, мы льстим слоям модели, это реализовано в функции module2traced.

[Conv2d(3, 64, kernel_size=(11, 11), stride=(4, 4), padding=(2, 2)), ReLU(inplace), MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False), Conv2d(64, 192, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)), ReLU(inplace), MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False), Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), ReLU(inplace), Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), ReLU(inplace), Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)), ReLU(inplace), MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False), Dropout(p=0.5), Linear(in_features=9216, out_features=4096, bias=True), ReLU(inplace), Dropout(p=0.5), Linear(in_features=4096, out_features=4096, bias=True), ReLU(inplace), Linear(in_features=4096, out_features=1000, bias=True)]

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

torch.Size([1, 55, 55])

Давайте остановимся на минуту, чтобы объяснить, что представляют собой эти изображения. Мы проследили входные данные через вычислительный граф, чтобы найти все слои наших моделей, в данном случае alexnet. Затем мы создаем экземпляр класса Weights, реализованного в visualization.core, и вызываем его, передавая текущий ввод, изображение кота и целевой слой. В качестве выходных данных мы получаем все веса текущего слоя в виде серых изображений. Затем мы наносим на график 16 из них. Мы можем заметить, что они в некотором роде имеют смысл; например, некоторые пиксели по краям изображения становятся ярче.

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

Если вам интересно, что делают операции maxpolling, проверьте это замечательное репо

torch.Size([1, 27, 27])

Давайте попробуем с другим входом, Базилика Сан-Пьетро

torch.Size([1, 27, 27])

Глядя на них, эти образы приобретают какой-то смысл; они выделяют макет базилики, но трудно понять, что на самом деле делает модель. У нас возникла идея, что что-то вычисляет правильно, но мы могли бы задать несколько вопросов, например: смотрит ли он на купол? Какие самые важные особенности базилики?

Более того, чем глубже мы погружаемся, тем труднее становится даже распознать ввод.

torch.Size([1, 13, 13])

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

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

torch.Size([1, 55, 55]) torch.Size([1, 55, 55])

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

Сходства с другими моделями

Мы видели веса alexnet, но одинаковы ли они в разных моделях? Ниже мы наносим первые 4 канала веса каждого первого слоя для alexnet, vgg и resnet.

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

Визуализация заметности

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

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

predicted class tiger cat

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

Мы видим, что alexnet выходит из-под кота. Мы можем даже лучше! Мы можем установить 0 каждый отрицательный градиент relu при обратном распространении. Эта техника называется guided.

Теперь мы ясно видим, что сеть смотрит на глаза и нос кошки. Можем попробовать сравнить разные модели

Alextnet кажется более интересным для глаз, в то время как VGG смотрит на уши, а реснет похож на alexnet. Теперь мы можем четко понять, какая часть входных данных помогает сети давать такой прогноз.

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

Базилика очень интересна, все четыре сети правильно классифицируют ее как dome, но только resnet152 больше интересует небо, чем купол. В последнем столбце у нас есть изображение с двумя классами, dog и cat. Во всех сетях выделены будки, как глаза собаки и уши кошки в vgg16. Что, если мы хотим обнаружить только область входных данных, относящихся к определенному классу? С такой техникой невозможно.

Сопоставление активации классов

Сопоставление активации классов - это методика, представленная в Изучение глубоких функций для дискриминационной локализации. Идея состоит в том, чтобы использовать последний выходной сверточный слой и нейроны в линейном слое модели, отвечающей за целевой класс, карта создается путем взятия их скалярного произведения. Однако, чтобы это работало, модель должна иметь некоторые ограничения. Прежде всего, выходные данные свертки должны сначала пройти через опрос глобального среднего, и для этого требуется, чтобы карты функций непосредственно предшествовали слоям softmax. Чтобы заставить его работать с другой архитектурой, такой как alexnet и vgg, мы должны изменить некоторые слои в модели и переобучить ее. Это серьезный недостаток, который будет устранен в следующем разделе. А пока мы можем использовать его бесплатно с resnet! Поскольку его архитектура идеальна.

Реализацию можно найти здесь. Мы можем передать визуализации параметр target_class, чтобы получить относительные веса из слоя fc.

Обратите внимание, что, изменив целевой класс, мы можем увидеть выделенную другую часть изображения. В первом изображении используется класс предсказания, во втором - другой тип cat, а в последнем - bookcase, просто чтобы увидеть, что модель будет делать с неправильным классом.

В этом есть смысл, единственное, что в последней строке у нас еще есть выделенная часть кота для bookcase

Давайте изобразим CAM на cat изображениях для разных resnet архитектур. Для resnet ›34 используется модуль Bottleneck

Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

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

Град Кэм

Grad Cam был представлен на сайте Grad-CAM: визуальные объяснения из глубоких сетей с помощью градиентной локализации. Идея на самом деле проста: мы возвращаем результат по отношению к целевому классу, сохраняя градиент и результат на заданном слое, в нашем случае последней свертке. Затем мы выполняем глобальное среднее значение сохраненного градиента, сохраняя размер канала, чтобы получить 1-мерный тензор, который будет представлять важность каждого канала в целевом сверточном слое. Затем мы умножаем каждый элемент выходных данных сверточного слоя на усредненные градиенты, чтобы создать градиентный кулачок. Вся эта процедура выполняется быстро и не зависит от архитектуры. Интересно, что авторы показывают, что это обобщение предыдущей методики.

Код здесь

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

Действительно интересно наблюдать, как alexnet смотрит на нос, vgg на уши и resnet на всю кошку. Интересно видеть, что две resnet версии смотрят на разные части кота.

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

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

Читатель может сразу заметить разницу между моделями.

Интересный регион

Раньше мы говорили об интересных локализациях регионов. Grad-cam также может использоваться для извлечения объекта класса из изображения. Легко, когда у нас есть изображение с градиентной камерой, мы можем использовать его в качестве маски, чтобы вырезать из входного изображения то, что мы хотим. Читатель может поиграть с параметром TR, чтобы увидеть различные эффекты.

et voilà! Мы также можем снова изменить класс и обрезать интересующую область для этого класса.

Разные модели

Мы видели, что все эти методы использовались с классическими моделями классификации, обученными на imagenet. А как насчет использования их в другом домене? Я перенес эту статью в Pytorch и переобучу ее. Модель учится на изображении робота с фронтальной камеры, чтобы предсказать локальные датчики расстояния, чтобы избежать препятствий. Давайте посмотрим, что, если, используя эти методы, мы сможем лучше понять, что происходит внутри модели.

Изучение восприятия на большом расстоянии с помощью самоконтроля с помощью датчиков ближнего действия и одометрии

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

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

Давайте импортируем это

Мы знаем, что для тестирования модели нужны входные данные, они берутся непосредственно из набора тестов.

Затем автор нормализует каждое изображение, это делается с помощью callind pre_processing. По какой-то причине изображения inpupts отличаются на Mac и ubuntu, они не должны быть такими, если вы запустите ноутбук на Mac, результат будет другим. Вероятно, это связано с предупреждающим сообщением.

Мы собираемся использовать SaliencyMap и GradCam, так как они лучшие

Мы ясно видим, что модель смотрит на объекты. В строке GradCam на втором рисунке план в основном сегментирован тепловой картой. Есть одна проблема, если посмотреть на третью картинку, белое поле перед камерой нечетко подсвечивается. Вероятно, это связано с тем, что белый цвет пола очень похож на цвет коробки. Давайте исследуем эту проблему.

Во второй строке SaliencyMaps выделяет все объекты, включая белое поле. Читатель может заметить, что отражение на первом рисунке слева, кажется, возбуждает сеть в этой области. Мы также должны исследовать этот случай, но из-за ограничений по времени мы оставим его в качестве упражнения для любознательного читателя.

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

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

Похожие цвета

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

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

Это идеальный сценарий для использования gradcam, чтобы увидеть, что модель смотрит на каждом изображении. На картинке ниже мы изобразили результаты gradcam.

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

Заключение

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

Более того, в качестве побочного проекта я разработал интерактивное приложение визуализации сверточной нейронной сети под названием mirro r, которое всего за несколько дней получает более сотни звезд на GitHub, что отражает интерес сообщества глубокого обучения к этой теме.

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

Спасибо за чтение