-
Notifications
You must be signed in to change notification settings - Fork 685
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
πAdd SuperSimpleNet model #2428
Open
blaz-r
wants to merge
25
commits into
openvinotoolkit:feature/v2
Choose a base branch
from
blaz-r:feature/supersimplenet
base: feature/v2
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
90625bb
Initial code structure
blaz-r 4d5e480
Add feature extraction
blaz-r 742e0ca
Add adaptor and segdec
blaz-r fd95793
Format files
blaz-r 26e6f5a
Add anomaly generation
blaz-r eeddb15
Add configurable params and docstrings
blaz-r 090120d
Add dosctring and fix lint issue
blaz-r 2be15bd
Implement loss and training step
blaz-r f986c8e
Update torchFX to also support str weights
blaz-r e08366d
Update names
blaz-r e45b6d4
Add supervision based param settings and validation step
blaz-r 8768607
Add optimizer configs
blaz-r 0e5a6c7
Fix loss and types
blaz-r 1238512
Add SSN to init
blaz-r 28006f1
Add SSN description
blaz-r 40e7475
Update supersimplenet README.md
blaz-r ce6c7f5
Update readme with arch and results
blaz-r a47fc50
Update SuperSimpleNet README.md
blaz-r 3c65da6
Update architecture image
blaz-r 07e02fb
Add SuperSimpleNet to init
blaz-r fda2c4e
Fix copyright location and format
blaz-r 8530a28
Merge branch 'feature/v2' into feature/supersimplenet
blaz-r 122ec03
Update SSN lightning model for v2
blaz-r fe57b27
Fix cls head to support onnx export
blaz-r 952653b
Merge branch 'feature/v2' into feature/supersimplenet
samet-akcay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
Copyright (c) 2024 Intel Corporation | ||
SPDX-License-Identifier: Apache-2.0 | ||
|
||
Some files in this folder are based on the original SuperSimpleNet implementation by BlaΕΎ Rolih | ||
|
||
Original license: | ||
----------------- | ||
|
||
MIT License | ||
|
||
Copyright (c) 2024 BlaΕΎ Rolih | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# SuperSimpleNet: Unifying Unsupervised and Supervised Learning for Fast and Reliable Surface Defect Detection | ||
|
||
This is an implementation of the [SuperSimpleNet](https://arxiv.org/pdf/2408.03143) paper, based on the [official code](https://github.com/blaz-r/SuperSimpleNet). | ||
|
||
Model Type: Segmentation | ||
|
||
## Description | ||
|
||
**SuperSimpleNet** is a simple yet strong discriminative defect / anomaly detection model evolved from the SimpleNet architecture. It consists of four components: | ||
feature extractor with upscaling, feature adaptor, synthetic feature-level anomaly generation module, and | ||
segmentation-detection module. | ||
|
||
A ResNet-like feature extractor first extracts features, which are then upscaled and | ||
average-pooled to capture neighboring context. Features are further refined for anomaly detection task in the adaptor module. | ||
During training, synthetic anomalies are generated at the feature level by adding Gaussian noise to regions defined by the | ||
binary Perlin noise mask. The perturbed features are then fed into the segmentation-detection | ||
module, which produces the anomaly map and the anomaly score. During inference, anomaly generation is skipped, and the model | ||
directly predicts the anomaly map and score. The predicted anomaly map is upscaled to match the input image size | ||
and refined with a Gaussian filter. | ||
|
||
This implementation supports both unsupervised and supervised setting, but Anomalib currently supports only unsupervised learning. | ||
|
||
## Architecture | ||
|
||
![SuperSimpleNet architecture](/docs/source/images/supersimplenet/architecture.png "SuperSimpleNet architecture") | ||
|
||
## Usage | ||
|
||
`anomalib train --model SuperSimpleNet --data MVTec --data.category <category>` | ||
|
||
> It is recommended to train the model for 300 epochs with batch size of 32 to achieve stable training with random anomaly generation. Training with lower parameter values will still work, but might not yield the optimal results. | ||
> | ||
> For supervised learning, refer to the [official code](https://github.com/blaz-r/SuperSimpleNet). | ||
|
||
## MVTec AD results | ||
|
||
The following results were obtained using this Anomalib implementation trained for 300 epochs with seed 42, default params, and batch size 32. | ||
| | **Image AUROC** | **Pixel AUPRO** | | ||
| ----------- | :-------------: | :-------------: | | ||
| Bottle | 1.000 | 0.914 | | ||
| Cable | 0.981 | 0.895 | | ||
| Capsule | 0.990 | 0.926 | | ||
| Carpet | 0.987 | 0.936 | | ||
| Grid | 0.998 | 0.935 | | ||
| Hazelnut | 0.999 | 0.946 | | ||
| Leather | 1.000 | 0.972 | | ||
| Metal_nut | 0.996 | 0.923 | | ||
| Pill | 0.960 | 0.942 | | ||
| Screw | 0.903 | 0.952 | | ||
| Tile | 0.989 | 0.817 | | ||
| Toothbrush | 0.917 | 0.861 | | ||
| Transistor | 1.000 | 0.909 | | ||
| Wood | 0.996 | 0.868 | | ||
| Zipper | 0.996 | 0.944 | | ||
| **Average** | 0.981 | 0.916 | | ||
|
||
For other results on VisA, SensumSODF, and KSDD2, refer to the [paper](https://arxiv.org/pdf/2408.03143). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
"""SuperSimpleNet model.""" | ||
|
||
# Copyright (C) 2024 Intel Corporation | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
from .lightning_model import SuperSimpleNet | ||
|
||
__all__ = ["SuperSimpleNet"] |
163 changes: 163 additions & 0 deletions
163
src/anomalib/models/image/supersimplenet/anomaly_generator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
"""Anomaly generator for the SuperSimplenet model implementation.""" | ||
|
||
# Copyright (C) 2024 Intel Corporation | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import torch | ||
import torch.nn.functional as F # noqa: N812 | ||
from torch import nn | ||
|
||
from anomalib.data.utils.generators.perlin import _rand_perlin_2d | ||
|
||
|
||
class SSNAnomalyGenerator(nn.Module): | ||
"""Anomaly generator of the SuperSimpleNet model.""" | ||
|
||
def __init__( | ||
self, | ||
noise_mean: float, | ||
noise_std: float, | ||
threshold: float, | ||
perlin_range: tuple[int, int] = (0, 6), | ||
) -> None: | ||
super().__init__() | ||
|
||
self.noise_mean = noise_mean | ||
self.noise_std = noise_std | ||
|
||
self.threshold = threshold | ||
|
||
self.min_perlin_scale = perlin_range[0] | ||
self.max_perlin_scale = perlin_range[1] | ||
|
||
@staticmethod | ||
def next_power_2(num: int) -> int: | ||
"""Get the next power of 2 for given number. | ||
|
||
Args: | ||
num (int): value of interest | ||
|
||
Returns: | ||
next power of 2 value for given number | ||
""" | ||
return 1 << (num - 1).bit_length() | ||
|
||
def generate_perlin(self, batches: int, height: int, width: int) -> torch.Tensor: | ||
"""Generate 2d perlin noise masks with dims [b, 1, h, w]. | ||
|
||
Args: | ||
batches (int): number of batches (different masks) | ||
height (int): height of features | ||
width (int): width of features | ||
|
||
Returns: | ||
tensor with b perlin binarized masks | ||
""" | ||
perlin = [] | ||
for _ in range(batches): | ||
# get scale of perlin in x and y direction | ||
perlin_scalex = 2 ** ( | ||
torch.randint( | ||
self.min_perlin_scale, | ||
self.max_perlin_scale, | ||
(1,), | ||
).item() | ||
) | ||
perlin_scaley = 2 ** ( | ||
torch.randint( | ||
self.min_perlin_scale, | ||
self.max_perlin_scale, | ||
(1,), | ||
).item() | ||
) | ||
|
||
perlin_height = self.next_power_2(height) | ||
perlin_width = self.next_power_2(width) | ||
|
||
perlin_noise = _rand_perlin_2d( | ||
(perlin_height, perlin_width), | ||
(perlin_scalex, perlin_scaley), | ||
) | ||
# original is power of 2 scale, so fit to our size | ||
perlin_noise = F.interpolate( | ||
perlin_noise.reshape(1, 1, perlin_height, perlin_width), | ||
size=(height, width), | ||
mode="bilinear", | ||
) | ||
# binarize | ||
perlin_thr = torch.where(perlin_noise > self.threshold, 1, 0) | ||
|
||
# 50% of anomaly | ||
if torch.rand(1).item() > 0.5: | ||
perlin_thr = torch.zeros_like(perlin_thr) | ||
|
||
perlin.append(perlin_thr) | ||
return torch.cat(perlin) | ||
|
||
def forward( | ||
self, | ||
features: torch.Tensor, | ||
mask: torch.Tensor, | ||
labels: torch.Tensor, | ||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | ||
"""Generate anomaly on features using thresholded perlin noise and Gaussian noise. | ||
|
||
Also update GT masks and labels with new anomaly information. | ||
|
||
Args: | ||
features: input features. | ||
mask: GT masks. | ||
labels: GT labels. | ||
|
||
Returns: | ||
perturbed features, updated GT masks and labels. | ||
""" | ||
b, _, h, w = features.shape | ||
|
||
# duplicate | ||
features = torch.cat((features, features)) | ||
mask = torch.cat((mask, mask)) | ||
labels = torch.cat((labels, labels)) | ||
|
||
noise = torch.normal( | ||
mean=self.noise_mean, | ||
std=self.noise_std, | ||
size=features.shape, | ||
device=features.device, | ||
requires_grad=False, | ||
) | ||
|
||
# mask indicating which regions will have noise applied | ||
# [B * 2, 1, H, W] initial all masked as anomalous | ||
noise_mask = torch.ones( | ||
b * 2, | ||
1, | ||
h, | ||
w, | ||
device=features.device, | ||
requires_grad=False, | ||
) | ||
|
||
# no overlap: don't apply to already anomalous regions (mask=1 -> bad) | ||
noise_mask = noise_mask * (1 - mask) | ||
|
||
# shape of noise is [B * 2, 1, H, W] | ||
perlin_mask = self.generate_perlin(b * 2, h, w).to(features.device) | ||
# only apply where perlin mask is 1 | ||
noise_mask = noise_mask * perlin_mask | ||
|
||
# update gt mask | ||
mask = mask + noise_mask | ||
# binarize | ||
mask = torch.where(mask > 0, torch.ones_like(mask), torch.zeros_like(mask)) | ||
|
||
# make new labels. 1 if any part of mask is 1, 0 otherwise | ||
new_anomalous = noise_mask.reshape(b * 2, -1).any(dim=1).type(torch.float32) | ||
labels = labels + new_anomalous | ||
# binarize | ||
labels = torch.where(labels > 0, torch.ones_like(labels), torch.zeros_like(labels)) | ||
|
||
# apply masked noise | ||
perturbed = features + noise * noise_mask | ||
|
||
return perturbed, mask, labels |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the missing points in Anomalib to support supervised setting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I believe there are no standard supervised datasets. Another problem is the Folder dataset as it assumes that abnormal samples are always in test set:
anomalib/src/anomalib/data/image/folder.py
Lines 174 to 175 in bcc0b43
Another thing for full reproduction of SuperSimpleNet results is the fixed flipping augmentation and frequency sampling. This is however not necessary, but needed for best results. It's also not SuperSimpleNet specific, so might be worth considering if other supervised model will be supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, we would like to diversify the model pool, and include more learning types than one-class models. Thanks for the feedback.
@abc-125, you might want to be aware of this discussion as you have recently worked on this stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding me, it would be great to have supervised models and datasets in Anomalib. Recently, I looked at how to add a supervised dataset, and it certainly would require changing some base structures, such as paths to folders (I guess we will need
abnormal_train_dir
and maybe renaming the rest to make it easier to understand,normal_train_dir
, etc.):anomalib/src/anomalib/data/image/folder.py
Lines 195 to 202 in bcc0b43