Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ManuSanchez02
GitHub Repository: ManuSanchez02/7506R-2c2022-GRUPO09
Path: blob/main/tp2/7506R_TP2_GRUPO09_ENTREGA_N1(nlp).ipynb
91 views
Kernel: Python 3

Open In Colab

Configuración inicial

Importamos las bibliotecas necesarias para el análisis.

from google.colab import drive drive.mount('/content/drive')
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
import numpy as np import pandas as pd import matplotlib.pyplot as plt import re from sklearn import metrics from xgboost import XGBRegressor from sklearn.model_selection import RandomizedSearchCV import regex !pip install xgboost np.random.seed(1) SEMILLA = 1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Requirement already satisfied: xgboost in /usr/local/lib/python3.8/dist-packages (0.90) Requirement already satisfied: numpy in /usr/local/lib/python3.8/dist-packages (from xgboost) (1.21.6) Requirement already satisfied: scipy in /usr/local/lib/python3.8/dist-packages (from xgboost) (1.7.3)

Funciones útiles.

Definimos funciones que se van a utilizar.

def imprimir_metricas_regresion(target_test, precios_predichos): # Error cuadrático medio mse = metrics.mean_squared_error( y_true=target_test, y_pred=precios_predichos, squared=True ) print(f"El error (mse) de test es: {mse}") # Raíz del error cuadrático medio rmse = metrics.mean_squared_error( y_true=target_test, y_pred=precios_predichos, squared=False ) print(f"El error (rmse) de test es: {rmse}") # R2 score r2 = metrics.r2_score(target_test, precios_predichos) print(f"El score R2 es: {r2}")
columnas = ["property_price", "id", "property_description"] def construir_target_y_features(dataset): target = dataset["property_price"] dataset.drop(columns=columnas, inplace=True) dataset = pd.get_dummies(dataset, drop_first=True) return target, dataset

Importamos los datasets ya preprocesados.

dataset_train = pd.read_csv("https://github.com/ManuSanchez02/7506R-2c2022-GRUPO09/blob/main/tp2/datasets/dataset_train_preprocesado.csv?raw=True") dataset_test = pd.read_csv("https://github.com/ManuSanchez02/7506R-2c2022-GRUPO09/blob/main/tp2/datasets/datasetc_test_preprocesado.csv?raw=True") dataset_train.drop(["Unnamed: 0"], axis = 1, inplace=True) dataset_test.drop(["Unnamed: 0"], axis = 1, inplace=True)
dataset_pln_train = dataset_train.copy() dataset_pln_test = dataset_test.copy()

Importamos y hacemos un head del dataset con las descripciones para ver qué forma tiene.

dataset_descrip = pd.read_csv("/content/drive/MyDrive/OrgaTP2/properati_argentina_2021_decrip.csv") dataset_descrip.head()

Unimos el dataset preprocesado con el que contiene las descripciones.

dataset_pln_train = dataset_pln_train.merge(dataset_descrip, left_on='id', right_on='id') dataset_pln_test = dataset_pln_test.merge(dataset_descrip, left_on='id', right_on='id') dataset_pln_train.head()

Normalizamos las descripciones para que sea más fácil realizar el análisis.

def normalizar(descripcion): descripcion = descripcion.lower() descripcion = descripcion.replace('á', 'a') descripcion = descripcion.replace('é', 'e') descripcion = descripcion.replace('í', 'i') descripcion = descripcion.replace('ó', 'o') descripcion = descripcion.replace('ú', 'u') return descripcion

Definimos una lista de palabras que no son relevantes a la hora de elegir aspectos, y creamos un diccionario que contiene todas las palabras de las descripciones con sus frecuencias, para ver cuáles podemos tomar como aspectos.

ignorar = ["a", "ante", "cabe", "con", "de", "desde", "durante", "en", "entre", "hacia", "hasta", "mediante", "para", "por", "según", "sin", "so", "sobre", "tras", "versus", "via", "el", "la", "los", "del", "las", "los", "etc", "es", "era", "br", "y", "que", "al", "o", "un", "ba", "una", "se", "", "cocina", "ambientes", "piso", "pisos", "propiedad", "son", "muy", "esta", "dos", "cuadras", "completo", "corredor", "responsable", "inmueble", "excelente", "amplio", "accede", "cucicba", "medidas", "cuenta", "lendar", "queres", "ley", "podes", "gran", "aire", "simula", "metros", "mls", "personas", "parte", "inmobiliario", "encuentra", "independiente", "todas", "todos", "unidades", "presente", "tiene", "este", "mas", "unidad", "aviso", "operacion", "bajo", "propietario", "como", "informacion", "operaciones", "mts", "discapacidades" ]
def obtener_aspectos(dataset): cantidades = {} for descripcion in dataset['property_description']: descripcion = normalizar(descripcion) palabras = re.split('[^a-z]', descripcion) for palabra in palabras: if not palabra in ignorar and len(palabra) > 2: cantidades[palabra] = cantidades.get(palabra, 0) + 1 return cantidades diccionario_aspectos = obtener_aspectos(dataset_pln_train)

Tomamos las 50 palabras más frecuentes como aspectos relevantes de las descripciones.

aspectos_comunes = sorted(diccionario_aspectos.items(), key=lambda x: x[1], reverse=True)[:50] for aspecto in aspectos_comunes: print(aspecto)
('balcon', 65617) ('comedor', 60511) ('living', 58609) ('departamento', 55953) ('edificio', 51346) ('dormitorio', 44593) ('expensas', 42030) ('frente', 41990) ('valor', 40105) ('placard', 32240) ('casa', 29942) ('luminoso', 29466) ('venta', 29335) ('lavadero', 28301) ('dormitorios', 27703) ('prestamo', 26319) ('terraza', 26252) ('cuota', 25832) ('contacto', 25406) ('ubicacion', 24576) ('vista', 23644) ('salida', 23598) ('espacio', 23272) ('cochera', 22612) ('compra', 22369) ('zona', 21992) ('patio', 20780) ('ubicado', 20579) ('linea', 20472) ('barrio', 20428) ('planta', 20358) ('mesada', 19960) ('principal', 19285) ('acceso', 19023) ('parrilla', 18774) ('accesible', 18627) ('subte', 18562) ('servicios', 18426) ('estado', 17936) ('servicio', 17562) ('acondicionado', 17071) ('agua', 16941) ('toilette', 16738) ('hall', 15808) ('comercial', 15729) ('estacion', 15541) ('calidad', 15113) ('madera', 14985) ('suite', 14645) ('calefaccion', 14427)

Buscamos frases que contengan los aspectos encontrados siguiendo reglas simples de regex. Tomamos como aspectos las más frecuentes y las guardamos en un diccionario.

aspectos_comunes_lista = [aspecto[0] for aspecto in aspectos_comunes] aspetos_regex = '|'.join(aspectos_comunes_lista) def obtener_matches(dataset, regex_match, cantidad): cantidades = {} for descripcion in dataset['property_description']: descripcion = normalizar(descripcion) frases = re.findall(regex_match, descripcion) for frase in frases: cantidades[frase] = cantidades.get(frase, 0) + 1 return dict(sorted(cantidades.items(), key=lambda item: item[1], reverse=True)[:cantidad]) grupo_aspectos = f"(?:{aspetos_regex})" diccionario_frases = {} diccionario_frases.update(obtener_matches(dataset_pln_train, "con " + grupo_aspectos + " \w{4,}", 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, "\w{4,} con " + grupo_aspectos, 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, "\w{4,} " + grupo_aspectos, 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, "\w{4,} de " + grupo_aspectos, 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, "de " + grupo_aspectos + " \w{4,}", 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, "\w{4,} " + grupo_aspectos + " \w{4,}", 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, grupo_aspectos + " con " + grupo_aspectos, 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, grupo_aspectos + " de " + grupo_aspectos, 10)) diccionario_frases.update(obtener_matches(dataset_pln_train, grupo_aspectos + " " + grupo_aspectos, 10))

Nos quedamos con las frases que se encuentran en más del 1,5% de las descripciones del dataset.

