microsoft/qlib · error · ValueError

model is not fitted yet!

Error message

model is not fitted yet!

What it means

Thrown by DEnsembleModel.predict when self.ensemble is None, intended to guard prediction before fit. Note a caveat: __init__ sets self.ensemble = [], so the attribute is normally an empty list rather than None — with a stock instance, predict before fit returns a zero vector divided by sum(sub_weights) instead of raising. The error effectively fires only if ensemble is explicitly set to None.

Solutions

  1. Always call fit(dataset) and let it complete before predict(dataset)
  2. Wrap fit in try/except and do not proceed to predict on failure
  3. Defensively check isinstance(model.ensemble, list) and len(model.ensemble) == model.num_models before predicting, since the built-in None check is unreliable

Example fix

# before
model = DEnsembleModel(num_models=5)
pred = model.predict(dataset)  # silent zeros, not the expected guard

# after
model.fit(dataset)
assert len(model.ensemble) == model.num_models
pred = model.predict(dataset)
Defensive patterns

Strategy: validation

Validate before calling

# built-in None-check is unreliable (ensemble is [] not None); validate explicitly
assert isinstance(model.ensemble, list) and len(model.ensemble) == model.num_models, "fit() must complete before predict()"

Prevention

When it happens

Trigger: Calling predict on a model whose fit failed before the ensemble loop or that was manually mutated (ensemble set to None); in practice the guard is largely dead code because fit/__init__ initialize an empty list.

Common situations: Exceptions during fit partially constructing the object; users assuming the guard protects them from untrained prediction when it actually does not reliably do so.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/60110d42a621b35c. Report an issue: GitHub.

Appendix: source

Thrown at qlib/contrib/model/double_ensemble.py:249

            # Lightgbm need 1D array as its label
            if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:
                y_train = np.squeeze(y_train.values)
            else:
                raise ValueError("LightGBM doesn't support multi-label training")

            N = x_train.shape[0]
            loss_curve = pd.DataFrame(np.zeros((N, num_trees)))
            pred_tree = np.zeros(N, dtype=float)
            for i_tree in range(num_trees):
                pred_tree += model.predict(x_train.values, start_iteration=i_tree, num_iteration=1)
                loss_curve.iloc[:, i_tree] = self.get_loss(y_train, pred_tree)
        else:
            raise ValueError("not implemented yet")
        return loss_curve

    def predict(self, dataset: DatasetH, segment: Union[Text, slice] = "test"):
        if self.ensemble is None:
            raise ValueError("model is not fitted yet!")
        x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
        pred = pd.Series(np.zeros(x_test.shape[0]), index=x_test.index)
        for i_sub, submodel in enumerate(self.ensemble):
            feat_sub = self.sub_features[i_sub]
            pred += (
                pd.Series(submodel.predict(x_test.loc[:, feat_sub].values), index=x_test.index)
                * self.sub_weights[i_sub]
            )
        pred = pred / np.sum(self.sub_weights)
        return pred

    def predict_sub(self, submodel, df_data, features):
        x_data = df_data["feature"].loc[:, features]
        pred_sub = pd.Series(submodel.predict(x_data.values), index=x_data.index)
        return pred_sub

    def get_feature_importance(self, *args, **kwargs) -> pd.Series:
        """get feature importance

View on GitHub (pinned to 79633dd950)