PaddlePaddle/PaddleOCR · error · ValueError

Expected positive integer steps_per_epoch, but got {}

Error message

Expected positive integer steps_per_epoch, but got {}

What it means

Companion check to error 318: the scheduler needs steps_per_epoch (iterations per epoch) as a positive integer because total_steps = epochs * steps_per_epoch drives the LR curve. A zero, negative, non-int, or missing value raises this ValueError before any training step runs.

Source

Thrown at ppocr/optimizer/lr_scheduler.py:74

        self,
        max_lr,
        epochs=None,
        steps_per_epoch=None,
        pct_start=0.3,
        anneal_strategy="cos",
        div_factor=25.0,
        final_div_factor=1e4,
        three_phase=False,
        last_epoch=-1,
        verbose=False,
    ):
        # Validate total_steps
        if epochs <= 0 or not isinstance(epochs, int):
            raise ValueError(
                "Expected positive integer epochs, but got {}".format(epochs)
            )
        if steps_per_epoch <= 0 or not isinstance(steps_per_epoch, int):
            raise ValueError(
                "Expected positive integer steps_per_epoch, but got {}".format(
                    steps_per_epoch
                )
            )
        self.total_steps = epochs * steps_per_epoch

        self.max_lr = max_lr
        self.initial_lr = self.max_lr / div_factor
        self.min_lr = self.initial_lr / final_div_factor

        if three_phase:
            self._schedule_phases = [
                {
                    "end_step": float(pct_start * self.total_steps) - 1,
                    "start_lr": self.initial_lr,
                    "end_lr": self.max_lr,
                },
                {

View on GitHub (pinned to 2661c7c0ef)

Solutions

  1. Pass a positive int steps_per_epoch, typically math.ceil(num_samples / batch_size)
  2. Guard: steps_per_epoch = max(1, math.ceil(num_samples / batch_size))
  3. Check the scheduler config key name expected by PaddleOCR's optimizer builder

Example fix

# before
steps_per_epoch = num_samples / batch_size  # 0.0 for tiny debug set
# after
import math
steps_per_epoch = max(1, math.ceil(num_samples / batch_size))
Defensive patterns

Strategy: validation

Validate before calling

import math
steps_per_epoch = max(1, math.ceil(num_samples / batch_size))
assert isinstance(steps_per_epoch, int) and steps_per_epoch > 0

Type guard

def valid_steps(s) -> bool:
    return isinstance(s, int) and not isinstance(s, bool) and s > 0

Prevention

When it happens

Trigger: Calling the scheduler with steps_per_epoch=0 (e.g. num_samples//batch_size with batch > num_samples), a float, or None passed positionally.

Common situations: Tiny debug datasets where batch size exceeds sample count; computing steps as float division; config keys named differently (iters_per_epoch) so the parameter never gets set.

Related errors


AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14). Data as JSON: /api/errors/c1c528ae33d45659. Report an issue: GitHub.