domingo, 5 de marzo de 2017

Clasificador MultiClase - SciKit Learn

En los próximos cuatro artículos del blog probaremos a realizar un experimento de clasificación lo suficientemente complejo para permitirnos probar las distintas tecnologías existentes en el mercado y las diferencias relativas a los siguientes 4 puntos:

1) Performance
2) Consumo de recursos
3) Dificultad para implementar la algorítmica asociada
4) Tiempo de aprendizaje

Para la realización del experimento utilizaremos un conjunto de datos de caracteres alfanuméricos en formato imagen (28x28) provistos por Google para la realización del curso "Deep Learning by Google" a través de la plataforma Udacity.

Es un conjunto de caracteres NOMINST en distintas formas y formatos simulando lo que sería un reconocimiento de escritura natural sobre 10 clases de caracteres y etiquetados por una clase de 0 a 9. de esta forma la correspondencia será:
A - 0
B - 1
C - 2
 D - 3 
E - 4
F - 5
G - 6
H - 7
I  - 8
J - 9

La elección de este tipo de conjunto de datos no es arbitraria o condicionada por ser un problema relativamente típico de Machine Lerarning. Para su tratamiento, las imágenes deben ser linealizadas y tratadas como un vector de 28x28 atributos, esto es, un problema complejo de análisis de instancias de 784 parámetros y con un patrón interno y estructural definido e interrelacionado.

Este problema se asemeja mucho a los problemas industriales a los que nos enfrentarnos y, sin embargo, es mucho más visual y didáctico que estos.

Para estos experimentos trabajaremos con un conjunto de 200.000 datos de entrenamiento y  dos conjuntos de validación y test de 10.000 sucesos. 

Todos los conjuntos han sido previamente mezclados aleatoriamente para el experimento y almacenados en fichero con formato cpickle para reproducir las mismas circunstancias en todos los experimentos.


Scikit-learn 0.18.1

Es una librería python orientada a la resolución de problemas complejos asociados al Datamining y al análisis avanzado de datos.

Esta librería ofrece soluciones para la resolución de los siguientes tipos de problemáticas:

  1. Clasificación
  2. Regresión
  3. Agrupación o clustering
  4. Reducción de la Dimensión
  5. Optimización de Modelos
  6. Preprocesado de datos

En nuestro caso utilizaremos el componente LogisticRegression para nuestro experimento y será propagado en las distintas tecnologías.


Inicialización



Lo primero que vamos a hacer es cargar las librerías y módulos necesarios.

from __future__ import print_function
import matplotlib.pyplot as plt
import numpy as np
import os
import sys
from IPython.display import display, Image
from scipy import ndimage
from six.moves import cPickle as pickle
import time

# Config the matplotlib backend as plotting inline in IPython
%matplotlib inline


Y cargar las imágenes y etiquetas previamente empaquetadas en nuestros vectores de trabajo.

os.chdir('d:/Data/Gdeeplearning-Udacity')
pickle_file = 'notMNIST.pickle'

with open(pickle_file, 'rb') as f:
  save = pickle.load(f)
  train_dataset = save['train_dataset']
  train_labels = save['train_labels']
  valid_dataset = save['valid_dataset']
  valid_labels = save['valid_labels']
  test_dataset = save['test_dataset']
  test_labels = save['test_labels']
  del save  # hint to help gc free up memory
  print('Training set', train_dataset.shape, train_labels.shape)
  print('Validation set', valid_dataset.shape, valid_labels.shape)
  print('Test set', test_dataset.shape, test_labels.shape)
Como vemos nuestras imágenes y etiquetas han sido perfectamente cargadas y estamos listos para trabajar con ellas.

Training set (200000, 28, 28) (200000,)
Validation set (10000, 28, 28) (10000,)
Test set (10000, 28, 28) (10000,)
 

Experimentos


Probaremos el algoritmos sobre tres ténicas de optimización (solvers) adecuados, según documentación, a Large Datasets. Veremos así su respuesta tanto en consumo de recursos (tiempo) como en resultados (performance)

Multiclass Logistic regresion sckitlearn con solver = lbfgs

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(solver='lbfgs',penalty='l2', max_iter=200)

start = time.time()
lr.fit(train_dataset.reshape(len(train_dataset),784),train_labels)
stop = time.time()
print('time %s' % (stop - start))
print('Valid Accuracy: %s'%lr.score(valid_dataset.reshape(len(valid_dataset),784),valid_labels))
print('Train Accuracy: %s'%lr.score(train_dataset.reshape(len(train_dataset),784),train_labels))
print('Test Accuracy: %s'%lr.score(test_dataset.reshape(len(test_dataset),784),test_labels))


Resultados:
time: 511.25009870529175
Valid Accuracy: 0.824
Train Accuracy: 0.831135
Test Accuracy: 0.8936



Validación:
Probemos ahora a analizar realmente como esta funcionando el clasificador y la complejidad a la que se enfrenta. Para ello seleccionaremos 9 imágenes al azar y comprobaremos que tal se comporta nuestro modelo.
random_index = np.random.randint(0,len(test_labels)-1,9)
actuals = test_labels[random_index]
predictions = lr.predict(test_dataset[random_index].reshape(len(random_index),784))
Nrows = 3
Ncols = 3
for i in range(9):
    plt.subplot(Nrows, Ncols, i+1)
    plt.imshow(test_dataset[random_index[i]].reshape(28,28), cmap='Greys_r')
    plt.title('Actual: ' + str(actuals[i]) + ' Pred: ' + str(predictions[i]),fontsize=10)
    frame = plt.gca()
    frame.axes.get_xaxis().set_visible(False)
    frame.axes.get_yaxis().set_visible(False)
plt.show()

Multiclass Logistic regresion sckitlearn con solver = sag


lr = LogisticRegression(solver='sag',penalty='l2', max_iter=200)

start = time.time()
lr.fit(train_dataset.reshape(len(train_dataset),784),train_labels)
stop = time.time()
print('time %s' % (stop - start))
print('Valid Accuracy: %s'%lr.score(valid_dataset.reshape(len(valid_dataset),784),valid_labels))
print('Train Accuracy: %s'%lr.score(train_dataset.reshape(len(train_dataset),784),train_labels))
print('Test Accuracy: %s'%lr.score(test_dataset.reshape(len(test_dataset),784),test_labels))

Resultados:
time: 942.5608134269714
Valid Accuracy: 0.8244
Train Accuracy: 0.83132
Test Accuracy: 0.8932
Validación:
# Plot the 9 of random results:
actuals = test_labels[random_index]
predictions = lr.predict(test_dataset[random_index].reshape(len(random_index),784))
Nrows = 3
Ncols = 3
for i in range(9):
    plt.subplot(Nrows, Ncols, i+1)
    plt.imshow(test_dataset[random_index[i]].reshape(28,28), cmap='Greys_r')
    plt.title('Actual: ' + str(actuals[i]) + ' Pred: ' + str(predictions[i]),fontsize=10)
    frame = plt.gca()
    frame.axes.get_xaxis().set_visible(False)
    frame.axes.get_yaxis().set_visible(False)
plt.show()

Multiclass Logistic regresion sckit-learn con solver = newton-cg

lr = LogisticRegression(solver='newton-cg',penalty='l2', max_iter=200)

start = time.time()
lr.fit(train_dataset.reshape(len(train_dataset),784),train_labels)
stop = time.time()
print('time %s' % (stop - start))
print('Valid Accuracy: %s'%lr.score(valid_dataset.reshape(len(valid_dataset),784),valid_labels))
print('Train Accuracy: %s'%lr.score(train_dataset.reshape(len(train_dataset),784),train_labels))
print('Test Accuracy: %s'%lr.score(test_dataset.reshape(len(test_dataset),784),test_labels))

Resultados:
time: 1919.3264200687408
Valid Accuracy: 0.8244
Train Accuracy: 0.83131
Test Accuracy: 0.8932
Validación:
# Plot the 9 of random results:
actuals = test_labels[random_index]
predictions = lr.predict(test_dataset[random_index].reshape(len(random_index),784))
Nrows = 3
Ncols = 3
for i in range(9):
    plt.subplot(Nrows, Ncols, i+1)
    plt.imshow(test_dataset[random_index[i]].reshape(28,28), cmap='Greys_r')
    plt.title('Actual: ' + str(actuals[i]) + ' Pred: ' + str(predictions[i]),fontsize=10)
    frame = plt.gca()
    frame.axes.get_xaxis().set_visible(False)
    frame.axes.get_yaxis().set_visible(False)
plt.show()

Desde el punto de vista de rendimiento

El algoritmo, desde el punto de vista de recursos presenta comportamientos similares para los solvers lbgfgs y sag no consiguiendo aprovechar la capacidad de CPU total disponible en la máquina y teniendo un consumo de memoria similar, como podemos ver en las siguientes imágenes



Sin embargo para el solver newton-ng podemos observar que el consumo de memoria es muy superior para obtener resultados similares.




Conclusión

Si bien la tecnología aportada por scikit-learn es muy sencilla de aprender, los resultados no son especialmente buenos a nivel de rendimiento con una tasa máxima de aciertos del 89% en el problema que estamos analizando.

Dado su bajo consumo de recursos uso puede ser adecuado para experimentos realizados en una estación de trabajo de uso común pero no parece ser la mejor opción para sistemas en producción donde la escalabilidad, el performance y la capacidad de adaptarse en recursos a nuevas problemáticas son condiciones indispensables.