Ahora trataremos parte muy importante del aprendizaje automático: la extracción de características cuantitativas a partir de los datos. Con este fin:
Recuerda que los datos en scikit-learn vienen en arrays de dos dimensiones con tamaño n_samples $\times$ n_features.
Anteriormente, vimos el dataset iris, que tienen 150 ejemplos y 4 características.
In [ ]:
from sklearn.datasets import load_iris
iris = load_iris()
print(iris.data.shape)
Las características son:
Las características numéricas como estas son directas: cada ejemplo contiene una lista de números con precisión real que se corresponden con las características.
¿Qué pasa si tenemos características categóricas?. Por ejemplo, imagina que disponemos del color de cada flor de iris: $color \in \{red, blue, purple\}$
Podrías estar tentado de usar algo así como i.e. red=1, blue=2, purple=3, pero, en general, esto es una mala idea. Los estimadores tienden a trabajar con la suposición de que las características numéricas se sitúan en una escala continua por lo que, en este ejemplo, 1 y 2 serían más parecidos que 1 y 3 y esto no tiene porque ser generalmente verdad.
De hecho, el ejemplo anterior es una subcategoría de las variables categóricas, en concreto, una variable nominal. Las variables nominales no tienen asociado un orden, mientras que las variables ordinales si que implican un orden. Por ejemplo, las tallas de las camisetas formarían una variable ordinal "XL > L > M > S".
Una forma de transformar variables nominales en un formato que prevenga al estimador de asumir un orden es la llamada representación $1$-$de$-$J$ (one-hot encoding). Cada categoría genera su propia variable por separado.
El conjunto de características aumentado sería:
Observa que al usar este conjunto de características puede que los datos se representen mejor usando matrices dispersas, como veremos en el ejemplo de clasificación de texto que analizaremos después.
Cuando los datos de entrada están codificados con un diccionario de tal forma que los valores son o cadenas o valores numéricos, se puede usar la clase DictVectorizer
para obtener la expansión booleana sin tocar las características numéricas:
In [ ]:
measurements = [
{'city': 'Dubai', 'temperature': 33.},
{'city': 'London', 'temperature': 12.},
{'city': 'San Francisco', 'temperature': 18.}
]
In [ ]:
from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer()
vec
In [ ]:
vec.fit_transform(measurements).toarray()
In [ ]:
vec.get_feature_names()
Otro tipo bastante común de características son las características derivadas, que son características obtenidas a partir de algún paso previo de preprocesamiento y que se supone que son más informativas que las originales. Este tipo de características pueden estar basadas en extracción de características y en reducción de la dimensionalidad (tales como PCA o aprendizaje de variedades) y pueden ser combinaciones lineales o no lineales de las características originales (como en regresión polinómica) o transformaciones más sofisticadas de las características.
Como un ejemplo de la forma en que se trabaja con datos numéricos y categóricos, vamos a realizar un ejercicio en el que predeciremos la supervivencia de los pasajeros del HMS Titanic.
Utilizaremos una versión del dataset Titatic que puede descargarse de titanic3.xls. Previamente, ya hemos convertido el .xls
a .csv
para que sea más fácil su manipulación (como texto), de manera que los datos no fueron modificados.
Necesitamos leer todas las líneas del fichero titanic3.csv
, ignorar la cabecera y encontrar las etiquetas (sobrevivió o murió) y los datos de entrada (características de la persona). Vamos a ver la cabecera y algunas líneas de ejemplo:
In [ ]:
import os
import pandas as pd
titanic = pd.read_csv(os.path.join('datasets', 'titanic3.csv'))
print(titanic.columns)
Aquí tenemos una descripción de lo que significan cada una de las variables:
pclass Passenger Class
(1 = 1st; 2 = 2nd; 3 = 3rd)
survival Survival
(0 = No; 1 = Yes)
name Name
sex Sex
age Age
sibsp Number of Siblings/Spouses Aboard
parch Number of Parents/Children Aboard
ticket Ticket Number
fare Passenger Fare
cabin Cabin
embarked Port of Embarkation
(C = Cherbourg; Q = Queenstown; S = Southampton)
boat Lifeboat
body Body Identification Number
home.dest Home/Destination
Parece que las variables name
, sex
, cabin
, embarked
, boat
, body
y homedest
son candidatas a ser variables categóricas, mientras que el resto parecen variables numéricas. Vamos a ver las primeras filas para tener un mejor conocimiento de la base de datos:
In [ ]:
titanic.head()
Podemos descartar directamente las columnas "boat" y "body" ya que está directamente relacionadas con que el pasajero sobreviviese. El nombre es (probablemente) único para cada persona y por tanto no es informativo. Vamos a intentar en primer lugar usar "pclass", "sibsp", "parch", "fare" y "embarked" como características:
In [ ]:
labels = titanic.survived.values
features = titanic[['pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked']]
In [ ]:
features.head()
En principio, los datos ahora solo contienen características útiles, pero no están en un formato que los algoritmos de aprendizaje automático puedan entender. Necesitamos transformar las cadenas "male" y "female" en variables binarias que indiquen el género y lo mismo para embarked
.Podemos hacer esto usando la función get_dummies
de pandas:
In [ ]:
pd.get_dummies(features).head()
Esta transformación ha codificado bien las columnas de cadenas. Sin embargo, parece que la variable pclass
también es una variable categórica. Podemos listar de forma explícita las variables que queremos codificar utilizando el parámetro columns
para incluir pclass
:
In [ ]:
features_dummies = pd.get_dummies(features, columns=['pclass', 'sex', 'embarked'])
features_dummies.head(n=16)
In [ ]:
#También podríamos hacerlo con DictVectorizer
from sklearn.feature_extraction import DictVectorizer
diccionario = features.to_dict('records')
vec = DictVectorizer()
dataset = vec.fit_transform(diccionario)
print(dataset.todense())
In [ ]:
data = features_dummies.values
In [ ]:
# Comprobamos que hay valores perdidos, tendremos que aplicar un Imputer
import numpy as np
np.isnan(data).any()
Una vez hemos hecho el trabajo de duro de cargar los datos, evaluar un clasificador con estos datos es directo. Vamos a ver que rendimiento obtenemos con el clasificador más simple, DummyClassifier('most_frequent')
, que es equivalente al ZeroR
.
In [ ]:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import Imputer
train_data, test_data, train_labels, test_labels = train_test_split(
data, labels, random_state=0)
imp = Imputer()
imp.fit(train_data)
train_data_finite = imp.transform(train_data)
test_data_finite = imp.transform(test_data)
In [ ]:
np.isnan(train_data_finite).any()
In [ ]:
from sklearn.dummy import DummyClassifier
clf = DummyClassifier('most_frequent')
clf.fit(train_data_finite, train_labels)
print("Accuracy: %f"
% clf.score(test_data_finite, test_labels))
In [ ]: