Обучение нейронной сети | Оптимизация

Обучение нейронной сети

Все курсы > Оптимизация > Занятие 6

В рамках вводного курса мы начали изучать нейросети. Кроме того, нам знаком важный элемент алгоритма нейронной сети — умножение матриц.

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

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

Содержание занятия

Зачем нужна нейронная сеть

Нелинейная гипотеза

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

нелинейная гипотеза

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

Для полинома n-ой степени (с одним признаком!) формула выглядит следующим образом.

$$ y = \sum{}^n_{j=0} \theta_j x^j $$

Например, полином второй степени будет иметь три коэффициента.

$$ y = \theta_0 + \theta_{1}x + \theta_{2}x^2 $$

Полином второй степени с двумя признаками уже будет иметь шесть коэффициентов.

$$ y = \theta_{0} + \theta_{1}x_1 + \theta_{2}x_2 + \theta_{3} x_1^2 + \theta_{4} x_1x_2 + \theta_{5} x_2^2 $$

В целом, количество полиномиальных коэффициентов (N) можно рассчитать по формуле.

$$ N(n, d) = C(n+d, d), \text{где} $$

  • n — количество линейных признаков
  • d — степень полинома
  • C — количество возможных сочетаний

Используя пример выше, получим

$$ N(2, 2) = C(4, 2) = 6 $$

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

$$ N(10, 3) = C(13, 10) = 286 $$

Если речь идет о картинках 28 х 28 пикселей, то после «вытягивания» каждой картинки у нас появится датасет с 784 признаками. Значит, количество членов полинома второй степени составит

$$ N(784, 2) = C(786, 2) = 308 505 $$

Замечу, что примерное количество признаков полинома второй степени также можно посчитать по формуле $ \frac{(n)^2}{2} $, то есть $ \frac{(784)^2}{2} = 307 328 $

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

Работа нейронной сети

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

Нейрон как дополнительный признак

Возьмем упрощенную модель нейронной сети с двумя скрытыми слоями.

нейрон как дополнительный признак

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

Слой как модель логистической регрессии

Одновременно, если считать, что каждый скрытый слой проходит через функцию активации (activation function), зачастую сигмоиду, то каждый слой, кроме выходного, можно представить как, например, модель логистический регрессии.

Для модели представленной выше рассмотрим как из второго скрытого слоя получается значение выходного слоя.

