Introducción a Machine Learning con Python (Parte 2)

En la entrada anterior, Introducción a Machine Learning con Python (Parte 1), di unas pequeñas pinceladas sobre lo que es el Aprendizaje Automático con algunos ejemplos prácticos. Ahora vamos a adentrarnos en materia de un modo más estructurado viendo paso a paso algunas de las técnicas que podemos emplear en Python.

Podemos dividir los problemas de aprendizaje automático en dos grandes categorías (Pedregosa et al., 2011):

  • Aprendizaje supervisado, cuando el conjunto de datos viene con los atributos adicionales que queremos predecir. El problema puede clasificarse en dos categorías:
    • Regresión: los valores de salida consisten en una o más variables continuas. Un ejemplo es la predicción del valor de una casa en función de su superficie útil, número de habitaciones, cuartos de baños, etc.
    • Clasificación: las muestras pertenecen a dos o más clases y queremos aprender a partir de lo que ya conocemos cómo clasificar nuevas muestras. Tenemos como ejemplo el Iris dataset que ya mostramos en la entrada anterior
  • Aprendizaje no supervisado, cuando no hay un conocimiento a priori de las salidas que corresponden al conjunto de datos de entrada. En estos casos el objetivo es encontrar grupos mediante clustering o determinar una distribución de probabilidad sobre un conjunto de entrada.

Como vemos, en ambos casos el aprendizaje automático trata de aprender una serie de propiedades del conjunto de datos y aplicarlos a nuevos datos.

Ésta entrada se la vamos a dedicar al aprendizaje supervisado, acompañando cada una de las técnica que veamos con un Notebook de Jupyter.

Aprendizaje supervisado

Empezaremos por el principio, y lo más sencillo, que es ajustar los datos a una línea para pasar luego a ver diferentes modelos de clasificación en orden creciente de complejidad en subsiguientes entradas.

Sigue leyendo...

Introducción a Machine Learning con Python (Parte 1)

Desde que escuché hablar de Kaggle por primera vez, precisamente a través de Pybonacci, me entró curiosidad por eso del data science y me propuse como un reto el participar en una de sus competiciones. Para aquel que no la conozca todavía, Kaggle es una plataforma que aloja competiciones de análisis de datos y modelado predictivo donde compañías e investigadores aportan sus datos mientras que estadistas e ingenieros de datos de todo el mundo compiten por crear los mejores modelos de predicción o clasificación.

Muchas y muy diferentes técnicas se pueden aplicar al procesado de datos para generar predicciones, estimaciones o clasificaciones. Desde técnicas de regresión logística hasta redes neuronales artificiales pasando por redes bayesianas, máquinas de vectores de soporte o árboles de decisión, en Kaggle no descartan ningún método, e incluso se fomenta la cooperación entre personas con experiencia en diferentes campos para obtener el mejor modelo posible. Varias de estas técnicas se encuadran dentro de lo que es el Machine Learning, o aprendizaje automático, que nos explica Jeremy Howard en el siguiente vídeo.

Sigue leyendo... >

Basemap y Google Geocode para representar puntos sobre un mapa

Como siempre, en GitHub podréis encontrar el notebook y los ficheros adicionales que forman parte de éste artículo.

Las gráficas pueden proporcionar mucha información de un sólo vistazo, pero no siempre son el mejor método de representación. A veces es necesario dar un paso más; y ese es precisamente el caso que vamos a tratar en éste artículo.

Representar valores sobre un mapa geográfico nos permite ubicar la información sobre el terreno. Lo datos a representar pueden ser desde contornos de temperatura hasta vectores de velocidad del viento pasando por la identificación de puntos geográficos. Para facilitarnos todo ese trabajo —en Python— disponemos de una serie de librerías. PyNGL, CDAT o Basemap —del que Kiko ya ha hablado en ésta entrada— son librerías que nacieron para satisfacer las necesidades de ciertos colectivos de científicos, como meteorólogos u oceanógrafos.

En éste notebook vamos a utilizar:

Datos

Lo primero es tener claro qué es lo que vamos a representar. Eso nos permitirá definir el tipo de representación a utilizar, así como las características del mapa —su proyección y límites geográficos.

Circuitos de Formula 1

La Formula 1 —categoría reina del automovilísmo— comenzó sus andanzas en el año 1950, con una primera prueba en Silverstone. Desde entonces, y aunque su foco de actividad se encuentra principalmente en Europa, ha ido expandiéndose para celebrar Grandes Premios por todo el mundo.

En Wikipedia podemos encontrar una simple tabla con una lista de todos los circuitos que alguna vez han albergado un Gran Premio. La copiamos y generamos un fichero CSV o XLS en Excel o en un editor de texto como Notepad++.

import pandas as pd
data = pd.DataFrame.from_csv('F1-circuits.csv', header=0, sep=';', index_col=None, parse_dates=False, encoding='latin-1')
data.head(5)
Circuit Type Direction Location Current Length Grands Prix Season(s) Grands Prix held
0 Adelaide Street Circuit Street Clockwise Adelaide, Australia 3.780 km (2.349 mi) Australian Grand Prix 1985-1995 11
1 Ain-Diab Circuit Road Clockwise Casablanca, Morocco 7.618 km (4.734 mi) Moroccan Grand Prix 1958 1
2 Aintree Road Clockwise Liverpool, United Kingdom 4.828 km (3.000 mi) British Grand Prix 1955, 1957, 1959, 1961-1962 5
3 Albert Park Street Clockwise Melbourne, Australia 5.303 km (3.295 mi) Australian Grand Prix 1996-2014 19
4 AVUS Street Anti-clockwise Berlin, Germany 8.300 km (5.157 mi) German Grand Prix 1959 1

Google Geocode

Ya tenemos la tabla cargada con datos como el nombre del circuito, tipo y localización, así como el número de Grandes Premios albergados. Pero entre toda esa información no hay nada que le diga a Python dónde colocar el circuito en un mapa —aunque nosotros si sepamos ubicar Casablanca o Melbourne—. Aquí es donde entra en juego el API de codificación geográfica de Google.

La codificación geográfica es el proceso de transformar direcciones (como "1600 Amphitheatre Parkway, Mountain View, CA") en coordenadas geográficas (como 37.423021 de latitud y -122.083739 de longitud), que se pueden utilizar para colocar marcadores o situar el mapa. El API de codificación geográfica de Google proporciona una forma directa de acceder a un geocoder mediante solicitudes HTTP.

El uso del API de codificación geográfica de Google está sujeto a un límite de 2.500 solicitudes de codificación geográfica al día, más que suficientes para ubicar los cerca de 70 circuitos que han albergado alguna vez en su historia un Gran Premio de Fórmula 1.

Como bien indica la documentación, al geocoder se accede mediante solicitudes HTTP, y para ello nada mejor que Requests. Todas las consultas se realizan a una dirección HTTP http://maps.googleapis.com/maps/api/geocode/json a la que se añaden una serie de parámetros obligatorios:

  • address: es la dirección que quieres codificar de forma geográfica.
  • sensor: indica si la solicitud de codificación geográfica procede de un dispositivo con un sensor de ubicación. Este valor debe ser true o false.

Hay, además, una serie de parámetros opcionales, pero no nos harán falta.

La consulta —requests.get(url, params)— devuelve una respuesta en formato json donde se incluyen las coordenadas geográficas que buscamos. El formato json, Python lo interpreta como un conjunto de diccionarios y arrays, para lo que indicaremos los key y los índices hasta llegar al punto donde se encuentra la información que buscamos. En éste caso: ['results'][0]['geometry']['location'].

import requests
_GEOCODE_QUERY_URL = 'http://maps.googleapis.com/maps/api/geocode/json'
def geocode(address, sensor='false'):
    """
    Given a string 'address', return a dictionary of information about
    that location, including its latitude and longitude.
    """
    params = dict(address=address, sensor=sensor)
    response = requests.get(url=_GEOCODE_QUERY_URL, params=params)
    return response.json()
def address_to_latlng(address):
    """
    Given a string 'address', return a '(latitude, longitude)' pair.
    """
    location = geocode(address)['results'][0]['geometry']['location']
    return tuple(location.values())

Basemap

Basemap es un toolkit de matplotlib que nos facilita la tarea de representar información 2D sobre mapas. Ésta información pueden ser contornos, vectores o puntos entre otros como se puede ver en los ejemplos.

from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt

Lo primero, vamos a definir es el tipo de proyección a emplear. Hay un montón de ellas descritas en la Wikipedia, y en Basemap disponemos de 24 entre las que escoger. Para gustos, proyecciones.

En este caso hemos optado por Eckert IV, una proyección pseudocilíndrica, para representar el mapamundi y la Albers Equal Area projection para Europa. Dibujaremos las líneas de costa, las fronteras entre países, los paralelos y meridianos y le daremos un toque de color a los continentes. Basemap, además de disponer de una base de datos con información para representar líneas costeras y fronteras políticas, permite utilizar una imagen como fondo para el mapa. Entre las opciones que ofrece Basemap podemos encontrar el Blue Marble de la NASA —m.bluemarble()—. Aquí hemos optado por una imagen shaded relief de tonos claros con m.shadedrelief().

def basic_world_map(ax=None, region='world'):
    if region=='world':
        m = Basemap(resolution='i',projection='eck4',
                    lat_0=0,lon_0=0)
        # draw parallels and meridians.
        m.drawparallels(np.arange(-90.,91.,30.))
        m.drawmeridians(np.arange(-180.,181.,30.))
    elif region=='europe':
        m = Basemap(width=4000000,height=4000000,
                    resolution='l',projection='aea',\
                    lat_1=40.,lat_2=60,lon_0=10,lat_0=50)
        # draw parallels and meridians.
        m.drawparallels(np.arange(-90.,91.,10.))
        m.drawmeridians(np.arange(-180.,181.,10.))
        m.shadedrelief(scale=0.5)
    m.drawcoastlines()
    m.drawcountries()
    m.fillcontinents(color='coral', alpha=0.3)
    return m

Creamos un subplot y le asignamos un título a la figura. En esa figura vamos a representar las localizaciones de los circuitos con puntos con un área que vendrá determinada por el número de carreras disputadas —tanto mayor será el círculo cuantas más carreras se hayan disputado.

Para que los circulos no sean demasiado grandes —en Monza se han celebrado 64 Grandes Premios— limitaremos el radio del círculo a entre 3 y 20 puntos. Le damos a los cículos algo de transparencia con alpha=0.7, añadimos una nota de texto y guardamos la figura.

maximum = data['Grands Prix held'].max()
minimum = data['Grands Prix held'].min()
f, ax = plt.subplots(figsize=(20, 8))
ax.set_title('Formula 1 Grand Prix Circuits since 1950\n(Radius by number of races held)')
m = basic_world_map(ax)
for cir, loc, num in zip(data['Circuit'].values, data['Location'].values, data['Grands Prix held'].values):
    lat, lng = address_to_latlng(cir + ', ' + loc)
    x, y = m(lat, lng)
    m.scatter(x, y, s=np.pi * (3 + (num-minimum)/(maximum-minimum)*17)**2, marker='o', c='red', alpha=0.7)
ax.annotate(u'\N{COPYRIGHT SIGN} 2014, Pablo Fernandez', (0, 0))
f.savefig('f1-circuits.png', dpi=72, transparent=False, bbox_inches='tight')

f1-circuits

Podemos ver una gran concentración de Grandes Premios en Europa, continente que vio nacer a la Fórmula 1 y base de operaciones de la mayoría de equipos que compiten en ella. Si centramos la imagen sobre europa, a la cual hemos añadido un fondo, podremos ver con mayor claridad la distribución de las que han sido sedes de algún Gran Premio por el viejo continente.

f, ax = plt.subplots(figsize=(20, 8))
ax.set_title('Formula 1 Grand Prix Circuits in Europe since 1950\n(Radius by number of races held)')
m = basic_world_map(ax, 'europe')
for cir, loc, num in zip(data['Circuit'].values, data['Location'].values, data['Grands Prix held'].values):
    lat, lng = address_to_latlng(cir + ', ' + loc)
    x, y = m(lat, lng)
    m.scatter(x, y, s=np.pi * (3 + (num-minimum)/(maximum-minimum)*17)**2, marker='o', c='red', alpha=0.7)
