Обучение выполнению линейной фильтрации с использованием данных естественного изображения

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

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

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

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

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

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

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

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

Мы можем провести следующую связь между линейным фильтром и сверточной нейронной сетью:

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

Параметры линейного фильтра, применяемого к данным изображения, называются ядром свертки. Мы начнем наши эксперименты с фильтрации данных изображения с помощью ядер свертки 3 x 3, называемых операторами Собеля, сначала в направлении x, а затем в направлении y. Операторы Собеля представлены как:

Для экспериментов мы будем использовать фреймворк Keras, работающий поверх TensorFlow.

Фильтр Собеля в x-направлении

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

В качестве данных для обучения и тестирования мы будем использовать категории городских и природных сцен - набор данных (Oliva, A. & Torralba, A. (2001). Моделирование формы сцены: целостное представление пространственной оболочки), собранный Лаборатория компьютерного зрительного познания, Массачусетский технологический институт.

Исходный набор данных состоит из естественных цветных изображений сцены (разрешение: 256 x 256) в восьми категориях, из которых мы используем три: улица, центр города и высокое здание. Таким образом, мы получаем обучающие и тестовые наборы достаточного размера (764 обучающих выборки и 192 тестовых выборки), так что обучение не страдает от переобучения и что обучение может выполняться также с использованием более умеренного оборудования в разумные сроки. . Выбранные категории представляют собой естественные сцены с сильными гранями (большой охват построенных людьми структур), что помогает нам сравнивать результаты.

Ниже мы можем наблюдать визуализацию исходного изображения, принадлежащего набору данных, преобразование в оттенки серого и версию изображения, отфильтрованную по Собелю:

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

Затем мы определяем модель: однослойная, одноядерная, сверточная сеть с линейной активацией, то есть функция активации идентична. Размер ядра свертки выбран равным 3 x 3, чтобы соответствовать размеру фильтра Собела.

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

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

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

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

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

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

Чтобы протестировать модель, мы можем увидеть, как прогнозы модели выглядят по сравнению с фильтрацией Собеля в x-направлении.

На рисунке (вверху) мы можем наблюдать вывод модели и результат фильтрации Собеля в x-направлении бок о бок. При визуальном осмотре оба изображения выглядят одинаково. Фактически, в значениях интенсивности нужно найти лишь незначительные различия между изображениями, поскольку изученное ядро ​​свертки сходилось близко к значениям исходного оператора Собеля.

Фильтр Собеля в направлении оси y

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

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

Еще раз, мы можем наблюдать, как веса ядра свертки продвигаются к фильтру Собеля в направлении y по мере того, как сеть учится на данных обучения. Поведение сходимости очень похоже на предыдущий случай с оператором Собеля в x-направлении.

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

Смайлик-фильтр

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

Ядро фильтра, которое мы будем использовать в следующем эксперименте, представляет собой смайлик 32 x 32 пикселя (спасибо, Лео, за идею :)). Ядро фильтра загружается, и обучающие данные создаются путем фильтрации изображений в градациях серого с помощью ядра смайлика. Из-за большого размера ядра ядро ​​существенно выходит за границы изображения. Граница изображения дополняется нулями, чтобы противодействовать уменьшению разрешения изображения в результате свертки.

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

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

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

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

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

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

Заключительные слова

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

Если вы заинтересованы в проведении экспериментов на собственном компьютере, вот ссылка на папку github, которая содержит все необходимое для повторения экспериментов: https://github.com/vexcel/Keras-LearnLinearFilter.