diccionario_frases = {k:v for k, v in diccionario_frases.items() if v >= dataset_pln_train.shape[0]*0.015 } print(diccionario_frases)
{'con vista abierta': 3667, 'con espacio para': 3025, 'con comedor diario': 2794, 'con placard empotrado': 1873, 'con balcon corrido': 1546, 'comedor con salida': 5661, 'dormitorio con placard': 4627, 'ambientes con balcon': 2979, 'dormitorios con placard': 2679, 'frente con balcon': 2881, 'cocina con comedor': 1934, 'terraza con parrilla': 1988, 'ambientes con cochera': 1369, 'cocina con lavadero': 1351, 'balcon con vista': 1337, 'living comedor': 31914, 'aire acondicionado': 16951, 'bajo mesada': 11551, 'excelente ubicacion': 9930, 'dormitorio principal': 7856, 'amplio living': 6901, 'buen estado': 5578, 'excelente estado': 5318, 'primera calidad': 5278, 'super luminoso': 5267, 'valores de expensas': 7105, 'pisos de madera': 4175, 'dependencia de servicio': 4017, 'entrada de servicio': 1781, 'baño de servicio': 1660, 'estacion de subte': 1809, 'hall de acceso': 1578, 'marcas de servicio': 1503, 'oferta de venta': 1337, 'habitacion de servicio': 1111, 'aire acondicionado frio': 5120, 'amplio living comedor': 4685, 'aire acondicionado split': 1462, 'gran living comedor': 1280, 'frente con vista': 1110, 'comedor con balcon': 1048, 'subte linea': 4627, 'balcon terraza': 4153, 'venta departamento': 3334, 'zona comercial': 1992}
frases_comunes = sorted(diccionario_frases.items(), key=lambda x: x[1], reverse=True) for frase in frases_comunes: print(frase)
('living comedor', 31914) ('aire acondicionado', 16951) ('bajo mesada', 11551) ('excelente ubicacion', 9930) ('dormitorio principal', 7856) ('valores de expensas', 7105) ('amplio living', 6901) ('comedor con salida', 5661) ('buen estado', 5578) ('excelente estado', 5318) ('primera calidad', 5278) ('super luminoso', 5267) ('aire acondicionado frio', 5120) ('amplio living comedor', 4685) ('dormitorio con placard', 4627) ('subte linea', 4627) ('pisos de madera', 4175) ('balcon terraza', 4153) ('dependencia de servicio', 4017) ('con vista abierta', 3667) ('venta departamento', 3334) ('con espacio para', 3025) ('ambientes con balcon', 2979) ('frente con balcon', 2881) ('con comedor diario', 2794) ('dormitorios con placard', 2679) ('zona comercial', 1992) ('terraza con parrilla', 1988) ('cocina con comedor', 1934) ('con placard empotrado', 1873) ('estacion de subte', 1809) ('entrada de servicio', 1781) ('baño de servicio', 1660) ('hall de acceso', 1578) ('con balcon corrido', 1546) ('marcas de servicio', 1503) ('aire acondicionado split', 1462) ('ambientes con cochera', 1369) ('cocina con lavadero', 1351) ('balcon con vista', 1337) ('oferta de venta', 1337) ('gran living comedor', 1280) ('habitacion de servicio', 1111) ('frente con vista', 1110) ('comedor con balcon', 1048)

Una vez generada la lista de aspectos (palabras + frases) los agregamos al dataset como columnas nuevas.

def agregar_aspectos(dataset, aspectos): for aspecto in aspectos: dataset[aspecto] = dataset["property_description"].apply(contiene_aspecto, args=(aspecto,)) def contiene_aspecto(descripcion, aspecto): descripcion = normalizar(descripcion) return int(None != re.search(aspecto, descripcion)) frases_comunes_lista = [frase[0] for frase in frases_comunes] aspectos_comunes_lista = [aspecto[0] for aspecto in aspectos_comunes] agregar_aspectos(dataset_pln_train, frases_comunes_lista) agregar_aspectos(dataset_pln_train, aspectos_comunes_lista) agregar_aspectos(dataset_pln_test, frases_comunes_lista) agregar_aspectos(dataset_pln_test, aspectos_comunes_lista) dataset_pln_train.head()

XGBoost

Entrenamos dos modelos XGBoost: uno utilizando los hiperparámetros obtenidos en el primer trabajo y otro con hiperparámetros optimizados para el nuevo dataset.

La idea es generar modelos de regresión que puedan predecir el precio de la propiedad.

Primero construimos el target y adaptamos el dataset.

(target_train, dataset_train) = construir_target_y_features(dataset_pln_train) (target_test, dataset_test) = construir_target_y_features(dataset_pln_test)

Parámetros del TP1

Entrenamos al primer modelo e imprimimos sus métricas de test y de train.

xgb = XGBRegressor(n_estimators= 500, min_child_weight= 6, max_depth= 12, learning_rate= 0.07) model = xgb.fit(dataset_train, target_train)
[14:54:53] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.

Vemos cómo es la performance con los datos de train. Performa demasiado bien, por lo que podría haber overfitting.

precios_predichos_xgb_train = model.predict(dataset_train) imprimir_metricas_regresion(target_train, precios_predichos_xgb_train)
El error (mse) de test es: 62762976.21201805 El error (rmse) de test es: 7922.308767778371 El score R2 es: 0.9941485604871629

Ahora analizamos con los datos de test.

precios_predichos_xgb = model.predict(dataset_test) imprimir_metricas_regresion(target_test, precios_predichos_xgb)
El error (mse) de test es: 947774176.1976094 El error (rmse) de test es: 30785.941210195433 El score R2 es: 0.9107822634935412