ax.annotate(u'\N{COPYRIGHT SIGN} 2014, Pablo Fernandez', (100000, 100000))
f.savefig('f1-circuits-europe.png', dpi=72, transparent=False, bbox_inches='tight')

f1-circuits-europe

Gráficas de tiempo real con Plotly

Y aquí llega otra nueva entrada dedicada a Plotly, ésta vez sin notebook, para hacer una aplicación de «tiempo-real» que monitorice nuestra tarjeta gráfica. Por ahora, el código, que tenéis disponible en GitHub, hace uso de los datos que proporciona la NVIDIA System Management Interface. Pero éste método tiene un pega, y es que el SMI está pensado para la familia de gráficas Tesla™ y Quadro™, con el soporte a la gama GeForce™ está limitado a un par de parámetros. En éste caso aprovecharemos sólo temperatura y velocidad del ventilador.

Si abrimos una ventana de comandos y ejecutamos nvidia-smi nos debería aparecer una tabla como ésta ---es posible que tengamos que añadir la ruta al ejecutable a nuestro PATH, en mi caso 'C:\Program Files\NVIDIA Corporation\NVSMI'.

+------------------------------------------------------+
| NVIDIA-SMI 340.62     Driver Version: 340.62         |
|-------------------------------+----------------------+----------------------+
| GPU  Name            TCC/WDDM | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 460    WDDM  | 0000:01:00.0     N/A |                  N/A |
| 40%   45C   P12    N/A /  N/A |    986MiB /  1023MiB |     N/A      Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Compute processes:                                               GPU Memory |
|  GPU       PID  Process name                                     Usage      |
|=============================================================================|
|    0            Not Supported                                               |
+-----------------------------------------------------------------------------+

¡No te quedes sin ver el resto de la entrada!

Gráficas interactivas con Plotly

Podéis conseguir el notebook y los archivos asociados en GitHub.

En un artículo anterior ya vimos como pasar las gráficas creadas con matplotlib a plotly para obtener cierto nivel de interacción con los datos en la web. Ahora, lo que vamos a ver es cómo crear gráficas directamente con plotly. Para ello vamos a utilizar:

  • pandas 0.14.1, para trabajar con tablas de datos.
  • plotly 1.2.6, para crear las gráficas.

Importamos los paquetes de la manera habitual en cada caso.

import pandas as pd
import plotly.plotly as py
from plotly.graph_objs import *

En el caso de Plotly hemos importado además unos objetos graph_objs que nos serán de ayuda a la hora de crear las gráficas. Trabajar con la API de Plotly en Python se resume en trabajar con listas y diccionarios de Python. Los graph_objs de Plotly nos ayudarán a trabajar con estas listas y diccionarios proporcionando ayuda y validando los datos y parámetros introducidos.

Entre los objetos que importamos tenemos los bloques principales de trabajo:

  • Figure, diccionario que representa la figura a representar en plotly.
  • Data, lista para englobar todos los trazos a representar en una gráfica, ya sean tipo Scatter, Heatmap, Box, etc.
  • Layout, diccionario que define los detalles de la disposición de la figura.

Sigue leyendo

De matplotlib a la web con plotly

(También podéis seguir las explicaciones de este post con este Notebook)

En esta entrada trataremos de dar a conocer plotly, un servicio web gratuito para generar gráficos interactivos y que permite la creación de proyectos colaborativos.

Los paquetes que vamos a emplear son,

  • pandas 0.14.0, para importar y analizar estructuras de datos en Python.
  • matplotlib 1.3.1, para generar diversisos tipos de gráficos con calidad de imprenta.
  • plotly 1.1.2, para generar gráficos interactivos para la web.

Empezaremos por importar los datos con pandas. Podeis encontrar una introducción a pandas en éste post de Pybonacci.

Datos

La estructura de datos con la que vamos a trajar es un CSV con datos de telemetría correspondientes a un BMW Z4 GT3 compitiendo en el circuito Brands Hatch Indy. El paquete de datos nos lo ha proporcionado Steve Barker, un mecánico que pasa la mayor parte de su tiempo en la GP2. Pero aquí sólo vamos a trabajar con los datos de la vuelta 27 de la primera carrera.

Sigue leyendo!