Tu primera red neuronal con sklearn, reconociendo imágenes con números

Redes Neuronales con sklearn

En este post te ayudaremos a crear tu primera red neuronal para que puedas reconocer el número que hay escrito en una imagen. Es decir, crearemos una red neuronal cuya entrada sea una imagen donde hay un número escrito, y en la salida se nos devolverá dicho número. Si aún no sabes muy bien lo que es una red neuronal, te recomendamos empezar por aquí antes.

Por lo general en cualquier problema que queramos resolver aplicando machine learning o redes neuronales con aprendizaje supervisado, necesitaremos lo siguiente:

  • Necesitamos un dataset. Es decir, un conjunto de datos que podamos usar para entrenar nuestro modelo. Para nuestro caso serán imágenes de números.
  • Dado que estamos trabajando con aprendizaje supervisado, además del dataset necesitamos las etiquetas del mismo. Es decir, necesitamos saber que es lo que se considera correcto, y será lo que use nuestra red neuronal para aprender. En nuestro caso la etiqueta es simplemente el número que un humano ha verificado que hay en la imagen.
  • A menos que quieras programar todo desde cero, cosa que sería una pérdida de tiempo, es necesaria alguna librería que tenga funciones de machine learning.

Pues bien, usaremos la librería sklearn[1] y como dataset el famoso MNIST[2]. Para la representación de las imágenes usaremos matplotlib. ¡Vamos a por ello!

Importando el dataset

Antes de nada asegúrate de que tienes las librerías instaladas. Puedes usar el gestor de paquetes pip con el siguiente comando en el terminal para realizar la instalación.

pip install matplotlib
pip install sklearn

Una vez instalados, importamos el siguiente contenido:

  • Usaremos matplotlib para representar las imágenes.
  • La función fetch_openml nos permite descargar datos de la web openml. La usaremos para descargar el dataset.
  • Y el MLPClassifier[3] es una clase que define una red neuronal simple, llamada perceptrón multicapa.
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_openml
from sklearn.exceptions import ConvergenceWarning
from sklearn.neural_network import MLPClassifier

Una vez importado, descargamos el dataset MNIST con la siguiente función. Puede tardar un rato en descargar ya que es relativamente grande.

X, y = fetch_openml('mnist_784', version=1, return_X_y=True)
X = X / 255.

Una vez ejecutado el comando anterior, tendremos dos nuevas variables X e y que corresponden a las imágenes del dataset para la X y las etiquetas para cada una y, es decir, el número que tiene cada imagen.

Ambas variables son arrays de numpy, y lo puedes verificar de la siguiente manera.

print(type(X))
print(type(y))
#<class 'numpy.ndarray'>
#<class 'numpy.ndarray'>

Usando shape sobre cada array puedes ver las dimensiones. Tenemos un total de 70000 imágenes y cada imagen tiene 784 elementos, lo que corresponde a los píxeles de cada imagen (28x28).

print(X.shape)
print(y.shape)
# (70000, 784)
# (70000,)

Una vez sabido esto, vamos a realizar la típica división entre datos de entrenamiento y de test. Es muy importante entender esto. Estamos dividiendo nuestro dataset en dos fragmentos, el _train y el _test. El primero tiene las 60000 imágenes y el segundo tiene las restantes.

Es importante realizar esta división porque queremos entrenar el modelo con un determinado conjunto de datos para después evaluar su precisión con otro conjunto de datos no visto con anterioridad. No tendría mucho sentido evaluar la precisión del modelo con unos datos ya conocidos. Aquí está la gracia del machine learning. Buscamos modelos que sean capaces de generalizar, y que funcionen con datos no visto hasta la fecha.

# Dividimos el dataset en dos 60000 y 10000
X_train, X_test = X[:60000], X[60000:]
y_train, y_test = y[:60000], y[60000:]

Por si te quedas con dudas sobre las dimensiones, puedes consultarlas. Tenemos 60000 y 10000 imágenes para cada set.

