Capitulo 2

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 34

8/5/22, 00:10 Capitulo 2 - Proyecto machine learning de extremo a extremo.

ipynb - Colaboratory

En esta lección revisaremos un ejemplo de proyecto de extremo a extremo

Observar el panorama completo


Obtener los datos
Descubrir y visualizar los datos para ganar perspectiva
Preparar los datos para algoritmos de Machine Learning
Seleccionar un modelo y entrenarlo
Afinar el modelo
Presentar la solución
Lanzar, monitorear y mantener el sistema

Trabajando con data real


Repositorios abiertos

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

1. Ver el panorama completo


Los datos tienen métricas tales como la población, ingreso medio, precio de las casas, etc. Para
cada grupo de manzanas “distrito” en California. Nuestro modelo debe aprender de estos datos
de forma que pueda predecir el precio medio de una casa en cualquier distrito, dadas las otras
métricas.

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.

Una secuencia de procesamiento de datos se llama un pipeline de datos.

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

Intenta responder a estas de estas preguntas:

¿Es aprendizaje supervisado, no supervisado, o por refuerzo?


¿Es una tarea de clasificación, regresión o algo diferente?
¿Se debe usar una técnica de aprendizaje en lotes o en línea ?

Obviamente es aprendizaje supervisado dado que se dan ejemplos etiquetados (cada instancia
llega con una salida determinada)

Es una tarea típica de regresión, dado que queremos predecir un valor.

Es un problema de regresión múltiple. Dado que el sistema utilizará muchas características para
hacer la predicción.

También es una regresión univariada, ya que solo queremos predecir un valor.

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.

Seleccionar una medida de rendimiento


Una métrica típica es el error medio cuadrático (RMSE) que da un valor alto para errores
grandes
m

1 (i) (i) 2
RM S E (X, h) = ∑(h(x ) − y )
m

i=1

m es la cantidad de instancias del dataset. Por ejemplo si estás evaluando un conjunto de


validación de 2000 distritos, entonces m = 2000 .

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).

Por ejemplo, si el primer distrito en el dataset se localiza en -188.29° de longitud y


33.91° de latitud, y tiene 1416 habitantes con un ingreso medio de $38,372 y el valor

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

medio de la vivienda es $156,400 (ignorando las demás características por ahora),


−118.29
⎛ ⎞

⎜ 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)

Por ejemplo, si el primer distrito es el descrito, entonces la matriz


(1) T
⎛ (x ) ⎞

⎜ (x (2) T ⎟
)
⎜ ⎟
⎜ ⎟ −118.29 33.91 1416 38372
X = ⎜ ... ⎟ = ( )
⎜ ⎟
...
⎜ (1999) T ⎟
⎜ (x ) ⎟

⎝ (2000) T ⎠
(x )

h es la función de predición del sistema, también llamado hipótesis. Cuando a tu sistema


le das el vector de características x(i) , produce una salida que predice el valor
(i)
^
y = h(x
(i)
) para esta instancia.

Por ejemplo, si tu sistema predice que el valor medio de la vivienda en el primer


(1)
distrito es $158400, entonces y^ = h(x
(1)
) = 158400 . entonces el error de
(1)
predicción para este distrito es de y^ − y
(1)
= 2000

RM S E (X, h) es el costo de la función medido en el conjunto de ejemplos usando la


hipótesis h.

Otra medida podría ser el Error medio absoluto (MAE)


m

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 raíz de la suma de los cuadrados RMSE, corresponde a la distancia euclidiana.

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

Verificando las suposiciones


Es una buena práctica listar y verificar las suposiciones que han sido hechas: esto puede ayudar
a capturar varios problemas tempranamente. Por ejemplo, los precios de los distritos que tu
sistema calcula serán alimentados a otro sistema, y supones que estos precios serán usados
tal cual. Pero, si ese sistema convierte el precio en categorías ("barato", "medio" o "caro") y luego
usa estas categorías en lugar de los precios mismos; de ser ese el caso obtener el precio con
mucha exactitud no es del todo importante sino que tu sistema neceista obtener la categoría
correcta. De ser así, el problema debe haber sido enmarcaado dentro de una tarea de
clasificación, no de regresión. No querrás descubrir esto depués de trabajar en un problema de
regresión por meses.

