Pensemos en una red neuronal de alimentación hacia adelante (feedforward) de cualquier número de capas.
Sean:
Para calcular el gradiente, es necesario calcular primero la propagación hacia adelante.
La fórmula general vectorizada para calcular el valor de la capa siguiente se puede expresar como: \begin{align*} A^{(l+1)} = g(A^{(l)} W^{(l)} + B^{(l)}) \end{align*}
import numpy as np
def sigma(z):
return 1 / (1 + np.exp(-z))
# Propagación hacia adelante
## Matrices
W0 = np.array([[-5.12, 3.38],
[-5.1, 3.37]])
B0 = np.array([[1.72, -5.25]])
W1 = np.array([[6.6],
[6.5]])
B1 = np.array([[-3.2]])
## Activaciones
A0 = X = np.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
A1 = sigma(np.dot(A0, W0) + B0)
A2 = sigma(np.dot(A1, W1) + B1)
print(A1)
[[8.48128836e-01 5.22012569e-03] [3.29263948e-02 1.32388874e-01] [3.22954647e-02 1.33541723e-01] [2.03426978e-04 8.17574476e-01]]
print(A2)
[[0.91920404] [0.10696175] [0.10728019] [0.89240796]]
Es verdad, para esta red, usando redondeo, ya tenemos la respuesta perfecta, pero la función de error aún nos reporta un valor y podemos calcular su gradiente con respecto a los pesos de la red.
vectorizando: \begin{align} J(\Theta) = - \frac{1}{m} \left[ \sum_{k=1}^{s_L} Y^T \log(A^{(L)}) + (1 - Y)^T \log(1 - A^{(L)}) \right] \end{align}
# Valores deseados
Y = np.array([[1],
[0],
[0],
[1]])
m = X.shape[0]
# Error
e = - np.sum(np.dot(Y.T, np.log(A2)) + np.dot((1 - Y).T, np.log(1 - A2))) / m
print("Error = ", e)
Error = 0.10617250539997253
Para calcular el gradiente con respecto a los pesos es necesario ir hacia atrás:
Delta_2 = Y - A2
print(Delta_2)
[[ 0.08079596] [-0.10696175] [-0.10728019] [ 0.10759204]]
Observa que el sesgo no aparece explícitamente porque no hay un error en su valor de activación, sin embargo su efecto está presente en el valor de $Z^{(l-1)}$.
Para la función sigmoide: \begin{align} g'(Z^{(l-1)}) &= A^{(l-1)} \circ (1-A^{(l-1)}) \end{align}
gp_1 = A1 * (1 - A1)
Delta_1 = np.dot(Delta_2, W1.T) * gp_1
# Error de cada neurona en la capa 1 para cada ejemplar
print(Delta_1)
[[ 0.0686864 0.00272716] [-0.02247896 -0.07985801] [-0.02212829 -0.08068588] [ 0.00014443 0.10430531]]
# Gradiente para los pesos que conectan las capas 1 y 2
G_W1 = - np.dot(A1.T, Delta_2) / m
print(G_W1)
[[-0.01539019] [-0.01497484]]
# Gradiente para el sesgo, pensemos en que A = 1
# Esto produce una suma sobre las columnas
G_B1 = - np.sum(Delta_2, axis=0, keepdims=True) / m
print(G_B1)
[[0.00646349]]
# Gradiente para los pesos que conectan las capas 0 y 1
G_W0 = - np.dot(A0.T, Delta_1) / m
print(G_W0)
[[ 0.00549597 -0.00590486] [ 0.00558363 -0.00611183]]
# Gradiente para el sesgo sobre la capa 1
G_B0 = - np.sum(Delta_1, axis=0, keepdims=True) / m
print(G_B0)
[[-0.00605589 0.01337785]]
Para saber si implementamos correctamente el cálculo del gradiente realizaremos un cálculo alternativo mediante pequeñas perturbaciones a cada peso de la red. Si los valores de ambos cálculos son semejantes, entonces podemos confiar en nuestra implementación.
params = [np.copy(W0), np.copy(B0), np.copy(W1), np.copy(B1)]
def forward():
global params
# Mismas entradas A0
A1 = sigma(np.dot(A0, params[0]) + params[1])
A2 = sigma(np.dot(A1, params[2]) + params[3])
return A2
g_params = [None, None, None, None]
epsilon = 0.0001
for ind in range(len(params)):
param = params[ind]
g_param = np.zeros(param.shape)
for i in range(param.shape[0]):
for j in range(param.shape[1]):
temp = param[i,j]
param[i,j] = temp - epsilon
A2 = forward()
e_min = - np.sum(np.dot(Y.T, np.log(A2)) + np.dot((1 - Y).T, np.log(1 - A2))) / m
param[i,j] = temp + epsilon
A2 = forward()
e_max = - np.sum(np.dot(Y.T, np.log(A2)) + np.dot((1 - Y).T, np.log(1 - A2))) / m
g_param[i,j] = (e_max - e_min) / (2 * epsilon)
param[i,j] = temp
g_params[ind] = g_param
for g_p in g_params:
print(g_p)
[[ 0.00549597 -0.00590486] [ 0.00558363 -0.00611183]] [[-0.00605589 0.01337785]] [[-0.01539019] [-0.01497484]] [[0.00646349]]
print(G_W0)
print(G_B0)
print(G_W1)
print(G_B1)
[[ 0.00549597 -0.00590486] [ 0.00558363 -0.00611183]] [[-0.00605589 0.01337785]] [[-0.01539019] [-0.01497484]] [[0.00646349]]