Source code for sgtlearn.ensemble.RandomSGForestClassifier

"""Bootstrap ensemble of :class:`sgtlearn.base.SGTClassifier` estimators."""

from __future__ import annotations

from typing import Any, Mapping, Optional, Union

import numpy as np
from sklearn.base import ClassifierMixin
from sklearn.preprocessing import LabelEncoder
from sklearn.utils.validation import check_X_y

from sgtlearn._weights import effective_sample_weight_classification
from sgtlearn.base import SGTClassifier
from sgtlearn.ensemble._random_sgforest import RandomSGForest


[docs] class RandomSGForestClassifier(ClassifierMixin, RandomSGForest): """Random forest of Shape Generalized Trees for classification. Bootstrap ensemble of :class:`sgtlearn.SGTClassifier` base estimators. Prediction semantics match :class:`sklearn.ensemble.RandomForestClassifier`: class probabilities are the mean of per-tree :meth:`predict_proba`, and :meth:`predict` returns the argmax class label. Parameters ---------- n_estimators : int, default=100 Number of trees in the forest. criterion : {"gini", "entropy"}, default="gini" Impurity criterion forwarded to each base tree's outer splits. num_partitions : int, default=2 Arity of the shape function at each outer split. See :class:`sgtlearn.SGTClassifier`. max_depth, max_leaf_nodes, min_samples_leaf, min_impurity_decrease : \ see :class:`sgtlearn.SGTClassifier` Outer-tree stopping criteria forwarded to each base estimator. inner_max_depth, inner_max_leaf_nodes, inner_min_samples_leaf, \ inner_min_impurity_decrease : see :class:`sgtlearn.SGTClassifier` Inner-tree (shape function) controls forwarded to each base estimator. coordinate_descent_max_iters, coordinate_descent_patience, \ coordinate_descent_smart_init : see :class:`sgtlearn.SGTClassifier` Coordinate-descent controls forwarded to each base estimator. max_features : int, float, {"sqrt", "log2"} or None, default="sqrt" Per-split feature subsampling for each base tree. Defaults to ``"sqrt"`` to follow ``RandomForestClassifier`` convention. See :class:`sgtlearn.SGTClassifier` for the full semantics. bootstrap : bool, default=True If ``True``, each tree is fit on a bootstrap resample (with replacement) of the training set. If ``False``, every tree is fit on the full training set — diversity then comes only from ``random_state`` and ``max_features``. max_samples : int or float, optional Size of each bootstrap sample. ``int`` gives an absolute count; ``float`` in ``(0, 1]`` gives a fraction of ``n_samples``. ``None`` (default) uses ``n_samples``. Only valid when ``bootstrap=True``. random_state : int, RandomState, optional Controls bootstrap resampling and the per-tree seeds. class_weight : dict, optional Per-class weights multiplied into ``sample_weight`` before training. Keys are class labels as in ``y``. n_jobs : int, optional Number of joblib workers used to fit trees. ``None`` means one job (sequential); ``-1`` uses all processors. Joblib's threading backend is used (the per-tree work is dominated by the native C++ trainer, same rationale as ``RandomForestClassifier``). verbose : int, default=0 Verbosity of the joblib ``Parallel`` driver. Attributes ---------- estimators_ : list of SGTClassifier The collection of fitted base estimators. classes_ : ndarray of shape (n_classes,) Class labels in original training label space. n_classes_ : int Number of classes seen during :meth:`fit`. n_features_in_ : int Number of features seen during :meth:`fit`. mean_feature_importances_ : ndarray of shape (n_logical_features,) Mean of per-tree :attr:`~sgtlearn.SGTClassifier.feature_importances_`, aligned with :attr:`processed_features_`. std_feature_importance_ : ndarray of shape (n_logical_features,) Population standard deviation of per-tree importances across the forest (same alignment as :attr:`mean_feature_importances_`). processed_features_ : ProcessedFeatures Logical features resolved once and shared by every base tree. See Also -------- sgtlearn.SGTClassifier : Single-tree base estimator. sgtlearn.ensemble.RandomSGForestRegressor : Regression counterpart. sklearn.ensemble.RandomForestClassifier : Standard CART forest with the same prediction semantics. Examples -------- >>> from sklearn.datasets import make_classification >>> from sgtlearn.ensemble import RandomSGForestClassifier >>> X, y = make_classification(n_samples=500, random_state=0) >>> clf = RandomSGForestClassifier(n_estimators=20, random_state=0).fit(X, y) >>> clf.predict(X[:5]).shape (5,) """ _estimator_name = "RandomSGForestClassifier" def __init__( self, n_estimators: int = 100, *, criterion: str = "gini", num_partitions: int = 2, max_depth: Optional[int] = None, max_leaf_nodes: Optional[int] = None, min_samples_leaf: int = 1, min_impurity_decrease: float = 0.0, inner_max_depth: int = 3, inner_max_leaf_nodes: int = 8, inner_min_samples_leaf: int = 1, inner_min_impurity_decrease: float = 0.0, coordinate_descent_max_iters: int = 20, coordinate_descent_patience: int = 5, coordinate_descent_smart_init: bool = True, max_features: Optional[Union[int, float, str]] = "sqrt", bootstrap: bool = True, max_samples: Optional[Union[int, float]] = None, random_state: Optional[Union[int, np.random.RandomState]] = None, class_weight: Optional[Mapping[Any, float]] = None, tao_n_runs: int = 10, tao_lambda: float = 0.0, n_jobs: Optional[int] = None, verbose: int = 0, ) -> None: self.class_weight = class_weight super().__init__( n_estimators=n_estimators, criterion=criterion, num_partitions=num_partitions, max_depth=max_depth, max_leaf_nodes=max_leaf_nodes, min_samples_leaf=min_samples_leaf, min_impurity_decrease=min_impurity_decrease, inner_max_depth=inner_max_depth, inner_max_leaf_nodes=inner_max_leaf_nodes, inner_min_samples_leaf=inner_min_samples_leaf, inner_min_impurity_decrease=inner_min_impurity_decrease, coordinate_descent_max_iters=coordinate_descent_max_iters, coordinate_descent_patience=coordinate_descent_patience, coordinate_descent_smart_init=coordinate_descent_smart_init, max_features=max_features, bootstrap=bootstrap, max_samples=max_samples, random_state=random_state, tao_n_runs=tao_n_runs, tao_lambda=tao_lambda, n_jobs=n_jobs, verbose=verbose, ) def _check_X_y(self, X: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]: X, y = check_X_y( X, y, accept_sparse=False, dtype=np.float64, ensure_all_finite="allow-nan", ) le = LabelEncoder() y_enc = le.fit_transform(y) self._label_encoder_ = le self.classes_ = le.classes_ self.n_classes_ = int(len(self.classes_)) if self.n_classes_ < 2: raise ValueError("RandomSGForestClassifier requires at least two classes.") return X, y_enc def _prepare_sample_weight( self, y: np.ndarray, sample_weight: Optional[np.ndarray], n_samples: int, ) -> Optional[np.ndarray]: if self.class_weight is None: return super()._prepare_sample_weight(y, sample_weight, n_samples) return effective_sample_weight_classification( sample_weight, y, self.class_weight, np.asarray(self.classes_), ) def _make_tree(self, tree_seed: int, tree_kw: dict[str, Any]) -> SGTClassifier: tree = SGTClassifier(**tree_kw, random_state=tree_seed) tree.classes_ = np.asarray(self.classes_) tree.n_classes_ = self.n_classes_ return tree def predict_proba(self, X: np.ndarray) -> np.ndarray: X32 = self._check_predict_X(X) acc = np.zeros((X.shape[0], self.n_classes_), dtype=np.float64) for est in self.estimators_: acc += est.predict_proba(X32) acc /= float(len(self.estimators_)) return acc def predict(self, X: np.ndarray) -> np.ndarray: proba = self.predict_proba(X) idx = np.argmax(proba, axis=1) return self.classes_.take(idx, axis=0)
__all__ = ["RandomSGForestClassifier"]