# Source code for core.matching.lda

import numpy as np
from core.lda_engine import models

from core.helper.tables import get_database

[docs]def score(paper_vec, author_vec, method):
"""
Scores a paper-author match.

:param paper_vec: the vector to be matched

:param author_vec: the vector to be scored against (usually it is the vector of an author)

:param method: the name of scoring implementation

:return: a scalar measuring the score of the match
"""
def euclidean_distance(p, q):
diff = p - q
sum_of_squares = sum([pow(i, 2) for i in diff])
if sum_of_squares == 0:
return 0
else:
return 1 / sum_of_squares

def js(p, q):

"""Computes the Jensen-Shannon divergence between two probability distributions.

:param p: array-like probability distributions of equal length that sum to 1.

:param q: same as P.

:return: the Jensen-Shannon divergence

Note: from https://stackoverflow.com/questions/15880133/jensen-shannon-divergence
"""
def _kldiv(a, b):
return np.sum([v for v in a * np.log2(a / b) if not np.isnan(v)])

p = np.array(p)
q = np.array(q)

M = 0.5 * (p + q)

return 0.5 * (_kldiv(p, M) + _kldiv(q, M))

def default(p, q):
"""Takes the dot product of the paper's topic vector and the author's vector.

:param p: vector of the paper

:param q: vector of a scholar in the author's lib.

:return: the score
"""
return np.dot(p, q) - 1

methods = {"euclidean": euclidean_distance,
"js": js,
"default": default}

return methods[method](paper_vec, author_vec)

[docs]def match_by_lda(text, model_name, top=50, detailed=True, scoring_impl="default", base=0):
"""Gives the best matching result given a string of raw text.

:param text: The text to be matched.

:param model_name: The name of the LDA model to be used.

:param top: the maximum number of results to be returned.

:param detailed: return a detailed result. It should always be True unless it is used outside the web app.

:param scoring_impl: the scoring implementation to be used.

:param base: The initial value of the vector.

:return: the matched result in dictionary form if detailed=True.
Otherwise it will return a matrix with author id and the score.
"""
print("Matching by LDA")

def update_author_vector(vec, doc_vec):
vec = [i for i in vec]  # weird, why on earth this could be immutable and I have to create a copy?
for topic_id, confidence in zip(doc_vec['topic_id'], doc_vec['confidence']):
vec[topic_id] = vec[topic_id] + confidence
return vec

model = models[model_name]
paper_topics = model.predict(text)
paper_vec = np.array([base for i in range(model.num_topics)])
paper_vec = update_author_vector(paper_vec, paper_topics)
results = []
for author_id_ in model.authors_lib:
results.append([author_id_, score(model.authors_lib[author_id_], paper_vec, scoring_impl)])
results.sort(key=lambda tup: tup, reverse=True)
print("Base:", base)
print("paper_vec: ")
print(paper_vec)
matched_topics = [(model.get_topic_in_string(i), v) for i, v in enumerate(paper_vec) if v > base]
matched_topics.sort(key=lambda tup: tup, reverse=True)
print("matched_topics:")
print(matched_topics)
if detailed:
return detailed_results(results[:top], model_name), matched_topics
else:
return results[:top], matched_topics

[docs]def detailed_results(results, model_name):
"""Retrieves matched author (aka reviewers) details.

:param results: a dictionary generated by match_by_lda with detailed=True

:param model_name: the name of the current model

:return: a dictionary of author details. You'll see an example output when you run on the demo model.

"""

author_, documents_, k_a, d_a = get_database(model_name)
authors = {}
for result in results:
author_id = int(result)
score = result
profile = author_.ix[author_id].to_dict()
profile['id'] = int(profile['id'])
keywords = list(k_a[k_a.authors_id == author_id].keyword.values)
documents_id = d_a[d_a.authors_id == author_id].documents_id.values
documents = []
for document_id in documents_id:
document = documents_.ix[document_id].to_dict()
document['id'] = int(document['id'])
try:
# Optional data fields
document['source_id'] = int(document['source_id'])
except KeyError:
pass
documents.append(document)
author = {'score': score, 'profile': profile, 'keywords': keywords, 'documents': documents}
authors[author_id] = author
if len(authors) == 1:  # For demo
print("Demo: authors dictionary sample.")
print(authors)
# Note that unlike dict in python, json does not allow integer keys
return authors