слой как модель логистической регрессии
  • На основе первого скрытого слоя мы получили некоторые значения нейронов второго скрытого слоя ($a_1^{(2)}$ и $a_2^{(2)}$)
  • У нас есть вектор весов ($w_1^{(2)}$ и $w_2^{(2)}$)
  • Кроме того, мы добавим смещение (b^{(2))

Замечу, что для удобства матричных операций мы можем добавить еще один нейрон скрытого слоя ($w_0^{(2)}$) со значением 1 так, как мы это делали, например, в модели линейной регрессии.

Результат умножения двух векторов мы пропустим через сигмодиду или функцию активации (отсюда выбор буквы a для обозначения этих нейронов) и таким образом получим значение выходного слоя ($a^{(3)}$). Уверен, вы распознали уравнение логистической регрессии.

$$ a^{(3)} = g(w_0^{(2)} \cdot b^{(2)} + w_1^{(2)} \cdot a_1^{(2)} + w_2^{(2)} \cdot a_2^{(2)}) $$

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

Нейросеть и таблица истинности

Продемонстрируем, как нейросеть обучается на нелинейной гипотезе с помощью таблиц истинности.

Этот пример взят из курса по машинному обучению Эндрю Ына⧉ (Andrew Ng).

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

нелинейная гипотеза с помощью таблиц истинности

Логически такая схема соответствует условию $x_1 XNOR x_2$ или $ NOT x_1 XNOR x_2 $. В таблице истинности это условие выглядит так.

XNOR

Другими словами, когда наблюдение по обоим признакам $x_1$ и $x_2$ имеет значение 0, то результатом будет класс 1, когда хотя бы один из признаков равен единице, то класс 0.

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

AND

Итак, $x_1. x_2 \in \{0, 1\} $ и $y = x_1 AND x_2 $. В нейросети будет два нейрона для признаков + смещение. Одновременно сразу пропишем веса модели.

нейросеть AND

Тогда выражение будет иметь вид, $y_{AND} = sigmoid(-30 + 20x_1 + 20x_2)$. Вспомним, как выглядит график сигмоиды.

график сигмоиды

Рассмотрим четыре варианта значений $x_1, x_2$ применительно к такой гипотезе.

  • Если оба признака будут равны нулю, то результат линейного выражения будет равен $-30$. Если «пропустить» это значение через сигмоиду, то результат будет близок к нулю.
  • Если один из них будет равен нулю, а второй единице, то результат будет равен $-10$. Сигмоида опять выдаст близкое к нулю значение.
  • И только если оба признака равны единице, то результат будет равен 10 и сигмоида выдаст значение близкое к единице.

Это и есть условие логического И. Аналогичным образом можно подобрать веса для логического ИЛИ (OR).

OR
нейросеть OR

Соответственно $y_{OR} = sigmoid(-10 + 20x_1 + 20x_2)$. Создадим еще более простую сеть для логического НЕ (NOT).

NOT
нейросеть NOT

Как следствие, $y_{NOT} = sigmoid(10-20x_1)$. Создадим нейросеть, которая будет предсказывать NOT($x1$) AND NOT($x2$).

NOT AND NOT
нейросеть NOT AND NOT

Объединим эти сети в одну.

нейросеть XNOR

Рассчитаем таблицу истинности.

таблица истинности XNOR

Таким образом, мы видим, что на каждом последующем слое нейросеть строит все более сложную зависимость. Первый скрытый слой обучился достаточно простым зависимостям ($x_1$ AND $x_2$ и NOT($x_1$) AND NOT($x_2$)), второй слой дополнил это знание новым ($x1$ OR $x2$), и вместе они обучились выдавать достаточно сложный результат ($x_1$ XNOR $x_2$).

Подготовка данных

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

Откроем ноутбук к этому занятию

Импортируем датасет о вине, удалим класс 2, из признаков оставим спирт и пролин, масштабируем данные.

Дополнительно преобразуем датафрейм признаков X в массив Numpy с размерность 2 x 130 и сделаем целевую переменную y двумерным массивом.

Нейросеть без смещения

Архитектура сети

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

нейросеть без смещения: архитектура сети

Итак, сеть будет состоять из следующих слоев:

  • входной слой $A^{(1)}$ из двух нейронов $a^{(1)}_1$ и $a^{(1)}_2$ т.е. двух признаков, на основе которых мы будем предсказывать класс вина;
  • один скрытый слой $A^{(2)}$, состоящий из трех нейронов: $a^{(2)}_1$, $a^{(2)}_2$ и $a^{(2)}_3$; и
  • выходной слой из одного нейрона $a^{(3)}$, т.е. вероятности принадлежности к одному из двух классов

Заглавной буквой, например, $A^{(1)}$ обозначаются сразу все нейроны, в данном случае, входного слоя, строчной с соответствющим индексом $a^{(1)}_1$ — отдельный нейрон этого слоя.

Также напомню, что у нас последовательная (sequential) архитектура сети, при которой каждый слой получает один тензор на вход и выдает также только один тензор. Кроме этого, мы используем полносвязные (дословно, «плотно связанные», dense, densely connected) слои, где каждый нейрон одного слоя связан с каждым нейроном последующего (обратите внимание на красные и зеленые стрелки между входным и скрытым слоями).

Размерность матриц

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

Первая матрица весов

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

На нее мы будем умножать матрицу весов ($W^{(1)} \cdot X$ или в терминологии слоев $W^{(1)} \cdot A^{(1)}$). Как определить размерность матрицы весов? Очень просто, нужно взглянуть на количество нейронов скрытого слоя. Их три. Значит размерность первой матрицы весов составит 3 х 2.

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

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

  • внутренние размеры, т.е. количество столбцов первой и строк второй, должны совпадать, в нашем случае 2 = 2.
  • размерность результирующей матрицы будет равна внешним размерам умножаемых матриц, 3 и 130.

Итак, в скрытом слое у нас будет матрица 3 х 130, где каждый столбец — это три (активационных) нейрона для каждого из наблюдений.

Полносвязный слой

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

полносвязность слоев

Рассмотрим первую операцию. Здесь веса $w_1$ и $w_4$ умножаются на нейроны входного слоя $a_1$ и $a_2$ и, таким образом, обеспечивают их «участие» в значении нейрона $a_1$ скрытого слоя. Аналогично, при второй операции за это отвечают $w_2$ и $w_5$. Наконец третий нейрон скрытого слоя рассчитывается благодаря весам $w_3$ и $w_6$ и опять же обоим нейронам входного слоя.

Эти же операции можно посмотреть на стрелках на схеме архитектуры сети.

Вторая матрица весов

Теперь, чтобы получить один единственный нейрон выходного слоя (вернее вектор-строку из 130 таких нейронов, 1 х 130), нам нужно новую матрицу весов умножить на результат скрытого слоя на $W^{(2)} \cdot A^{(2)}$.

вторая матрица весов

Очевидно, это должна быть матрица 1 х 3, потому что только она даст нам нужную итоговую размерность 1 x 130.

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

В целом мы только что рассмотрели прямое распространение. Давайте напишем соответствующий код.

Код прямого распространения

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

функции сигмоиды и логистической ошибки в архитектуре сети

Дополнительно замечу, что в нашей терминологии $z^(1)$ и $z^(2)$ — это результат умножения матрицы весов на матрицу нейронов соответствующего слоя, который мы «пропускаем» через сигмоиду (g). Т.е., например, для скрытого слоя

$$ Z^{(1)} = W^{(1)} \cdot A^{(1)} $$

$$ A^{(2)} = g(Z^{(1)}) $$

Теперь объявим веса и поместим признаки в нейроны скрытого слоя (исключительно ради единнобразия терминологии).

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

Найдем ошибку при текущих весах.

Обратное распространение

Теперь главный вопрос. Как обновить веса так, чтобы уменьшить ошибку?

По большому счету нам нужно рассчитать частную производную функции логистической ошибки ($L$) относительно каждого веса ($w_1, w_2, w_3, …, w_9$), ведь именно их мы и будем обновлять. Начнем с весов второго слоя, а именно, $w_7, w_8, w_9$ (все вместе мы будем обозначать их как $W^{(2)}$).

Частные производные весов $W^{(2)}$

Согласно chain rule градиент (т.е. совокупность частных производных) весов второго слоя будет иметь вид

$$ \frac{\partial L}{\partial w^{(2)}} = \frac{\partial L}{\partial a^{(3)}} \circ \frac{\partial a^{(3)} }{\partial z^{(2)}} \circ \frac{ \partial z^{(2)} }{\partial w^{(2)} } $$

Что нам нужно сделать?

  • Вначале найти производную функции логистической ошибки $ \frac{\partial L}{\partial a^{(3)}} $
  • После этого производную сигмоиды $\frac{\partial a^{(3)} }{\partial z^{(2)}}$
  • И наконец линейной функции $\frac{ \partial z^{(2)} }{\partial w^{(2)} }$
  • Перемножить эти производные

Возможно вы заметили, что выше использовались индексы (3) и (2), индекс (3) относит активационную функцию $a^{(3)}$ к третьему выходному слою, а линейную функцию $z^{(2)}$ и веса линейной функции $ w^{(2)} $ ко второму. В такой нотации нам будет удобнее в дальнейшем рассчитывать градиенты и писать код.

На всякий случай также уточню, что это будет поэлементное умножение или произведение Адамара (Hadamard product), которое мы будем обозначать через оператор $\circ$.

Далее, уверен, вы обратили внимание на то, что мы выполняем операции в обратном от прямого распространения порядке: сначала производная ошибки, потом сигмоиды третьего слоя, затем линейной функции второго. Именно поэтому процесс называется обратным распространением ошибки (error back propagation).

Производная функции логистической ошибки

$$ \frac{\partial L}{\partial a^{(3)}} = \frac{\partial}{\partial a^{(3)}} \left( -y \log(a^{(3)})-(1-y) \log(1-a^{(3)}) \right) $$

Применим правило производной разности и вынесем константы.

$$ -y \frac{\partial}{\partial a^{(3)}} \log(a^{(3)})-(1-y) \frac{\partial}{\partial a^{(3)}} \log(1-a^{(3)}) $$

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

$$ -\left( \frac{y}{a^{(3)}}-\frac{(1-y) }{1-a^{(3)}} \right) = \frac{a^{(3)}-y}{a^{(3)}(1-a^{(3)})} $$

Производная сигмоиды

Производную сигмоиды мы уже находили.

$$ \frac{\partial a^{(3)} }{\partial z^{(2)}} = g(z^{(2)}) (1-g(z^{(2)})) $$

При этом так как результат сигмоиды $ g(z^{(2)}) $ — это нейрон выходного слоя $ a^{(3)} $, то

$$ \frac{\partial a^{(3)} }{\partial z^{(2)}} = a^{(3)} (1-a^{(3)}) $$

Производная линейной функции

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

$$ \frac{ \partial }{\partial w^{(2)} } \left( w_7 \times a^{(2)}_1 + w_8 \times a^{(2)}_2 + w_9 \times a^{(2)}_3 \right) $$

Для того чтобы найти производную относительно, например, веса $w_7$, мы «замораживаем» (считаем константами, производная которых равна нулю) все веса кроме первого и тогда

$$ w_7^{1-1} \times a^{(2)}_1 + 0 \times a^{(2)}_2 + 0 \times a^{(2)}_3 = $$

$$ 1 \times a^{(2)}_1 + 0 \times a^{(2)}_2 + 0 \times a^{(2)}_3 = a^{(2)}_1 $$

Аналогичный результат мы получим, продифференцировав относительно других весов. Тогда,

$$ \frac{ \partial z^{(2)} }{\partial w^{(2)}} = a^{(2)} $$

Наконец перемножим найденные производные и упростим выражение.

$$ \frac{\partial L}{\partial w^{(2)}} = \frac{a^{(3)}-y}{a^{(3)}(1-a^{(3)})} \circ a^{(3)} (1-a^{(3)}) \circ a^{(2)} = $$

$$ (a^{(3)}-y) \circ a^{(2)} $$

В векторной нотации (и матричном умножении) получим

$$ \frac{\partial L}{\partial W^{(2)}} = (A^{(3)}-y) \cdot A^{(2)}.T \times \frac{1}{n} $$

Множитель $ \frac{1}{n} $ усредняет градиент на количество наблюдений.

Дельта-правило ($ \delta_2 $)

Замечу, что $\frac{\partial L}{\partial a^{(3)}} \circ \frac{\partial a^{(3)} }{\partial z^{(2)}}$ также обозначают через греческую букву «дельта» (в нашем случае $\delta_2$), и тогда градиент для обновления весов $W^{(2)}$, с учетом векторизованного кода, приобретет вид (опять же в векторной нотации)

$$ \frac{\partial L}{\partial W^{(2)}} = \delta_2 \cdot A^{(2)}.T \times \frac{1}{n} $$

В дальнейшем использование так называемого «дельта-правила» (delta rule) упростит наш код.

Обновление весов $W^{(2)}$

Остается только обновить веса $W^{(2)}$ в направлении антиградиента, умноженного на коэффициент скорости обучения.

$$ W^{(2)} := W^{(2)}-\alpha \times \frac{\partial L}{\partial W^{(2)}}$$

Частные производные весов $W^{(1)}$

Теперь нужно найти производные относительно весов ($w_1, …, w_6$) или $W^{(1)}$. И мы снова должны «раскручивать» chain rule от функции логистической ошибки. На этот раз цепь будет более длинной.

$$ \frac{\partial L}{\partial w^{(1)}} = \frac{\partial L}{\partial a^{(3)}} \circ \frac{\partial a^{(3)} }{\partial z^{(2)}} \circ \frac{ \partial z^{(2)} }{\partial a^{(2)} } \circ \frac{ \partial a^{(2)} }{\partial z^{(1)} } \circ \frac{ \partial z^{(1)} }{\partial w^{(1)} } $$

Нахождение производных

Вспомним, что первые два множителя $\frac{\partial L}{\partial a^{(3)}} \circ \frac{\partial a^{(3)} }{\partial z^{(2)}}$ мы обозначили через $\delta_2$.

Обратим внимание на третий множитель $ \frac{ \partial z^{(2)} }{\partial a^{(2)} } $. В отличие от градиента весов $W^{(2)}$, где мы, напомню, искали производную линейной функции относительно весов $\frac{ \partial z^{(2)} }{\partial w^{(2)} }$, здесь нас интересует частная производная относительно нейронов активационного слоя $a^{(2)}$.

Тогда в данном случае мы «замораживаем» (считаем константами) не веса, а нейроны $ a^{(2)} $ (считая веса просто числами) и, например, частная производная относительно $a^{(2)}_1$ будет равна

$$ \frac{ \partial }{\partial a^{(2)}_1 } \left( w_7 \times a^{(2)}_1 + w_8 \times a^{(2)}_2 + w_9 \times a^{(2)}_3 \right) $$

$$ w_7 \times 1 + w_8 \times 0 + w_9 \times 0 = w_7 $$

Аналогично находим производные относительно других нейронов. В векторной нотации,

$$ \frac{ \partial z^{(2)} }{\partial a^{(2)} } = W^{(2)}$$

Интересно, что ошибкой скрытого слоя $E_2$ (ошибкой $E_1$ была бы общая ошибка, которую мы рассчитали с помощью функции логистической ошибки) называют произведение

$$ E_2 = \frac{\partial L}{\partial a^{(3)}} \circ \frac{\partial a^{(3)} }{\partial z^{(2)}} \circ \frac{ \partial z^{(2)} }{\partial a^{(2)} } $$

Это утверждение более понятно, если переписать (в векторной нотации) выражение выше как,

$$ E_2 = W^{(2)}.T \cdot \delta_2 $$

То есть, мы по сути распространяем «ошибку» $ \delta_2 $ (число, скаляр) на каждый из трех весов $W^{(2)}$.

Перейдем к четвертому множителю $ \frac{ \partial a^{(2)} }{\partial z^{(1)} }$. Это снова производная сигмоиды, только уже «на слой раньше»,

$$ \frac{ \partial a^{(2)} }{\partial z^{(1)} } = g(z^{(1)}) (1-g(z^{(1)})) = a^{(2)} (1-a^{(2)}) $$

И наконец пятый компонент,

$$ \frac{ \partial z^{(1)} }{\partial w^{(1)} } = a^{(1)} $$

Дельта-правило ($ \delta_1 $)

Аналогично предыдущему слою мы можем обозначить $ \frac{\partial L}{\partial a^{(3)}} \circ \frac{\partial a^{(3)} }{\partial z^{(2)}} \circ \frac{ \partial z^{(2)} }{\partial a^{(2)} } \circ \frac{ \partial a^{(2)} }{\partial z^{(1)} } $ как $ \delta_1 $ (то есть мы опять взяли все множители, кроме последнего).

Градиент относительно $W^{(1)}$

В итоге градиент относительно весов $W^{(1)}$ имел бы вид,

$$ \frac{\partial L}{\partial W^{(1)}} = \left( E_2 \circ A^{(2)} \circ (1-A^{(2)}) \right) \cdot A^{(1)}.T \times \frac{1}{n} $$

Или, раскрыв $E_2$,

$$ \frac{\partial L}{\partial W^{(1)}} = \left( ( W^{(2)}.T \cdot \delta_2) \circ A^{(2)} \circ (1-A^{(2)}) \right) \cdot A^{(1)}.T \times \frac{1}{n} $$

Или через $ \delta_1 $

$$ \frac{\partial L}{\partial W^{(1)}} = \delta_1 \cdot A^{(1)}.T \times \frac{1}{n} $$

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

$$ \frac{\partial L}{\partial W^{(2)}} = \delta_2 \cdot A^{(2)}.T \times \frac{1}{n} $$

$$ \frac{\partial L}{\partial W^{(1)}} = \delta_1 \cdot A^{(1)}.T \times \frac{1}{n} $$

Разумеется, это правило справедливо и для большего количества скрытых слоев.

Обновление весов $W^{(1)}$

Обновление весов $W^{(1)}$ аналогично предыдущему слою.

$$ W^{(1)} := W^{(1)}-\alpha \times \frac{\partial L}{\partial W^{(1)}}$$

Перейдем к коду.

Код обратного распространения

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

Обучение модели

Теперь соединим прямое и обратное распространение и с помощью цикла произведем обучение нейронной сети.

Прогноз и оценка качества

Сделаем прогноз и оценим качество.

Инициализация весов

В моделях линейной и логистической регрессии в качестве начальных значений коэффициентов мы использовали нули, в алгоритме нейронной сети — случайные значения, почему так?

инициализация весов

Если веса изначально равны нулю, то произойдет несколько нежелательных событий:

  • значения активационных слоев $a_1^(2) = a_2^(2) = a_3^(2) $ будут одинаковыми, то есть запоминать одну и ту же зависимость
  • более того, так как веса между вторым и третьим (выходным) слоем будут одинаковыми, то и значения матрицы $ \delta_2 $ будут одинаковыми,
  • а значит и частные производные, относящиеся к весам одного входного нейрона (например, $w_1, w_2, w_3$) будут одинаковыми

Таким образом, после, например, одного обновления весов, хотя значения весов $w_1, w_2, w_3$ не будут нулевыми, они будут одинаковыми. То же можно сказать про веса $w_4, w_5, w_6$. И снова $a_1^(2) = a_2^(2) = a_3^(2) $.

Как следствие, мы существенно ограничиваем возможности (гибкость) нейронной сети запоминать сложные зависимости.

Масштабирование целевой переменной

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

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

Модель в Tensorflow и Keras

Библиотека Keras представляет собой «надстройку«⧉ (интерфейс, API), через которую удобно создавать нейросети в библиотеке Tensorflow. Реализуем созданную выше несложную нейросеть в библиотеке Keras.

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

Перейдем к созданию и обучению нейросети.

Оценим качество.

Нейросеть со смещением

Добавим смещение, это должно сделать нашу модель более гибкой.

нейросеть со смещением

В отличие от линейных моделей, мы не будем использовать одну и ту же производную и для $b$, и для $W$ (с добавлением столбца из единиц в X). Это связано с тем, что в нейросетях при обратном распространении ошибку на смещение мы не распространяем.

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

$$ \frac{ \partial z^{(2)} }{\partial b^{(2)}} = 1 $$

$$ \frac{ \partial z^{(2)} }{\partial b^{(1)}} = 1 $$

Тогда в целом, используя дельта-правило, частные производные относительно $b^{(2)}$ и $b^{(1)}$ будут равны

$$ \frac{\partial L}{\partial b^{(2)}} = \sum \delta_2 \times \frac{1}{n} $$

$$ \frac{\partial L}{\partial b^{(1)}} = \sum \delta_1 \times \frac{1}{n} $$

Перейдем к коду.

TF / Keras. Добавим смещение в нашу модель в библиотеке Keras.

Два скрытых слоя

Добавим второй скрытый слой.

Сравним с моделью в Keras.

Многоклассовая классификация

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

Постановка задачи и архитектура

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

Архитектуру модели сохраним прежней.

архитектура нейросети для распознавания цифр из MNIST

Функции активации

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

Функция потерь

Функцией потерь будет категориальная кросс-энтропия.

Обратное распространение

Очевидно, так как изменилась функция потерь и функция активации выходного слоя (softmax) необходимо заново рассчитать производные. Напомню, для весов $W^{(3)}$ цепное правило будет работать следующим образом.

$$ \frac{\partial L}{\partial w^{(3)}} = \frac{\partial L}{\partial a^{(4)}} \circ \frac{\partial a^{(4)} }{\partial z^{(3)}} \circ \frac{ \partial z^{(3)} }{\partial w^{(3)} } $$

При этом, оказывается, что производная первых двух компонентов $\frac{\partial L}{\partial a^{(4)}} \circ \frac{\partial a^{(4)} }{\partial z^{(3)}}$ (т.е. кросс-энтропии и softmax) сводится к $a^{(4)}-y$ (она аналогична бинарной кросс-энтропии и сигмоиде, но находится⧉, разумеется, иначе).

Одновременно этот компонент производной представляет собой $\delta_3$, которую для нахождения градиента необходимо умножить на $A^{(3)}.T$.

$$ \frac{\partial L}{\partial W^{(3)}} = \delta_3 \cdot A^{(3)}.T \times \frac{1}{n} $$

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

$$ \frac{\partial L}{\partial W^{(2)}} = \delta_2 \cdot A^{(2)}.T \times \frac{1}{n} $$

$$ \frac{\partial L}{\partial W^{(1)}} = \delta_1 \cdot A^{(1)}.T \times \frac{1}{n} $$

Подготовка данных

Обучение модели

Код ниже исполняется в Google Colab около 10 минут.

Прогноз и оценка качества

Сделаем прогноз и оценим качество на обучающей выборке.

Теперь на тестовых данных.

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

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

Подведем итог

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

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


Ответы на вопросы

Вопрос. Чем отличается умножение матриц от векторизации?

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