Skip to content
Back to skills

Skill Ctc Decoder

ASecurity

Write greedy and beam-search CTC decoders from scratch, including length normalisation. Use when you need help with skill ctc decoder.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 8, 2026
developmentpythonc++git

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill skill-ctc-decoder --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Skill Ctc Decoder?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Skill Ctc Decoder
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-skill-ctc-decoder/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-skill-ctc-decoder)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: skill-ctc-decoder
description: Write greedy and beam-search CTC decoders from scratch, including length normalisation. Use when you need help with skill ctc decoder.
license: CC-BY-NC-SA-4.0
phase: 4
lesson: 19
metadata:
  version: 1.0.0
  tags: [ocr, ctc, decoding, sequence-models]
---

# CTC Decoder

Produce two decoding routines for CTC outputs: greedy (fast) and beam (better on noisy inputs).

## When to use

- Running OCR inference on custom CRNN outputs.
- Benchmarking a pretrained OCR model against different decoders.
- Implementing a simple beam search without pulling in ctcdecode.

## Inputs

- `log_probs`: (T, N, C) log-softmax over vocab (index 0 = blank by convention).
- `vocab`: list of C characters.
- `beam_width` (beam only): typically 5-10.

## Greedy decoder

```python
def greedy_ctc_decode(log_probs, vocab, blank=0):
    preds = log_probs.argmax(dim=-1).transpose(0, 1).cpu().tolist()
    out = []
    for seq in preds:
        decoded = []
        prev = None
        for idx in seq:
            if idx != prev and idx != blank:
                decoded.append(vocab[idx])
            prev = idx
        out.append("".join(decoded))
    return out
```

## Beam search decoder

```python
import heapq
import math

def beam_ctc_decode(log_probs, vocab, beam_width=5, blank=0):
    T, N, C = log_probs.shape
    lp = log_probs.cpu()
    results = []
    for n in range(N):
        beams = {("",): (0.0, -math.inf)}  # (prefix_tuple) -> (p_blank, p_nonblank)
        for t in range(T):
            logits_t = lp[t, n]
            new_beams = {}
            for prefix, (p_b, p_nb) in beams.items():
                for c in range(C):
                    p = logits_t[c].item()
                    if c == blank:
                        nb = p_b + p
                        nnb = p_nb + p
                        upd = new_beams.get(prefix, (-math.inf, -math.inf))
                        new_beams[prefix] = (
                            _logsumexp(upd[0], _logsumexp(nb, nnb)),
                            upd[1],
                        )
                    else:
                        last = prefix[-1] if prefix else ""
                        char = vocab[c]
                        if char == last:
                            # Case 1: stay on same prefix (collapse from p_nb)
                            upd = new_beams.get(prefix, (-math.inf, -math.inf))
                            new_beams[prefix] = (upd[0], _logsumexp(upd[1], p_nb + p))
                            # Case 2: extend prefix via blank-separated repeat ("a_a" -> "aa")
                            new_prefix = prefix + (char,)
                            upd = new_beams.get(new_prefix, (-math.inf, -math.inf))
                            new_beams[new_prefix] = (upd[0], _logsumexp(upd[1], p_b + p))
                        else:
                            new_prefix = prefix + (char,)
                            upd = new_beams.get(new_prefix, (-math.inf, -math.inf))
                            nb = _logsumexp(p_b, p_nb) + p
                            new_beams[new_prefix] = (upd[0], _logsumexp(upd[1], nb))
            beams = dict(heapq.nlargest(
                beam_width,
                new_beams.items(),
                key=lambda kv: _logsumexp(kv[1][0], kv[1][1]),
            ))
        best = max(beams.items(), key=lambda kv: _logsumexp(kv[1][0], kv[1][1]))[0]
        results.append("".join(best))
    return results


def _logsumexp(a, b):
    if a == -math.inf: return b
    if b == -math.inf: return a
    m = max(a, b)
    return m + math.log(math.exp(a - m) + math.exp(b - m))
```

## Rules

- The blank index in CTC is 0 by convention in PyTorch's `nn.CTCLoss`.
- Beam search improves accuracy on low-confidence inputs; on clean inputs the improvement is <1% CER.
- Never prune the beam below 5; the accuracy-latency trade flattens below that.
- When running beam search inside a tight latency budget, drop to greedy; the quality hit is small on most production OCR data.
- For large vocabularies (CJK with 3000+ characters), switch to `ctcdecode` (C++) instead of the pure Python version above; the Python beam quickly becomes the bottleneck.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.