| from __future__ import absolute_import, division, print_function |
|
|
| import abc |
| import os |
|
|
| import faiss |
| import numpy as np |
| import torch |
| from sklearn.metrics import pairwise_distances |
| from sklearn.random_projection import SparseRandomProjection |
| from torch import nn |
| from torch.nn import functional as F |
| from tqdm import tqdm |
|
|
| from .base_postprocessor import BasePostprocessor |
|
|
|
|
| def embedding_concat(x, y): |
| B, C1, H1, W1 = x.size() |
| _, C2, H2, W2 = y.size() |
| s = int(H1 / H2) |
| x = F.unfold(x, kernel_size=s, dilation=1, stride=s) |
| x = x.view(B, C1, -1, H2, W2) |
| z = torch.zeros(B, C1 + C2, x.size(2), H2, W2) |
| for i in range(x.size(2)): |
| z[:, :, i, :, :] = torch.cat((x[:, :, i, :, :], y), 1) |
| z = z.view(B, -1, H2 * W2) |
| z = F.fold(z, kernel_size=s, output_size=(H1, W1), stride=s) |
| return z |
|
|
|
|
| def reshape_embedding(embedding): |
| embedding_list = [] |
| for k in range(embedding.shape[0]): |
| for i in range(embedding.shape[2]): |
| for j in range(embedding.shape[3]): |
| embedding_list.append(embedding[k, :, i, j]) |
| return embedding_list |
|
|
|
|
| class PatchcorePostprocessor(BasePostprocessor): |
| def __init__(self, config): |
| super(PatchcorePostprocessor, self).__init__(config) |
| self.config = config |
| self.postprocessor_args = config.postprocessor.postprocessor_args |
| self.n_neighbors = config.postprocessor.postprocessor_args.n_neighbors |
| self.feature_mean, self.feature_prec = None, None |
| self.alpha_list = None |
| self.gt_list_px_lvl = [] |
| self.pred_list_px_lvl = [] |
| self.gt_list_img_lvl = [] |
| self.pred_list_img_lvl = [] |
| self.img_path_list = [] |
| self.features = [] |
|
|
| def setup(self, net: nn.Module, id_loader_dict, ood_loader_dict): |
| |
| self.model = net |
| |
| self.model.eval() |
| self.embedding_list = [] |
|
|
| if (self.config.network.load_cached_faiss): |
| path = self.config.output_dir |
| |
| if os.path.isfile(os.path.join(path, 'index.faiss')): |
| self.index = faiss.read_index(os.path.join( |
| path, 'index.faiss')) |
| if torch.cuda.is_available(): |
| res = faiss.StandardGpuResources() |
| self.index = faiss.index_cpu_to_gpu(res, 0, self.index) |
| self.init_results_list() |
| return |
|
|
| |
| train_dataiter = iter(id_loader_dict['train']) |
|
|
| for train_step in tqdm(range(1, |
| len(train_dataiter) + 1), |
| position=0, |
| leave=True): |
| batch = next(train_dataiter) |
| x = batch['data'].cuda() |
| features = self.model.forward(x, return_feature=True) |
| embeddings = [] |
| for feature in features: |
| m = torch.nn.AvgPool2d(9, 1, 1) |
| embeddings.append(m(feature)) |
| embedding = embedding_concat(embeddings[0], embeddings[1]) |
| self.embedding_list.extend(reshape_embedding(np.array(embedding))) |
|
|
| |
| total_embeddings = np.array(self.embedding_list) |
|
|
| |
| print('Random projection') |
| self.randomprojector = SparseRandomProjection( |
| n_components='auto', |
| eps=0.9) |
| self.randomprojector.fit(total_embeddings) |
| |
| print('Coreset Subsampling') |
| selector = kCenterGreedy(total_embeddings, 0, 0) |
| selected_idx = selector.select_batch( |
| model=self.randomprojector, |
| already_selected=[], |
| N=int(total_embeddings.shape[0] * |
| self.postprocessor_args.coreset_sampling_ratio)) |
| self.embedding_coreset = total_embeddings[selected_idx] |
|
|
| print('initial embedding size : ', total_embeddings.shape) |
| print('final embedding size : ', self.embedding_coreset.shape) |
| |
| print('faiss indexing') |
| self.index = faiss.IndexFlatL2(self.embedding_coreset.shape[1]) |
| self.index.add(self.embedding_coreset) |
| if not os.path.isdir(os.path.join('./results/patch/')): |
| os.mkdir('./results/patch/') |
| faiss.write_index(self.index, |
| os.path.join('./results/patch/', 'index.faiss')) |
|
|
| def init_results_list(self): |
| self.gt_list_px_lvl = [] |
| self.pred_list_px_lvl = [] |
| self.gt_list_img_lvl = [] |
| self.pred_list_img_lvl = [] |
|
|
| def postprocess(self, net: nn.Module, data): |
|
|
| self.init_results_list() |
| score_patch = [] |
| |
| for x in data.split(1, dim=0): |
| features = self.model.forward(x, return_feature=True) |
| embeddings = [] |
| for feature in features: |
| m = torch.nn.AvgPool2d(3, 1, 1) |
| embeddings.append(m(feature)) |
| embedding_ = embedding_concat(embeddings[0], embeddings[1]) |
| embedding_test = np.array(reshape_embedding(np.array(embedding_))) |
| score_patches, _ = self.index.search(embedding_test, |
| k=self.n_neighbors) |
|
|
| score_patch.append(score_patches) |
|
|
| N_b = score_patches[np.argmax(score_patches[:, 0])] |
| w = (1 - (np.max(np.exp(N_b)) / np.sum(np.exp(N_b)))) |
| score = w * max(score_patches[:, 0]) |
|
|
| self.pred_list_img_lvl.append(score) |
|
|
| pred = [] |
| for i in self.pred_list_img_lvl: |
| |
| if (i > 6.3): |
| pred.append(torch.tensor(1)) |
| else: |
| pred.append(torch.tensor(-1)) |
| conf = [] |
| for i in score_patch: |
| conf.append(i) |
| conf = torch.tensor(conf, dtype=torch.float32) |
| conf = conf.cuda() |
|
|
| pred_list_img_lvl = [] |
|
|
| for patchscore in np.concatenate([conf.cpu().tolist()]): |
| N_b = patchscore[np.argmax(patchscore[:, 0])] |
| w = (1 - (np.max(np.exp(N_b)) / np.sum(np.exp(N_b)))) |
| score = w * max(patchscore[:, 0]) |
|
|
| pred_list_img_lvl.append(score) |
|
|
| if self.config.evaluator.name == 'patch': |
| return pred, conf |
| else: |
| return pred, -1 * torch.tensor(pred_list_img_lvl).cuda() |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Abstract class for sampling methods. |
| |
| Provides interface to sampling methods that allow same signature for |
| select_batch. Each subclass implements select_batch_ with the desired |
| signature for readability. |
| """ |
|
|
|
|
| class SamplingMethod(object): |
| __metaclass__ = abc.ABCMeta |
|
|
| @abc.abstractmethod |
| def __init__(self, X, y, seed, **kwargs): |
| self.X = X |
| self.y = y |
| self.seed = seed |
|
|
| def flatten_X(self): |
| shape = self.X.shape |
| flat_X = self.X |
| if len(shape) > 2: |
| flat_X = np.reshape(self.X, (shape[0], np.product(shape[1:]))) |
| return flat_X |
|
|
| @abc.abstractmethod |
| def select_batch_(self): |
| return |
|
|
| def select_batch(self, **kwargs): |
| return self.select_batch_(**kwargs) |
|
|
| def to_dict(self): |
| return None |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Returns points that minimizes the maximum distance of any point to a center. |
| |
| Implements the k-Center-Greedy method in |
| Ozan Sener and Silvio Savarese. A Geometric Approach to Active Learning for |
| Convolutional Neural Networks. https://arxiv.org/abs/1708.00489 2017 |
| |
| Distance metric defaults to l2 distance. Features used to calculate distance |
| are either raw features or if a model has transform method then uses the output |
| of model.transform(X). |
| |
| Can be extended to a robust k centers algorithm that ignores a certain number |
| of outlier datapoints. |
| Resulting centers are solution to multiple integer program. |
| """ |
|
|
|
|
| class kCenterGreedy(SamplingMethod): |
| def __init__(self, X, y, seed, metric='euclidean'): |
| self.X = X |
| self.y = y |
| self.flat_X = self.flatten_X() |
| self.name = 'kcenter' |
| self.features = self.flat_X |
| self.metric = metric |
| self.min_distances = None |
| self.n_obs = self.X.shape[0] |
| self.already_selected = [] |
|
|
| def update_distances(self, |
| cluster_centers, |
| only_new=True, |
| reset_dist=False): |
| """Update min distances given cluster centers. |
| |
| Args: |
| cluster_centers: indices of cluster centers |
| only_new: only calculate distance for newly selected points and |
| update min_distances. |
| rest_dist: whether to reset min_distances. |
| """ |
|
|
| if reset_dist: |
| self.min_distances = None |
| if only_new: |
| cluster_centers = [ |
| d for d in cluster_centers if d not in self.already_selected |
| ] |
| if cluster_centers: |
| |
| x = self.features[cluster_centers] |
| dist = pairwise_distances(self.features, x, metric=self.metric) |
|
|
| if self.min_distances is None: |
| self.min_distances = np.min(dist, axis=1).reshape(-1, 1) |
| else: |
| self.min_distances = np.minimum(self.min_distances, dist) |
|
|
| def select_batch_(self, model, already_selected, N, **kwargs): |
| """Diversity promoting active learning method that greedily forms a |
| batch to minimize the maximum distance to a cluster center among all |
| unlabeled datapoints. |
| |
| Args: |
| model: model with scikit-like API with decision_function implemented |
| already_selected: index of datapoints already selected |
| N: batch size |
| |
| Returns: |
| indices of points selected to minimize distance to cluster centers |
| """ |
|
|
| try: |
| |
| |
| print('Getting transformed features...') |
| self.features = model.transform(self.X) |
| print('Calculating distances...') |
| self.update_distances(already_selected, |
| only_new=False, |
| reset_dist=True) |
| except: |
| print('Using flat_X as features.') |
| self.update_distances(already_selected, |
| only_new=True, |
| reset_dist=False) |
|
|
| new_batch = [] |
|
|
| for _ in tqdm(range(N)): |
| if self.already_selected is None: |
| |
| ind = np.random.choice(np.arange(self.n_obs)) |
| else: |
| ind = np.argmax(self.min_distances) |
| |
| |
| assert ind not in already_selected |
|
|
| self.update_distances([ind], only_new=True, reset_dist=False) |
| new_batch.append(ind) |
| print('Maximum distance from cluster centers is %0.2f' % |
| max(self.min_distances)) |
|
|
| self.already_selected = already_selected |
|
|
| return new_batch |
|
|