Spaces:
Sleeping
Sleeping
import math | |
import random | |
import numpy as np | |
from tqdm import tqdm | |
import cv2 | |
from PIL import Image | |
import torch | |
import torch.nn.functional as F | |
# import torchvision.transforms as transforms | |
from itertools import combinations | |
from collections import OrderedDict | |
class MultiModalSubModularExplanation(object): | |
def __init__(self, | |
model, | |
semantic_feature, | |
preproccessing_function, | |
k = 40, | |
lambda1 = 1.0, | |
lambda2 = 1.0, | |
lambda3 = 1.0, | |
lambda4 = 1.0, | |
device = "cuda"): | |
super(MultiModalSubModularExplanation, self).__init__() | |
# Parameters of the submodular | |
self.k = k | |
self.model = model | |
self.semantic_feature = semantic_feature | |
self.preproccessing_function = preproccessing_function | |
self.lambda1 = lambda1 | |
self.lambda2 = lambda2 | |
self.lambda3 = lambda3 | |
self.lambda4 = lambda4 | |
self.device = device | |
def partition_collection(self, image_set): | |
""" | |
Divide m image elements into n sets | |
""" | |
image_set_size = len(image_set) | |
sample_size_per_partition = image_set_size | |
image_set_clone = list(image_set) | |
random.shuffle(image_set_clone) | |
V_partition = [image_set_clone[i: i + sample_size_per_partition] for i in range(0, image_set_size, sample_size_per_partition)] | |
assert len(V_partition[0]) == sample_size_per_partition | |
self.s_size = sample_size_per_partition | |
# assert image_set_size > sample_size_per_partition * self.k # 其实就是 self.n > self.k ? | |
return V_partition | |
def merge_image(self, sub_index_set, partition_image_set): | |
""" | |
merge image | |
""" | |
sub_image_set_ = np.array(partition_image_set)[sub_index_set] | |
image = sub_image_set_.sum(0) | |
return image.astype(np.uint8) | |
# def compute_effectiveness_score(self, features): | |
# """ | |
# Computes Eeffectiveness Score: The point should be distant from all the other elements in the subset. | |
# features: torch.Size(batch, d) | |
# """ | |
# norm_feature = F.normalize(features, p=2, dim=1) | |
# # Consine Similarity | |
# cosine_similarity = torch.mm(norm_feature, norm_feature.t()) | |
# cosine_similarity = torch.clamp(cosine_similarity, min=-1, max=1) | |
# # Normlize 0-1 | |
# cosine_dist = torch.arccos(cosine_similarity) / math.pi | |
# if cosine_dist.shape[0] == 1: | |
# eye = 1 - torch.eye(norm_feature.shape[0], device=self.device) | |
# masked_dist = cosine_dist * eye | |
# e_score = torch.sum(torch.min(masked_dist, dim=1).values) | |
# else: | |
# eye = torch.eye(norm_feature.shape[0], device=self.device) | |
# adjusted_cosine_dist = cosine_dist + eye | |
# e_score = torch.sum(torch.min(adjusted_cosine_dist, dim=1).values) | |
# return e_score # tensor(0.0343, device='cuda:0') | |
# def proccess_compute_effectiveness_score_v1(self, components_image_feature, combination_list): | |
# """ | |
# Compute each S's effectiveness score | |
# """ | |
# e_scores = [] | |
# for sub_index in combination_list: | |
# sub_feature_set = components_image_feature[sub_index] | |
# e_score = self.compute_effectiveness_score(sub_feature_set) | |
# e_scores.append(e_score) | |
# return torch.stack(e_scores) | |
def proccess_compute_confidence_score(self): | |
""" | |
Compute confidence score | |
""" | |
# visual_features = self.model(batch_input_images) | |
# predicted_scores = torch.softmax(visual_features @ self.semantic_feature.T, dim=-1) | |
entropy = - torch.sum(self.predicted_scores * torch.log(self.predicted_scores + 1e-7), dim=1) | |
max_entropy = torch.log(torch.tensor(self.predicted_scores.shape[1])).to(self.device) | |
confidence = 1 - (entropy / max_entropy) | |
return confidence | |
def proccess_compute_effectiveness_score(self, sub_index_sets): | |
""" | |
Compute each S's effectiveness score | |
""" | |
e_scores = [] | |
for sub_index in sub_index_sets: | |
cosine_dist = self.effectiveness_dist[:, np.array(sub_index)] # [len(element) , len(main_set)] | |
cosine_dist = cosine_dist[np.array(sub_index), :] | |
eye = torch.eye(cosine_dist.shape[0], device=self.device) | |
adjusted_cosine_dist = cosine_dist + eye | |
e_score = torch.sum(torch.min(adjusted_cosine_dist, dim=1).values) | |
e_scores.append(e_score) | |
effectiveness_score = torch.stack(e_scores) | |
if len(sub_index_sets[0]) == 1: | |
effectiveness_score = effectiveness_score * 0 | |
return effectiveness_score | |
def proccess_compute_consistency_score(self, batch_input_images): | |
""" | |
Compute each consistency score | |
""" | |
with torch.no_grad(): | |
visual_features = self.model(batch_input_images) | |
self.predicted_scores = torch.softmax(visual_features @ self.semantic_feature.T, dim=-1) | |
consistency_scores = self.predicted_scores[:, self.target_label] | |
return consistency_scores | |
def evaluation_maximun_sample(self, | |
main_set, | |
candidate_set, | |
partition_image_set): | |
""" | |
Given a subset, return a best sample index | |
""" | |
sub_index_sets = [] | |
for candidate_ in candidate_set: | |
sub_index_sets.append( | |
np.concatenate((main_set, np.array([candidate_]))).astype(int)) | |
# merge images / 组合图像 | |
sub_images = torch.stack([ | |
self.preproccessing_function( | |
self.merge_image(sub_index_set, partition_image_set) | |
) for sub_index_set in sub_index_sets]) | |
batch_input_images = sub_images.to(self.device) | |
with torch.no_grad(): | |
# 2. Effectiveness Score | |
score_effectiveness = self.proccess_compute_effectiveness_score(sub_index_sets) | |
# 3. Consistency Score | |
score_consistency = self.proccess_compute_consistency_score(batch_input_images) | |
# 1. Confidence Score | |
score_confidence = self.proccess_compute_confidence_score() | |
# 4. Collaboration Score | |
sub_images_reverse = torch.stack([ | |
self.preproccessing_function( | |
self.org_img - self.merge_image(sub_index_set, partition_image_set) | |
) for sub_index_set in sub_index_sets]) | |
batch_input_images_reverse = sub_images_reverse.to(self.device) | |
score_collaboration = 1 - self.proccess_compute_consistency_score(batch_input_images_reverse) | |
# 1. Confidence Score | |
# score_confidence = self.proccess_compute_confidence_score() | |
# submodular score | |
smdl_score = self.lambda1 * score_confidence + self.lambda2 * score_effectiveness + self.lambda3 * score_consistency + self.lambda4 * score_collaboration | |
# smdl_score = self.lambda2 * score_effectiveness + self.lambda3 * score_consistency + self.lambda4 * score_collaboration | |
arg_max_index = smdl_score.argmax().cpu().item() | |
# if self.lambda1 != 0: | |
self.saved_json_file["confidence_score"].append(score_confidence[arg_max_index].cpu().item()) | |
self.saved_json_file["effectiveness_score"].append(score_effectiveness[arg_max_index].cpu().item()) | |
self.saved_json_file["consistency_score"].append(score_consistency[arg_max_index].cpu().item()) | |
self.saved_json_file["collaboration_score"].append(score_collaboration[arg_max_index].cpu().item()) | |
self.saved_json_file["smdl_score"].append(smdl_score[arg_max_index].cpu().item()) | |
return sub_index_sets[arg_max_index] | |
def save_file_init(self): | |
self.saved_json_file = {} | |
self.saved_json_file["sub-k"] = self.k | |
self.saved_json_file["confidence_score"] = [] | |
self.saved_json_file["effectiveness_score"] = [] | |
self.saved_json_file["consistency_score"] = [] | |
self.saved_json_file["collaboration_score"] = [] | |
self.saved_json_file["smdl_score"] = [] | |
self.saved_json_file["lambda1"] = self.lambda1 | |
self.saved_json_file["lambda2"] = self.lambda2 | |
self.saved_json_file["lambda3"] = self.lambda3 | |
self.saved_json_file["lambda4"] = self.lambda4 | |
def calculate_distance_of_each_element(self, partition_image_set): | |
""" | |
Calculate the similarity of each element, obtain a similarity matrix | |
""" | |
with torch.no_grad(): | |
partition_images = torch.stack([ | |
self.preproccessing_function( | |
partition_image | |
) for partition_image in partition_image_set]).to(self.device) | |
partition_image_features = self.model(partition_images) | |
norm_feature = F.normalize(partition_image_features, p=2, dim=1) | |
# Consine Similarity | |
cosine_similarity = torch.mm(norm_feature, norm_feature.t()) | |
cosine_similarity = torch.clamp(cosine_similarity, min=-1, max=1) | |
# Normlize 0-1 | |
self.effectiveness_dist = torch.arccos(cosine_similarity) / math.pi | |
def get_merge_set(self, partition): | |
""" | |
""" | |
Subset = np.array([]) | |
indexes = np.arange(len(partition)) | |
# First calculate the similarity of each element to facilitate calculation of effectiveness score. | |
self.calculate_distance_of_each_element(partition) | |
self.smdl_score_best = 0 | |
for j in tqdm(range(self.k)): | |
diff = np.setdiff1d(indexes, np.array(Subset)) # in indexes but not in Subset | |
sub_candidate_indexes = diff | |
Subset = self.evaluation_maximun_sample(Subset, sub_candidate_indexes, partition) | |
return Subset | |
def __call__(self, image_set, id = None): | |
""" | |
Compute Source Face Submodular Score | |
@image_set: [mask_image 1, ..., mask_image m] (cv2 format) | |
""" | |
# V_partition = self.partition_collection(image_set) # [ [image1, image2, ...], [image1, image2, ...], ... ] | |
self.save_file_init() | |
self.org_img = np.array(image_set).sum(0).astype(np.uint8) | |
source_image = self.preproccessing_function(self.org_img) | |
self.source_feature = self.model(source_image.unsqueeze(0).to(self.device)) | |
self.target_label = id | |
Subset_merge = np.array(image_set) | |
Submodular_Subset = self.get_merge_set(Subset_merge) # array([17, 42, 49, ...]) | |
submodular_image_set = Subset_merge[Submodular_Subset] # sub_k x (112, 112, 3) | |
submodular_image = submodular_image_set.sum(0).astype(np.uint8) | |
self.saved_json_file["smdl_score_max"] = max(self.saved_json_file["smdl_score"]) | |
self.saved_json_file["smdl_score_max_index"] = self.saved_json_file["smdl_score"].index(self.saved_json_file["smdl_score_max"]) | |
return submodular_image, submodular_image_set, self.saved_json_file |