Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 306 additions & 0 deletions mlcli/automl/base_automl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
"""
Base AutoML Abstract Class

Defines the interface and shared utilities for AutoML implementations.
Tracks best model, best score, best params, and maintains a leaderboard.
"""

from abc import abstractmethod, ABC

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in import statement. Should be "from abc import abstractmethod, ABC" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
from dataclasses import dataclass, field
Comment thread
codeMaestro78 marked this conversation as resolved.
from datetime import datetime
from typing import Any, Dict, List, Optional, Union

import numpy as np
import logging

logger = logging.getLogger(__name__)


@dataclass
class LeaderboardEntry:
# One row in the AutoML leaderboard.
model_name: str
framework: str
score: float
params: Dict[str, Any]
duration_seconds: float
Comment on lines +21 to +26

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in dataclass field annotations. Should be "model_name: str", "framework: str", etc., according to PEP 8 style guidelines for type annotations.

Copilot uses AI. Check for mistakes.
extra: Dict[str, Any] = field(default_factory=dict)


class BaseAutoML(ABC):
"""
Abstract base class for AutoML implementations.

Subclasses must implement:
- fit(X, y) -> self
- predict(X) -> np.ndarray
- predict_proba(X) -> Optional[np.ndarray]
- get_best_model() -> Any
"""

def __init__(
self,
task: str = "classification",
metric: str = "accuracy",
time_budget_minutes: Optional[Union[int, float]] = None,
random_state: Optional[int] = 42,
tracker: Optional[Any] = None,
verbose: bool = True,
) -> None:
"""
Initialize BaseAutoML.

Args:
task: 'classification' or 'regression'
metric: Scoring metric (accuracy, f1, roc_auc, etc.)
time_budget_minutes: Max time for AutoML run (None = no limit)
random_state: Random seed for reproducibility
Comment on lines +55 to +57

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons and equals signs in initialization statements. Should be "self.time_budget_minutes = time_budget_minutes" and "self.random_state = random_state" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
tracker: Optional ExperimentTracker instance
verbose: Whether to print progress

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error in comment. "Optinal" should be "Optional".

Copilot uses AI. Check for mistakes.
"""
self.task = task.lower()
self.metric = metric
self.time_budget_minutes = time_budget_minutes
self.random_state = random_state
self.verbose = verbose

# Optinal ExperimentTracker
self.tracker = tracker

# Time state
self._start_time: Optional[datetime] = None
self._end_time: Optional[datetime] = None

# Best model state
self.best_model_: Optional[Any] = None
self.best_model_name_: Optional[str] = None
self.best_score_: Optional[float] = None
self.best_params_: Dict[str, Any] = {}
Comment on lines +69 to +78

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons and equals signs in variable assignments. Should follow PEP 8 style guidelines with proper spacing: "self.best_model_: Optional[Any] = None", "self.best_model_name_: Optional[str] = None", "self.best_score_: Optional[float] = None", "self.best_params_: Dict[str, Any] = {}", "self.leaderboard_: List[LeaderboardEntry] = []", "self.is_fitted_: bool = False".

Copilot uses AI. Check for mistakes.

# Leaderboard
self.leaderboard_: List[LeaderboardEntry] = []

# Fitted flag
self.is_fitted_: bool = False

logger.debug(
f"Initialized {self.__class__.__name__}(task={self.task}, "

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function parameter type annotations. Should be "def fit(self, X: np.ndarray, y: np.ndarray, **kwargs) -> "BaseAutoML":" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
f"metric={self.metric}, time_budget={self.time_budget_minutes})"
)

@abstractmethod
def fit(self, X: np.ndarray, y: np.ndarray, **kwargs) -> "BaseAutoML":
"""
Run AutoML search and fit the best model.

Args:
X: Feature matrix
y: Target vector
**kwargs: Additional arguments

Returns:
self

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function parameter type annotations. Should be "def predict(self, X: np.ndarray) -> np.ndarray:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
"""
pass

@abstractmethod
def predict(self, X: np.ndarray) -> np.ndarray:
"""
Predict using the best model.

Args:
X: Feature matrix

Returns:
Predictions array

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function parameter type annotations. Should be "def predict_proba(self, X: np.ndarray) -> Optional[np.ndarray]:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
"""
pass

@abstractmethod
def predict_proba(self, X: np.ndarray) -> Optional[np.ndarray]:
"""
Predict probabilities using the best model.

Args:
X: Feature matrix

Returns:
Probability array or None if not supported

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function parameter type annotations. Should be "def get_best_model(self) -> Any:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
"""
pass

@abstractmethod
def get_best_model(self) -> Any:
"""
Get the best fitted model/trainer.

Returns:
Best model object

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def get_leaderboard(self) -> List[Dict[str, Any]]:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
"""
pass

def get_leaderboard(self) -> List[Dict[str, Any]]:
"""
Get leaderboard as list of dicts (JSON-serializable).

Returns:
List of leaderboard entries
"""

return [
{
"rank": i + 1,
"model_name": entry.model_name,
"framework": entry.framework,
"score": round(entry.score, 6),
Comment on lines +147 to +155

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in dictionary literals and inconsistent formatting. Should be "rank": i+1, "model_name": entry.model_name, etc., with spaces after colons in dictionary literals according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
"params": entry.params,

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in "for i ,entry". Should be "for i, entry in enumerate(self.leaderboard_):" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
"duration_seconds": round(entry.duration_seconds, 2),
"extra": entry.extra,
}
for i, entry in enumerate(self.leaderboard_)

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def get_run_duration_seconds(self) -> Optional[float]:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
]

# Get total AutoML run duration in seconds.
def get_run_duration_seconds(self) -> Optional[float]:
if self._start_time is None:
return None
end = self._end_time or datetime.now()

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def set_tracker(self, tracker: Any) -> None:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
return (end - self._start_time).total_seconds()

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space around equals sign. Should be "self.tracker = tracker" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
def set_tracker(self, tracker: Any) -> None:
# Attach an ExperimentTracker after initialization.
self.tracker = tracker

# Time Utilities

def _start_timer(self) -> None:
# Start the run timer.
self._start_time = datetime.now()
self._end_time = None

def _end_timer(self) -> None:
# End the run timer

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _time_budget_seconds(self) -> Optional[float]:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
self._end_time = datetime.now()

def _time_budget_seconds(self) -> Optional[float]:
# Get time budget in seconds

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space around multiplication operator. Should be "return self.time_budget_minutes * 60.0" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
if self.time_budget_minutes is None:
return None

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _elapsed_seconds(self) -> float:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
return self.time_budget_minutes * 60.0

def _elapsed_seconds(self) -> float:
# Get elapsed time since start.
if self._start_time is None:
return 0.0

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _remaining_seconds(self) -> Optional[float]:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
return (datetime.now() - self._start_time).total_seconds()

def _remaining_seconds(self) -> Optional[float]:
# Get remaining time budget in seconds.
budget = self._time_budget_seconds()

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in "max(0.0 ,budget". Should be "max(0.0, budget - self._elapsed_seconds())" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
if budget is None:
return None

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _time_budget_exceeded(self) -> bool:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
return max(0.0, budget - self._elapsed_seconds())

def _time_budget_exceeded(self) -> bool:
# Check if the time budget is exceeded.
budget = self._time_budget_seconds()

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space around comparison operator. Should be "return self._elapsed_seconds() >= budget" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
if budget is None:
return False
return self._elapsed_seconds() >= budget

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _maybe_log_params(self, params: Dict[str, Any]) -> None:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
# Tracker Utilities

def _maybe_log_params(self, params: Dict[str, Any]) -> None:
# Log params to tracker if available.
if self.tracker is None:
return
try:
self.tracker.log_params(params)
except Exception as e:

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _maybe_log_metrics(self, metrics: Dict[str, Any], prefix: str = "") -> None:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
logger.debug(f"Tracker log_params failed: {e}")

