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

В этом руководстве мы необычным образом рассмотрим крошечный аспект проблемы. Мы заставим нейронные сети рисовать за нас абстрактные изображения, а затем будем интерпретировать эти изображения, чтобы лучше понять, что может происходить под капотом. Кроме того, в качестве бонуса к концу урока вы сможете сгенерировать изображения, подобные приведенным ниже (все меньше 100 строк кода PyTorch. Ознакомьтесь с прилагаемой записной книжкой Jupyter здесь):

Как было создано это изображение?

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

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

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

Не обращайте внимания на z и r на изображении выше и обратите внимание, что сеть принимает координаты x, y пикселя и выводит какой цвет (представленный c ) должен быть этот пиксель. Модель PyTorch для такой сети будет выглядеть так:

Обратите внимание, что он принимает 2 входа и имеет 3 выхода (значения RGB для пикселя). Способ создания всего изображения состоит в том, чтобы передать все позиции x, y желаемого изображения (определенного размера) и продолжать устанавливать цвет этих позиций x, y в соответствии с тем, что выводит сеть.

Эксперименты с нейронной сетью

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

Я провел много часов, почесывая голову, задаваясь вопросом, почему сеть выводит серый цвет независимо от того, какие позиции x, y я вводил в качестве входных данных. В идеале этого не должно происходить, потому что для такой глубокой сети. Изменение входных значений должно изменить выходные значения. Я также знал, что каждый раз, когда нейронная сеть инициализируется, она может генерировать совершенно новое изображение из-за случайной инициализации ее параметров (весов и смещений). Но ясно, что даже после нескольких попыток все, что я получал от своих нейронных сетей, было этой серой слизью. Почему?

Мои подозрения касались конкретной используемой функции активации: tanh. Возможно, несколько последовательностей tanh в последующих слоях сжимали все входные числа до близких к 0,5. в выходном слое (который представляет собой серый цвет). Однако сообщение в блоге, за которым я следил, также использовало tanh. Все, что я делал, это преобразовывал нейронные сети блога, написанные на JavaScript, в PyTorch * без * каких-либо изменений.

Я наконец выяснил виновника. Именно так PyTorch инициализировал веса при инициализации новой нейронной сети. Согласно их пользовательскому форуму они инициализируют веса случайным образом выбранным числом от -1 / sqrt (N) до + 1 / sqrt (N), где N - количество входящих соединений в слое. Итак, если N = 16 для скрытых слоев, веса будут инициализированы от -1/4 до +1/4. Моя гипотеза о том, почему это приводило к появлению серой слизи, заключалась в том, что веса были взяты из небольшого диапазона и не сильно менялись.

Если бы все веса в сети были от -1/4 до +1/4, при умножении на любой вход и сложении, возможно, мог бы иметь место эффект, подобный центральной предельной теореме.

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

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

В нашем случае первый входной слой имеет 2 значения (x, y), а второй скрытый слой имеет 16 нейронов. Итак, каждый нейрон на втором слое получает 2 значения, умноженные на веса от -1/4 до +1/4. Они суммируются, а затем, после перехода из функции активации tanh, становятся новыми значениями для передачи на третий уровень.

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

Вот где мы делаем еще одно предположение. Поскольку дисперсия весов меньше (от -1/4 до +1/4), значения z (которые представляют собой входные данные x, y, умноженные на веса и затем переданные через функцию tanh) также сильно меняться не собираюсь (а значит собираюсь быть похожим). Таким образом, уравнение можно представить как:

И наиболее вероятное значение суммы 16 весов, взятых от -0,25 до +0,25 для каждого нейрона, должно было равняться нулю. Даже если в первом слое сумма не была близка к нулю, восемь уровней сети давали вышеупомянутому уравнению достаточно шансов, чтобы в конечном итоге получить значение, близкое к нулю. Следовательно, независимо от входного значения (x, y), общее значение (сумма весов * входы), поступающее в функцию активации, всегда приближалось к нулевому значению, которое tanh сопоставляется с нулем (и, следовательно, значение во всех последующих слоях остается нулевым).

В чем причина серого цвета? Это потому, что сигмоид (функция активации последнего слоя) принимает это входящее значение, равное нулю, и преобразуется в 0,5 (что представляет серый цвет, 0 - черный, а 1 - белый).

Как исправить серую слизь?

Поскольку причиной была небольшая разница в весе, следующим моим шагом было ее увеличение. Я изменил функцию инициализации по умолчанию, чтобы присвоить веса от -100 до +100 (вместо -1/4 до +1/4). Запустив нейронную сеть, вот что у меня получилось:

Вот и прогресс. Моя гипотеза верна.

Но созданное изображение по-прежнему не имеет особой структуры. Это просто.

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

Обратите внимание, что изображение выше было сгенерировано, когда я вводил X, Y как необработанные пиксельные координаты, начиная с 0,0 и заканчивая 128, 128 (что является размером изображения). Это означало, что моя сеть никогда не принимала отрицательное число на входе, а также, поскольку эти числа были большими (скажем, X, Y могло быть 100, 100), tanh либо получал действительно большое число (что он сжимается до +1) или очень маленькое число (которое сжимается до -1). Вот почему я видел простые комбинации основных цветов (например, выход R, G, B 0,1,1 представляет голубой цвет, который вы видите на изображении выше).

Как сделать изображение интереснее?

Как и в исходном сообщении в блоге (за которым я следил), я решил нормализовать X и Y. Поэтому вместо ввода X я ввел (X / image_size) -0.5. Это означало, что значения X и Y будут в диапазоне от -0,5 до +0,5 (независимо от размера изображения). В результате я получил следующее изображение:

Интересно отметить, что на предыдущем изображении линии росли в правом нижнем углу (потому что значения X, Y увеличивались). Здесь, поскольку значения X, Y нормализованы и теперь включают отрицательные числа, линии равномерно растут наружу.

Однако изображения все равно недостаточно.

Как сделать изображение ЕЩЕ ИНТЕРЕСНЫМ?

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

Есть три способа увеличения к центру изображения:

  • Создайте большое изображение. Поскольку координаты пикселей нормализованы, мы можем просто запустить нейронную сеть для получения изображения большего размера. А после этого мы можем увеличить середину с помощью инструмента для редактирования изображений и посмотреть, что мы находим.
  • Умножьте входные данные X и Y на небольшую величину (коэффициент масштабирования), что эффективно приведет к тому же (и избавит нас от бесполезных вычислений в остальных неинтересных областях), что и в предыдущем методе.
  • Поскольку результат определяется входными * весами, вместо уменьшения входных значений мы могли бы также увеличить масштаб, уменьшив значения веса с -100, +100 до чего-то другого, например + 3, -3 (при этом не забывая не уменьшать слишком сильно. Помните серая слизь, которая появляется, если вес находится в диапазоне от -0,25 до +0,25?)

Когда я применил второй подход и умножил X и Y на 0,01, я получил следующее:

Когда я применил третий подход и установил веса в диапазоне от -3 до +3, вот изображение, которое я получил.

Больше экспериментов

Я изменил инициализацию веса на нормальное распределение (среднее значение 0 и стандартное отклонение 1) и сгенерировал несколько изображений (из случайных инициализаций).

Когда я удалил все скрытые слои (просто ввод для отображения вывода):

Когда я оставил только один скрытый слой (вместо 8 скрытых слоев по умолчанию):

Когда я удвоил количество скрытых слоев до 16:

Как вы понимаете, изображения становились более сложными по мере того, как я увеличивал количество скрытых слоев. Мне было интересно, что произойдет, если вместо удвоения слоев я оставлю количество слоев постоянным (8), но удвою количество нейронов на слой (с 16 до 32). Вот что у меня получилось:

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

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

Сложность вычисляемой функции экспоненциально растет с глубиной

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

Эксперименты, которые не имеют смысла, но доставляют массу удовольствия

Что, если мы увеличим количество нейронов в слое с 8 до 128 (увеличение на порядок).

Что, если мы начнем со 128 нейронов на скрытый слой, но постепенно уменьшим их вдвое на каждом последующем слое, как показано ниже.

Вот что у меня получилось:

Есть * тонны * экспериментов, которые можно провести и получить интересные изображения, поэтому я оставлю это здесь, чтобы вы могли поиграть с кодом (Jupyter Notebook). Попробуйте больше архитектур, активаций и слоев. Если вы получите что-то интересное, отметьте меня в Твиттере или прокомментируйте здесь на Medium, и я расскажу о нем в своей сети.

Или вы можете объединить изображения, созданные нейронной сетью, с философией, созданной нейронной сетью, и сделать что-то вроде этого:

Вот и все. Надеюсь, вам нравится создавать красивые картинки.

Понравился этот урок? Посмотрите и мои предыдущие:

Подпишись на меня в Твиттере

Я регулярно пишу в Твиттере об искусственном интеллекте, глубоком обучении, стартапах, науке и философии. Следуйте за мной на https://twitter.com/paraschopra