print(X_train.shape)
print(X_test.shape)
# (60000, 784)
# (10000, 784)

Como hemos comentado anteriormente, en la X tenemos un conjunto de imágenes de números, por lo que vamos a ver la pinta que tienen. Tomaremos la primera imagen del set de training. Podemos hacerlo simplemente usando el índice 0 y usamos reshape para darle forma de imagen, ya que los datos están guardados en una dimensión.

# Mostramos la primer imagen de X_train
pixels = X_train[0].reshape((28, 28))
plt.imshow(pixels, cmap='gray')
plt.show()
print("En la imagen hay un número:", y_train[0])

# Mostramos otra imagen del set de training
pixels = X_train[12].reshape((28, 28))
plt.imshow(pixels, cmap='gray')
plt.show()
print("En la imagen hay un número:", y_train[12])

Una vez que tenemos los datos listos, sabemos como están organizados y hemos visto que parece que tienen sentido, podemos pasar al siguiente paso, entrenar el modelo.

Entrenando el modelo

Para entrenar nuestro modelo vamos a usar el MLPClassifier que nos proporciona sklearn. Se trata de una clase que permite trabajar con un perceptrón multicapa[4]. Podemos ver que recibe varios parámetros de entrada que explicamos a continuación:

  • Tenemos hidden_layer_sizes que es el número de capas ocultas de la red neuronal.
  • El número de iteraciones a realizar en epochs.
  • El alpha, conocido como regularization term.
  • El solver, que es el algoritmo utilizado para la optimización.
  • verbose indica si se imprime o no por pantalla la evolución.
  • random_state para la inicialiación aleatoria de pesos y bias.
  • learning_rate_init el learning rate. Cuidado porque un número muy alto podría hacer que nunca convergiera.
# Creamos el objeto de MLPClassifier
mlp = MLPClassifier(hidden_layer_sizes=(50,), max_iter=10, alpha=1e-4,
                    solver='sgd', verbose=10, random_state=1,
                    learning_rate_init=.1)

Una vez tenemos ya nuestro objeto, usamos fit para entrenar el modelo con los datos de training. Al tener el verbose activado veremos por pantalla el loss obtenido en cada iteración. Un loss decreciente indica que vamos por buen camino.

mlp.fit(X_train, y_train)
#Iteration 1, loss = 0.32009978
#Iteration 2, loss = 0.15347534
#Iteration 3, loss = 0.11544755
#Iteration 4, loss = 0.09279764
#Iteration 5, loss = 0.07889367
#Iteration 6, loss = 0.07170497
#Iteration 7, loss = 0.06282111
#Iteration 8, loss = 0.05530788
#Iteration 9, loss = 0.04960484
#Iteration 10, loss = 0.04645355

Una vez hemos entrenado el modelo, es muy importante ver como se comporta con datos aún no vistos. Es decir, queremos ver como se comporta ahora con X_test e y_test. Podemos hacerlo con score.

print("Score con training: %f" % mlp.score(X_train, y_train))
print("Score con test: %f" % mlp.score(X_test, y_test))

# Score con training: 0.986800
# Score con test: 0.970000

Suele ser normal que el score sea mayor con el dataset de training, ya que son datos que ya han sido vistos. Como podemos ver, tenemos una precisión del 97% con el set de prueba, algo que es bastante bueno. Esto significa que solo clasificamos mal 3 de cada 100 imágenes.

Jugando con los parámetros

Hemos visto como MLPClassifier puede ser inicializado con diferentes parámetros. Pues bien, la elección de estos parámetros es toda una ciencia, y a continuación te mostramos un par de ejemplos que prueban la importancia de elegirlos bien. Vamos a realizar diferentes pruebas para ver como afecta a nuestro score, es decir, como afecta a la precisión:

  • Modificamos max_iter=1. Score con test: 0.949400
  • Modificamos alpha=30. Score con test: 0.818200
  • Modificamos hidden_layer_size=3. Score con test: 0.655800

El código para estos tres ejemplos sería el siguiente. Empezamos cambiando el número de iteraciones. Menos iteraciones indican que realizamos un "menor número de intentos" para optimizar nuestra red. Por lo general cuantos más, mejor, pero se suele llegar siempre a un punto en el que deja se deja de mejorar.

# Modificamos max_iter=1
mlp = MLPClassifier(hidden_layer_sizes=(50,), max_iter=1, alpha=1e-4,
                    solver='sgd', verbose=10, random_state=1,
                    learning_rate_init=.1)
mlp.fit(X_train, y_train)
print("Score con training: %f" % mlp.score(X_train, y_train))
print("Score con test: %f" % mlp.score(X_test, y_test))
#Score con training: 0.949933
#Score con test: 0.949400

Por otro lado podemos cambiar el alpha.

# Modificaos alpha=30
mlp = MLPClassifier(hidden_layer_sizes=(50,), max_iter=10, alpha=30,
                    solver='sgd', verbose=10, random_state=1,
                    learning_rate_init=.1)
mlp.fit(X_train, y_train)
print("Score con training: %f" % mlp.score(X_train, y_train))
print("Score con test: %f" % mlp.score(X_test, y_test))
#Score con training: 0.813150
#Score con test: 0.818200

Y por último, se pueden cambiar el número de capas ocultas o hidden layers. Pocas capas representan una red más simple, pero que tal vez no sea capaz de clasificar correctamente las entradas.

# hidden_layer_sizes=3
mlp = MLPClassifier(hidden_layer_sizes=(3,), max_iter=10, alpha=1e-4,
                    solver='sgd', verbose=10, random_state=1,
                    learning_rate_init=.1)
mlp.fit(X_train, y_train)
print("Score con training: %f" % mlp.score(X_train, y_train))
print("Score con test: %f" % mlp.score(X_test, y_test))
#Score con training: 0.651617
#Score con test: 0.655800

Usando el modelo

Por último, imagínate que tienes una fotografía tuya con un número escrito y quieres hacer uso de la red neuronal que acabamos de entrenar.

Deberías de hacer un pequeño procesado para adaptar la imagen a nuestro modelo, ya que solo acepta imágenes en blanco y negro de 28x28 píxeles.

Una vez hecho, puedes hacer lo siguiente. En este ejemplo usamos una imagen existente en el dataset.

# Tomamos una imagen
mi_imagen = X_test[30]

# La mostramos por pantalla
pixels = mi_imagen.reshape((28, 28))
plt.imshow(pixels, cmap='gray')
plt.show()

# Usamos predict para saber que número hay
prediccion = mlp.predict([mi_imagen])
print(prediccion)
# ['3']

Como se puede ver, haciendo uso de predict y pasando como entrada la imagen, se nos devuelve el número que nuestra red neuronal cree que hay. Para el ejemplo anterior se ve como efectivamente es 3.

Realzamos lo mismo para otra imagen, y vemos como predict devuelve en este caso 6.

# Probamos con otra imagen
mi_imagen = X_test[50]

pixels = mi_imagen.reshape((28, 28))
plt.imshow(pixels, cmap='gray')
plt.show()

prediccion = mlp.predict([mi_imagen])
print(prediccion)
# ['6']

Por último, si has leído este otro post donde hablábamos de los pesos de la red neuronal, tal vez te preguntes donde están. Pues bien, con el siguiente código podrías acceder a todos los pesos que sklearn ha buscado para que esta red neuronal reconozca el número en las imágenes.

print(mlp.coefs_)
print(mlp.intercepts_)

for coef in mlp.coefs_:
    for c in coef:
        print(c)

Tal vez te interesen otros de nuestros posts sobre sklearn:

¡Deja un comentario!

avatar
  Subscribe  
Notify of