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.
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)
Solutions
- Make your Metric return one of the supported keys — conventionally 'acc' for recognition, 'precision' for detection.
- Or stop requesting best-model saving (drop 'best' from save_model_tag / disable save_best_model) and keep 'latest' checkpoints only.
- 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
- Design custom Metric classes to publish a scalar under 'acc' or 'precision'.
- When introducing a new task type, extend the score-key lookup in save_load.py alongside the metric.
- Smoke-test one save_model_tag cycle with a tiny run before launching full training.
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
- Expected float between 0 and 1 pct_start, but got {}
- anneal_strategy must by one of 'cos' or 'linear', instead go
- Tried to step {} times. The specified number of total steps
- The type of 'T_max1' in 'CosineAnnealingDecay' must be 'int'
- The type of 'T_max2' in 'CosineAnnealingDecay' must be 'int'
AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14).
Data as JSON: /api/errors/4beb4d2c3e2950c7.
Report an issue: GitHub.