import os
import argparse
import numpy
import tokenizers
import transformers
import huggingface_hub
import qarac.corpora.BNCorpus
import qarac.models.QaracTrainerModel
import qarac.corpora.CombinedCorpus
import torch
import spacy
import pandas
import qarac.utils.CoreferenceResolver
import nltk.corpus
import difflib
import scipy.stats
import scipy.spatial
import seaborn
import tqdm
import gradio
import boto3
class SequenceCrossEntropyLoss(torch.nn.Module):
def __init__(self):
super(SequenceCrossEntropyLoss,self).__init__()
self.crossentropy = torch.nn.CrossEntropyLoss()
def forward(self,y_pred,y_true):
(batch_size,sequence_length,n_classes) = y_pred.shape
predictions = y_pred.view(-1,n_classes)
labels = y_true.view(-1)
return self.crossentropy(predictions,labels)
class CombinedLoss(torch.nn.Module):
def __init__(self):
super(CombinedLoss,self).__init__()
self.component_losses = (SequenceCrossEntropyLoss(),
torch.nn.MSELoss(),
SequenceCrossEntropyLoss(),
torch.nn.MSELoss())
def forward(self,y_pred,y_true):
return sum((fn(pred,obs)
for (fn,pred,obs) in zip(self.component_losses,
y_pred,
y_true)))
def capitalise(token,i):
return token.text_with_ws.title() if i==0 or token.tag_.startswith('NNP') else token.text_with_ws.lower()
def clean_question(doc):
words = [capitalise(token,i) for (i,token) in enumerate(doc)]
if words[-1]!='?':
words.append('?')
return ''.join(words)
def download_training_data():
if not os.path.exists('corpora'):
os.makedirs('corpora')
s3 = boto3.client('s3',
aws_access_key_id=os.environ['AWS_KEY'],
aws_secret_access_key=os.evviron['AWS_SECRET'])
for obj in s3.list_objects(Bucket='qarac')['Contents']:
filename = obj['Key']
s3.download_file('qarac',filename,'corpora/{}'.format(filename))
def prepare_wiki_qa(filename,outfilename):
data = pandas.read_csv(filename,sep='\t')
data['QNum']=data['QuestionID'].apply(lambda x: int(x[1:]))
nlp = spacy.load('en_core_web_trf')
predictor = qarac.utils.CoreferenceResolver.CoreferenceResolver()
data['Resolved_answer'] = data.groupby('QNum')['Sentence'].transform(predictor)
unique_questions = data.groupby('QNum')['Question'].first()
cleaned_questions = pandas.Series([clean_question(doc)
for doc in nlp.pipe(unique_questions)],
index = unique_questions.index)
for (i,question) in cleaned_questions.items():
data.loc[data['QNum']==i,'Cleaned_question']=question
data[['Cleaned_question','Resolved_answer','Label']].to_csv(outfilename)
def prepare_training_datasets():
wikiqa = pandas.read_csv('corpora/WikiQA.csv')
avicenna = pandas.read_csv('corpora/Avicenna_Train.csv',encoding='iso-8859-1')
snli = pandas.read_csv('corpora/snli_1.0_train.csv')
question_answering = wikiqa.loc[wikiqa['Label']==1,
['Cleaned_question',
'Resolved_answer']].rename(columns={'Cleaned_question':'question',
'Resolved_answer':'answer'})
reasoning = avicenna.loc[avicenna['Syllogistic relation']=='yes',
['Premise 1',
'Premise 2',
'Conclusion']].rename(columns={'Premise 1':'proposition0',
'Premise 2':'proposition1',
'Conclusion':'conclusion'})
consistency = snli.loc[snli['gold_label']!='-',
['sentence1',
'sentence2']].rename(columns={'sentence1':'statement0',
'sentence2':'statement1'})
mapping = {'entailment':1.0,
'neutral':0.0,
'contradiction':-1.0}
consistency['consistency'] = snli.loc[snli['gold_label']!='-',
'gold_label'].apply(lambda x:mapping[x])
all_text = pandas.concat([wikiqa['Resolved_answer'],
avicenna['Premise 1'],
avicenna['Premise 1'],
reasoning['conclusion'],
snli['sentence1'],
snli['sentence2']]).to_frame(name='all_text').reset_index(drop=True)
all_text.to_csv('corpora/all_text.csv')
question_answering.to_csv('corpora/question_answering.csv')
reasoning.to_csv('corpora/reasoning_train.csv')
consistency.to_csv('corpora/consistency.csv')
def train_models(path,progress=gradio.Progress(track_tqdm=True)):
tokenizer = tokenizers.Tokenizer.from_pretrained('roberta-base')
trainer = qarac.models.QaracTrainerModel.QaracTrainerModel('roberta-base',
tokenizer)
trainer.cuda()
loss_fn = CombinedLoss()
loss_fn.cuda()
optimizer = torch.optim.NAdam(trainer.parameters(),lr=5.0e-5)
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer,gamma=0.9)
training_data = qarac.corpora.CombinedCorpus.CombinedCorpus(tokenizer,
all_text='corpora/all_text.csv',
question_answering='corpora/question_answering.csv',
reasoning='corpora/reasoning_train.csv',
consistency='corpora/consistency.csv')
n_batches = len(training_data)
history = {}
for epoch in range(10):
print("Epoch",epoch)
epoch_label = 'Epoch {}'.format(epoch)
epoch_data = {}
for (batch,(X,Y)) in enumerate(tqdm.tqdm(training_data)):
prediction = trainer(X['all_text'],
X['offset_text'],
X['question'],
X['answer'],
X['proposition0'],
X['proposition1'],
X['conclusion_offset'],
X['statement0'],
X['statement1'])
loss = loss_fn(prediction,Y)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if batch % 1024 == 0 or batch == n_batches-1:
epoch_data[batch] = loss.item()
history[epoch_label] = epoch_data
scheduler.step()
huggingface_hub.login(token=os.environ['HUGGINGFACE_TOKEN'])
trainer.question_encoder.push_to_hub('{}/qarac-roberta-question-encoder'.format(path))
trainer.answer_encoder.push_to_hub('{}/qarac-roberta-answer-encoder'.format(path))
trainer.decoder.push_to_hub('{}/qarac-roberta-decoder'.format(path))
return history
def test_encode_decode(path):
encoder = transformers.Transformer.from_pretrained('{}/qarac-roberta-answer-encoder'.format(path))
decoder = transformers.Transformer.from_pretrained('{}/qarac-robeerta-decoder'.format(path))
tokenizer=tokenizers.Tokenizer.from_pretrained('roberta-base')
exclude = tokenizer.encode(' ').ids
analyser = difflib.SequenceMatcher(lambda x: x in exclude)
bnc = nltk.corpus.reader.bnc.BNCCorpusReader('/'.join([os.environ['HOME'],
'BNC',
'Texts']),
fileids=r'[A-K]/\w*/\w*\.xml')
matches = []
batch = []
pad_token = tokenizer.token_to_id('')
for sent in bnc.sents(strip_space=False):
batch.append(tokenizer.encode(''.join(sent)))
if len(batch)==32:
maxlen = max((len(sentence) for sentence in batch))
for sample in batch:
sample.pad(maxlen,pad_id=pad_token)
input_ids = torch.tensor([sample.ids for sample in batch])
attention_mask = torch.not_equal(input_ids,pad_token)
vectors = encoder(input_ids,
attention_mask)
decoded = decoder.generate(vector=vectors)
for (s1,s2) in zip(batch,decoded):
analyser.set_seqs(s1.ids, s2)
matches.append(analyser.ratio())
batch = []
if len(batch)!=0:
maxlen = max((len(sentence) for sentence in batch))
for sample in batch:
sample.pad(maxlen,pad_id=pad_token)
input_ids = torch.tensor([sample.ids for sample in batch])
attention_mask = torch.not_equal(input_ids, pad_token)
vectors = encoder(input_ids,
attention_mask)
decoded = decoder.generate(vector=vectors)
for (s1,s2) in zip(batch,decoded):
analyser.set_seqs(s1.ids, s2)
matches.append(analyser.ratio())
matches = numpy.array(matches)
print("Accuracy: mean = {0}, sd = {1}".format(matches.mean(),
matches.sd()))
(alpha,beta,loc,scale)=scipy.stats.beta.fit(matches,floc=0.0,fscale=1.0)
print("Beta distribution parameters alpha = {0}, beta = {1}".format(alpha,beta))
(hist,bins) = numpy.histogram(matches,bins='fd')
with pandas.option_context('plotting.backend','matploblib.backends.backend_svg') as options:
axes = pandas.Series(hist,index=(bins[1:]+bins[:-1]/2)).plot.bar()
axes.get_figure().savefig('encode_decode_histogram.svg')
percent = numpy.linspace(0.0,1.0,101)
percentiles = numpy.quantile(matches,percent)
with pandas.option_context('plotting.backend','matplotlib.backends.backend_svg') as options:
axes = pandas.Series(percentiles, index=percent).plot.bar()
axes.get_figure().savefig('encode_decode_percentile.svg')
def test_question_answering(path):
question_encoder = transformers.Transformer.from_pretrained('{}/qarac-roberta-question-encoder'.format(path))
answer_encoder = transformers.Transformer.from_pretrained('{}/qarac-roberta-answer-encoder'.format(path))
tokenizer = tokenizers.Tokenizer.from_pretrained('roberta-base')
data = pandas.read_csv('WikiQA.tsv',sep='\t')
data['QNum']=data['QuestionID'].apply(lambda x: int(x[1:]))
nlp = spacy.load('en_core_web_trf')
predictor = qarac.utils.CoreferenceResolver.CoreferenceResolver()
data['Resolved_answer'] = data.groupby('QNum')['Sentence'].transform(predictor)
unique_questions = data.groupby('QNum')['Question'].first()
cleaned_questions = pandas.Series([clean_question(doc)
for doc in nlp.pipe(unique_questions)],
index = unique_questions.index)
def tokenize(column):
return tokenizer.encode_batch(column.apply(lambda x:tokenizers.TextInputSequence(x)),
add_special_tokens=False)
questions = tokenize(cleaned_questions)
maxlen=max((len(question) for question in questions))
pad_token = tokenizer.token_to_id('')
for question in questions:
question.pad(maxlen,pad_id=pad_token)
question_ids = torch.tensor([question.ids
for question in questions])
attention_mask = torch.not_equal(question_ids,
pad_token)
q_vectors = question_encoder(question_ids,
attention_mask=attention_mask).numpy()
answers = tokenize(data['Resolved_answer'])
maxlen = max((len(answer) for answer in answers))
for answer in answers:
answer.pad(maxlen,pad_id=pad_token)
answer_ids = torch.tensor([answer.ids
for answer in answers])
attention_mask = torch.not_equal(answer_ids,
pad_token)
answer_lookup = scipy.spatial.KDTree(answer_encoder(answer_ids,
attention_mask=attention_mask).numpy())
n_correct = 0
all_distances = 0.0
correct_distances = 0.0
wrong_distances = 0.0
all_sq = 0.0
correct_sq = 0.0
wrong_sq = 0.0
for (i,qv) in enumerate(q_vectors):
(d,row) = answer_lookup.query(qv)
dsq=d**2.0
correct = (row['QNum']==i and row['Label']==1)
all_distances+=d
all_sq+=dsq
if correct:
n_correct+=1
correct_distances+=d
correct_sq+=dsq
else:
wrong_distances+=d
wrong_sq+=dsq
N = cleaned_questions.shape[0]
print("{0} questions, {1} possible answers, {2} correct answers".format(N,
data.shape[0],
n_correct))
accuracy = n_correct/N
baseline = N/data.shape[0]
kappa = 1.0 - ((1.0-accuracy)/(1.0-baseline))
print(("Accuracy: {0}, Baseline {1}, kappa{2} ".format(accuracy,baseline,kappa)))
mean_dist =all_distances/N
mean_sq = all_sq/N
all_sd = numpy.sqrt(mean_sq-(mean_dist**2.0))
print("Question-answer distances")
print("All: mean {0}, sd {1}".format(mean_dist,all_sd))
correct_mean = correct_distances/n_correct
correct_meansq = correct_sq/n_correct
correct_sd = numpy.sqrt(correct_meansq - (correct_mean**2.0))
print("Correct: mean {0}, sd {1}".format(correct_mean,correct_sd))
wrong_mean = wrong_distances/(N-n_correct)
wrong_meansq = wrong_sq/(N-n_correct)
wrong_sd = numpy.sqrt(wrong_meansq - (wrong_mean**2.0))
print("Wrong: mean {0}, sd {1}".format(wrong_mean,wrong_sd))
def test_reasoning(path):
encoder = transformers.Transformer.from_pretrained('{}/qarac-roberta-answer-encoder'.format(path))
decoder = transformers.Transformer.from_pretrained('{}/qarac-robeerta-decoder'.format(path))
tokenizer=tokenizers.Tokenizer.from_pretrained('roberta-base')
exclude = tokenizer.encode(' ').ids
analyser = difflib.SequenceMatcher(lambda x: x in exclude)
data = pandas.read_csv('corpora/Avicenna_Test.csv',encoding='iso-8859-1')
data = data.loc[data['Syllogistic relation']=='yes']
def tokenize(column):
return tokenizer.encode_batch(column.apply(lambda x:tokenizers.TextInputSequence(x)),
add_special_tokens=False)
p0 = tokenize(data['Premise 1'])
p1 = tokenize(data['Premise 2'])
c = tokenize(data['Conclusion'])
p0_batch = []
p1_batch = []
c_batch = []
n=0
pad_token = tokenizer.token_to_id('')
matches=[]
for (p0_sample,p1_sample,c_sample) in zip(p0,p1,c):
p0_batch.append(p0_sample)
p1_batch.append(p1_sample)
c_batch.append(c_sample)
n+=1
if n==32:
maxlen=max((len(sample for sample in p0_batch)))
for sample in p0_batch:
sample.pad(maxlen,pad_token)
p0_in = torch.tensor([sample.ids for sample in p0.batch])
p0_attn = torch.not_equal(p0_in,
pad_token)
maxlen=max((len(sample for sample in p1_batch)))
for sample in p1_batch:
sample.pad(maxlen,pad_token)
p1_in = torch.tensor([sample.ids for sample in p1.batch])
p1_attn = torch.not_equal(p0_in,
pad_token)
predictions = decoder.generate(vector=(encoder(p0_in,
attention_mask=p0_attn)
+encoder(p1_in,
attention_mask=p1_attn)))
for (s1,s2) in zip(c_batch,predictions):
analyser.set_seqs(s1.ids, s2)
matches.append(analyser.ratio())
n=0
p0_batch=[]
p1_batch=[]
c_batch=[]
if n!=0:
maxlen=max((len(sample for sample in p0_batch)))
for sample in p0_batch:
sample.pad(maxlen,pad_token)
p0_in = torch.tensor([sample.ids for sample in p0.batch])
p0_attn = torch.not_equal(p0_in,
pad_token)
maxlen=max((len(sample for sample in p1_batch)))
for sample in p1_batch:
sample.pad(maxlen,pad_token)
p1_in = torch.tensor([sample.ids for sample in p1.batch])
p1_attn = torch.not_equal(p0_in,
pad_token)
predictions = decoder.generate(vector=(encoder(p0_in,
attention_mask=p0_attn)
+encoder(p1_in,
attention_mask=p1_attn)))
for (s1,s2) in zip(c_batch,predictions):
analyser.set_seqs(s1.ids, s2)
matches.append(analyser.ratio())
matches = numpy.array(matches)
print("Accuracy: mean = {0}, sd = {1}".format(matches.mean(),
matches.sd()))
(alpha,beta,loc,scale)=scipy.stats.beta.fit(matches,floc=0.0,fscale=1.0)
print("Beta distribution parameters alpha = {0}, beta = {1}".format(alpha,beta))
(hist,bins) = numpy.histogram(matches,bins='fd')
with pandas.option_context('plotting.backend','matploblib.backends.backend_svg') as options:
axes = pandas.Series(hist,index=(bins[1:]+bins[:-1]/2)).plot.bar()
axes.get_figure().savefig('reasoning_histogram.svg')
percent = numpy.linspace(0.0,1.0,101)
percentiles = numpy.quantile(matches,percent)
with pandas.option_context('plotting.backend','matplotlib.backends.backend_svg') as options:
axes = pandas.Series(percentiles, index=percent).plot.bar()
axes.get_figure().savefig('reasoning_percentile.svg')
def test_consistency(path):
encoder = transformers.Transformer.from_pretrained('{}/qarac-roberta-answer-encoder'.format(path))
tokenizer = tokenizer=tokenizers.Tokenizer.from_pretrained('roberta-base')
data = pandas.read_csv('corpora/snli_1.0_test.csv')
data = data.loc[data['gold_label']!='-']
pad_token=tokenizer.token_to_id('')
def tokenize(column):
return tokenizer.encode_batch(column.apply(lambda x:tokenizers.TextInputSequence(x)),
add_special_tokens=False)
s0 =tokenize(data['sentence1'])
s1 = tokenize(data['sentence1'])
maxlen = max((len(sentence for sentence in s0)))
for sentence in s0:
sentence.pad(maxlen,pad_id=pad_token)
s0_in = torch.tensor([sentence.ids for sentence in s0])
s0_attn = torch.not_equal(s0_in,
pad_token)
maxlen = max((len(sentence for sentence in s1)))
for sentence in s1:
sentence.pad(maxlen,pad_id=pad_token)
s1_in = torch.tensor([sentence.ids for sentence in s1])
s1_attn = torch.not_equal(s1_in,
pad_token)
s0_vec = encoder(s0_in,attention_mask=s0_attn)
s1_vec = encoder(s1_in,attention_mask=s1_attn)
cosine = torch.nn.CosineSimilarity(dim=2,eps=1.0e-12)
consistency = cosine(s0_vec,s1_vec).numpy()
results = pandas.DataFrame({'label':data['gold_label'],
'score':consistency})
third = 1.0/3.0
def predicted_labels(x):
return 'entailment' if x>third else 'contradiction' if x<-third else 'neutral'
results['prediction'] = results['score'].apply(predicted_labels)
confusion=results.groupby('label')['prediction'].value_counts().fillna(0)
seaborn.heatmap(confusion).save('consistency_confusion_matrix.svg')
correct = pandas.Series({label:confusion[label,label]
for label in confusion.index})
print("Accuracy: {}".format(correct.sum()/data.shape[0]))
print("Precision")
print(correct/confusion.sum(axis='columns'))
print("Recall")
print(correct/confusion.sum(axis='rows'))
def stats(group):
(alpha,beta,loc,scale) = scipy.stats.beta.fit(group)
mean = group.mean()
sd = group.std()
return pandas.Series({'mean':mean,
'sd':sd,
'min':loc,
'max':loc+scale,
'alpha':alpha,
'beta':beta})
print(results.groupby('label')['score'].apply(stats))
quartiles = numpy.quantile(consistency,[0.0,0.25,0.5,0.75,1.0])
IQR = quartiles[3]-quartiles[1]
bin_width = 2.0*IQR/(data.shape[0]**1.5)
n_bins = int((quartiles[4]-quartiles[0])/bin_width)
bins = numpy.linspace(quartiles[0],quartiles[4],n_bins)
def hist(col):
(result,_) = numpy.histogram(col,bins)
return result
histograms = results.groupby('label')['score'].apply(hist)
histograms.coluumns = (bins[1:]+bins[:-1])/2
with pandas.option_context('plotting.backend','matploblib.backends.backend_svg') as options:
axes=histograms.T.plot.bar(stacked=True)
axes.get_figure().savefig('consistency_histograms.svg')
percent = numpy.linspace(0.0,1.0,101)
percentiles = results.groupby('label')['score'].apply(lambda x: numpy.percentile(x,percent))
with pandas.option_context('plotting.backend','matploblib.backends.backend_svg') as options:
axes=percentiles.T.plot.line()
axes.get_figure().savefig('consistency_percentiles.svg')
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='QARAC',
description='Experimental NLP system, aimed at improving factual accuracy')
parser.add_argument('task')
parser.add_argument('-f','--filename')
parser.add_argument('-t','--training-task')
parser.add_argument('-o','--outputfile')
args = parser.parse_args()
if args.task == 'prepare_wiki_qa':
prepare_wiki_qa(args.filename,args.outputfile)
elif args.task == 'prepare_training_datasets':
prepare_training_datasets()
elif args.task == 'train_models':
train_models(args.filename)
elif args.task == 'test_encode_decode':
test_encode_decode(args.filename)
elif args.task== 'test_question_answering':
test_question_answering(args.filename)
elif args.task=="test_reasoning":
test_reasoning(args.filename)
elif args.task=='test_consistency':
test_consistency(args.filename)