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

В этом посте мы увидим, как применить обратное распространение для обучения нейронной сети с несколькими входами и несколькими выходами. Это эквивалентно функциональному API Keras.

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

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

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

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

5.8 Несколько входов и несколько выходов

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

Мы видим, что сначала у нас есть первый входной слой. Затем 2 параллельных скрытых слоя. Затем мы объединили выходные данные двух скрытых слоев со вторым входным слоем, который мы назовем «Объединенный слой». Затем третий скрытый слой. И, наконец, 2 выходных слоя.

Несколько вещей,

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

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

Теперь давайте посмотрим на шаги

Step 1 - A forward feed to calculate loss
Step 2 - Initializing SGD Optimizer
Step 3 - Entering training loop
         Step 3.1 - Forward feed to calculate losses
         Step 3.2 - Calculate gradients using Backpropagation
         Step 3.3 - Using SGD Optimizer to update weights and biases
Step 4 - A forward feed to verify that loss has been reduced and to 
         see how close predicted values are to true values

Шаг 1 – предварительная подача для расчета убытка

import numpy as np                          # importing NumPy
np.random.seed(42)
input_1_nodes = 5                             # nodes in each layer
input_2_nodes = 3
hidden_1_nodes = 4
hidden_2_nodes = 3
concatenated_nodes = input_2_nodes + hidden_1_nodes + hidden_2_nodes
hidden_3_nodes = 4
output_1_nodes = 5
output_2_nodes = 4

x1 = np.random.randint(1, 100, size = (input_1_nodes, 1)) / 100
x1                                       # Input 1
x2 = np.random.randint(1, 100, size = (input_2_nodes, 1)) / 100
x2                                       # Input 2
y1 = np.array([[1], [1], [0], [0], [1]])
y1                                       # Output 1
y2 = np.array([[0], [1], [0], [0]])
y2                                       # Output 2

def relu(x, leak = 0):                          # ReLU
    return np.where(x <= 0, leak * x, x)
def relu_dash(x, leak = 0):                     # ReLU derivative
    return np.where(x <= 0, leak, 1)
def sig(x):                                     # Sigmoid
    return 1/(1 + np.exp(-x))
def sig_dash(x):                                # Sogmoid 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 B_cross_E(y_true, y_pred):                  # BCE
    return -np.mean(y_true * np.log(y_pred + 10**-100) + (1 -
                      y_true) * np.log(1 - y_pred + 10**-100))
def B_cross_E_grad(y_true, y_pred):             # BCE derivative
    
    N = y_true.shape[0]
    
    return -(y_true/(y_pred + 10**-100) - (1 - y_true)/(1 - y_pred +
                                                        10**-100))/N
def mae(y_true, y_pred):                        # MAE
    return np.mean(abs(y_true - y_pred))
def mae_grad(y_true, y_pred):                   # MAE derivative
    
    N = y_true.shape[0]
    
    return -((y_true - y_pred) / (abs(y_true - y_pred) +
                                            10**-100))/N

w1 = np.random.random(size = (hidden_1_nodes, input_1_nodes))  # w1
b1 = np.zeros(shape = (hidden_1_nodes, 1))                     # b1
w2 = np.random.random(size = (hidden_2_nodes, input_1_nodes))  # w2
b2 = np.zeros(shape = (hidden_2_nodes, 1))                     # b2
w3 = np.random.random(size = (hidden_3_nodes, concatenated_nodes))
b3 = np.zeros(shape = (hidden_3_nodes, 1))                  # w3, b3
w4 = np.random.random(size = (output_1_nodes, hidden_3_nodes)) # w4
b4 = np.zeros(shape = (output_1_nodes, 1))                     # b4
w5 = np.random.random(size = (output_2_nodes, hidden_3_nodes)) # w5
b5 = np.zeros(shape = (output_2_nodes, 1))                     # b5

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

in_hidden_1 = w1.dot(x1) + b1
out_hidden_1 = relu(in_hidden_1)
print('out_hidden_1')
print(out_hidden_1, out_hidden_1.shape)
print('\n')

in_hidden_2 = w2.dot(x1) + b2
out_hidden_2 = relu(in_hidden_2)
print('out_hidden_2')
print(out_hidden_2, out_hidden_2.shape)
print('\n')

concatenated_layer = np.concatenate((x2, out_hidden_1, out_hidden_2))
print('concatenated_layer')
print(concatenated_layer, concatenated_layer.shape)
print('\n')

in_hidden_3 = w3.dot(concatenated_layer) + b3
out_hidden_3 = relu(in_hidden_3)
print('out_hidden_3')
print(out_hidden_3, out_hidden_3.shape)
print('\n')

in_output_layer_1 = w4.dot(out_hidden_3) + b4
y1_hat = sig(in_output_layer_1)
print('y1_hat')
print(y1_hat, y1_hat.shape)
print('\n')

in_output_layer_2 = w5.dot(out_hidden_3) + b5
y2_hat = softmax(in_output_layer_2)
print('y2_hat')
print(y2_hat, y2_hat.shape)
print('\n')

y1                                             # y_hat 1
y2                                             # y_hat 2
B_cross_E(y1, y1_hat) + mae(y2, y2_hat)        # total loss
B_cross_E(y1, y1_hat)                          # loss 1
mae(y2, y2_hat)                                # loss 2

Шаг 2 — Инициализация оптимизатора SGD

learning_rate = 0.01

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

epochs = 1000

Шаг 3.1. Предварительная подача для расчета потерь перед обучением

for epoch in range(epochs):
    
    #------------------------Forward Propagation--------------------
    
    in_hidden_1 = w1.dot(x1) + b1
    out_hidden_1 = relu(in_hidden_1)
    in_hidden_2 = w2.dot(x1) + b2
    out_hidden_2 = relu(in_hidden_2)
    concatenated_layer = np.concatenate((x2, out_hidden_1,
                                            out_hidden_2))
    in_hidden_3 = w3.dot(concatenated_layer) + b3
    out_hidden_3 = relu(in_hidden_3)
    in_output_layer_1 = w4.dot(out_hidden_3) + b4
    y1_hat = sig(in_output_layer_1)
    in_output_layer_2 = w5.dot(out_hidden_3) + b5
    y2_hat = softmax(in_output_layer_2)
    
    loss = B_cross_E(y1, y1_hat) + mae(y2, y2_hat)
    print(f'loss before training is {loss}')
    print(f'output_1 loss is {B_cross_E(y1, y1_hat)}')
    print(f'output_2 loss is {mae(y2, y2_hat)}-- epoch number {epoch
                                                              + 1}')
    print('\n')

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

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

Начнем с потерь.

Мы вычисляем градиенты для (w4, b4) и (w5, b5) отдельно, потому что (w4, b4) и (w5, b5) не зависят друг от друга.

    #-----------Gradient Calculations via Back Propagation----------
    grad_w4 = B_cross_E_grad(y1, y1_hat) *                # grad_w4
                  sig_dash(in_output_layer_1) .dot( out_hidden_3.T )
    
    grad_b4 = B_cross_E_grad(y1, y1_hat) *                # grad_b4
                sig_dash(in_output_layer_1)
    
    #-------------------------------------------
    
    error_upto_softmax = np.sum(mae_grad(y2, y2_hat) *
         softmax_dash(in_output_layer_2), axis = 0).reshape((-1, 1))
    
    grad_w5 = error_upto_softmax .dot( out_hidden_3.T )   # grad_w5
    
    grad_b5 = error_upto_softmax                          # grad_b5

Теперь, когда мы вернемся к w4 и w5, мы добавим градиенты после изменения их формы на (-1, 1) и продолжим дальше.

    error_grad_H3 = ( np.sum(B_cross_E_grad(y1, y1_hat) *
        sig_dash(in_output_layer_1) * w4, axis = 0).reshape((-1, 1))
                               +
        np.sum(error_upto_softmax * w5, axis = 0).reshape((-1, 1)) )
    
    grad_w3 = error_grad_H3 * relu_dash(in_hidden_3) .dot(
                                  concatenated_layer.T )   # grad_w3
    
    grad_b3 = error_grad_H3 * relu_dash(in_hidden_3)       # grad_b3

Теперь, как рассчитать градиенты для (w1, b1) и (w2, b2).

Если вы заметили, сейчас мы не имеем ничего общего с ‘x2’. Почему? Потому что «x2» подключен к скрытому слою 3 через w3 и b3 между конкатенированным слоем и скрытым слоем 3.

Форма w3 (hidden_3_nodes, concatenated_nodes), т. е. (4, 10)

Матрица весов w3 есть

В этих весах есть связи между компонентами конкатенированного слоя и входом скрытого 3 слоя.

Веса в синем поле выше соединяют вход 2, то есть «x2», со скрытым 3 «I_H3». Форма (hidden_3_nodes, input_2_nodes) или (4, 3)

Веса в синем поле выше соединяют вывод скрытого 1, т. е. «O_H1», со скрытым 3 «I_H3». Форма (hidden_3_nodes, hidden_1_nodes) или (4, 4)

Веса в синем поле выше соединяют выходные данные скрытого 2, т. е. «O_H2», со скрытым 3 «I_H3». Форма (hidden_3_nodes, hidden_2_nodes) или (4, 3)

Чтобы вычислить градиенты для (w1, b1) и (w2, b2), мы будем использовать только вторую и третью подматрицы синего прямоугольника.

    error_grad_H1 = np.sum(error_grad_H3 * relu_dash(in_hidden_3) *
              w3[:, input_2_nodes: input_2_nodes + hidden_1_nodes], 
                           axis = 0).reshape((-1, 1))
    
    grad_w1 = error_grad_H1 * relu_dash(in_hidden_1) .dot( x1.T )
                                                          # grad_w1
    grad_b1 = error_grad_H1 * relu_dash(in_hidden_1)      # grad_b1
    
    #-------------------------------------------
    
    error_grad_H2 = np.sum(error_grad_H3 * relu_dash(in_hidden_3) *
                              w3[:, input_2_nodes + hidden_1_nodes:                                             
                   input_2_nodes + hidden_1_nodes + hidden_2_nodes], 
                                          axis = 0).reshape((-1, 1))
    
    grad_w2 = error_grad_H2 * relu_dash(in_hidden_2) .dot( x1.T )
                                                          # grad_w2
    grad_b2 = error_grad_H2 * relu_dash(in_hidden_2)      # grad_b2

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

    #-------------Updating weights and biases with SGD--------------
    update_w1 = - learning_rate * grad_w1
    w1 += update_w1                                             # w1
    
    update_b1 = - learning_rate * grad_b1
    b1 += update_b1                                             # b1
    
    update_w2 = - learning_rate * grad_w2
    w2 += update_w2                                             # w2
    
    update_b2 = - learning_rate * grad_b2
    b2 += update_b2                                             # b2
    
    update_w3 = - learning_rate * grad_w3
    w3 += update_w3                                             # w3
    
    update_b3 = - learning_rate * grad_b3
    b3 += update_b3                                             # b3
    
    update_w4 = - learning_rate * grad_w4
    w4 += update_w4                                             # w4
    
    update_b4 = - learning_rate * grad_b4
    b4 += update_b4                                             # b4
    
    update_w5 = - learning_rate * grad_w5
    w5 += update_w5                                             # w5
    
    update_b5 = - learning_rate * grad_b5
    b5 += update_b5                                             # b5

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

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

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

in_hidden_1 = w1.dot(x1) + b1                     # forward feed
out_hidden_1 = relu(in_hidden_1) 
in_hidden_2 = w2.dot(x1) + b2
out_hidden_2 = relu(in_hidden_2)
concatenated_layer = np.concatenate((x2, out_hidden_1, out_hidden_2))
in_hidden_3 = w3.dot(concatenated_layer) + b3
out_hidden_3 = relu(in_hidden_3)
in_ouput_layer_1 = w4.dot(out_hidden_3) + b4
y1_hat = sig(in_ouput_layer_1)
in_ouput_layer_2 = w5.dot(out_hidden_3) + b5
y2_hat = softmax(in_ouput_layer_2)
y1_hat                                   # first predicted output
y1                                       # first true output
y2_hat                                   # second predicted output
y2                                       # second true output
B_cross_E(y1, y1_hat) + mae(y2, y2_hat)  # total loss
B_cross_E(y1, y1_hat)                    # BCE loss
mae(y2, y2_hat)                          # MAE loss

Надеюсь, теперь вы понимаете, как работает такая нейронная сеть.

В следующем посте, который будет последним в этой главе и о простых ИНС, мы подгоним нейронную сеть к набору данных качества белого вина UCI. И после этого мы начнем главу 6 — CNN.

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

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

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

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

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

Перейти к следующему сообщению — набор данных 5.9 UCI White Wine Quality, точность и матрица путаницы.