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

глубокое обучение

«От направления к науке о данных», Поль-Луи Преве,Сердце Машины собрано,Участие: Панда.

Может быть, код поможет нам лучше понять различные модули свертки, чем непонятные и сложные математические или текстовые описания. Ученый-компьютерщик Поль-Луи Преве использовал Keras для введения и кодирования модуля узкого места, начального модуля, остаточного модуля и т. д. и оставил в конце задачу упражнения для реализации кода AmoebaNet Normal Cell. Ты можешь ответить? Не стесняйтесь оставлять свой ответ в разделе комментариев!

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

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

def conv(x, f, k=3, s=1, p='same', d=1, a='relu'):
 return Conv2D(filters=f, kernel_size=k, strides=s, 
 padding=p, dilation_rate=d, activation=a)(x)
def dense(x, f, a='relu'):
 return Dense(f, activation=a)(x)
def maxpool(x, k=2, s=2, p='same'):
 return MaxPooling2D(pool_size=k, strides=s, padding=p)(x)
def avgpool(x, k=2, s=2, p='same'):
 return AveragePooling2D(pool_size=k, strides=s, padding=p)(x)
def gavgpool(x):
 return GlobalAveragePooling2D()(x)
def sepconv(x, f, k=3, s=1, p='same', d=1, a='relu'):
 return SeparableConv2D(filters=f, kernel_size=k, strides=s, 
 padding=p, dilation_rate=d, activation=a)(x)

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

модуль узкого места

Количество параметров сверточного слоя зависит от размера ядра свертки (kernel), количества входных фильтров и количества выходных фильтров. Чем шире ваша сеть, тем дороже будет свертка 3×3.

def bottleneck(x, f=32, r=4):
 x = conv(x, f//r, k=1)
 x = conv(x, f//r, k=3)
 return conv(x, f, k=1)

Идея модуля узкого места заключается в использовании более дешевой свертки 1 × 1 с определенной скоростью r для уменьшения количества каналов, чтобы последующая свертка 3 × 3 имела меньше параметров. Наконец, мы используем еще одну свертку 1 × 1, чтобы расширить сеть.

Начальный модуль

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

def naive_inception_module(x, f=32):
 a = conv(x, f, k=1)
 b = conv(x, f, k=3)
 c = conv(x, f, k=5)
 d = maxpool(x, k=3, s=1)
 return concatenate([a, b, c, d])

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

Начальный модуль

def inception_module(x, f=32, r=4):
 a = conv(x, f, k=1)
 b = conv(x, f//3, k=1)
 b = conv(b, f, k=3)
 c = conv(x, f//r, k=1)
 c = conv(c, f, k=5)
 d = maxpool(x, k=3, s=1)
 d = conv(d, f, k=1)
 return concatenate([a, b, c, d])

остаточный модуль

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

def residual_block(x, f=32, r=4):
 m = conv(x, f//r, k=1)
 m = conv(m, f//r, k=3)
 m = conv(m, f, k=1)
 return add([x, m])

Идея остаточного модуля состоит в том, чтобы добавить начальную активацию на выходе модуля свертки. Таким образом, сеть может решить, сколько новых сверток использовать для вывода в процессе обучения. Обратите внимание, что модуль Inception объединяет выходные данные, а модуль Residual добавляет их.

Модуль ResNeXt

Как видно из названия, ResNeXt тесно связан с ResNet. Мы вводим термин мощности для модуля свертки как еще одно измерение, подобное ширине (количество каналов) и глубине (количество слоев).

Мощность относится к количеству параллельных путей, присутствующих в модуле. Это похоже на модуль Inception (с 4 параллельными операциями). Однако вместо параллельного использования различных типов операций, когда основание равно 4, 4 параллельных операции одинаковы.

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

def resnext_block(x, f=32, r=2, c=4):
 l = []
 for i in range(c):
 m = conv(x, f//(c*r), k=1)
 m = conv(m, f//(c*r), k=3)
 m = conv(m, f, k=1)
 l.append(m)
 m = add(l)
 return add([x, m])

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

Предположим, есть модуль узкого места, который сначала уменьшает 256 входных каналов до 64, используя коэффициент сжатия 4, а затем возвращает их к 256 каналам в качестве вывода. Если бы мы хотели ввести базу 32 и степень сжатия 2, то у нас было бы 32 параллельных сверточных слоя 1×1, где каждый сверточный слой имеет 4 выходных канала (256/(32*2)). После этого мы используем 32 сверточных слоя 3×3 с 4 выходными каналами, за которыми следуют 32 слоя 1×1 с 256 выходными каналами. Последний шаг включает в себя объединение этих 32 параллельных путей, что обеспечивает вывод перед добавлением начального ввода для построения остаточного соединения.

Слева: модуль ResNet; справа: модуль RexNeXt с примерно такой же сложностью параметров

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

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

Плотный модуль

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


def dense_block(x, f=32, d=5):
 l = x
 for i in range(d):
 x = conv(l, f)
 l = concatenate([l, x])
 return l

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

Модуль сжатия и возбуждения

SENet ненадолго достигла наилучшей производительности на ImageNet. Он основан на ResNeXt и фокусируется на моделировании аспектов каналов в сети. В обычном сверточном слое операции суммирования при вычислении скалярного произведения для каждого канала имеют равные веса.


Модуль сжатия и возбуждения

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

def se_block(x, f, rate=16):
 m = gavgpool(x)
 m = dense(m, f // rate)
 m = dense(m, f, a='sigmoid')
 return multiply([x, m])

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

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

NASNet Normal Cell

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

Сторонники NASNet вручную определили пространство поиска, содержащее различные типы слоев свертки и объединения, с различными возможными настройками. Они также определяют, как эти слои могут располагаться параллельно или последовательно, а также как их можно добавлять или соединять. После определения они построили алгоритм обучения с подкреплением (RL), основанный на рекуррентной нейронной сети, в награду за разработку конкретного дизайна, который хорошо показал себя в наборе данных CIFAR-10.

Получившаяся в результате архитектура не только хорошо работает на CIFAR-10, но и соответствует последнему слову техники в ImageNet. NASNet состоит из нормальной ячейки и ячейки сокращения, которые повторяются друг за другом.

def normal_cell(x1, x2, f=32):
 a1 = sepconv(x1, f, k=3)
 a2 = sepconv(x1, f, k=5)
 a = add([a1, a2])
 b1 = avgpool(x1, k=3, s=1)
 b2 = avgpool(x1, k=3, s=1)
 b = add([b1, b2])
 c2 = avgpool(x2, k=3, s=1)
 c = add([x1, c2])
 d1 = sepconv(x2, f, k=5)
 d2 = sepconv(x1, f, k=3)
 d = add([d1, d2])
 e2 = sepconv(x2, f, k=3)
 e = add([x2, e2])
 return concatenate([a, b, c, d, e])

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

Перевернутый остаточный модуль

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

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

def inv_residual_block(x, f=32, r=4):
 m = conv(x, f*r, k=1)
 m = sepconv(m, f, a='linear')
 return add([m, x])

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

AmoebaNet Normal Cell

Нормальная ячейка от AmoebaNet

AmoebaNet в настоящее время является лучшим исполнителем в ImageNet и, вероятно, даже лучшим в общих задачах распознавания изображений. Подобно NASNet, он разработан с использованием алгоритма, использующего то же пространство поиска, что и описанное выше. Единственная разница в том, что они использовали не алгоритмы обучения с подкреплением, а общий алгоритм, который часто называют «эволюцией». Детали того, как работает алгоритм, выходят за рамки этой статьи. В конце концов, исследователи нашли лучшее решение с меньшими вычислительными затратами с помощью эволюционных алгоритмов, чем NASNet. В ImageNet он входит в пятерку лучших с точностью 97,87% — новый уровень, достигнутый одной архитектурой.

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

Суммировать

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

Оригинальная ссылка:к data science.com/history-of-…