<jupyter_start><jupyter_text>Construcción de una red profundaPreviamente hemos entrenado una red neuronal de dos capas (con una capa escondida), Ahora vamos a cosntruir una red profunda, con múltiples capas escondidas.- En este taller, se van a implementar las funciones requeridas para construir una red neuronal profunda.- Luego se podrán utilizar estas funciones para construir una red neuronal profunda para clasificación de imagenes.**Luego de este taller usted va a haber aprendido a:**- Utilizar unidades no-lineales mediante una función como ReLU para mejorar el modelo- Construir una red neuronal profunda (con más de una capa escondida)- Implementar de manera práctica una red neuronal**Notación**:- Superíndice $[l]$ denota una cantidad asociada con la $l-ésima$ capa.     - Ejemplo: $a^{[l]}$ es la activación de la $l-ésima$ capa. $W^{[l]}$ y $b^{[l]}$ son los parámetros de la $l-ésima$ capa.- Superíndice $(i)$ denota una cantidad asociada con el $i-ésimo$ ejemplo.     - Ejemplo: $x^{(i)}$ es el $i-ésimo$ ejemplo de entrenamiento.- Subíndice $i$ denota la $i-ésima$ entrada de un vector.    - Ejemplo: $a^{[l]}_i$ denota la $i-ésima$ entrada de las activaciones de la $l-ésima$ capa.  1. PaquetesPrimero se deben importar todos los paquetes que se van a necesitar durante este taller.- [numpy](www.numpy.org) paquete básico para ciencias computacionales con Python.- [matplotlib](http://matplotlib.org) librería para graficar en Python.- dnn_utils provee distintas funciones que se van a usar durante el taller- testCases tiene los ejemplos de prueba para evaluar la implementacion de las funciones<jupyter_code>import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases_v4 import *
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward

%matplotlib inline
plt.rcParams['figure.figsize'] = (5.0, 4.0) # se fija el tamaño de los gráficos
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

%load_ext autoreload
%autoreload 2

np.random.seed(1)     # se utiliza para replicar las funciones aleatorias<jupyter_output>The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload<jupyter_text>2 - Resumen del tallerPara construir la red neuronal, primero se construirán funciones auxiliares que permitirán implementar una red neuronal de dos y de L capas. Cada función auxiliar se puede construir siguiendo las instrucciones: - Inicializar los parámetros para una red de 2 capas y para una red de $L$ capas.- Implementar la propagación hacia delante.     - Desarrolle la propagación LINEAL hacia delante de una capa, obteniendo $Z^{[l]}$, y luego la función de ACTIVACION, RELU o SIGMOIDE.     - Combine los pasos [LINEAL->ACTIVACION] en una sola función (hacia delante).     - Agrupe las funciones de propagación hacia delante [LINEAL->RELU] L-1 veces (para las capas 1 hasta L-1) y añada una [LINEAL->SIGMOIDE] al final (para la última capa $L$). De esta manera obtendrá la función L_model_forward.- Compute la pérdida.- Implemente la retro-propagación.    - Complete la parte LINEAL de la retro-propagación de una capa (el gradiente de la función de activación le va a ser proporcionado).     - Combine los pasos en una nueva función de retro-propagación [LINEAL->ACTIVACION].    - Agrupe la funciones de retro-propagación [LINEAL->RELU] L-1 veces y añada la correspondiente [LINEAL->SIGMOIDE] en una nueva función L_model_backward.- Por último, actualice los parámetros.**Anotación:** cada función de propagación hacia delante tiene su correspondiente función hacia atrás. Por ello, a cada paso hacia delante, se guardan en la caché algunos valores necesarios para calcular los gradientes.  3 - InicializaciónDesarrolle dos funciones auxiliares para inicializar los parámetros de su modelo. La primera función permitirá inicializar los parámetros para un modelo con dos capas. La segunda permitirá generalizar el proceso de inicialización para $L$ capas. 3.1 - Red neuronal con 2 capas**Ejercicio**: Crear e inicializar los parámetros para una red de 2 capas.**Instrucciones**:- La estructura del modelo es *LINEAL -> RELU -> LINEAL -> SIGMOIDE*. - Inicializar aleatoriamente los pesos. Puede utilizar la función `np.random.randn(dimensiones)*0.01` con las dimensiones correctas.- Inicialice los sesgos a cero. Puede utilizar la función `np.zeros(dimensiones)`.<jupyter_code># FUNCIÓN A CALIFICAR: initialize_parameters

def initialize_parameters(n_x, n_h, n_y):
    """
    Input:
    n_x: tamaño de la capa de entrada
    n_h: tamaño de la capa escondida
    n_y: tamaño de la capa de salida
    Output:
    parameters: diccionario python con los parametros parameters:
                    W1: matriz de pesos con dimensiones (n_h, n_x)
                    b1: matriz de sesgos con dimensiones (n_h, 1)
                    W2: matriz de pesos con dimensiones (n_y, n_h)
                    b2: matriz de sesgos con dimensiones (n_y, 1)
    """
    
    np.random.seed(1)
    
    ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 4 líneas de código)
    W1 = 
    b1 = 
    W2 = 
    b2 =
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    assert(W1.shape == (n_h, n_x))
    assert(b1.shape == (n_h, 1))
    assert(W2.shape == (n_y, n_h))
    assert(b2.shape == (n_y, 1))
    
    parameters = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}
    
    return parameters    
parameters = initialize_parameters(3,2,1)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:              **W1**      [[ 0.01624345 -0.00611756 -0.00528172] [-0.01072969  0.00865408 -0.02301539]]           **b1**    [[ 0.] [ 0.]]           **W2**     [[ 0.01744812 -0.00761207]]           **b2**      [[ 0.]]        3.2 - Red Neuronal con L capasLa inicialización de una red neuronal profunda es más compleja al haber más matrices de pesos y vectores de sesgo. Al completar `initialize_parameters_deep`, debe asegurarse que sus dimensiones sean coherentes al pasar de capa en capa. Recuerde que  $n^{[l]}$ es el número de unidades en la capa $l$. Entonces, e.g., si el tamaño de la entrada $X$ es $(12288, 209)$ (con $m=209$ ejemplos), se tiene que:                        **Dimensión de W**           **Dimensión de b**            **Activación**          **Dimensión de la activación**                       **Capa 1**           $(n^{[1]},12288)$           $(n^{[1]},1)$           $Z^{[1]} = W^{[1]}  X + b^{[1]} $                   $(n^{[1]},209)$                       **Capa 2**           $(n^{[2]}, n^{[1]})$            $(n^{[2]},1)$          $Z^{[2]} = W^{[2]} A^{[1]} + b^{[2]}$           $(n^{[2]}, 209)$                         $\vdots$           $\vdots$            $\vdots$            $\vdots$          $\vdots$                       **Capa L-1**           $(n^{[L-1]}, n^{[L-2]})$           $(n^{[L-1]}, 1)$           $Z^{[L-1]} =  W^{[L-1]} A^{[L-2]} + b^{[L-1]}$           $(n^{[L-1]}, 209)$                          **Capa L**           $(n^{[L]}, n^{[L-1]})$           $(n^{[L]}, 1)$          $Z^{[L]} =  W^{[L]} A^{[L-1]} + b^{[L]}$         $(n^{[L]}, 209)$       Recuerde que en python, el cálculo de $W X + b$ lleva a cabo la operación de broadcasting.Entonces, si:$$ W = \begin{bmatrix}    j  & k  & l\\    m  & n & o \\    p  & q & r \end{bmatrix}\;\;\; X = \begin{bmatrix}    a  & b  & c\\    d  & e & f \\    g  & h & i \end{bmatrix} \;\;\; b =\begin{bmatrix}    s  \\    t  \\    u\end{bmatrix}$$el resultado de $WX + b$ será:$$ WX + b = \begin{bmatrix}    (ja + kd + lg) + s  & (jb + ke + lh) + s  & (jc + kf + li)+ s\\    (ma + nd + og) + t & (mb + ne + oh) + t & (mc + nf + oi) + t\\    (pa + qd + rg) + u & (pb + qe + rh) + u & (pc + qf + ri)+ u\end{bmatrix} $$ **Ejercicio**: Implemente la inicialización de una red neuronal con L capas. **Instrucciones**:- La estructura del modelo es *[LINEAL -> RELU] $ \times$ (L-1) -> LINEAL -> SIGMOIDE*. Esto es, la red tiene $L-1$ capas utilizando una función de activación ReLU, seguido de una capa de salida con la función de activación Sigmoide.- Use una inicialización aleatoria para las matrices de pesos. Utilice `np.random.randn(shape) * 0.01`.- Use una inicialización de ceros para los sesgos. Utilice `np.zeros(shape)`.- El número de unidades en cada capa $n^{[l]}$, se guarda en la variable `layer_dims`. De esta manera, por ejemplo, las `layer_dims` para el taller de la semana pasada con "Un red neuronal sencilla" sería [2,4,1]: donde hay 2 entradas, una capa escondida con 4 unidades, y una capa de salida con una unidad. Por lo tanto, la forma de `W1` es de (4,2), la de `b1` (4,1), `W2` (1,4) y `b2` (1,1). Ahora se puede generalizar para $L$ capas! **Ayuda**:La implementación para $L=1$ sería de la siguiente manera:```python    if L == 1:        parameters["W" + str(L)] = np.random.randn(layer_dims[1], layer_dims[0]) * 0.01        parameters["b" + str(L)] = np.zeros((layer_dims[1], 1))```<jupyter_code># FUNCIÓN A CALIFICAR: initialize_parameters_deep

def initialize_parameters_deep(layer_dims):
    """
    Input:
    layer_dims: arreglo (lista) de python con las dimensiones de cada capa de la red
    Output:
    parameters: diccionario python con los parametros "W1", "b1", ..., "WL", "bL":
                    Wl: matriz de pesos (layer_dims[l], layer_dims[l-1])
                    bl: vector de sesgo (layer_dims[l], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layer_dims)            # número de capas de la red

    for l in range(1, L):
        ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
        parameters['W' + str(l)] = 
        parameters['b' + str(l)] = 
        ### TERMINE EL CÓDIGO AQUÍ ###
        
        assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l-1]))
        assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))

        
    return parameters
parameters = initialize_parameters_deep([5,4,3])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:              **W1**     [[ 0.01788628  0.0043651   0.00096497 -0.01863493 -0.00277388] [-0.00354759 -0.00082741 -0.00627001 -0.00043818 -0.00477218] [-0.01313865  0.00884622  0.00881318  0.01709573  0.00050034] [-0.00404677 -0.0054536  -0.01546477  0.00982367 -0.01101068]]           **b1**     [[ 0.] [ 0.] [ 0.] [ 0.]]           **W2**     [[-0.01185047 -0.0020565   0.01486148  0.00236716] [-0.01023785 -0.00712993  0.00625245 -0.00160513] [-0.00768836 -0.00230031  0.00745056  0.01976111]]           **b2**     [[ 0.] [ 0.] [ 0.]]       4 - Propagación hacia delante 4.1 - Lineal hacia delante Una vez inicializados los parámetros, debe implementar la propagación hacia delante. Va a empezar por implementar algunas funciones básicas para ser utilizadas más adelante en la implementación del modelo. Va a implementar 3 funciones:- LINEAL- LINEAL -> ACTIVACION donde la activación será ReLU o Sigmoide. - [LINEAL -> RELU] $\times$ (L-1) -> LINEAL -> SIGMOIDE (modelo completo)Esta implementación (vectorizada) de la propagación hacia delante calcula las siguientes ecuaciones:$$Z^{[l]} = W^{[l]}A^{[l-1]} +b^{[l]}$$donde $A^{[0]} = X$. **Ejercicio**: Construya la parte LINEAL de la propagación hacia delante.**Ayuda**:La representación matemática de esta implementación para una capa es $Z^{[l]} = W^{[l]}A^{[l-1]} +b^{[l]}$. Puede ser útil la función`np.dot()`. También, si las dimensiones no casan, puede investigar lo que ocurre llamando a `W.shape`.<jupyter_code># FUNCIÓN A CALIFICAR: linear_forward

def linear_forward(A, W, b):
    """
    Implemente la parte lineal para la propagación hacia delante de una capa.
    Input:
    A: las activaciones de la capa previa (o los datos de entrada): (tamaño de la capa previa, número de ejemplos)
    W: matriz de pesos, un arreglo numpy de dimensiones (tamaño de la capa actual, tamaño de la capa previa)
    b: vector de sesgo, un arreglo numpy de dimensiones (tamaño de la capa actual, 1)
    Output:
    Z: la entrada para la función de activación, también llamado parámetro de pre-activación 
    cache: diccionario python con "A", "W" y "b", almacenados para computar los pasos hacia atrás de manera eficiente
    """
    
    ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 1 línea de código)
    Z = 
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    assert(Z.shape == (W.shape[0], A.shape[1]))
    cache = (A, W, b)
    
    return Z, cache
A, W, b = linear_forward_test_case()

Z, linear_cache = linear_forward(A, W, b)
print("Z = " + str(Z))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:         **Z**      [[ 3.26295337 -1.23429987]]        4.2 - Activación-lineal hacia delanteEn este taller, vamos a utilizar dos funciones de activación:- **Sigmoide**: $\sigma(Z) = \sigma(W A + b) = \frac{1}{ 1 + e^{-(W A + b)}}$. Esta función `sigmoid` está dada, y devuelve 2 objetos: el valor de activación "`a`" y una "`cache`" que contiene "`Z`" (la cual se le pasa a la correspondiente función de retro-propagación). Para usarla basta con este comando: ``` pythonA, activation_cache = sigmoid(Z)```- **ReLU**: La fórmula matemática para ReLU es $A = RELU(Z) = max(0, Z)$. Esta función `relu` también está dada, y devuelve 2 objetos: el valor de activación "`a`" y una "`cache`" que contiene "`Z`" (la cual se le pasa a la correspondiente función de retro-propagación). Para usarla basta con este comando: ``` pythonA, activation_cache = relu(Z)``` Para mayor conveniencia, vamos a agrupar dos funciones (Lineal y Activacion) en una sola (LINEAL->ACTIVACION). Por lo tanto, va a implementar una función que da el paso LINEAL hacia delante seguido del paso de ACTIVACION hacia delante.**Ejercicio**: Implemente la propagación hacia delante de la capa *LINEAL->ACTIVACION*. La ecuación matemática es: $$A^{[l]} = g(Z^{[l]}) = g(W^{[l]}A^{[l-1]} +b^{[l]})$$ donde la activación "g" puede ser sigmoide() o relu(). Utilice linear_forward() y la función de activación correcta.<jupyter_code># FUNCIÓN A CALIFICAR: linear_activation_forward

def linear_activation_forward(A_prev, W, b, activation):
    """
    Implemente la propagación hacia delante para la capa LINEAL->ACTIVACION
    Input:
    A_prev: activaciones de la capa previa (o de los datos de entrada): (tamaño de la capa previa, número de ejemplos)
    W: matriz de pesos, un arreglo numpy de dimensiones (tamaño de la capa actual, tamaño de la capa previa)
    b: vector de sesgo, un arreglo numpy de dimensiones (tamaño de la capa actual, 1)
    activation: la activación a ser usada en la capa, guardada como una cadena de texto: "sigmoid" or "relu"
    Output:
    A: la salida de la función de activación, también llamada valor de post-activacion 
    cache: dicionario python con la "cache_lineal" y la "cache_activacion"
    """
    
    if activation == "sigmoid":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".
        ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
        Z, linear_cache = 
        A, activation_cache = 
        ### TERMINE EL CÓDIGO AQUÍ ###
    
    elif activation == "relu":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".
        ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
        Z, linear_cache = 
        A, activation_cache = 
        ### TERMINE EL CÓDIGO AQUÍ ###
    
    assert (A.shape == (W.shape[0], A_prev.shape[1]))
    cache = (linear_cache, activation_cache)

    return A, cache
A_prev, W, b = linear_activation_forward_test_case()

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "sigmoid")
print("Con sigmoide: A = " + str(A))

A, linear_activation_cache = linear_activation_forward(A_prev, W, b, activation = "relu")
print("Con ReLU: A = " + str(A))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:              **With sigmoid: A **      [[ 0.96890023  0.11013289]]          **With ReLU: A **      [[ 3.43896131  0.        ]]    **Nota**: En deep learning, la computación de "[LINEAL->ACTIVACION]" se cuenta como una sola capa de la red neuronal.  d) Modelo con L capas Con el fin de facilitar la implementación de la red neuronal de $L$ capas que queremos implementar, necesitamos una función que replique la propagación hacia delante (`linear_activation_forward`) con RELU, $L-1$ veces, seguida por la función (`linear_activation_forward`) SIGMOIDE.**Ejercicio**: Implemente la propagación hacia delante del modelo descrito anteriormente.**Instrucciones**: En el código abajo, la variable `AL` denota $A^{[L]} = \sigma(Z^{[L]}) = \sigma(W^{[L]} A^{[L-1]} + b^{[L]})$ (Esta es la estimación $\hat{Y}$.) **Ayuda**:- Use las funciones que ha programada arriba - Use un bucle for para replicar la [LINEAL->RELU] (L-1) veces- No olvide ir guardando las caches en la lista "caches". Para añadir un nuevo valor use `list.append(c)`.<jupyter_code># FUNCIÓN A CALIFICAR: L_model_forward

def L_model_forward(X, parameters):
    """
    Implemente la propagación hacia delante para calcular [LINEAL->RELU]*(L-1)->LINEAL->SIGMOIDE
    Input:
    X: datos de entrada, arreglo de tamaño (tamaño del input, número de ejemplos)
    parameters: salida de initialize_parameters_deep()
    Output:
    AL: último valor de post-activación
    caches: lista de caches con cada caché de linear_activation_forward() (hay L-1 cachés, indexadas de 0 a L-1)
    """

    caches = []
    A = X
    L = len(parameters) // 2                  # número de capas en la red neuronal
    
    # Implemente [LINEAL -> RELU]*(L-1). Añada "cache" a la lista de "caches".
    for l in range(1, L):
        A_prev = A 
        ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
        A, cache = 
                        
        ### TERMINE EL CÓDIGO AQUÍ ###
    
    # Implemente LINEAL -> SIGMOIDE. Añada "cache" a la lista de "caches".
    ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
    AL, cache =
    
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    assert(AL.shape == (1,X.shape[1]))
            
    return AL, caches
X, parameters = L_model_forward_test_case_2hidden()
AL, caches = L_model_forward(X, parameters)
print("AL = " + str(AL))
print("Longitud de la lista de caches = " + str(len(caches)))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:       **AL**      [[ 0.03921668  0.70498921  0.19734387  0.04728177]]          **Longitud de la lista de caches **      3     Muy bien, llegado a este punto ya tiene todo el proceso de propagación hacia delente completo, tomando el input X y obteniendo outputs del vector-fila $A^{[L]}$ con sus predicciones (a partir de lo cual puede calcular el coste o pérdida de sus predicciones). También se ha quedado con los valores intermedios en "caches".   5 - Función de pérdida o costeAhora va a implementar la propagación hacia delate y hacia atrás. Debe computar el coste con el fin de verificar si su modelo en verdad está aprendiendo.**Ejercicio**: Calcule el coste por entropía-cruzada $J$, en base a la siguiente fórmula: $$-\frac{1}{m} \sum\limits_{i = 1}^{m} (y^{(i)}\log\left(a^{[L] (i)}\right) + (1-y^{(i)})\log\left(1- a^{[L](i)}\right)) $$<jupyter_code># FUNCIÓN A CALIFICAR: compute_cost

def compute_cost(AL, Y):
    """
    Implemente la función de coste por entrpía cruzada.
    Input:
    AL: vector con las probabilidades para las etiquetas de predicción, dimensiones (1, número de ejemplos)
    Y: vector de etiquetas observadas, de dimensión (1, número de ejemplos)
    Output:
    coste: coste de entropía cruzada
    """
    
    m = Y.shape[1]

    # Compute la pérdida de AL e Y.
    ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 1 línea de código)
    cost = 
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    cost = np.squeeze(cost)      # Para asegurar que la dimensión de se coste es correcta (e.g. [[17]] se torna en 17).
    assert(cost.shape == ())
    
    return cost
Y, AL = compute_cost_test_case()

print("coste = " + str(compute_cost(AL, Y)))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:        **coste**      0.41493159961539694       6 - Retro-propagaciónComo en el caso de la propagación hacia delante, va a implementar funciones auxiliares para la retro-propagación. Recuerde que la retro-propagación permite calcular el gradiente de la función de coste con respecto a los parámetros. Análogamente a la propgación hacia delante, la retro-propagación se va a construir en tres pasos:- LINEAL hacia atrás- LINEAL -> ACTIVACION hacia atrás, donde ACTIVACION calcula la derivada de la función de activación (ReLU o sigmoide)- [LINEAL -> RELU] $\times$ (L-1) -> LINEAL -> SIGMOIDE hacia atrás (modelo completo)  6.1 - Linear hacia atrásPara la capa $l$, la parte lineal es: $Z^{[l]} = W^{[l]} A^{[l-1]} + b^{[l]}$ (seguida por una activación).Suponga que ya ha calculado la derivada $dZ^{[l]} = \frac{\partial \mathcal{L} }{\partial Z^{[l]}}$. Ahora quiere obtener $(dW^{[l]}, db^{[l]} dA^{[l-1]})$.Los tres outputs $(dW^{[l]}, db^{[l]}, dA^{[l]})$ son computados utilizando el input $dZ^{[l]}$. Estas son la fórmulas que necesita:$$ dW^{[l]} = \frac{\partial \mathcal{L} }{\partial W^{[l]}} = \frac{1}{m} dZ^{[l]} A^{[l-1] T} $$$$ db^{[l]} = \frac{\partial \mathcal{L} }{\partial b^{[l]}} = \frac{1}{m} \sum_{i = 1}^{m} dZ^{[l](i)}$$$$ dA^{[l-1]} = \frac{\partial \mathcal{L} }{\partial A^{[l-1]}} = W^{[l] T} dZ^{[l]} $$ **Ejercicio**: Utilice las tres fórmulas (arriba) para implementar linear_backward().<jupyter_code># FUNCIÓN A CALIFICAR: linear_backward

def linear_backward(dZ, cache):
    """
    Implemente la parte lineal de la retro-propagación para una sola capa [l]
    Input:
    dZ: Gradiente del coste con respecto al output lineal de la capa actual
    cache: conjunto de velores (A_prev, W, b) provenientes de la propagación hacia delante en la capa actual
    Output:
    dA_prev: Gradiente del coste con respecto a la activación (de la capa previa: l-1), del mismo tamaño como A_prev
    dW: Gradiente del coste con respecto a W (de la capa actual: l), del mismo tamaño que W
    db: Gradiente del coste con respecto a b (de la capa actual: l), del mismo tamaño que b
    """
    A_prev, W, b = cache
    m = A_prev.shape[1]

    ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 3 líneas de código)
    dW = 
    db = 
    dA_prev = 
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    assert (dA_prev.shape == A_prev.shape)
    assert (dW.shape == W.shape)
    assert (db.shape == b.shape)
    
    return dA_prev, dW, db
dZ, linear_cache = linear_backward_test_case()

dA_prev, dW, db = linear_backward(dZ, linear_cache)
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))<jupyter_output><empty_output><jupyter_text>**Salida esperada**:       **dA_prev**      [[ 0.51822968 -0.19517421] [-0.40506361  0.15255393] [ 2.37496825 -0.89445391]]                    **dW**          [[-0.10076895  1.40685096  1.64992505]]                      **db**          [[ 0.50629448]]             6.2 - Activación-lineal hacia atrásA continuación, va a crear una función que combine la dos funciones auxiliares: **`linear_backward`** y el paso hacia atrás de la activación **`linear_activation_backward`**. Para implementar `linear_activation_backward`, se provee de dos funciones hacia atrás:- **`sigmoid_backward`**: Implementa retro-propagación para la unidad SIGMOIDE, tal que ```pythondZ = sigmoid_backward(dA, activation_cache)```- **`relu_backward`**: Implementa retro-propagación para la unidad RELU, de forma que ```pythondZ = relu_backward(dA, activation_cache)```Si $g(.)$ es la función de activación, entonces `sigmoid_backward` y `relu_backward` calculan $$dZ^{[l]} = dA^{[l]} * g'(Z^{[l]}) \tag{11}$$.  **Ejercicio**: Implemente la retro-propagación para la capa *LINEAL->ACTIVACION*.<jupyter_code># FUNCIÓN A CALIFICAR: linear_activation_backward

def linear_activation_backward(dA, cache, activation):
    """
    Implemente la retro-propagación par la capa LINEAL->ACTIVACION .
    Input:
    dA: gradiente post-activacion para la capa actual l 
    cache: conjunto de valores (linear_cache, activation_cache) que se guardan para calcular la retro-propagación de manera eficiente
    activacion: la activación a ser usada en esta capa, guardada como un arreglo de texto: "sigmoid" o "relu"
    Output:
    dA_prev: gradiente del coste con respecto a la activación (de la capa previa l-1), de las mismas dimensiones que A_prev
    dW: gradiente del coste con respecto a W (capa actual l), mismas dimensiones que W
    db: gradiente del coste con respecto a b (capa actual l), mismas dimensiones que b
    """
    linear_cache, activation_cache = cache
    
    if activation == "relu":
        ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
        dZ = 
        dA_prev, dW, db = 
        ### TERMINE EL CÓDIGO AQUÍ ###
        
    elif activation == "sigmoid":
        ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 2 líneas de código)
        dZ = 
        dA_prev, dW, db = 
        ### TERMINE EL CÓDIGO AQUÍ ###
    
    return dA_prev, dW, db
dAL, linear_activation_cache = linear_activation_backward_test_case()

dA_prev, dW, db = linear_activation_backward(dAL, linear_activation_cache, activation = "sigmoid")
print ("sigmoid:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db) + "\n")

dA_prev, dW, db = linear_activation_backward(dAL, linear_activation_cache, activation = "relu")
print ("relu:")
print ("dA_prev = "+ str(dA_prev))
print ("dW = " + str(dW))
print ("db = " + str(db))<jupyter_output><empty_output><jupyter_text>**Salida esperada con el sigmoide:**       dA_prev             [[ 0.11017994  0.01105339] [ 0.09466817  0.00949723] [-0.05743092 -0.00576154]]                dW              [[ 0.10266786  0.09778551 -0.01968084]]                db              [[-0.05729622]]      **Salida esperada con RELU:**       dA_prev              [[ 0.44090989  0.        ] [ 0.37883606  0.        ] [-0.2298228   0.        ]]                dW              [[ 0.44513824  0.37371418 -0.10478989]]                db              [[-0.20837892]]       6.3 - Retro-propagación en L capas Ahora va a implementar la función de retro-propagación para toda la red neuronal. Recuerde que cuando implementó la función `L_model_forward`, en cada iteración, guardó una caché que contenía (X,W,b, z). En el paso e la retro-propagación, esas variables están a disposición para calcular los gradientes. Por lo tanto, en la función `L_model_backward`, se puede iterar sobre todas las capas escondidas hacia atrás, empezando de la última capa $L$. En cada paso hacia atrás, se utilizan los valores de la caché en la capa $l$, para retro-propagar sobre la capa $l$. ** Inicializando la retro-propagación**:Para retro-propagar sobre esta red, sabemos que la salida es, $A^{[L]} = \sigma(Z^{[L]})$. Por lo tanto, necesita calcular `dAL` $= \frac{\partial \mathcal{L}}{\partial A^{[L]}}$.Para hacerlo, utilice la fórmula:```pythondAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))  derivada del coste con respecto a AL```Puede utilizar el gradiente de post-activacion `dAL` para seguir yendo hacia atrás. Este gradiente se le puede pasar la función hacia atrás LINEAL->SIGMOIDE implementada antes (que utilizarán los valores guaradados por la función L_model_forward). Luego se debe utilizar un bucle `for` para iterar sobre todas las otras capas utilizando la función hacia atrás LINEAL->RELU. Se debe guardar cada dA, dW, y db en el diccionario grads. Para hacerlo, utilice la fórmula : $$grads["dW" + str(l)] = dW^{[l]}$$Por ejemplo, para $l=3$ se guardaría $dW^{[l]}$ en `grads["dW3"]`.**Ejercicio**: Implemente la retro-propagación para el modelo *[LINEAL->RELU] $\times$ (L-1) -> LINEAL -> SIGMOIDE*.<jupyter_code># FUNCIÓN A CALIFICAR: L_model_backward

def L_model_backward(AL, Y, caches):
    """
    Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
    Input:
    AL: vector con las probabilidades, salida para propagación hacia delante L_model_forward()
    Y: vector de clases/etiquetas observadas, de dimensión (1, número de ejemplos)
    caches: lista de caches, donde se tiene
                - cada cache de linear_activation_forward() con "relu" (i.e., caches[l]; l = 0...L-2)
                - el cache de linear_activation_forward() con "sigmoid" (i.e, [L-1])
    Output:
    grads: Un diccionario con los gradientes
             grads["dA" + str(l)] = ... 
             grads["dW" + str(l)] = ...
             grads["db" + str(l)] = ... 
    """
    grads = {}
    L = len(caches) # número de capas
    m = AL.shape[1]
    Y = Y.reshape(AL.shape) # Y es del mismo tamaño que AL
    
    # Initializacion de la retro-propagación
    ### EMPIEZE EL CÓDIGO AQUÍ ### (1 línea de código)
    dAL =  # derivada del coste con respecto a AL
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    # Gradientes para la ultima capa L (SIGMOIDE -> LINEAL). Inputs: "dAL, current_cache". Outputs: "grads["dAL-1"], grads["dWL"], grads["dbL"]
    ### EMPIEZE EL CÓDIGO AQUÍ ### (2 líneas de código)
    current_cache = 
    grads["dA" + str(L-1)], grads["dW" + str(L)], grads["db" + str(L)] = 
    ### TERMINE EL CÓDIGO AQUÍ ###
    
    #Loop from l=L-2 to l=0
    for l in reversed(range(L-1)):
        # Gradientes para la l-ésima capa: gradientes (RELU -> LINEAL).
        # Inputs: "grads["dA" + str(l + 1)], current_cache". Outputs: "grads["dA" + str(l)] , grads["dW" + str(l + 1)] , grads["db" + str(l + 1)] 
        ### EMPIEZE EL CÓDIGO AQUÍ ### (5 líneas de código)
        current_cache = 
        dA_prev_temp, dW_temp, db_temp = 
        grads["dA" + str(l)] = 
        grads["dW" + str(l + 1)] = 
        grads["db" + str(l + 1)] = 
        ### TERMINE EL CÓDIGO AQUÍ ###

    return grads
AL, Y_assess, caches = L_model_backward_test_case()
grads = L_model_backward(AL, Y_assess, caches)
print_grads(grads)<jupyter_output><empty_output><jupyter_text>**Salida esperada**         dW1              [[ 0.41010002  0.07807203  0.13798444  0.10502167] [ 0.          0.          0.          0.        ] [ 0.05283652  0.01005865  0.01777766  0.0135308 ]]                db1              [[-0.22007063] [ 0.        ] [-0.02835349]]            dA1              [[ 0.12913162 -0.44014127] [-0.14175655  0.48317296] [ 0.01663708 -0.05670698]]       6.4 - Actualización de parámetrosEn esta sección, se actualizan los parámetros del modelo, utilizando el método de Descenso en la Dirección del Gradiente (GD): $$ W^{[l]} = W^{[l]} - \alpha \text{ } dW^{[l]} \tag{16}$$$$ b^{[l]} = b^{[l]} - \alpha \text{ } db^{[l]} \tag{17}$$donde $\alpha$ es la tasa de aprendizaje. Tras la actualización de los parametros, se deben guardar en los parametros del diccionario.  **Ejercicio**: Implemente `update_parameters()` para actualizar los parámtros usando GD.**Instrucciones**:Actualice los parámetros utilizando GD en cada $W^{[l]}$ y $b^{[l]}$; $l = 1, 2, ..., L$.<jupyter_code># FUNCIÓN A CALIFICAR: update_parameters

def update_parameters(parameters, grads, learning_rate):
    """
    Actualice los parametros utilizando GD
    Input: 
    parameters: diccionario python con los parametros 
    grads: diccionario python con los gradientes, resultado de L_model_backward
    Output:
    parameters: diccionario python con los parametros actualizados 
                  parameters["W" + str(l)] = ... 
                  parameters["b" + str(l)] = ...
    """
    
    L = len(parameters) // 2 # numero de capas en la red neuronal

    # Regla de actualización para cada parámetro (utilice on bucle for).
    ### EMPIEZE EL CÓDIGO AQUÍ ### (≈ 3 líneas de código)
    for l in range(L):
        parameters["W" + str(l+1)] = 
        parameters["b" + str(l+1)] = 
    ### TERMINE EL CÓDIGO AQUÍ ###
    return parameters
parameters, grads = update_parameters_test_case()
parameters = update_parameters(parameters, grads, 0.1)

print ("W1 = "+ str(parameters["W1"]))
print ("b1 = "+ str(parameters["b1"]))
print ("W2 = "+ str(parameters["W2"]))
print ("b2 = "+ str(parameters["b2"]))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13221884498480244, 'fraction_numerical': 0.036226040568202965, 'mean_word_length': 3.2015897836851708, 'pattern_counts': {'":': 9, '<': 44, '<?xml version=': 0, '>': 86, 'https://': 0, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '435202', 'license': 'Apache-2.0', 'n_tokens_mistral': 12136, 'n_tokens_neox': 11453, 'n_words': 3703, 'path': '.ipynb_checkpoints/Taller4_RedesNeuronalesProfundas-checkpoint.ipynb', 'repo_name': 'AndresJejen/MachineLearningSpanishTalleres', 'size': '32242'}