2. Obteniendo los datos

Descargando la data
los datos pueden estar en una base de datos relacional o en múltiples documentos como CSV

[ ] ↳ 3 celdas ocultas

Dando un vistazo a la estructura de datos

Cada fila representa un distrito

housing.head()

longitude latitude housing_median_age total_rooms to

0 -122.23 37.88 41.0 880.0

1 -122.22 37.86 21.0 7099.0

2 -122.24 37.85 52.0 1467.0

El método info() es útil para obtener una descripción rápida de los datos

housing.info()

<class 'pandas.core.frame.DataFrame'>

RangeIndex: 20640 entries, 0 to 20639

Data columns (total 10 columns):

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

# Column Non-Null Count Dtype

--- ------ -------------- -----

0 longitude 20640 non-null float64

1 latitude 20640 non-null float64

2 housing_median_age 20640 non-null float64

3 total_rooms 20640 non-null float64

4 total_bedrooms 20433 non-null float64

5 population 20640 non-null float64

6 households 20640 non-null float64

7 median_income 20640 non-null float64

8 median_house_value 20640 non-null float64

9 ocean_proximity 20640 non-null object

dtypes: float64(9), object(1)

memory usage: 1.6+ MB

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()

<1H OCEAN 9136

INLAND 6551

NEAR OCEAN 2658

NEAR BAY 2290

ISLAND 5

Name: ocean_proximity, dtype: int64

El método describe() muestra un resumen de los atributos numéricos

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

longitude latitude housing_median_age tota

count 20640.000000 20640.000000 20640.000000 20640

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

Podemos notar varias cosas en estos histogramas

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).

Creando el conjunto de datos

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.

Crear un conjunto de prueba es relativamente simple: es tomar un conjunto de datos


aleatoriamente, usualmente 20% del conjunto de datos y ponerlo aparte.
La función
split_train_test() hace esto de forma aleatoria:

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]

Puedes usar esta función así:

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

Aquí una posible implementación:

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]

Desafortunadamente, el dataset de vivienda no contiene un identificador columna. La solución


simple es usar el índice de la fila como un ID:

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")

Scikit-Learn proporciona algunas instrucciones para dividir datasets en varios subcojuntos. La


función mas simple es train_test_split() , la cual hace lo mismo que la función
split_train_test() , con un par de características adicionales. Primero, hay un parámetro
random_state qque permite establecer la semilla del generador. Segundo, puedes pasar
múltiples datasets con un número identico de filas, y este lo dividirá en el mismo índice (esto es
bastante útil, por ejemplo, si tienes un DataFrame separado para las etiquetas):

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

Para asegurarse que la muestra sea representativa de la población, Se debería hacer un


muestreo estratificado, donde la población se divide en grupos homogeneos llamados estratos
para garantizar que el conjunto es representativo de toda la población.

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.

El siguiente código usa la función pd.cut() para crear 5 categorías etiquetadas de 1 a 5 y


agrega la columna income_cat al conjunto de datos:
housing["income_cat"] = pd.cut(housing["median_income"], 

                               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>

Ahora estamos listos para muestrear de forma estratificada en la categoría de ingreso.

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

Name: income_cat, dtype: float64


Con un código similar puedes medir las proporciones de las categorías de entrada en el dataset
completo. La figura siguiente compara las proporciones de las categorías de entrada y un
conjunto de prueba generado unicamente con muestreo aleatorio. Como se puede ver, el
conjunto de prueba generado usando muestreo estratificado tiene proporciones más idénticas a
aquellas en el dataset completo.

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'>

Int64Index: 4128 entries, 5241 to 3965

Data columns (total 10 columns):

# Column Non-Null Count Dtype

--- ------ -------------- -----

0 longitude 4128 non-null float64

1 latitude 4128 non-null float64

2 housing_median_age 4128 non-null float64

3 total_rooms 4128 non-null float64

4 total_bedrooms 4079 non-null float64

5 population 4128 non-null float64

6 households 4128 non-null float64

7 median_income 4128 non-null float64

8 median_house_value 4128 non-null float64

9 ocean_proximity 4128 non-null object

dtypes: float64(9), object(1)

memory usage: 354.8+ KB

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.

3. Descubriendo y visualizando datos para obtener


información

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()

Visualizando datos geográficos

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>

El radio de cada círculo representa la población, y el color representa el precio. Usaremos un


color predeterminado de color llamado jet , que va del azul(bajos precios) al rojo (altos
precios).

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

Name: median_house_value, dtype: float64

El coeficiente de correlación va en el rango de -1 a 1. Cuando es cercano a 1 significa que la


correlación es positiva; por ejemplo, el valor medio de las casas tiende a ir hacia arriba cuando
el ingreso medio crece. También se puede ver una pequeña correlación negativa entre la latitud
y el valor medio de la casa. Por último los valores cercanos a cero significan que no hay
correlación lineal.

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

array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7


<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
[<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
[<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
[<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
<matplotlib.axes._subplots.AxesSubplot object at 0x7
dtype=object)

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.

Experimentando con combinaciones de atributos


Hemos identificado algunos datos peculiares que podrías desear limpiar antes de alimentarlos
al algoritmo de Machine Learning, y encontrarás correlaciones interesantes entre los atributos,
en particular con el atributo objetivo. Tambien notamos que hay atributos con distribuciones de
cola pesada que podríamos desear transformar.

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"]

Y ahora veamos la matriz de correlación:

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

Name: median_house_value, dtype: float64

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

El nuevo atributo bedrooms_per_room es mucho más correlacionado con el valor medio de la


casa que el número total de habitaciones o dormitorios. Aparentemente, las casas con un ratio
bajo dormitorios/habitaciones tienden a ser mas caras. El número de cuartos por hogar también
parece más informativo que el número total de habitaciones en un distrito.

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.

4. Preparando los datos para algoritmos de Machine


Learning

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:

Te permite reproducir estas transformaciones facilmente en cualquier dataset.


Puedes construir gradualmente una biblioteca de funciones de transformación que puedes
reutilizar en le futuro.
Puedes usar esas funcioens en tu sistema para transforma los nuevos datos antes de
alimentarlas en tu programa.

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:

Deshacerse de esos distritos


Deshacerse de todo el atributo
Establecer estos valores a algún valor (cero, la media, la mediana, etc)

Podemos cumplir esto rápidamente usando los métodos dropna() , drop() , y fillna() :
housing.dropna(subset=["total_bedrooms"]) #opción 1

housing.drop("total_bedrooms", axis=1) #opción 2

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

median = housing["total_bedrooms"].median() #opción 3

housing["total_bedrooms"].fillna(median, inplace= True)

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')

El imputer ha simplemente calculado la media de cada atributo y almacenado el resultado en la


variable statistics_ . Sólo el atributo total_bedrooms tenía valores perdidos, pero no podemos
estar seguros que no haya valores perdidos en los nuevos datos una vez que el sistema se
ponga en marcha, por lo que es mas seguro aplicar el imputer a todos los atributos numéricos.

imputer.statistics_

array([-118.51 , 34.26 , 29. , 2119. , 433. ,

1164. , 408. , 3.54155])

housing_num.median().values

array([-118.51 , 34.26 , 29. , 2119. , 433. ,

1164. , 408. , 3.54155])

Ahora puedes utilizar el imputer "entrenado" para transformar el conjunto entrenado


reemplazando valores faltantes por los medios aprendidos:

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

Si quieres volver a convertirlo en un DataFrame es simple:

housing_tr = pd.DataFrame(X, columns=housing_num.columns)

Manejando texto y atributos categóricos


Inicialmente dejamos fuera el atributo ocean_proximity por que es un atributo de texto de
modo que no podemos calcular su media:

housing_cat = housing[["ocean_proximity"]]

housing_cat.head(10)

ocean_proximity

12655 INLAND

15502 NEAR OCEAN

2908 INLAND

14053 NEAR OCEAN

20496 <1H OCEAN

1481 NEAR BAY

18125 <1H OCEAN

5830 <1H OCEAN

17989 <1H OCEAN

4861 <1H OCEAN

La mayoría de algoritmos de Machine Learning prefieren trabajar con números, vamos a


convertir estas categorías en números. Para esto podemos utilizar la clase OrdinalEncoder de
Scikit-learn:

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_

[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],

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

<16512x5 sparse matrix of type '<class 'numpy.float64'>'

with 16512 stored elements in Compressed Sparse Row format>

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()

array([[0., 1., 0., 0., 0.],

[0., 0., 0., 0., 1.],

[0., 1., 0., 0., 0.],

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

...,

[1., 0., 0., 0., 0.],

[1., 0., 0., 0., 0.],

[0., 1., 0., 0., 0.]])

Una vez mas puedes obtener la lista de categorías usando la variable categories_ :

cat_encoder.categories_

[array(['<1H OCEAN', 'INLAND', 'ISLAND', 'NEAR BAY', 'NEAR OCEAN'],

dtype=object)]

Si un atributo categórico tiene un gran número de posibles categorías, la codificación one-hot


puede resultar en una gran número de características de entrada. Esto puede realentizar el
entrenamiento y degradar el rendimiento. Si esto pasa, se puede reemplazar la entrada
categórica con características numéricas relacionadas con la categoría: por ejemplo, puedes
reemplazar la característica ocean_proximity con la distancia al oceano (de la misma forma se
puede reemplazar un código de país con la población del país y el GDP percápita).

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

Escalamiento min-max (normalización): los valores son cambiados y reescalados de


modo que tengan un rango de 0 a 1. Hacemos esto sustrayendo el valor mínimo y
dividendo por el máximo menos el mínimo. ScikitLearn proporciona un transformador para
eso llamado MinMaxScaler para esto.
Estandarización: Es un poco diferente, primero se resta el valor medio, y luego este se
divide por la desviación estándar. A diferencia de min-max, esto no encaja los valores en
un rango específico, lo cual puede ser un problema para algunos algoritmos. Sin embargo,
la estandarización es mucho menos afectada por los valores atípicos. Por ejemplo,
suponiendo que un distrito tiene un ingreso medio mensual igual a 100 (por error). Min-
max aplastará los valores de 0-15 a 0-0.15, mientras que la estandarización casi no se verá
afectada. Scikit-Learn proporciona un transformador llamado StandardScaler para
estandarización.

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):

Cuando llamamos al método fit() de pipeline, este llama secuencialmente a fit_transform()


en todos los transformadores, pasando la salida de cada llamada como parámetros para la

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

5. Selecionando un modelo de entrenamiento


Se ha delimitado el problema, obtuvimos los datos y los exploramos, muestreamos un conjunto
de entrenamiento y un conjunto de prueba, y se ha escrito pipelines de transformación para
limpiar y preparar los datos para el algoritmo de MachineLearning. Ahora estas listo para
seleccionar y entrenar un modelo de Machine Learning.

Entrenamiento y evaluación en el conjunto de entrenamiento


Las buenas noticias son que gracias a todos los pasos previos, las cosas son ahora más
simples de lo que podemos pensar. Vamos a entrenar un modelo de Regresión lineal, como
hicimos en el capítulo anterior.

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()

lin_reg.fit(housing_prepared, housing_labels)

LinearRegression()

Ahora tenemos un modelo de regresión lineal trabajando. Vamos a intentarlo en algunas


instancias del conjunto de entrenamiento:

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))

Predictions: [ 85657.90192014 305492.60737488 152056.46122456 186095.70946094

244550.67966089]

Labels: [72100.0, 279600.0, 82700.0, 112500.0, 238300.0]

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

Es mejor que nada, pero claramente no es un gran puntaje: muchos median_housing_values


están en el rango entre 120,000 y 256,000 de modo que un error de 68,000 no es muy
satisfactorio. Este es un ejemplo de un modelo subentrenado. Cuando esto pasa puede
significar que las características no proporcionan suficiente información para hacer buenas
predicciones, o que el modelo no es suficientemente potente. Como vimos en el capítulo
anterior, las principales formas de arreglar el subentrenamiento es seleccionar un modelo más
poderoso, para alimentar el algoritmo con mejores características o reducir las restricciones en
el modelo. Podemos tratar de agregar más características, pero primero probaremos un modelo
más complejo para ver cómo va.

Vamos a entrenar un DecisionTreeRegressor . Este es un modelo poderoso, capaz de encontrar


relaciones complejas no lineales en los datos. El código luce familiar por ahora:

from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(random_state=42)

tree_reg.fit(housing_prepared, housing_labels)

DecisionTreeRegressor(random_state=42)

Ahora que el modelo es entrenado, vamos a evaluarlo en el conjunto de entrenamiento:

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.

Una buena alternativa es usar la característica de K-fold cross-validation de Scikit-Learn. El


siguiente código divide aleatoriamente el conjunto de entrenamiento en 10 subconjuntos
diferentes llamdos folds. Luego entrena y evalúa el modelo de árbol de decisión 10 veces,

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)

Scores: [72831.45749112 69973.18438322 69528.56551415 72517.78229792

69145.50006909 79094.74123727 68960.045444 73344.50225684

69826.02473916 71077.09753998]

Mean: 71629.89009727491

Standard desviation 2914.035468468928

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)

Scores: [71762.76364394 64114.99166359 67771.17124356 68635.19072082

66846.14089488 72528.03725385 73997.08050233 68802.33629334

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

Standard desviation 2880.3282098180644

Es cierto: el árbol de decisión está sobreentrenado de forma que es peor que el modelo de
regresión lineal.

Vamos a intentar un último modelo ahora: el RandomForestRegressor . Random Forest trabaja


entrenando muchos árboles de decisión en subconjuntos aleatorios, luego se promedian sus
predicciones. Construir un modelo sobre muchos otros modelos es llamado Ensemble Learning,
y es a menudo una buena forma de mejorar el Machine Learning. El código es esencialmente
igual que para otros modelos:

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)

Scores: [51499.74841622 49287.95477067 47095.8821186 52002.36250913

47103.18972083 51643.1007983 52711.7051989 49545.27740485

47966.20935756 53877.62199464]

Mean: 50273.30522897029

Standard desviation 2286.40147365949

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(),

param_grid=[{'max_features': [2, 4, 6, 8],

'n_estimators': [3, 10, 30]},

{'bootstrap': [False], 'max_features': [2, 3, 4],

'n_estimators': [3, 10]}],

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).

En total, la búsqueda en rejilla explorará 12+6=18 combinaciones de valores de hiperparámetros


de RandomForestRegressor , y esto entrenará cada modelo cinco veces (dado que estamos
usando five-fold cross validation). En otras palabras habrán 90 rondas de entrenamiento que
podrían tomar mucho tiempo, pero cuando se hace puedes obtener la mejor combinación de
parámetros:

grid_search.best_params_

{'max_features': 6, 'n_estimators': 30}

grid_search.best_estimator_

RandomForestRegressor(max_features=6, n_estimators=30)

Y por supuesto la evalucaión de los puntajes están también disponibles:

cvres = grid_search.cv_results_

for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):

  print(np.sqrt(-mean_score), params)

63022.13126508942 {'max_features': 2, 'n_estimators': 3}

55003.39529614612 {'max_features': 2, 'n_estimators': 10}

52747.24181727869 {'max_features': 2, 'n_estimators': 30}

60730.672191132726 {'max_features': 4, 'n_estimators': 3}

53197.62315124209 {'max_features': 4, 'n_estimators': 10}

50594.153108512204 {'max_features': 4, 'n_estimators': 30}

60158.13288688205 {'max_features': 6, 'n_estimators': 3}

52661.59064411657 {'max_features': 6, 'n_estimators': 10}

49992.91025580593 {'max_features': 6, 'n_estimators': 30}

57973.99783406269 {'max_features': 8, 'n_estimators': 3}

52491.707977476384 {'max_features': 8, 'n_estimators': 10}

50111.245352364764 {'max_features': 8, 'n_estimators': 30}

63267.35519424648 {'bootstrap': False, 'max_features': 2, 'n_estimators': 3}

53687.956847867266 {'bootstrap': False, 'max_features': 2, 'n_estimators': 10}

