Использование простого примера для освещения принципов проектирования нейронных сетей

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

Это становится еще хуже, когда мы пытаемся получить явное выражение, решая для y.

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

В полярных координатах наш единичный круг принимает простой вид r = 1, θ ∈ [0,2π). Таким образом, в полярных координатах круг принимает форму прямоугольника (высота = 1, ширина = 2π). Декартовы координаты являются естественными координатами для прямоугольников, а полярные координаты - естественными координатами для окружностей. В этой статье я применю этот образ мышления к построению простых нейронных сетей и построю наглядный пример важности выбора правильных координат.

Искусственные нейронные сети для аппроксимации кривой

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

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

  • Как найти актуальные координаты.
  • Если это действительно хороший способ представления информации.

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

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

Подгонка функции sin с помощью нейронной сети

Давайте применим этот мощный подход к аппроксимации функций для построения нейросетевой версии простой функции sin (x). Я буду использовать фантастическую библиотеку нейронных сетей Flux, написанную на Джулии. Этот пакет предоставляет очень простой и мощный фреймворк для построения нейронных сетей. Он также добавляет очень мало дополнительного синтаксиса для построения нейронных сетей и позволяет нам сосредоточиться на основных строительных блоках. Я просто собираюсь включить в статью фрагменты кода. Полный код можно найти в этом репозитории на github.

Приведенный ниже код создает простую сеть с двумя скрытыми слоями, используя стандартную нелинейную функцию tanh.

ann = Chain(Dense(1,20,tanh),Dense(20,20,tanh),Dense(20,1));

Мы можем визуализировать эту нейронную сеть как:

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

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

function loss(x, y)
    pred=ann(x)
    loss=Flux.mse(ann(x), y)
    #loss+=0.1*sum(l1,params(ann)) #l1 reg
    return loss
end
@epochs 3000 Flux.train!(loss,params(ann), data, ADAM())

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

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

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

Мы могли бы попытаться применить грубую силу и построить более крупные сети или тренироваться дольше и т. Д. Однако очень простое изменение даст нам почти идеальное приближение за очень короткое время.

Лучшая идея

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

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

Записанное в функциональной форме семейство функций, которые мы используем, принимает форму:

Где Q - количество нейронов в скрытом слое. В Flux это очень просто сделать:

Q = 20;
ann = Chain(Dense(1,Q,sin),Dense(Q,1));

Давайте подберем эту новую модель и посмотрим, сможем ли мы получить лучшую модель.

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

Функциональная форма нейронной сети выше очень близка к форме для ряда Фурье. Ряды Фурье - это базис (набор координат), который можно использовать для описания всех хороших функций в конечной области [a, b]. Если функция периодическая, то описание в конечной области может быть распространено на всю вещественную линию. Поэтому для периодических функций часто используются ряды Фурье.

Ряд Фурье имеет вид:

Единственное различие между нашей нейронной сетью и рядом Фурье состоит в том, что наша нейронная сеть позволяет изменять веса W, тогда как у Фурье есть только коэффициенты A и члены смещения (b), которые могут быть выбраны для соответствия функции.

В библиотеке Flux достаточно легко удалить эти весовые параметры на первом уровне из обучаемого набора параметров. Устанавливая их на целые значения, мы фактически можем создать нейронную сеть, которая ЯВЛЯЕТСЯ рядом Фурье.

# Fourier Series ANN
Q = 20;
ann = Chain(Dense(1,Q,sin),Dense(Q,1));
function loss(x, y)
    pred=ann(x)
    loss=Flux.mse(ann(x), y)
    return loss
end
opt = ADAM()
ps=params(ann)
for j=1:20
    ann[1].W[j]=j
end
delete!(ps, ann[1].W) #Make the first layers weights fixed
@epochs 3000 Flux.train!(loss,ps, data, opt)

Вот пример с использованием функции sin (x) и ИНС Фурье:

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

Некоторые уроки на вынос

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

  1. Дизайн ваших нейронных сетей может иметь огромное влияние на результаты. Конкретное знание проблемы и предметной области может иметь огромное значение.
  2. Убедитесь, что все, что вы знаете о проблеме, передается в нейронную сеть. Здесь мы увидели, что указание функции генерировать периодический сигнал делает гораздо больше, чем может сделать увеличение размера сети или времени обучения.
  3. Как и в предыдущем уроке, важны знания предметной области.
  4. Прикладные математики и физики очень давно работают над проблемой приближения функций. Ряды Фурье были открыты в 1820-х годах для решения задач теплопроводности. Возможно, вам стоит потратить время на изучение этих методов, даже если вас не волнует приложение.
  5. Вычисление ряда Фурье с использованием ИНС очень глупо. Один из десяти лучших алгоритмов прошлого века был придуман именно для такого рода вычислений. Смысл в том, чтобы показать, как небольшое изменение для включения большего количества знаний в предметную область может значительно улучшить результаты.

Чтобы узнать больше о создании более умных нейронных сетей, я бы порекомендовал проверить Работы Натана Куца.