Creaci贸n de una red neuronal desde cero en Python: A帽adiendo capas ocultas

    Si es absolutamente principiante en las redes neuronales, primero debe leer la Parte 1 de esta serie. Una vez que se sienta c贸modo con los conceptos explicados en ese art铆culo, puede regresar y continuar con este art铆culo.

    Introducci贸n

    En el art铆culo anterior, comenzamos nuestra discusi贸n sobre redes neuronales artificiales; vimos c贸mo crear una red neuronal simple con una entrada y una capa de salida, desde cero en Python. Esta red neuronal se llama perceptr贸n. Sin embargo, las redes neuronales del mundo real, capaces de realizar tareas complejas como la clasificaci贸n de im谩genes y el an谩lisis del mercado de valores, contienen m煤ltiples capas ocultas adem谩s de la capa de entrada y salida.

    En el art铆culo anterior, concluimos que un perceptr贸n es capaz de encontrar un l铆mite de decisi贸n lineal. Usamos perceptr贸n para predecir si una persona es diab茅tica o no usando un conjunto de datos de juguetes. Sin embargo, un perceptr贸n no es capaz de encontrar l铆mites de decisi贸n no lineales.

    En este art铆culo, nos basaremos en los conceptos que estudiamos en la Parte 1 de esta serie y desarrollaremos una red neuronal con una capa de entrada, una capa oculta y una capa de salida. Veremos que la red neuronal que desarrollaremos ser谩 capaz de encontrar fronteras no lineales.

    Conjunto de datos

    Para este art铆culo, necesitamos datos separables de forma no lineal. En otras palabras, necesitamos un conjunto de datos que no se pueda clasificar mediante una l铆nea recta.

    Afortunadamente, la biblioteca Scikit Learn de Python viene con una variedad de herramientas que se pueden usar para generar autom谩ticamente diferentes tipos de conjuntos de datos.

    Ejecutamos el siguiente script para generar el conjunto de datos que vamos a utilizar, con el fin de entrenar y probar nuestra red neuronal.

    from sklearn import datasets
    
    np.random.seed(0)
    feature_set, labels = datasets.make_moons(100, noise=0.10)
    plt.figure(figsize=(10,7))
    plt.scatter(feature_set[:,0], feature_set[:,1], c=labels, cmap=plt.cm.winter)
    

    En el script anterior, importamos la datasetsclase de la sklearnbiblioteca. Para crear un conjunto de datos no lineal de 100 puntos de datos, usamos el make_moonsm茅todo y lo pasamos 100 como primer par谩metro. El m茅todo devuelve un conjunto de datos, que cuando se traza contiene dos semic铆rculos entrelazados, como se muestra en la siguiente figura:

    Puede ver claramente que estos datos no se pueden separar por una sola l铆nea recta, por lo que el perceptr贸n no se puede utilizar para clasificar correctamente estos datos.

    Verifiquemos este concepto. Para hacerlo, usaremos un perceptr贸n simple con una capa de entrada y una capa de salida (la que creamos en el 煤ltimo art铆culo) e intentaremos clasificar nuestro conjunto de datos de “lunas”. Ejecute el siguiente script:

    from sklearn import datasets
    import numpy as np
    import matplotlib.pyplot as plt
    
    np.random.seed(0)
    feature_set, labels = datasets.make_moons(100, noise=0.10)
    plt.figure(figsize=(10,7))
    plt.scatter(feature_set[:,0], feature_set[:,1], c=labels, cmap=plt.cm.winter)
    
    labels = labels.reshape(100, 1)
    
    def sigmoid(x):
        return 1/(1+np.exp(-x))
    
    def sigmoid_der(x):
        return sigmoid(x) *(1-sigmoid (x))
    
    np.random.seed(42)
    weights = np.random.rand(2, 1) 
    lr = 0.5
    bias = np.random.rand(1)
    
    for epoch in range(200000):
        inputs = feature_set
    
        # feedforward step 1
        XW = np.dot(feature_set,weights) + bias
    
        # feedforward step 2
        z = sigmoid(XW)
    
        # backpropagation step 1
        error_out = ((1 / 2) * (np.power((z - labels), 2)))
        print(error_out.sum())
    
        error = z - labels
    
        # backpropagation step 2
        dcost_dpred = error
        dpred_dz = sigmoid_der(z) 
    
        z_delta = dcost_dpred * dpred_dz
    
        inputs = feature_set.T
        weights -= lr * np.dot(inputs, z_delta)
    
        for num in z_delta:
            bias -= lr * num
    

    Ver谩 que el valor del error cuadr谩tico medio no converger谩 m谩s all谩 del 4,17 por ciento, sin importar lo que haga. Esto nos indica que no podemos clasificar correctamente todos los puntos del conjunto de datos utilizando este perceptr贸n, sin importar lo que hagamos.

    Redes neuronales con una capa oculta

    En esta secci贸n, crearemos una red neuronal con una capa de entrada, una capa oculta y una capa de salida. La arquitectura de nuestra red neuronal se ver谩 as铆:

    En la figura anterior, tenemos una red neuronal con 2 entradas, una capa oculta y una capa de salida. La capa oculta tiene 4 nodes. La capa de salida tiene 1 node ya que estamos resolviendo un problema de clasificaci贸n binaria, donde solo puede haber dos salidas posibles. Esta arquitectura de red neuronal es capaz de encontrar l铆mites no lineales.

    No importa cu谩ntos nodes y capas ocultas haya en la red neuronal, el principio de funcionamiento b谩sico sigue siendo el mismo. Comienza con la fase de avance en la que las entradas de la capa anterior se multiplican con los pesos correspondientes y se pasan a trav茅s de la funci贸n de activaci贸n para obtener el valor final para el node correspondiente en la siguiente capa. Este proceso se repite para todas las capas ocultas hasta que se calcula la salida. En la fase de retropropagaci贸n, la salida prevista se compara con la salida real y se calcula el costo del error. El prop贸sito es minimizar la funci贸n de costos.

    Esto es bastante sencillo si no hay una capa oculta involucrada como vimos en el art铆culo anterior.

    Sin embargo, si una o m谩s capas ocultas est谩n involucradas, el proceso se vuelve un poco m谩s complejo porque el error tiene que propagarse a m谩s de una capa, ya que los pesos en todas las capas contribuyen al resultado final.

    En este art铆culo, veremos c贸mo realizar pasos de retropropagaci贸n y retroalimentaci贸n para la red neuronal que tiene una o m谩s capas ocultas.

    Feed Forward

    Para cada registro, tenemos dos caracter铆sticas “x1” y “x2”. Para calcular los valores de cada node en la capa oculta, tenemos que multiplicar la entrada con los pesos correspondientes del node para el que estamos calculando el valor. Luego pasamos el producto escalar a trav茅s de una funci贸n de activaci贸n para obtener el valor final.

    Por ejemplo, para calcular el valor final para el primer node de la capa oculta, que se indica con “ah1”, debe realizar el siguiente c谩lculo:

    $$
    zh1 = x1w1 + x2w2
    $$

    $$
    ah1 = frac {mathrm {1}} {mathrm {1} + e ^ {- zh1}}
    $$

    Este es el valor resultante para el node superior de la capa oculta. De la misma manera, puede calcular los valores para los nodes 2, 3 y 4 de la capa oculta.

    De manera similar, para calcular el valor de la capa de salida, los valores en los nodes de la capa oculta se tratan como entradas. Por lo tanto, para calcular la salida, multiplique los valores de los nodes de la capa oculta con sus pesos correspondientes y pase el resultado a trav茅s de una funci贸n de activaci贸n.

    Esta operaci贸n se puede expresar matem谩ticamente mediante la siguiente ecuaci贸n:

    $$
    entonces = ah1w9 ah2w10 + + + ah2w11 ah4w12
    $$

    $$
    a0 = frac {mathrm {1}} {mathrm {1} + e ^ {- z0}}
    $$

    Aqu铆 “a0” es la salida final de nuestra red neuronal. ReString que la funci贸n de activaci贸n que estamos usando es la funci贸n sigmoidea, como hicimos en el art铆culo anterior.

    Nota: En aras de la simplicidad, no agregamos un t茅rmino de sesgo a cada ponderaci贸n. Ver谩 que la red neuronal con capa oculta funcionar谩 mejor que el perceptr贸n, incluso sin el t茅rmino de sesgo.

    Propagaci贸n hacia atr谩s

    El paso de avance es relativamente sencillo. Sin embargo, la propagaci贸n hacia atr谩s no es tan sencilla como lo fue en la Parte 1 de esta serie.

    En la fase de retropropagaci贸n, primero definiremos nuestra funci贸n de p茅rdida. Usaremos la funci贸n de costo de error cuadr谩tico medio . Se puede representar matem谩ticamente como:

    $$
    MSE =
    frac {mathrm {1}} {mathrm {n}}
    sumnolimits_ {i = 1} ^ {n}
    (predicho – observado) ^ {2}
    $$

    Aqu铆 nest谩 el n煤mero de observaciones.

    Fase 1

    En la primera fase de retropropagaci贸n, necesitamos actualizar los pesos de la capa de salida, es decir, w9, w10, w11 y w12. Entonces, por el momento, solo considere que nuestra red neuronal tiene la siguiente parte:

    Esto se parece al perceptr贸n que desarrollamos en el art铆culo anterior. El prop贸sito de la primera fase de retropropagaci贸n es actualizar los pesos w9, w10, w11 y w12 de tal manera que se minimice el error final. Este es un problema de optimizaci贸n donde tenemos que encontrar los m铆nimos de funci贸n para nuestra funci贸n de costo.

    Para encontrar los m铆nimos de una funci贸n, podemos usar el algoritmo de degradado decente . El algoritmo de degradado decente se puede representar matem谩ticamente de la siguiente manera:

    $$
    repetir hasta la convergencia: comenzar {Bmatrix} w_j: = w_j – alpha frac {parcial} {parcial w_j} J (w_0, w_1 ……. w_n) end {Bmatrix} …….. ….. (1)
    $$

    Los detalles sobre c贸mo la funci贸n decente del gradiente minimiza el costo ya se discutieron en el art铆culo anterior. Aqu铆 veremos las operaciones matem谩ticas que necesitamos realizar.

    Nuestra funci贸n de costos es:

    $$
    MSE = frac {mathrm {1}} {mathrm {n}} sumnolimits_ {i = 1} ^ {n} (predicho – observado) ^ {2}
    $$

    En nuestra red neuronal, la salida predicha est谩 representada por “ao”. Lo que significa que b谩sicamente tenemos que minimizar esta funci贸n:

    $$
    cost = frac {mathrm {1}} {mathrm {n}} sumnolimits_ {i = 1} ^ {n} (ao – observado) ^ {2}
    $$

    Del art铆culo anterior, sabemos que para minimizar la funci贸n de costo, tenemos que actualizar los valores de peso de manera que el costo disminuya. Para hacerlo, necesitamos obtener la derivada de la funci贸n de costo con respecto a cada peso. Dado que en esta fase, estamos tratando con pesos de la capa de salida, necesitamos diferenciar la funci贸n de costo con respecto a w9, w10, w11 y w2.

    La diferenciaci贸n de la funci贸n de costo con respecto a los pesos en la capa de salida se puede representar matem谩ticamente de la siguiente manera utilizando la regla de diferenciaci贸n de la cadena .

    $$
    frac {dcost}{dwo} = frac {dcost}{dao} *, frac {dao}{dzo} * frac {dzo}{dwo} …… (1)
    $$

    Aqu铆 “wo” se refiere a los pesos en la capa de salida. La letra “d” al comienzo de cada t茅rmino se refiere a la derivada.

    Encontremos el valor de cada expresi贸n en la Ecuaci贸n 1.

    Aqu铆,

    $$
    frac {dcost} {dao} = frac {2} {n} * (ao – etiquetas)
    $$

    Aqu铆 2y nson constantes. Si los ignoramos, tenemos la siguiente ecuaci贸n.

    $$
    frac {dcost} {dao} = (ao – etiquetas) …….. (5)
    $$

    A continuaci贸n, podemos encontrar “dao” con respecto a “dzo” de la siguiente manera:

    $$
    frac {dao}{dzo} = sigmoid(zo) * (1-sigmoid(zo)) …….. (6)
    $$

    Finalmente, necesitamos encontrar “dzo” con respecto a “dwo”. La derivada son simplemente las entradas que provienen de la capa oculta como se muestra a continuaci贸n:

    $$
    frac {dzo}{dwo} = ah
    $$

    Aqu铆 “ah” se refiere a las 4 entradas de las capas ocultas. La ecuaci贸n 1 se puede utilizar para encontrar los valores de peso actualizados para los pesos de la capa de salida. Para encontrar nuevos valores de peso, los valores devueltos por la Ecuaci贸n 1 pueden simplemente multiplicarse con la tasa de aprendizaje y restarse de los valores de peso actuales. Esto es sencillo y lo hemos hecho anteriormente.

    Fase 2

    En la secci贸n anterior, vimos c贸mo podemos encontrar los valores actualizados para los pesos de la capa de salida, es decir, w9, w10, w11 y 12. En esta secci贸n, propagaremos nuestro error a la capa anterior y encontraremos los nuevos valores de peso. para pesos de capa oculta, es decir, pesos w1 a w8.

    Denotemos colectivamente los pesos de las capas ocultas como “wh”. B谩sicamente tenemos que diferenciar la funci贸n de coste con respecto a “wh”. Matem谩ticamente podemos usar la regla de diferenciaci贸n de la cadena para representarla como:

    $$
    frac {dcost} {dwh} = frac {dcost} {dah} *, frac {dah} {dzh} * frac {dzh} {dwh} …… (2)
    $$

    Aqu铆 nuevamente dividiremos la Ecuaci贸n 2 en t茅rminos individuales.

    El primer t茅rmino “dcost” se puede diferenciar con respecto a “dah” usando la regla de diferenciaci贸n de la cadena de la siguiente manera:

    $$
    frac {dcost} {dah} = frac {dcost} {dzo} *, frac {dzo} {dah} …… (3)
    $$

    Dividamos nuevamente la Ecuaci贸n 3 en t茅rminos individuales. Usando la regla de la cadena nuevamente, podemos diferenciar “dcost” con respecto a “dzo” de la siguiente manera:

    $$
    frac {dcost}{dzo} = frac {dcost}{dao} *, frac {dao}{dzo} …… (4)
    $$

    Ya hemos calculado el valor de dcost / dao en la Ecuaci贸n 5 y dao / dzo en la Ecuaci贸n 6.

    Ahora necesitamos encontrar dzo / dah de la Ecuaci贸n 3. Si miramos zo, tiene el siguiente valor:

    $$
    entonces = a01w9 a02w10 + + + a03w11 a04w12
    $$

    Si lo diferenciamos con respecto a todas las entradas de la capa oculta, denotado por “ao”, entonces nos quedamos con todos los pesos de la capa de salida, denotados por “wo”. Por lo tanto,

    $$
    frac {dzo} {dah} = wo …… (7)
    $$

    Ahora podemos encontrar el valor de dcost / dah reemplazando los valores de las ecuaciones 7 y 4 en la ecuaci贸n 3.

    Volviendo a la Ecuaci贸n 2, todav铆a tenemos que encontrar dah / dzh y dzh / dwh.

    El primer t茅rmino dah / dzh se puede calcular como:

    $$
    frac {dah} {dzh} = sigmoide (zh) * (1-sigmoide (zh)) …….. (8)
    $$

    Y finalmente, dzh / dwh son simplemente los valores de entrada:

    $$
    frac {dzh} {dwh} = caracter铆sticas de entrada …….. (9)
    $$

    Si reemplazamos los valores de las ecuaciones 3, 8 y 9 en la ecuaci贸n 3, podemos obtener la matriz actualizada para los pesos de las capas ocultas. Para encontrar nuevos valores de peso para los pesos de capa ocultos “wh”, los valores devueltos por la Ecuaci贸n 2 se pueden multiplicar simplemente con la tasa de aprendizaje y restar de los valores de peso actuales. Y eso es todo.

    Las ecuaciones pueden parecerle agotadoras ya que se est谩n realizando muchos c谩lculos. Sin embargo, si los observa de cerca, solo hay dos operaciones que se realizan en una cadena: derivaciones y multiplicaciones.

    Una de las razones por las que las redes neuronales son m谩s lentas que los otros algoritmos de Machine Learning es el hecho de que se realizan muchos c谩lculos en el back-end. Nuestra red neuronal ten铆a solo una capa oculta con cuatro nodes, dos entradas y una salida, sin embargo, tuvimos que realizar largas operaciones de derivaci贸n y multiplicaci贸n para actualizar los pesos para una sola iteraci贸n. En el mundo real, las redes neuronales pueden tener cientos de capas con cientos de valores de entrada y salida. Por tanto, las redes neuronales se ejecutan lentamente.

    C贸digo para redes neuronales con una capa oculta

    Ahora implementemos la red neuronal que acabamos de discutir en Python desde cero. Ver谩 claramente la correspondencia entre los fragmentos de c贸digo y la teor铆a que discutimos en la secci贸n anterior. Intentaremos nuevamente clasificar los datos no lineales que creamos en la secci贸n Conjunto de datos del art铆culo. Eche un vistazo al siguiente gui贸n.

    # -*- coding: utf-8 -*-
    """
    Created on Tue Sep 25 13:46:08 2018
    
    @author: usman
    """
    
    from sklearn import datasets
    import numpy as np
    import matplotlib.pyplot as plt
    
    np.random.seed(0)
    feature_set, labels = datasets.make_moons(100, noise=0.10)
    plt.figure(figsize=(10,7))
    plt.scatter(feature_set[:,0], feature_set[:,1], c=labels, cmap=plt.cm.winter)
    
    labels = labels.reshape(100, 1)
    
    def sigmoid(x):
        return 1/(1+np.exp(-x))
    
    def sigmoid_der(x):
        return sigmoid(x) *(1-sigmoid (x))
    
    wh = np.random.rand(len(feature_set[0]),4) 
    wo = np.random.rand(4, 1)
    lr = 0.5
    
    for epoch in range(200000):
        # feedforward
        zh = np.dot(feature_set, wh)
        ah = sigmoid(zh)
    
        zo = np.dot(ah, wo)
        ao = sigmoid(zo)
    
        # Phase1 =======================
    
        error_out = ((1 / 2) * (np.power((ao - labels), 2)))
        print(error_out.sum())
    
        dcost_dao = ao - labels
        dao_dzo = sigmoid_der(zo) 
        dzo_dwo = ah
    
        dcost_wo = np.dot(dzo_dwo.T, dcost_dao * dao_dzo)
    
        # Phase 2 =======================
    
        # dcost_w1 = dcost_dah * dah_dzh * dzh_dw1
        # dcost_dah = dcost_dzo * dzo_dah
        dcost_dzo = dcost_dao * dao_dzo
        dzo_dah = wo
        dcost_dah = np.dot(dcost_dzo , dzo_dah.T)
        dah_dzh = sigmoid_der(zh) 
        dzh_dwh = feature_set
        dcost_wh = np.dot(dzh_dwh.T, dah_dzh * dcost_dah)
    
        # Update Weights ================
    
        wh -= lr * dcost_wh
        wo -= lr * dcost_wo
    

    En el script anterior comenzamos importando las bibliotecas deseadas y luego creamos nuestro conjunto de datos. A continuaci贸n, definimos la funci贸n sigmoidea junto con su derivada. Luego inicializamos la capa oculta y los pesos de la capa de salida con valores aleatorios. La tasa de aprendizaje es 0,5. Prob茅 diferentes tasas de aprendizaje y descubr铆 que 0.5 es un buen valor.

    Luego ejecutamos el algoritmo para 2000 茅pocas. Dentro de cada 茅poca, primero realizamos la operaci贸n de feed-forward. El fragmento de c贸digo para la operaci贸n de avance es el siguiente:

    zh = np.dot(feature_set, wh)
    ah = sigmoid(zh)
    
    zo = np.dot(ah, wo)
    ao = sigmoid(zo)
    

    Como se discuti贸 en la secci贸n de teor铆a, la propagaci贸n hacia atr谩s consta de dos fases. En la primera fase, se calculan los gradientes para los pesos de la capa de salida. El siguiente script se ejecuta en la primera fase de la propagaci贸n hacia atr谩s.

    error_out = ((1 / 2) * (np.power((ao - labels), 2)))
    print(error_out.sum())
    
    dcost_dao = ao - labels
    dao_dzo = sigmoid_der(zo) 
    dzo_dwo = ah
    
    dcost_wo = np.dot(dzo_dwo.T, dcost_dao * dao_dzo)
    

    En la segunda fase, se calculan los degradados para los pesos de las capas ocultas. El siguiente script se ejecuta en la segunda fase de la propagaci贸n inversa.

    dcost_dzo = dcost_dao * dao_dzo
    dzo_dah = wo
    dcost_dah = np.dot(dcost_dzo , dzo_dah.T)
    dah_dzh = sigmoid_der(zh) 
    dzh_dwh = feature_set
    dcost_wh = np.dot( dzh_dwh.T, dah_dzh * dcost_dah)
    

    Finalmente, los pesos se actualizan en el siguiente script:

    wh -= lr * dcost_wh
    wo -= lr * dcost_wo
    

    Cuando se ejecute el script anterior, ver谩 un valor de error cuadr谩tico medio m铆nimo de 1,50, que es menor que nuestro error cuadr谩tico medio anterior de 4,17, que se obtuvo utilizando el perceptr贸n. Esto muestra que la red neuronal con capas ocultas funciona mejor en el caso de datos separables no linealmente.

    Conclusi贸n

    En este art铆culo, vimos c贸mo podemos crear una red neuronal con 1 capa oculta, desde cero en Python. Vimos c贸mo nuestra red neuronal super贸 a una red neuronal sin capas ocultas para la clasificaci贸n binaria de datos no lineales.

    Sin embargo, es posible que necesitemos clasificar los datos en m谩s de dos categor铆as. En nuestro pr贸ximo art铆culo, veremos c贸mo crear una red neuronal desde cero en Python para problemas de clasificaci贸n de varias clases.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *