A tu propio ritmo

Explora nuestra extensa colección de cursos diseñados para ayudarte a dominar varios temas y habilidades. Ya seas un principiante o un aprendiz avanzado, aquí hay algo para todos.

Bootcamp

Aprende en vivo

Únete a nosotros en nuestros talleres gratuitos, webinars y otros eventos para aprender más sobre nuestros programas y comenzar tu camino para convertirte en desarrollador.

Próximos eventos en vivo

Catálogo de contenidos

Para los geeks autodidactas, este es nuestro extenso catálogo de contenido con todos los materiales y tutoriales que hemos desarrollado hasta el día de hoy.

Tiene sentido comenzar a aprender leyendo y viendo videos sobre los fundamentos y cómo funcionan las cosas.

Buscar en lecciones


IngresarEmpezar
← Regresar a lecciones
Editar en Github
Abrir en Colab

Explorando K Nearest Neighbors

KNN en Python

A continuación veremos cómo podemos implementar este modelo en Python. Para ello, utilizaremos la librería scikit-learn.

KNN para clasificación

Para ejemplificar la implementación de un KNN para clasificación, utilizaremos el conjunto de datos que venimos utilizando en los módulos anteriores y que ha sido normalizado debido a que este tipo de modelos lo requiere.

Paso 1. Lectura del conjunto de datos procesado

In [1]:
import pandas as pd
from sklearn.model_selection import train_test_split

total_data = pd.read_csv("https://raw.githubusercontent.com/4GeeksAcademy/machine-learning-content/master/assets/clean_iris.csv")

X = total_data.drop("specie", axis = 1)
y = total_data["specie"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 42)

X_train.head()
Out[1]:
sepal length (cm)sepal width (cm)petal length (cm)petal width (cm)
22-1.5065211.249201-1.567576-1.315444
15-0.1736743.090775-1.283389-1.052180
651.0380050.0982170.3648960.264142
11-1.2641850.788808-1.226552-1.315444
42-1.7488560.328414-1.397064-1.315444

El conjunto train lo utilizaremos para entrenar el modelo, mientras que con el test lo evaluaremos para medir su grado de efectividad.

Para asegurar el correcto funcionamiento de este modelo y evitar errores, la estandarización de los datos es necesaria, ya que si las características no están en la misma escala, aquellas con magnitudes más grandes pueden dominar la distancia y afectar el resultado del algoritmo.

Por ejemplo, si tenemos dos características: la edad (con valores entre 0 y 100) y el ingreso anual (con valores entre 0 y 100.000), la diferencia de escala entre ambas variables podría hacer que el ingreso anual tenga un impacto desproporcionado en la distancia, ignorando la importancia de la edad.

Estandarizar los datos ayuda a que todas las características contribuyan de manera equitativa a la distancia, lo que puede mejorar el rendimiento del algoritmo KNN. La elección entre normalización y estandarización Min-Max dependerá del comportamiento de las variables y de cómo afectan al rendimiento del modelo. Si tenemos características con diferentes escalas y rango, Min-Max es la mejor alternativa. Si, por el contrario, tienen la misma escala o similar, la normalización es la más apropiada.

A continuación visualizaremos la relación entre las variables del dataset (hemos escogido tres para realizar un gráfico 3D, ya que no podemos coger más y graficarlas; no existen los gráficos 4D):

In [2]:
# Añadimos el nombre de la especie para el plot 

total_data["specie"] = total_data["specie"].map({0: "setosa", 1: "versicolor", 2: "virginica"})
In [3]:
import plotly.express as px

fig = px.scatter_3d(total_data, x = "petal width (cm)", y = "petal length (cm)", z = "sepal width (cm)", color = "specie", width = 1000, height = 500,
                    size = total_data["petal length (cm)"].abs(), color_discrete_sequence=["#E58139", "#39E581", "#8139E5"])
camera = dict(
    up = dict(x = 1, y = 3.5, z = 0),
    eye = dict(x = 2, y = 0, z = 0)
)

fig.update_layout(scene_camera = camera)
fig.show()

El gráfico 3D permite analizar la separación y distribución de las clases en función de la combinatoria de las 3 variables. Claramente, se observa que dado un punto y las variables predictoras seríamos capaz de, aprovechando la potencia y capacidad predictora de un modelo KNN, realizar una precisión adecuada.

Para más información, podríamos calcular un diagrama de puntos para la relación entre las variables dos a dos (esto tendría que hacerse en el EDA):

In [4]:
import matplotlib.pyplot as plt
import seaborn as sns

fig, axis = plt.subplots(2, 3, figsize = (15, 7))

palette = ["#E58139", "#39E581", "#8139E5"]
sns.scatterplot(ax = axis[0, 0], data = total_data, x = "sepal length (cm)", y = "sepal width (cm)", hue = "specie", palette = palette)
sns.scatterplot(ax = axis[0, 1], data = total_data, x = "sepal length (cm)", y = "petal length (cm)", hue = "specie", palette = palette)
sns.scatterplot(ax = axis[0, 2], data = total_data, x = "sepal length (cm)", y = "petal width (cm)", hue = "specie", palette = palette)
sns.scatterplot(ax = axis[1, 0], data = total_data, x = "sepal width (cm)", y = "petal length (cm)", hue = "specie", palette = palette)
sns.scatterplot(ax = axis[1, 1], data = total_data, x = "sepal width (cm)", y = "petal width (cm)", hue = "specie", palette = palette)
sns.scatterplot(ax = axis[1, 2], data = total_data, x = "petal length (cm)", y = "petal width (cm)", hue = "specie", palette = palette)

plt.tight_layout()

plt.show()
No description has been provided for this image

Comparando las predictoras una a una (para que sea más gráfico y explícito) se observa mejor la separación en función de los valores de las clases. Por lo tanto, el modelo KNN es también muy apropiado para resolver el problema.

Paso 2: Inicialización y entrenamiento del modelo

In [5]:
from sklearn.neighbors import KNeighborsClassifier

model = KNeighborsClassifier()
model.fit(X_train, y_train)
Out[5]:
KNeighborsClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

El tiempo de entrenamiento de un modelo dependerá, en primer lugar, del tamaño del conjunto de datos (instancias y características), y también de la tipología de modelo y su configuración.

Paso 3: Predicción del modelo

Una vez se ha entrenado el modelo, se puede utilizar para predecir con el conjunto de datos de prueba.

In [6]:
y_pred = model.predict(X_test)
y_pred
Out[6]:
array([1, 0, 2, 1, 1, 0, 1, 2, 1, 1, 2, 0, 0, 0, 0, 1, 2, 1, 1, 2, 0, 2,
       0, 2, 2, 2, 2, 2, 0, 0])

Con los datos en crudo es muy complicado saber si el modelo está acertando o no. Para ello, debemos compararlo con la realidad. Existe una gran cantidad de métricas para medir la efectividad de un modelo a la hora de predecir, entre ellas la precisión (accuracy), que es la fracción de predicciones que el modelo realizó correctamente.

In [7]:
from sklearn.metrics import accuracy_score

accuracy_score(y_test, y_pred)
Out[7]:
1.0

¡El modelo es perfecto!

Paso 4: Guardado del modelo

Una vez tenemos el modelo que estábamos buscando (presumiblemente tras la optimización de hiperparámetros), para poder utilizarlo a futuro es necesario almacenarlo en nuestro directorio, junto a la semilla.

In [8]:
from pickle import dump

dump(model, open("knn_classifier_default.sav", "wb"))

Añadir un nombre explicativo al modelo es vital, ya que en el caso de perder el código que lo ha generado sabremos qué configuración tiene (en este caso decimos default porque no hemos personalizado ninguno de los hiperparámetros del modelo, hemos dejado los que tiene por defecto la función).

KNN para regresión

Para ejemplificar la implementación de un algoritmo KNN mostraremos cómo generar un conjunto de datos que satisfaga nuestras necesidades.

Paso 1. Lectura del conjunto de datos procesado

In [9]:
import pandas as pd
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