59906.264712035256 {'bootstrap': False, 'max_features': 3, 'n_estimators': 3}

52592.79275544566 {'bootstrap': False, 'max_features': 3, 'n_estimators': 10}

59450.12560797967 {'bootstrap': False, 'max_features': 4, 'n_estimators': 3}

51519.9105103819 {'bootstrap': False, 'max_features': 4, 'n_estimators': 10}

En este ejemplo, obtenemos la mejor solución estableciendo el hiperparámetro max_features a


6, y el hiperparámetro n_estimators a 30. El puntaje RMSE para esta combinación es 49992, el
cual es levemente mejor que el puntaje que que habíamos obtenido usando los valores de
hiperparámetros por defecto (50273). Felicidades, has afinado tu modelo.

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

array([7.82309193e-02, 7.12526044e-02, 4.17855223e-02, 1.94985779e-02,

1.69324447e-02, 1.86579216e-02, 1.54989672e-02, 3.40760510e-01,

6.20326006e-02, 1.06574326e-01, 5.81488154e-02, 1.40244941e-02,

1.48251919e-01, 7.93736124e-05, 3.11504547e-03, 5.15595823e-03])

Vamos a mostrar los puntajes de importancia junto a sus nombre de atributos:

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.

Analizar los mejores modelos y sus errores


Puedes obtener un mejor juicio sobre el problema inspeccionanado los mejores modelos. Por
ejemplo, el RandomForestRegressor puede indicar la importancia relativa de cada atributo para
hacer predicciones:

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'),

(0.014024494090379824, '<1H OCEAN'),

(0.005155958227094447, 'NEAR OCEAN'),

(0.003115045467703663, 'NEAR BAY'),

(7.9373612384383e-05, 'ISLAND')]

Evaluar tu sistema en el conjunto de prueba


Después de ajustar tus modelos por el momento, eventualmente tienes un sistema que funciona
suficientemente bien. Ahora es tiempo de evaluar el modelo fina en el conjunto de prueba. No
hay nada especial con este proceso, acabando de obtener los predictios y las etiquetas desde tu
conjunto de prueba, ejecuta tu full_pipeline para transformar los datos (llama a
transform() ) y evalua el modelo final en el conjunto de prueba.

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.

8. Lanzar, Monitorear y mantener el sistema


Necesitas tener tu solución lista para producción, en particular conectando los datos de entrada
de producción en tu sistema y hacer las pruebas. Una forma de hacer esto es guardar el modelo
entreando (usando joblib ), incluyendo el preprocesamiento completo y la secuencia de
predicción, luego cargar este modelo entrenado en tu entorno de producción y usarlo para hacer
predicciones llamando al método predict() . Por ejemplo, si el modelo será usado dentro de un
website: el usuario escribirá algunos datos sobre un nuevo distrito y hará click en el botón
Estimar Precio. Entonces enviará una consulta conteniendo los datos al web server, el cual
reenviará esto a tu aplicación web, y finalmente tu código simplemente llama al método
predict() .

Alternativamente puedes envolver el modelo dentro de un web service dedicado que tu


aplicación web pueda consultar mediante un REST API. Esto hace fácil actualizar tu modelo a
una versión nueva sin interrumpir la aplicación. Esto permite que tu aplicación web use
cualquier lenguaje, no necesariamente Python.

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.

Necesitas también escribir código de monitoreo para verificar el rendimiento de tu sistema en


intervalos regulars y disparar alertas. Es importante capturar no solo fallas repentinas, sino
también degradación del rendimiento. Esto es bastante común porque los modelos tienden a
"malograrse" cuando la data evoluciona en el tiempo, a menos que los modelos sean
regularmente entrenados con datos frescos.

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.

También deberías asegurarte de evaluar la calidad de la data de entrada. Algunas veces el


rendimiento se puede degradar levemente debido a una pobre calidad de la señal, pero puede
tomar un tiempo antes que el rendimiento de tu sistema se degrade lo suficiente para disparar
una alerta. Si monitoreas las entradas de tu sistema, puedes capturar esto tempranamente.
Monitorear el sistema es particularmente importante para sistemas de aprendizaje en linea.

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

También podría gustarte