Introducción a las Redes Neuronales con Scikit-Learn

Los seres humanos tienen la capacidad de identificar patrones dentro de la información accesible con un grado de precisión sorprendentemente alto. Siempre que veas un coche o una bicicleta...

¿Qué es una red neuronal?

Los seres humanos tienen la capacidad de identificar patrones dentro de la información accesible con un grado de precisión sorprendentemente alto. Cada vez que ves un coche o una bicicleta, puedes reconocer inmediatamente lo que son. Esto se debe a que hemos aprendido durante un período de tiempo cómo se ve un automóvil y una bicicleta y cuáles son sus características distintivas. Las redes neuronales artificiales son sistemas de computación que pretenden imitar las capacidades de aprendizaje humano a través de una arquitectura compleja que se asemeja al sistema nervioso humano.

En este artículo, revisaremos brevemente qué son las redes neuronales, cuáles son los pasos computacionales por los que pasa una red neuronal (sin profundizar en las matemáticas complejas que hay detrás) y cómo se pueden implementar usando Scikit-Learn, que es una popular biblioteca de IA para Python.

El sistema nervioso humano

El sistema nervioso humano consta de miles de millones de neuronas. Estas neuronas procesan colectivamente la entrada recibida de los órganos sensoriales, procesan la información y deciden qué hacer en reacción a la entrada. Una neurona típica en el sistema nervioso humano tiene tres partes principales: dendritas, núcleo y axones. La información que pasa a una neurona es recibida por las dendritas. El núcleo es el encargado de procesar esta información. La salida de una neurona pasa a otras neuronas a través del axón, que está conectado a las dendritas de otras neuronas más abajo en la red.

neuron{.img-responsive}

Perceptrones

Las redes neuronales artificiales están inspiradas en la arquitectura de la red neuronal humana. La red neuronal más simple consta de una sola neurona y se denomina perceptrón, como se muestra en la siguiente figura:

perceptron{.img-responsive}

Un perceptrón tiene una capa de entrada y una neurona. La capa de entrada actúa como las dendritas y es responsable de recibir las entradas. La cantidad de nodos en la capa de entrada es igual a la cantidad de entidades en el conjunto de datos de entrada. Cada entrada se multiplica con un peso (que normalmente se inicializa con algún valor aleatorio) y los resultados se suman. Luego, la suma se pasa a través de una [función de activación] (https://en.wikipedia.org/wiki/Activation_function). La función de activación de un perceptrón se asemeja al núcleo de una neurona del sistema nervioso humano. Procesa la información y produce una salida. En el caso de un perceptrón, esta salida es el resultado final. Sin embargo, en el caso de los perceptrones multicapa, la salida de las neuronas de la capa anterior sirve como entrada para las neuronas de la capa anterior.

Red neuronal artificial (perceptrón multicapa) {#perceptrón multicapa de red neuronal artificial}

Ahora que sabemos qué es un perceptrón de una sola capa, podemos extender esta discusión a los perceptrones multicapa, o más comúnmente conocidos como redes neuronales artificiales. Un perceptrón de una sola capa puede resolver problemas simples en los que los datos se pueden separar linealmente en 'n' dimensiones, donde 'n' es el número de características en el conjunto de datos. Sin embargo, en el caso de datos separables no linealmente, la precisión del perceptrón de una sola capa disminuye significativamente. Los perceptrones multicapa, por otro lado, pueden funcionar de manera eficiente con datos separables no lineales.

Los perceptrones multicapa, o más comúnmente conocidos como redes neuronales artificiales, son una combinación de múltiples neuronas conectadas en forma de red. Una red neuronal artificial tiene una capa de entrada, una o más capas ocultas y una capa de salida. Esto se muestra en la siguiente imagen:

neural network hidden layer{.img-responsive}

Una red neuronal se ejecuta en dos fases: Feed-Forward y Back Propagation.

Feedforward

Los siguientes son los pasos realizados durante la fase de feed-forward:

  1. Los valores recibidos en la capa de entrada se multiplican por los pesos. Se agrega un sesgo a la suma de las entradas y los pesos para evitar valores nulos.
  2. Cada neurona en la primera capa oculta recibe diferentes valores de la capa de entrada dependiendo de los pesos y sesgos. Las neuronas tienen una función de activación que opera sobre el valor recibido de la capa de entrada. La función de activación puede ser de muchos tipos, como función escalonada, función sigmoidea, función relu, o función [bronceado](https://brenocon .com/blog/2013/10/tanh-is-a-rescaled-logistic-sigmoid-function/). Como regla general, la función relu se usa en las neuronas de la capa oculta y la función sigmoidea se usa para la neurona de la capa de salida.
  3. Las salidas de las neuronas de la primera capa oculta se multiplican por los pesos de la segunda capa oculta; los resultados se suman y se pasan a las neuronas de las capas precedentes. Este proceso continúa hasta que se alcanza la capa exterior. Los valores calculados en la capa exterior son las salidas reales del algoritmo.

La fase de avance consiste en estos tres pasos. Sin embargo, la salida pronosticada no es necesariamente correcta de inmediato; puede estar mal, y tenemos que corregirlo. El propósito de un algoritmo de aprendizaje es hacer predicciones que sean lo más precisas posible. Para mejorar estos resultados previstos, una red neuronal pasará por una fase de propagación hacia atrás. Durante la retropropagación, los pesos de diferentes neuronas se actualizan de manera que la diferencia entre la salida deseada y la predicha sea lo más pequeña posible.

Propagación hacia atrás {#propagación hacia atrás}

La fase de retropropagación consta de los siguientes pasos:

  1. El error se calcula cuantificando la diferencia entre la salida prevista y la salida deseada. Esta diferencia se denomina "pérdida" y la función utilizada para calcular la diferencia se denomina "función de pérdida". Las funciones de pérdida pueden ser de diferentes tipos, p. Error cuadrático medio o funciones de entropía cruzada. Recuerda, las redes neuronales son algoritmos de aprendizaje supervisado que necesitan las salidas deseadas para un conjunto determinado de entradas, que es lo que le permite aprender de los datos.
  2. Una vez que se calcula el error, el siguiente paso es minimizar ese error. Para ello, se calcula la derivada parcial de la función de error con respecto a todos los pesos y sesgos. Esto se llama gradiente decente. Las derivadas se pueden usar para encontrar la pendiente de la función de error. Si la pendiente es positiva, se puede reducir el valor de los pesos o si la pendiente es negativa, se puede aumentar el valor del peso. Esto reduce el error general. La función que se utiliza para reducir este error se denomina función de optimización.

Este ciclo único de propagación hacia adelante y hacia atrás se denomina "época". Este proceso continúa hasta que se logra una precisión razonable. No existe un estándar para una precisión razonable, lo ideal es que se esfuerce por lograr una precisión del 100 %, pero esto es extremadamente difícil de lograr para cualquier conjunto de datos no trivial. En muchos casos, se considera aceptable una precisión superior al 90%, pero realmente depende de su caso de uso.

Implementación de una red neuronal con Scikit-Learn {#implementación de una red neuronal con Scikit-Learn}

Ahora sabemos qué son las redes neuronales y cuáles son los diferentes pasos que debemos realizar para construir una red neuronal simple y densamente conectada. En esta sección intentaremos construir una red neuronal simple que prediga la clase a la que pertenece una planta de lirio dada. Usaremos la biblioteca Scikit-Aprender de Python para crear nuestra red neuronal que realiza esta tarea de clasificación. Las instrucciones de descarga e instalación de la biblioteca Scikit-Learn están disponibles en: http://scikit-learn.org/stable/install.html

Nota: Los scripts proporcionados con este tutorial se ejecutaron y probaron en un cuaderno Python Jupyter.

Conjunto de datos

El conjunto de datos que vamos a utilizar para este tutorial es el popular conjunto de datos Iris, disponible en https://archive.ics.uci.edu/ml/datasets/iris. Los detalles del conjunto de datos están disponibles en el enlace mencionado anteriormente.

Saltemos directamente al código. El primer paso es importar este conjunto de datos a nuestro programa. Para ello, utilizaremos la biblioteca pandas de Python.

Ejecute el siguiente comando para cargar el conjunto de datos del iris en un marco de datos de Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd

# Location of dataset
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"

# Assign colum names to the dataset
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'Class']

# Read dataset to pandas dataframe
irisdata = pd.read_csv(url, names=names)

El script anterior simplemente descarga los datos del iris, asigna los nombres, es decir, 'longitud del sépalo', 'ancho del sépalo', 'longitud del pétalo', 'ancho del pétalo' y 'Clase\ ’ a las columnas del conjunto de datos y luego lo carga en el marco de datos irisdata.

Para ver cómo se ve realmente este conjunto de datos, ejecute el siguiente comando:

1
irisdata.head()

Ejecutar el script anterior mostrará las primeras cinco filas de nuestro conjunto de datos, como se muestra a continuación:

  sepal-length   sepal-width   petal-length   petal-width   Class

0 5.1 3.5 1.4 0.2 Iris-setosa 1 4.9 3.0 1.4 0.2 Iris-setosa 2 4.7 3.2 1.3 0.2 Iris-setosa 3 4.6 3.1 1.5 0.2 Iris-setosa 4 5.0 3.6 1.4 0.2 Iris-setosa

Preprocesamiento

Puede ver que nuestro conjunto de datos tiene cinco columnas. La tarea es predecir la clase (que son los valores en la quinta columna) a la que pertenece la planta de iris, que se basa en la longitud del sépalo, el ancho del sépalo, la longitud del pétalo y el ancho del pétalo (las primeras cuatro columnas) . El siguiente paso es dividir nuestro conjunto de datos en atributos y etiquetas. Ejecute el siguiente script para hacerlo:

1
2
3
4
5
# Assign data from first four columns to X variable
X = irisdata.iloc[:, 0:4]

# Assign data from first fifth columns to y variable
y = irisdata.select_dtypes(include=[object])

Para ver cómo se ve y, ejecute el siguiente código:

1
y.head()
  Class

0 Iris-sedoso 1 Iris-setosa 2 Iris-setosa 3 Iris-setosa 4 Iris-setosa

Puede ver que los valores en la serie y son categóricos. Sin embargo, las redes neuronales funcionan mejor con datos numéricos. Nuestra siguiente tarea es convertir estos valores categóricos en valores numéricos. Pero primero veamos cuántos valores únicos tenemos en nuestra serie y. Ejecute el siguiente script:

1
y.Class.unique()

Producción:

1
array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'], dtype=object)

Tenemos tres clases únicas 'Iris-setosa', 'Iris-versicolor' e 'Iris-virginica'. Vamos a convertir estos valores categóricos a valores numéricos. Para ello, utilizaremos la clase LabelEncoder de Scikit-Learn.

Ejecute el siguiente script:

1
2
3
4
from sklearn import preprocessing
le = preprocessing.LabelEncoder()

y = y.apply(le.fit_transform)

Ahora, si vuelve a comprobar los valores únicos en la serie y, verá los siguientes resultados:

1
array([0, 1, 2], dtype=int64)

Puede ver que los valores categóricos se han codificado en valores numéricos, es decir, 0, 1 y 2.

División de prueba de tren

Para evitar sobreajustado, dividiremos nuestro conjunto de datos en divisiones de entrenamiento y prueba. Los datos de entrenamiento se utilizarán para entrenar la red neuronal y los datos de prueba se utilizarán para evaluar el rendimiento de la red neuronal. Esto ayuda con el problema del ajuste excesivo porque estamos evaluando nuestra red neuronal en datos que no ha visto (es decir, en los que ha sido entrenado) antes.

Para crear divisiones de entrenamiento y prueba, ejecute el siguiente script:

1
2
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20)

El script anterior divide el 80 % del conjunto de datos en nuestro conjunto de entrenamiento y el otro 20 % en datos de prueba.

Escalado de funciones {#escalado de funciones}

Antes de hacer predicciones reales, siempre es una buena práctica escalar las características para que todas puedan evaluarse uniformemente. El escalado de características se realiza solo en los datos de entrenamiento y no en los datos de prueba. Esto se debe a que en el mundo real, los datos no se escalan y el propósito final de la red neuronal es hacer predicciones sobre datos del mundo real. Por lo tanto, tratamos de mantener nuestros datos de prueba lo más reales posible.

El siguiente script realiza el escalado de funciones:

1
2
3
4
5
6
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)

X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)

Entrenamiento y predicciones

Y ahora finalmente es el momento de hacer lo que ha estado esperando, entrenar una red neuronal que realmente pueda hacer predicciones. Para ello, ejecute el siguiente script:

1
2
3
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(hidden_layer_sizes=(10, 10, 10), max_iter=1000)
mlp.fit(X_train, y_train.values.ravel())

Sí, con Scikit-Learn, puede crear una red neuronal con estas tres líneas de código, que se encargan de gran parte del trabajo preliminar por usted. Veamos qué sucede en el script anterior. El primer paso es importar la clase MLPClassifier de la biblioteca sklearn.neural_network. En la segunda línea, esta clase se inicializa con dos parámetros.

El primer parámetro, hidden_layer_sizes, se utiliza para establecer el tamaño de las capas ocultas. En nuestro script crearemos tres capas de 10 nodos cada una. No existe una fórmula estándar para elegir la cantidad de capas y nodos para una red neuronal y varía bastante según el problema en cuestión. La mejor manera es probar diferentes combinaciones y ver cuál funciona mejor.

El segundo parámetro de MLPClassifier especifica el número de iteraciones, o las épocas, que desea que ejecute su red neuronal. Recuerde, una época es una combinación de un ciclo de fase de propagación hacia adelante y hacia atrás.

Por defecto, la función de activación 'relu' se utiliza con el optimizador de costes 'adam'. Sin embargo, puede cambiar estas funciones usando los parámetros activación y solucionador, respectivamente.

En la tercera línea, la función fit se usa para entrenar el algoritmo en nuestros datos de entrenamiento, es decir, X_train y y_train.

El paso final es hacer predicciones sobre nuestros datos de prueba. Para hacerlo, ejecute el siguiente script:

1
predictions = mlp.predict(X_test)

Evaluación del algoritmo

Creamos nuestro algoritmo e hicimos algunas predicciones sobre el conjunto de datos de prueba. Ahora es el momento de evaluar qué tan bien funciona nuestro algoritmo. Para evaluar un algoritmo, las métricas más utilizadas son una matriz de confusión, precisión, recuperación y puntaje f1. Los métodos confusion_matrix y classification_report de la biblioteca sklearn.metrics pueden ayudarnos a encontrar estas puntuaciones. El siguiente script genera un informe de evaluación para nuestro algoritmo:

1
2
3
from sklearn.metrics import classification_report, confusion_matrix
print(confusion_matrix(y_test,predictions))
print(classification_report(y_test,predictions))

Este código anterior genera el siguiente resultado:

1
2
3
4
5
6
7
8
9
[[11  0  0]
   0  8  0]
   0  1 10]]
             precision   recall   f1-score   support
          0       1.00     1.00       1.00        11
          1       0.89     1.00       0.94         8
          2       1.00     0.91       0.95        11

avg / total       0.97     0.97       0.97        30

Puede ver en la matriz de confusión que nuestra red neuronal solo clasificó incorrectamente una planta de las 30 plantas en las que probamos la red. Además, la puntuación f1 de 0,97 es muy buena, dado que solo teníamos 150 instancias para entrenar.

Sus resultados pueden ser ligeramente diferentes a estos porque train_test_split divide aleatoriamente los datos en conjuntos de entrenamiento y prueba, por lo que es posible que nuestras redes no hayan sido entrenadas/probadas con los mismos datos. Pero, en general, la precisión también debe ser superior al 90 % en sus conjuntos de datos.

Más información {#más información}

Este artículo apenas raspa la superficie de lo que es posible con las redes neuronales y la biblioteca Scikit-Learn Python. Si desea obtener una mejor comprensión de estos temas, le recomiendo que consulte los siguientes recursos:

Conclusión

En este artículo, brindamos una breve descripción general de lo que son las redes neuronales y explicamos cómo crear una red neuronal muy simple que se entrenó en el conjunto de datos del iris. Le recomendaría que intente jugar con la cantidad de capas ocultas, las funciones de activación y el tamaño de la división de entrenamiento y prueba para ver si puede lograr mejores resultados que los que presentamos aquí. amos aquí.