Пошаговая реализация на 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.
Большое спасибо за вашу поддержку и отзывы.
Если вам понравился этот курс, вы можете поддержать меня на
Это много значило бы для меня.