6. Бэкпроп своими руками

6. Бэкпроп своими руками#

У Маши есть нейросеть с картинки ниже. Она использует функцию потерь

\[ L(W_1, W_2, W_3) = \frac{1}{2} \cdot (\hat y - y)^2. \]

В качестве функции активации Маша выбрала сигмоиду \(\sigma(t) = \frac{e^t}{1 + e^t}\).

../_images/img06_task.png

Выпишите для Машиной нейросетки алгоритм обратного распространения ошибки в общем виде. Пусть Маша инициализировала веса нейронной сети нулями. У неё есть два наблюдения.

Первое: \(x_1 = 1, x_2 = 1, y = 0\). И второе: \(x_1 = 5, x_2 = 2, y = 1\).

Сделайте руками два шага алгоритма обратного распространения ошибки. Пусть скорость обучения \(\eta = 1\). Стохастический градиентный спуск решил, что сначала для шага будет использоваться второе наблюдение, а затем первое.

Решение

Для начала запишем алгоритм в общем виде. Для этого нам надо взять схему из предыдущей задачи и записать там все производные. Для сигмоиды \(\sigma'(t) = \sigma(t) \cdot (1 - \sigma(t)).\) Прямой проход по нейронной сети (forward pass):

dobronet_forward

Обратный проход по нейронной сети (backward pass):

dobronet_forward

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

\[\begin{equation*} \begin{aligned} & d = (\hat{y} - y) \\ & \frac{\partial MSE}{\partial W_3} = O_2^T \cdot d \\ \end{aligned} \end{equation*}\]

На втором слое:

\[\begin{equation*} \begin{aligned} & d = d \cdot W_3^T \odot O_2 \odot (1 - O_2) \\ & \frac{\partial MSE}{\partial W_2} = O_1^T \cdot d \\ \end{aligned} \end{equation*}\]

На первом слое:

\[\begin{equation*} \begin{aligned} & d = d \cdot W_2^T \odot O_1 \odot (1 - O_1) \\ & \frac{\partial MSE}{\partial W_1} = X^T \cdot d \\ \end{aligned} \end{equation*}\]

Когда мы аккуратно подставим все числа, можно будет сделать шаг SGD

\[\begin{equation*} \begin{aligned} & W_3^t = W_3^{t-1} - \eta \cdot \frac{\partial MSE}{\partial W_3} \\ & W_2^t = W_2^{t-1} - \eta \cdot \frac{\partial MSE}{\partial W_2} \\ & W_1^t = W_1^{t-1} - \eta \cdot \frac{\partial MSE}{\partial W_1} \\ \end{aligned} \end{equation*}\]

Сделаем шаг SGD для второго наблюдения. Делаем прямое распространение для второго наблюдения, напомним, что матрицы весов инициализированы нулями:

dobronet_forward

Делаем обратный проход.

Шаг 1:

\[\begin{equation*} \begin{aligned} & d = (\hat{y} - y) = -1 \\ & \frac{\partial MSE}{\partial W_3} = O_2^T \cdot d = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} \cdot (-1) = \begin{pmatrix} -0.5 \\ -0.5 \end{pmatrix} \\ \end{aligned} \end{equation*}\]

Шаг 2:

\[\begin{equation*} \begin{aligned} & d = d \cdot W_3^T * O_2 * (1 - O_2) = -1 \cdot (0, 0) \odot (0.5, 0.5) \odot (0.5, 0.5) = (0, 0) \\ & \frac{\partial MSE}{\partial W_2} = O_1^T \cdot d = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} \cdot (0, 0) = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \\ \end{aligned} \end{equation*}\]

Шаг 3:

\[\begin{equation*} \begin{aligned} & d = d \cdot W_2^T \odot O_1 \odot (1 - O_1) = (0, 0) \cdot \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \odot (0.5, 0.5) \odot (0.5, 0.5) = (0, 0) \\ & \frac{\partial MSE}{\partial W_1} = X^T \cdot d = \begin{pmatrix} 5 \\ 2 \end{pmatrix} \cdot (0, 0) = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \\ % & W_1^1 = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} - 1 \cdot \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \end{aligned} \end{equation*}\]

