Creación de una red neuronal desde cero en Python

    Introducción

    ¿Alguna vez te has preguntado cómo los chatbots como Siri, Alexa y Cortona pueden responder a las consultas de los usuarios? ¿O cómo los coches autónomos pueden conducirse solos sin ayuda humana? Todos estos productos sofisticados tienen una cosa en común: la inteligencia artificial (IA). Es la IA la que les permite realizar tales tareas sin ser supervisados ​​o controlados por un humano. Pero la pregunta sigue siendo: “¿Qué es la IA?” Una respuesta simple a esta pregunta es: “La IA es una combinación de algoritmos complejos de varios dominios matemáticos como Álgebra, Cálculo y Probabilidad y Estadística”.

    En este artículo, estudiaremos una red neuronal artificial simple, que es uno de los componentes principales de la inteligencia artificial. Existen diferentes variantes de una Red Neuronal Artificial, dedicadas a resolver un problema en particular. Por ejemplo, las redes neuronales convolucionales se usan comúnmente para problemas de reconocimiento de imágenes, mientras que las redes neuronales recurrentes se usan para resolver problemas de secuencia.

    Hay muchas bibliotecas de aprendizaje profundo que se pueden usar para crear una red neuronal en una sola línea de código. Sin embargo, si realmente desea comprender el funcionamiento en profundidad de una red neuronal, le sugiero que aprenda a codificarla desde cero en cualquier lenguaje de programación. Realizar este ejercicio realmente aclarará muchos de los conceptos para usted. Y esto es exactamente lo que haremos en este artículo.

    El problema

    Dado que este es un artículo introductorio, el problema que vamos a resolver es bastante simple. Suponga que tenemos información sobre la obesidad, los hábitos de fumar y los hábitos de ejercicio de cinco personas. También sabemos si estas personas son diabéticas o no. Nuestro conjunto de datos se ve así:

    PersonSmokingObesityExerciseDiabetic

    Persona 10101
    Persona 20010
    Persona 31000
    Persona 41101
    Persona 51111

    En la tabla anterior, tenemos cinco columnas: Persona, Tabaquismo, Obesidad, Ejercicio y Diabético. Aquí 1 se refiere a verdadero y 0 a falso. Por ejemplo, la primera persona tiene valores de 0, 1, 0, lo que significa que la persona no fuma, es obesa y no hace ejercicio. La persona también es diabética.

    Es claramente evidente a partir del conjunto de datos que la obesidad de una persona indica que es diabético. Nuestra tarea es crear una red neuronal que sea capaz de predecir si una persona desconocida es diabética o no dados datos sobre sus hábitos de ejercicio, obesidad y hábitos de tabaquismo. Este es un tipo de problema de aprendizaje supervisado donde se nos dan entradas y las correspondientes salidas correctas y nuestra tarea es encontrar el mapeo entre las entradas y las salidas.

    Nota : Este es solo un conjunto de datos ficticio; en la vida real, las personas obesas no siempre son necesariamente diabéticas.

    La solución

    Crearemos una red neuronal muy simple con una capa de entrada y una capa de salida. Antes de escribir cualquier código real, primero veamos cómo se ejecutará nuestra red neuronal, en teoría.

    Teoría de redes neuronales

    Una red neuronal es un algoritmo de aprendizaje supervisado, lo que significa que le proporcionamos los datos de entrada que contienen las variables independientes y los datos de salida que contienen la variable dependiente. Por ejemplo, en nuestro ejemplo, nuestras variables independientes son el tabaquismo, la obesidad y el ejercicio. La variable dependiente es si una persona es diabética o no.

    Al principio, la red neuronal hace algunas predicciones aleatorias, estas predicciones se comparan con la salida correcta y se calcula el error o la diferencia entre los valores predichos y los valores reales. La función que encuentra la diferencia entre el valor real y los valores propagados se llama función de costo. El costo aquí se refiere al error. Nuestro objetivo es minimizar la función de costes. Entrenar una red neuronal básicamente se refiere a minimizar la función de costo. Veremos cómo podemos realizar esta tarea.

    La red neuronal que vamos a crear tiene la siguiente representación visual.

    Una red neuronal se ejecuta en dos pasos: retroalimentación y propagación hacia atrás. Discutiremos estos dos pasos en detalle.

    Feed Forward

    En la parte de retroalimentación de una red neuronal, las predicciones se realizan en función de los valores en los nodes de entrada y los pesos. Si observa la red neuronal en la figura anterior, verá que tenemos tres características en el conjunto de datos: tabaquismo, obesidad y ejercicio, por lo tanto, tenemos tres nodes en la primera capa, también conocida como capa de entrada. Hemos reemplazado nuestros nombres de funciones con la variable x, por generalidad en la figura anterior.

    Los pesos de una red neuronal son básicamente las cadenas que tenemos que ajustar para poder predecir correctamente nuestra salida. Por ahora, recuerde que para cada función de entrada, tenemos un peso.

    Los siguientes son los pasos que se ejecutan durante la fase de retroalimentación de una red neuronal:

    Paso 1: (Calcule el producto escalar entre las entradas y los pesos)

    Los nodes de la capa de entrada están conectados con la capa de salida a través de tres parámetros de ponderación. En la capa de salida, los valores en los nodes de entrada se multiplican con sus pesos correspondientes y se suman. Finalmente, el término de sesgo se agrega a la suma. El ben la figura anterior se refiere al término de sesgo.

    El término de sesgo es muy importante aquí. Supongamos que si tenemos una persona que no fuma, no es obesa y no hace ejercicio, la suma de los productos de los nodes de entrada y los pesos será cero. En ese caso, la salida siempre será cero sin importar cuánto entrenemos los algoritmos. Por lo tanto, para poder hacer predicciones, incluso si no tenemos ninguna información distinta de cero sobre la persona, necesitamos un término de sesgo. El término de sesgo es necesario para crear una red neuronal robusta.

    Matemáticamente, en el paso 1, realizamos el siguiente cálculo:

    $$
    XW = x1w1 + x2w2 + x3w3 + b
    $$

    Paso 2: (Pase el resultado del paso 1 a través de una función de activación)

    El resultado del Paso 1 puede ser un conjunto de valores. Sin embargo, en nuestra salida tenemos los valores en forma de 1 y 0. Queremos que nuestra salida tenga el mismo formato. Para hacerlo, necesitamos una función de activación , que aplasta los valores de entrada entre 1 y 0. Una de esas funciones de activación es la función sigmoidea .

    La función sigmoidea devuelve 0.5 cuando la entrada es 0. Devuelve un valor cercano a 1 si la entrada es un número positivo grande. En caso de entrada negativa, la función sigmoidea genera un valor cercano a cero.

    Matemáticamente, la función sigmoidea se puede representar como:

    $$
    theta_ {XW} = frac {mathrm {1}} {mathrm {1} + e ^ {- XW}}
    $$

    Intentemos trazar la función sigmoidea:

    input = np.linspace(-10, 10, 100)
    
    def sigmoid(x):
        return 1/(1+np.exp(-x))
    
    from matplotlib import pyplot as plt
    plt.plot(input, sigmoid(input), c="r")
    

    En el script anterior, primero generamos aleatoriamente 100 puntos espaciados linealmente entre -10 y 10. Para hacerlo, usamos el linspacemétodo de la biblioteca NumPy. A continuación, definimos la sigmoidfunción. Finalmente, usamos la matplotlibbiblioteca para trazar los valores de entrada contra los valores devueltos por la sigmoidfunción. La salida se ve así:

    Puede ver que si la entrada es un número negativo, la salida es cercana a cero, de lo contrario, si la entrada es positiva, la salida está cerca de 1. Sin embargo, la salida siempre está entre 0 y 1. Esto es lo que queremos.

    Esto resume la parte feedforward de nuestra red neuronal. Es bastante sencillo. Primero tenemos que encontrar el producto escalar de la matriz de características de entrada con la matriz de ponderación. A continuación, pase el resultado de la salida a través de una función de activación, que en este caso es la función sigmoidea. El resultado de la función de activación es básicamente la salida prevista para las características de entrada.

    Propagación hacia atrás

    Al principio, antes de realizar cualquier entrenamiento, la red neuronal hace predicciones aleatorias que están lejos de ser correctas.

    El principio detrás del funcionamiento de una red neuronal es simple. Comenzamos dejando que la red haga predicciones aleatorias sobre la salida. Luego comparamos la salida predicha de la red neuronal con la salida real. A continuación, ajustamos nuestros pesos y el sesgo de tal manera que nuestra salida predicha se acerque más a la salida real, que básicamente se conoce como “entrenamiento de la red neuronal”.

    En la sección de propagación hacia atrás, entrenamos nuestro algoritmo. Echemos un vistazo a los pasos involucrados en la sección de propagación hacia atrás.

    Paso 1: (Calcular el costo)

    El primer paso en la sección de retropropagación es encontrar el “costo” de las predicciones. El costo de la predicción se puede calcular simplemente encontrando la diferencia entre la salida prevista y la salida real. Cuanto mayor sea la diferencia, mayor será el costo.

    Hay varias otras formas de encontrar el costo, pero usaremos la función del costo del error cuadrático medio . Una función de costo es simplemente la función que encuentra el costo de las predicciones dadas.

    La función del costo del 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.

    Paso 2: (Minimizar el costo)

    Nuestro propósito final es ajustar las perillas de nuestra red neuronal de tal manera que se minimice el costo. Si observa nuestra red neuronal, notará que solo podemos controlar los pesos y el sesgo. Todo lo demás está fuera de nuestro control. No podemos controlar las entradas, no podemos controlar los productos escalares y no podemos manipular la función sigmoidea.

    Para minimizar el costo, necesitamos encontrar el peso y los valores de sesgo para los cuales la función de costo devuelve el valor más pequeño posible. Cuanto menor sea el costo, más correctas serán nuestras predicciones.

    Este es un problema de optimización donde tenemos que encontrar los mínimos de la función .

    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)
    $$

    Aquí, en la ecuación anterior, Jestá la función de costo. Básicamente, lo que dice la ecuación anterior es: encuentre la derivada parcial de la función de costo con respecto a cada peso y sesgo y reste el resultado de los valores de peso existentes para obtener los nuevos valores de peso.

    La derivada de una función nos da su pendiente en cualquier punto dado. Para encontrar si el costo aumenta o disminuye, dado el valor de peso, podemos encontrar la derivada de la función en ese valor de peso particular. Si el costo aumenta con el aumento de peso, la derivada devolverá un valor positivo que luego se restará del valor existente.

    Por otro lado, si el costo disminuye con un aumento de peso, se devolverá un valor negativo, que se sumará al valor de peso existente, ya que de negativo a negativo es positivo.

    En la Ecuación 1, podemos ver que hay un símbolo alfa, que se multiplica por el gradiente. Esto se llama tasa de aprendizaje. La tasa de aprendizaje define qué tan rápido aprende nuestro algoritmo. Para obtener más detalles sobre cómo se puede definir la tasa de aprendizaje, consulte este artículo .

    Necesitamos repetir la ejecución de la Ecuación 1 para todos los pesos y sesgos hasta que el costo se minimice al nivel deseable. En otras palabras, necesitamos seguir ejecutando la Ecuación 1 hasta que obtengamos dichos valores de sesgo y ponderaciones, para lo cual la función de costo devuelve un valor cercano a cero.

    Y eso es todo. Ahora es el momento de implementar lo que hemos estudiado hasta ahora. Crearemos una red neuronal simple con una entrada y una capa de salida en Python.

    Implementación de redes neuronales en Python

    Primero creemos nuestro conjunto de características y las etiquetas correspondientes. Ejecute el siguiente script:

    import numpy as np
    feature_set = np.array([[0,1,0],[0,0,1],[1,0,0],[1,1,0],[1,1,1]])
    labels = np.array([[1,0,0,1,1]])
    labels = labels.reshape(5,1)
    

    En el script anterior, creamos nuestro conjunto de funciones. Contiene cinco registros. De manera similar, creamos un labelsconjunto que contiene las etiquetas correspondientes para cada registro en el conjunto de características. Las etiquetas son las respuestas que intentamos predecir con la red neuronal.

    El siguiente paso es definir hiperparámetros para nuestra red neuronal. Ejecute el siguiente script para hacerlo:

    np.random.seed(42)
    weights = np.random.rand(3,1)
    bias = np.random.rand(1)
    lr = 0.05
    

    En el script anterior usamos la random.seedfunción para que podamos obtener los mismos valores aleatorios siempre que se ejecute el script.

    En el siguiente paso, inicializamos nuestros pesos con números aleatorios distribuidos normalmente. Como tenemos tres características en la entrada, tenemos un vector de tres pesos. Luego inicializamos el valor de sesgo con otro número aleatorio. Finalmente, establecemos la tasa de aprendizaje en 0.05.

    A continuación, necesitamos definir nuestra función de activación y su derivada (explicaré en un momento por qué necesitamos encontrar la derivada de la activación). Nuestra función de activación es la función sigmoidea, que cubrimos anteriormente.

    La siguiente secuencia de comandos de Python crea esta función:

    def sigmoid(x):
        return 1/(1+np.exp(-x))
    

    Y el método que calcula la derivada de la función sigmoidea se define de la siguiente manera:

    def sigmoid_der(x):
        return sigmoid(x)*(1-sigmoid(x))
    

    La derivada de la función sigmoidea es simple sigmoid(x) * sigmoid(1-x).

    Ahora estamos listos para entrenar nuestra red neuronal que podrá predecir si una persona es obesa o no.

    Mira el siguiente guión:

    for epoch in range(20000):
        inputs = feature_set
    
        # feedforward step1
        XW = np.dot(feature_set, weights) + bias
    
        #feedforward step2
        z = sigmoid(XW)
    
    
        # backpropagation step 1
        error = z - labels
    
        print(error.sum())
    
        # 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
    

    No se deje intimidar por este código. Lo explicaré línea por línea.

    En el primer paso, definimos el número de épocas. Una época es básicamente la cantidad de veces que queremos entrenar el algoritmo en nuestros datos. Entrenaremos el algoritmo con nuestros datos 20.000 veces. Probé este número y descubrí que el error se minimiza bastante después de 20,000 iteraciones. Puedes probar con un número diferente. El objetivo final es minimizar el error.

    A continuación, almacenamos los valores de la feature_seta la inputvariable. Luego ejecutamos la siguiente línea:

    XW = np.dot(feature_set, weights) + bias
    

    Aquí encontramos el producto escalar de la entrada y el vector de peso y le agregamos sesgo. Este es el Paso 1 de la sección de feedforward.

    En esta línea:

    z = sigmoid(XW)
    

    Pasamos el producto escalar a través de la función de activación sigmoidea, como se explica en el Paso 2 de la sección de avance. Esto completa la parte de avance de nuestro algoritmo.

    Ahora es el momento de comenzar la propagación hacia atrás. La variable zcontiene las salidas previstas. El primer paso de la propagación hacia atrás es encontrar el error. Lo hacemos en la siguiente línea:

    error = z - labels
    

    Luego imprimimos el error en la pantalla.

    Ahora es el momento de ejecutar el Paso 2 de retropropagación, que es la esencia de este código.

    Sabemos que nuestra función de costos es:

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

    Necesitamos diferenciar esta función con respecto a cada peso. Usaremos la regla de diferenciación de la cadena para este propósito. Supongamos que “d_cost” es la derivada de nuestra función de costo con respecto al peso “w”, podemos usar la regla de la cadena para encontrar esta derivada, como se muestra a continuación:

    $$
    frac {d_cost} {dw} = frac {d_cost} {d_pred}, frac {d_pred} {dz}, frac {dz} {dw}
    $$

    Aquí,

    $$
    frac {d_cost} {d_pred}
    $$

    se puede calcular como:

    $$
    2 (previsto – observado)
    $$

    Aquí, 2 es constante y, por tanto, puede ignorarse. Este es básicamente el error que ya calculamos. En el código, puede ver la línea:

    dcost_dpred = error # ........ (2)
    

    A continuación tenemos que encontrar:

    $$
    frac {d_pred}{dz}
    $$

    Aquí “d_pred” es simplemente la función sigmoidea y la hemos diferenciado con respecto al producto escalar de entrada “z”. En el script, esto se define como:

    dpred_dz = sigmoid_der(z) # ......... (3)
    

    Finalmente, tenemos que encontrar:

    $$
    frac {d_z} {dw}
    $$

    Lo sabemos:

    $$
    z = x1w1 + x2w2 + x3w3 + b
    $$

    Por lo tanto, la derivada con respecto a cualquier peso es simplemente la entrada correspondiente. Por lo tanto, nuestra derivada final de la función de costo con respecto a cualquier peso es:

    slope = input x dcost_dpred x dpred_dz
    

    Eche un vistazo a las siguientes tres líneas:

    z_delta = dcost_dpred * dpred_dz
    inputs = feature_set.T
    weights -= lr * np.dot(inputs, z_delta)
    

    Aquí tenemos la z_deltavariable, que contiene el producto de dcost_dpredy dpred_dz. En lugar de recorrer cada registro y multiplicar la entrada por la correspondiente z_delta, tomamos la transposición de la matriz de características de entrada y la multiplicamos por z_delta. Finalmente, multiplicamos la variable de la tasa de aprendizaje lrpor la derivada para aumentar la velocidad de convergencia.

    Luego recorrimos cada valor derivado y actualizamos nuestros valores de sesgo, como se muestra en este script:

    Una vez que comience el ciclo, verá que el error total comienza a disminuir como se muestra a continuación:

    0.001700995120272485
    0.001700910187124885
    0.0017008252625468727
    0.0017007403465365955
    0.00170065543909367
    0.0017005705402162556
    0.0017004856499031988
    0.0017004007681529695
    0.0017003158949647542
    0.0017002310303364868
    0.0017001461742678046
    0.0017000613267565308
    0.0016999764878018585
    0.0016998916574025129
    0.00169980683555691
    0.0016997220222637836
    0.0016996372175222992
    0.0016995524213307602
    0.0016994676336875778
    0.0016993828545920908
    0.0016992980840424554
    0.0016992133220379794
    0.0016991285685766487
    0.0016990438236577712
    0.0016989590872797753
    0.0016988743594415108
    0.0016987896401412066
    0.0016987049293782815
    

    Puede ver que el error es extremadamente pequeño al final del entrenamiento de nuestra red neuronal. En este momento, nuestros pesos y sesgos tendrán valores que pueden usarse para detectar si una persona es diabética o no, en función de sus hábitos de tabaquismo, obesidad y hábitos de ejercicio.

    Ahora puede intentar predecir el valor de una sola instancia. Supongamos que tenemos un registro de un paciente que ingresa, fuma, no es obeso y no hace ejercicio. Veamos si es probable que sea diabético o no. La función de entrada se verá así: [1,0,0].

    Ejecute el siguiente script:

    single_point = np.array([1,0,0])
    result = sigmoid(np.dot(single_point, weights) + bias)
    print(result)
    

    En la salida verá:

    [0.00707584]
    

    Puede ver que es probable que la persona no sea diabética, ya que el valor está mucho más cerca de 0 que de 1.

    Ahora probemos a otra persona que no fuma, es obesa y no hace ejercicio. El vector de características de entrada será [0,1,0]. Ejecute este script:

    single_point = np.array([0,1,0])
    result = sigmoid(np.dot(single_point, weights) + bias)
    print(result)
    

    En la salida verá el siguiente valor:

    [0.99837029]
    

    Puede ver que el valor está muy cerca de 1, lo que probablemente se deba a la obesidad de la persona.

    Recursos

    ¿Quiere aprender más sobre la creación de redes neuronales para resolver problemas complejos? Si es así, intente consultar otros recursos, como este curso en línea:

    Deep Learning AZ: redes neuronales artificiales prácticas

    Cubre las redes neuronales con mucho más detalle, incluidas las redes neuronales convolucionales, las redes neuronales recurrentes y mucho más.

    Conclusión

    En este artículo creamos una red neuronal muy simple con una entrada y una capa de salida desde cero en Python. Esta red neuronal se llama simplemente perceptrón . Un perceptrón puede clasificar datos separables linealmente. Los datos linealmente separables son el tipo de datos que pueden separarse mediante un hiperplano en un espacio n-dimensional.

    Las redes neuronales artificiales de palabras reales son mucho más complejas, poderosas y consisten en múltiples capas ocultas y múltiples nodes en la capa oculta. Estas redes neuronales pueden identificar límites de decisión reales no lineales. Explicaré cómo crear una red neuronal multicapa desde cero en Python en un próximo artículo.

     

    Etiquetas:

    Deja una respuesta

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