Source code for revelionn.utils.explanation
import os
import subprocess
import pkg_resources
import torch
from revelionn.mapping_nets.simultaneous_mapping_net import SimultaneousMappingNet
[docs]
def extract_concepts_from_img(main_module, mapping_module, img, transformation):
"""
Extracts a set of concepts present in a given image.
Parameters
----------
main_module : MainModelProcessing
Class for training, evaluation and processing the main network model.
mapping_module : MappingModelProcessing
Class for training, evaluation and processing the mapping network model.
img : PIL.Image
Class that represents a PIL image.
transformation : torchvision.transforms
A transform to apply to the image.
Returns
-------
main_concept : list[str]
Target concept extracted by the convolutional network.
extracted_concepts : list[str]
Concepts relevant to the target concept, which are extracted by the mapping network.
mapping_probabilities : list[float]
The probabilities, obtained from the output of the sigmoid, of each of the extracted concepts relevant to the
target concept.
"""
device = main_module.get_device()
image = transformation(img)
main_net = main_module.get_main_net()
mapping_net = mapping_module.get_mapping_net()
main_concept = []
extracted_concepts = []
mapping_probabilities = []
with torch.no_grad():
main_net.eval()
mapping_net.eval()
image = image.to(device)
main_class_labels = main_module.get_class_labels()
output = main_net(image.unsqueeze(0))
if output > 0.5:
main_concept.append(main_class_labels[1])
else:
main_concept.append(main_class_labels[0])
mapping_class_labels = mapping_module.get_class_labels()
mapping_output = mapping_net(mapping_module.get_activation_extractor().get_activations(1))
if isinstance(mapping_net, SimultaneousMappingNet):
for i in range(len(mapping_output)):
if mapping_output[i] > 0.5:
extracted_concepts.append(mapping_class_labels[i])
mapping_probabilities.append(mapping_output[i].cpu().detach().numpy()[0][0])
else:
extracted_concepts.append(f'Not{mapping_class_labels[i]}')
mapping_probabilities.append(1 - mapping_output[i].cpu().detach().numpy()[0][0])
else:
if mapping_output > 0.5:
extracted_concepts.append(mapping_class_labels[0])
mapping_probabilities.append(mapping_output.cpu().detach().numpy()[0][0])
else:
extracted_concepts.append(f'Not{mapping_class_labels[0]}')
mapping_probabilities.append(1 - mapping_output.cpu().detach().numpy()[0][0])
torch.cuda.empty_cache()
return main_concept, extracted_concepts, mapping_probabilities
[docs]
def to_main_observation(concept):
"""
Formats a string from the name of the target concept to be parsed by the justifier.
Parameters
----------
concept : str
Name of the target concept.
Returns
-------
str
String from the name of the target concept to be parsed by the justifier.
"""
return f'__input__ Type: {concept}\n'
[docs]
def to_mapping_observation(concept, probability):
"""
Formats a string from the name of the concept relevant to the target concept, which will be parsed by the justifier.
Parameters
----------
concept : str
Name of the concept relevant to the target concept.
probability : float
The probability of the concept obtained at the output of the sigmoid.
Returns
-------
str
String from the name of the concept relevant to the target concept, which will be parsed by the justifier.
"""
return f'__input__ Type: {concept}, {str(probability)}\n'
def form_observations(observations_filepath, concepts_map, target_concept, extracted_concepts, mapping_probabilities):
with open(observations_filepath, 'w') as observations_file:
observations_file.write(to_main_observation(concepts_map[target_concept]))
for i in range(len(extracted_concepts)):
concept = extracted_concepts[i]
probability = mapping_probabilities[i]
if concept.startswith('Not'):
concept = concept[3:]
observations_file.write(to_mapping_observation(f'not {concepts_map[concept]}', probability))
else:
observations_file.write(to_mapping_observation(concepts_map[concept], probability))
observations_file.close()
[docs]
def explain_target_concept(extracted_concepts, mapping_probabilities, concepts_map, target_concept, ontology_filepath,
path_to_temp_files):
"""
Parameters
----------
extracted_concepts : list[str]
Concepts relevant to the target concept, which are extracted by the mapping network.
mapping_probabilities : list[float]
The probabilities, obtained from the output of the sigmoid, of each of the extracted concepts relevant to the
target concept.
concepts_map : dict
Dictionary whose keys are the names of the attributes of the dataset, and the values are the corresponding
concepts of the ontology.
target_concept : str
The concept of ontology, which should be obtained by ontological inference from the extracted concepts.
ontology_filepath : str
Path to the OWL ontology file.
path_to_temp_files
Temporary files directory for storing observations and explanations.
Returns
-------
justifications : str
A set of obtained justifications of the target class.
"""
observations_filepath = os.path.join(path_to_temp_files, 'observations.txt')
form_observations(observations_filepath, concepts_map, target_concept, extracted_concepts, mapping_probabilities)
justifications_filepath = os.path.join(path_to_temp_files, 'justifications.txt')
jar_filepath = pkg_resources.resource_filename(__name__, 'onto_justify.jar')
subprocess.call(["java", "-Dsun.stdout.encoding=UTF-8", "-Dsun.err.encoding=UTF-8", "-jar",
jar_filepath, ontology_filepath, observations_filepath, justifications_filepath])
with open(justifications_filepath, 'r') as f:
justifications = f.read()
return justifications