Делаем шаг градиентного спуска

\[\begin{equation*} \begin{aligned} & W_1^1 = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} - 1 \cdot \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix}\\ & W_2^1 = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} - 1 \cdot \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \\ & W_3^1 = \begin{pmatrix} 0 \\ 0 \end{pmatrix} - 1 \cdot \begin{pmatrix} -0.5 \\ -0.5 \end{pmatrix} = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} \end{aligned} \end{equation*}\]

Сделаем шаг SGD для первого наблюдения. Делаем прямое распространение для второго наблюдения, напомним, что матрицы весов инициализированы нулями:

dobronet_forward

Делаем обратный проход.

Шаг 1:

\[\begin{equation*} \begin{aligned} & d = (\hat{y} - y) = 0.5 \\ & \frac{\partial MSE}{\partial W_3} = O_2^T \cdot d = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} \cdot (0.5) = \begin{pmatrix} 0.25 \\ 0.25 \end{pmatrix} \\ \end{aligned} \end{equation*}\]

Шаг 2:

\[\begin{equation*} \begin{aligned} & d = d \cdot W_3^T \odot O_2 \odot (1 - O_2) = 0.5 \cdot (0.5, 0.5) \odot (0.5, 0.5) \odot (0.5, 0.5) = (1/16, 1/16) \\ & \frac{\partial MSE}{\partial W_2} = O_1^T \cdot d = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} \cdot (1/16, 1/16) = \begin{pmatrix} 1/32 & 1/32 \\ 1/32 & 1/32 \end{pmatrix} \\ \end{aligned} \end{equation*}\]

Шаг 3:

\[\begin{equation*} \begin{aligned} & d = d \cdot W_2^T \odot O_1 \odot (1 - O_1) = (1/16, 1/16) \cdot \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \odot (0.5, 0.5) \odot (0.5, 0.5) = (0, 0) \\ & \frac{\partial MSE}{\partial W_1} = X^T \cdot d = \begin{pmatrix} 1 \\ 1 \end{pmatrix} \cdot (0, 0) = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \\ \end{aligned} \end{equation*}\]

На этой задаче видно, как сигмоида способствует затуханию градиента. Её производная по абсолютной величине всегда принимает значения меньше \(1\). Из-за этого значение \(d\) от слоя к слою становится всё меньше и меньше. Чем ближе к началу нашей сети мы находимся, тем на меньшую величину шагают веса. Если сетка оказывается очень глубокой, такой эффект ломает её обучение. Его обычно называют \indef{параличом нейронной сети.} Именно из-за этого сигмоиду обычно не используют в глубоких архитектурах.

Делаем шаг градиентного спуска

\[\begin{equation*} \begin{aligned} & W_3^2 = \begin{pmatrix} 0.5 \\ 0.5 \end{pmatrix} - 1 \cdot \begin{pmatrix} 0.25 \\ 0.25 \end{pmatrix} = \begin{pmatrix} 0.25 \\ 0.25 \end{pmatrix} \\ & W_2^2 = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} - 1 \cdot \begin{pmatrix}1/32 & 1/32 \\ 1/32 & 1/32 \end{pmatrix} = \begin{pmatrix} -1/32 & -1/32 \\ -1/32 & -1/32 \end{pmatrix} \\ & W_1^1 = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} - 1 \cdot \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix} \end{aligned} \end{equation*}\]

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

Решение

Из-за того, что мы инициализировали веса нулями, слои поначалу учатся по очереди. Пока мы не сдвинем веса более поздних слоёв, веса более ранних слоёв не сдвинутся. Это замедляет обучение. Обратите внимание, что все веса меняются на одну и ту же величину в одном и том же направлении.

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