{"record":{"id":"6e8dc8094d0f1d0c","repo":"microsoft/qlib","slug":"unknown-loss-s-6e8dc8","errorCode":null,"errorMessage":"unknown loss `%s`","messagePattern":"unknown loss `(.+?)`","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"qlib/contrib/model/pytorch_gats.py","lineNumber":154,"sourceCode":"\n        self.fitted = False\n        self.GAT_model.to(self.device)\n\n    @property\n    def use_gpu(self):\n        return self.device != torch.device(\"cpu\")\n\n    def mse(self, pred, label):\n        loss = (pred - label) ** 2\n        return torch.mean(loss)\n\n    def loss_fn(self, pred, label):\n        mask = ~torch.isnan(label)\n\n        if self.loss == \"mse\":\n            return self.mse(pred[mask], label[mask])\n\n        raise ValueError(\"unknown loss `%s`\" % self.loss)\n\n    def metric_fn(self, pred, label):\n        mask = torch.isfinite(label)\n\n        if self.metric in (\"\", \"loss\"):\n            return -self.loss_fn(pred[mask], label[mask])\n\n        raise ValueError(\"unknown metric `%s`\" % self.metric)\n\n    def get_daily_inter(self, df, shuffle=False):\n        # organize the train data into daily batches\n        daily_count = df.groupby(level=0, group_keys=False).size().values\n        daily_index = np.roll(np.cumsum(daily_count), 1)\n        daily_index[0] = 0\n        if shuffle:\n            # shuffle data\n            daily_shuffle = list(zip(daily_index, daily_count))\n            np.random.shuffle(daily_shuffle)","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/contrib/model/pytorch_gats.py#L136-L172","documentation":"GATsModel.loss_fn supports exactly one loss: self.loss == 'mse'. Any other value skips the branch and raises ValueError from loss_fn, which is called by both the training loop and metric_fn, so the error surfaces on the first training batch of fit().","triggerScenarios":"Constructing GATsModel(loss='mae') or loss='' and calling fit(); feeding a loss name valid in another qlib model (ALSTM, TRA) into GATs.","commonSituations":"Copying a hyperparameter YAML between qlib contrib models where 'mse' is the only supported value here; leaving loss unset in a config template whose default differs from the class default.","solutions":["Set loss='mse' (the default) in the GATsModel constructor or workflow config.","Audit the loss hyperparameter for typos or values ported from other model configs.","Subclass GATsModel and extend loss_fn with a new elif branch for a custom loss."],"exampleFix":"# before\nmodel = GATsModel(loss='huber')\n\n# after\nmodel = GATsModel(loss='mse')","handlingStrategy":"validation","validationCode":"assert model.loss == 'mse', f\"GATsModel only supports loss='mse', got {model.loss!r}\"","typeGuard":"def is_supported_gats_loss(loss: str) -> bool:\n    return loss == 'mse'","tryCatchPattern":"try:\n    model.fit(dataset)\nexcept ValueError as e:\n    if 'unknown loss' in str(e):\n        model.loss = 'mse'\n        model.fit(dataset)\n    else:\n        raise","preventionTips":["Pin loss='mse' for GATs in config templates.","Do not port loss names between qlib contrib models.","Add a pre-fit assertion in experiment scripts that enumerate loss variants."],"tags":["pytorch","qlib","loss-function","config-validation","gats"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}