DualLoop / src / blocking.py
blocking.py
Raw
#
#     dualloop
#
#        File:  blocking.py
#
#     Authors: Deleted for purposes of anonymity
#
#     Proprietor: Deleted for purposes of anonymity --- PROPRIETARY INFORMATION
#
# The software and its source code contain valuable trade secrets and shall be maintained in
# confidence and treated as confidential information. The software may only be used for
# evaluation and/or testing purposes, unless otherwise explicitly stated in the terms of a
# license agreement or nondisclosure agreement with the proprietor of the software.
# Any unauthorized publication, transfer to third parties, or duplication of the object or
# source code---either totally or in part---is strictly prohibited.
#
#     Copyright (c) 2019 Proprietor: Deleted for purposes of anonymity
#     All Rights Reserved.
#
# THE PROPRIETOR DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE AND THE WARRANTY AGAINST LATENT
# DEFECTS, WITH RESPECT TO THE PROGRAM AND ANY ACCOMPANYING DOCUMENTATION.
#
# NO LIABILITY FOR CONSEQUENTIAL DAMAGES:
# IN NO EVENT SHALL THE PROPRIETOR OR ANY OF ITS SUBSIDIARIES BE
# LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES
# FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF INFORMATION, OR
# OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL, INCIDENTAL,
# ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF OR INABILITY
# TO USE THIS PROGRAM, EVEN IF the proprietor HAS BEEN ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGES.
#
# For purposes of anonymity, the identity of the proprietor is not given herewith.
# The identity of the proprietor will be given once the review of the
# conference submission is completed.
#
# THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY.
#

import pandas as pd
import numpy as np
import pickle
import sys

from change_finder import ChangeFinder, SDAR
import matplotlib.pyplot as plt
import ruptures as rpt
import pickle
import seaborn as sns

# Count the arguments
arguments = len(sys.argv) - 1

if arguments < 1:
    print("please specify the data set [conference|ai4eu|nasa|anatomy]")
    exit()
else:
    dataset_name = sys.argv[1]

all_dataset_df = pd.read_csv('./results/' + dataset_name + '/' + dataset_name + '.csv')

source_ontology = pickle.load(open('./results/' + dataset_name + '/' + dataset_name + "_source_ontology.pk", "rb"))
target_ontology = pickle.load(open('./results/' + dataset_name + '/' + dataset_name + "_target_ontology.pk", "rb"))


def calculate_common_words(row):
    x = int(row['x'])
    y = int(row['y'])
    source_class = source_ontology[x]
    target_class = target_ontology[y]

    s1 = source_class.class_name_words
    s2 = target_class.class_name_words
    t = list(set(s1) & set(s2))

    return len(t)


all_dataset_df['num_common_words'] = all_dataset_df.apply(calculate_common_words, axis=1)

from scipy import spatial
import numpy as np


def calculate_cosine_similarity(s_embedding, t_embedding):
    if s_embedding is None or t_embedding is None:
        return None

    if len(s_embedding) == 0 or len(t_embedding) == 0:
        return None

    if np.count_nonzero(s_embedding) == 0 or np.count_nonzero(t_embedding) == 0:
        return None

    similarity = 1 - spatial.distance.cosine(s_embedding, t_embedding)
    return similarity


# calculate the embedding for the selected feature
from sentence_transformers import SentenceTransformer

distilbert_a = SentenceTransformer('distilbert-base-nli-mean-tokens')
distilbert_b = SentenceTransformer('paraphrase-MiniLM-L6-v2')

features = ['class_name_words', 'label_words', 'comment']


def computer_embedding(ontology_list, feature, embedding_feature, model_type):
    for idx in range(len(ontology_list)):
        obj = ontology_list[idx]

        attr = getattr(obj, feature)
        if attr != None and attr != "":
            if model_type == 'a':
                em = distilbert_a.encode(getattr(obj, feature))
            elif model_type == 'b':
                em = distilbert_b.encode(getattr(obj, feature))

            setattr(obj, embedding_feature, em)


def calculate_similarity(row, selected_feature):
    x = int(row['x'])
    y = int(row['y'])
    source_class = source_ontology[x]
    target_class = target_ontology[y]

    if hasattr(source_class, selected_feature) and hasattr(target_class, selected_feature):
        em1 = getattr(source_class, selected_feature)
        em2 = getattr(target_class, selected_feature)

        return calculate_cosine_similarity(em1, em2)
    else:
        return None


