¿Qué es el algoritmo de backpropagation y cómo se implementa?
El algoritmo de backpropagation juega un papel crucial en el entrenamiento de una red neuronal. Permite ajustar los pesos de la red mediante la propagación del error desde la salida hacia las capas anteriores. En términos sencillos, se parte del error obtenido en la predicción final y se distribuye hacia atrás, ajustando los pesos para optimizar la precisión del modelo.
Implementación de backpropagation en Colab
Cálculo de las deltas en la última capa:
Se inicia calculando las deltas de la última capa, delta_z3, utilizando la derivada de la función de pérdida multiplicada por la derivada de la función de activación.
Para ajustar los pesos, se utiliza el producto punto (np.dot). Recuerda que para aplicar el producto punto, las dimensiones deben coincidir, a menudo se utiliza la traspuesta para solucionarlo.
Las deltas de las capas intermedias se calculan utilizando las derivadas parciales y el producto punto con los pesos transpuestos de la capa siguiente.
¿Cómo se actualizan los pesos con gradient descent?
El algoritmo gradient descent se utiliza para actualizar los pesos y minimizar el error. Mediante este enfoque iterativo, se ajustan los parámetros de acuerdo con la dirección opuesta del gradiente calculado.
Actualización de los pesos
Aplicación del descenso de gradiente:
Ajusta los pesos restando la tasa de aprendizaje multiplicada por las deltas calculadas.
params['w3']-= learning_rate * params['delta_w3']
Ajuste de las bias:
Las bias se ajustan de manera similar, tomando en cuenta que a menudo se utiliza la media para reducir el resultado a una sola dimensión.
¿Cómo encapsular todo en una función de entrenamiento?
Encapsular el proceso en una función permite realizar el entrenamiento de forma organizada y reutilizable. La función acepta como parámetros los datos de inicio, la tasa de aprendizaje, los parámetros iniciales y una bandera que indica si se está en modo de entrenamiento.
Creación de la función train
Definición de la función:
deftrain(x_data, learning_rate, params, training=True):# Código de procesamiento hacia adelante y backpropagation output = forward(x_data, params)if training: backpropagation(output, params, learning_rate)return output
Parámetros personalizables:
La función recibe parámetros que permiten ajustar el comportamiento, como learning_rate y training, para adaptarse a diferentes escenarios.
Al implementar estos conceptos, podrás crear un modelo de red neuronal más eficiente. Aunque es un proceso meticuloso y técnico, dominarlo te permitirá desarrollar algoritmos avanzados y personalizados. ¡Sigue explorando y mejorando tus habilidades en aprendizaje profundo!
Backpropagation es solo una forma de llamarle al descenso del gradiente. Básicamente es derivar. Si no lo entiendes tienes que repasar calculo diferencial y álgebra lineal.
Backpropagation = derivadas + algebra lineal
No exactamente, Backpropagation es el procedimiento que te permite saber que tanto influye una neurona en el error final que presenta una predicción (aca recordar que la predicción se calcula usando los pesos).
Ahora bien, El descenso del gradiante es el procedimiento que modifica cada uno de los pesos, dependiendo de que tanto influyo una neurona en el error final.
Uno te dice quien fue el responsable del error(Backpropagation), y el otro aplica los correctivos necesarios(Descenso del gradainte).
Hace tiempo que estudie eso y ya no me acuerdo bien qué onda, pero tenía entendido que parte del procedimiento de backpropagation es el descenso del gradiente. Ya me dejaste con la duda, voy a tener que repasarlo de nuevo 💪
En Backpropagation para realizar el calculo de dZ3 utiliza sigmoid de params[A3] que es sigmoid de params[Z3] ¿eso no estaría mal? en vez de ello seria calcular la derivada para
params[Z3] ¿no?
Profesor estás enfocando la respuesta por donde no es, claramente estamos utilizando la derivada de la función de activación, en este caso la derivada sigmoid, pero otra cosa es dónde evalúas esa derivada, el cual es un valor, no una función, y no tiene sentido que evalúes la derivada de la función en el valor de salida de la derivada, debe ser en el valor de entrada, el cual sería Z3, y no A3. Puede que estemos equivocados en este análisis, pero lo que sí estoy seguro es que la respuesta que estás dando no responde a la pregunta, tampoco en el video que sugieres. Ahora lo mismo aplica para relu, y bueno, puede que te esté dando porque al final pues la salida de la relu es prácticamente igual a la entrada, pero pues, no se. Realmente matemáticamente no entiendo el sentido de lo que estás haciendo. Creo que debes dar una respuesta mucho más de fondo.
Creo que hay un error cuando se calculan las deltas, especificamente en las derivadas de las funciones de activacion.
Creo que no deberian calcularse con A3, A2 y A1 como entrada sino con Z3, Z2 y Z1. Esto porque la entrada a las funciones de activacion son las Zs no las As
es decir, no se debe utilizar
sigmoid(params['A3'], True), relu(params['A2'],True) y relu(params['A1'],True)
sino:
sigmoid(params['Z3'], True), relu(params['Z2'],True) y relu(params['Z1'],True)
Me dicen si estoy equivocado por favor
Yo realicé los ajustes al modelo, e incluí los Zs a las funciones de activación, y no los As, como lo indica el video, y el error que obtuve fue 6 veces menor al del profesor, la convergencia más rápida y no se generaron puntos de divergencia, que creo que se habían presentado precisamente por el error en el cálculo.
le comparto el colab
https://colab.research.google.com/drive/19MGAy6vj0aCzm74h0-nGugip0h_0g3gZ?usp=sharing
La corrección es la siguiente:
Se debe usar la salida de las neuronas, es decir las activaciones A, no los Z. De hecho, a mi el error final me salió 0.0008892517506923521, mientras que a Luis (usando el ejemplo de no tomar en cuenta las activaciones) le salió 0.0021579051894923076, cerca de 2.5 veces más alto el error final que con activación. Con esto de forma practica se puede ver que la activación es crucial en el calculo e influye directamente en la precisión
Sería bueno q se parara a explicar mejor la parte donde usa (np.mean(params['dW2'], axis =o, keepdims = True) quedé en las nubes, me hizo falta más contexto en esta parte.
¿Por qué cambiamos la función de activación en backpropagation?
a cual te refieres... a las derivadas de la función?
Durante el entrenamiento, en las capas internas está utilizando la función de activación RELU, ya que esta es más rápida en converger habitualmente. Sin embargo, en la salida final, buscamos tener un sí o no, o un 1 o 0 y este resultado se aproxima mejor con la función sigmoide. El profesor creo ha utilizado esta forma también para mostrarnos que se pueden utilizar dos funciones de activación en la misma red neuronal.
Bias Backpropagation
Hola! Hay algo que no me termina de cerrar, al momento de calcular las derivadas parciales que se utilizaran en el gradient descent, se define a delta como 'dZ3' lo que es igual a :(derivada de la funcion de coste * derivada de la funcion de activacion de la ultima capa)
Aca viene mi duda, el gradient descent toma los siguientes dos parametros:
++derivada de la funcion de coste con respecto a w(pesos) ++: este parametro lo definimos luego de 'dZ3' como 'dW3', hasta ahi entiendo.
++derivada de la funcion de coste con respecto a b(bias)++ : este parametro no entiendo como esta definido, por que se utiliza 'dW3' ? tengo entendido que lo que necesitamos es la multiplicacion de la derivada de la funcion de coste por la derivada de la funcion de activacion en la ultima capa
Segun lo que entiendo de las clases anteriores e informacion extra, b3 deberia quedar definido como:
np.mean(params['dZ3'], axis=0, keepdims=True) * lr
Perdon que la pregunta sea tan extensa, quiero ser lo mas claro posible con mi inquietud, creo que si no encuentro la respuesta esta noche no voy a poder dormir (?. Saludos!
Me corrijo, me falto agregar la parte de la resta en la definicion de b3, quedaria asi:
params['b3'] = params['b3'] - np.mean(params[‘dZ3’], axis=0, keepdims=True) * lr
Forward Propagation
Se realiza la propagación hacia adelante a través de las capas de la red neuronal, calculando las activaciones en cada capa.
Backpropagation
Si estamos en modo de entrenamiento (training=True), se realiza la retropropagación para calcular los gradientes de la función de pérdida con respecto a los parámetros (pesos y sesgos).
Gradient Descent
Se actualizan los pesos y sesgos utilizando el descenso del gradiente.
Considero que hay un error, en esta parte del código:
Consulta, por qué al calcular la derivada de los W's, se usa la transpuesta en vez de un reshape?
es mas facil, la transpuesta trae la estructura que necesitamos para la multiplicación de matrices
Hola compañeros ,tengo una pregunta. Se supone que "dZ3" representa la variación de la funcicón de coste respeto a las entradas de la ultima capa. Dicho termino expresado mediante la regla de la cadena es dCoste/dA3 * dA3/dZ3.
Mi duda es con el factor dA3/dZ3 , que es la derivada de la función de activación en relación a Z3. Como el dominio de esa derivada sigue siendo respecto a la misma entrada. En el caso de los delta Z.
¿En vez de F_activación( params ['An'],True) , no debería ser F_activación( params ['Zn'],True)?
No acabo de entender. Porque le pasamos a la funcion sigmoide un valor que es un array?
function segmoid:
def sigmoid(x, derivate=False):ifderivate: y =1/(1+np.exp(-x))return sp.diff(y,x)else:return1/(1+np.exp(-x))
Gracias
Les comparte mi código para realizar una red neuronal completa desde cero:
import numpy as np
np.random.seed(42)#-----Aclassto represent the layer of a NeuralNetwork, the atributes are the basic of a layer:# ---- number of neurons in the layer, activatio function, weights and biases of the layer
classLayer(): def __init__(self, neurons, n_inputs, activation): self.neurons= neurons
self.x_inputs=None self.activation= activation
self.weights= np.random.rand(neurons, n_inputs) self.biases= np.random.rand(neurons) self._values= np.zeros(neurons) def weighted_sum(self, x_inputs):return x_inputs.dot(self.weights.T)+ self.biases def activation_function(self, weights_sum):try:if self.activation=='sigmoid':return1/(1+ np.exp(-weights_sum)) elif self.activation=='tanh':return(np.exp(weights_sum)- np.exp(-weights_sum))/(np.exp(weights_sum)+ np.exp(-weights_sum)) elif self.activation=='relu':return np.maximum(0, weights_sum) elif self.activation=='softmax': exps = np.exp(weights_sum - np.max(weights_sum, axis=1, keepdims=True)) # numerical stability
return exps / np.sum(exps, axis=1, keepdims=True) except Exceptionase:print(f'Error: {e}')print(f'Activation: {self.activation}') def forward_propagation(self, x_inputs): #Propagationof the inputs through the layer
self.x_inputs= x_inputs
weights_sum = self.weighted_sum(self.x_inputs) self._values= self.activation_function(weights_sum) def values(self): #Return the output values of the layer
return self._values def softmax_derivate(self, output):return output *(1- output) def weights_sum_derivate(self):print(f'jjfjfjf {self.x_inputs}')return self.x_inputs.T#-----Aclassto represent the NeuralNetwork, the arquitecture of how it works
classNeuralNetwork(): def __init__(self, x_inputs, y_train, epochs=1000): self._layers=[] # list of all the layers
self._output=[] self.x_inputs= x_inputs
self.y_train= np.array(y_train) self.epochs= epochs
def add_layer(self, layer): # Add a layer to the neural network
self._layers.append(layer) def layers(self):return self._layers def propagate_network(self, inputs): # Propagationof the inputs through all the newtork
for i, layer inenumerate(self._layers):if i ==0: layer.forward_propagation(x_inputs=inputs)else: layer.forward_propagation(x_inputs=self._layers[i-1].values()) def output(self): # Get the output of the NeuralNetwork last_layer =len(self._layers)-1 self._output= self._layers[last_layer].values()return self._output def loss_function(self): cross_entroy =-1* np.sum(self.y_train* np.log(self._output+1e-9), axis=1) cross_entroy = np.mean(cross_entroy)return cross_entroy
def cross_entropy_derivate(self):return- self.y_train/ self._output def backpropgation(self): # Backpropagationof the error through all the network
last_layer =len(self._layers)-1 output = self.output() learning_rate =0.001 delta = self.cross_entropy_derivate()for i inrange(last_layer,-1,-1):#print(f'--------------LAYER {i}-----------------') #Derivativeof the activation functionif self._layers[i].activation=='relu': values = self._layers[i].values() de_activation =(values >0).astype(float)return de_activation
elif self._layers[i].activation=='softmax': de_activation = self._layers[i].softmax_derivate(output) # Choose the inputs of the layer
input_to_layer = self.x_inputsif i ==0else self._layers[i-1].values() # Calculate the delta
#delta = delta * de_activation
delta = self._output- self.y_train delta_weights = np.dot(delta.T, input_to_layer)#print(f'Weights: {self._layers[i].weights}') #GradientDescent self._layers[i].weights= self._layers[i].weights-(learning_rate*delta_weights) self._layers[i].biases= self._layers[i].biases-(learning_rate * np.sum(delta, axis=0))#print(f'Weights New: {self._layers[i].weights}')#Backpropagation(next layer) #delta =Wᵗ ⋅ δ
delta = np.dot(delta, self._layers[i].weights) def train(self): # Loop to train the neural network, # the goal is to minimize the loss function by updating the weights and biases of the network
for i inrange(self.epochs): self.propagate_network(self.x_inputs) self.backpropgation() #if i %1000==0: # print(f'Loss: {self.loss_function()}') def predict(self, x_test): # if weights and biases are trained, we can predict the output of the neural network
self.propagate_network(x_test) values =list(self.output()[0]) value_predicted =max(values) predicted_index = values.index(value_predicted)return value_predicted, predicted_index
```import<u>numpy</u>as<u>np</u><u>np</u>.<u>random</u>.seed(42)\#-----Aclassto represent the layer of a NeuralNetwork, the atributes are the basic of a layer:# ---- number of neurons in the layer, activatio function, weights and biases of the layer*class*<u>Layer</u>():*def* \_\_init\_\_(*self*,*neurons*,*n\_inputs*,*activation*):*self*.neurons=*neurons**self*.x\_inputs =None *self*.activation=*activation**self*.weights=<u>np</u>.<u>random</u>.rand(*neurons*,*n\_inputs*)*self*.biases=<u>np</u>.<u>random</u>.rand(*neurons*)*self*.\_values =<u>np</u>.zeros(*neurons*)*def* weighted\_sum(*self*,*x\_inputs*):return*x\_inputs*.dot(*self*.weights.T)+*self*.biases*def* activation\_function(*self*,*weights\_sum*):try:if*self*.activation=='sigmoid':return1/(1+<u>np</u>.exp(-*weights\_sum*)) elif *self*.activation=='tanh':return(<u>np</u>.exp(*weights\_sum*)-<u>np</u>.exp(-*weights\_sum*))/(<u>np</u>.exp(*weights\_sum*)+<u>np</u>.exp(-*weights\_sum*)) elif *self*.activation=='relu':return<u>np</u>.maximum(0,*weights\_sum*) elif *self*.activation=='softmax': exps =<u>np</u>.exp(*weights\_sum*-<u>np</u>.max(*weights\_sum*,*axis*=1,*keepdims*=True)) # numerical stability return exps /<u>np</u>.sum(exps,*axis*=1,*keepdims*=True) except <u>Exception</u>ase:print(f'Error: {e}')print(f'Activation: {*self*.activation}')*def* forward\_propagation(*self*,*x\_inputs*): #Propagationof the inputs through the layer *self*.x\_inputs =*x\_inputs* weights\_sum =*self*.weighted\_sum(*self*.x\_inputs)*self*.\_values =*self*.activation\_function(weights\_sum)*def*values(*self*): #Return the output values of the layer return*self*.\_values *def* softmax\_derivate(*self*,*output*):return*output* \*(1-*output*)*def* weights\_sum\_derivate(*self*):print(f'jjfjfjf {*self*.x\_inputs}')return*self*.x\_inputs.T \#-----Aclassto represent the NeuralNetwork, the arquitecture of how it works*class*<u>NeuralNetwork</u>():*def* \_\_init\_\_(*self*,*x\_inputs*,*y\_train*,*epochs*=1000):*self*.\_layers = \[] # list of all the layers *self*.\_output = \[]*self*.x\_inputs =*x\_inputs**self*.y\_train =<u>np</u>.array(*y\_train*)*self*.epochs=*epochs**def* add\_layer(*self*,*layer*): # Add a layer to the neural network *self*.\_layers.append(*layer*)*def*layers(*self*):return*self*.\_layers *def* propagate\_network(*self*,*inputs*): # Propagationof the inputs through all the newtork for i, layer in<u>enumerate</u>(*self*.\_layers):if i ==0: layer.forward\_propagation(*x\_inputs*=*inputs*)else: layer.forward\_propagation(*x\_inputs*=*self*.\_layers\[i-1].values())*def*output(*self*): # Get the output of the NeuralNetwork last\_layer =len(*self*.\_layers)-1*self*.\_output =*self*.\_layers\[last\_layer].values()return*self*.\_output *def* loss\_function(*self*): cross\_entroy =-1 \*<u>np</u>.sum(*self*.y\_train \*<u>np</u>.log(*self*.\_output +1e-9),*axis*=1) cross\_entroy =<u>np</u>.mean(cross\_entroy)return cross\_entroy *def* cross\_entropy\_derivate(*self*):return-*self*.y\_train /*self*.\_output *def*backpropgation(*self*): # Backpropagationof the error through all the network last\_layer =len(*self*.\_layers)-1 output =*self*.output() learning\_rate =0.001 delta =*self*.cross\_entropy\_derivate()for i in<u>range</u>(last\_layer,-1,-1):#print(f'--------------LAYER {i}-----------------') #Derivativeof the activation functionif*self*.\_layers\[i].activation=='relu': values =*self*.\_layers\[i].values() de\_activation =(values >0).astype(<u>float</u>)return de\_activation elif *self*.\_layers\[i].activation=='softmax': de\_activation =*self*.\_layers\[i].softmax\_derivate(output) # Choose the inputs of the layer input\_to\_layer =*self*.x\_inputs if i ==0else*self*.\_layers\[i-1].values() # Calculate the delta #delta = delta \* de\_activation delta =*self*.\_output -*self*.y\_train delta\_weights =<u>np</u>.dot(delta.T, input\_to\_layer)#print(f'Weights: {self.\_layers\[i].weights}') #GradientDescent *self*.\_layers\[i].weights=*self*.\_layers\[i].weights-(learning\_rate\*delta\_weights)*self*.\_layers\[i].biases=*self*.\_layers\[i].biases-(learning\_rate \*<u>np</u>.sum(delta,*axis*=0))#print(f'Weights New: {self.\_layers\[i].weights}')#Backpropagation(next layer) #delta =Wᵗ ⋅ δ delta =<u>np</u>.dot(delta,*self*.\_layers\[i].weights)*def*train(*self*): # Loop to train the neural network, # the goal is to minimize the loss function by updating the weights and biases of the network for i in<u>range</u>(*self*.epochs):*self*.propagate\_network(*self*.x\_inputs)*self*.backpropgation() #if i %1000==0: # print(f'Loss: {self.loss\_function()}')*def*predict(*self*,*x\_test*): # if weights and biases are trained, we can predict the output of the neural network *self*.propagate\_network(*x\_test*) values =<u>list</u>(*self*.output()\[0]) value\_predicted =max(values) predicted\_index = values.index(value\_predicted)return value\_predicted, predicted\_index