Makine Öğrenimi ile Gayrimenkul Değerleme: Zestimate Benzeri Bir Sistem Oluşturmak
2006 yılında Zillow, ilk otomatik derecelendirme sistemi olan Zestimate'i piyasaya sürdü ulusal ölçekte mülkler. Bugün ABD'de değerlendirilen 135 milyondan fazla evin bulunduğu Zestimate, tüm PropTech sektörü için referans ölçütü haline geldi. Ama nasıl bir sistem gerçekten çalışıyor Otomatik Değerleme Modeli (AVM)? Hangileri Makine öğrenimi algoritmaları en iyi sonuçları üretir mi? Ve her şeyden önemlisi nasıl inşa edildiği güvenilir, yorumlanabilir ve ayrımcılık karşıtı düzenlemelere uygun bir sistem mi?
Bu makalede eksiksiz bir AVM oluşturacağız: veri toplama ve temizlemeden özelliklere kadar Gradyan artırma modellerinin eğitiminden üretim dağıtımına kadar ileri mühendislik REST API ile SHAP yorumlanabilirlik tekniklerinden geçerek izleme modelin zaman içinde kayması.
Ne Öğreneceksiniz
- Gayrimenkul için Otomatik Değerleme Modelinin (AVM) uçtan uca mimarisi
- Gelişmiş özellik mühendisliği: fiziksel özellikler, konum, karşılaştırılabilirlikler, makro ekonomi
- Karşılaştırılan makine öğrenimi modelleri: XGBoost, LightGBM, CatBoost, Random Forest, Neural Networks
- Her bir değerlendirmeyi açıklamak için SHAP değerleriyle yorumlanabilirlik
- Hedonik fiyatlandırma modeli ve karşılaştırılabilir yaklaşım (CMA)
- FastAPI ile dağıtım ve Evidently AI ile model kayması izleme
- Algoritmik önyargının yönetimi ve Adil Konut düzenlemelerine uyum
2025'te AVM Pazarı
Küresel Otomatik Değerleme Modeli pazarı 2024 yılında %23 büyüyecek, Mortgage sürecinin dijitalleşmesi ve hibrit modellerin benimsenmesiyle Yapay zeka ve insan değerlendirmesini birleştirirler. Başlıca oyuncular arasında Zillow (Zestimate), CoreLogic, Kara Şövalye, HouseCanary ve Hometrack (İngiltere).
Modern AVM'lerin ortalama doğruluğu bir civarındadır medyan mutlak yüzde hatası (MdAPE) %3 ila %6 arasında yüksek yoğunluklu pazarlardaki konut mülkleri için veri. Bu, 300.000 Euro'luk bir daire için tipik hatanın şu olduğu anlamına gelir: 9.000 ila 18.000 avro arasında, bu sonuç birçok bağlamda doğruluğu aşan bir sonuçtur. Standart özellikler için insana değer verenler.
AVM Sistem Mimarisi
Bir kurumsal AVM sistemi, her birinin sorumlulukları olan beş ana katmandan oluşur. iyi tanımlanmış ve spesifik gecikme gereksinimleri.
# Architettura AVM - Schema a livelli
┌─────────────────────────────────────────────────────┐
│ DATA LAYER │
│ MLS Feed │ Catasto │ Transazioni │ OSM/Maps │
└─────────────────────┬───────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────┐
│ FEATURE ENGINEERING │
│ Property Features │ Location │ Market │ Temporal │
└─────────────────────┬───────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────┐
│ MODEL ENSEMBLE │
│ XGBoost │ LightGBM │ Neural Net │ CMA Model │
└─────────────────────┬───────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────┐
│ INFERENCE & EXPLAINABILITY │
│ Confidence Interval │ SHAP Values │ Comparables │
└─────────────────────┬───────────────────────────────┘
│
┌─────────────────────▼───────────────────────────────┐
│ SERVING LAYER (FastAPI) │
│ REST API │ Caching │ Monitoring │ Audit Log │
└─────────────────────────────────────────────────────┘
Veri Kümeleri ve Özellik Mühendisliği
Etkili bir AVM'nin kalbi özellik mühendisliğidir. Zillow araştırmacıları Makine öğrenimi ekiplerinin genellikle farklı algoritmalarla denemeler yaparak haftalar harcadığını belgeledi, gerçek rekabet avantajının özelliklerde değil, özelliklerde yattığı zaman modelin karmaşıklığı.
Özellikler dört makro kategoriye ayrılmıştır:
| Kategori | Ana Özellikler | Veri Kaynağı | AVM etkisi |
|---|---|---|---|
| Fiziksel Özellikler | yüzey alanı, odalar, banyolar, yapım yılı, zemin, koruma durumu | Tapu Sicili, MLS | Yüksek (%35-45) |
| Konum | GPS koordinatları, mahalle, yakındaki okullar, ulaşım, sel riski | OpenStreetMap, ISTAT, PCN | Çok Yüksek (%40-50) |
| Pazar | karşılaştırılabilir fiyatlar, bölge eğilimleri, pazardaki günler, emilim oranı | MLS, noter işlemleri | Orta (%10-20) |
| Makro-ekonomik | ipotek oranları, enflasyon, inşaat endeksleri, yerel GSYİH | ECB, ISTAT, İtalya Bankası | Düşük-Orta (%5-10) |
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from geopy.distance import geodesic
class PropertyFeatureEngineer:
"""
Feature engineering per AVM immobiliare.
Gestisce attributi fisici, location e market features.
"""
def __init__(self, comparables_db, poi_db):
self.comparables_db = comparables_db
self.poi_db = poi_db
self.scaler = StandardScaler()
def build_physical_features(self, prop: dict) -> dict:
"""Feature relative agli attributi fisici dell'immobile."""
surface = prop['superficie_mq']
rooms = prop['num_vani']
bathrooms = prop['num_bagni']
year_built = prop['anno_costruzione']
return {
'superficie_mq': surface,
'num_vani': rooms,
'num_bagni': bathrooms,
'rapporto_superficie_vani': surface / max(rooms, 1),
'eta_immobile': 2025 - year_built,
'eta_categoria': self._categorize_age(year_built),
'piano_normalizzato': prop.get('piano', 0) / max(prop.get('piani_totali', 1), 1),
'is_ultimo_piano': int(prop.get('piano', 0) == prop.get('piani_totali', 0)),
'has_garage': int(prop.get('garage', False)),
'has_terrazzo': int(prop.get('terrazzo', False)),
'classe_energetica_encoded': self._encode_energy_class(
prop.get('classe_energetica', 'G')
),
}
def build_location_features(self, lat: float, lon: float) -> dict:
"""Feature geografiche e di prossimita ai servizi."""
coords = (lat, lon)
# Distanze dai principali POI
nearest_school = self._nearest_poi(coords, 'scuola')
nearest_metro = self._nearest_poi(coords, 'metro')
nearest_hospital = self._nearest_poi(coords, 'ospedale')
nearest_supermarket = self._nearest_poi(coords, 'supermercato')
city_center = self.poi_db.get_city_center()
return {
'lat': lat,
'lon': lon,
'dist_scuola_km': nearest_school['distance'],
'dist_metro_km': nearest_metro['distance'],
'dist_ospedale_km': nearest_hospital['distance'],
'dist_supermercato_km': nearest_supermarket['distance'],
'dist_centro_km': geodesic(coords, city_center).km,
'zona_istat': self._get_zone_code(lat, lon),
'reddito_medio_zona': self._get_zone_income(lat, lon),
'densita_abitativa': self._get_population_density(lat, lon),
'walk_score': self._calculate_walk_score(lat, lon),
'rischio_idrogeologico': self._get_flood_risk(lat, lon),
}
def build_market_features(self, lat: float, lon: float,
surface: float, reference_date: str) -> dict:
"""Feature di mercato basate su transazioni recenti nell'area."""
# CRITICO: usare solo dati passati rispetto a reference_date
# per evitare data leakage!
comps = self.comparables_db.get_comparables(
lat=lat,
lon=lon,
radius_km=0.5,
before_date=reference_date,
limit=20
)
if len(comps) == 0:
# Fallback su area più ampia
comps = self.comparables_db.get_comparables(
lat=lat, lon=lon, radius_km=2.0,
before_date=reference_date, limit=20
)
prices_per_sqm = [c['prezzo'] / c['superficie_mq'] for c in comps]
return {
'prezzo_medio_mq_zona': np.mean(prices_per_sqm) if prices_per_sqm else 0,
'prezzo_mediano_mq_zona': np.median(prices_per_sqm) if prices_per_sqm else 0,
'prezzo_std_mq_zona': np.std(prices_per_sqm) if prices_per_sqm else 0,
'num_transazioni_6m': len(comps),
'dom_medio_zona': np.mean([c.get('days_on_market', 0) for c in comps]),
'trend_prezzi_12m': self._calculate_price_trend(lat, lon, reference_date),
'absorption_rate': self._calculate_absorption_rate(lat, lon, reference_date),
}
def _categorize_age(self, year_built: int) -> int:
"""Categorizza l'eta dell'immobile in fasce storiche."""
if year_built < 1919: return 0 # Storico
elif year_built < 1945: return 1 # Pre-guerra
elif year_built < 1970: return 2 # Dopoguerra
elif year_built < 1990: return 3 # Anni 70-80
elif year_built < 2000: return 4 # Anni 90
elif year_built < 2010: return 5 # Anni 2000
else: return 6 # Recente
def _encode_energy_class(self, energy_class: str) -> float:
"""Converte la classe energetica in valore numerico."""
mapping = {'A4': 10, 'A3': 9, 'A2': 8, 'A1': 7, 'A': 7,
'B': 6, 'C': 5, 'D': 4, 'E': 3, 'F': 2, 'G': 1}
return mapping.get(energy_class.upper(), 1)
def _calculate_price_trend(self, lat, lon, reference_date) -> float:
"""Calcola il trend % dei prezzi negli ultimi 12 mesi."""
# Prezzi mediani 12m fa vs 6m fa vs oggi
prices_12m = self.comparables_db.get_median_price(lat, lon, reference_date, months_back=12)
prices_6m = self.comparables_db.get_median_price(lat, lon, reference_date, months_back=6)
if prices_12m and prices_12m > 0:
return (prices_6m - prices_12m) / prices_12m * 100
return 0.0
Değerlendirme için Makine Öğrenimi Modelleri
Zillow, CoreLogic ve akademik araştırmacılar tarafından yayınlanan kıyaslamalar gösteriyor degrade artırma modellerinin (XGBoost, LightGBM, CatBoost) sürekli olarak hakim olduğu tablo halindeki emlak verileri için doğruluk sıralamaları. Sinir ağları para kazanıyor mülk görselleri özellik olarak mevcut olduğunda arazi.
| Modeli | MdAPE Tipik | Eğitim Hızı | Yorumlanabilirlik | En İyisi |
|---|---|---|---|---|
| XGBoost | %3,8 - 5,2 | Orta | Yüksek (SHAP) | Dengeli veri kümeleri, önemli özellikler |
| LightGBM | %3,5 - 4,9 | Çok hızlı | Yüksek (SHAP) | Büyük veri kümeleri, kategorik özellikler |
| KediBoost | %3,6 - 5,0 | Orta | Yüksek (SHAP) | Kodlama olmadan kategorik özellikler |
| Rastgele Orman | %4,5 - 6,5 | Yavaş | Ortalama | Sağlam taban çizgisi, aykırı direnç |
| Sinir Ağı (tablo) | %4,0 - %5,5 | Çok Yavaş | Düşük | Karmaşık özellikler, görüntü entegrasyonu |
| Topluluk (istifleme) | %3,2 - 4,5 | - | Ortalama | Üretim, maksimum doğruluk |
import xgboost as xgb
import lightgbm as lgb
from sklearn.ensemble import RandomForestRegressor, StackingRegressor
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score, KFold
from sklearn.metrics import mean_absolute_percentage_error
import shap
import numpy as np
class AVMEnsemble:
"""
Ensemble di modelli per valutazione immobiliare.
Combina XGBoost, LightGBM e Random Forest con meta-learner Ridge.
"""
def __init__(self):
self.xgb_model = xgb.XGBRegressor(
n_estimators=1000,
learning_rate=0.05,
max_depth=6,
subsample=0.8,
colsample_bytree=0.8,
reg_alpha=0.1,
reg_lambda=1.0,
random_state=42,
n_jobs=-1,
early_stopping_rounds=50,
)
self.lgb_model = lgb.LGBMRegressor(
n_estimators=1000,
learning_rate=0.05,
num_leaves=63,
min_child_samples=20,
feature_fraction=0.8,
bagging_fraction=0.8,
bagging_freq=5,
lambda_l1=0.1,
lambda_l2=1.0,
random_state=42,
n_jobs=-1,
verbose=-1,
)
self.rf_model = RandomForestRegressor(
n_estimators=500,
max_depth=None,
min_samples_leaf=5,
n_jobs=-1,
random_state=42,
)
# Meta-learner: combina le previsioni dei base models
self.ensemble = StackingRegressor(
estimators=[
('xgb', self.xgb_model),
('lgb', self.lgb_model),
('rf', self.rf_model),
],
final_estimator=Ridge(alpha=1.0),
cv=5,
n_jobs=-1,
)
self.explainer = None
def train(self, X_train, y_train, X_val=None, y_val=None):
"""Addestramento con cross-validation e early stopping."""
# Early stopping per XGBoost
if X_val is not None:
self.xgb_model.fit(
X_train, np.log1p(y_train), # log-transform per stabilità
eval_set=[(X_val, np.log1p(y_val))],
verbose=False,
)
self.lgb_model.fit(
X_train, np.log1p(y_train),
eval_set=[(X_val, np.log1p(y_val))],
callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)],
)
else:
self.xgb_model.fit(X_train, np.log1p(y_train))
self.lgb_model.fit(X_train, np.log1p(y_train))
self.rf_model.fit(X_train, np.log1p(y_train))
# Fit dell'ensemble finale
self.ensemble.fit(X_train, np.log1p(y_train))
# Inizializza SHAP explainer per interpretabilita
self.explainer = shap.TreeExplainer(self.xgb_model)
return self
def predict(self, X) -> dict:
"""
Restituisce valutazione con intervallo di confidenza.
"""
log_pred = self.ensemble.predict(X)
price_pred = np.expm1(log_pred) # Inverti log-transform
# Calcola incertezza tramite predizioni dei singoli modelli
xgb_pred = np.expm1(self.xgb_model.predict(X))
lgb_pred = np.expm1(self.lgb_model.predict(X))
rf_pred = np.expm1(self.rf_model.predict(X))
predictions = np.stack([xgb_pred, lgb_pred, rf_pred])
uncertainty = np.std(predictions, axis=0) / price_pred
return {
'valuation': price_pred,
'low_estimate': price_pred * (1 - 2 * uncertainty),
'high_estimate': price_pred * (1 + 2 * uncertainty),
'confidence_score': np.clip(1 - uncertainty * 10, 0, 1),
}
def explain(self, X_single) -> dict:
"""
Genera spiegazione SHAP per una singola valutazione.
Mostra quali feature hanno influenzato il prezzo e in che misura.
"""
shap_values = self.explainer.shap_values(X_single)
feature_impacts = [
{
'feature': feat,
'value': float(X_single[feat]),
'impact_euro': float(shap_val * np.expm1(1)), # Approx
'direction': 'positive' if shap_val > 0 else 'negative',
}
for feat, shap_val in zip(X_single.index, shap_values[0])
]
# Ordina per impatto assoluto
feature_impacts.sort(key=lambda x: abs(x['impact_euro']), reverse=True)
return {
'top_features': feature_impacts[:10],
'base_value': float(np.expm1(self.explainer.expected_value)),
'final_value': float(np.expm1(self.explainer.expected_value + shap_values[0].sum())),
}
def evaluate(self, X_test, y_test) -> dict:
"""Metriche di valutazione complete."""
predictions = self.predict(X_test)
pred_prices = predictions['valuation']
mape = mean_absolute_percentage_error(y_test, pred_prices) * 100
mdape = np.median(np.abs((y_test - pred_prices) / y_test)) * 100
# Percentuale previsioni entro 5%, 10%, 20% del valore reale
errors = np.abs((y_test - pred_prices) / y_test)
within_5 = np.mean(errors <= 0.05) * 100
within_10 = np.mean(errors <= 0.10) * 100
within_20 = np.mean(errors <= 0.20) * 100
return {
'mape': round(mape, 2),
'mdape': round(mdape, 2),
'within_5pct': round(within_5, 1),
'within_10pct': round(within_10, 1),
'within_20pct': round(within_20, 1),
}
Hedonik Fiyatlandırma Modeli ve Karşılaştırılabilir Piyasa Analizi
Saf ML modellerine ek olarak, bir üretim AVM'si iki tamamlayıcı yaklaşımı birleştirir: the Hedonik Fiyatlandırma Modeli (HPM) ve Karşılaştırılabilir Piyasa Analizi (CMA). HPM, bir mülkün fiyatını her mülkün zımni değerlerinin toplamı olarak ele alır. karakteristik (her metrekare X değerindedir, her ilave banyo Y değerindedir, vb.). Ancak CMA, yakın zamanda satılan benzer mülkleri araştırıyor ve gerekli düzenlemeleri yapıyor farkların fiyatı.
from dataclasses import dataclass
from typing import List
import numpy as np
@dataclass
class Comparable:
id: str
prezzo: float
superficie_mq: float
num_vani: int
num_bagni: int
distanza_km: float
giorni_fa: int
lat: float
lon: float
class ComparableMarketAnalysis:
"""
CMA: stima il valore comparando con immobili simili venduti di recente.
Applica aggiustamenti per differenze nelle caratteristiche.
"""
# Aggiustamenti di mercato (da calibrare per zona)
ADJUSTMENTS = {
'per_mq_extra': 1800, # EUR per mq di differenza
'per_bagno_extra': 8000, # EUR per bagno aggiuntivo
'per_anno_eta': -150, # EUR per anno di eta
'per_piano': 1200, # EUR per piano (es: piano 3 vs piano 1)
'garage_premium': 15000, # EUR per garage incluso
'terrazzo_premium': 8000, # EUR per terrazzo
}
def estimate(self, subject: dict, comparables: List[Comparable]) -> dict:
"""
Stima il valore dell'immobile tramite analisi dei comparables.
"""
if not comparables:
return {'error': 'Nessun comparable disponibile'}
adjusted_prices = []
for comp in comparables:
adjusted_price = comp.prezzo
# Aggiustamento superficie
surface_diff = subject['superficie_mq'] - comp.superficie_mq
adjusted_price += surface_diff * self.ADJUSTMENTS['per_mq_extra']
# Aggiustamento bagni
bath_diff = subject.get('num_bagni', 1) - comp.num_bagni
adjusted_price += bath_diff * self.ADJUSTMENTS['per_bagno_extra']
# Aggiustamento eta (più recente = più valore)
age_diff = (2025 - subject.get('anno_costruzione', 1980)) - \
(2025 - getattr(comp, 'anno_costruzione', 1980))
adjusted_price += age_diff * self.ADJUSTMENTS['per_anno_eta']
# Peso per distanza e freschezza dei dati
distance_weight = 1 / (1 + comp.distanza_km * 2)
recency_weight = 1 / (1 + comp.giorni_fa / 90)
weight = distance_weight * recency_weight
adjusted_prices.append((adjusted_price, weight))
# Media ponderata
total_weight = sum(w for _, w in adjusted_prices)
weighted_value = sum(p * w for p, w in adjusted_prices) / total_weight
return {
'cma_value': round(weighted_value, -3), # Arrotonda a migliaia
'num_comparables': len(comparables),
'comparable_range': {
'min': min(p for p, _ in adjusted_prices),
'max': max(p for p, _ in adjusted_prices),
},
}
FastAPI ile Değerlendirme API'si
Model sunumu, FastAPI'ye sahip bir REST API aracılığıyla yapılır. Dakikada binlerce isteği 200 ms'nin altındaki gecikmeyle yönetin.
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field, field_validator
from typing import Optional
import numpy as np
import logging
app = FastAPI(title="AVM API", version="2.0.0")
logger = logging.getLogger(__name__)
class PropertyInput(BaseModel):
"""Schema di input per la valutazione immobiliare."""
superficie_mq: float = Field(..., gt=10, lt=2000, description="Superficie in mq")
num_vani: int = Field(..., ge=1, le=20)
num_bagni: int = Field(..., ge=1, le=10)
anno_costruzione: int = Field(..., ge=1800, le=2025)
piano: int = Field(default=0, ge=0, le=50)
piani_totali: int = Field(default=1, ge=1, le=50)
classe_energetica: str = Field(default='G')
has_garage: bool = False
has_terrazzo: bool = False
lat: float = Field(..., ge=35.0, le=48.0) # Italia
lon: float = Field(..., ge=6.0, le=19.0)
@field_validator('classe_energetica')
@classmethod
def validate_energy_class(cls, v):
valid = ['A4', 'A3', 'A2', 'A1', 'A', 'B', 'C', 'D', 'E', 'F', 'G']
if v.upper() not in valid:
raise ValueError(f'Classe energetica non valida. Usa: {valid}')
return v.upper()
class ValuationResponse(BaseModel):
"""Risposta della valutazione con range e spiegazione."""
valuation: float
low_estimate: float
high_estimate: float
confidence_score: float
price_per_sqm: float
comparable_value: Optional[float]
top_factors: list
model_version: str
@app.post("/api/v1/valuation", response_model=ValuationResponse)
async def valuate_property(
prop: PropertyInput,
model: AVMEnsemble = Depends(get_model),
feature_eng: PropertyFeatureEngineer = Depends(get_feature_engineer),
):
"""
Valuta un immobile con ML ensemble + CMA.
Restituisce stima puntuale, range e spiegazione.
"""
try:
# Feature engineering
features = {
**feature_eng.build_physical_features(prop.dict()),
**feature_eng.build_location_features(prop.lat, prop.lon),
**feature_eng.build_market_features(
prop.lat, prop.lon, prop.superficie_mq,
reference_date='2025-03-01'
),
}
X = pd.DataFrame([features])
# Predizione ensemble
prediction = model.predict(X)
explanation = model.explain(X.iloc[0])
# CMA come cross-check
comparables = feature_eng.comparables_db.get_comparables(
lat=prop.lat, lon=prop.lon, radius_km=1.0,
before_date='2025-03-01', limit=10
)
cma = ComparableMarketAnalysis().estimate(prop.dict(), comparables)
logger.info(
"Valuation completed",
extra={
"lat": prop.lat, "lon": prop.lon,
"valuation": prediction['valuation'][0],
"confidence": prediction['confidence_score'][0],
}
)
return ValuationResponse(
valuation=round(float(prediction['valuation'][0]), -3),
low_estimate=round(float(prediction['low_estimate'][0]), -3),
high_estimate=round(float(prediction['high_estimate'][0]), -3),
confidence_score=round(float(prediction['confidence_score'][0]), 3),
price_per_sqm=round(float(prediction['valuation'][0]) / prop.superficie_mq, 0),
comparable_value=cma.get('cma_value'),
top_factors=explanation['top_features'][:5],
model_version="avm-v2.1.0",
)
except Exception as e:
logger.error(f"Valuation error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Errore durante la valutazione")
Açıkça Yapay Zeka ile Model Sapması İzleme
Gayrimenkul değerleme modelleri sapmalara karşı özellikle hassastır zamanla: fiyatlar değişir, mahalleler dönüşür, yeni altyapı inşa edilmiştir. Üretimdeki bir AVM sisteminin sürekli izlemesi gerekir tahminlerin kalitesi ve veri dağıtımındaki değişiklikler.
from evidently.report import Report
from evidently.metrics import (
DataDriftTable, ColumnDriftMetric,
RegressionQualityMetric, RegressionPredictedVsActualScatter,
)
from evidently.test_suite import TestSuite
from evidently.tests import (
TestColumnDrift, TestValueMeanInNSigmas,
)
import pandas as pd
from datetime import datetime, timedelta
class AVMMonitor:
"""
Monitoraggio continuo della qualità del modello AVM.
Rilevazione data drift e degradazione delle performance.
"""
def __init__(self, reference_data: pd.DataFrame):
self.reference_data = reference_data
def run_quality_report(self, current_data: pd.DataFrame,
predictions: pd.Series,
actuals: pd.Series) -> dict:
"""
Report completo: data drift + qualità regressione.
"""
report = Report(metrics=[
DataDriftTable(),
RegressionQualityMetric(),
RegressionPredictedVsActualScatter(),
ColumnDriftMetric(column_name='prezzo_medio_mq_zona'),
ColumnDriftMetric(column_name='superficie_mq'),
])
# Unisci predictions e actuals al current_data
eval_data = current_data.copy()
eval_data['prediction'] = predictions.values
eval_data['target'] = actuals.values
report.run(
reference_data=self.reference_data,
current_data=eval_data,
)
report_dict = report.as_dict()
# Estrai metriche chiave
metrics = report_dict.get('metrics', [])
drift_detected = any(
m.get('result', {}).get('drift_detected', False)
for m in metrics
)
return {
'drift_detected': drift_detected,
'report_date': datetime.now().isoformat(),
'metrics': report_dict,
}
def run_test_suite(self, current_data: pd.DataFrame) -> dict:
"""
Test automatici: fallisce se il modello degrada oltre soglie.
"""
tests = TestSuite(tests=[
TestColumnDrift(column_name='superficie_mq'),
TestColumnDrift(column_name='prezzo_medio_mq_zona'),
TestValueMeanInNSigmas(
column_name='error_pct',
n=3, # Allerta se media errore supera 3 sigma
),
])
tests.run(
reference_data=self.reference_data,
current_data=current_data,
)
return {
'passed': tests.as_dict()['summary']['all_passed'],
'results': tests.as_dict(),
}
PropTech'te Algoritmik Önyargı Yönetimi
Mayıs 2024'te HUD, Adil Konut Yasası aynı zamanda otomatik derecelendirme sistemleri için de geçerlidir. Alanlarda sistematik olarak daha düşük derecelendirmeler üreten bir AVM modeli Etnik azınlıkların yaygınlığı yasanın ihlali anlamına gelebilir, ayrımcı bir kast olmasa bile.
Zorunlu önlemler:
- Irk, etnik köken, milliyet ve dini özelliklerden proaktif bir şekilde hariç tutun
- Derecelendirmeleri posta koduna göre izleyin ve demografik yapıyla karşılaştırın
- CI/CD hattının bir parçası olarak adalet testlerini (farklı etki, eşitlenmiş oranlar) uygulayın
- Gerçekleştirilen her değerlendirmenin eksiksiz denetim kayıtlarını tutun
- Kullanıcı isteği üzerine her bir tahmini açıklamak için SHAP değerlerini kullanın
En İyi Uygulamalar ve Anti-Kalıplar
Üretimde AVM için En İyi Uygulamalar
- Hedefin log dönüşümü: Gayrimenkul fiyatları log-normal dağılıma uymaktadır. Log1p()'nin hedefe uygulanması aykırı değerlerin etkisini azaltır ve eğitimi geliştirir.
- Kesin zaman ayrımı: Özellik mühendisliğinde (sızıntı) asla tahmin tarihinden sonraki verileri kullanmayın. Her zaman kullan
before_datekarşılaştırılabilir sorgularda. - Zorunlu güven aralığı: Güven aralığı olmadan asla bir nokta tahmini döndürmeyin. Kullanıcıların modelin ne kadar belirsiz olduğunu bilmeleri gerekir.
- CMA'da geri dönüş: ML güvenirliği düşükse (<0,5), birincil tahmin olarak CMA'yı kullanın veya ikisini güvenle orantılı ağırlıkla birleştirin.
- Aylık Yeniden Eğitim: AVM modelleri değişken piyasalarda hızla bozulur. 24 aylık sürekli veriler üzerinde aylık yeniden eğitim planlayın.
- Model versiyonlaması: Her tahmin, onu oluşturan modelin spesifik versiyonuna göre izlenebilir olmalıdır (denetim uyumluluğu).
Kritik Anti-Desenler
- Özellik Sızıntısı: İşlemlerden elde edilen verilerin değerlendirme tarihinden sonra dahil edilmesi, eğitimde yapay olarak doğru olan ancak üretimde işe yaramayan modeller üretir.
- Posta koduna fazla uyum: Posta kodunu yumuşatmadan kategorik bir özellik olarak kullanmak, az veri içeren alanlar için istikrarsızlığa yol açar.
- Aykırı değerleri göz ardı edin: Lüks mülkler, açık artırmalar, portföy elden çıkarmaları eğitimi çarpıtıyor. Ayrı olarak filtreleyin veya tartın.
- Doğrulamadan tahmin edin: İzleme sistemi olmayan bir AVM, hareketli pazarlarda birkaç ay sonra güvenilmez hale gelir.
Sonuçlar ve Sonraki Adımlar
Güvenilir bir AVM oluşturmak, iyi bir makine öğrenimi algoritmasından daha fazlasını gerektirir: Titiz özellik mühendisliği, algoritmik önyargı yönetimi, yorumlanabilirlik SHAP değerleri ve sürekli izleme sistemi aracılığıyla. Gradyan artırma modelleri (XGBoost, LightGBM) gayrimenkul tablo verileri açısından en gelişmiş teknoloji olmaya devam ediyor, ancak CMA ve güven katmanının birleşimi, bir prodüksiyon AVM'sini diğerlerinden ayıran özelliklerdir basit bir akademik alıştırmadan.
PropTech serisindeki diğer makaleleri keşfedin
- Madde 00 - Scala'daki Gayrimenkul Platformunun Mimarisi
- Madde 02 - BIM Yazılım Mimarisi: AEC için 3D Modelleme
- Madde 03 - Akıllı Bina IoT: Sensör Entegrasyonu ve Uç Bilgi İşlem
- Madde 09 - PropTech'te Gizlilik ve Uyumluluk: Adil Konut ve Önyargı







