diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c18dd8d83ceed1806b50b0aaa46beb7e335fff13 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/scorer.py b/scorer.py new file mode 100644 index 0000000000000000000000000000000000000000..513e3ad362efa9726da42e57c5fcb6d850127e47 --- /dev/null +++ b/scorer.py @@ -0,0 +1,195 @@ +# Compares some results (e.g. output of Tenet) w.r.t a corpus +# with the expected ODRL representations of the corpus +# +# ODRL files in the corpus directory should have the same name as in the +# output directory + +import os +import sys +from rdflib import Graph + +class ODRL: + + def __init__(self): + self.has_perm = False + self.has_obl = False + self.has_proh = False + self.perm = [] + self.obl = [] + self.proh = [] + + """ Parses the content of an ODRL file and adds its contents to the ODRL """ + def parse(self, odrl_fname): + graph = Graph() + graph.parse(odrl_fname) + odrl_ns = "http://www.w3.org/ns/odrl/2/" + ccrel_ns = "http://creativecommons.org/ns#" + perm_node = None + obl_node = None + proh_node = None + actions = {} # action listing for every blank node + + # List actions and modalities + for (src, prop, tgt) in graph: + src, prop, tgt = str(src), str(prop), str(tgt) + if prop == odrl_ns + "permission": + self.has_perm = True + perm_node = tgt + elif prop == odrl_ns + "obligation": + self.has_obl = True + obl_node = tgt + elif prop == odrl_ns + "prohibition": + self.has_proh = True + proh_node = tgt + elif prop == odrl_ns + "action": + if src not in actions: actions[src] = [] + actions[src].append(tgt) + + # Link modalities with actions + for node in actions: + if node == perm_node: mod = self.perm + elif node == obl_node: mod = self.obl + elif node == proh_node: mod = self.proh + else: print("Warning: ill-formed ODRL") + for act in actions[node]: + mod.append(act) + + def actions(self): + acts = [] + for act in self.perm: acts.append(act) + for act in self.obl: acts.append(act) + for act in self.proh: acts.append(act) + return acts + + def __str__(self): + s = "" + if self.has_perm: + s += "Permissions:\n" + for act in self.perm: + s += f"\t{act}\n" + if self.has_obl: + s += "Obligations:\n" + for act in self.obl: + s += f"\t{act}\n" + if self.has_proh: + s += "Prohibitions:\n" + for act in self.proh: + s += f"\t{act}\n" + return s + + +class Scores: + + def __init__(self): + self.scores = {} + + def check_and_add_criterion(self, crit): + if crit not in self.scores: + self.scores[crit] = {"tp": 0, "fp": 0, "fn": 0} + + """ Adds a single measure on the score. + tgt (bool): real value + output (bool): measured value """ + def add_measure(self, crit, tgt, output): + self.check_and_add_criterion(crit) + if tgt and output: + self.scores[crit]["tp"] += 1 + elif not tgt and output: + self.scores[crit]["fp"] += 1 + elif tgt and not output: + self.scores[crit]["fn"] += 1 + + def get_precision(self, crit): + return self.scores[crit]["tp"]/(self.scores[crit]["tp"] + self.scores[crit]["fp"]) + + def get_recall(self, crit): + return self.scores[crit]["tp"]/(self.scores[crit]["tp"] + self.scores[crit]["fn"]) + + def get_total_precision(self): + tot_tp = 0 + tot_fp = 0 + for crit in self.scores: + tot_tp += self.scores[crit]["tp"] + tot_fp += self.scores[crit]["fp"] + return tot_tp/(tot_tp + tot_fp) + + def get_total_recall(self): + tot_tp = 0 + tot_fn = 0 + for crit in self.scores: + tot_tp += self.scores[crit]["tp"] + tot_fn += self.scores[crit]["fn"] + return tot_tp/(tot_tp + tot_fn) + + def __str__(self): + s = "" + for crit in self.scores: + s += f"{crit}:\n" + s += f'\tTP = {self.scores[crit]["tp"]}, FP = {self.scores[crit]["fp"]}, FN = {self.scores[crit]["fn"]}\n' + s += f'\tPrecision = {self.get_precision(crit)}, Recall = {self.get_recall(crit)}\n' + s += f'Total precision = {self.get_total_precision()}\n' + s += f'Total recall = {self.get_total_recall()}\n' + return s + + + +def add_comp_modalities(odrl_target, odrl_output, scores): + scores.add_measure("permission", odrl_target.has_perm, odrl_output.has_perm) + scores.add_measure("obligation", odrl_target.has_obl, odrl_output.has_obl) + scores.add_measure("prohibition", odrl_target.has_proh, odrl_output.has_proh) + +def add_comp_actions(odrl_target, odrl_output, scores): + actions_target = odrl_target.actions() + actions_output = odrl_output.actions() + + for action in actions_output: + scores.add_measure(action, action in actions_target, True) + for action in actions_target: + if action not in actions_output: # Don't reconsider True, True + scores.add_measure(action, True, False) + +def add_comp_global(odrl_target, odrl_output, scores): + same = set(odrl_target.perm) == set(odrl_output.perm) + same = same and set(odrl_target.obl) == set(odrl_output.obl) + same = same and set(odrl_target.proh) == set(odrl_output.proh) + scores.add_measure("global", True, same) + + +""" path_target: path to the ODRL files of the corpus + path_output: path to the ODRL files of the output """ +def main_scorer(path_target, path_output): + + scores_modalities = Scores() + scores_actions = Scores() + scores_global = Scores() + + for fname in os.listdir(path_odrl): + if fname.endswith(".ttl"): + + odrl_target = ODRL() + odrl_target.parse(path_odrl + fname) + odrl_output = ODRL() + odrl_output.parse(path_output + fname) + + add_comp_modalities(odrl_target, odrl_output, scores_modalities) + add_comp_actions(odrl_target, odrl_output, scores_actions) + add_comp_global(odrl_target, odrl_output, scores_global) + + return scores_modalities, scores_actions, scores_global + + + +if __name__ == "__main__": + + if len(sys.argv) < 3: + print(f"Usage: python3 {sys.argv[0]} <path_corpus_odrl> <path_output_odrl") + exit(1) + + path_target = sys.argv[1] + path_output = sys.argv[2] + + scores_modalities, scores_actions, scores_global = main_scorer(path_target, path_output) + + print(f"Modality scores:\n{scores_modalities}") + print(f"Actions scores:\n{scores_actions}") + print(f"Global scores:\n{scores_global}")