Spaces:
Build error
Build error
| """Gaussian Kernel Density Estimation.""" | |
| # Copyright (C) 2020 Intel Corporation | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions | |
| # and limitations under the License. | |
| import math | |
| from typing import Optional | |
| import torch | |
| from torch import Tensor | |
| from anomalib.models.components.base import DynamicBufferModule | |
| class GaussianKDE(DynamicBufferModule): | |
| """Gaussian Kernel Density Estimation. | |
| Args: | |
| dataset (Optional[Tensor], optional): Dataset on which to fit the KDE model. Defaults to None. | |
| """ | |
| def __init__(self, dataset: Optional[Tensor] = None): | |
| super().__init__() | |
| if dataset is not None: | |
| self.fit(dataset) | |
| self.register_buffer("bw_transform", Tensor()) | |
| self.register_buffer("dataset", Tensor()) | |
| self.register_buffer("norm", Tensor()) | |
| self.bw_transform = Tensor() | |
| self.dataset = Tensor() | |
| self.norm = Tensor() | |
| def forward(self, features: Tensor) -> Tensor: | |
| """Get the KDE estimates from the feature map. | |
| Args: | |
| features (Tensor): Feature map extracted from the CNN | |
| Returns: KDE Estimates | |
| """ | |
| features = torch.matmul(features, self.bw_transform) | |
| estimate = torch.zeros(features.shape[0]).to(features.device) | |
| for i in range(features.shape[0]): | |
| embedding = ((self.dataset - features[i]) ** 2).sum(dim=1) | |
| embedding = torch.exp(-embedding / 2) * self.norm | |
| estimate[i] = torch.mean(embedding) | |
| return estimate | |
| def fit(self, dataset: Tensor) -> None: | |
| """Fit a KDE model to the input dataset. | |
| Args: | |
| dataset (Tensor): Input dataset. | |
| Returns: | |
| None | |
| """ | |
| num_samples, dimension = dataset.shape | |
| # compute scott's bandwidth factor | |
| factor = num_samples ** (-1 / (dimension + 4)) | |
| cov_mat = self.cov(dataset.T) | |
| inv_cov_mat = torch.linalg.inv(cov_mat) | |
| inv_cov = inv_cov_mat / factor**2 | |
| # transform data to account for bandwidth | |
| bw_transform = torch.linalg.cholesky(inv_cov) | |
| dataset = torch.matmul(dataset, bw_transform) | |
| # | |
| norm = torch.prod(torch.diag(bw_transform)) | |
| norm *= math.pow((2 * math.pi), (-dimension / 2)) | |
| self.bw_transform = bw_transform | |
| self.dataset = dataset | |
| self.norm = norm | |
| def cov(tensor: Tensor) -> Tensor: | |
| """Calculate the unbiased covariance matrix. | |
| Args: | |
| tensor (Tensor): Input tensor from which covariance matrix is computed. | |
| Returns: | |
| Output covariance matrix. | |
| """ | |
| mean = torch.mean(tensor, dim=1) | |
| tensor -= mean[:, None] | |
| cov = torch.matmul(tensor, tensor.T) / (tensor.size(1) - 1) | |
| return cov | |