From 838494cc0031d6782926a0364d2127b93c26e1c4 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Thu, 26 May 2022 11:25:17 -0700 Subject: [PATCH 01/26] Add relative imports to freia __init__ --- anomalib/models/components/freia/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/anomalib/models/components/freia/__init__.py b/anomalib/models/components/freia/__init__.py index b52144c7a7..3fb8f62365 100644 --- a/anomalib/models/components/freia/__init__.py +++ b/anomalib/models/components/freia/__init__.py @@ -9,3 +9,8 @@ # Copyright (c) 2018-2022 Lynton Ardizzone, Visual Learning Lab Heidelberg. # SPDX-License-Identifier: MIT # + +from .framework import SequenceINN +from .modules import AllInOneBlock + +__all__ = ["SequenceINN", "AllInOneBlock"] From 09f5d9e62d450eeacfee8ee4dc7b84e658db2c38 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Thu, 26 May 2022 11:26:34 -0700 Subject: [PATCH 02/26] =?UTF-8?q?=F0=9F=8E=AC=20Started=20fastflow=20imple?= =?UTF-8?q?mentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anomalib/models/fastflow/README.md | 0 anomalib/models/fastflow/__init__.py | 9 + anomalib/models/fastflow/anomaly_map.py | 5 + anomalib/models/fastflow/config.yaml | 97 +++++++++++ anomalib/models/fastflow/lightning_model.py | 5 + anomalib/models/fastflow/torch_model.py | 183 ++++++++++++++++++++ 6 files changed, 299 insertions(+) create mode 100644 anomalib/models/fastflow/README.md create mode 100644 anomalib/models/fastflow/__init__.py create mode 100644 anomalib/models/fastflow/anomaly_map.py create mode 100644 anomalib/models/fastflow/config.yaml create mode 100644 anomalib/models/fastflow/lightning_model.py create mode 100644 anomalib/models/fastflow/torch_model.py diff --git a/anomalib/models/fastflow/README.md b/anomalib/models/fastflow/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/anomalib/models/fastflow/__init__.py b/anomalib/models/fastflow/__init__.py new file mode 100644 index 0000000000..872f8e0c00 --- /dev/null +++ b/anomalib/models/fastflow/__init__.py @@ -0,0 +1,9 @@ +"""FastFlow Algorithm Implementation.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from .torch_model import FastFlowModel + +__all__ = ["FastFlowModel"] diff --git a/anomalib/models/fastflow/anomaly_map.py b/anomalib/models/fastflow/anomaly_map.py new file mode 100644 index 0000000000..7e62a75610 --- /dev/null +++ b/anomalib/models/fastflow/anomaly_map.py @@ -0,0 +1,5 @@ +"""FastFlow Anomaly Map Generator Implementation.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/anomalib/models/fastflow/config.yaml b/anomalib/models/fastflow/config.yaml new file mode 100644 index 0000000000..dc88001c57 --- /dev/null +++ b/anomalib/models/fastflow/config.yaml @@ -0,0 +1,97 @@ +dataset: + name: mvtec #options: [mvtec, btech, folder] + format: mvtec + path: ./datasets/MVTec + task: segmentation + category: wood + image_size: 256 + train_batch_size: 32 + test_batch_size: 32 + num_workers: 0 + transform_config: + train: null + val: null + create_validation_set: false + tiling: + apply: false + tile_size: null + stride: null + remove_border_count: 0 + use_random_tiling: False + random_tile_count: 16 + +model: + name: fastflow + input_size: 256 + backbone: resnet18 # options: [resnet18, wide_resnet50_2, cait_m48_448, deit_base_distilled_patch16_384] + flow_step: 8 + hidden_ratio: 1.0 + conv3x3_only: True + normalization_method: min_max # options: [null, min_max, cdf] + +metrics: + image: + - F1Score + - AUROC + pixel: + - F1Score + - AUROC + threshold: + image_default: 0 + pixel_default: 0 + adaptive: true + +project: + seed: 0 + path: ./results + log_images_to: [local] + logger: false # options: [tensorboard, wandb, csv] or combinations. + +# PL Trainer Args. Don't add extra parameter here. +trainer: + accelerator: auto # <"cpu", "gpu", "tpu", "ipu", "hpu", "auto"> + accumulate_grad_batches: 1 + amp_backend: native + auto_lr_find: false + auto_scale_batch_size: false + auto_select_gpus: false + benchmark: false + check_val_every_n_epoch: 1 # Don't validate before extracting features. + default_root_dir: null + detect_anomaly: false + deterministic: false + devices: 1 + enable_checkpointing: true + enable_model_summary: true + enable_progress_bar: true + fast_dev_run: false + gpus: null # Set automatically + gradient_clip_val: 0 + ipus: null + limit_predict_batches: 1.0 + limit_test_batches: 1.0 + limit_train_batches: 1.0 + limit_val_batches: 1.0 + log_every_n_steps: 50 + log_gpu_memory: null + max_epochs: 1 + max_steps: -1 + max_time: null + min_epochs: null + min_steps: null + move_metrics_to_cpu: false + multiple_trainloader_mode: max_size_cycle + num_nodes: 1 + num_processes: null + num_sanity_val_steps: 0 + overfit_batches: 0.0 + plugins: null + precision: 32 + profiler: null + reload_dataloaders_every_n_epochs: 0 + replace_sampler_ddp: true + strategy: null + sync_batchnorm: false + tpu_cores: null + track_grad_norm: -1 + val_check_interval: 1.0 # Don't validate before extracting features. diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py new file mode 100644 index 0000000000..1434532f38 --- /dev/null +++ b/anomalib/models/fastflow/lightning_model.py @@ -0,0 +1,5 @@ +"""FastFlow Lightning Model Implementation.""" + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/anomalib/models/fastflow/torch_model.py b/anomalib/models/fastflow/torch_model.py new file mode 100644 index 0000000000..4961f88e7e --- /dev/null +++ b/anomalib/models/fastflow/torch_model.py @@ -0,0 +1,183 @@ +"""FastFlow Torch Model Implementation.""" + +# Original Code +# Copyright (c) 2022 @gathierry +# https://github.com/gathierry/FastFlow/. +# SPDX-License-Identifier: Apache-2.0 +# +# Modified +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +import timm +import torch +import torch.nn as nn +import torch.nn.functional as F + +from anomalib.models.components.freia.framework import SequenceINN +from anomalib.models.components.freia.modules import AllInOneBlock + +BACKBONE_DEIT = "deit_base_distilled_patch16_384" +BACKBONE_CAIT = "cait_m48_448" +BACKBONE_RESNET18 = "resnet18" +BACKBONE_WIDE_RESNET50 = "wide_resnet50_2" + +SUPPORTED_BACKBONES = [ + BACKBONE_DEIT, + BACKBONE_CAIT, + BACKBONE_RESNET18, + BACKBONE_WIDE_RESNET50, +] + + +def subnet_conv_func(kernel_size, hidden_ratio): + def subnet_conv(in_channels, out_channels): + hidden_channels = int(in_channels * hidden_ratio) + return nn.Sequential( + nn.Conv2d(in_channels, hidden_channels, kernel_size, padding="same"), + nn.ReLU(), + nn.Conv2d(hidden_channels, out_channels, kernel_size, padding="same"), + ) + + return subnet_conv + + +def nf_fast_flow(input_chw, conv3x3_only, hidden_ratio, flow_steps, clamp=2.0): + nodes = SequenceINN(*input_chw) + for i in range(flow_steps): + if i % 2 == 1 and not conv3x3_only: + kernel_size = 1 + else: + kernel_size = 3 + nodes.append( + AllInOneBlock, + subnet_constructor=subnet_conv_func(kernel_size, hidden_ratio), + affine_clamping=clamp, + permute_soft=False, + ) + return nodes + + +class FastFlowModel(nn.Module): + def __init__( + self, + backbone, + flow_steps, + input_size, + conv3x3_only: bool =False, + hidden_ratio: float =1.0, + ): + super(FastFlowModel, self).__init__() + assert ( + backbone in SUPPORTED_BACKBONES + ), f"backbone must be one of {SUPPORTED_BACKBONES}" + + if backbone in [BACKBONE_CAIT, BACKBONE_DEIT]: + self.feature_extractor = timm.create_model(backbone, pretrained=True) + channels = [768] + scales = [16] + else: + self.feature_extractor = timm.create_model( + backbone, + pretrained=True, + features_only=True, + out_indices=[1, 2, 3], + ) + channels = self.feature_extractor.feature_info.channels() + scales = self.feature_extractor.feature_info.reduction() + + # for transformers, use their pretrained norm w/o grad + # for resnets, self.norms are trainable LayerNorm + self.norms = nn.ModuleList() + for in_channels, scale in zip(channels, scales): + self.norms.append( + nn.LayerNorm( + [in_channels, int(input_size / scale), int(input_size / scale)], + elementwise_affine=True, + ) + ) + + for param in self.feature_extractor.parameters(): + param.requires_grad = False + + self.nf_flows = nn.ModuleList() + for in_channels, scale in zip(channels, scales): + self.nf_flows.append( + nf_fast_flow( + [in_channels, int(input_size / scale), int(input_size / scale)], + conv3x3_only=conv3x3_only, + hidden_ratio=hidden_ratio, + flow_steps=flow_steps, + ) + ) + self.input_size = input_size + + def forward(self, x): + self.feature_extractor.eval() + if isinstance( + self.feature_extractor, timm.models.vision_transformer.VisionTransformer + ): + x = self.feature_extractor.patch_embed(x) + cls_token = self.feature_extractor.cls_token.expand(x.shape[0], -1, -1) + if self.feature_extractor.dist_token is None: + x = torch.cat((cls_token, x), dim=1) + else: + x = torch.cat( + ( + cls_token, + self.feature_extractor.dist_token.expand(x.shape[0], -1, -1), + x, + ), + dim=1, + ) + x = self.feature_extractor.pos_drop(x + self.feature_extractor.pos_embed) + for i in range(8): # paper Table 6. Block Index = 7 + x = self.feature_extractor.blocks[i](x) + x = self.feature_extractor.norm(x) + x = x[:, 2:, :] + N, _, C = x.shape + x = x.permute(0, 2, 1) + x = x.reshape(N, C, self.input_size // 16, self.input_size // 16) + features = [x] + elif isinstance(self.feature_extractor, timm.models.cait.Cait): + x = self.feature_extractor.patch_embed(x) + x = x + self.feature_extractor.pos_embed + x = self.feature_extractor.pos_drop(x) + for i in range(41): # paper Table 6. Block Index = 40 + x = self.feature_extractor.blocks[i](x) + N, _, C = x.shape + x = self.feature_extractor.norm(x) + x = x.permute(0, 2, 1) + x = x.reshape(N, C, self.input_size // 16, self.input_size // 16) + features = [x] + else: + features = self.feature_extractor(x) + features = [self.norms[i](feature) for i, feature in enumerate(features)] + + loss = 0 + outputs = [] + for i, feature in enumerate(features): + output, log_jac_dets = self.nf_flows[i](feature) + loss += torch.mean( + 0.5 * torch.sum(output**2, dim=(1, 2, 3)) - log_jac_dets + ) + outputs.append(output) + ret = {"loss": loss} + + if not self.training: + anomaly_map_list = [] + for output in outputs: + log_prob = -torch.mean(output**2, dim=1, keepdim=True) * 0.5 + prob = torch.exp(log_prob) + a_map = F.interpolate( + -prob, + size=[self.input_size, self.input_size], + mode="bilinear", + align_corners=False, + ) + anomaly_map_list.append(a_map) + anomaly_map_list = torch.stack(anomaly_map_list, dim=-1) + anomaly_map = torch.mean(anomaly_map_list, dim=-1) + ret["anomaly_map"] = anomaly_map + return ret From 89e599c245dc0033582d71e72cf06b8cf6286af9 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Thu, 26 May 2022 11:26:51 -0700 Subject: [PATCH 03/26] =?UTF-8?q?=F0=9F=8E=AC=20Started=20fastflow=20imple?= =?UTF-8?q?mentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anomalib/models/fastflow/torch_model.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/anomalib/models/fastflow/torch_model.py b/anomalib/models/fastflow/torch_model.py index 4961f88e7e..ebaee39c3b 100644 --- a/anomalib/models/fastflow/torch_model.py +++ b/anomalib/models/fastflow/torch_model.py @@ -65,13 +65,11 @@ def __init__( backbone, flow_steps, input_size, - conv3x3_only: bool =False, - hidden_ratio: float =1.0, + conv3x3_only: bool = False, + hidden_ratio: float = 1.0, ): super(FastFlowModel, self).__init__() - assert ( - backbone in SUPPORTED_BACKBONES - ), f"backbone must be one of {SUPPORTED_BACKBONES}" + assert backbone in SUPPORTED_BACKBONES, f"backbone must be one of {SUPPORTED_BACKBONES}" if backbone in [BACKBONE_CAIT, BACKBONE_DEIT]: self.feature_extractor = timm.create_model(backbone, pretrained=True) @@ -115,9 +113,7 @@ def __init__( def forward(self, x): self.feature_extractor.eval() - if isinstance( - self.feature_extractor, timm.models.vision_transformer.VisionTransformer - ): + if isinstance(self.feature_extractor, timm.models.vision_transformer.VisionTransformer): x = self.feature_extractor.patch_embed(x) cls_token = self.feature_extractor.cls_token.expand(x.shape[0], -1, -1) if self.feature_extractor.dist_token is None: @@ -159,9 +155,7 @@ def forward(self, x): outputs = [] for i, feature in enumerate(features): output, log_jac_dets = self.nf_flows[i](feature) - loss += torch.mean( - 0.5 * torch.sum(output**2, dim=(1, 2, 3)) - log_jac_dets - ) + loss += torch.mean(0.5 * torch.sum(output**2, dim=(1, 2, 3)) - log_jac_dets) outputs.append(output) ret = {"loss": loss} From 0723896c2368f1cde6c63c99f14e39fa7180450c Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sat, 28 May 2022 06:36:07 -0700 Subject: [PATCH 04/26] Create anomaly map generator for fastflow --- anomalib/models/fastflow/anomaly_map.py | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/anomalib/models/fastflow/anomaly_map.py b/anomalib/models/fastflow/anomaly_map.py index 7e62a75610..ad530b43bc 100644 --- a/anomalib/models/fastflow/anomaly_map.py +++ b/anomalib/models/fastflow/anomaly_map.py @@ -3,3 +3,47 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 # + +from typing import List, Tuple, Union + +import torch +import torch.nn.functional as F +from omegaconf import ListConfig +from torch import Tensor + + +class AnomalyMapGenerator: + """Generate Anomaly Heatmap.""" + + def __init__(self, input_size: Union[ListConfig, Tuple]): + self.input_size = input_size if isinstance(input_size, tuple) else tuple(input_size) + + def __call__(self, hidden_variables: List[Tensor]) -> Tensor: + """Generate Anomaly Heatmap. + + This implementation generates the heatmap based on the flow maps + computed from the normalizing flow (NF) FastFlow blocks. Each block + yields a flow map, which overall is stacked and averaged to an anomaly + map. + + Args: + hidden_variables (List[Tensor]): List of hidden variables from each NF FastFlow block. + + Returns: + Tensor: Anomaly Map. + """ + flow_maps: List[Tensor] = [] + for hidden_variable in hidden_variables: + log_prob = -torch.mean(hidden_variable**2, dim=1, keepdim=True) * 0.5 + prob = torch.exp(log_prob) + flow_map = F.interpolate( + input=-prob, + size=self.input_size, + mode="bilinear", + align_corners=False, + ) + flow_maps.append(flow_map) + flow_maps = torch.stack(flow_maps, dim=-1) + anomaly_map = torch.mean(flow_maps, dim=-1) + + return anomaly_map From 52fcb8ba08c94691e434cbd6b3349671676a134f Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sat, 28 May 2022 06:37:00 -0700 Subject: [PATCH 05/26] Add fastflow to the list of available trainable models --- anomalib/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/models/__init__.py b/anomalib/models/__init__.py index 7b86048ae1..766bd253e2 100644 --- a/anomalib/models/__init__.py +++ b/anomalib/models/__init__.py @@ -42,7 +42,7 @@ def get_model(config: Union[DictConfig, ListConfig]) -> AnomalyModule: Returns: AnomalyModule: Anomaly Model """ - model_list: List[str] = ["cflow", "dfkde", "dfm", "ganomaly", "padim", "patchcore", "stfpm"] + model_list: List[str] = ["cflow", "dfkde", "dfm", "fastflow", "ganomaly", "padim", "patchcore", "stfpm"] model: AnomalyModule if config.model.name in model_list: From 80ffcde5290ed17dc8497eda888f94f3a65ffbae Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sat, 28 May 2022 06:37:34 -0700 Subject: [PATCH 06/26] Add trainer parameters to fastflow.config --- anomalib/models/fastflow/config.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/anomalib/models/fastflow/config.yaml b/anomalib/models/fastflow/config.yaml index dc88001c57..3ad6e20d99 100644 --- a/anomalib/models/fastflow/config.yaml +++ b/anomalib/models/fastflow/config.yaml @@ -22,11 +22,16 @@ dataset: model: name: fastflow - input_size: 256 backbone: resnet18 # options: [resnet18, wide_resnet50_2, cait_m48_448, deit_base_distilled_patch16_384] - flow_step: 8 + flow_steps: 8 hidden_ratio: 1.0 conv3x3_only: True + lr: 0.001 + weight_decay: 0.00001 + early_stopping: + patience: 3 + metric: pixel_AUROC + mode: max normalization_method: min_max # options: [null, min_max, cdf] metrics: @@ -74,7 +79,7 @@ trainer: limit_val_batches: 1.0 log_every_n_steps: 50 log_gpu_memory: null - max_epochs: 1 + max_epochs: 500 max_steps: -1 max_time: null min_epochs: null From ca34609a20647e69acd30c03f4f6bba094ae864c Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sat, 28 May 2022 06:50:26 -0700 Subject: [PATCH 07/26] Modify FastflowModel and FastFlowLightningModule --- anomalib/models/fastflow/__init__.py | 5 +- anomalib/models/fastflow/lightning_model.py | 130 +++++++++++++ anomalib/models/fastflow/torch_model.py | 197 ++++++++++++++------ 3 files changed, 268 insertions(+), 64 deletions(-) diff --git a/anomalib/models/fastflow/__init__.py b/anomalib/models/fastflow/__init__.py index 872f8e0c00..03c02995ca 100644 --- a/anomalib/models/fastflow/__init__.py +++ b/anomalib/models/fastflow/__init__.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # -from .torch_model import FastFlowModel +from .lightning_model import Fastflow, FastflowLightning +from .torch_model import FastflowLoss, FastflowModel -__all__ = ["FastFlowModel"] +__all__ = ["FastflowModel", "FastflowLoss", "FastflowLightning", "Fastflow"] diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index 1434532f38..cdf059c57f 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -3,3 +3,133 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: Apache-2.0 # + +import logging +from typing import Tuple, Union + +import torch +from omegaconf import DictConfig, ListConfig +from pytorch_lightning.callbacks import EarlyStopping +from pytorch_lightning.utilities.cli import MODEL_REGISTRY +from pytorch_lightning.utilities.types import STEP_OUTPUT +from torch import optim + +from anomalib.models.components import AnomalyModule +from anomalib.models.fastflow.torch_model import FastflowLoss, FastflowModel + +logger = logging.getLogger(__name__) + + +@MODEL_REGISTRY +class Fastflow(AnomalyModule): + """PL Lightning Module for the FastFlow algorithm. + + Args: + input_size (Tuple[int, int]): Model input size. + backbone (str): Backbone CNN network + flow_steps (int): Flow steps. + conv3x3_only (bool, optinoal): Use only conv3x3 in fast_flow model. Defaults to False. + hidden_ratio (float, optional): Ratio to calculate hidden var channels. Defaults to 1.0. + """ + + def __init__( + self, + input_size: Tuple[int, int], + backbone: str, + flow_steps: int, + conv3x3_only: bool = False, + hidden_ratio: float = 1.0, + ): + super().__init__() + logger.info("Initializing Stfpm Lightning model.") + + self.model = FastflowModel( + input_size=input_size, + backbone=backbone, + flow_steps=flow_steps, + conv3x3_only=conv3x3_only, + hidden_ratio=hidden_ratio, + ) + self.loss_func = FastflowLoss() + + def training_step(self, batch, _batch_idx) -> STEP_OUTPUT: + """Forward-pass input and return the loss. + + Args: + batch (Tensor): Input batch + _batch_idx: Index of the batch. + + Returns: + STEP_OUTPUT: Dictionary containing the loss value. + """ + hidden_variables, jacobians = self.model(batch["image"]) + loss = self.loss_func(hidden_variables, jacobians) + return {"loss": loss} + + def validation_step(self, batch, _batch_idx) -> dict: + """Forward-pass the input and return the anomaly map. + + Args: + batch (Tensor): Input batch + _batch_idx: Index of the batch. + + Returns: + dict: batch dictionary containing anomaly-maps. + """ + anomaly_maps = self.model(batch["image"]) + batch["anomaly_maps"] = anomaly_maps + return batch + + +class FastflowLightning(Fastflow): + """PL Lightning Module for the FastFlow algorithm. + + Args: + hparams (Union[DictConfig, ListConfig]): Model params + """ + + def __init__(self, hparams: Union[DictConfig, ListConfig]) -> None: + super().__init__( + # TODO: Input size. + input_size=hparams.model.input_size, + backbone=hparams.model.backbone, + flow_steps=hparams.model.flow_steps, + conv3x3_only=hparams.model.conv3x3_only, + hidden_ratio=hparams.model.hidden_ratio, + ) + self.hparams: Union[DictConfig, ListConfig] # type: ignore + self.save_hyperparameters(hparams) + + def configure_callbacks(self): + """Configure model-specific callbacks. + + Note: + This method is used for the existing CLI. + When PL CLI is introduced, configure callback method will be + deprecated, and callbacks will be configured from either + config.yaml file or from CLI. + """ + early_stopping = EarlyStopping( + monitor=self.hparams.model.early_stopping.metric, + patience=self.hparams.model.early_stopping.patience, + mode=self.hparams.model.early_stopping.mode, + ) + return [early_stopping] + + def configure_optimizers(self) -> torch.optim.Optimizer: + """Configures optimizers for each decoder. + + Note: + This method is used for the existing CLI. + When PL CLI is introduced, configure optimizers method will be + deprecated, and optimizers will be configured from either + config.yaml file or from CLI. + + Returns: + Optimizer: Adam optimizer for each decoder + """ + return optim.Adam( + params=self.model.parameters(), + lr=self.hparams.model.lr, + weight_decay=self.hparams.model.weight_decay, + ) diff --git a/anomalib/models/fastflow/torch_model.py b/anomalib/models/fastflow/torch_model.py index ebaee39c3b..968018313c 100644 --- a/anomalib/models/fastflow/torch_model.py +++ b/anomalib/models/fastflow/torch_model.py @@ -10,29 +10,35 @@ # SPDX-License-Identifier: Apache-2.0 # +from typing import Callable, List, Tuple, Union + import timm import torch -import torch.nn as nn -import torch.nn.functional as F +from timm.models.cait import Cait +from timm.models.vision_transformer import VisionTransformer +from torch import Tensor, nn from anomalib.models.components.freia.framework import SequenceINN from anomalib.models.components.freia.modules import AllInOneBlock +from anomalib.models.fastflow.anomaly_map import AnomalyMapGenerator + -BACKBONE_DEIT = "deit_base_distilled_patch16_384" -BACKBONE_CAIT = "cait_m48_448" -BACKBONE_RESNET18 = "resnet18" -BACKBONE_WIDE_RESNET50 = "wide_resnet50_2" +def subnet_conv_func(kernel_size: int, hidden_ratio: float) -> Callable: + """Subnet Convolutional Function. -SUPPORTED_BACKBONES = [ - BACKBONE_DEIT, - BACKBONE_CAIT, - BACKBONE_RESNET18, - BACKBONE_WIDE_RESNET50, -] + Callable class or function ``f``, called as ``f(channels_in, channels_out)`` and + should return a torch.nn.Module. + Predicts coupling coefficients :math:`s, t`. + Args: + kernel_size (int): Kernel Size + hidden_ratio (float): Hidden ratio to compute number of hidden channels. -def subnet_conv_func(kernel_size, hidden_ratio): - def subnet_conv(in_channels, out_channels): + Returns: + Callable: Sequential for the subnet constructor. + """ + + def subnet_conv(in_channels: int, out_channels: int) -> nn.Sequential: hidden_channels = int(in_channels * hidden_ratio) return nn.Sequential( nn.Conv2d(in_channels, hidden_channels, kernel_size, padding="same"), @@ -43,8 +49,29 @@ def subnet_conv(in_channels, out_channels): return subnet_conv -def nf_fast_flow(input_chw, conv3x3_only, hidden_ratio, flow_steps, clamp=2.0): - nodes = SequenceINN(*input_chw) +def create_fast_flow_block( + input_dimensions: List[int], + conv3x3_only: bool, + hidden_ratio: float, + flow_steps: int, + clamp: float = 2.0, +) -> SequenceINN: + """Create NF Fast Flow Block. + + This is to create Normalizing Flow (NF) Fast Flow model block based on + Figure 2 and Section 3.3 in the paper. + + Args: + input_dimensions (List[int]): Input dimensions (Channel, Height, Width) + conv3x3_only (bool): Boolean whether to use conv3x3 only or conv3x3 and conv1x1. + hidden_ratio (float): Ratio for the hidden layer channels. + flow_steps (int): Flow steps. + clamp (float, optional): Clamp. Defaults to 2.0. + + Returns: + SequenceINN: FastFlow Block. + """ + nodes = SequenceINN(*input_dimensions) for i in range(flow_steps): if i % 2 == 1 and not conv3x3_only: kernel_size = 1 @@ -59,23 +86,58 @@ def nf_fast_flow(input_chw, conv3x3_only, hidden_ratio, flow_steps, clamp=2.0): return nodes -class FastFlowModel(nn.Module): +class FastflowLoss(nn.Module): + """FastFlow Loss.""" + + def forward(self, hidden_variables: List[Tensor], jacobians: List[Tensor]) -> Tensor: + """Calculate the Fastflow loss. + + Args: + hidden_variables (List[Tensor]): Hidden variables from the fastflow model. f: X -> Z + jacobians (List[Tensor]): Log of the jacobian determinants from the fastflow model. + + Returns: + Tensor: _description_ + """ + loss = torch.tensor(0.0, device=hidden_variables[0].device) + for (hidden_variable, jacobian) in zip(hidden_variables, jacobians): + loss += torch.mean(0.5 * torch.sum(hidden_variable**2, dim=(1, 2, 3)) - jacobian) + return loss + + +class FastflowModel(nn.Module): + """FastFlow. + + Unsupervised Anomaly Detection and Localization via 2D Normalizing Flows. + + Args: + input_size (Tuple[int, int]): Model input size. + backbone (str): Backbone CNN network + flow_steps (int): Flow steps. + conv3x3_only (bool, optinoal): Use only conv3x3 in fast_flow model. Defaults to False. + hidden_ratio (float, optional): Ratio to calculate hidden var channels. Defaults to 1.0. + + Raises: + ValueError: When the backbone is not supported. + """ + def __init__( self, - backbone, - flow_steps, - input_size, + input_size: Tuple[int, int], + backbone: str, + flow_steps: int, conv3x3_only: bool = False, hidden_ratio: float = 1.0, - ): - super(FastFlowModel, self).__init__() - assert backbone in SUPPORTED_BACKBONES, f"backbone must be one of {SUPPORTED_BACKBONES}" + ) -> None: + super().__init__() - if backbone in [BACKBONE_CAIT, BACKBONE_DEIT]: + self.input_size = input_size + + if backbone in ["cait_m48_448", "deit_base_distilled_patch16_384"]: self.feature_extractor = timm.create_model(backbone, pretrained=True) channels = [768] scales = [16] - else: + elif backbone in ["resnet18", "wide_resnet50_2"]: self.feature_extractor = timm.create_model( backbone, pretrained=True, @@ -88,32 +150,51 @@ def __init__( # for transformers, use their pretrained norm w/o grad # for resnets, self.norms are trainable LayerNorm self.norms = nn.ModuleList() - for in_channels, scale in zip(channels, scales): + for channel, scale in zip(channels, scales): self.norms.append( nn.LayerNorm( - [in_channels, int(input_size / scale), int(input_size / scale)], + [channel, int(input_size[0] / scale), int(input_size[1] / scale)], elementwise_affine=True, ) ) + else: + raise ValueError( + f"Backbone {backbone} is not supported. List of available backbones are " + "[cait_m48_448, deit_base_distilled_patch16_384, resnet18, wide_resnet50_2]." + ) - for param in self.feature_extractor.parameters(): - param.requires_grad = False + for parameter in self.feature_extractor.parameters(): + parameter.requires_grad = False - self.nf_flows = nn.ModuleList() - for in_channels, scale in zip(channels, scales): - self.nf_flows.append( - nf_fast_flow( - [in_channels, int(input_size / scale), int(input_size / scale)], + self.fast_flow_blocks = nn.ModuleList() + for channel, scale in zip(channels, scales): + self.fast_flow_blocks.append( + create_fast_flow_block( + input_dimensions=[channel, int(input_size[0] / scale), int(input_size[1] / scale)], conv3x3_only=conv3x3_only, hidden_ratio=hidden_ratio, flow_steps=flow_steps, ) ) - self.input_size = input_size + self.anomaly_map_generator = AnomalyMapGenerator(input_size=input_size) + + def forward(self, x: Tensor) -> Union[Tuple[List[Tensor], List[Tensor]], Tensor]: + """Forward-Pass the input to the FastFlow Model. + + Args: + x (Tensor): Input tensor. + + Returns: + Union[Tuple[Tensor, Tensor], Tensor]: During training, return + (hidden_variables, log-of-the-jacobian-determinants). + During the validation/test, return the anomaly map. + """ + # pylint: disable=invalid-name + + return_val: Union[Tuple[List[Tensor], List[Tensor]], Tensor] - def forward(self, x): self.feature_extractor.eval() - if isinstance(self.feature_extractor, timm.models.vision_transformer.VisionTransformer): + if isinstance(self.feature_extractor, VisionTransformer): x = self.feature_extractor.patch_embed(x) cls_token = self.feature_extractor.cls_token.expand(x.shape[0], -1, -1) if self.feature_extractor.dist_token is None: @@ -134,9 +215,9 @@ def forward(self, x): x = x[:, 2:, :] N, _, C = x.shape x = x.permute(0, 2, 1) - x = x.reshape(N, C, self.input_size // 16, self.input_size // 16) + x = x.reshape(N, C, self.input_size[0] // 16, self.input_size[1] // 16) features = [x] - elif isinstance(self.feature_extractor, timm.models.cait.Cait): + elif isinstance(self.feature_extractor, Cait): x = self.feature_extractor.patch_embed(x) x = x + self.feature_extractor.pos_embed x = self.feature_extractor.pos_drop(x) @@ -145,33 +226,25 @@ def forward(self, x): N, _, C = x.shape x = self.feature_extractor.norm(x) x = x.permute(0, 2, 1) - x = x.reshape(N, C, self.input_size // 16, self.input_size // 16) + x = x.reshape(N, C, self.input_size[0] // 16, self.input_size[1] // 16) features = [x] else: features = self.feature_extractor(x) features = [self.norms[i](feature) for i, feature in enumerate(features)] - loss = 0 - outputs = [] - for i, feature in enumerate(features): - output, log_jac_dets = self.nf_flows[i](feature) - loss += torch.mean(0.5 * torch.sum(output**2, dim=(1, 2, 3)) - log_jac_dets) - outputs.append(output) - ret = {"loss": loss} + # Compute the hidden variable f: X -> Z and log-likelihood of the jacobian + # (See Section 3.3 in the paper.) + # NOTE: output variable has z, and jacobian tuple for each fast-flow blocks. + hidden_variables: List[Tensor] = [] + log_jacobians: List[Tensor] = [] + for fast_flow_block, feature in zip(self.fast_flow_blocks, features): + hidden_variable, log_jacobian = fast_flow_block(feature) + hidden_variables.append(hidden_variable) + log_jacobians.append(log_jacobian) + + return_val = (hidden_variables, log_jacobians) if not self.training: - anomaly_map_list = [] - for output in outputs: - log_prob = -torch.mean(output**2, dim=1, keepdim=True) * 0.5 - prob = torch.exp(log_prob) - a_map = F.interpolate( - -prob, - size=[self.input_size, self.input_size], - mode="bilinear", - align_corners=False, - ) - anomaly_map_list.append(a_map) - anomaly_map_list = torch.stack(anomaly_map_list, dim=-1) - anomaly_map = torch.mean(anomaly_map_list, dim=-1) - ret["anomaly_map"] = anomaly_map - return ret + return_val = self.anomaly_map_generator(hidden_variables) + + return return_val From 4546bdf645ff2eb78c27a6e5f0f0f5ec812b7f53 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sat, 28 May 2022 06:59:33 -0700 Subject: [PATCH 08/26] Log performance metrics to the progress bar --- anomalib/models/components/base/anomaly_module.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/anomalib/models/components/base/anomaly_module.py b/anomalib/models/components/base/anomaly_module.py index cff2985c6d..9f424fc0f4 100644 --- a/anomalib/models/components/base/anomaly_module.py +++ b/anomalib/models/components/base/anomaly_module.py @@ -171,6 +171,8 @@ def _outputs_to_cpu(self, output): def _log_metrics(self): """Log computed performance metrics.""" - self.log_dict(self.image_metrics) if self.pixel_metrics.update_called: - self.log_dict(self.pixel_metrics) + self.log_dict(self.pixel_metrics, prog_bar=True) + self.log_dict(self.image_metrics, prog_bar=False) + else: + self.log_dict(self.image_metrics, prog_bar=True) From c3a21ce843f777aac7d6d81bc38cfbed0dbe1ef9 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sat, 28 May 2022 07:09:57 -0700 Subject: [PATCH 09/26] Added configs for other backbones --- anomalib/models/fastflow/config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/anomalib/models/fastflow/config.yaml b/anomalib/models/fastflow/config.yaml index 3ad6e20d99..ca8583fda9 100644 --- a/anomalib/models/fastflow/config.yaml +++ b/anomalib/models/fastflow/config.yaml @@ -4,7 +4,7 @@ dataset: path: ./datasets/MVTec task: segmentation category: wood - image_size: 256 + image_size: 256 # options: [256, 256, 448, 384] - for each supported backbone train_batch_size: 32 test_batch_size: 32 num_workers: 0 @@ -23,9 +23,9 @@ dataset: model: name: fastflow backbone: resnet18 # options: [resnet18, wide_resnet50_2, cait_m48_448, deit_base_distilled_patch16_384] - flow_steps: 8 - hidden_ratio: 1.0 - conv3x3_only: True + flow_steps: 8 # options: [8, 8, 20, 20] - for each supported backbone + hidden_ratio: 1.0 # options: [1.0, 1.0, 0.16, 0.16] - for each supported backbone + conv3x3_only: True # options: [True, False, False, False] - for each supported backbone lr: 0.001 weight_decay: 0.00001 early_stopping: From 545e29bb6ba6c5bfebcea86519866fb8a4b00184 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sun, 29 May 2022 05:33:45 -0700 Subject: [PATCH 10/26] Added readme --- anomalib/models/fastflow/README.md | 117 +++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/anomalib/models/fastflow/README.md b/anomalib/models/fastflow/README.md index e69de29bb2..541371dd17 100644 --- a/anomalib/models/fastflow/README.md +++ b/anomalib/models/fastflow/README.md @@ -0,0 +1,117 @@ +# FastFlow: Unsupervised Anomaly Detection and Localization via 2D Normalizing Flows + +This is the implementation of the [FastFlow](https://arxiv.org/abs/2111.07677) paper. This code is developed inspired by [https://github.com/gathierry/FastFlow](https://github.com/gathierry/FastFlow). + +Model Type: Segmentation + +## Description + +Unsupervised anomaly detection and localization is crucial to the practical application when collecting and labeling sufficient anomaly data is infeasible. Most existing representation-based approaches extract normal image features with a deep convolutional neural network and characterize the corresponding distribution through non-parametric distribution estimation methods. The anomaly score is calculated by measuring the distance between the feature of the test image and the estimated distribution. However, current methods can not effectively map image features to a tractable base distribution and ignore the relationship between local and global features which are important to identify anomalies. To this end, we propose FastFlow implemented with 2D normalizing flows and use it as the probability distribution estimator. Our FastFlow can be used as a plug-in module with arbitrary deep feature extractors such as ResNet and vision transformer for unsupervised anomaly detection and localization. In training phase, FastFlow learns to transform the input visual feature into a tractable distribution and obtains the likelihood to recognize anomalies in inference phase. Extensive experimental results on the MVTec AD dataset show that FastFlow surpasses previous state-of-the-art methods in terms of accuracy and inference efficiency with various backbone networks. Our approach achieves 99.4% AUC in anomaly detection with high inference efficiency. + +## Architecture + +![FastFlow Architecture](../../../docs/source/images/fastflow/architecture.jpg "FastFlow Architecture") + +## Usage + +`python tools/train.py --model fastflow` + +## Benchmark + +All results gathered with seed `42`. + +## [MVTec AD Dataset](https://www.mvtec.com/company/research/datasets/mvtec-ad) + +### Image-Level AUC + +| | ResNet-18 | Wide ResNet50 | DeiT | CaiT | +| ---------- | --------- | ------------- | ---- | ---- | +| Bottle | 1.000 | 1.000 | | | +| Cable | 0.891 | 0.962 | | | +| Capsule | 0.900 | 0.963 | | | +| Carpet | 0.979 | 0.994 | | | +| Grid | 0.988 | 1.000 | | | +| Hazelnut | 0.846 | 0.994 | | | +| Leather | 1.000 | 0.999 | | | +| Metal_nut | 0.963 | 0.995 | | | +| Pill | 0.916 | 0.942 | | | +| Screw | 0.521 | 0.839 | | | +| Tile | 0.744 | 1.000 | | | +| Toothbrush | 0.844 | 0.836 | | | +| Transistor | 0.916 | 0.979 | | | +| Wood | 0.978 | 0.992 | | | +| Zipper | 0.878 | 0.951 | | | +| Average | | | | | + + +### Pixel-Level AUC + +| | ResNet-18 | Wide ResNet50 | DeiT | CaiT | +| ---------- | --------- | ------------- | ---- | ---- | +| Bottle | 0.983 | 0.986 | | | +| Cable | 0.954 | 0.972 | | | +| Capsule | 0.985 | 0.990 | | | +| Carpet | 0.983 | 0.991 | | | +| Grid | 0.985 | 0.992 | | | +| Hazelnut | 0.953 | 0.980 | | | +| Leather | 0.996 | 0.996 | | | +| Metal_nut | 0.972 | 0.988 | | | +| Pill | 0.972 | 0.976 | | | +| Screw | 0.920 | 0.966 | | | +| Tile | 0.893 | 0.966 | | | +| Toothbrush | 0.979 | 0.980 | | | +| Transistor | 0.958 | 0.971 | | | +| Wood | 0.956 | 0.941 | | | +| Zipper | 0.965 | 0.985 | | | +| Average | | | | | + + + +### Image F1 Score +| | ResNet-18 | Wide ResNet50 | DeiT | CaiT | +| ---------- | --------- | ------------- | ---- | ---- | +| Bottle | 0.976 | 0.952 | | | +| Cable | 0.851 | 0.918 | | | +| Capsule | 0.937 | 0.952 | | | +| Carpet | 0.955 | 0.983 | | | +| Grid | 0.941 | 0.974 | | | +| Hazelnut | 0.852 | 0.979 | | | +| Leather | 0.995 | 0.974 | | | +| Metal_nut | 0.925 | 0.969 | | | +| Pill | 0.946 | 0.949 | | | +| Screw | 0.853 | 0.893 | | | +| Tile | 0.875 | 0.994 | | | +| Toothbrush | 0.875 | 0.870 | | | +| Transistor | 0.769 | 0.854 | | | +| Wood | 0.983 | 0.968 | | | +| Zipper | 0.921 | 0.975 | | | +| Average | | | | | + +### Pixel F1 Score +| | ResNet-18 | Wide ResNet50 | DeiT | CaiT | +| ---------- | --------- | ------------- | ---- | ---- | +| Bottle | 0.670 | 0.733 | | | +| Cable | 0.547 | 0.564 | | | +| Capsule | 0.472 | 0.490 | | | +| Carpet | 0.573 | 0.598 | | | +| Grid | 0.412 | 0.481 | | | +| Hazelnut | 0.522 | 0.545 | | | +| Leather | 0.560 | 0.576 | | | +| Metal_nut | 0.728 | 0.754 | | | +| Pill | 0.589 | 0.611 | | | +| Screw | 0.061 | 0.660 | | | +| Tile | 0.053 | 0.660 | | | +| Toothbrush | 0.479 | 0.481 | | | +| Transistor | 0.560 | 0.573 | | | +| Wood | 0.557 | 0.488 | | | +| Zipper | 0.492 | 0.621 | | | +| Average | | | | | + + +### Sample Results + +![Sample Result 1](../../../docs/source/images/cflow/results/0.png "Sample Result 1") + +![Sample Result 2](../../../docs/source/images/cflow/results/1.png "Sample Result 2") + +![Sample Result 3](../../../docs/source/images/cflow/results/2.png "Sample Result 3") From 77eda6d73b956e2f236b1339aefeecb2863ed0c0 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sun, 29 May 2022 05:34:17 -0700 Subject: [PATCH 11/26] Modify lighning logger message --- anomalib/models/fastflow/lightning_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anomalib/models/fastflow/lightning_model.py b/anomalib/models/fastflow/lightning_model.py index cdf059c57f..5625623e07 100644 --- a/anomalib/models/fastflow/lightning_model.py +++ b/anomalib/models/fastflow/lightning_model.py @@ -41,7 +41,7 @@ def __init__( hidden_ratio: float = 1.0, ): super().__init__() - logger.info("Initializing Stfpm Lightning model.") + logger.info("Initializing Fastflow Lightning model.") self.model = FastflowModel( input_size=input_size, From d51516f9599caa2770ef247d35c9502897d0be84 Mon Sep 17 00:00:00 2001 From: Samet Akcay Date: Sun, 29 May 2022 05:34:44 -0700 Subject: [PATCH 12/26] Added architecture figure --- docs/source/images/fastflow/architecture.jpg | Bin 0 -> 238240 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/images/fastflow/architecture.jpg diff --git a/docs/source/images/fastflow/architecture.jpg b/docs/source/images/fastflow/architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5c470d5649028418d1503e9a7e3583eace9a237 GIT binary patch literal 238240 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