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.



domingo, 5 de febrero de 2017

Reducción de la Dimensión

Las técnicas de reducción de la dimensión están orientadas a detectar los atributos con un mayor poder predictivo de forma que se puedan discriminar aquellos que tienen un valor predictivo mayor de aquellos que simplemente generan ruido y consumen capacidad sin aportar prácticamente nada la modelo.

Son técnicas eminentemente estadísticas o teóricas que no pueden ser tomadas como "verdades absolutas" pero que en la mayoría de los casos aportan una información muy útil a la hora de optimizar el modelo.

Dentro de estas técnicas últimamente suena con mucha fuerza el denominado Análisis de Componentes Principales o PCA (de sus siglas en inglés Principal Component Analysis).

Esta técnica tiene una base estadística y su funcionamiento no es complejo aunque su base matemática no lo es tanto. Se trata de ordenar todos los atributos que componen los sucesos en un nuevo espacio de coordenadas (mediante transformaciones lineales) por orden descendente con respecto a la varianza que estos presentan.

Pero ¿porqué y cuando es útil es técnica?. Para que esta técnica sea efectiva debemos que asumir que gran parte de las variables están correlacionadas. Esto supondría que, realmente, al utilizar todas las variables en nuestro modelo, estamos incluyendo gran cantidad de información redundante y que, por tanto, podemos eliminarlas sin afectar significativamente a nuestra capacidad predictiva.

¿Pero cuales son los pasos para realizar un análisis PCA?

Primero: seleccionar un conjunto de sucesos representativos del sistema que queremos modelar. Recordemos que cada uno de estos sucesos esta representado por un vector (v) de dimensión N, siendo N el numero de atributos o valores que caracterizan dicho suceso.

Segundo: Seleccionamos la media de cada atributo (o dimensión) del conjunto de sucesos seleccionado.

Tercero: Calculamos la matriz de covarianza. Esto nos debe dar una matriz de NxN dimensiones.

Cuarto: Extraemos los autovectores y autovalores ( Eigenvectors y Eigenvalues) de la matriz de covarianza. Recordemos que un autovector es aquel que cuando se le aplica una transformación (en nuestro caso lineal) el resultado es un múltiplo de si mismo, no variando su dirección.

Quinto: Seleccionar aquellos autovectores que tengan un mayor autovalor y ordenarlos de forma descendente, esto es, el de mayor autovalor el primero y el menor el último.  Pero, ¿cuantos debemos seleccionar?

La proporción del autovalor con respecto a la suma de los autovalores nos dan la proporción de varianza que estamos asumiendo. Es recomendable incluir valores por encima del 90% pero todo dependerá de la perdida de precisión que podamos asumir.

Supongamos que tenemos 7 dimensiones, si con un análisis PCA obtenemos que con los 3 primeros autovalores obtenemos un 97,3% de la proporción, claramente la técnica resultará interesante ya que estamos reduciendo de 7 a 3 dimensiones con una perdida muy pequeña. 

Si por el contrario la varianza esta muy repartida y con estos tres primeros autovalores solo conseguimos el 70% es posible que la reducción de la dimensión no sea posible o interesante. 

Sexto: Calculamos la matriz Factorial. Esta tendrá tantas columnas como componentes principales y tantas filas como variables y cada elemento de ésta representa los coeficientes factoriales de las variables (las correlaciones entre las variables y los componentes principales).

Séptimo: Trasformación del conjunto de sucesos al nuevo dataset de trabajo.
Para ello, tomaremos las características seleccionadas en orden descendente de forma que cada autovector se corresponda con una columna y la multiplicaremos por el conjunto de sucesos (ajustado contra la media) y traspuesto.

A modo de ejemplo:
Si tenemos un conjunto de 2000 sucesos de 7 atributos tenemos una matriz de dimensión 2000 x 7.

Si seleccionamos 3 características principales tendremos una matriz de de dimensión  3 x 7 donde en las filas tendremos cada uno de los factores principales y en las columnas los atributos.

Si multiplicamos ambos de la siguiente forma:


New_dataset = Traspuesta ( Matriz_de_características_principales x Matriz_Traspuesta_sucesos ) =>

Que en dimensiones supone:

Traspuesta((3,7)xTraspuesta(2000,7)) = Traspuesta((3,7)x(7,2000)) = Traspuesta(3,2000) = (2000,3)

Esto es, un conjunto de 2000 sucesos reducido a 3 dimensiones, con una perdida mínima (2,9)% de la varianza o capacidad predictiva.


Próximos pasos:

Tras esto, en las próximas entradas del blog pasaremos a probar cada una de las técnicas expuestas anteriormente con distintos frameworks y validando de esta forma las bondadesy problemas de cada uno de ellos.

Para tal caso utilizaremos un PC MSI-PE60 6QE con las siguiente configuración:

  • Windows 10
  • Procesador i7-6700 HQ de 8 núcleos a 2,6 GHz
  • 16 G de RAM
  • Tarjeta Gráfica NVIDIA GTX960M
Y probaremos los siguientes frameworks de base sobre lenguaje Python 3.5:

  • Apache Spark 2.1.0
  • Tensorflow r0.12
  • SciKit learn 0.18.1
  • H2O.ai 3.10.0.9


martes, 3 de enero de 2017

Mejora de modelos: Transformación de atributos



Una vez vistos los distintos modelos de clasificación, clusterización o agrupamiento y las técnicas básicas de regresión se hace necesario abordar de forma breve un conjunto de técnicas necesarias que nos permitirán mejorar los resultados de nuestros modelos.

Transformación de atributos de vectores

El concepto es tan simple como útil. Se basa en transformar un atributo o conjunto de atributos en otros, linealmente dependientes de estos, que faciliten y mejoren la calidad de los resultados del modelo. A modo de ejemplo, una normalización de un valor en el rango de funcionamiento [0,500000] a un rango [-1,1].

Siempre que se realice una transformación de datos es muy importante asegurarse que se realiza dicha transformación de forma “idéntica” para todo conjunto de datos utilizados (training, test, aprendizaje y producción)

Estandarización: es una transformación simple utilizada para normalizar valores discordantes en distribuciones continuas. Para ello se utiliza la desviación estándar (standard deviation) como elemento normalizador.



Normalización: mientras que la estandarización unifica atributos de naturaleza continua, la normalización unifica sus rangos de trabajo. Es el caso presentado anteriormente.


Agregación: Es una práctica más común de lo que su nombre pueda indicar. Se basa en agregar, sobre distribuciones discretas, los valores menos frecuentes sobre un mismo conjunto o bucket, generando distribuciones más homogéneas. Este principio de agregación es la principal base de trasformación de variables continuas a discretas (discretización).

Imputación:  Técnica necesaria en el tratamiento de grandes conjuntos de datos utilizada para rellenar “huecos” o valores perdidos dentro de grandes conjuntos de datos. Normalmente se usa la media o la mediana para distribuciones continuas y la moda para distribuciones discretas.

Codificación binaria: El concepto es también sencillo y supone una gran mejorar en el uso de los recursos computacionales. Supongamos un atributo de naturaleza discreta con N valores distintos. La idea esas asignar esos valores a una palabra binaria de longitud N.
Así a modo de ejemplo, supongamos una distribución discreta de cuatro colores {verde, rojo, amarillo y azul}. La codificación binaria de esta distribución podría ser:
  • {verde:     1000}
  •  {rojo:       0100}
  • {amarillo: 0010}
  • {azul:       0001}


Discretización: Muchas veces es fácil trabajar con valores discretos que con valores continuos siempre y cuando la pérdida de calidad producida en el proceso sea asumible. La discretización es un proceso similar al de agregación, con la salvedad de que no se realiza solo para los valores menos frecuentes sino para toda la distribución.

El proceso es sencillo y se basa en dividir el rango de trabajo de la distribución en intervalos o subconjuntos más pequeños que serán, posteriormente tratados en su conjunto como clases discretas.
Para hacer esta división de intervalos hay dos enfoques predominantes.
  •          Intervalos de tamaño fijo
  •          Intervalos de frecuencia fija

Lo más importante cuando se realiza un proceso de discretización de un atributo de naturaleza continua es tener en cuenta el cumplimiento de los siguientes principios:
  •       El conjunto de valores o clases que se obtienen tras el proceso de discretización tiene que ser significativamente menor que el conjunto de valores distintos existentes en su naturaleza continua.
  •          Debe prevalecer y garantizarse la capacidad predictiva del atributo.