DualLoop / src / blocking_all_methods.py
blocking_all_methods.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")

import changefinder


def findChangePoints(ts, r, order, smooth):
    '''
       r: Discounting rate
       order: AR model order
       smooth: smoothing window size T
    '''
    cf = changefinder.ChangeFinder(r=r, order=order, smooth=smooth)
    ts_score = [cf.update(p) for p in ts]
    return (ts_score)


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_n(df, column, n):
    df[column + '_blocked'] = 1

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


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


def blocking_per_class_top_x(xdf, ranked_feature, check_point_detection, with_delta, debug_flag):
    # for ranked integer feature, the last increase
    if ranked_feature in ['num_common_words']:
        return len(xdf[xdf[ranked_feature] > 0])

    distances = xdf[ranked_feature].values

    num = len(distances)
    points = []

    if with_delta == True:
        for i in range(num - 1):
            cur_delta = abs(distances[i + 1] - distances[i])
            points.append(cur_delta)
    else:
        points = distances

    top_x = num
    scores = []
    if check_point_detection == "my_top_x":
        lst = np.gradient(np.gradient(points))
        top_x = len(lst) - np.argmax(lst[::-1])
    elif check_point_detection == "rbf":
        model = "rbf"
        algo = rpt.Dynp(model=model).fit(np.array(points))
        scores = algo.predict(n_bkps=1)
        top_x = scores[0] - 1
    elif check_point_detection == "l2":
        model = "l2"
        algo = rpt.Dynp(model=model).fit(np.array(points))
        scores = algo.predict(n_bkps=1)
        top_x = scores[0] - 1
    elif check_point_detection == "l1":
        model = "l1"
        algo = rpt.Dynp(model=model).fit(np.array(points))
        scores = algo.predict(n_bkps=1)
        top_x = scores[0] - 1
    elif check_point_detection == "online1":
        scores = findChangePoints(points, r=0.01, order=3, smooth=5)
        top_x = pd.Series(scores).nlargest(3).index[2]
    elif check_point_detection == "online2":
        cf = ChangeFinder()
        try:
            scores = [cf.update(p) for p in points]
            for idx in range(len(scores)):
                if scores[idx] < 2:
                    top_x = idx + 1
                    break
        except:
            top_x = 0

    if debug_flag == True:
        print(points)
        print(scores)
        print(top_x)

        xdf[:top_x].apply(print_included_match, f=ranked_feature, axis=1)

        plt.figure(figsize=(16, 4))
        plt.plot(distances, color='blue')

        plt.figure(figsize=(16, 4))
        plt.plot(points)

        plt.figure(figsize=(16, 4))
        plt.plot(scores, color='red')

        plt.show()

    return top_x


def apply_blocking_top_x(df, column, algm, max_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)
        top_x = blocking_per_class_top_x(x_df.head(max_k), column, algm, False, False)
        if top_x == 0:
            # print(x, '0, ignored')
            continue

        # update the block_flag
        # print(x, top_x)
        df.loc[x_df[:top_x].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-n =========================
top_n = 10000

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

# 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-N 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_n_recall = total_selected_matches * 100.0 / total_matches
top_n_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_n' + '.csv',
                                   index=False, header=True)

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

MAX_NUM_CANDIDATES = 20
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)

# =============================== top-x =========================

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

changing_point_detection_algorithm = 'my_top_x'  # 'online2' 'rdf', 'l1', 'l2', 'online1', 'online2', 'my_top_x'
print_matches = True

for blocked_column in columns:
    print("\r\n" + blocked_column)
    blocked_key = blocked_column + '_blocked'
    apply_blocking_top_x(all_dataset_df, blocked_column, changing_point_detection_algorithm, 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-X 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_x_recall = total_selected_matches * 100.0 / total_matches
top_x_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_x' + '.csv',
                                   index=False, header=True)

# save the result into a figure
data = [
    ['top-n', 'recall', top_n_recall],
    ['top-n', 'reduction', top_n_reduction],
    ['top-k', 'recall', top_k_recall],
    ['top-k', 'reduction', top_k_reduction],
    ['top-x', 'recall', top_x_recall],
    ['top-x', 'reduction', top_x_reduction],
]

columns = ["blocking methods", "category", "result"]

result_df = pd.DataFrame.from_records(data, columns=columns)

# save the result into a file
result_df.to_csv('./results/' + dataset_name + '/' + dataset_name + '_blocking_result' + '.csv', index=False,
                 header=True)

# Plot
fig, ax1 = plt.subplots(figsize=(10, 6))
g = sns.barplot(x=columns[0], y="result", hue="category", \
                data=result_df, ax=ax1)

ax2 = ax1.twinx()

ax2.set_ylim(ax1.get_ylim())
ax2.set_yticklabels(np.round(ax1.get_yticks(), 1))

ax1.set_ylabel('recall(%)')
ax2.set_ylabel('reduction(%)')

plt.grid(False)
ax1.grid(False)
ax2.grid(False)

plt.savefig('./results/' + dataset_name + '/' + dataset_name + '_blocking_result.png', transparent=True)
plt.close(fig)