{"record":{"id":"3e956d56b8e23ea2","repo":"microsoft/qlib","slug":"lightgbm-doesn-t-support-multi-label-training","errorCode":null,"errorMessage":"LightGBM doesn't support multi-label training","messagePattern":"LightGBM doesn't support multi-label training","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/model/double_ensemble.py","lineNumber":134,"sourceCode":"            dtrain,\n            num_boost_round=self.epochs,\n            valid_sets=[dtrain, dvalid],\n            valid_names=[\"train\", \"valid\"],\n            callbacks=callbacks,\n        )\n        evals_result[\"train\"] = list(evals_result[\"train\"].values())[0]\n        evals_result[\"valid\"] = list(evals_result[\"valid\"].values())[0]\n        return model\n\n    def _prepare_data_gbm(self, df_train, df_valid, weights, features):\n        x_train, y_train = df_train[\"feature\"].loc[:, features], df_train[\"label\"]\n        x_valid, y_valid = df_valid[\"feature\"].loc[:, features], df_valid[\"label\"]\n\n        # Lightgbm need 1D array as its label\n        if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:\n            y_train, y_valid = np.squeeze(y_train.values), np.squeeze(y_valid.values)\n        else:\n            raise ValueError(\"LightGBM doesn't support multi-label training\")\n\n        dtrain = lgb.Dataset(x_train, label=y_train, weight=weights)\n        dvalid = lgb.Dataset(x_valid, label=y_valid)\n        return dtrain, dvalid\n\n    def sample_reweight(self, loss_curve, loss_values, k_th):\n        \"\"\"\n        the SR module of Double Ensemble\n        :param loss_curve: the shape is NxT\n        the loss curve for the previous sub-model, where the element (i, t) if the error on the i-th sample\n        after the t-th iteration in the training of the previous sub-model.\n        :param loss_values: the shape is N\n        the loss of the current ensemble on the i-th sample.\n        :param k_th: the index of the current sub-model, starting from 1\n        :return: weights\n        the weights for all the samples.\n        \"\"\"\n        # normalize loss_curve and loss_values with ranking","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/model/double_ensemble.py#L116-L152","documentation":"Thrown by DEnsembleModel._prepare_data_gbm when the label block is not exactly one column. The underlying base model is LightGBM, whose Dataset label must be 1D, so qlib only squeezes labels of shape (N, 1) and rejects multi-column labels.","triggerScenarios":"Running Double Ensemble (workflow 'DENsemble' / model.cls: DEnsembleModel) with a handler whose label list has more than one expression.","commonSituations":"Reusing a multi-label handler config from another model; adding auxiliary label columns for analysis.","solutions":["Set the handler label to a single expression, e.g. label: [\"Ref($close, -2)/Ref($close, -1) - 1\"]","Use a multi-output-capable model if you truly need several targets"],"exampleFix":"# before (handler config)\nlabel: [\"Ref($close, -2)/Ref($close, -1) - 1\", \"Ref($high, -1)/$close - 1\"]\n\n# after\nlabel: [\"Ref($close, -2)/Ref($close, -1) - 1\"]","handlingStrategy":"validation","validationCode":"y = dataset.prepare(\"train\", col_set=\"label\", data_key=\"learn\")\nassert y.values.shape[1] == 1, \"Double Ensemble (LightGBM base) supports only single-column labels\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use single-label handler configs for all LightGBM-based models","Assert label width once at workflow startup when configs are shared"],"tags":["double-ensemble","lightgbm","multi-label","qlib"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}