Spaces:
Build error
Build error
Download src/models/ensemble.py from leovean/Forex-AI-Analyst: direct link, hf CLI and curl.
- Browser
- Download file 5.49 kB
-
https://huggingface.co/spaces/leovean/Forex-AI-Analyst/resolve/main/src/models/ensemble.py
- Command line
-
hf download hf://spaces/leovean/Forex-AI-Analyst/src/models/ensemble.py
-
curl -L -o ensemble.py https://huggingface.co/spaces/leovean/Forex-AI-Analyst/resolve/main/src/models/ensemble.py
5.49 kB
| # src/models/ensemble.py | |
| import numpy as np | |
| import pandas as pd | |
| import lightgbm as lgb | |
| import xgboost as xgb | |
| from catboost import CatBoostClassifier | |
| from sklearn.linear_model import LogisticRegression | |
| import joblib | |
| import os | |
| class SuperLearnerEnsemble: | |
| """ | |
| Ensemble of XGBoost, LightGBM, CatBoost with a logistic regression meta-learner. | |
| Now stores feature names to prevent shape mismatches. | |
| """ | |
| def __init__(self): | |
| self.xgb_model = None | |
| self.lgb_model = None | |
| self.cat_model = None | |
| self.meta_learner = LogisticRegression(max_iter=1000) | |
| self.is_fitted = False | |
| self.feature_names = None # NEW: store feature names | |
| def fit(self, X_train, y_train, X_val=None, y_val=None, feature_names=None): | |
| """ | |
| Train base models and meta-learner. | |
| Args: | |
| X_train: Training features (numpy array or DataFrame) | |
| y_train: Training targets | |
| X_val: Validation features for meta-learner training | |
| y_val: Validation targets for meta-learner training | |
| feature_names: List of feature names (optional, extracted if X is DataFrame) | |
| """ | |
| # Extract feature names if available | |
| if feature_names is not None: | |
| self.feature_names = feature_names | |
| elif hasattr(X_train, 'columns'): | |
| self.feature_names = list(X_train.columns) | |
| else: | |
| self.feature_names = [f"feature_{i}" for i in range(X_train.shape[1])] | |
| # Convert to numpy if DataFrame | |
| if hasattr(X_train, 'values'): | |
| X_train = X_train.values | |
| if X_val is not None and hasattr(X_val, 'values'): | |
| X_val = X_val.values | |
| # Initialize base models | |
| self.xgb_model = xgb.XGBClassifier( | |
| n_estimators=200, max_depth=6, learning_rate=0.05, | |
| objective='binary:logistic', random_state=42, | |
| use_label_encoder=False, eval_metric='logloss' | |
| ) | |
| self.lgb_model = lgb.LGBMClassifier( | |
| n_estimators=200, max_depth=6, learning_rate=0.05, | |
| random_state=42, verbose=-1 | |
| ) | |
| self.cat_model = CatBoostClassifier( | |
| n_estimators=200, depth=6, learning_rate=0.05, | |
| random_state=42, verbose=0 | |
| ) | |
| # Train base models | |
| self.xgb_model.fit(X_train, y_train) | |
| self.lgb_model.fit(X_train, y_train) | |
| self.cat_model.fit(X_train, y_train) | |
| # Meta-learner training | |
| if X_val is not None and y_val is not None: | |
| meta_X = np.column_stack([ | |
| self.xgb_model.predict_proba(X_val)[:, 1], | |
| self.lgb_model.predict_proba(X_val)[:, 1], | |
| self.cat_model.predict_proba(X_val)[:, 1] | |
| ]) | |
| self.meta_learner.fit(meta_X, y_val) | |
| else: | |
| split = int(len(X_train) * 0.8) | |
| X_train_base, X_meta = X_train[:split], X_train[split:] | |
| y_train_base, y_meta = y_train[:split], y_train[split:] | |
| self.xgb_model.fit(X_train_base, y_train_base) | |
| self.lgb_model.fit(X_train_base, y_train_base) | |
| self.cat_model.fit(X_train_base, y_train_base) | |
| meta_X = np.column_stack([ | |
| self.xgb_model.predict_proba(X_meta)[:, 1], | |
| self.lgb_model.predict_proba(X_meta)[:, 1], | |
| self.cat_model.predict_proba(X_meta)[:, 1] | |
| ]) | |
| self.meta_learner.fit(meta_X, y_meta) | |
| self.is_fitted = True | |
| return self | |
| def predict_proba(self, X): | |
| if not self.is_fitted: | |
| raise ValueError("Model not fitted.") | |
| # Convert to numpy if DataFrame | |
| if hasattr(X, 'values'): | |
| X = X.values | |
| xgb_proba = self.xgb_model.predict_proba(X)[:, 1] | |
| lgb_proba = self.lgb_model.predict_proba(X)[:, 1] | |
| cat_proba = self.cat_model.predict_proba(X)[:, 1] | |
| meta_X = np.column_stack([xgb_proba, lgb_proba, cat_proba]) | |
| return self.meta_learner.predict_proba(meta_X)[:, 1] | |
| def predict(self, X): | |
| proba = self.predict_proba(X) | |
| return (proba >= 0.5).astype(int) | |
| def save(self, path_prefix: str): | |
| os.makedirs(os.path.dirname(path_prefix), exist_ok=True) | |
| joblib.dump(self.xgb_model, f"{path_prefix}_xgb.joblib") | |
| joblib.dump(self.lgb_model, f"{path_prefix}_lgb.joblib") | |
| joblib.dump(self.cat_model, f"{path_prefix}_cat.joblib") | |
| joblib.dump(self.meta_learner, f"{path_prefix}_meta.joblib") | |
| # Save feature names | |
| joblib.dump(self.feature_names, f"{path_prefix}_features.joblib") | |
| print(f"✅ Ensemble saved with prefix '{path_prefix}'") | |
| def load(self, path_prefix: str): | |
| self.xgb_model = joblib.load(f"{path_prefix}_xgb.joblib") | |
| self.lgb_model = joblib.load(f"{path_prefix}_lgb.joblib") | |
| self.cat_model = joblib.load(f"{path_prefix}_cat.joblib") | |
| self.meta_learner = joblib.load(f"{path_prefix}_meta.joblib") | |
| # Load feature names if available | |
| try: | |
| self.feature_names = joblib.load(f"{path_prefix}_features.joblib") | |
| except: | |
| self.feature_names = None | |
| print("⚠️ No feature names file found.") | |
| self.is_fitted = True | |
| print(f"✅ Ensemble loaded from '{path_prefix}_*.joblib'") | |
| return self |