Las métricas también son muy buenas, por lo que si bien puede haber un poco de overfitting el modelo está prediciendo bien los resultados.

En el TP1 los resultados habían sido:

  • El error (mse) de test es: 1194394914.1879835

  • El error (rmse) de test es: 34560.0190131311

  • El score R2 es: 0.8875645071859526

por lo que vemos una mejora. Sin embargo, buscamos un nuevo modelo con los hiperparámetros optimizados a ver si mejoran aún más los resultados y podemos reducir el overfitting.

Nuevos hiperparámetros

Entrenamos al segundo modelo, esta vez realizando un randomized search para obtener los mejores hiperparámetros.

xgb = XGBRegressor() param_grid = { "max_depth": [4, 8, 12], "learning_rate": [0.03, 0.05, 0.07, 1], "min_child_weight": [2, 4, 6, 8], "n_estimators": [250, 500, 750], "gamma": [0, 1, 5, 10, 20], "lambda": [1, 2, 5], } xgb_randcv = RandomizedSearchCV( xgb, param_grid, scoring="r2", cv=3, random_state=SEMILLA, n_iter=15 ) xgb_randcv.fit(dataset_train, target_train)
[15:03:22] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:06:17] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:09:07] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:11:49] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:13:37] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:15:25] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:17:12] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:19:00] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:20:48] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:22:38] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:23:58] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:25:17] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:26:37] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:28:21] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:30:05] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:31:49] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:32:15] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:32:41] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:33:07] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:34:01] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:34:54] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:35:47] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:38:31] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:41:17] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:44:03] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:44:56] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:45:49] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:46:42] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:47:35] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:48:29] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:49:23] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:51:00] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:52:37] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:54:14] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:54:41] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:55:08] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:55:35] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [15:58:16] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:00:59] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:03:40] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:05:27] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:07:13] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:09:00] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:10:42] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:12:24] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror. [16:14:07] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.
RandomizedSearchCV(cv=3, estimator=XGBRegressor(), n_iter=15, param_distributions={'gamma': [0, 1, 5, 10, 20], 'lambda': [1, 2, 5], 'learning_rate': [0.03, 0.05, 0.07, 1], 'max_depth': [4, 8, 12], 'min_child_weight': [2, 4, 6, 8], 'n_estimators': [250, 500, 750]}, random_state=1, scoring='r2')
print("Mejores hiperparámetros: ", xgb_randcv.best_params_) print("Mejor métrica: ", xgb_randcv.best_score_)
Mejores hiperparámetros: {'n_estimators': 750, 'min_child_weight': 6, 'max_depth': 8, 'learning_rate': 0.07, 'lambda': 2, 'gamma': 1} Mejor métrica: 0.9071796488969764

Entrenamos al modelo con la mejor métrica.

xgb_model = xgb_randcv.best_estimator_ model = xgb_model.fit(X=dataset_train, y=target_train)
[16:22:25] WARNING: /workspace/src/objective/regression_obj.cu:152: reg:linear is now deprecated in favor of reg:squarederror.

Analizamos su performance con los datos de train.

precios_predichos_xgb_train = model.predict(dataset_train) imprimir_metricas_regresion(target_train, precios_predichos_xgb_train)
El error (mse) de test es: 296934449.53851545 El error (rmse) de test es: 17231.786022885597 El score R2 es: 0.9723165777721756

Nuevamente, vemos que performa demasiado bien, por lo que podría haber overfitting.

Analizamos cómo performa con los datos de test.

precios_predichos_xgb = model.predict(dataset_test) imprimir_metricas_regresion(target_test, precios_predichos_xgb)
El error (mse) de test es: 959083000.3801513 El error (rmse) de test es: 30969.065216440602 El score R2 es: 0.9097177190889196

Sigue teniendo muy buenos resultados, similares a los obtenidos con el modelo anterior.

Conclusiones

El dataset con aspectos tiene una mejor performance que sin, pero la mejora no es demasiado significativa ya que sin eso ya se obtenían buenos resultados.

El método explorado de Regex resultó ser útil para encontrar de forma rápida frases comunes relacionadas al dominio, pero resultó lento y difícil hallar las mejores reglas que generaban el mejor rendimiento ya que primero era necesario familiarizarnos mucho con el dominio y el dataset.

En los modelos entrenados se puede observar un poco de overfitting, pero incluso optimizando los hiperparámetros esto no mejora. Sin embargo, como los resultados con los datos de test también son muy buenos podemos suponer que el modelo se adapta muy bien a los datos.