Cómo programar una red neuronal
desde 0 en Python
Las redes neuronales son unos algoritmos muy potentes en el mundo del Machine Learning.
En este blog ya los hemos visto varias veces, desde cómo crear una red neuronal en R hasta
la clasificación de imágenes con Keras. Pero, ¿cómo podría yo programar una red
neuronal desde 0 en Python? En este post te lo explico. ¿Te suena interesante? ¡Pues
vamos a ello!
Las redes neuronales están compuestas de neuronas, que a su vez se agrupan en
capas: cada neurona de cada capa está conectada con todas las neuronas de la capa anterior.
En cada neurona, se realizarán una serie de operaciones (que explicaremos más adelante)
las cuales, al optimizar, conseguiremos que nuestra red aprenda. Vamos, que si vamos a
programar una red neuronal desde 0 en Python, lo primero es programar las capas de
neuronas. ¡Pues manos a la obra!
Creando las capas de neuronas
Para poder programar una capa de neuronas, primero debemos entender bien cómo
funcionan. Básicamente una red neuronal funciona de la siguiente manera:
1. Una capa recibe valores, llamados inputs. En la primera capa, esos valores
vendrán definidos por los datos de entrada, mientras que el resto de capas
recibirán el resultado de la capa anterior.
2. Se realiza una suma ponderada todos los valores de entrada. Para hacer esa
ponderación necesitamos una matriz de pesos, conocida como W. La matriz W
tiene tantas filas como neuronas la capa anterior y tantas columnas como
neuronas tiene esa capa.
3. Al resultado de la suma ponderada anterior se le sumará otro parámetro,
conocido como bias o, simplemente, b. En este caso, cada neurona tiene su
propio bias, por lo que las dimensiones del vector bias será una columna y tantas
filas como neuronas tiene esa capa.
4. Por cuarto lugar tenemos una de las claves de las redes neuronales: la función de
activación. Y es que, si te das cuenta, lo que tenemos hasta ahora no es más que
una regresión lineal. Para evitar que toda la red neuronal se pueda reducir a
una simple regresión lineal, al resultado de la suma del bias a la suma
ponderada se le aplica una función, conocido como función de activación. El
resultado de esta función será el resultado de la neurona.
Por tanto, para poder montar una capa de una red neuronal solo necesitamos saber el
número de neuronas en la capa y el número de neuronas de la capa anterior. Con eso,
podremos crear tanto W como b.
Para crear esta estructura vamos a crear una clase, que llamaremos capa. Además,
vamos a inicializar los parámetros (b y W) con datos aleatorios. Para esto último usaremos
la función trunconorm de la librería stats, ya que nos permite crear datos aleatorios
dado un rango, media y desviación estándar, lo cual hará que a nuestra red le cueste menos
arrancar. In [1]:
from scipy import stats
class capa():
def __init__(self, n_neuronas_capa_anterior, n_neuronas,
funcion_act):
self.funcion_act = funcion_act
self.b = np.round(stats.truncnorm.rvs(-1, 1, loc=0,
scale=1, size= n_neuronas).reshape(1,n_neuronas),3)
self.W = np.round(stats.truncnorm.rvs(-1, 1, loc=0,
scale=1, size= n_neuronas *
n_neuronas_capa_anterior).reshape(n_neuronas_capa_anterior,n_
neuronas),3)
Con esto ya tendríamos definida la estructura de una capa. Sin duda alguna, la definición y
uso de clases en Python es más sencilla que en R. Punto a para Python.
Dicho esto, vayamos a una cosa que hemos dejado en el tintero: las funciones de
activación.
Funciones de Activación
Como he dicho antes, al resultado de la suma ponderada del input y el parámetro bias
se aplica una función de activación, es decir, una transformación a los datos. El motivo es
que, si no lo hiciéramos, cada neurona lo único que haría sería una transformación lineal de
los datos, dando como resultado una función lineal normal.
¿Qué función usamos? Podríamos usar cualquier función de activación que haga que el
resultado no sea lineal, pero generalmente se suelen usar dos: función sigmoide y función
ReLu.
Función de activación: Función Sigmoide
La función sigmoide básicamente recibe un valor x y devuelve un valor entre 0 y 1. Esto
hace que sea una función muy interesante, ya que indica la probabilidad de un estado. Por
ejemplo, si usamos la función sigmoide en la última capa para un problema de clasificación
entre dos clases, la función devolverá la probabilidad de pertenencia a un grupo. In [2]:
import numpy as np
import math
import matplotlib.pyplot as plt
sigmoid = (
lambda x:1 / (1 + np.exp(-x)),
lambda x:x * (1 - x)
rango = np.linspace(-10,10).reshape([50,1])
datos_sigmoide = sigmoid[0](rango)
datos_sigmoide_derivada = sigmoid[1](rango)
#Cremos los graficos
fig, axes = plt.subplots(nrows=1, ncols=2, figsize =(15,5))
axes[0].plot(rango, datos_sigmoide)
axes[1].plot(rango, datos_sigmoide_derivada)
fig.tight_layout()
Función de activación: Función ReLu
La función ReLu es muy simple: para valores negativos, la función devuelve cero. Para
valores positivos, la función devuelve el mismo valor. Pero, a pesar de ser tan simple, esta
función es la función de activación más usada en el campo de las redes neuronales y deep
learning. ¿El motivo? Pues precisamente porque es sencilla y porque evita el gradient
vanish (más info aquí). In [3]: