Понимание капсульных сетей – новая привлекательная архитектура ИИ

1656679360 ponimanie kapsulnyh setej – novaya privlekatelnaya arhitektura ii

Ник Бурдакос

4z10yCe2rMRlEylHCMfo4Ia4DABK6mdcNxnm
«Наука» Алекса Рейнольдса

Сверточные нейронные сети проделали удивительную работу, но коренятся в проблемах. Пора подумать о новых решениях или усовершенствованиях — а теперь введите капсулы.

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

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

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

Op1KJBfyIbUzHSC0vkoRJvjH3ocLz0JHkGQa

Часть 0: Введение

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

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

JsT7hjCCcZeqgMPdCwsIm0kZM0mZyI9CmJpJ

Каждый из этих пикселей представляется как значение от 0 до 255 и хранится в матрице 28x28x1 [28, 28, 1]. Чем ярче пиксель, тем больше значение.

Часть 1а: Свертки

Первая часть CapsNet является традиционным сверточным слоем. Что такое сверточный слой, как он работает и каково его предназначение?

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

Как мы можем сделать это?

Давайте подумаем о крае:

-9bmuVavZCvlaCXQ4MbGOtmoVhbiNW46gVmk

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

dflKsJkPVCdZEX4IxiL8-AWo-rEx57UzGSgu

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

255 - 114 = 141
114 - 153 = -39
153 - 153 = 0
255 - 255 = 0

Что, если бы мы просмотрели каждый пиксель на изображении и заменили его значение значением разницы пикселей влево и вправо от него? Теоретически изображение должно стать черным, за исключением краев.

Мы могли бы сделать это, просмотрев каждый пиксель на картинке:

for pixel in image {
  result[pixel] = image[pixel - 1] - image[pixel + 1]
}

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

Свертка, в сущности, делает то же, что и наш цикл, но использует преимущества матричной математики.

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

Это окно представляет собой матрицу весов, которая называется «ядром».

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

Window:
┌─────────────────────────────────────┐
│ left_pixel middle_pixel right_pixel │
└─────────────────────────────────────┘

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

Window:
┌─────────────────────────────────────┐
│ left_pixel middle_pixel right_pixel │
└─────────────────────────────────────┘
(w1 * 255) + (w2 * 255) + (w3 * 114) = 141

Спойлеры ниже!

 │            │            │
 │            │            │
 │            │            │
 │            │            │
 │            │            │
\│/          \│/          \│/
 V            V            V

Мы можем сделать нечто подобное:

Window:
┌─────────────────────────────────────┐
│ left_pixel middle_pixel right_pixel │
└─────────────────────────────────────┘
(1 * 255) + (0 * 255) + (-1 * 114) = 141

С этими весами наше ядро ​​будет выглядеть так:

kernel = [1  0 -1]

Однако ядра, как правило, квадратные, поэтому мы можем дополнить их большим количеством нулей, чтобы выглядеть так:

kernel = [
  [0  0  0]
  [1  0 -1]
  [0  0  0]
]

Вот хороший gif, чтобы увидеть свертку в действии:

rqyFdp9d5FoE45uEXUQ2aBJO0o9VXTDQJgK3

Примечание: Размер вывода уменьшается на размер ядра плюс 1. Например:(7 — 3) + 1 = 5 (подробнее об этом в следующей главе)

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

AGSh8yNcivMnI6qWMg8Bv-oXzlDt0iJq1Tbn

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

kernel = [
  [0  1  0]
  [0  0  0]
  [0 -1  0]
]

Кроме того, оба эти ядра не будут работать хорошо с краями под другими углами или размытыми краями. По этой причине мы используем множество ядер (в нашей реализации CapsNet мы используем 256 ядер). А ядра, как правило, больше, чтобы иметь больше места для шевелений (наши ядра будут 9×9).

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

kernel = [
  [ 0.02 -0.01  0.01 -0.05 -0.08 -0.14 -0.16 -0.22 -0.02]
  [ 0.01  0.02  0.03  0.02  0.00 -0.06 -0.14 -0.28  0.03]
  [ 0.03  0.01  0.02  0.01  0.03  0.01 -0.11 -0.22 -0.08]
  [ 0.03 -0.01 -0.02  0.01  0.04  0.07 -0.11 -0.24 -0.05]
  [-0.01 -0.02 -0.02  0.01  0.06  0.12 -0.13 -0.31  0.04]
  [-0.05 -0.02  0.00  0.05  0.08  0.14 -0.17 -0.29  0.08]
  [-0.06  0.02  0.00  0.07  0.07  0.04 -0.18 -0.10  0.05]
  [-0.06  0.01  0.04  0.05  0.03 -0.01 -0.10 -0.07  0.00]
  [-0.04  0.00  0.04  0.05  0.02 -0.04 -0.02 -0.05  0.04]
]

Примечание: Например, я округлил значение, поскольку они достаточно велики 0.01783941

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

Вот так выглядели 256 ядер (я выкрасил их в пикселе, чтобы было легче усвоить). Чем отрицательнее числа, тем они синее. 0 – зеленый, положительный – желтый:

Y8IabW01OPBRTqQWsAWB2P3Hf8hAAGHmmWHo
256 ядер (9×9)

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

Часть 1b: ReLU

ReLU (официально известный как Rectified Linear Unit) может показаться сложным, но на самом деле он достаточно прост. ReLU – это функция активации, принимающая значение. Если он отрицателен, он равен нулю, а если положительный, то остается неизменным.

В коде:

x = max(0, x)

И как график:

JhWsW6xuVqhqPP-sZxcjYQaxQtBvAo8C9QPZ

Мы применяем эту функцию ко всем выходам наших сверток.

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

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

Вот результаты уровня ReLU Conv1:

GgrWjGfNKnG1Fq0cMHfd3r7r75BPn3XstvpK
256 выходов (20×20 пикселей)

Часть 2a: PrimaryCaps

Уровень PrimaryCaps начинается как обычный слой свертки, но на этот раз мы сворачиваем стек из 256 результатов предыдущих сверток. Поэтому вместо ядра 9×9 у нас есть ядро ​​9x9x256.

Итак, что мы ищем?

В первом слое извилин мы искали простые ребра и кривые. Теперь мы ищем несколько более сложные формы из краев, которые мы нашли раньше.

На этот раз наш «шаг» равен 2. Это значит, что вместо того, чтобы двигаться на 1 пиксель за раз, мы делаем шаги в 2. Выбирается больший шаг, чтобы мы могли быстрее снизить размер нашего ввода:

NCX7uIB7uaBTSWeBKL8rkvGNsnp0C2joHZWb

Примечание: Размер результата обычно равен 12, но мы делим его на 2 через шаг. Например: ((20 — 9) + 1) / 2 = 6

Мы обратимся к результатам еще 256 раз. Итак, мы получим стек из 256 выходов 6×6.

Но на этот раз мы не довольствуемся только старыми скверными цифрами.

Мы разрежем стопку на 32 бревна по 8 карт в каждой.

Мы можем назвать это бревно «слоем капсулы».

Каждый слой капсулы содержит 36 «капсул».

Если вы все успеваете (и являетесь умником математики), это означает, что каждая капсула имеет массив из 8 значений. Это то, что мы можем назвать «вектором».

Вот о чем я говорю:

WLSoM2zmTDLNhLvNiKYqHmP0y-PdRBOlEM0K

Эти «капсулы» – наш новый пиксель.

Имея один пиксель, мы могли сохранять только уверенность в том, нашли ли мы край в этой точке или нет. Чем выше число, тем выше уверенность.

Посредством капсулы мы можем хранить 8 значений на место! Это дает нам возможность хранить больше информации, чем просто то, нашли ли мы форму в этом месте или нет. Но какую информацию мы хотели бы хранить?

Глядя на форму ниже, что вы можете мне сказать о ней? Если бы вам пришлось сказать кому-то другому, как это перерисовать, и они не могли бы на это посмотреть, что бы вы сказали?

q4J8nHBxTm35TE99THoituJTGJ6ssKnyWEpO

Это изображение очень простое, поэтому нам нужно лишь несколько деталей, чтобы описать форму:

  • Тип формы
  • Позиция
  • Вращение
  • цвет
  • Размер

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

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

Это одна из причин, почему традиционные нейронные сети плохо справляются с невидимыми вращениями.

Q3flECHZIW6K8e7CDMQZ0CFa2rhXjB2rK2IZ

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

Вот упрощенное сравнение 2 слоев капсулы (один для прямоугольников, а другой для треугольников) с 2 традиционными выходами пикселей:

zzt6LWsLz4-aSteZyXvJcM8bydUtgOrtaLFX

Как и традиционный 2D или 3D вектор, этот вектор имеет угол и длину. Длина описывает вероятность, а угол описывает параметры экземпляра. В вышеприведенном примере угол фактически совпадает с углом фигуры, но обычно это не так.

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

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

Звучит отлично, но как нам побуждать сеть захотеть узнать эти вещи?

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

Я расскажу об этом более подробно в следующих разделах, но вот простой пример:

0jLnyPRFOgdEwEn7DqZwWJfaC05p09CUQmUd

Часть 2b: Сквошинг

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

XlLc1MM5cqr99LQrKT5yUJ99CTdU09xGuZ7C

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

NiLltl6OVOmQJFsirTZUmIeNEEN9w5Tpdmfe
Имейте в виду, что каждый пиксель является вектором длины 8

Часть 3: Маршрутизация по договоренности

Следующий шаг – решить, какую информацию отправлять на следующий уровень. В традиционных сетях мы, вероятно, сделали бы что-нибудь вроде «максимального объединения». Максимальное объединение — это способ уменьшения размера передачи на следующий уровень только самого высокого активированного пикселя в регионе.

EunY9PPwbi-iYghdzGKkqKvbZMuM3w4gnFb2

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

UDpR9qNsGInbGZU8wHqC1oY3rDQ9l9X0IC3K

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

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

В традиционных сетях неуместные функции не мешают:

mby8Ob1WxB0mhI20jgQOiZGjHkS-dtbp6Kvg

В капсульных сетях функции не согласны друг с другом:

vlks3XiJhSJR94-5D7Yqklknx4dsbPaqAsZA

Надеюсь, это работает интуитивно. Однако как работает математика?

У нас есть 10 разных классов цифр, которые мы прогнозируем:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Примечание: В примере с лодкой и домом мы предусмотрели 2 объекта, а сейчас мы предусмотрели 10.

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

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

Помните, что у нас есть 32 слоя капсул, и каждый слой капсулы имеет 36 капсул. Это означает, что у нас есть 1152 капсулы.

cap_1 * weight_for_0 = prediction
cap_1 * weight_for_1 = prediction
cap_1 * weight_for_2 = prediction
cap_1 * ...
cap_1 * weight_for_9 = prediction

cap_2 * weight_for_0 = prediction
cap_2 * weight_for_1 = prediction
cap_2 * weight_for_2 = prediction
cap_2 * ...
cap_2 * weight_for_9 = prediction

...

cap_1152 * weight_for_0 = prediction
cap_1152 * weight_for_1 = prediction
cap_1152 * weight_for_2 = prediction
cap_1152 * ...
cap_1152 * weight_for_9 = prediction

Вы получите список из 11 520 предсказаний.

Каждый вес на самом деле является матрицей 16×8, поэтому каждый прогноз представляет собой умножение матрицы между вектором капсулы и этой матрицей весов:

t8WRLiBJtfWjHLUSvhtx2bywlEO7SM0oNKnb

Как вы видите, наш прогноз является вектором 16 градусов.

Откуда 16? Это случайный выбор, как и 8 для наших оригинальных капсул.

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

Следующим шагом будет выяснить, какие из этих 11 520 предсказаний больше согласуются друг с другом.

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

8uTYSisUIxhfjxwWpRQND1XfNZUXyfmlU-UI

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

HMOGH8gQek305eZpEnFag2ygCnKwD7TvjcFN

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

eAlr6nS0NEb7E4OxH1eWZBSTkJBxD9owKHdW

Затем мы перечисляем среднее, на этот раз учитывая важность точки:

ewEmGgjUWcL50lc4X-oWwYvxcvwYKwKCtTm0

В итоге мы проходим этот цикл 3 раза:

XA6mhI9UzGcTbM90pI9kiiNKBVLhd4YBX9Yo

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

Часть 4: DigitCaps

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

Вот как выглядят длины векторов с вводом 4:

-t0Ke6HoBg-EBjS3XIQfROS827ilzyLzWEbK

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

Часть 5: Реконструкция

Реконструкционная часть применения не очень интересна. Это всего несколько полностью связанных слоев. Но сама реконструкция очень крутая и с ней весело играть.

Если мы реконструируем наш 4 вход из его вектора, мы получим следующее:

eQWDk11SEC85p0ygADaNCofea7Y2Ogl-ugo9

Если мы манипулируем ползунками (вектором), мы можем увидеть, как каждое измерение влияет на 4:

9lXaCa9nKAK93jrOHN5exN1sKiXPuYwAmEV7

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

git clone 
cd CapsNet-Visualization
pip install -r requirements.txt

Запустите инструмент:

python run_visualization.py

Затем в браузере перейдите на: http://localhost:5000

Заключительные мнения

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

Мне очень интересно посмотреть, как это повлияет на манипулирование векторами реконструкции более сложного изображения. По этой причине мой следующий проект – заставить капсульные сети работать с наборами данных CIFAR и smallNORB.

Спасибо за чтение! Если у вас возникли вопросы, пишите на bourdakos1@gmail.com, свяжитесь со мной в LinkedIn или подписывайтесь на меня в Medium и Twitter.

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

Добавить комментарий

Ваш адрес email не будет опубликован.