Source code for cursus.steps.hyperparams.hyperparameters_xgboost

from pydantic import Field, model_validator, PrivateAttr
from typing import List, Optional, Dict, Any, Union

from ...core.base.hyperparameters_base import ModelHyperparameters


[docs] class XGBoostModelHyperparameters(ModelHyperparameters): """ Hyperparameters for the XGBoost model training, extending the base ModelHyperparameters. Fields are organized into three tiers: 1. Tier 1: Essential User Inputs - fields that users must explicitly provide 2. Tier 2: System Inputs with Defaults - fields with reasonable defaults that can be overridden 3. Tier 3: Derived Fields - fields calculated from other fields (private attributes with properties) """ # ===== Essential User Inputs (Tier 1) ===== # These are fields that users must explicitly provide # Most essential XGBoost hyperparameters num_round: int = Field(description="The number of boosting rounds for XGBoost.") max_depth: int = Field(description="Maximum depth of a tree.") # ===== System Inputs with Defaults (Tier 2) ===== # These are fields with reasonable defaults that users can override # Override model_class from base model_class: str = Field( default="xgboost", description="Model class identifier, set to XGBoost." ) min_child_weight: float = Field( default=1.0, description="Minimum sum of instance weight (hessian) needed in a child.", ) # General Parameters booster: str = Field( default="gbtree", description="Specify which booster to use: gbtree, gblinear or dart.", ) # Booster Parameters eta: float = Field( default=0.3, ge=0.0, le=1.0, description="Step size shrinkage used in update to prevents overfitting. Alias: learning_rate.", ) gamma: float = Field( default=0.0, ge=0.0, description="Minimum loss reduction required to make a further partition on a leaf node of the tree.", ) max_delta_step: float = Field( default=0.0, description="Maximum delta step we allow each tree's weight estimation to be. If 0, no constraint.", ) subsample: float = Field( default=1.0, gt=0.0, le=1.0, description="Subsample ratio of the training instances.", ) colsample_bytree: float = Field( default=1.0, gt=0.0, le=1.0, description="Subsample ratio of columns when constructing each tree.", ) colsample_bylevel: float = Field( default=1.0, gt=0.0, le=1.0, description="Subsample ratio of columns for each level.", ) colsample_bynode: float = Field( default=1.0, gt=0.0, le=1.0, description="Subsample ratio of columns for each split.", ) lambda_xgb: float = Field( default=1.0, ge=0.0, description="L2 regularization term on weights. Alias: reg_lambda.", ) alpha_xgb: float = Field( default=0.0, ge=0.0, description="L1 regularization term on weights. Alias: reg_alpha.", ) tree_method: str = Field( default="auto", description="The tree construction algorithm used in XGBoost." ) sketch_eps: Optional[float] = Field( default=None, ge=0.0, le=1.0, description="For tree_method 'approx'. Approximately (1 / sketch_eps) buckets are made.", ) scale_pos_weight: float = Field( default=1.0, description="Control the balance of positive and negative weights, useful for unbalanced classes.", ) num_parallel_tree: Optional[int] = Field( default=None, ge=1, description="Number of parallel trees constructed during each iteration. Used for random forests.", ) # Learning Task Parameters base_score: Optional[float] = Field( default=None, description="The initial prediction score of all instances, global bias.", ) seed: Optional[int] = Field(default=None, description="Random number seed.") early_stopping_rounds: Optional[int] = Field( default=None, ge=1, description="Activates early stopping. Requires eval_metric.", ) # ===== Derived Fields (Tier 3) ===== # These are fields calculated from other fields _objective: Optional[str] = PrivateAttr(default=None) _eval_metric: Optional[Union[str, List[str]]] = PrivateAttr(default=None) model_config = ModelHyperparameters.model_config.copy() model_config.update( {"extra": "allow"} ) # Changed from "forbid" to "allow" to fix circular reference handling # Public read-only properties for derived fields @property def objective(self) -> str: """Get objective derived from is_binary.""" if self._objective is None: self._objective = "binary:logistic" if self.is_binary else "multi:softmax" return self._objective @property def eval_metric(self) -> List[str]: """Get evaluation metrics derived from is_binary.""" if self._eval_metric is None: self._eval_metric = ( ["logloss", "auc"] if self.is_binary else ["mlogloss", "merror"] ) return self._eval_metric
[docs] @model_validator(mode="after") def validate_xgboost_hyperparameters(self) -> "XGBoostModelHyperparameters": """Validate XGBoost-specific hyperparameters""" # Call the base model validator first super().validate_dimensions() # Initialize derived fields self._objective = "binary:logistic" if self.is_binary else "multi:softmax" self._eval_metric = ( ["logloss", "auc"] if self.is_binary else ["mlogloss", "merror"] ) # Validate multiclass parameters if self._objective.startswith("multi:") and self.num_classes < 2: raise ValueError( f"For multiclass objective '{self._objective}', 'num_classes' must be >= 2. " f"Current num_classes: {self.num_classes}" ) # Validate early stopping configuration if self.early_stopping_rounds is not None and not self._eval_metric: raise ValueError( "'early_stopping_rounds' requires 'eval_metric' to be set." ) # Validate GPU usage if self.tree_method == "gpu_hist" and self.device == -1: print( f"Warning: tree_method is '{self.tree_method}' but device is CPU (-1). " f"Ensure SageMaker instance is GPU for gpu_hist." ) return self
[docs] def get_public_init_fields(self) -> Dict[str, Any]: """ Override get_public_init_fields to include XGBoost-specific fields. Gets a dictionary of public fields suitable for initializing a child config. """ # Get fields from parent class base_fields = super().get_public_init_fields() # Add derived fields that should be exposed derived_fields = {"objective": self.objective, "eval_metric": self.eval_metric} # Combine (derived fields take precedence if overlap) return {**base_fields, **derived_fields}