Пространственный преобразователь - еще один блок LEGO в коллекции дифференцируемых модулей. Он удаляет пространственную инвариантность изображений, применяя обучаемое аффинное преобразование с последующей интерполяцией. Блок STN может быть помещен в сверточную нейронную сеть (CNN), и он в основном работает сам по себе.

Полный код этого проекта доступен на GitHub: https://github.com/dnkirill/stn_idsia_convnet

Чтобы получить некоторое представление о том, что делает STN, посмотрите две демонстрации из моего проекта ниже:

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

Обнаружение STN работает даже в более сложных случаях (например, два знака на изображении). Однако наиболее важным является то, что STN действительно повышает точность классификатора (в моем случае IDSIA).

Абсолютно минимальный ускоренный курс по сетям пространственных трансформаторов

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

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

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

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

Вот еще одно изображение, чтобы получить представление о STN:

Если исключить обучающую часть, модуль STN можно свести к следующему процессу:

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

СТН: шаги трансформации

Шаг 1. Определите матрицу преобразования 𝛳 (тета), которая описывает само линейное преобразование. Тету можно определить следующим образом:

Разным матрицам соответствуют разные преобразования. Нас больше всего интересуют эти четыре:

  • Идентичность (выводит то же изображение). Это наши начальные значения теты. В этом случае тета-матрица диагональна:
    theta = np.array([[1.0, 0, 0], [0, 1.0, 0]])
  • Вращение (против часовой стрелки, 45º). cos(45º) = 1/sqrt(2) ≈ 0.7:
    theta = np.array([[0.7, -0.7, 0], [0.7, 0.7, 0]])
  • Увеличение.
    Увеличение в центре (с коэффициентом 2):
    theta = np.array([[0.5, 0, 0], [0, 0.5, 0]])
  • Уменьшение.
    Уменьшение центра (в 2 раза):
    theta = np.array([[2.0, 0, 0], [0, 2.0, 0]])

Шаг 2. Вместо того, чтобы применять преобразование непосредственно к исходному изображению, мы генерируем сетку выборки того же размера, что и исходное изображение U. Сетчатая сетка - это набор индексов (x_t, y_t), которые покрывают все пространство входного изображения. Он не содержит информации о цвете. Это лучше объяснить в коде:

Из-за специфики TensorFlow назначения x_t и y_t могут выглядеть громоздкими. Numpy-перевод работает лучше:

x_t, y_t = np.meshgrid(np.linspace(-1, 1, width), np.linspace(-1, 1, height))

Шаг 3. Примените матрицу линейного преобразования к сетке, которую мы инициализировали, чтобы получить новый набор точек выборки. Каждую точку преобразованной сетки можно определить как матрично-векторное умножение теты на координаты (x_t, y_t) и член смещения:

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

Проблема с обучением. Если мы каким-то образом предоставим правильную тэту для каждого входного изображения, мы можем начать использовать процесс, который мы только что описали. На самом деле мы хотим получить тэту из данных - и действительно можем! Во-первых, нам нужно убедиться, что потери от классификатора дорожных знаков могут передаваться обратно в пробоотборник и через него. Во-вторых, мы определяем градиенты относительно U и G (сетка): по этой причине функция интерполяции должна быть дифференцируемой или субдифференцируемой. В-третьих, мы вычисляем частные производные для x и y по тета. Если вам действительно интересно, посмотрите исходную статью для расчета градиентов.

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

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

Это вкратце STN. Вот схема STN из исходной статьи:

Как видите, мы рассмотрели все строительные блоки STN: сеть локализации, генератор meshgrid и сэмплер. В следующей части мы обучаем классификатор TensorFlow, который включает STN как часть своего графа.

Обзор модели TensorFlow

Код модели слишком длинный, чтобы его можно было перечислить и рассмотреть в этом посте, но он находится в свободном доступе в репозитории github:
https://github.com/dnkirill/stn_idsia_convnet

Я лучше сосредоточусь на некоторых важных фрагментах кода и обучении модели.

Во-первых, поскольку последней задачей является классификация дорожных знаков, нам необходимо определить и обучить сам классификатор. Выбор велик: от LeNet до любой новой первоклассной сети, которую вы только можете себе представить. В этом проекте, вдохновленном исследованием Moodstocks по STN (реализованным в Torch), я использую сеть, подобную IDSIA.

Во-вторых, мы определяем и обучаем модуль STN, который принимает исходное изображение в качестве входных данных, преобразует его с помощью сэмплера и создает другое изображение (или пакет изображений). Это изображение затем используется классификатором в качестве входных данных. Обратите внимание, что STN можно легко отсоединить от DAG и заменить исходным пакетным генератором изображений. Возможность размещения в любой части CNN - одно из основных преимуществ STN.

Вот обзор объединенной сети:

Эта часть DAG использует пакет исходных изображений в качестве входных данных, преобразует их через модуль STN, передает преобразованные данные в классификатор (IDSIA) и выводит логиты:

При условии, что у нас есть метод вычисления логитов (сеть STN + IDSIA), следующим шагом будет вычисление потерь (мы используем функцию потерь с перекрестной энтропией или лог-потерь как хороший выбор по умолчанию для задачи классификации):

Затем следует определение дополнительных операций - в основном мы инициализируем операции DAG TensorFlow. Нам нужно выбрать оптимизатор и обучающую операцию, которая возвращает потерю входным слоям:

Я начинаю с более высокой скорости обучения (0,02): это помогает распространять более сильную информацию о градиенте в локализационную сеть STN, которая расположена на передних уровнях всей сети и может тренироваться очень медленно (из-за проблемы исчезающего градиента ) иначе. Меньшие начальные значения скорости обучения затрудняют масштабирование сети на более мелкие дорожные знаки.

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

Часть DAG, которая выводит логиты (прямой проход сети), добавляется к графу с помощью очень простого объявления:

Это простое объявление разворачивает всю сеть, то есть STN + IDSIA. Они обсуждаются в следующем разделе.

Сеть классификаторов IDSIA

Я был вдохновлен как исследованием Moodstocks, так и первоначальным документом IDSIA Swiss AI Group, в котором они использовали ансамбль CNN, чтобы побить современный результат по этому набору данных. Я взял проект архитектуры отдельной сети из ансамбля и определил его в TensorFlow на своих условиях. Конфигурация классификатора выглядит так:

  • Уровень 1: сверточный (пакетная нормализация, повторное подключение, выпадение). Ядро: 7x7, 100 фильтров. Ввод: 32 x 32 x 1 (в пакете по 256). Вывод: 32 x 32 x 100.
  • Уровень 2: Максимальное объединение. Ввод: 32 x 32 x 100. Вывод: 16 x 16 x 100.
  • Уровень 3: сверточный (пакетная нормализация, повторное подключение, выпадение). Ядро: 5x5, 150 фильтров. Ввод: 16 x 16 x 100 (в пакете по 256). Вывод: 16 x 16 x 150.
  • Уровень 4: Максимальное объединение. Ввод: 16 x 16 x 150. Вывод: 8 x 8 x 150.
  • Уровень 5: сверточный (пакетная нормализация, повторное подключение, выпадение). Ядро: 5x5, 250 фильтров. Ввод: 16 x 16 x 100 (в пакете по 256). Вывод: 16 x 16 x 150.
  • Уровень 6: Максимальное объединение. Ввод: 8 x 8 x 250. Вывод: 4 x 4 x 250.
  • Уровень 7: Дополнительный пул для многомасштабных функций. Размеры ядра 8, 4, 2 для сверточных слоев 1, 2 и 3 соответственно.
  • Слой 8: Сглаживание и объединение многомасштабных элементов. Ввод: 2x2x100; 2х2х150; 2х2х250. Выход: вектор признаков 400 + 600 + 1000 = 2000 для полносвязных слоев.
  • Уровень 9: Полностью подключен (пакетная нормализация, повторное подключение, выпадение). Ввод: 2000 функций (в пакете по 256). 300 нейронов.
  • Уровень 10: уровень логитов (пакетная нормализация). Ввод: 300 функций. На выходе: логиты (43 класса).

Это можно проиллюстрировать следующим изображением:

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

Вход для conv1 передается по конвейеру из выхода STN, как мы обсуждали ранее.

Пространственные преобразователи в TensorFlow

Зоопарк моделей TensorFlow содержит реализацию STN, которая будет использоваться для нашей модели: https://github.com/tensorflow/models/tree/master/transformer.

Наша задача - определить и обучить сеть локализации, предоставить transformer тета и внедрить модуль STN в DAG TensorFlow. transformer генерирует сетку и обеспечивает функции преобразования и интерполяции.

Конфигурация сети локализации следующая:

