Capitulo 2
Capitulo 2
Capitulo 2
ipynb - Colaboratory
http://archive.ics.uci.edu/ml/index.php
https://www.kaggle.com/datasets
https://registry.opendata.aws/
Meta portales
http://dataportals.org/
http://opendatamonitor.eu/
http://quandl.com/
Otras
https://en.wikipedia.org/wiki/List_of_datasets_for_machine-learning_research
https://www.quora.com/Where-can-I-find-large-datasets-open-to-the-public
https://www.reddit.com/r/datasets/
Para los ejemplos de este capítulo usaremos California Housing Prices dataset from the StatLib
repository
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 1/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Delimitando el problema
Probablemente la meta final no sea construir un sistema. Sino la utilidad de este.
La salida de
un sistema puede alimentar a otro sistema ML, junto con otras variables.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 2/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Obviamente es aprendizaje supervisado dado que se dan ejemplos etiquetados (cada instancia
llega con una salida determinada)
Es un problema de regresión múltiple. Dado que el sistema utilizará muchas características para
hacer la predicción.
No hay un flujo continuo de datos por lo que no es necesario ajustarlo rápidamente y los datos
son pequeños como para entrar en memoria por lo que el plan es aprendizaje por lotes.
1 (i) (i) 2
RM S E (X, h) = ∑(h(x ) − y )
m
i=1
x
(i)
es un vector de todos los valores (excluyendo la etiqueta) de la i-esima instancia en el
dataset, y y (i) es la etiqueta (la salida deseada del valor de la instancia).
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 3/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
⎜ 33.91 ⎟
entonces: x(1) = ⎜ ⎟
y y (1) = 156400
⎜ 1416 ⎟
⎝ ⎠
38372
X es una matriz que contiene todos los valores de las características de todas las
instancias en el dataset. Hay una fila por cada instancia y la i-esima fila es igual a la
transpuesta de x(i)
⎜ (x (2) T ⎟
)
⎜ ⎟
⎜ ⎟ −118.29 33.91 1416 38372
X = ⎜ ... ⎟ = ( )
⎜ ⎟
...
⎜ (1999) T ⎟
⎜ (x ) ⎟
⎝ (2000) T ⎠
(x )
1 (i) (i)
M AE (X, h) = | ∑(h(x ) − y )|
m
i=1
Ambas métricas son formas de medir la distancia entre dos vectores, por lo que se pueden usar
varias medidas de distancia.
Calcular la suma de los valores absolutos MAE, corresponde a la distancia de Manhattan por
que es como medir la distancia entres dos puntos de una ciudad si solo se puede viajar por
cuadras ortogonales.
RMSE es mas sensible a los valores atípicos que MAE, pero cuando los valores atípicos son
exponencialmente raros RMSE trabaja mejor
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 4/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Descargando la data
los datos pueden estar en una base de datos relacional o en múltiples documentos como CSV
[ ] ↳ 3 celdas ocultas
housing.head()
El método info() es útil para obtener una descripción rápida de los datos
housing.info()
<class 'pandas.core.frame.DataFrame'>
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 5/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Hay 20640 instancias en el dataset, lo cual es bastante pequeño para los estándares de
Machine Learning, pero es perfecto para un ejemplo. Note que el total de filas de
total_bedrooms solo tiene 20433 valores no nulos, lo que significa que 207 distritos están
perdidos.
Todos los atributos son numéricos excepto ocean_proximity . Este es de tipo object que
puede contener cualquier objeto Python. Pero dado que lo cargamos desde un CSV, sabemos
que es de tipo texto. Este atributo es un valor que se repite, lo cual significa que probablemente
es un atributo categórico. Podemos consultar cuántas categorías existen usando el método
value_counts() .
housing["ocean_proximity"].value_counts()
INLAND 6551
ISLAND 5
housing.describe()
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 6/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
mean nulos
Los valores -119.569704 35.631861
son ignorados. La fila std muestra 28.639486
la desviación2635
estándar, la cual mide qué
tan dispersos
std estan los valores. Las
2.003532 filas 25% a 75% muestran
2.135952 12.585558los 2181
percentiles que indican el
valor debajo
min del-124.350000
cual se ubica un32.540000
porcentaje dado de observaciones.
1.000000 Los
2 percentiles 25%, 50% Y
75% se llaman quartiles.
25% -121.800000 33.930000 18.000000 1447
Otra forma de observar los datos es mediante un gráfico de histograma para cada atributo
numérico. Un histograma muestra la cantidad de instancia (en el eje vertical) que tiene un valor
dado en el rango (en el eje horizontal). Se puede llamar al método hist() en el dataset y se
graficará un histograma por cada atributo numérico.
import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(20,15))
plt.show()
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 7/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
1. El ingreso medio median_income está escalado y limitados a 15 para los ingresos más
altos y a 0.5 para los ingresos menores. Los números representan aproximadamente
decenas de miles de dólares (p.ejm. 3 significa $30000). Trabajar con atributos
preprocesados es bastante común en Machien Learning, esto no es necesariamente un
problema, pero debes tratar de entender cómo se calcularon los datos.
2. La antiguedad media, ( housing_media_age ) y el valor medio de la casa
( median_house_value ) también están acotados, esto puede ser un problema ya que el
valor de la casa es nuestra variable de salida (etiqueta). Tu algoritmo de Machine Learning
podría aprender que los valores de las casas nunca van más allá de este límite. Necesitas
verificar con tu cliente si este es un problema o no. Si te dicen que necesitan hacer
predicciones mas allá del límite, entonces puedes:
Recolectar etiquetas apropiadas para los distritos cuyas etiquetas fueron limitadas o
remover los distritos que estén fuera del rango del conjunto de entrenamiento.
3. Los atributos tienen diferentes escalas. Discutiremos esto luego.
4. Muchos histogramas tienen colas pesadas, que se extienden mas a la derecha de la
media. Esto puede ser duro para algunos algoritmos de Machine Learning para detectar
patrones. Trataremos de transformar estos atributos luego para tener distribuciones más
normales (forma de campana).
Puede parecer extraño intentar partir el datase en este momento. Despues de todo, solamente
hemos dado un pequeño vistazo de los datos, y seguramente deberías aprender mucho más
antes de decidir qué algoritmo usar. Esto es cierto, pero tu cerebro es un maravilloso sistema de
detección de patrones, lo cual significa que es áltamente propensa a sobreentrenarse: si
observas el conjunto de prueba, puedes tropezar con algun patron interesante en los datos de
prueba que te permita seleccionar un tipo particular de modelo de Machine Learning. Cuando
estimas el error de generalización usando el conjunto de prueba, tu estimación será muy
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 8/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
obtimista, y y lanzarás un sistema que no se desempeñará tan bien como esperas. A esto se le
llama sesgo data snooping.
import numpy as np
np.random.seed(42)
# Solo para ilustración. Sklearn tiene un train_test_split()
def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
return data.iloc[train_indices], data.iloc[test_indices]
train_set, test_set = split_train_test(housing, 0.2)
len(train_set)
16512
len(test_set)
4128
Esto funciona pero no muy bien, si corres el programa otra vez, se generará un conjunto de
datos diferente. Con el tiempo se usará el dataset entero y eso es algo que queremos evitar.
Una solución es guardar este conjunto de datos. Otra es inicializar la semilla del generador de
números aleatorios con un valor fijo (p.ej. np.random.seed(42) ) antes de llamar a
np.random.permutation() de forma que siempre se generen la misma mezcla de índices.
Pero ambas opciones pueden fallar la siguiente vez que actualices el dataset. Para tener una
partición estable entrenamiento/prueba, una solución es usar un identificador de instancia para
decidir si debe ir o no en el conjunto de prueba (asumiendo que las instancias tienen un
identificador inmutable y único). Por ejemplo podrías calcular un hash de cada identificador de
instancia y poner esta instancia en el conjunto de prueba, si el valor del hash es menor o igual al
20% del máximo valor de hash. Esto asegura que el conjunto de prueba permanezca
consistente, a lo largo de varias corridas, aún si refrescas el dataset. El nuevo conjunto de
prueba contendrá el 20% de las instancias, pero no contendrá ninguna instancia que haya
estado previamente en el conjunto de entrenamiento.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 9/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
from zlib import crc32
def test_set_check(identifier, test_ratio):
return crc32(np.int64(identifier)) & 0xffffffff < test_ratio * 2**32
def split_train_test_by_id(data, test_ratio, id_column):
ids = data[id_column]
in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio))
return data.loc[~in_test_set], data.loc[in_test_set]
housing_with_id = housing.reset_index() # adiciona una columna `index`
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")
Si usasa el índice de fial como un identificador único, necesitas asegura que los nuevos datos
se agreguen al final del dataset y que ninguna fila sea borrada. Si esto no es posible, puedes
tratar de usar las características más estables para construir un identificador único. Por
ejemplo, la latitud y longitud de un distrito permanecerán estables por algunos millones de años,
de modo que podrías combinarlos en un ID así:
housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
Por ahora hemos considerado unicamente métodos de muestreo aleatorio. Esto funciona bien si
nuestro dataset es bastante grande, pero si no se corre el riesgo de introducir un sesgo (bias)
significativo.
Como por ejemplo si llamamos a 1000 personas para preguntarles su preferencia
de voto, corremos el riesgo de llamar a mayor cantidad de hombres o de sector A o B.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 10/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Suponiendo que hablas con expertos y determinas que el ingreso medio es un atributo
importante para predecir los precios de las casas. Dado que el ingreso medio es un atributo
numérico contínuo, necesitamos crear primero un atributo categoría.
bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
labels = [1,2,3,4,5])
housing["income_cat"].hist()
<matplotlib.axes._subplots.AxesSubplot at 0x7f6b07bb8a90>
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
Vamos a ver si trabaja como esperamos. Podemos empezar viendo las proporciones del
conjunto de prueba
strat_test_set["income_cat"].value_counts() / len(strat_test_set)
3 0.350533
2 0.318798
4 0.176357
5 0.114341
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 11/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
1 0.039971
Ahora debemos remover el atributo income_cat de modo que los datos vuelvan a su estado
original
strat_train_set.drop("income_cat", axis=1, inplace=True)
strat_test_set.drop("income_cat", axis=1, inplace=True)
strat_test_set.info()
<class 'pandas.core.frame.DataFrame'>
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 12/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Hemos empleado un poco de tiempo en la generación del conjunto de prueba; esta es una parte
crítica del proyecto de Machine Learning. Muchas de estas ideas serán útiles cuando
discutamos cross-validation.
Luego de dar una mirada a los datos, ahora vamos a profundizar en ellos.
Primero debes estar
seguro de poner a un lado el conjunto de prueba y explorar solamente el conjunto de
entrenamiento. Además, si el conjunto de entrenamiento es muy grande, puedes hacer un
conjunto de exploración para hacer manipulaciones fácilmente. En nuestro caso el conjunto es
muy pequeño de modo que podemos trabajar directamente en el conjunto completo. Vamos a
crear una copia que podamos corren sin perjudicar el conjunto de entrenamiento
housing = strat_train_set.copy()
Dado que hay información geográfica (latitud y longitud), es una buena idea crear un gráfico de
dispersión de todos los distritos para visualizar los datos.
housing.plot(kind = "scatter", x="longitude", y="latitude")
<matplotlib.axes._subplots.AxesSubplot at 0x7f6b167244d0>
Esto luce como el estado de California, pero es dificil ver un patrón en particular. Estableciendo
el alpha a 0.1 resulta mas facil visualizar los lugares en una alta densidad de puntos
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 13/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.05)
<matplotlib.axes._subplots.AxesSubplot at 0x7f6b07a1f950>
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,
s=housing["population"]/100, label="population", figsize=(10,7),
c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True,
plt.legend()
<matplotlib.legend.Legend at 0x7f6b07a995d0>
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 14/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Buscando correlaciones
Dado que el dataset no es muy largo, puedes facilmente calcular el coeficiente de correlación
(también llamado r de Pearson) entre cada par de atributos usando el método
corr_matrix = housing.corr()
Ahora vamos a ver cuántos atributos están correlacionados con el valor medio de las casas
corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687151
total_rooms 0.135140
housing_median_age 0.114146
households 0.064590
total_bedrooms 0.047781
population -0.026882
longitude -0.047466
latitude -0.142673
Otra forma de verificar las correlaciones entre los atributos es usar la función scatter_matrix
de Pandas, el cual dibuja cada atributo numérico contra los otros atributos numéricos.
from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
"housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12,8))
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 15/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
El atributo más prometedor para predecir el valor medio de una casa es el ingreso medio,
hagamos zoom en su propio gráfico de dispersión.
housing.plot(kind="scatter", x="median_income", y="median_house_value",
alpha=0.1)
<matplotlib.axes._subplots.AxesSubplot at 0x7f6b16f05150>
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 16/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Este gráfico revela algunas cosas. Primero, la correlación parece ser muy fuerte, se puede
apreciar la tendencia y los puntos no están muy dispersos. Segundo, se puede ver una linea
horizontal en 500,000. Pero este gráfico revela otras líneas menos obvias: una linea horizontal
en 450,000, otra por 350,00, incluso una en 280,000. Podríamos desear remover estos distritos
para prevenir que el algoritmo aprenda estas peculiaridades.
Una última cosa que podríamos hacer antes de preparar los datos para el algoritmo de Machine
Learning es intentar varias combinaciones de atributos. Por ejemplo, el número total de cuartos
en un distrito no es muy util si no sabemos cuantos hogares hay. Lo que realmente queremos es
el número de cuartos por hogar. De manera similar, el número total de dormitorios por si mismo
no es útil por si mismo; probablemente queremos compararlo con el número de habitaciones.
La población por hogar también parece ser una interesante combinación de atributos para
observar.
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value 1.000000
median_income 0.687151
rooms_per_household 0.146255
total_rooms 0.135140
housing_median_age 0.114146
households 0.064590
total_bedrooms 0.047781
population_per_household -0.021991
population -0.026882
longitude -0.047466
latitude -0.142673
bedrooms_per_room -0.259952
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 17/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Esta ronda de exploración no tiene que ser exhaustiva; la idea es empezar con el pie derecho y
rápidamente ganar perspectiva que ayuden a obtener un primer prototipo razonable. Pero este
es un proceso iterativo: una vez que el prototipo está corriendo, puedes analizar su salida para
ganar más perspectiva y volver al paso de exploración.
Ahora vamos a preparar los datos para tu algorimo de Machine Learning. En lugar de hacer esto
manualmente, deberías escribir funciones que hagan esto por varias razones:
Pero primero vamos a revertir a un conjunto de entrenamiento limpio, separando los predictores
de las etiquetas dado que no necesariamente queremos aplicar las mismas transformaciones a
los predictores y los valores objetivo, drop() crea una copia de los datos sin afectar a
strat_train_set :
housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
Limpieza de datos
La mayoría de algoritmos de Machine Learning no trabajan con características perdidas, vamos
a crear algunas funciones que cuiden esto. Has notado que el atributo total_bedrooms tiene
algunos valores perdidos, tenemos que arreglar esto, hay tres opciones:
Podemos cumplir esto rápidamente usando los métodos dropna() , drop() , y fillna() :
housing.dropna(subset=["total_bedrooms"]) #opción 1
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 18/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Scikit_learn proporciona una clase para manejar valores fallados: SimpleImputer . Primero
necesitas crear una instancia de SimpleImputer , especificando que quieres reemplazar el valor
faltante del atributo con la media del atributo:
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy = "median")
Dado que la mediana puede calcularse solamente en valores numéricos, necesitamos crear una
copia de los datos sin el atributo de texto ocean_proximity :
housing_num = housing.drop("ocean_proximity", axis=1)
Ahora podemos entrenar la instancia imputer a los datos de entrenamiento usando el método
fit() :
imputer.fit(housing_num)
SimpleImputer(strategy='median')
imputer.statistics_
housing_num.median().values
X = imputer.transform(housing_num)
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 19/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
housing_tr = pd.DataFrame(X, columns=housing_num.columns)
housing_cat = housing[["ocean_proximity"]]
housing_cat.head(10)
ocean_proximity
12655 INLAND
2908 INLAND
from sklearn.preprocessing import OrdinalEncoder
ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10] #mostrando las 10 primeras filas
array([[1.],
[4.],
[1.],
[4.],
[0.],
[3.],
[0.],
[0.],
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 20/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
[0.],
[0.]])
Puedes obtener la lista de categorías usando la instancia de variable categories_ . Esta es una
lista que contiene un array unidimensional de categorías para cada atributo categórico:
ordinal_encoder.categories_
dtype=object)]
Un problema con esta representación es que el algoritmo de ML asumirá que dos valores
cercanos son más similares que dos valores distantes. Esto puede ser bueno en algunos casos
(p.ejm. para categorías ordinales como "malo", "promedio", "bueno", "excelente"), pero este
obviamente no es el caso de la columna ocean_proximity (por ejemplo, las categorías 0 y 4 son
claramente más similares que las categorías 0 y 1). Para arreglar este problema, una solución
común es crear un atributo binario por categoría: un atributo igual a 1 cuando la categoría es "
<1H OCEAN" (y 0 de otra forma), otro atributo igual a 1 cuando la categoría es "INLAND" (y 0 de
otra forma), y así. Esto se llama codificación one-hot, porque solo un atributo será igual a 1
(hot), mientras que los otros serán 0 (cold). Los nuevos atributos son algunas veces llamados
atributos dummy. Scikit-Learn proporciona una clase OneHotEncoder para convertir valores
categóricos en vectores one-hot:
from sklearn.preprocessing import OneHotEncoder
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
Nota que la salida es una matriz poco densa de SciPy, en lugar de un array NumPy. Esto es útil
cuando tienes atributos categóricos con cientos de categorías. Después de una codificación
one-hot obtenemos una matriz con cientos de columnas, y la matriz está llena de ceros excepto
por un sólo 1 por columna. Usa grandes cantidades de memoria para almacenar ceros que es
un desperdicio, en su lugar una matriz de dispersión solo almacena la ubicación de los
elementos que no son cero. Pueds usar esta como un array 2D normal, pero si quieres
realmente convertirla a un NumPy, hay que llamar al método toarray() :
housing_cat_1hot.toarray()
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 21/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
...,
Una vez mas puedes obtener la lista de categorías usando la variable categories_ :
cat_encoder.categories_
dtype=object)]
Transformadores personalizados
A pesar de que Scikit-Learn proporciona muchos transformadores útiles, necesitas escribir tus
propias tareas tales como operaciones de limpieza personalizados o combinación de atributos
específicos. Por ejemplo aquí hay un pequeña clase transformadora que agrega los atributos
combinados que discutimos antes:
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, add_bedrooms_per_room = True): # no *args or **kargs
self.add_bedrooms_per_room = add_bedrooms_per_room
def fit(self, X, y=None):
return self # nada mas que hacer
def transform(self, X, y=None):
rooms_per_household = X[:, rooms_ix] / X[:, households_ix]
population_per_household = X[:, population_ix] / X[:, households_ix]
if self.add_bedrooms_per_room:
bedrooms_per_room = X[:, bedrooms_ix] / X[:, rooms_ix]
return np.c_[X, rooms_per_household, population_per_household,
bedrooms_per_room]
else:
return np.c_[X, rooms_per_household, population_per_household]
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 22/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Escalando características
Con pocas excepciones, los algoritmos de Machine Learning no funcionan bien cuando los
valores de las entradas tienen diferentes escalas. Este es el caso de para nuestros datos: el
número total de habitaciones van desde 6 hasta 39,320 mientras que la media de ingresos está
en el rango de 0 a 15. Notese que escalar los valores del objetivo generalmente no se requiere.
Hay dos formas comunes de obtener atributos que tengan la misma escala
Pipelines de transformación
Como hemos visto, hay muchos pasos de transformación de datos que necesitan ser
ejecutados en el orden correcto. Afortunadamente, Scikit-Learn proporciona la clase PipeLine
para ayudar con tal secuencia de transformaciones. Aquí tenemos un pequeño pipeline para los
atributos numéricos:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
El constructor PipeLine toma una lista de pares nombre/estimador definiendo una secuencia
de pasos. Todos menos el último estimador deben ser transformadores (deben tener un método
fit_transform() ). Los nombres pueden ser cualquiera (únicos y no deben contener
subgiones):
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 23/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
siguiente llamada, hasta que se alcanza el estimador final, para el cual se llama el método
fit() .
El pipeline expone los mismos métodos que el estimador final. En este ejemplo, el último
estimador es un StandardScaler , el cual es un transformador, así el pipeline tiene un método
transform() que aplica todas las transformaciones a los datos en secuencia (y por supuesto
también un método fit_transform() el cual es el que usamos.
Hasta aquí, hemos manejado las columnas categóricas y las columnas numéricas
separadamente. Podría ser mas conveniente tener un sólo transformador disponible para
manejar todas las columnas, aplicando las transformaciones apropiadas a cada columna. En su
versión 0.20, Scikit-Learn introduce el ColumnTransformer para este propósito, y la buena noticia
es que este trabaja bien con Pandas DataFrames. Vamos a usarlo para aplicar todas las
transformaciones a la data:
from sklearn.compose import ColumnTransformer
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
full_pipeline = ColumnTransformer([
("num", num_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
housing_prepared = full_pipeline.fit_transform(housing)
Aquí esta como trabaja: primero importamos la clase ColumnsTransformer , luego obtenemos la
lista de columnas numéricas y la lista de nombres de columnas categóricas, y construimos un
ColumnTransformer() . El constructor requiere una lista de tuplas, donde cada tupla contiene un
nombre, un transformador y una lista de nombres (o ínidices) de columnas a las que se debe
aplicar el transformador. En este ejemplo, especificamos qué columnas numéricas deben ser
transformadas usando un num_pipeline que definimos anteriormente, y las columnas
categóricas deben ser transformadas usando un OneHotEncoder . Finalmente, aplicamos este
ColumnTransformer para la data de vivienda: este apllica cada transformador a la columna
apropiada y concatena las salidas a lo largo del segundo eje (los transformadores deben
retornar el mismo número de filas).
Note que el OneHotEncoder retorna una matriz dispersa, mientras el num_pipeline retorna una
matriz densa. Cuando hay tal mezcla de matrices densas y dispersas, el ColumnTransformer
estima la densidad de la matriz final (p. ej. el ratio de celdas diferentes de cero), y esto devuelve
una matriz dispersa si la densidad es más baja que un límite dado (por defecto,
sparse_threshold=0.3 ). En este ejemplo, se devuelve una matriz densa. Y aquí está, tenemos
un pipeline de procesamiento que toma la data completa de vivienda y aplica las
transformaciones apropiadas para cada columna.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 24/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(housing_prepared, housing_labels)
LinearRegression()
some_data = housing.iloc[:5]
some_labels = housing_labels.iloc[:5]
some_data_prepared = full_pipeline.transform(some_data)
print("Predictions:", lin_reg.predict(some_data_prepared))
print("Labels:", list(some_labels))
244550.67966089]
Funciona, pero las predicciones no son precisas. Vamos a medir el regresión RMSE del modelo
de regresión en el conjunto de entrenamiento completo usando la función de Scikit-Learn
mean_squared :
from sklearn.metrics import mean_squared_error
housing_predictions = lin_reg.predict(housing_prepared)
lin_mse = mean_squared_error(housing_labels, housing_predictions)
lin_rmse = np.sqrt(lin_mse)
lin_rmse
68627.87390018745
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 25/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(random_state=42)
tree_reg.fit(housing_prepared, housing_labels)
DecisionTreeRegressor(random_state=42)
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
tree_rmse
0.0
Un momento, hay algo malo aquí. ¿Puede ser un modelo absolutamente perfecto? Posiblemente
es más probable que el modelo haya sobreentrenado los datos. ¿Cómo podemos estar
seguros?
Validación cruzada
Una forma de evaluar el modelo del árbol de decisión sería usar la función train_test_split
para dividir el conjunto de entrenamiento en un pequeño conjunto de entrenamiento y un
conjunto de validación, luego entrenar el modelo con el conjunto de entrenamiento y entonces
evaluarlo contra el conjunto de validación. Esto es un poco trabajoso, pero no tan dificil y
trabajará bastante bien.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 26/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
tomando un fold diferentes para evaluarlo cada vez y entrenarlo en los otros 9 folds. El resultado
es un array que contiene los puntajes de las 10 evaluaciones:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(tree_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
tree_rmse_scores = np.sqrt(-scores)
La característica de cross-validation espera una función de utilidad (más grande mejor) en lugar
de una función de costo (más pequeño es mejor), de modo que la función de puntuación es
actualmente lo opuesto a MSE (valores negativos), esta es la razón por la que el código anterior
calcula -scores antes de calcular la raiz cuadrada.
Veamos el resultado:
def display_scores(scores):
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard desviation", scores.std())
display_scores(tree_rmse_scores)
69826.02473916 71077.09753998]
Mean: 71629.89009727491
Ahora el árbol de decisión no parece tan bueno como antes. De hecho, parece que se
desempeña peor que el modelo de regresión lineal. Observa que la validación cruzada permite
que obtengas no sólo un estimado del rendimiento de tu modelo, sino también una medida de
cuan preciso es esta estimación (p. ej. su desviación estándar). El árbol de decisión tiene un
puntaje de aproximadamente 71629, generalmente es ±2439. No tendrías esta información si
sólamente usas un conjunto de validación. Pero la validación cruzada solo se puede hacer si se
entrena el modelo varias veces, aunque esto no siempre es posible.
Vamos a calcular los mismos puntajes para el modelo de regresión lineal para asegurarnos:
lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
lin_rmse_scores = np.sqrt(-lin_scores)
display_scores(lin_rmse_scores)
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 27/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
66443.28836884 70139.79923956]
Mean: 69104.07998247063
Es cierto: el árbol de decisión está sobreentrenado de forma que es peor que el modelo de
regresión lineal.
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)
housing_predictions = forest_reg.predict(housing_prepared)
forest_mse = mean_squared_error(housing_labels, housing_predictions)
forest_rmse = np.sqrt(forest_mse)
forest_rmse
18754.384349586322
forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,
scoring="neg_mean_squared_error", cv=10)
forest_rmse_scores = np.sqrt(-forest_scores)
display_scores(forest_rmse_scores)
47966.20935756 53877.62199464]
Mean: 50273.30522897029
Esto está mucho mejor: Random Forests se ven prometedores. Sin embargo, nota que el puntaje
en el conjunto de entrenamiento es aún mucho menor que en los conjuntos de validación,
significa que el modelo está aun sobreentrenando el conjunto de entrenamiento. Las posibles
soluciones para el sobreentrenamiento es la simplificación del modelo, restricciones, u obtener
más datos de entrenamiento. Sin embargo, antes de profundizar en Random Forests, deberías
probar muchos otros modelos de las varias categorías de algoritmos de Machine Learning
(Varios vectores de soporte con diferentes kernels, posiblemente una red neuronal, etc.), sin
gastar mucho tiempo en ajustar los hiperparámetros. La meta es un lista corta de de unos
pocos (dos a cinco) modelos prometedores.
Deberías guardar cada modelo con el que experimentes, de forma que puedas facilmento volver
al cualquier modelo que quieras. Asegurándote de guardar los hiperparámetros y los
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 28/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
parámetros entrenados, así como los puntajes de validación cruzada y tal vez las precicciones
actuales. Esto te permitirá comparar fácilmente puntajes entre los tipos de modelos, y comparar
los tipos de errores que resultan. Puedes guardar fácilmente los modelos de Scikit-Learn
usando el módulo de Python pickle , o usando sklearn.externals.joblib , el cual es más
eficiente serializando arras NumPy grandes:
from joblib import dump, load
dump(forest_reg, "my_forest.pkl")
#y luego lo recuperamos...
my_forest_loaded = load("my_forest.pkl")
6. Afinar tu modelo
Búsqueda en rejilla
Una forma es manipular los hiperparámetros manualmente, hasta que encuentres un buena
combinación de valores de hiperparámetros. Esto puede ser verdaderamente tedioso, y podrías
no tener tiempo de explorar muchas combinaciones.
En cambio deberías dejar que GridSearchCV lo busque por ti. Todo lo que necesitas decirle es
qué hiperparámetros quieres experimentar, y que valores queries intentar; y este evaluará todas
las posibles combinaciones de valores de hiperparámetros, usando validación cruzada. Por
ejemplo, el siguiente código busca la mejor combinación de valores de hiperparámetros para el
RandomForestRegressor :
from sklearn.model_selection import GridSearchCV
param_grid = [
{'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
{'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
]
forest_reg = RandomForestRegressor()
grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
scoring='neg_mean_squared_error',
return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)
GridSearchCV(cv=5, estimator=RandomForestRegressor(),
return_train_score=True, scoring='neg_mean_squared_error')
Este param_grid le dice a Scikit-Learn que evalue primero todas las combinaciones de valores
de n_estimators y max_features (3x4=12) especificados en el primer dict (no te preocupes
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 29/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
por el significado de estos hiperparámetros los veremos en otro capítulo), luego prueba todas
las combinacione de hiperparámetros en el segundo dict (2x3=6), pero esta vez con el
hiperparámetro bootstrap establecido a False en lugar de True (que es el valor por defecto
para este parámetro).
grid_search.best_params_
grid_search.best_estimator_
RandomForestRegressor(max_features=6, n_estimators=30)
cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
print(np.sqrt(-mean_score), params)
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 30/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances
Con esta información, podemos eliminar algunos atributos que son poco útiles (aparentemente,
sólo una caracteística de ocean_proximity es realmente útil, por lo que podríamos borrar los
otros)
Búsqueda aleatoria
La búsqueda en rejilla es buena cuando exploras una cantidad relativamente pequeña de
combinaciones, como en el ejemplo previo, pero cuando el espacio de búsqueda del
hiperparámetro es grande, es preferible usar RandomizedSearchCV en su lugar. Esta clase se
puede usar de la misma forma que la clase GridSearchCV , pero en lugar de probar todas las
posibles combinaciones, se evalua un número dado de combinaciones aleatorias seleccionando
un valor aleatorio para cada hiperparámetro en cada iteración.
Métodos conjugados
Otra forma de afinar el sistema es probar a combinar lo modelos que se desempeñan mejor. El
grupo puede a menudo desempeñarse mejor que el mejor modelo individual, especialmente si
los modelos individuales cometen muy diferentes tipos de errores.
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)
[(0.3407605104141816, 'median_income'),
(0.1482519188408875, 'INLAND'),
(0.10657432575180929, 'pop_per_hhold'),
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 31/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
(0.07823091928204086, 'longitude'),
(0.07125260444180682, 'latitude'),
(0.06203260062505124, 'rooms_per_hhold'),
(0.05814881544060101, 'bedrooms_per_room'),
(0.04178552230199962, 'housing_median_age'),
(0.019498577945250044, 'total_rooms'),
(0.018657921625354387, 'population'),
(0.01693244474525297, 'total_bedrooms'),
(0.015498967188202376, 'households'),
(7.9373612384383e-05, 'ISLAND')]
final_model = grid_search.best_estimator_
X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()
X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)
final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
En algunos casos, la estimación del error de generalización no será suficiente para convencerte
de lanzarlo. Querrías tener un idea de cuan precisa es esta estimación. Para esto, puedes
calcular un intervalo de confianza para el error de generalización usando
scipy.stats.t.interval() :
from scipy import stats
confidence = 0.95
squared_errors = (final_predictions - y_test)**2
np.sqrt(stats.t.interval(confidence, len(squared_errors)-1,
loc=squared_errors.mean(),
scale=stats.sem(squared_errors)))
array([45746.57867955, 49642.36038056])
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 32/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
El rendimiento usualmente será ligeramente peor que cuando mediste usando validación
cruzada si hiciste muchos ajustes de hiperparámetros (por que tu sistema terminó estando
afinado para rendir bien en la data de validación, y podría no rendir bien en un conjunto de datos
desconocido). No es el caso en este ejemplo, pero cuando esto ocurre debes resistir la
tentación de ajustar los hiperparámetros para hacer que el número se vea mejor en el conjunto
de prueba; es poco probable que las mejoras generalicen los datos nuevos.
Ahora llega la fase de prelanzamiento: necesitas para presentar tu solución (resaltando lo que
has aprendido, lo que has trabajado y lo que no, las suposiciones que has hecho, y las
limitaciones que tiene el sistema), documentar todo y crear una buena presentación con
visualizaciones claras y sentencias fáciles de recordar. En este ejemplo de viviendas en
California por ejemplo, el rendimiento final del sistema no es mejor que el experto, pero aun es
una buena idea lanzarlo, especialmente si éste descarga algún tiempo a los expertos de forma
que ellos puedan trabajar en tareas más interesantes y productivas.
Otra estrategia es poner tu modelo en la nube, por ejemplo en Google Cloud AI Platform
guardando tu modelo usando joblib y cargarlo en Google Cloud Storage. Puedes tener un Web
Service que reciba consultas en JSON y devuelva las respuestas de las predicciones.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 33/34
8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.ipynb - Colaboratory
Evaluar tu sistema requerirá muestrear las predicciones del sistema y evaluarlas. Esto
generalmente requiere una análisis humano. Estos analistas pueden ser expertos de campo.
Finalmente, tu generalmente desearás entrenar tus modelos cada cierto tiempo usando datos
frescos. Deberías automatizar este proceso tanto como sea posible. Si tu sistema es un
sistema de aprendizaje en linea, deberías asegura que guardar snapshots de su estado a
intervalos regulars de modo que fácilmente puedas volver a un estado funcional previo.
https://colab.research.google.com/drive/1ueCGd-lkCV61wyK2-6_SDxckuYbRExZb?usp=sharing#printMode=true 34/34