def _maybe_log_metrics(self, metrics: Dict[str, Any], prefix: str = "") -> None:
# Log metrics to tracker if available
if self.tracker is None:
return
Comment on lines +224 to +225

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in variable annotation and inconsistent spacing in for loop. Should be "clean: Dict[str, float] = {}" and "for k, v in metrics.items():" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
try:
clean: Dict[str, float] = {}
for k, v in metrics.items():
Comment on lines +227 to +228

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in dictionary type annotation and missing space after comma. Should be "clean[k] = float(v)" and the assignment should be "self.tracker.log_metrics(clean, prefix=prefix)" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
if isinstance(v, (int, float, np.floating, np.integer)):
clean[k] = float(v)
self.tracker.log_metrics(clean, prefix=prefix)
except Exception as e:
logger.debug(f"Tracker log_metrics failed: {e}")

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _add_leaderboard_entry(self, model_name: str, framework: str, score: float, params: Dict[str, Any], duration_seconds: float, extra: Optional[Dict[str, Any]] = None) -> None:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
# Leaderboard Utilities
def _add_leaderboard_entry(
self,
model_name: str,
framework: str,
score: float,
params: Dict[str, Any],
duration_seconds: float,

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space around equals sign. Should be "extra = extra or {}" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
extra: Optional[Dict[str, Any]] = None,
) -> None:
# Add an entry to the leaderboard and keep sorted.
entry = LeaderboardEntry(

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space around equals sign in lambda function. Should be "self.leaderboard_.sort(key = lambda e: e.score, reverse=True)" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
Comment on lines +245 to +246

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sort operation in the leaderboard assumes higher scores are better. This is correct for most metrics like accuracy, F1, and ROC-AUC, but some metrics like MSE, RMSE, or MAE are better when lower. Consider adding a parameter to track whether the metric should be maximized or minimized, or document this assumption clearly.

Copilot uses AI. Check for mistakes.
model_name=model_name,
framework=framework,
score=float(score),
params=params or {},
duration_seconds=float(duration_seconds),
extra=extra or {},
)
self.leaderboard_.append(entry)
Comment on lines +248 to +254

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _update_best_if_improved(self, model_name: str, model_obj: Any, score: float, params: Dict[str, Any]) -> bool:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
# Sort by score descending (best first)
self.leaderboard_.sort(key=lambda e: e.score, reverse=True)

def _update_best_if_improved(
self,
model_name: str,
model_obj: Any,
score: float,
params: Dict[str, Any],

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best model comparison logic assumes higher scores are better (score > self.best_score_). This works for metrics like accuracy, F1, and ROC-AUC, but for regression metrics like MSE, RMSE, or MAE, lower values are better. Consider tracking metric direction (maximize vs minimize) or clearly document this assumption in the docstring.

Copilot uses AI. Check for mistakes.
) -> bool:
"""
Update best model if score improves.

Comment on lines +265 to +267

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around equals signs in variable assignments. Should be "self.best_model_ = model_obj", "self.best_model_name_ = model_name", "self.best_params_ = params if params is not None else {}" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
Returns:
True if best was updated, False otherwise
"""

if self.best_score_ is None or score > self.best_score_:
self.best_score_ = float(score)
self.best_model_ = model_obj
self.best_model_name_ = model_name

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _validate_inputs(self, X: np.ndarray, y: np.ndarray) -> None:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
self.best_params_ = params if params is not None else {}
logger.info(f"New best model: {model_name} with score {score:.4f}")
return True
return False

# Validation Utilities

def _validate_inputs(self, X: np.ndarray, y: np.ndarray) -> None:

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space around not-equal operator. Should be "if X.shape[0] != y.shape[0]:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
# Validate input arrays.
if X is None or y is None:
raise ValueError("X and y must not be None")
if not isinstance(X, np.ndarray):
raise TypeError(f"X must be np.ndarray, got {type(X)}")
if not isinstance(y, np.ndarray):

Copilot AI Jan 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing spaces around colons in function signature. Should be "def _check_is_fitted(self) -> None:" according to PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
raise TypeError(f"y must be np.ndarray, got {type(y)}")
if X.shape[0] != y.shape[0]:
raise ValueError(f"X and y must have same n_samples: {X.shape[0]} vs {y.shape[0]}")

def _check_is_fitted(self) -> None:
# Raise error if not fitted.
if not self.is_fitted_:
raise RuntimeError(f"{self.__class__.__name__} is not fitted. Call fit() first.")

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"task={self.task}, metric={self.metric}, "
f"time_budget={self.time_budget_minutes}, "
f"best_model={self.best_model_name_}, "
f"best_score={self.best_score_})"
)