{"record":{"id":"9f1fb2c0fe3e02af","repo":"microsoft/qlib","slug":"model-is-not-fitted-yet-9f1fb2","errorCode":null,"errorMessage":"model is not fitted yet!","messagePattern":"model is not fitted yet!","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/model/pytorch_gats.py","lineNumber":303,"sourceCode":"                stop_steps = 0\n                best_epoch = step\n                best_param = copy.deepcopy(self.GAT_model.state_dict())\n            else:\n                stop_steps += 1\n                if stop_steps >= self.early_stop:\n                    self.logger.info(\"early stop\")\n                    break\n\n        self.logger.info(\"best score: %.6lf @ %d\" % (best_score, best_epoch))\n        self.GAT_model.load_state_dict(best_param)\n        torch.save(best_param, save_path)\n\n        if self.use_gpu:\n            torch.cuda.empty_cache()\n\n    def predict(self, dataset: DatasetH, segment: Union[Text, slice] = \"test\"):\n        if not self.fitted:\n            raise ValueError(\"model is not fitted yet!\")\n\n        x_test = dataset.prepare(segment, col_set=\"feature\")\n        index = x_test.index\n        self.GAT_model.eval()\n        x_values = x_test.values\n        preds = []\n\n        # organize the data into daily batches\n        daily_index, daily_count = self.get_daily_inter(x_test, shuffle=False)\n\n        for idx, count in zip(daily_index, daily_count):\n            batch = slice(idx, idx + count)\n            x_batch = torch.from_numpy(x_values[batch]).float().to(self.device)\n\n            with torch.no_grad():\n                pred = self.GAT_model(x_batch).detach().cpu().numpy()\n\n            preds.append(pred)","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/model/pytorch_gats.py#L285-L321","documentation":"GATsModel.predict() checks self.fitted, which is set True only after fit() completes its early-stopping loop and restores the best parameters. Calling predict() beforehand raises ValueError with no data access attempted. The guard prevents scoring with an untrained attention network.","triggerScenarios":"model.predict(dataset) on a GATsModel that never ran fit(), or whose fit() aborted mid-way (empty data, bad loss/metric/optimizer, OOM) leaving fitted=False.","commonSituations":"Notebook runs where the training cell failed and downstream prediction cells still execute; checkpoint scripts that construct the model but forget to load weights; swallowing fit() exceptions with bare except and proceeding.","solutions":["Run fit(dataset) to completion before predict(dataset).","If fit() failed, resolve that failure first; fitted flips True only on a clean finish.","When restoring a saved model in a fresh process, load its state dict and set model.fitted = True before predict()."],"exampleFix":"# before\nmodel = GATsModel()\nmodel.predict(dataset)  # ValueError\n\n# after\nmodel = GATsModel()\nmodel.fit(dataset)\nmodel.predict(dataset)","handlingStrategy":"validation","validationCode":"if not getattr(model, 'fitted', False):\n    raise RuntimeError('GATsModel is not fitted; call fit() before predict()')","typeGuard":"def is_fitted(m) -> bool:\n    return bool(getattr(m, 'fitted', False))","tryCatchPattern":"try:\n    pred = model.predict(dataset)\nexcept ValueError as e:\n    if 'not fitted' in str(e):\n        model.fit(dataset)\n        pred = model.predict(dataset)\n    else:\n        raise","preventionTips":["Check model.fitted before predict in automated pipelines.","Abort downstream cells/tasks when fit() raises instead of continuing.","For saved-model serving, load state dict and set fitted=True explicitly."],"tags":["pytorch","qlib","lifecycle","state-error","gats"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}