rohitg00/ai-engineering-from-scratch · error · ValueError

No trainable parameters. Did you freeze the head as well?

Error message

No trainable parameters. Did you freeze the head as well?

What it means

train_classifier is a sentinel guard: it filters model.parameters() for requires_grad and finds none, meaning every parameter including the classification head was frozen, so gradient descent has nothing to update. The message itself suggests the likely cause (freezing the head along with the backbone).

Solutions

  1. Re-enable gradients on the head parameters with requires_grad_(True) before training
  2. Freeze only the backbone, not the final linear layer
  3. When using BN/dropout freezing helpers, exclude modules with trainable affine parameters
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at phases/19-capstone-projects/38-classifier-finetuning/code/main.py:396 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26). Data as JSON: /api/errors/caa58e0b00067ad9. Report an issue: GitHub.

Appendix: source

Thrown at phases/19-capstone-projects/38-classifier-finetuning/code/main.py:396

@dataclass
class TrainReport:
    losses: List[float]
    final_loss: float
    trainable: int


def train_classifier(
    model: Classifier,
    train_loader: DataLoader,
    epochs: int,
    lr: float,
    seed: int,
) -> TrainReport:
    torch.manual_seed(seed)
    params = [p for p in model.parameters() if p.requires_grad]
    if not params:
        raise ValueError("No trainable parameters. Did you freeze the head as well?")
    opt = torch.optim.Adam(params, lr=lr)
    losses: List[float] = []
    model.train()
    for _ in range(epochs):
        epoch_loss = 0.0
        n_batches = 0
        for ids, mask, y in train_loader:
            logits = model(ids, mask)
            loss = F.cross_entropy(logits, y)
            opt.zero_grad()
            loss.backward()
            opt.step()
            epoch_loss += float(loss.item())
            n_batches += 1
        losses.append(epoch_loss / max(n_batches, 1))
    return TrainReport(losses=losses, final_loss=losses[-1], trainable=trainable_params(model))

View on GitHub (pinned to 39ea8a1c6d)