X, y = make_regression(n_samples = 1000, n_features = 4, noise = 1, random_state = 42)
X = pd.DataFrame(X, columns = ["Var1", "Var2", "Var3", "Var4"])

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 42)

X_train.head()
Out[9]:
Var1Var2Var3Var4
29-0.5182700.3571131.477894-0.219672
5350.457687-2.120700-0.606865-2.238231
695-0.2246330.940771-0.982487-0.989628
5570.360648-0.3202981.643378-2.077812
836-0.307962-0.144519-0.792420-0.675178

El conjunto train lo utilizaremos para entrenar el modelo, mientras que con el test lo evaluaremos para medir su grado de efectividad. Dividiremos también las predictoras de las características.

En la regresión también es necesario estandarizar los datos. En este caso, ya nos vienen estandarizados. Tal y como hemos hecho anteriormente, dibujaremos de nuevo el gráfico 3D y las relaciones una a una de las características del dataset generado artificialmente:

In [10]:
import plotly.express as px

total_data = X.copy()
total_data["target"] = y

fig = px.scatter_3d(total_data, x = "Var1", y = "Var2", z = "Var3", color = "target", width = 1000, height = 500,
                    size = total_data["Var4"].abs())
camera = dict(
    up = dict(x = 1, y = 3.5, z = 0),
    eye = dict(x = 2, y = 0, z = 0)
)

fig.update_layout(scene_camera = camera)
fig.show()
In [12]:
import matplotlib.pyplot as plt
import seaborn as sns

fig, axis = plt.subplots(2, 3, figsize = (15, 7))

palette = sns.color_palette("gnuplot2_r", as_cmap=True)
sns.scatterplot(ax = axis[0, 0], data = total_data, x = "Var1", y = "Var2", hue = "target", palette = palette)
sns.scatterplot(ax = axis[0, 1], data = total_data, x = "Var1", y = "Var3", hue = "target", palette = palette)
sns.scatterplot(ax = axis[0, 2], data = total_data, x = "Var1", y = "Var4", hue = "target", palette = palette)
sns.scatterplot(ax = axis[1, 0], data = total_data, x = "Var2", y = "Var3", hue = "target", palette = palette)
sns.scatterplot(ax = axis[1, 1], data = total_data, x = "Var2", y = "Var4", hue = "target", palette = palette)
sns.scatterplot(ax = axis[1, 2], data = total_data, x = "Var3", y = "Var4", hue = "target", palette = palette)

plt.tight_layout()

plt.show()
No description has been provided for this image

Vemos que para la mayoría de variables se establece cierto patrón diferenciador y que la regresión puede arrojar buenos resultados.

Paso 2: Inicialización y entrenamiento del modelo

In [13]:
from sklearn.neighbors import KNeighborsRegressor

model = KNeighborsRegressor()
model.fit(X_train, y_train)
Out[13]:
KNeighborsRegressor()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Paso 3: Predicción del modelo

Una vez se ha entrenado el modelo, se puede utilizar para predecir con el conjunto de datos de prueba.

