A continuación veremos cómo podemos implementar RNA (Redes de Neuronas Artificiales) en Python. Para ello, utilizaremos la librería keras
sobre tensorflow
(que es lo más común).
Vamos a utilizar el conjunto de datos de inicio de diabetes de los indios Pima. Este es un conjunto de datos de Machine Learning estándar del repositorio de Machine Learning de UCI. Describe los datos de los registros médicos de los pacientes de los indios Pima y si tuvieron un inicio de diabetes dentro de los cinco años.
import pandas as pd
from sklearn.model_selection import train_test_split
total_data = pd.read_csv("https://raw.githubusercontent.com/4GeeksAcademy/machine-learning-content/master/assets/clean-pima-indians-diabetes.csv")
X = total_data.drop("8", axis = 1)
y = total_data["8"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 42)
X_train.head()
El conjunto train lo utilizaremos para entrenar el modelo, mientras que con el test lo evaluaremos para medir su grado de efectividad. Además, generalmente es una buena práctica normalizar los datos antes de entrenar una red neuronal artificial (RNA). Se pueden aplicar dos tipos: de 0 a 1 o de -1 a 1.
Los modelos en Keras se definen como una secuencia de capas. Creamos un modelo secuencial y agregamos capas una a una hasta que estemos satisfechos con nuestra arquitectura de red.
La capa de entrada siempre tendrá tantas neuronas como variables predictoras. En este caso, tenemos un total de 8 (de la 0 a la 7). A continuación, añadimos dos capas ocultas, una de 12 neuronas y otra de 8. Por último, la cuarta capa, de salida, tendrá una única neurona, ya que el problema es dicotómico. Si fuese de n
clases, la red tendría n
salidas.
Nota: Hemos creado una red por defecto con capas ocultas y neuronas en cada capa oculta aleatorias. Normalmente se suele empezar así y a continuación hacer una optimización de hiperparámetros.
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.utils import set_random_seed
set_random_seed(42)
model = Sequential()
model.add(Dense(12, input_shape = (8,), activation = "relu"))
model.add(Dense(8, activation = "relu"))
model.add(Dense(1, activation = "sigmoid"))
A continuación, una vez que el modelo está definido, podemos compilarlo. El backend elige automáticamente la mejor manera de representar la red para entrenar y hacer predicciones para ejecutar en su hardware, como CPU o GPU o incluso distribuido.
Al compilar, debemos especificar algunas propiedades adicionales requeridas al entrenar la red. Recordemos que entrenar una red significa encontrar el mejor conjunto de pesos para asignar entradas a salidas en nuestro conjunto de datos.
model.compile(loss = "binary_crossentropy", optimizer = "adam", metrics = ["accuracy"])
model
Definiremos el optimizador conocido como adam
. Esta es una versión popular del descenso de gradiente porque se sintoniza automáticamente y brinda buenos resultados en una amplia gama de problemas. Recopilaremos e informaremos la precisión de la clasificación, definida a través del argumento de las métricas.
El entrenamiento ocurre en épocas (epoch) y cada época se divide en lotes (batch).
El proceso de entrenamiento se ejecutará durante un número fijo de iteraciones, que son las épocas. También debemos establecer la cantidad de filas del conjunto de datos que se consideran antes de que se actualicen los pesos del modelo dentro de cada época, lo que se denomina tamaño de batch y se establece mediante el argumento batch_size
(tamaño_lote).
Para este problema, ejecutaremos una pequeña cantidad de epochs (150) y usaremos un tamaño de batch relativamente pequeño de 10:
# Ajustar el modelo de keras en el conjunto de datos
model.fit(X_train, y_train, epochs = 150, batch_size = 10)
_, accuracy = model.evaluate(X_train, y_train)
print(f"Accuracy: {accuracy}")
El tiempo de entrenamiento de un modelo dependerá, en primer lugar, del tamaño del conjunto de datos (instancias y características), y también de la tipología de modelo y su configuración.
El accuracy del conjunto de entrenamiento es de un 84,20%
.
y_pred = model.predict(X_test)
y_pred[:15]
Como vemos, el modelo no devuelve las clases 0
y 1
directamente, sino que requiere de un preprocesamiento previo:
y_pred_round = [round(x[0]) for x in y_pred]
y_pred_round[:15]
Con los datos en crudo es muy complicado saber si el modelo está acertando o no. Para ello, debemos compararlo con la realidad. Existe una gran cantidad de métricas para medir la efectividad de un modelo a la hora de predecir, entre ellas la precisión (accuracy), que es la fracción de predicciones que el modelo realizó correctamente.
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred_round)
Una vez tenemos el modelo que estábamos buscando (presumiblemente tras la optimización de hiperparámetros), para poder utilizarlo a futuro es necesario almacenarlo en nuestro directorio.
model.save("keras_8-12-8-1_42.keras")
Añadir un nombre explicativo al modelo es vital, ya que en el caso de perder el código que lo ha generado sabremos qué arquitectura tiene (en este caso decimos 8-12-8-1
porque tiene 8 neuronas en la capa de entrada, 12 y 8 en las dos capas ocultas y una neurona en la capa de salida) y además la semilla para replicar los componentes aleatorios del modelo, que en este caso lo hacemos añadiendo un número al nombre del archivo, el 42
.
A continuación se muestra un ejemplo simple de cómo entrenar una red neuronal para clasificar imágenes del dataset MNIST. MNIST es un conjunto de datos de imágenes de dígitos escritos a mano, desde 0 hasta 9.
from tensorflow.keras.datasets import mnist
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# Normalizar los datos (transformamos los valores de los píxeles de 0-255 a 0-1)
X_train, X_test = X_train / 255.0, X_test / 255.0
Los valores de los píxeles de las imágenes se normalizan para que estén en el rango de 0 a 1 en lugar de 0 a 255.
Se define la arquitectura de la red neuronal. En este caso, estamos utilizando un modelo secuencial simple con una capa de aplanamiento que transforma las imágenes 2D en vectores 1D, una capa densa con 128 neuronas y una capa de salida con 10 neuronas.
A continuación se proporciona una forma alternativa a la anterior para crear una RNA. Ambas son válidas:
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
from tensorflow.keras.utils import set_random_seed
set_random_seed(42)
model = Sequential([
# Capa que aplana la imagen de entrada de 28x28 píxeles a un vector de 784 elementos
Flatten(input_shape = (28, 28)),
# Capa oculta densa con 128 neuronas y función de activación ReLU
Dense(128, activation = "relu"),
# Capa de salida con 10 neuronas (una para cada dígito del 0 al 9)
Dense(10)
])
También añadimos el compilador de la red para definir el optimizador y la función de pérdida, como hicimos anteriormente:
from tensorflow.keras.losses import SparseCategoricalCrossentropy
model.compile(optimizer = "adam", loss = SparseCategoricalCrossentropy(from_logits = True), metrics = ["accuracy"])
Se entrena el modelo en el conjunto de entrenamiento durante un cierto número de épocas. Cuando se trabaja con imágenes es menos común utilizar el parámetro del batch_size
:
model.fit(X_train, y_train, epochs = 5)
_, accuracy = model.evaluate(X_train, y_train)
print(f"Accuracy: {accuracy}")
El tiempo de entrenamiento de un modelo dependerá, en primer lugar, del tamaño del conjunto de datos (instancias y características), y también de la tipología de modelo y su configuración.
test_loss, test_acc = model.evaluate(X_test, y_test, verbose=2)
print('\nTest accuracy:', test_acc)
Una vez tenemos el modelo que estábamos buscando (presumiblemente tras la optimización de hiperparámetros), para poder utilizarlo a futuro es necesario almacenarlo en nuestro directorio.
model.save("keras_28x28-128-10_42.keras")
Añadir un nombre explicativo al modelo es vital, ya que en el caso de perder el código que lo ha generado sabremos qué arquitectura tiene (en este caso ponemos 28x28-128-10
porque tiene una capa de entrada de 28 x 28 píxeles, 128 neuronas en la única capa oculta que tiene y 10 neuronas en la capa de salida) y además la semilla para replicar los componentes aleatorios del modelo, que en este caso lo hacemos añadiendo un número al nombre del archivo, el 42
.