SPB Git forge

spb/wp23_uqo

Public
6commits 1branches 0releases
32.7 MBsize
maindefault branch
yesterdaylast push
TeX 62.3% Python 37.6%
4.4 KB · 100 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca2"""Task A models (property-level price at a future sale date). All expose fit(X, y, meta) / predict(X, meta) → point (log price)3and, where available, predict_quantiles(X, meta, taus) → (n, len(taus)). `meta` carries the categorical keys for the4hierarchical model (municipality / MRC / region codes, horizon)."""5from __future__ import annotations67import os89import numpy as np1011# NOTE (macOS): LightGBM / XGBoost (libomp) and PyTorch cannot train in the same process without a segmentation fault or a12# deadlock in the OpenMP runtime. Tree models live here (lazy imports); neural models live in models_a_torch.py; the runner13# executes the two families in separate processes (`--family trees|torch`).14from sklearn.ensemble import RandomForestRegressor15from sklearn.linear_model import ElasticNet, Ridge16from sklearn.svm import SVR1718TAUS = np.array([0.05, 0.25, 0.5, 0.75, 0.95])192021class OLS:22    name = "hedonic_ols"23    def fit(self, X, y, meta=None):24        Xa = np.column_stack([np.ones(len(X)), X]); self.b = np.linalg.lstsq(Xa, y, rcond=None)[0]25        r = y - Xa @ self.b; self.sigma = r.std(); return self26    def predict(self, X, meta=None):27        return np.column_stack([np.ones(len(X)), X]) @ self.b28    def predict_quantiles(self, X, meta=None, taus=TAUS):29        from scipy.stats import norm30        m = self.predict(X); return m[:, None] + self.sigma * norm.ppf(taus)[None, :]313233class RidgeM(OLS):34    name = "ridge"35    def fit(self, X, y, meta=None):36        self.mu = X.mean(0); self.sd = X.std(0) + 1e-9; self.m = Ridge(alpha=1.0).fit((X - self.mu) / self.sd, y)37        self.sigma = (y - self.m.predict((X - self.mu) / self.sd)).std(); return self38    def predict(self, X, meta=None):39        return self.m.predict((X - self.mu) / self.sd)404142class ElasticNetM(RidgeM):43    name = "elastic_net"44    def fit(self, X, y, meta=None):45        self.mu = X.mean(0); self.sd = X.std(0) + 1e-9; self.m = ElasticNet(alpha=0.001, l1_ratio=0.5, max_iter=3000).fit((X - self.mu) / self.sd, y)46        self.sigma = (y - self.m.predict((X - self.mu) / self.sd)).std(); return self474849class RF:50    name = "random_forest"51    def fit(self, X, y, meta=None):52        self.m = RandomForestRegressor(n_estimators=150, max_depth=18, min_samples_leaf=5, max_features=0.5, n_jobs=int(os.environ.get("WP23_JOBS", "8")), random_state=0).fit(X, y); return self53    def predict(self, X, meta=None):54        return self.m.predict(X)555657class XGB:58    name = "xgboost"59    def fit(self, X, y, meta=None):60        import xgboost as xgb61        self.m = xgb.XGBRegressor(n_estimators=600, learning_rate=0.05, max_depth=8, subsample=0.8, colsample_bytree=0.8, tree_method="hist", n_jobs=int(os.environ.get("WP23_JOBS", "8")), random_state=0).fit(X, y); return self62    def predict(self, X, meta=None):63        return self.m.predict(X)646566class LGBM:67    name = "lightgbm"68    def __init__(self, quantiles: bool = True):69        self.quantiles = quantiles70    def fit(self, X, y, meta=None):71        import lightgbm as lgb72        kw = dict(n_estimators=800, learning_rate=0.04, num_leaves=63, min_child_samples=30, subsample=0.8, subsample_freq=1, colsample_bytree=0.8, n_jobs=int(os.environ.get("WP23_JOBS", "8")), random_state=0, verbose=-1)73        self.m = lgb.LGBMRegressor(objective="l2", **kw).fit(X, y)74        if self.quantiles:75            self.q = {t: lgb.LGBMRegressor(objective="quantile", alpha=float(t), **{**kw, "n_estimators": 400}).fit(X, y) for t in TAUS}76        return self77    def predict(self, X, meta=None):78        return self.m.predict(X)79    def predict_quantiles(self, X, meta=None, taus=TAUS):80        Q = np.column_stack([self.q[t].predict(X) for t in taus]); return np.sort(Q, axis=1)818283class SVRM:84    name = "svr"85    def fit(self, X, y, meta=None, n_max=15000):86        rng = np.random.default_rng(0); idx = rng.choice(len(X), min(n_max, len(X)), replace=False)87        self.mu = X.mean(0); self.sd = X.std(0) + 1e-9; self.m = SVR(C=3.0, epsilon=0.05, gamma="scale").fit((X[idx] - self.mu) / self.sd, y[idx]); return self88    def predict(self, X, meta=None):89        return self.m.predict((X - self.mu) / self.sd)909192def registry(family: str = "trees", fast: bool = False) -> list:93    if family == "torch":94        from wp23.models_a_torch import HSHN, MLP95        return [MLP(), HSHN("partial"), HSHN("full"), HSHN("none")]96    base = [OLS(), RidgeM(), ElasticNetM(), LGBM(), XGB()]97    if not fast:98        base += [RF(), SVRM()]99    return base100