Anomaly models like padim spend the training epoch collecting features rather than backpropagating, so the first time your metric code actually executes is the validation pass at the end of that epoch. When the pass dies with AttributeError: 'AnomalibMetricCollection' object has no attribute 'image_F1Score', nothing is wrong with your dataset category, your config or your GPU: the installed torchmetrics release no longer matches the version the framework's requirements pin. This guide walks the failure from traceback to repair — what raises the error, why switching the category changes nothing, which metric library version to install, the commands that confirm the fix in seconds, and how to keep the pairing stable on the machine that retrains your inspection models.
Why does anomaly training crash on a missing image_F1Score attribute?
Your anomalib install pulled a torchmetrics release that no longer exposes each member of a MetricCollection as an attribute, while the collection's own update path reads image_F1Score by name; the mismatch raises the AttributeError at the first validation pass.
The failing run stops here:
AttributeError: 'AnomalibMetricCollection' object has no attribute 'image_F1Score'
Read the traceback from the bottom. validation_epoch_end collects scores and labels through _collect_outputs, which calls image_metric.update(...) on the framework's metric collection. Inside torchmetrics' collections.py, update() runs _merge_compute_groups(), and that function evaluates getattr(self, cg_members1[0]) — an attribute lookup of the member name image_F1Score. The lookup fails and Python raises the error you see.
AnomalibMetricCollection is the framework's subclass, built at model init from the metrics block of your config — in the reported run, image: [F1Score, AUROC] and pixel: [F1Score, AUROC]. Its model summary lists image_metrics | AnomalibMetricCollection and pixel_metrics | AnomalibMetricCollection, which proves both collections were constructed. What changed is the base class: releases newer than the 0.7.x series added the merge step inside update(), and it addresses members as attributes in a way the subclass does not provide. On 0.7.x the merge step does not run, so the same call succeeds.
Two warnings in the same log — configure_optimizers returned None and training_step returned None — are normal for this feature-collection model and unrelated to the crash.
Is the dataset category the cause?
No — the same config crashed on one category after training another, the traceback never touches the data pipeline, and identical settings train cleanly once the correct metric library version is installed; the category only decides which images flow through the same code.
The log backs this up: Loading the datamodule and Found the dataset both complete minutes before the exception, so the images and their folder layout are not the problem. The collection is built once from the metrics block and is the same object whatever the category: line says. A run that fails on several categories in exactly the same place is itself the diagnostic: category-specific data problems look different from a crash inside the metric library.
How many image- and pixel-level metrics to request in that block is a separate decision, and Anomaly detection metric configuration: align task and metrics walks it. Keep the metrics you need and fix the versions.
Which torchmetrics version does this anomalib release need?
Pin torchmetrics to the 0.7.x series for this 2023-era anomalib release: its requirements list that series, and 0.7.x is the last line whose update() runs without the merge-and-getattr step that the custom collection cannot satisfy.
The practical decision: install torchmetrics>=0.7,<0.8 and stay off 0.8 and later next to this framework release. If you upgrade the framework itself, take the metric library version from that release's requirements in the same change — each generation pairs with its own metric library. Do not let a bare resolver pick the version: unconstrained resolution is what installed the failing copy in the first place.
The interpreter is not the lever. Nothing in the traceback implicates Python 3.8; the crash is a pure attribute lookup that follows from the library pairing, so the repair behaves the same on any supported interpreter.
How do you pin and verify the fix?
Install the pinned series with pip, confirm the interpreter loads it, and prove the attribute exists before spending another training run: the version print must start with 0.7, and hasattr on the collection must return True.
pip install 'torchmetrics>=0.7,<0.8'
python -c 'import torchmetrics; print(torchmetrics.__version__)'
The printed version must start with 0.7. Then run the attribute proof. The framework builds image_metrics and pixel_metrics on the module it passes to trainer.fit(model, datamodule) — the same objects its model summary lists — so one assertion per collection is enough:
import torchmetrics
print(torchmetrics.__version__) # must print 0.7.*
# model = the anomalib Lightning module from tools/train.py
assert hasattr(model.image_metrics, 'image_F1Score')
assert hasattr(model.pixel_metrics, 'pixel_F1Score')
assert hasattr(model.image_metrics, 'image_AUROC')
hasattr and __version__ are library calls; model.image_metrics and model.pixel_metrics are the attribute names the framework's own summary prints, and model here stands for that Lightning module. Each assert passing is the test: the pinned release exposes the prefixed members the merge step wants.
Rerun python tools/train.py --config your-config.yaml. Validation now completes, and the log reports image-level and pixel-level F1Score and AUROC values instead of a traceback.
Why does the crash wait until validation?
The metric collection's update() runs for the first time at the end of the training epoch, so a padim run can log Aggregating the embedding and Fitting a Gaussian for roughly six minutes before the AttributeError lands inside validation_epoch_end.
The sequence in the failing log shows the order. Model init builds both collections — the summary table lists them at 17:35. The epoch then runs feature extraction only, because training_step produces no loss: at 17:41, about six minutes later, the model aggregates the embedding and fits the Gaussian, still without touching torchmetrics. The first call that reaches the library is _collect_outputs in validation_epoch_end, and it fails on the first update().
That timing explains why the bug looks data-dependent. A larger category takes longer to arrive at the identical crash; a smaller one gets there sooner. Where the run fails is fixed by code structure — the first validation pass — not by image content, so read the timestamp as a clue about dataset size, never about the parts.
What if the pin does not clear the error?
Check which copy of the library Python actually imports: the failing run loaded torchmetrics from the conda environment's site-packages while pytorch_lightning came from the per-user AppData\Roaming\Python\Python38\site-packages directory, and mixed installs like that let a stray newer copy survive your pin.
Check what actually imports, with the environment active:
python -c 'import torchmetrics; print(torchmetrics.__file__, torchmetrics.__version__)'
python -c 'import pytorch_lightning; print(pytorch_lightning.__file__)'
Both paths must sit inside your activated environment. If one does not, remove that copy with the same interpreter — python -m pip uninstall torchmetrics, then reinstall the pinned version without the --user flag. python -m site lists every directory the interpreter searches and the order it searches in, so you can see which copy wins before the framework does.
A second pattern hides in install history: pip install -e . applies the framework's requirements once, at install time. A later bare pip install torchmetrics, or an unrelated package whose dependencies pull a newer release, can move the version without touching the framework. Re-run the version print after every install on a training machine.
How do you keep the environment stable on the inspection cell?
Record the working versions once and install every station from that record: pin the metric library series in the same requirements file as the framework, freeze the result, and never mix pip install --user packages into a managed environment.
Freeze the environment that works:
python -m pip freeze > requirements-lock.txt
Install every training machine, and any scheduled retraining job, from that lock file, with the metric pin stated explicitly so it survives framework re-installs:
torchmetrics>=0.7,<0.8
Treat every install that is not from the lock file as a change to a validated system: afterwards, re-run the version print and the hasattr asserts from the section above. Upgrade the framework deliberately, and take the metric library pairing that release calls for in the same change rather than two moves apart. The habit generalizes: Docker build SDK version mismatch: pin one library version everywhere applies the same one-version rule to containers, and SDK examples raise AttributeError: fix the version mismatch covers the stale-import failures the same root cause produces elsewhere.
Once training and validation run clean on every category you ship, batch the retraining with the workflow in Training anomaly detection across multiple product classes.
When the model validates, the images it will score come from the cameras on your line: choose the sensor with the camera finder, or start at the camera step of the configurator — the same anomaly pipeline runs on images from the area scan cameras on a cell or the smart cameras doing on-device inference. More fixes in this series sit under the systems topic.
Questions
Why did the same config train without error on another category before?
The config did not change — the environment did. Between runs, a fresh pip install (the framework checkout or another package) moved torchmetrics past the 0.7.x series the release pins, or a user-site copy started shadowing the environment's own. Categories ride on top of the same code, so a run that trained fine earlier is evidence for a version change, not for a data problem; compare installed package versions across the two runs.
Does this error depend on GPU or CPU training?
No. The failing run's own log states GPU available: False, used: False, and the exception is a pure attribute lookup in the metric collection — no tensor, kernel or device takes part. It behaves identically on CPU-only workstations and GPU machines; only how quickly the run reaches the validation pass changes.
Can I drop F1Score from the metrics list instead of pinning the version?
No. The exception comes from how the newer MetricCollection release walks its members, not from one metric class: the same attribute lookup fails on the next prefixed member in line, and AUROC sits in the same list. Dropping metrics also deletes the numbers you validate and threshold on. Pin the metric library and keep the metrics your config declares.
What do image-level and pixel-level F1Score and AUROC mean for defect inspection?
Image-level scores say whether the part passes or scraps: F1Score balances missed defects against false rejects at the chosen threshold, and AUROC rates that separation across all thresholds. Pixel-level scores grade the localization mask, how tightly the model outlines the defect, which matters when an operator repairs rather than rejects the part. With threshold method adaptive, the cut-off is chosen from the validation set automatically.
Do I need the pinned metric library on the deployment machine too?
Only if that machine re-validates or loads the full training module. A model exported through the config's export options torch, onnx or openvino carries no metric collection, so an inference-only runtime stays lean. Any station that runs validation — the trainer, a retraining job or a threshold review — must carry the same pinned pairing, confirmed with the version print.