mirror of
https://github.com/wassname/Castor.git
synced 2026-08-28 12:42:36 +08:00
216 lines
8.5 KiB
Python
216 lines
8.5 KiB
Python
# module to compute various external features for the sm cnn model.
|
|
# TODO: add more external features like:
|
|
# word mover distance, cosine sim in tf.idf space, cosine sim in word embedding space
|
|
# overlap based on parts of speech: noun, verb, adj (POS tag)
|
|
# word embedding cosine sim based on part of speech: noun, verb, adj
|
|
import sys
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
import string
|
|
from collections import defaultdict
|
|
|
|
import numpy as np
|
|
|
|
import nltk
|
|
nltk.download('stopwords', quiet=True)
|
|
|
|
from nltk.stem.porter import PorterStemmer
|
|
from nltk.corpus import stopwords
|
|
|
|
def stopped(sentences):
|
|
"""
|
|
remove stop words from given sentences (questions|answers)
|
|
"""
|
|
stoplist = set(stopwords.words('english'))
|
|
#stoplist.update(set(string.punctuation))
|
|
def stop(sentence):
|
|
return ' '.join([word for word in sentence.split() if word not in stoplist])
|
|
return [stop(sentence) for sentence in sentences]
|
|
|
|
def stemmed(sentences):
|
|
"""
|
|
reduce sentence terms to stemmed representations
|
|
"""
|
|
stemmer = PorterStemmer()
|
|
def stem(sentence):
|
|
return ' '.join([stemmer.stem(word) for word in sentence.split()])
|
|
return [stem(sentence) for sentence in sentences]
|
|
|
|
def get_qadata_only_idf(all_data):
|
|
"""
|
|
returns idf weights computed over all question answer pairs in the dataset
|
|
"""
|
|
if not type(all_data) is list:
|
|
all_data = list(all_data)
|
|
term_idfs = defaultdict(float)
|
|
for doc in all_data:
|
|
for term in list(set(doc.split())):
|
|
term_idfs[term] += 1.0
|
|
N = len(all_data)
|
|
for term, n_t in term_idfs.items():
|
|
term_idfs[term] = np.log(N/(1+n_t))
|
|
return term_idfs
|
|
|
|
def get_source_corpus_idf(all_data, path_to_index):
|
|
"""
|
|
fetches idf weights from source corpus (disks1-5+aquaint|wikipedia) index, for all the qa pairs
|
|
"""
|
|
# first run maven to build ../idf_baseline/FetchTermIDF
|
|
maven_cmd = "mvn -f ../idf_baseline/pom.xml clean package appassembler:assemble"
|
|
pargs = shlex.split(maven_cmd)
|
|
p = subprocess.Popen(pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, \
|
|
bufsize=1, universal_newlines=True)
|
|
pout, perr = p.communicate()
|
|
# if build failure, exit with message
|
|
if "BUILD FAILURE" in pout or "BUILD FAILURE" in perr:
|
|
print("\nERROR: Could not build ../idf_baseline/FetchTermIDF. Fix build errors before proceeding")
|
|
print("$ cd ../idf_baseline")
|
|
print("$ mvn clean package appassembler:assemble")
|
|
sys.exit(0)
|
|
|
|
if not type(all_data) is list:
|
|
all_data = list(all_data)
|
|
term_idfs = defaultdict(float)
|
|
all_terms = set([term for doc in all_data for term in doc.split()])
|
|
with open('dataset.vocab', 'w') as vf:
|
|
for term in list(all_terms):
|
|
print(term, file=vf)
|
|
|
|
fetchIDF_cmd = \
|
|
"sh ../idf_baseline/target/appassembler/bin/FetchTermIDF -index {} -vocabFile {}".\
|
|
format(path_to_index, 'dataset.vocab')
|
|
pargs = shlex.split(fetchIDF_cmd)
|
|
p = subprocess.Popen(pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, \
|
|
bufsize=1, universal_newlines=True)
|
|
pout, perr = p.communicate()
|
|
|
|
lines = str(pout).split('\n')
|
|
for line in lines:
|
|
if not line:
|
|
continue
|
|
fields = line.strip().split("\t")
|
|
term, weight = fields[0], fields[-1]
|
|
term_idfs[term] = float(weight)
|
|
|
|
for line in str(perr).split('\n'):
|
|
print('Warning: '+line)
|
|
return term_idfs
|
|
|
|
def compute_overlap(questions, answers):
|
|
"""
|
|
returns simple overlap between document pairs
|
|
"""
|
|
overlap_scores = []
|
|
for q, a in zip(questions, answers):
|
|
q_terms = set(q.split())
|
|
a_terms = set(a.split())
|
|
common_terms = q_terms.intersection(a_terms)
|
|
overlap = float(len(common_terms))/(len(q_terms) + len(a_terms))
|
|
overlap_scores.append(overlap)
|
|
return np.array(overlap_scores)
|
|
|
|
def compute_idf_weighted_overlap(questions, answers, idf_weights):
|
|
"""
|
|
returns idf weighted overlap
|
|
"""
|
|
overlap_scores = []
|
|
for q, a in zip(questions, answers):
|
|
q_terms = set(q.split())
|
|
a_terms = set(a.split())
|
|
common_terms = q_terms.intersection(a_terms)
|
|
idf_weighted_overlap = np.sum([idf_weights[term] for term in list(common_terms)])
|
|
idf_weighted_overlap /= (len(q_terms) + len(a_terms))
|
|
overlap_scores.append(idf_weighted_overlap)
|
|
return np.array(overlap_scores)
|
|
|
|
|
|
def set_external_features_as_per_paper(trainer, corpus_index=None):
|
|
"""
|
|
computes external features as per the paper AND saves them into trainer
|
|
"""
|
|
all_questions, all_answers = [], []
|
|
for split in trainer.data_splits.keys():
|
|
questions, answers, labels, max_q_len, max_a_len, default_ext_feats = \
|
|
trainer.data_splits[split]
|
|
all_questions.extend(questions)
|
|
all_answers.extend(answers)
|
|
|
|
all_data = set(all_questions + all_answers)
|
|
print('corpus_index', corpus_index)
|
|
idf_weights = get_qadata_only_idf(list(all_data)) if not corpus_index else \
|
|
get_source_corpus_idf(list(all_data), corpus_index)
|
|
|
|
external_features = {}
|
|
|
|
# NOTE: expected external features as per paper are
|
|
# 1. overlap(q, a),
|
|
# 2. idf_overlap(q, a),
|
|
# 3. overlap(stopped(q), stopped(a)),
|
|
# 4. idf_over(stopped(q), stopped(a))
|
|
|
|
for split in trainer.data_splits.keys():
|
|
questions, answers, labels, max_q_len, max_a_len, default_ext_feats = \
|
|
trainer.data_splits[split]
|
|
|
|
overlap = compute_overlap(questions, answers)
|
|
idf_weighted_overlap = compute_idf_weighted_overlap(questions, answers, idf_weights)
|
|
overlap_no_stopwords =\
|
|
compute_overlap(stopped(questions), stopped(answers))
|
|
idf_weighted_overlap_no_stopwords =\
|
|
compute_idf_weighted_overlap(stopped(questions), stopped(answers), idf_weights)
|
|
ext_feats = [np.array(feats) for feats in zip(overlap, idf_weighted_overlap,\
|
|
overlap_no_stopwords, idf_weighted_overlap_no_stopwords)]
|
|
trainer.data_splits[split][-1] = ext_feats
|
|
external_features[split] = ext_feats
|
|
return external_features
|
|
|
|
|
|
def set_external_features_as_per_paper_and_stem(trainer, corpus_index=None):
|
|
"""
|
|
computes external features as per the paper but performs stemming before computing IDF.
|
|
features are saved into the trainer.data_splits
|
|
"""
|
|
all_questions, all_answers = [], []
|
|
for split in trainer.data_splits.keys():
|
|
questions, answers, labels, max_q_len, max_a_len, default_ext_feats = \
|
|
trainer.data_splits[split]
|
|
all_questions.extend(questions)
|
|
all_answers.extend(answers)
|
|
|
|
all_data = set(all_questions + all_answers)
|
|
|
|
# stem all words except stopwords to compute idf (required for feature number 2.)
|
|
stoplist = set(stopwords.words('english'))
|
|
stemmer = PorterStemmer()
|
|
def stem_non_stop_words(sentence):
|
|
return ' '.join([stemmer.stem(word) if word not in stoplist else word \
|
|
for word in sentence.split()])
|
|
all_but_stopwords_stemmed = [stem_non_stop_words(sentence) for sentence in list(all_data)]
|
|
idf_weights = get_qadata_only_idf(all_but_stopwords_stemmed) if not corpus_index else \
|
|
get_source_corpus_idf(all_but_stopwords_stemmed, corpus_index)
|
|
|
|
external_features = {}
|
|
|
|
for split in trainer.data_splits.keys():
|
|
questions, answers, labels, max_q_len, max_a_len, default_ext_feats = \
|
|
trainer.data_splits[split]
|
|
|
|
que_stem_all_but_stopwords = [stem_non_stop_words(que) for que in questions]
|
|
ans_stem_all_but_stopwords = [stem_non_stop_words(ans) for ans in answers]
|
|
|
|
overlap = compute_overlap(que_stem_all_but_stopwords, ans_stem_all_but_stopwords)
|
|
idf_weighted_overlap = compute_idf_weighted_overlap(que_stem_all_but_stopwords,\
|
|
ans_stem_all_but_stopwords, idf_weights)
|
|
|
|
que_stopped_stemmed = stemmed(stopped(questions))
|
|
ans_stopped_stemmed = stemmed(stopped(answers))
|
|
|
|
overlap_no_stopwords = compute_overlap(que_stopped_stemmed, ans_stopped_stemmed)
|
|
idf_weighted_overlap_no_stopwords =\
|
|
compute_idf_weighted_overlap(que_stopped_stemmed, ans_stopped_stemmed, idf_weights)
|
|
ext_feats = [np.array(feats) for feats in zip(overlap, idf_weighted_overlap,\
|
|
overlap_no_stopwords, idf_weighted_overlap_no_stopwords)]
|
|
trainer.data_splits[split][-1] = ext_feats
|
|
external_features[split] = ext_feats
|
|
return external_features |