{"record":{"id":"17e5a0b49d315790","repo":"hankcs/HanLP","slug":"emissions-must-have-dimension-of-3-got-emissions","errorCode":null,"errorMessage":"emissions must have dimension of 3, got {emissions.dim()}","messagePattern":"emissions must have dimension of 3, got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"hanlp/layers/crf/crf.py","lineNumber":166,"sourceCode":"            List of list containing the best tag sequence for each batch.\n        \"\"\"\n        self._validate(emissions, mask=mask)\n        if mask is None:\n            mask = emissions.new_ones(emissions.shape[:2], dtype=torch.uint8)\n\n        if self.batch_first:\n            emissions = emissions.transpose(0, 1)\n            mask = mask.transpose(0, 1)\n\n        return self._viterbi_decode(emissions, mask)\n\n    def _validate(\n            self,\n            emissions: torch.Tensor,\n            tags: Optional[torch.LongTensor] = None,\n            mask: Optional[torch.ByteTensor] = None) -> None:\n        if emissions.dim() != 3:\n            raise ValueError(f'emissions must have dimension of 3, got {emissions.dim()}')\n        if emissions.size(2) != self.num_tags:\n            raise ValueError(\n                f'expected last dimension of emissions is {self.num_tags}, '\n                f'got {emissions.size(2)}')\n\n        if tags is not None:\n            if emissions.shape[:2] != tags.shape:\n                raise ValueError(\n                    'the first two dimensions of emissions and tags must match, '\n                    f'got {tuple(emissions.shape[:2])} and {tuple(tags.shape)}')\n\n        if mask is not None:\n            if emissions.shape[:2] != mask.shape:\n                raise ValueError(\n                    'the first two dimensions of emissions and mask must match, '\n                    f'got {tuple(emissions.shape[:2])} and {tuple(mask.shape)}')\n            no_empty_seq = not self.batch_first and mask[0].all()\n            no_empty_seq_bf = self.batch_first and mask[:, 0].all()","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/hankcs/HanLP/blob/ddb1299bddff079e447af52ec12549c50636bfa8/hanlp/layers/crf/crf.py#L148-L184","documentation":"CRF._validate checks that emissions is a rank-3 tensor of shape (batch, seq_len, num_tags) (or (seq_len, batch, num_tags) if batch_first=False). A rank-2 or rank-4 emissions tensor (e.g. a flat (batch*seq, tags) score matrix) fails this check in both forward (loss) and decode (prediction).","triggerScenarios":"Passing reshaped logits like emissions.view(-1, num_tags) into crf(...); feeding a 2D per-token score matrix from a softmax tagger; forgetting the batch dimension when scoring a single sentence.","commonSituations":"Adapting softmax-tagger code (which flattens to 2D for CrossEntropyLoss) to a CRF; single-sample inference where the batch dim was squeezed.","solutions":["Keep emissions 3D: use (batch, seq_len, num_tags); avoid .view(-1, num_tags) before the CRF","For a single sentence, keep the batch dim: emissions.unsqueeze(0)","Check batch_first consistency with your tensor layout (transpose if needed)"],"exampleFix":"# before\nloss = crf(logits.view(-1, n_tags), tags.view(-1), mask.view(-1))\n# after\nloss = crf(logits, tags, mask)  # logits: (batch, seq_len, n_tags)","handlingStrategy":"type-guard","validationCode":"assert emissions.dim() == 3, f'emissions must be (batch, seq, tags), got {tuple(emissions.shape)}'","typeGuard":"def is_valid_crf_emissions(t: torch.Tensor) -> bool:\n    return t.dim() == 3 and t.dtype.is_floating_point","tryCatchPattern":null,"preventionTips":["Never flatten to 2D before CRF; keep (batch, seq_len, num_tags)","Add shape asserts at model boundaries"],"tags":["python","pytorch","crf","tensor-shape"],"backgroundTag":"invalid-tensor-shape","analyzedSha":"ddb1299bddff079e447af52ec12549c50636bfa8","analyzedAt":"2026-08-27T03:36:54.287Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}