Skip to content

DeepGBoostMultiClassifier

deepgboost.DeepGBoostMultiClassifier

Bases: CategoricalEncoderMixin, BaseEstimator, ClassifierMixin

DeepGBoost multi-output classifier — sklearn-compatible interface.

Trains a single DGBFMultiOutputModel where each boosting tree learns residuals for all K classes simultaneously. This contrasts with DeepGBoostClassifier which trains K independent OvR binary models.

Parameters:

Name Type Description Default
n_trees int

Number of trees per boosting layer.

5
n_layers int

Number of boosting layers.

20
max_depth int or None

Maximum depth of each decision tree.

None
max_features (int, float, str or None)

Features to consider at each split. None uses all features. Set to "sqrt" for Random Forest-style feature subsampling.

None
min_weight_fraction_leaf float

Minimum fraction of the total (weighted) number of samples required to be at a leaf node. Prevents leaves with small accumulated Hessian mass, analogous to XGBoost's min_child_weight. The default 0.0 preserves the original behaviour exactly.

0.0
learning_rate float

Shrinkage factor.

0.1
subsample_min_frac float

Minimum subsample fraction at layer 0.

0.3
weight_solver str

How to combine trees in each layer. "nnls" finds optimal non-negative weights per class; "uniform" assigns equal weight.

"nnls"
hessian_reg float

L2 regularisation added to the Hessian denominator.

0.0
random_state int or None

Seed for reproducibility.

None
n_jobs int

Reserved for future use.

1
early_stopping_rounds int or None

Early stopping patience (requires eval_set in fit).

None
eval_metric str or None

Metric for early stopping monitoring.

None
Source code in src/deepgboost/deepgboost_multiclassifier.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
class DeepGBoostMultiClassifier(
    CategoricalEncoderMixin,
    BaseEstimator,
    ClassifierMixin,
):
    """
    DeepGBoost multi-output classifier — sklearn-compatible interface.

    Trains a single ``DGBFMultiOutputModel`` where each boosting tree learns
    residuals for all K classes simultaneously.  This contrasts with
    ``DeepGBoostClassifier`` which trains K independent OvR binary models.

    Parameters
    ----------
    n_trees : int, default=5
        Number of trees per boosting layer.
    n_layers : int, default=20
        Number of boosting layers.
    max_depth : int or None, default=None
        Maximum depth of each decision tree.
    max_features : int, float, str or None, default=None
        Features to consider at each split.  ``None`` uses all features.
        Set to ``"sqrt"`` for Random Forest-style feature subsampling.
    min_weight_fraction_leaf : float, default=0.0
        Minimum fraction of the total (weighted) number of samples required
        to be at a leaf node.  Prevents leaves with small accumulated
        Hessian mass, analogous to XGBoost's ``min_child_weight``.
        The default ``0.0`` preserves the original behaviour exactly.
    learning_rate : float, default=0.1
        Shrinkage factor.
    subsample_min_frac : float, default=0.3
        Minimum subsample fraction at layer 0.
    weight_solver : str, default="nnls"
        How to combine trees in each layer.  ``"nnls"`` finds optimal
        non-negative weights per class; ``"uniform"`` assigns equal weight.
    hessian_reg : float, default=0.0
        L2 regularisation added to the Hessian denominator.
    random_state : int or None, default=None
        Seed for reproducibility.
    n_jobs : int, default=1
        Reserved for future use.
    early_stopping_rounds : int or None, default=None
        Early stopping patience (requires ``eval_set`` in ``fit``).
    eval_metric : str or None, default=None
        Metric for early stopping monitoring.
    """

    def __init__(
        self,
        n_trees: int = 5,
        n_layers: int = 20,
        max_depth: int | None = None,
        max_features: int | float | str | None = None,
        min_weight_fraction_leaf: float = 0.0,
        learning_rate: float = 0.1,
        subsample_min_frac: float = 0.3,
        weight_solver: str = "nnls",
        hessian_reg: float = 0.0,
        random_state: int | None = None,
        n_jobs: int = 1,
        early_stopping_rounds: int | None = None,
        eval_metric: str | None = None,
    ):
        self.n_trees = n_trees
        self.n_layers = n_layers
        self.max_depth = max_depth
        self.max_features = max_features
        self.min_weight_fraction_leaf = min_weight_fraction_leaf
        self.learning_rate = learning_rate
        self.subsample_min_frac = subsample_min_frac
        self.weight_solver = weight_solver
        self.hessian_reg = hessian_reg
        self.random_state = random_state
        self.n_jobs = n_jobs
        self.early_stopping_rounds = early_stopping_rounds
        self.eval_metric = eval_metric

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _encode_y(self, y_enc: np.ndarray) -> np.ndarray:
        """Encode integer class labels to one-hot (n_samples, K)."""
        y_onehot = label_binarize(y_enc, classes=np.arange(self.n_classes_))
        # label_binarize returns (n, 1) for binary — expand to (n, 2)
        if self.n_classes_ == 2:
            y_onehot = np.hstack([1 - y_onehot, y_onehot])
        return y_onehot.astype(np.float64)

    # ------------------------------------------------------------------
    # Sklearn interface
    # ------------------------------------------------------------------

    def fit(
        self,
        X: ArrayLike,
        y: ArrayLike,
        *,
        eval_set: list[tuple] | None = None,
        callbacks: Sequence[TrainingCallback] | None = None,
        sample_weight: ArrayLike | None = None,
    ) -> "DeepGBoostMultiClassifier":
        """
        Fit the classifier.

        Parameters
        ----------
        X : array-like of shape (n_samples, n_features)
        y : array-like of shape (n_samples,)
            Class labels (encoded internally).
        eval_set : list of (X_val, y_val) tuples, optional
        callbacks : list of TrainingCallback, optional
        sample_weight : ignored

        Returns
        -------
        self
        """
        X = self._fit_transform_X(X)
        y_raw = np.asarray(y)

        self.label_encoder_ = LabelEncoder()
        y_enc = self.label_encoder_.fit_transform(y_raw)
        self.classes_ = self.label_encoder_.classes_
        self.n_classes_ = len(self.classes_)
        self.n_features_in_ = X.shape[1]

        y_onehot = self._encode_y(y_enc)  # (n_samples, K)

        raw_evals = None
        if eval_set:
            raw_evals = []
            for i, (X_val, y_val) in enumerate(eval_set):
                X_val_enc = self._transform_X(X_val)
                y_val_enc = self.label_encoder_.transform(np.asarray(y_val))
                y_val_onehot = self._encode_y(y_val_enc)
                raw_evals.append((X_val_enc, y_val_onehot, f"eval_{i}"))

        all_callbacks = list(callbacks or [])
        if self.early_stopping_rounds is not None and raw_evals:
            from .callbacks import EarlyStoppingCallback

            all_callbacks.append(
                EarlyStoppingCallback(patience=self.early_stopping_rounds),
            )

        self.model_ = DGBFMultiOutputModel(
            n_trees=self.n_trees,
            n_layers=self.n_layers,
            max_depth=self.max_depth,
            max_features=self.max_features,
            min_weight_fraction_leaf=self.min_weight_fraction_leaf,
            learning_rate=self.learning_rate,
            subsample_min_frac=self.subsample_min_frac,
            weight_solver=self.weight_solver,
            hessian_reg=self.hessian_reg,
            random_state=self.random_state,
        )
        self.model_.fit(X, y_onehot, callbacks=all_callbacks, evals=raw_evals)

        return self

    def predict_proba(self, X: ArrayLike) -> np.ndarray:
        """
        Probability estimates.

        Returns
        -------
        np.ndarray of shape (n_samples, n_classes)
            Rows sum to 1.
        """
        check_is_fitted(self, "classes_")
        X = self._transform_X(X)
        F = self.model_.predict_raw(X)  # (n_samples, K)
        return softmax(F, axis=1)

    def predict(self, X: ArrayLike) -> np.ndarray:
        """
        Predict class labels.

        Returns
        -------
        np.ndarray of shape (n_samples,) with original class labels.
        """
        proba = self.predict_proba(X)
        indices = np.argmax(proba, axis=1)
        return self.label_encoder_.inverse_transform(indices)

    def score(
        self, X: ArrayLike, y: ArrayLike, sample_weight: ArrayLike | None = None,
    ) -> float:
        """Return accuracy."""
        return float(np.mean(self.predict(X) == np.asarray(y)))

    @property
    def feature_importances_(self) -> np.ndarray:
        """Impurity-based feature importances averaged across all trees and layers."""
        check_is_fitted(self, "model_")
        return self.model_.feature_importances_

feature_importances_ property

Impurity-based feature importances averaged across all trees and layers.

fit(X, y, *, eval_set=None, callbacks=None, sample_weight=None)

Fit the classifier.

Parameters:

Name Type Description Default
X array-like of shape (n_samples, n_features)
required
y array-like of shape (n_samples,)

Class labels (encoded internally).

required
eval_set list of (X_val, y_val) tuples
None
callbacks list of TrainingCallback
None
sample_weight ignored
None

Returns:

Type Description
self
Source code in src/deepgboost/deepgboost_multiclassifier.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def fit(
    self,
    X: ArrayLike,
    y: ArrayLike,
    *,
    eval_set: list[tuple] | None = None,
    callbacks: Sequence[TrainingCallback] | None = None,
    sample_weight: ArrayLike | None = None,
) -> "DeepGBoostMultiClassifier":
    """
    Fit the classifier.

    Parameters
    ----------
    X : array-like of shape (n_samples, n_features)
    y : array-like of shape (n_samples,)
        Class labels (encoded internally).
    eval_set : list of (X_val, y_val) tuples, optional
    callbacks : list of TrainingCallback, optional
    sample_weight : ignored

    Returns
    -------
    self
    """
    X = self._fit_transform_X(X)
    y_raw = np.asarray(y)

    self.label_encoder_ = LabelEncoder()
    y_enc = self.label_encoder_.fit_transform(y_raw)
    self.classes_ = self.label_encoder_.classes_
    self.n_classes_ = len(self.classes_)
    self.n_features_in_ = X.shape[1]

    y_onehot = self._encode_y(y_enc)  # (n_samples, K)

    raw_evals = None
    if eval_set:
        raw_evals = []
        for i, (X_val, y_val) in enumerate(eval_set):
            X_val_enc = self._transform_X(X_val)
            y_val_enc = self.label_encoder_.transform(np.asarray(y_val))
            y_val_onehot = self._encode_y(y_val_enc)
            raw_evals.append((X_val_enc, y_val_onehot, f"eval_{i}"))

    all_callbacks = list(callbacks or [])
    if self.early_stopping_rounds is not None and raw_evals:
        from .callbacks import EarlyStoppingCallback

        all_callbacks.append(
            EarlyStoppingCallback(patience=self.early_stopping_rounds),
        )

    self.model_ = DGBFMultiOutputModel(
        n_trees=self.n_trees,
        n_layers=self.n_layers,
        max_depth=self.max_depth,
        max_features=self.max_features,
        min_weight_fraction_leaf=self.min_weight_fraction_leaf,
        learning_rate=self.learning_rate,
        subsample_min_frac=self.subsample_min_frac,
        weight_solver=self.weight_solver,
        hessian_reg=self.hessian_reg,
        random_state=self.random_state,
    )
    self.model_.fit(X, y_onehot, callbacks=all_callbacks, evals=raw_evals)

    return self

predict(X)

Predict class labels.

Returns:

Type Description
np.ndarray of shape (n_samples,) with original class labels.
Source code in src/deepgboost/deepgboost_multiclassifier.py
206
207
208
209
210
211
212
213
214
215
216
def predict(self, X: ArrayLike) -> np.ndarray:
    """
    Predict class labels.

    Returns
    -------
    np.ndarray of shape (n_samples,) with original class labels.
    """
    proba = self.predict_proba(X)
    indices = np.argmax(proba, axis=1)
    return self.label_encoder_.inverse_transform(indices)

predict_proba(X)

Probability estimates.

Returns:

Type Description
np.ndarray of shape (n_samples, n_classes)

Rows sum to 1.

Source code in src/deepgboost/deepgboost_multiclassifier.py
192
193
194
195
196
197
198
199
200
201
202
203
204
def predict_proba(self, X: ArrayLike) -> np.ndarray:
    """
    Probability estimates.

    Returns
    -------
    np.ndarray of shape (n_samples, n_classes)
        Rows sum to 1.
    """
    check_is_fitted(self, "classes_")
    X = self._transform_X(X)
    F = self.model_.predict_raw(X)  # (n_samples, K)
    return softmax(F, axis=1)

score(X, y, sample_weight=None)

Return accuracy.

Source code in src/deepgboost/deepgboost_multiclassifier.py
218
219
220
221
222
def score(
    self, X: ArrayLike, y: ArrayLike, sample_weight: ArrayLike | None = None,
) -> float:
    """Return accuracy."""
    return float(np.mean(self.predict(X) == np.asarray(y)))