PaddlePaddle/PaddleOCR · error · ValueError

No metric score found.

Error message

No metric score found.

What it means

ValueError from save_load.py's train result bookkeeping when saving a 'best' checkpoint but the metric dict contains none of the three recognized keys: 'acc', 'precision', or 'exp_rate'. The best-model ranking needs a scalar score, so an evaluation metric named anything else (e.g. 'hmean' variants not under precision, 'recall', 'f1', 'editdistance') has no score to record.

Solutions

  1. Make your Metric return one of the supported keys — conventionally 'acc' for recognition, 'precision' for detection.
  2. Or stop requesting best-model saving (drop 'best' from save_model_tag / disable save_best_model) and keep 'latest' checkpoints only.
  3. If a custom score is required, extend the lookup list in this function to include your metric key.

Example fix

# before (custom metric returns only unsupported key)
return {"metric": {"cer": 0.08}}

# after (expose a supported score alongside)
return {"metric": {"acc": 1 - 0.08, "cer": 0.08}}
Defensive patterns

Strategy: validation

Validate before calling

SUPPORTED_SCORE_KEYS = ("acc", "precision", "exp_rate")

def metric_has_supported_score(metric_info) -> bool:
    return any(k in metric_info.get("metric", {}) for k in SUPPORTED_SCORE_KEYS)

if "best" in prefix and not metric_has_supported_score(metric_info):
    prefix = prefix.replace("best", "latest")  # degrade to latest-only saving

Type guard

def is_supported_metric_dict(m) -> bool:
    return isinstance(m, dict) and any(k in m.get("metric", {}) for k in ("acc", "precision", "exp_rate"))

Prevention

When it happens

Trigger: Training with save_best_model/save_model_tag including 'best' while the task's Eval metric returns only unsupported keys — e.g. a custom recognizer metric returning 'cer'/'edit_dist', or a KIE/SDMGR-style metric dict without precision.

Common situations: Custom datasets/tasks with custom Metric classes; users renaming metric outputs; new model types whose evaluation returns niche keys; snapshot/export tools (model list generation) that hit the best branch.

Related errors


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

Appendix: source

Thrown at ppocr/utils/save_load.py:383

                label_dict_path = ""
        train_results["label_dict"] = label_dict_path
        train_results["train_log"] = "train.log"
        train_results["visualdl_log"] = ""
        train_results["config"] = "config.yaml"
        train_results["models"] = {}
        for i in range(1, last_num + 1):
            train_results["models"][f"last_{i}"] = {}
        train_results["models"]["best"] = {}
    train_results["done_flag"] = done_flag
    if "best" in prefix:
        if "acc" in metric_info["metric"]:
            metric_score = metric_info["metric"]["acc"]
        elif "precision" in metric_info["metric"]:
            metric_score = metric_info["metric"]["precision"]
        elif "exp_rate" in metric_info["metric"]:
            metric_score = metric_info["metric"]["exp_rate"]
        else:
            raise ValueError("No metric score found.")
        train_results["models"]["best"]["score"] = metric_score
        for tag in save_model_tag:
            if tag == "pdparams" and encrypted:
                train_results["models"]["best"][tag] = os.path.join(
                    prefix,
                    (
                        f"{prefix}.encrypted.{tag}"
                        if tag != "pdstates"
                        else f"{prefix}.states"
                    ),
                )
            else:
                train_results["models"]["best"][tag] = os.path.join(
                    prefix,
                    f"{prefix}.{tag}" if tag != "pdstates" else f"{prefix}.states",
                )
        for key in save_inference_files:
            train_results["models"]["best"][key] = os.path.join(

View on GitHub (pinned to 2661c7c0ef)