La predicción de precios de viviendas es un problema frecuente en el análisis de datos. Dado un conjunto de datos de propiedades, es crucial desarrollar un modelo que capture las relaciones subyacentes. La Regresión de Vectores de Soporte (SVR) destaca por su capacidad para manejar patrones no lineales, lo que la hace adecuada para esta tarea. Este artículo explica cómo implementar un flujo completo de trabajo para predecir precios de casas utilizando Python y la biblioteca scikit-learn.
Generación y Exploración de Datos Simulados
Para ilustrar el proceso, crearemos un conjunto de datos sintético donde el precio depende de manera no lineal de la superficie. Usaremos funciones de numpy y matplotlib para la generación y visualización.
import numpy as np
import matplotlib.pyplot as plt
# Configurar semilla para reproducibilidad y generar datos
rng = np.random.default_rng(123)
superficie = np.sort(200 * rng.random((80, 1)), axis=0) # Superficie en metros cuadrados
precio = 45 * np.sin(superficie / 55) + 0.6 * superficie + 12 * rng.standard_normal(80).ravel() # Precio en miles de euros
# Visualizar la relación
plt.figure(figsize=(10, 6))
plt.scatter(superficie, precio, color='teal', label='Datos observados')
plt.xlabel('Superficie (m²)')
plt.ylabel('Precio (miles de euros)')
plt.title('Relación entre superficie y precio de viviendas')
plt.legend()
plt.show()
Este código produce 80 muestras con una tendencia lineal y una componente sinusoidal, además de ruido aleatorio. El gráfico resultante muestra una clara no linealidad en los datos.
Preprocesamiento y División del Conjunto de Datos
Antes de entrenar el modelo, es esencila escalar las características y separar los datos en conjuntos de entrenamiento y prueba para una evaluación válida.
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Escalar datos para mejorar el rendimiento de SVR
escalador_ent = StandardScaler()
escalador_obj = StandardScaler()
X_esc = escalador_ent.fit_transform(superficie)
y_esc = escalador_obj.fit_transform(precio.reshape(-1, 1)).ravel()
# Dividir en entrenamiento (80%) y prueba (20%)
X_ent, X_pru, y_ent, y_pru = train_test_split(
X_esc, y_esc, test_size=0.2, random_state=99
)
print(f"Muestras de entrenamiento: {len(X_ent)}")
print(f"Muestras de prueba: {len(X_pru)}")
La estandarización es necesaria porque SVR es sensible a la escala de las variables. Esto ayuda a acelerar la convergencia y mejora el desempeño del modelo.
Construcción de Modelos SVR y Selección de Kernel
SVR utiliza funciones kernel para mapear datos a un espacio de mayor dimensión. Compararemos tres kernels comunes: RBF, lineal y polinomial.
Definición de Modelos con Diferentes Kernels
from sklearn.svm import SVR
# Configurar modelos SVR con distintos kernels
modelos = {
'RBF': SVR(kernel='rbf', C=150, gamma=0.12, epsilon=0.15),
'Lineal': SVR(kernel='linear', C=150),
'Polinomial': SVR(kernel='poly', C=150, degree=4)
}
Parámetros clave en SVR:
- C: Controla el compromiso entre error de entrenamiento y complejidad del modelo.
- gamma: Define la influencia de los puntos de soporte en kernels como RBF.
- epsilon: Establece el margen de tolerancia para errores.
- degree: Orden del kernel polinomial.
Entrenamiento y Evaluación Inicial
from sklearn.metrics import mean_squared_error, r2_score
# Entrenar cada modelo y calcular métricas
metricas = {}
for nombre, modelo in modelos.items():
modelo.fit(X_ent, y_ent)
predicciones = modelo.predict(X_pru)
metricas[nombre] = {
'ECM': mean_squared_error(y_pru, predicciones),
'R2': r2_score(y_pru, predicciones)
}
# Mostrar resultados
for nombre, val in metricas.items():
print(f"Modelo {nombre} - ECM: {val['ECM']:.4f}, R²: {val['R2']:.4f}")
El Error Cuadrático Medio (ECM) mide la desviación promedio de las predicciones, mientras que R² indica la proporción de varianza explicada por el modelo. Valores de R² cercanos a 1 son deseables.
Ajuste de Hiperparámetros y Optimización
Para mejorar el rendimiento, realizamos una búsqueda en cuadrícula para encontrar la combinación óptima de parámetros, enfocándonos en el kernel RBF.
Implementación de Búsqueda en Cuadrícula
from sklearn.model_selection import GridSearchCV
# Definir espacio de búsqueda para hiperparámetros
param_grid = {
'C': [0.5, 5, 50, 200],
'gamma': ['scale', 'auto', 0.05, 0.2],
'epsilon': [0.05, 0.2, 0.6]
}
# Ejecutar búsqueda con validación cruzada
busqueda = GridSearchCV(
SVR(kernel='rbf'),
param_grid,
cv=4,
scoring='neg_mean_squared_error',
n_jobs=-1
)
busqueda.fit(X_ent, y_ent)
print("Mejores parámetros encontrados:", busqueda.best_params_)
Evaluación del Modelo Optimizado
# Utilizar el mejor modelo para predicciones
mejor_svr = busqueda.best_estimator_
predicciones_finales = mejor_svr.predict(X_pru)
ecm_final = mean_squared_error(y_pru, predicciones_finales)
r2_final = r2_score(y_pru, predicciones_finales)
print(f"Modelo optimizado - ECM: {ecm_final:.4f}, R²: {r2_final:.4f}")
En la búsqueda de parámetros, es recomendable iniciar con rangos amplios y luego refinar en áreas prometedoras para equilibrar precisión y costo computacional.
Visualización de Resultados e Interpretación
Visualizar las predicciones ayuda a entender el comportamiento del modelo. Graficaremos la curva de regresión ajustada junto con los datos originales.
Gráfico de Predicciones
# Generar puntos para la curva de predicción
rango_superficie = np.linspace(superficie.min(), superficie.max(), 500).reshape(-1, 1)
rango_esc = escalador_ent.transform(rango_superficie)
pred_esc = mejor_svr.predict(rango_esc)
pred_real = escalador_obj.inverse_transform(pred_esc.reshape(-1, 1))
# Crear visualización
plt.figure(figsize=(12, 7))
plt.scatter(superficie, precio, color='teal', alpha=0.7, label='Datos reales')
plt.plot(rango_superficie, pred_real, color='darkred', linewidth=2, label='Predicción SVR')
plt.xlabel('Superficie (m²)')
plt.ylabel('Precio (miles de euros)')
plt.title('Ajuste del modelo SVR a datos inmobiliarios')
plt.legend()
plt.show()
Análisis de Vectores de Soporte
# Obtener vectores de soporte del modelo
vectores_soporte = mejor_svr.support_vectors_
# Visualizar los vectores de soporte en el espacio original
plt.figure(figsize=(10, 6))
plt.scatter(X_ent, y_ent, color='teal', alpha=0.5, label='Datos de entrenamiento')
plt.scatter(
escalador_ent.inverse_transform(vectores_soporte),
escalador_obj.inverse_transform(y_ent[mejor_svr.support_].reshape(-1, 1)),
color='red', s=120, facecolors='none', edgecolors='red', linewidths=2,
label='Vectores de soporte'
)
plt.xlabel('Superficie (m²)')
plt.ylabel('Precio (miles de euros)')
plt.title('Distribución de vectores de soporte en SVR')
plt.legend()
plt.show()
Los vectores de soporte son los puntos de datos críticos que definen el margen del modelo. Su número y distribución revelan la complejidad del ajuste y las regiones donde el modelo es más sensible.
Aplicación Práctica y Función de Predicción
Para usar el modelo en producción, encapsulamos la lógica de predicción en una función que maneje la preprocesamiento y postprocesamiento.
def estimar_precio_vivienda(metros_cuadrados):
"""Estima el precio de una vivienda basado en su superficie."""
# Validar entrada
if not isinstance(metros_cuadrados, (int, float)) or metros_cuadrados <= 0:
raise ValueError("La superficie debe ser un número positivo.")
# Escalar, predecir y desescalar
entrada_esc = escalador_ent.transform([[metros_cuadrados]])
prediccion_esc = mejor_svr.predict(entrada_esc)
precio_estimado = escalador_obj.inverse_transform(prediccion_esc.reshape(-1, 1))
return precio_estimado[0][0]
# Ejemplo de uso
area_prueba = 110 # metros cuadrados
resultado = estimar_precio_vivienda(area_prueba)
print(f"Precio estimado para {area_prueba} m²: {resultado:.2f} miles de euros")
Consideraciones clave para el despliegue: garantizar que los datos de entrada estén dentro del rango de entrenamiento, reentrenar el modelo periódicamente con datos actualizados, y monitorear el desempeño en el tiempo para detectar degradación.
Extensiones y Mejoras Avanzadas
Para problemas más complejos, se pueden incorporar múltiples características, aplicar ingeniería de características o utilizar métodos de ensamblado.
Expansión a Múltiples Variables Predictoras
En escenarios reales, el precio depende de varios factores. A continuación, un ejemplo con datos simulados multivariados.
# Simular dataset con múltiples características
rng2 = np.random.default_rng(456)
n_muestras = 300
superficie_multi = 60 + 180 * rng2.random(n_muestras)
habitaciones = rng2.integers(1, 6, n_muestras)
antigüedad = 3 + 28 * rng2.random(n_muestras)
ubicación = rng2.random(n_muestras)
# Definir variable objetivo
precio_multi = (
40 * np.sin(superficie_multi / 60) +
0.5 * superficie_multi +
15 * habitaciones -
0.7 * antigüedad +
45 * ubicación +
8 * rng2.standard_normal(n_muestras)
)
# Crear matriz de características
X_multi = np.column_stack((superficie_multi, habitaciones, antigüedad, ubicación))
y_multi = precio_multi
Ingeniería de Características con Pipelines
Combinar transformaciones como características polinomiales con SVR puede capturar interacciones no lineales.
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
# Crear pipeline con características polinomiales
pipeline_svr = make_pipeline(
PolynomialFeatures(degree=2, include_bias=False),
StandardScaler(),
SVR(kernel='rbf', C=100, gamma=0.1)
)
# Entrenar y evaluar (requiere división previa de datos multivariados)
# pipeline_svr.fit(X_ent_multi, y_ent_multi)
# predicciones_poly = pipeline_svr.predict(X_pru_multi)
# print(f"ECM con características polinomiales: {mean_squared_error(y_pru_multi, predicciones_poly):.4f}")
Ensamblado de Modelos para Mejorar la Precisión
Los métodos de ensamblado, como stacking, combinan múltiples modelos para reducir el error.
from sklearn.ensemble import StackingRegressor
from sklearn.linear_model import RidgeCV
# Definir modelos base para el ensamblado
estimadores_base = [
('svr_rbf', SVR(kernel='rbf', C=100, gamma=0.1)),
('svr_lineal', SVR(kernel='linear', C=100))
]
# Configurar modelo de stacking
modelo_apilado = StackingRegressor(
estimators=estimadores_base,
final_estimator=RidgeCV()
)
# Entrenar y evaluar (ejemplo conceptual)
# modelo_apilado.fit(X_ent, y_ent)
# predicciones_apiladas = modelo_apilado.predict(X_pru)
# print(f"ECM del modelo apilado: {mean_squared_error(y_pru, predicciones_apiladas):.4f}")
Resolución de Problemas Comunes
Al aplicar SVR, se pueden encontrar desafíos específicos. Aquí algunas soluciones típicas.
- Entrenamiento lento: Reducir el tamaño del dataset, usar kernels lineales, o aumentar el parámetro de tolerancia
tol. - Predicciones inestables: Incrementar el valor de C, disminuir epsilon, o probar kernels alternativos.
- Bajo rendimiento en datos nuevos: Verificar si la distribución de datos ha cambiado, aumentar la diversidad en el conjunto de entrenamiento, o realizar ajuste fino de parámetros.
- Dificultad para interpretar el modelo: Emplear herramientas de explicabilidad como SHAP para analizar la conttribución de características.
# Ejemplo conceptual de uso de SHAP para explicabilidad
import shap
# Crear un explicador basado en el modelo entrenado
explicador = shap.KernelExplainer(mejor_svr.predict, X_ent[:50])
valores_shap = explicador.shap_values(X_pru[:5])
# Visualizar la importancia de características (en caso de datos multivariados)
# shap.summary_plot(valores_shap, X_pru[:5], feature_names=['Superficie', 'Habitaciones', 'Antigüedad', 'Ubicación'])
En la práctica, el kernel RBF de SVR suele ofrecer un buen equilibrio para problemas no lineales, pero su parámetro gamma debe calibrarse cuidadosamente para evitar sobreajuste o subajuste. Un punto de partida es establecer gamma como el inverso del número de características multiplicado por la varianza de los datos escalados.