Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Custom loops POC] Support model loading #996

Conversation

ashwinvaidya17
Copy link
Collaborator

@ashwinvaidya17 ashwinvaidya17 commented Mar 31, 2023

Description

  • Since normalization metrics is stored in the anomaly module, this PR ensures that it is loaded and assigned correctly when the model is loaded from the weights file.
  • Refactors the NormalizationMetrics

@github-actions github-actions bot removed the Docs label Apr 19, 2023
@ashwinvaidya17 ashwinvaidya17 marked this pull request as ready for review April 19, 2023 14:44
Copy link
Contributor

@samet-akcay samet-akcay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gone through my first round of review.

  • Loading weights: as discussed, loading the object directly could potentially avoid all this manual loading. (Not 100% sure if it worked)
  • Normalization: Some minor comments here and there

src/anomalib/models/components/base/anomaly_module.py Outdated Show resolved Hide resolved
src/anomalib/models/components/base/anomaly_module.py Outdated Show resolved Hide resolved
src/anomalib/trainer/utils/normalizer/cdf.py Outdated Show resolved Hide resolved
@@ -26,7 +26,7 @@ def _evaluation_step_end(self, *args, **kwargs) -> STEP_OUTPUT | None:
if outputs is not None:
self.trainer.post_processor.apply_predictions(outputs)
self.trainer.thresholder.update(outputs)
self.trainer.normalizer.update_metrics(outputs)
self.trainer.normalizer.update(outputs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this method name is descriptive enough. I'll need to go to the definition to check what update really does to outputs

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about update_normalization_statistics?

Comment on lines 51 to 52
self.trainer.thresholder.initialize()
self.trainer.metrics_manager.initialize()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may not be relevant to the scope of this PR, but I see that Trainer object has normalizer which is a NormalizationManager. To follow the same concept, would it be an idea to have self.trainer.metrics, which would be a MetricsManager?

I feel this would be in line with other attributes such as trainer.post_processor, trainer.normalizer, trainer.metrics, rather than using Manager? Or vice-versa.

Thoughts? @djdameln, @ashwinvaidya17 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, consistency would be better. I am fine with metrics.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also prefer metrics.

if not hasattr(self.trainer.lightning_module, "normalization_metrics"):
self.trainer.lightning_module.normalization_metrics = self.metric_class()
# Every time metric is called, it is moved to the cpu
return self.trainer.lightning_module.normalization_metrics.cpu()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be an edge case where the user wants this to be on GPU?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is somethig might have to address later. We move the metrics and outputs to cpu at a bunch of places. We have this PostProcessor._outputs_to_cpu(outputs) in post-processor. I guess the original idea was to avoid oom issues by moving metrics computation to the cpu. But in this case we can look into adding a new key to the config to allow gpu computation.

@@ -62,5 +62,5 @@ def __init__(
manual_pixel_threshold=manual_pixel_threshold,
)
self.post_processor = PostProcessor(trainer=self)
self.normalizer = Normalizer(trainer=self, normalization_method=normalization_method)
self.normalizer = NormalizationManager(trainer=self, normalization_method=normalization_method)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminology is a bit confusing here. I would suggest calling this attribute self.normalization_manager to be more in line with the metrics_manager and avoid confusion with the Normalizer classes. This would also avoid trainer.normalizer.normalizer.

Returns:
BaseNormalizer: Normalizer class
"""
normalization_method: BaseNormalizer | None = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe normalizer instead of normalization_method? Since the object is an instance of BaseNormalizer, not NormalizationMethod.

src/anomalib/trainer/utils/normalization_manager.py Outdated Show resolved Hide resolved
src/anomalib/trainer/utils/normalizer/base.py Outdated Show resolved Hide resolved
src/anomalib/trainer/utils/normalizer/cdf.py Outdated Show resolved Hide resolved
src/anomalib/trainer/utils/normalizer/base.py Outdated Show resolved Hide resolved
Comment on lines +40 to +43
if "anomaly_maps" in outputs.keys():
outputs["anomaly_maps"] = cdf.standardize(
outputs["anomaly_maps"], stats.pixel_mean, stats.pixel_std, center_at=stats.image_mean
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another piece of logic that could be handled based on task_type. (Not for this PR, just wanted to point it out)

src/anomalib/models/components/base/anomaly_module.py Outdated Show resolved Hide resolved
@ashwinvaidya17
Copy link
Collaborator Author

I've updated the PR with partial changes while we finalize the naming convention regarding the normalizer and metrics manager.

Copy link
Contributor

@samet-akcay samet-akcay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm alright with the overall approach for now. Thanks for your effort

Copy link
Contributor

@djdameln djdameln left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'm happy with the changes. Just one concern about the export, but maybe you were planning to address this in a different PR.

self._load_metrics()

def _load_metrics(self) -> None:
"""Loads the thersholding and normalization classes from the checkpoint."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: thresholding

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's in the scope of this PR, but export functionality is broken, because it tries to retrieve threshold information from the lightning model, which is now stored in trainer.

@ashwinvaidya17 ashwinvaidya17 merged commit 2792771 into openvinotoolkit:feature/custom_loops May 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants