hankcs/HanLP · error · ValueError

emissions must have dimension of 3, got {emissions.dim()}

Error message

emissions must have dimension of 3, got {emissions.dim()}

What it means

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).

Source

Thrown at hanlp/layers/crf/crf.py:166

            List of list containing the best tag sequence for each batch.
        """
        self._validate(emissions, mask=mask)
        if mask is None:
            mask = emissions.new_ones(emissions.shape[:2], dtype=torch.uint8)

        if self.batch_first:
            emissions = emissions.transpose(0, 1)
            mask = mask.transpose(0, 1)

        return self._viterbi_decode(emissions, mask)

    def _validate(
            self,
            emissions: torch.Tensor,
            tags: Optional[torch.LongTensor] = None,
            mask: Optional[torch.ByteTensor] = None) -> None:
        if emissions.dim() != 3:
            raise ValueError(f'emissions must have dimension of 3, got {emissions.dim()}')
        if emissions.size(2) != self.num_tags:
            raise ValueError(
                f'expected last dimension of emissions is {self.num_tags}, '
                f'got {emissions.size(2)}')

        if tags is not None:
            if emissions.shape[:2] != tags.shape:
                raise ValueError(
                    'the first two dimensions of emissions and tags must match, '
                    f'got {tuple(emissions.shape[:2])} and {tuple(tags.shape)}')

        if mask is not None:
            if emissions.shape[:2] != mask.shape:
                raise ValueError(
                    'the first two dimensions of emissions and mask must match, '
                    f'got {tuple(emissions.shape[:2])} and {tuple(mask.shape)}')
            no_empty_seq = not self.batch_first and mask[0].all()
            no_empty_seq_bf = self.batch_first and mask[:, 0].all()

View on GitHub (pinned to ddb1299bdd)

Solutions

  1. Keep emissions 3D: use (batch, seq_len, num_tags); avoid .view(-1, num_tags) before the CRF
  2. For a single sentence, keep the batch dim: emissions.unsqueeze(0)
  3. Check batch_first consistency with your tensor layout (transpose if needed)

Example fix

# before
loss = crf(logits.view(-1, n_tags), tags.view(-1), mask.view(-1))
# after
loss = crf(logits, tags, mask)  # logits: (batch, seq_len, n_tags)
Defensive patterns

Strategy: type-guard

Validate before calling

assert emissions.dim() == 3, f'emissions must be (batch, seq, tags), got {tuple(emissions.shape)}'

Type guard

def is_valid_crf_emissions(t: torch.Tensor) -> bool:
    return t.dim() == 3 and t.dtype.is_floating_point

Prevention

When it happens

Trigger: 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.

Common situations: Adapting softmax-tagger code (which flattens to 2D for CrossEntropyLoss) to a CRF; single-sample inference where the batch dim was squeezed.

Related errors


AI-assisted analysis of hankcs/HanLP@ddb1299bdd (2026-08-27). Data as JSON: /api/errors/17e5a0b49d315790. Report an issue: GitHub.