Сверточные слои LocNet:

  • Уровень 1: Максимальное объединение. Ввод: 32 x 32 x 1. Вывод: 16x16x1.
  • Уровень 2: сверточный (relu, пакетная нормализация). Ядро: 5x5, 100 фильтров. Ввод: 16 x 16 x 1 (в пакете по 256). Вывод: 16 x 16 x 100.
  • Уровень 3: Максимальное объединение. Ввод: 16 x 16 x 100. Вывод: 8x8x100.
  • Уровень 4: сверточный (пакетная нормализация, relu). Ядро: 5x5, 200 фильтров. Ввод: 8x8x100 (в пакете по 256). Вывод: 8x8x200.
  • Уровень 5: Максимальное объединение. Сырьё: 8x8x200. Выход: 4х4х200.
  • Уровень 6: Дополнительный пул для многомасштабных функций. Размеры ядра 4, 2 для сверточных слоев 1 и 2 соответственно.
  • Слой 7: Сглаживание и объединение многомасштабных элементов. Ввод: 2x2x100; 2x2x200. Выход: вектор признаков 400 + 800 = 1200 для полносвязных слоев.

Полностью подключенная сеть LocNet:

  • Уровень 8: Полностью подключен (пакетная нормализация, повторное подключение, отключение). Ввод: 1200 характеристик (в пакете по 256). 100 нейронов.
  • Уровень 9: Логиты - тета-матрица 2x3, определяющая аффинное преобразование. Веса инициализируются как нули, смещения инициализируются как преобразование идентичности: [[1.0, 0, 0], [0, 1.0, 0]].
  • Уровень 10: Трансформатор: генератор сетки и семплер. Эта логика реализована в spatial_transformer.py. Этот слой выводит преобразованные изображения с теми же размерами, что и исходное (32x32x1), с примененным к нему аффинным преобразованием (например, увеличенное или повернутое изображение).

Структура сверточных слоев локальной сети аналогична IDSIA (хотя локальная сеть состоит из 2 сверточных слоев вместо 3, и мы сначала понижаем дискретизацию входных изображений). Иллюстрация полностью связанных слоев более примечательна:

Обучение и результаты

Проблема с CNN с модулем STN заключается в том, что необходимо контролировать обе сети на предмет переоснащения. Это делает обучение сложным и нестабильным. С другой стороны, добавление немного расширенных данных (особенно увеличение яркости) действительно спасает обе сети от переобучения. В любом случае, плюсы перевешивают минусы: мы получаем хорошие результаты без каких-либо дополнений, а STN + IDSIA превосходит IDSIA без этого модуля на 0,5–1%.

Для обучения использовались следующие параметры:

Даже спустя всего 10 эпох мы получаем accuracy = 99.3 в наборе проверки. CNN по-прежнему переоснащается, но эффекты переобучения можно уменьшить за счет увеличения изображения. Фактически, добавив дополнительные дополнения, я получил accuracy = 99.6 на проверке, установленной после 10 эпох (хотя время обучения резко увеличилось).

Вот результаты обучения (idsia_1 - это одна сеть IDSIA, idsia_stn - это STN + IDSIA). Точность - это общая точность проверки для немецкого набора данных дорожных знаков, а не для STN:

STN + IDSIA превосходит одну сеть IDSIA, однако для обучения требуется больше времени. Обратите внимание, что точность проверки на приведенной выше диаграмме рассчитывается для пакетов, а не для всего набора для проверки.

Наконец, вот пример вывода трансформатора STN после обучения:

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

Наконец, подведем итоги:

  • STN - это дифференцируемый модуль, который можно внедрить в сверточную нейронную сеть. По умолчанию его следует разместить сразу «после» входного слоя, чтобы он изучил лучшую матрицу преобразования тета, которая минимизирует функцию потерь основного классификатора (в нашем случае это IDSIA).
  • Сэмплер STN применяет аффинное преобразование к входным изображениям (или картам функций).
  • STN можно рассматривать как альтернативу увеличению изображения, которое является стандартным способом достижения пространственной инвариантности для CNN.
  • Добавление одного или нескольких STN к вашей CNN делает обучение более сложным и нестабильным: теперь вам нужно контролировать две (вместо одной) сети на предмет переобучения. Похоже, это причина того, что STN пока не получили широкого распространения.
  • STN, которые обучены с некоторым увеличением данных (особенно с увеличением условий освещения), работают лучше и, как правило, не слишком подходят для переобучения.