Retropropagación
La retroprogación (backpropagation o propagación hacia atrás) es un método de cálculo del gradiente.
El método emplea un ciclo propagación – adaptación de dos fases. Una vez que se ha aplicado un patrón a la entrada de la red como estímulo, este se propaga desde la primera capa a través de las capas siguientes de la red, hasta generar una salida. La señal de salida se compara con la salida deseada y se calcula una señal de error para cada una de las salidas.
Las salidas de error se propagan hacia atrás, partiendo de la capa de salida, hacia todas las neuronas de la capa oculta que contribuyen directamente a la salida. Sin embargo las neuronas de la capa oculta solo reciben una fracción de la señal total del error, basándose aproximadamente en la contribución relativa que haya aportado cada neurona a la salida original. Este proceso se repite, capa por capa, hasta que todas las neuronas de la red hayan recibido una señal de error que describa su contribución relativa al error total.
La importancia de este proceso consiste en que, a medida que se entrena la red, las neuronas de las capas intermedias se organizan a sí mismas de tal modo que las distintas neuronas aprenden a reconocer distintas características del espacio total de entrada. Después del entrenamiento, cuando se les presente un patrón arbitrario de entrada que contenga ruido o que esté incompleto, las neuronas de la capa oculta de la red responderán con una salida activa si la nueva entrada contiene un patrón que se asemeje a aquella característica que las neuronas individuales hayan aprendido a reconocer durante su entrenamiento.
Los pasos del método de retropropagación son:
- Hacer una operación de feedforward.
- Comparar la salida del modelo con la deseada.
- Calcular el error.
- Ejecutar la propagación hacia atrás para distribuir el error a cada uno de los pesos.
- Actualizar los pesos y obtener un modelo mejor.
- Continuar hasta obtener un modelo suficientemente bueno.
Ejemplo de cálculo de los errores con retropropagación
En el siguiente ejemplo se va a:
- Calcular el error de salida de la red.
- Calcular el término de error de la capa de salida.
- Usar retropropagación para calcular el término de error de la capa oculta.
- Calcular el cambio en los pesos (las deltas) que resulta de propagar los errores hacia atrás en la red.
import numpy as np
def sigmoid(x):
"""
Calculate sigmoid
"""
return 1 / (1 + np.exp(-x))
x = np.array([0.5, 0.1, -0.2])
target = 0.6
learnrate = 0.5
weights_input_hidden = np.array([[0.5, -0.6],
[0.1, -0.2],
[0.1, 0.7]])
weights_hidden_output = np.array([0.1, -0.3])
## Forward pass
hidden_layer_input = np.dot(x, weights_input_hidden)
hidden_layer_output = sigmoid(hidden_layer_input)
output_layer_in = np.dot(hidden_layer_output, weights_hidden_output)
output = sigmoid(output_layer_in)
## Backwards pass
## (1) Calculate output error
error = target - output
# (2) Calculate error term for output layer
output_error_term = error * output * (1 - output)
# (3) Calculate error term for hidden layer
hidden_error_term = np.dot(output_error_term, weights_hidden_output) * \
hidden_layer_output * (1 - hidden_layer_output)
# (4) Calculate change in weights for hidden layer to output layer
delta_w_h_o = learnrate * output_error_term * hidden_layer_output
# (5) Calculate change in weights for input layer to hidden layer
delta_w_i_h = learnrate * hidden_error_term * x[:, None]
print('Change in weights for hidden layer to output layer:')
print(delta_w_h_o)
print('Change in weights for input layer to hidden layer:')
print(delta_w_i_h)
Change in weights for hidden layer to output layer: [0.00804047 0.00555918] Change in weights for input layer to hidden layer: [[ 1.77005547e-04 -5.11178506e-04] [ 3.54011093e-05 -1.02235701e-04] [-7.08022187e-05 2.04471402e-04]]
Implementación de la retropropagación
El término error para la capa de salida es
$$
\delta_k = (y_k - \hat{y}_k) f'(a_k)
$$
y el término error de la capa oculta es
$$
\delta_j = \sum [w_{jk}\delta_k] f'(h_j)
$$
El algoritmo general para actualizar los pesos con retropropagación es el siguiente (consideramos una red simple con una capa oculta y una capa de salida):
- Establecer los incrementos de peso de cada capa a cero:
- Los pesos de la capa de entrada a la capa oculta $\Delta w_{ij} = 0$.
- Los pesos de la capa oculta a la capa de salida $\Delta W_{ij} = 0$.
- Para cada registro en los datos de entrenamiento:
- Avanzar a través de la red, calculando la salida $\hat{y}$.
- Calcular el gradiente de error en la unidad de salida, $\delta^0 = (y - \hat{y}) f'(z)$, donde $z=\sum_j W_j a_j$ es la entrada de la unidad de salida.
- Propagar los errores a la capa oculta $\delta_j^h = \delta^0 W_j f'(h_j)$.
- Actualizar los incrementos de peso:
- $\Delta W_j = \Delta W_j + \delta^0 a_j$
- $\Delta w_{ij} = \Delta w_{ij} + \delta_j^h a_i$ - Actualizar los pesos, donde $\eta$ es la tasa de aprendizaje y $m$ es el número de registros:
- $W_j = W_j + \eta \Delta W_j / m$
- $w_{ij} = w_{ij} + \eta \Delta w_{ij} / m$
- Repetir para $e$ etapas.
Ejemplo
En este ejemplo se va a:
- Implementar el avance.
- Implementar el algoritmo de retropropagación.
- Actualizar los pesos.
import numpy as np
from data_prep import features, targets, features_test, targets_test
np.random.seed(21)
def sigmoid(x):
"""
Calculate sigmoid
"""
return 1 / (1 + np.exp(-x))
# Hyperparameters
n_hidden = 2 # number of hidden units
epochs = 900
learnrate = 0.005
n_records, n_features = features.shape
last_loss = None
# Initialize weights
weights_input_hidden = np.random.normal(scale=1 / n_features ** .5,
size=(n_features, n_hidden))
weights_hidden_output = np.random.normal(scale=1 / n_features ** .5,
size=n_hidden)
for e in range(epochs):
del_w_input_hidden = np.zeros(weights_input_hidden.shape)
del_w_hidden_output = np.zeros(weights_hidden_output.shape)
for x, y in zip(features.values, targets):
## Forward pass ##
# TODO: Calculate the output
hidden_input = np.dot(x, weights_input_hidden)
hidden_output = sigmoid(hidden_input)
output = sigmoid(np.dot(hidden_output,
weights_hidden_output))
## Backward pass ##
# TODO: Calculate the network's prediction error
error = y - output
# TODO: Calculate error term for the output unit
output_error_term = error * output * (1 - output)
## propagate errors to hidden layer
# TODO: Calculate the hidden layer's contribution to the error
hidden_error = np.dot(output_error_term, weights_hidden_output)
# TODO: Calculate the error term for the hidden layer
hidden_error_term = hidden_error * hidden_output * (1 - hidden_output)
# TODO: Update the change in weights
del_w_hidden_output += output_error_term * hidden_output
del_w_input_hidden += hidden_error_term * x[:, None]
# TODO: Update weights
weights_input_hidden += learnrate * del_w_input_hidden / n_records
weights_hidden_output += learnrate * del_w_hidden_output / n_records
# Printing out the mean square error on the training set
if e % (epochs / 10) == 0:
hidden_output = sigmoid(np.dot(x, weights_input_hidden))
out = sigmoid(np.dot(hidden_output,
weights_hidden_output))
loss = np.mean((out - targets) ** 2)
if last_loss and last_loss < loss:
print("Train loss: ", loss, " WARNING - Loss Increasing")
else:
print("Train loss: ", loss)
last_loss = loss
# Calculate accuracy on test data
hidden = sigmoid(np.dot(features_test, weights_input_hidden))
out = sigmoid(np.dot(hidden, weights_hidden_output))
predictions = out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))