Пошаговая реализация на Python

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

Вы можете скачать Jupyter Notebook здесь.

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

Вернуться к предыдущему сообщению

Вернуться к первому сообщению

5.2.2 Обратное распространение в ИНС — часть 2

Примечание. Архитектура нейронной сети такая же, как и в предыдущем посте, т. е. 4 слоя с 5, 3, 5 и 4 узлами.

Во-первых, функция активации для первого скрытого слоя — функция Sigmoid
Во-вторых, функция активации для второго скрытого слоя и выходного слоя — это функция Softmax.
В-третьих, используемая функция потерь — это категориальные кросс-энтропийные потери, CE
В-четвертых, мы будем использовать SGD с оптимизатором импульса со скоростью обучения = 0,01 и импульс = 0,9

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

Теперь давайте посмотрим на шаги, которые мы будем делать здесь

Step 1 - A forward feed like we did in the previous post
Step 2 - Initializing SGD with Momentum Optimizer
Step 3 - Entering the training loop
      Step 3.1 - A forward feed to see loss before training
      Step 3.2 - Using Backpropagation to calculate gradients
      Step 3.3 - Using SGD with Momentum Optimizer to update weights
                 and biases
Step 4 - A forward feed to verify that the loss has been reduced
         and to see how close predicted values are to true values

Давайте сделаем это на Python.

Шаг 1 – прямая лента, как мы делали в предыдущем посте

import numpy as np                          # importing NumPy
np.random.seed(42)
input_nodes = 5                             # nodes in each layer
hidden_1_nodes = 3
hidden_2_nodes = 5
output_nodes = 4

x = np.random.randint(1, 100, size = (input_nodes, 1)) / 100
x                                       # Inputs
y = np.array([[0], [1], [0], [0]])
y                                       # Outputs

def sig(x):                                     # Sigmoid  
    return 1/(1 + np.exp(-x))
def sig_dash(x):                                # Sigmoid derivative  
    return sig(x) * (1 - sig(x))
def softmax(x):                                 # Softmax
    return np.exp(x) / np.sum(np.exp(x))
def softmax_dash(x):                            # Softmax derivative
    
    I = np.eye(x.shape[0])
    
    return softmax(x) * (I - softmax(x).T)
def cross_E(y_true, y_pred):                    # CE
    return -np.sum(y_true * np.log(y_pred + 10**-100))
def cross_E_grad(y_true, y_pred):               # CE derivative
    return -y_true/(y_pred + 10**-100)

w1 = np.random.random(size = (hidden_1_nodes, input_nodes))    # w1
b1 = np.zeros(shape = (hidden_1_nodes, 1))                     # b1
w2 = np.random.random(size = (hidden_2_nodes, hidden_1_nodes)) # w2
b2 = np.zeros(shape = (hidden_2_nodes, 1))                     # b2
w3 = np.random.random(size = (output_nodes, hidden_2_nodes))   # w3
b3 = np.zeros(shape = (output_nodes, 1))                       # b3

in_hidden_1 = w1.dot(x) + b1                     # forward feed
out_hidden_1 = sig(in_hidden_1)
in_hidden_2 = w2.dot(out_hidden_1) + b2
out_hidden_2 = softmax(in_hidden_2)
in_output_layer = w3.dot(out_hidden_2) + b3
y_hat = softmax(in_output_layer)
y_hat                                            # y_hat
y                                                # y
cross_E(y, y_hat)                                # CE loss

Шаг 2. Инициализация SGD с помощью Momentum Optimizer

learning_rate = 0.01                   # learning rate
momentum = 0.9                         # momentum
update_w1 = np.zeros(w1.shape)         # Initializing updates with 0
update_b1 = np.zeros(b1.shape)
update_w2 = np.zeros(w2.shape)
update_b2 = np.zeros(b2.shape)
update_w3 = np.zeros(w3.shape)
update_b3 = np.zeros(b3.shape)

Шаг 3. Вход в цикл обучения

epochs = 1000

Шаг 3.1 — Предварительная подача, чтобы увидеть потери перед тренировкой

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

for epoch in range(epochs):
#----------------------Forward Propagation--------------------------
    
    in_hidden_1 = w1.dot(x) + b1
    out_hidden_1 = sig(in_hidden_1)
    in_hidden_2 = w2.dot(out_hidden_1) + b2
    out_hidden_2 = softmax(in_hidden_2)
    in_output_layer = w3.dot(out_hidden_2) + b3
    y_hat = softmax(in_output_layer)
    
    loss = cross_E(y, y_hat)
    print(f'loss before training is {loss} -- epoch number {epoch +
                                                                1}')
    print('\n')

Шаг 3.2. Расчет градиентов с помощью обратного распространения

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

Мы начнем с того, что поговорим о весе w3₁₁.
Нам нужно вычислить

Затем мы можем обновить его с помощью SGD с оптимизатором Momentum или каждого веса в w3, как мы сделали в предыдущем посте в матричной форме.

Мы знаем это,

и,

Итак, мы можем написать

Мы также знаем, что,

Это отличается от предыдущего поста.

Итак, мы можем написать

Мы также знаем, что,

Таким образом, эти термины равны 0 (ноль)

У нас есть

or,

Таким образом, мы можем найти каждый термин в grad_w3

Матрица будет очень большая, но я покажу вам ее в уменьшенном виде.

Теперь вам интересно, что такое «error_upto_softmax». Если вы помните правило № 2 игры «Прыжок назад», мы должны взять сумму по оси = 0, а затем изменить ее форму на (-1, 1), если градиенты после прыжка не имеют формы (-1, 1)

Потому что после пересечения линии Softmax наши градиенты не имеют формы (-1, 1), что связано с определением производной Softma или якобиана.

Итак, вот «error_upto_softmax».

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

Посмотрим, но в несколько неясной форме. После эфира имеем вот это

После взятия суммы у нас будет что-то вроде этого

И, изменив его на (-1, 1), мы получим это

Эти измененные градиенты в форме (-1, 1) будут иметь скалярное произведение с транспонированием O_H2, а член в синем поле на самом деле является большим членом, который мы вычисляем для w3₁₁, т. е.

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

Правила игры

Rule 1 - If we cross a line, then we have to include the gradients
Rule 2 - After a jump, if the shape of gradients is not (-1, 1)
         then we will take sum along axis = 0 and then reshape it to
         (-1, 1)

Мы начинаем с истинного значения ‘y’

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

Отпрыгнув назад, пересекаем линию softmax. Из-за якобиана функции Softmax мы возьмем сумму по оси = 0, а затем изменим ее форму на (-1, 1) после трансляции.

Теперь мы достигли весов w3 и смещения b3. Таким образом, градиенты до сих пор будут использоваться для обновления b3.

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

#------------Gradient Calculations via Back Propagation-------------
    error_upto_softmax = np.sum(cross_E_grad(y, y_hat) *
           softmax_dash(in_output_layer), axis = 0).reshape((-1, 1))
                                               # error upto softmax
    grad_w3 = error_upto_softmax .dot( out_hidden_2.T )     # w3
    
    grad_b3 = error_upto_softmax                            # b3

Теперь, если мы прыгнем назад, мы пересечем веса w3. Мы суммируем все градиенты до этого момента в форме (-1, 1) после трансляции градиентов до I_OL с весами w3.

Снова прыгнув назад, мы пересекли линию Softmax. Мы снова воспользуемся Правилом номер 2 и сохраним градиенты в форме (-1, 1) в «error_upto_softmax_H2».

Поскольку мы достигли весов w2 и смещений b2, градиенты до сих пор будут использоваться для обновления b2.

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

    error_grad_upto_H2 = np.sum(error_upto_softmax * w3, axis = 0)
                                                   .reshape((-1, 1))
                                               # error grad upto H2
    
    error_upto_softmax_H2 = np.sum(error_grad_upto_H2 *
               softmax_dash(in_hidden_2), axis = 0).reshape((-1, 1))
                                            # error upto softmax H2
    grad_w2 = error_upto_softmax_H2 .dot( out_hidden_1.T ) # grad w2
    grad_b2 = error_upto_softmax_H2                        # grad b2

Теперь, если мы прыгнем назад, мы пересечем веса w2. Мы просуммируем все градиенты до этого момента в форме (-1, 1) после трансляции градиентов до I_H2 с весами w2.

Теперь, если мы прыгнем назад, мы пересечем сигмовидную линию, поэтому градиенты будут иметь сигмовидную производную.

Теперь мы достигли весов w1 и смещений b1. Градиенты до сих пор будут использоваться для обновления b1.

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

    error_grad_upto_H1 = np.sum(error_upto_softmax_H2 * w2, axis =
                                                0) .reshape((-1, 1))
                                                # error grad upto H1
    grad_w1 = error_grad_upto_H1 * sig_dash(in_hidden_1) .dot( x.T )
                                                          # grad w1
    grad_b1 = error_grad_upto_H1 * sig_dash(in_hidden_1)  # grad b1

Шаг 3.3. Использование SGD с оптимизатором импульса для обновления весов и смещений

    #----------Updating weights and biases via SGD Momentum---------
    update_w1 = - learning_rate * grad_w1 + momentum * update_w1
    w1 += update_w1                                  # w1
    
    update_b1 = - learning_rate * grad_b1 + momentum * update_b1
    b1 += update_b1                                  # b1
    
    update_w2 = - learning_rate * grad_w2 + momentum * update_w2
    w2 += update_w2                                  # w2
    
    update_b2 = - learning_rate * grad_b2 + momentum * update_b2
    b2 += update_b2                                  # b2
    
    update_w3 = - learning_rate * grad_w3 + momentum * update_w3
    w3 += update_w3                                  # w3
    
    update_b3 = - learning_rate * grad_b3 + momentum * update_b3
    b3 += update_b3                                  # b3

Цикл обучения будет выполняться 1000 раз.

Это небольшой скриншот после тренировки.

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

in_hidden_1 = w1.dot(x) + b1                      # forward feed
out_hidden_1 = sig(in_hidden_1)
in_hidden_2 = w2.dot(out_hidden_1) + b2
out_hidden_2 = softmax(in_hidden_2)
in_output_layer = w3.dot(out_hidden_2) + b3
y_hat = softmax(in_output_layer)
y_hat                                             # predicted values
y                                                 # true values
cross_E(y, y_hat)                                 # loss

На этом обратное распространение заканчивается. Я надеюсь, что теперь Backpropagation не так уж и сложна для вас.

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

Если вам понравился этот пост, подпишитесь на мой канал на YouTube neuralthreads и присоединяйтесь ко мне на Reddit.

В ближайшее время я буду загружать новые интерактивные видео на канал YouTube. И я буду рад помочь вам с любыми сомнениями на Reddit.

Большое спасибо за вашу поддержку и отзывы.

Если вам понравился этот курс, вы можете поддержать меня на

Это много значило бы для меня.

Перейти к следующему сообщению — 5.3 Регуляризация L1 и L2.