for feature in features:
    print(feature)

    # appply bert_a
    embedding_feature = feature + '_embedding_a'
    computer_embedding(target_ontology, feature, embedding_feature, 'a')
    computer_embedding(source_ontology, feature, embedding_feature, 'a')
    # print("compute feature embedding")
    similarity_feature = feature + '_similarity_a'
    all_dataset_df[similarity_feature] = all_dataset_df.apply(calculate_similarity,
                                                              selected_feature=embedding_feature, axis=1)
    print("compute embedding distance a")

    # appply bert_b
    embedding_feature = feature + '_embedding_b'
    computer_embedding(target_ontology, feature, embedding_feature, 'b')
    computer_embedding(source_ontology, feature, embedding_feature, 'b')
    # print("compute feature embedding")
    similarity_feature = feature + '_similarity_b'
    all_dataset_df[similarity_feature] = all_dataset_df.apply(calculate_similarity,
                                                              selected_feature=embedding_feature, axis=1)
    print("compute embedding distance b")

def print_true_match(row):
    x = int(row['x'])
    y = int(row['y'])
    source_class = source_ontology[x]
    target_class = target_ontology[y]
    print(source_class.class_name, ' => ', target_class.class_name, row['x'], row['y'])


def print_included_match(row, f):
    x = int(row['x'])
    y = int(row['y'])
    source_class = source_ontology[x]
    target_class = target_ontology[y]
    print(source_class.class_name, ' => ', target_class.class_name, row[f], row['x'], row['y'])

def apply_blocking_top_k(df, column, k):
    df[column + '_blocked'] = 1

    for x in range(len(source_ontology)):
        x_df = df.query('x==' + str(x) + ' &' + column + '.notnull()')[[column, 'x', 'y', column + '_blocked']]

        if len(x_df) == 0:
            # print(x, '0, ignored')
            continue

        x_df = x_df.sort_values(column, ascending=False)
        df.loc[x_df[:k].index, column + '_blocked'] = 0


columns = [
    'num_common_words',
    'class_name_words_similarity_a',
    'class_name_words_similarity_b',
    'label_words_similarity_a',
    'label_words_similarity_b',
    'comment_similarity_a',
    'comment_similarity_b',
]

# =============================== top-k =========================

MAX_NUM_CANDIDATES = 5
target_size = len(target_ontology)
max_k = target_size
if target_size > MAX_NUM_CANDIDATES:
    max_k = MAX_NUM_CANDIDATES

for blocked_column in columns:
    print("\r\n" + blocked_column)
    blocked_key = blocked_column + '_blocked'
    apply_blocking_top_k(all_dataset_df, blocked_column, max_k)

# show the matches included by different blocking indexes
combination_statement = "";
missing_statement = ""
for blocked_column in columns:
    print("\r\n" + blocked_column)
    blocked_key = blocked_column + '_blocked'

    all_dataset_df.query(blocked_key + '==0 & label==1').apply(print_included_match, f=blocked_column, axis=1)

    total_num = len(all_dataset_df)
    num_after_blocking = len(all_dataset_df.query(blocked_key + '==0'))

    print(num_after_blocking, ' out of ', total_num, num_after_blocking * 100.0 / total_num)

    total_true_matches = len(all_dataset_df[all_dataset_df['label'] == 1])
    captured_true_matches = len(all_dataset_df.query(blocked_key + '==0 & label==1'))

    print(captured_true_matches, ' out of ', total_true_matches, captured_true_matches * 100.0 / total_true_matches)

    if combination_statement == "":
        combination_statement = " (" + blocked_key + '==0)'
        missing_statement = " (" + blocked_key + '==1)'
    else:
        combination_statement = combination_statement + " | (" + blocked_key + '==0)'
        missing_statement = missing_statement + " & (" + blocked_key + '==1)'

all_dataset_df['selected_after_blocking'] = 0

# calculate the union of all selected items by the committee of blockers
print("\r\n============= RESULT OF TOP-K blocking =====================")

# print(combination_statement)
selected_items = all_dataset_df.query(combination_statement)
all_dataset_df.loc[selected_items.index, 'selected_after_blocking'] = 1
total_selected_items = len(selected_items)

query = '(label==1) & (' + combination_statement + ')'
# print(query)
total_selected_matches = len(all_dataset_df.query(query))

query = 'label==1'
# print(query)
total_matches = len(all_dataset_df.query(query))

total_num = len(all_dataset_df)

print(total_selected_items, ' out of ', total_num, total_selected_items * 100.0 / total_num)
print(total_selected_matches, ' out of ', total_matches, total_selected_matches * 100.0 / total_matches)

top_k_recall = total_selected_matches * 100.0 / total_matches
top_k_reduction = 100 - total_selected_items * 100.0 / total_num

print("\r\n=======list of all true matches========")
true_matches = all_dataset_df.query('label==1').apply(print_true_match, axis=1)

print("\r\n***** MISSING ****")

query = '(label==1) & (' + missing_statement + ')'
# print(query)
true_matches = all_dataset_df.query(query).apply(print_true_match, axis=1)

# save the blocked dataframe
query = 'selected_after_blocking==1'
all_dataset_df.query(query).to_csv('./results/' + dataset_name + '/' + dataset_name + '_blocked_top_k' + '.csv',
                                   index=False, header=True)