In [14]:
y_pred = model.predict(X_test)
y_pred
Out[14]:
array([-147.41721871,   12.7128117 ,  -36.12302539,  -21.93648933,
       -144.32582093,   -4.62694737,   25.33274569,  -32.72285068,
         58.56266885,  155.66838297,  -91.57904794, -209.25552065,
       -125.0049947 ,  -40.94453209,   77.06000418,   -2.31024234,
         89.23243529,  -87.10430605,  -12.75608929, -107.40101528,
         59.5285574 ,  -73.2172528 ,   12.89565   , -152.20708656,
         68.31889013, -150.10454511,  -34.63535979,  -46.82258216,
        137.590877  ,   84.72199868, -115.13576847,  -23.02387497,
         99.53383414,  221.35135356,   26.10949006,  -78.30179293,
       -153.46745873,  155.37084184,   97.19477898,  156.55504796,
         33.52863468,  -53.06285465,   -2.71200404,  138.92351044,
        -28.8475082 ,  -54.68111873,    0.39307033,   14.64850846,
        -18.68501556,   82.45571704,  171.35059264,  -39.28831728,
         50.92959341, -140.09104896,   55.2744179 ,  -45.23494761,
        -55.32923476,   40.641162  ,   13.65687921,   79.13259557,
         24.21322023,  205.77188903,  162.12907189, -214.64288553,
        -48.12750922,   57.84638636,  -48.11409853,  132.86152958,
         78.94155338,  173.47253872,  -63.43007483,  -56.28153853,
        -20.57233102,  -41.89870694,   27.50335358,   56.21961522,
        -67.01690518,   65.86291158,   35.82856198,   93.27327307,
       -135.42551895,  -23.28890253,  100.60637024, -169.75802585,
         16.80237377, -145.92720763,    7.65154432,   70.31085359,
       -138.08858909,  -20.97488562, -202.84423981, -186.29010982,
        118.36374156,  -46.46130478,    3.04367133,   20.90597603,
         72.59324354,  160.86426614,   -2.64495043,  -69.61344901,
         55.47044742,  125.32284865,    6.14957185, -135.19505319,
         33.55728658,   87.92606067,  106.99087434,  164.64474228,
         59.48733257,  -83.81266187, -122.13094227,    0.53719761,
        236.86271787,  -67.3417169 ,   25.8694997 , -174.7047103 ,
        -80.57484935, -135.11344691,  224.10044744,  -66.70764275,
          1.56024283, -105.47176455,   52.03445355,   95.74231157,
         16.19497672,   39.93612672,  -44.40873841,  -27.81008649,
       -177.51227362,   56.16766096,  206.66743023,   20.80449655,
         52.2654225 ,  166.1518485 ,  -53.23874069,   21.70964561,
         34.02812113,    4.15932292,    7.3331871 ,  -50.75381914,
       -213.20370255,   15.77528335,  116.86174989,  196.84267769,
         26.6297435 , -125.17028769,  -79.75376986,   -2.84775957,
        -75.91484381, -183.30498253,  125.48685399,  -97.87501807,
        -50.80763193,  -54.4973921 ,   63.03342149,   23.2467635 ,
        -20.74887764,   75.23614017,   37.61678359,  -81.40887724,
       -130.38933037,  191.29075295, -124.56063375, -108.24640512,
         -0.58051144,   20.05594007,   -9.4429406 ,  -76.12710791,
         90.35499728,   85.84797897,   14.17483093,  -16.96825839,
        -14.64975853, -107.57040037,   96.11298658,  -47.20140352,
          5.71159723,  -59.80612262,  101.68961644, -151.40241857,
         39.95250414, -136.80702629, -155.86579813,  102.57850137,
        108.23524256,   21.45005089, -216.76995688,   92.26877057,
        -48.18787188,   46.04898414,   76.48356855,  -72.32331055,
       -207.22300555,  104.51122023,   62.90183088, -165.52273551,
         64.05278101,   98.47222997,  121.36946183,   13.74458948])

Para calcular la efectividad del modelo utilizaremos el error cuadrático medio (MSE):

In [15]:
from sklearn.metrics import mean_squared_error, r2_score

print(f"Error cuadrático medio: {mean_squared_error(y_test, y_pred)}")
print(f"Coefficient of determination: {r2_score(y_test, y_pred)}")
Error cuadrático medio: 564.8367646867368
Coefficient of determination: 0.9547914514799766

El modelo está muy cerca de ser perfecto.

Paso 4: Guardado del modelo

Una vez tenemos el modelo que estábamos buscando (presumiblemente tras la optimización de hiperparámetros), para poder utilizarlo a futuro es necesario almacenarlo en nuestro directorio, junto a la semilla.

In [16]:
dump(model, open("knn_regressor_default.sav", "wb"))

Añadir un nombre explicativo al modelo es vital, ya que en el caso de perder el código que lo ha generado sabremos qué configuración tiene (en este caso ponemos default porque no hemos personalizado ninguno de los hiperparámetros del modelo, hemos dejado los que tiene por defecto la función).