Ускорение сверточных нейронных сетей

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

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

Можем ли мы разработать быстрые и эффективные свертки?

В какой-то степени - да!

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

  • Факторизация / Разложение ядер свертки
  • Слои узких мест
  • Более широкие свертки
  • Глубоко разделимые свертки

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

Простая факторизация

Начнем со следующего примера в NumPy

>>> from numpy.random import random
>>> random((3, 3)).shape == (random((3, 1)) * random((1, 3))).shape
>>> True

Вы можете спросить, зачем я показываю вам этот глупый отрывок? Что ж, ответ таков: он показывает, что вы можете написать матрицу NxN, подумайте о сверточном ядре, как о продукте 2 меньших матриц / ядер, форм Nx1 и 1xN. Напомним, что операция свертки требует in_channels * n * n * out_channels параметров или весов. Также помните, что каждый вес / параметр требует активации. Таким образом, любое уменьшение количества параметров приведет к уменьшению количества требуемых операций и вычислительных затрат.

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

В Керасе это будет выглядеть так:

# k - kernel size, for example 3, 5, 7...
# n_filters - number of filters/channels
# Note that you shouldn't apply any activation
# or normalization between these 2 layers
fact_conv1 = Conv(n_filters, (1, k))(inp)
fact_conv1 = Conv(n_filters, (k, 1))(fact_conv1)

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

Прежде чем мы углубимся в тему, есть более стабильный способ разложить большие ядра на множители: вместо этого просто складывайте меньшие ядра. Например, вместо использования сверток 5x5 сложите две свертки 3x3 или 3, если вы хотите заменить ядро ​​7x7. Для получения дополнительной информации см. [4].

Слои узких мест

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

Вот код Кераса для этого:

from keras.layers import Conv2D
# given that conv1 has shape (None, N, N, 128)
conv2 = Conv2D(96, (1, 1), ...)(conv1) # squeeze
conv3 = Conv2D(96, (3, 3), ...)(conv2) # map
conv4 = Conv2D(128, (1, 1), ...)(conv3) # expand

Почти все CNN, от революционной InceptionV1 до современной DenseNet, так или иначе используют уровни узких мест. Этот метод помогает сохранить на низком уровне количество параметров и, следовательно, вычислительные затраты.

Более широкие свертки

Еще один простой способ ускорить свертки - это так называемый широкий сверточный слой. Видите ли, чем больше сверточных слоев в вашей модели, тем медленнее она будет. Тем не менее, вам нужна репрезентативная сила множества сверток. Что вы делаете? Вы используете менее толстые слои, где жир означает больше ядер на слой. Почему это работает? Потому что графическому процессору или другим машинам с массовым параллелизмом, если на то пошло, проще обрабатывать один большой кусок данных вместо множества более мелких. Более подробную информацию можно найти в [6].

# convert from
conv = Conv2D(96, (3, 3), ...)(conv)
conv = Conv2D(96, (3, 3), ...)(conv)
# to
conv = Conv2D(128, (3, 3), ...)(conv)
# roughly, take the sqrt of the number of layers you want
# to merge and multipy the number to
# the number of filters/channels in the initial convolutions
# to get the number of filters/channels in the new layer

Глубоко разделимые свертки

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

Идея состоит в том, что вместо совместной свертки по всем каналам изображения вы запускаете отдельную двумерную свертку для каждого канала с глубиной channel_multiplier. in_channels * channel_multiplier промежуточные каналы объединяются и отображаются в out_channels с использованием свертки 1x1. [5] Таким образом, у человека получается значительно меньше параметров для обучения. [2]

# in Keras
from keras.layers import SeparableConv2D
...
net = SeparableConv2D(32, (3, 3))(net)
...
# it's almost 1:1 similar to the simple Keras Conv2D layer

Но все не так просто. Помните, что разделимые свертки иногда не используются. В таких случаях измените множитель глубины с 1 на 4 или 8. Также обратите внимание, что они не так эффективны для небольших наборов данных, таких как CIFAR 10, тем более для MNIST. Еще одна вещь, о которой следует помнить: не используйте Separable Convolutions на ранних этапах сети.

CP-декомпозиция и расширенные методы

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

На основе [1] вот фрагмент кода, показывающий, как выполнить CP-Decomposition в Keras:

# **kwargs - anything valid for Keras layers,
# like regularization, or activation function
# Though, add at your own risk
# Take a look into how ExpandDimension and SqueezeDimension
# are implemented in the associated Colab Notebook
# at the end of the article
first = Conv2D(rank, kernel_size=(1, 1), **kwargs)(inp)
expanded = ExpandDimension(axis=1)(first)
mid1  = Conv3D(rank, kernel_size=(d, 1, 1), **kwargs)(exapanded)
mid2  = Conv3D(rank, kernel_size=(1, d, 1), **kwargs)(mid1)
squeezed = SqueezeDimension(axis=1)(mid2)
last  = Conv2D(out,  kernel_size=(1, 1), **kwargs)(squeezed)

К сожалению, это не работает, но дает интуитивное представление о том, как это должно выглядеть в коде. Кстати, изображение в верхней части статьи представляет собой графическое объяснение того, как работает CP-Decomposition.

Следует отметить такие схемы, как декомпозиция TensorTrain и Tucker. Для PyTorch и NumPy есть отличная библиотека под названием Tensorly, которая выполняет всю низкоуровневую реализацию за вас. В TensorFlow ничего близкого к этому нет, но есть реализация TensorTrain aka TT scheme, здесь.

Эпилог

Полный код в настоящее время доступен в виде Коллаборационного ноутбука с ускорителем Tesla K80 GPU. Сделайте себе копию и получайте удовольствие, возясь с кодом.

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

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

использованная литература

[1] https://medium.com/@krishnatejakrothapalli/hi-rain-4e76039423e2
[2] Ф. Чолле, Xception: глубокое обучение с разделенными по глубине свертками, https://arxiv.org/ abs / 1610.02357v2
[3] В. Лебедев и др., Ускорение сверточных нейронных сетей с использованием тонко настроенной CP-декомпозиции, https://arxiv.org/abs/1412.6553
[ 4] К. Сегеди и др., Переосмысление начальной архитектуры компьютерного зрения, https://arxiv.org/pdf/1512.00567v1.pdf
[5] https://stackoverflow.com/questions/ 37092037 / tensorflow-what-does-tf-nn-separable-conv2d-do # 37092986
[6] С. Загоруйко и Н. Комодакис, Широкие остаточные сети, https://arxiv.org/pdf/ 1605.07146v1.pdf