Veblen34's picture
Upload folder using huggingface_hub
cd6775d verified
Raw
History Blame Contribute Delete
11.4 kB
# Copyright (c) 2019-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
#
from logging import getLogger
import os
import copy
import time
import json
from collections import OrderedDict
import numpy as np
import torch
from torch import nn
import torch.nn.functional as F
from scipy.stats import spearmanr, pearsonr
from sklearn.metrics import f1_score, matthews_corrcoef
from ..optim import get_optimizer
from ..utils import concat_batches, truncate, to_cuda
from ..data.dataset import Dataset, ParallelDataset
from ..data.loader import load_binarized, set_dico_parameters
N_CLASSES = {
'MNLI-m': 3,
'MNLI-mm': 3,
'QQP': 2,
'QNLI': 2,
'SST-2': 2,
'CoLA': 2,
'MRPC': 2,
'RTE': 2,
'STS-B': 1,
'WNLI': 2,
'AX_MNLI-m': 3,
}
logger = getLogger()
class GLUE:
def __init__(self, embedder, scores, params):
"""
Initialize GLUE trainer / evaluator.
Initial `embedder` should be on CPU to save memory.
"""
self._embedder = embedder
self.params = params
self.scores = scores
def get_iterator(self, splt):
"""
Build data iterator.
"""
return self.data[splt]['x'].get_iterator(
shuffle=(splt == 'train'),
return_indices=True,
group_by_size=self.params.group_by_size
)
def run(self, task):
"""
Run GLUE training / evaluation.
"""
params = self.params
# task parameters
self.task = task
params.out_features = N_CLASSES[task]
self.is_classif = task != 'STS-B'
# load data
self.data = self.load_data(task)
if not self.data['dico'] == self._embedder.dico:
raise Exception(("Dictionary in evaluation data (%i words) seems different than the one " +
"in the pretrained model (%i words). Please verify you used the same dictionary, " +
"and the same values for max_vocab and min_count.") % (len(self.data['dico']), len(self._embedder.dico)))
# embedder
self.embedder = copy.deepcopy(self._embedder)
self.embedder.cuda()
# projection layer
self.proj = nn.Sequential(*[
nn.Dropout(params.dropout),
nn.Linear(self.embedder.out_dim, params.out_features)
]).cuda()
# optimizers
self.optimizer_e = get_optimizer(list(self.embedder.get_parameters(params.finetune_layers)), params.optimizer_e)
self.optimizer_p = get_optimizer(self.proj.parameters(), params.optimizer_p)
# train and evaluate the model
for epoch in range(params.n_epochs):
# update epoch
self.epoch = epoch
# training
logger.info("GLUE - %s - Training epoch %i ..." % (task, epoch))
self.train()
# evaluation
logger.info("GLUE - %s - Evaluating epoch %i ..." % (task, epoch))
with torch.no_grad():
scores = self.eval('valid')
self.scores.update(scores)
self.eval('test')
def train(self):
"""
Finetune for one epoch on the training set.
"""
params = self.params
self.embedder.train()
self.proj.train()
# training variables
losses = []
ns = 0 # number of sentences
nw = 0 # number of words
t = time.time()
iterator = self.get_iterator('train')
lang_id = params.lang2id['en']
while True:
# batch
try:
batch = next(iterator)
except StopIteration:
break
if self.n_sent == 1:
(x, lengths), idx = batch
x, lengths = truncate(x, lengths, params.max_len, params.eos_index)
else:
(sent1, len1), (sent2, len2), idx = batch
sent1, len1 = truncate(sent1, len1, params.max_len, params.eos_index)
sent2, len2 = truncate(sent2, len2, params.max_len, params.eos_index)
x, lengths, _, _ = concat_batches(sent1, len1, lang_id, sent2, len2, lang_id, params.pad_index, params.eos_index, reset_positions=False)
y = self.data['train']['y'][idx]
bs = len(lengths)
# cuda
x, y, lengths = to_cuda(x, y, lengths)
# loss
output = self.proj(self.embedder.get_embeddings(x, lengths, positions=None, langs=None))
if self.is_classif:
loss = F.cross_entropy(output, y, weight=self.weights)
else:
loss = F.mse_loss(output.squeeze(1), y.float())
# backward / optimization
self.optimizer_e.zero_grad()
self.optimizer_p.zero_grad()
loss.backward()
self.optimizer_e.step()
self.optimizer_p.step()
# update statistics
ns += bs
nw += lengths.sum().item()
losses.append(loss.item())
# log
if ns != 0 and ns % (10 * bs) < bs:
logger.info(
"GLUE - %s - Epoch %s - Train iter %7i - %.1f words/s - %s Loss: %.4f"
% (self.task, self.epoch, ns, nw / (time.time() - t), 'XE' if self.is_classif else 'MSE', sum(losses) / len(losses))
)
nw, t = 0, time.time()
losses = []
# epoch size
if params.epoch_size != -1 and ns >= params.epoch_size:
break
def eval(self, splt):
"""
Evaluate on XNLI validation and test sets, for all languages.
"""
params = self.params
self.embedder.eval()
self.proj.eval()
assert splt in ['valid', 'test']
has_labels = 'y' in self.data[splt]
scores = OrderedDict({'epoch': self.epoch})
task = self.task.lower()
idxs = [] # sentence indices
prob = [] # probabilities
pred = [] # predicted values
gold = [] # real values
lang_id = params.lang2id['en']
for batch in self.get_iterator(splt):
# batch
if self.n_sent == 1:
(x, lengths), idx = batch
# x, lengths = truncate(x, lengths, params.max_len, params.eos_index)
else:
(sent1, len1), (sent2, len2), idx = batch
# sent1, len1 = truncate(sent1, len1, params.max_len, params.eos_index)
# sent2, len2 = truncate(sent2, len2, params.max_len, params.eos_index)
x, lengths, _, _ = concat_batches(sent1, len1, lang_id, sent2, len2, lang_id, params.pad_index, params.eos_index, reset_positions=False)
y = self.data[splt]['y'][idx] if has_labels else None
# cuda
x, y, lengths = to_cuda(x, y, lengths)
# prediction
output = self.proj(self.embedder.get_embeddings(x, lengths, positions=None, langs=None))
p = output.data.max(1)[1] if self.is_classif else output.squeeze(1)
idxs.append(idx)
prob.append(output.cpu().numpy())
pred.append(p.cpu().numpy())
if has_labels:
gold.append(y.cpu().numpy())
# indices / predictions
idxs = np.concatenate(idxs)
prob = np.concatenate(prob)
pred = np.concatenate(pred)
assert len(idxs) == len(pred), (len(idxs), len(pred))
assert idxs[-1] == len(idxs) - 1, (idxs[-1], len(idxs) - 1)
# score the predictions if we have labels
if has_labels:
gold = np.concatenate(gold)
prefix = f'{splt}_{task}'
if self.is_classif:
scores['%s_acc' % prefix] = 100. * (pred == gold).sum() / len(pred)
scores['%s_f1' % prefix] = 100. * f1_score(gold, pred, average='binary' if params.out_features == 2 else 'micro')
scores['%s_mc' % prefix] = 100. * matthews_corrcoef(gold, pred)
else:
scores['%s_prs' % prefix] = 100. * pearsonr(pred, gold)[0]
scores['%s_spr' % prefix] = 100. * spearmanr(pred, gold)[0]
logger.info("__log__:%s" % json.dumps(scores))
# output predictions
pred_path = os.path.join(params.dump_path, f'{splt}.pred.{self.epoch}')
with open(pred_path, 'w') as f:
for i, p in zip(idxs, prob):
f.write('%i\t%s\n' % (i, ','.join([str(x) for x in p])))
logger.info(f"Wrote {len(idxs)} {splt} predictions to {pred_path}")
return scores
def load_data(self, task):
"""
Load pair regression/classification bi-sentence tasks
"""
params = self.params
data = {splt: {} for splt in ['train', 'valid', 'test']}
dpath = os.path.join(params.data_path, 'eval', task)
self.n_sent = 1 if task in ['SST-2', 'CoLA'] else 2
for splt in ['train', 'valid', 'test']:
# load data and dictionary
data1 = load_binarized(os.path.join(dpath, '%s.s1.pth' % splt), params)
data2 = load_binarized(os.path.join(dpath, '%s.s2.pth' % splt), params) if self.n_sent == 2 else None
data['dico'] = data.get('dico', data1['dico'])
# set dictionary parameters
set_dico_parameters(params, data, data1['dico'])
if self.n_sent == 2:
set_dico_parameters(params, data, data2['dico'])
# create dataset
if self.n_sent == 1:
data[splt]['x'] = Dataset(data1['sentences'], data1['positions'], params)
else:
data[splt]['x'] = ParallelDataset(
data1['sentences'], data1['positions'],
data2['sentences'], data2['positions'],
params
)
# load labels
if splt != 'test' or task in ['MRPC']:
# read labels from file
with open(os.path.join(dpath, '%s.label' % splt), 'r') as f:
lines = [l.rstrip() for l in f]
# STS-B task
if task == 'STS-B':
assert all(0 <= float(x) <= 5 for x in lines)
y = [float(l) for l in lines]
# QQP
elif task == 'QQP':
UNK_LABEL = 0
lab2id = {x: i for i, x in enumerate(sorted(set(lines) - set([''])))}
y = [lab2id.get(x, UNK_LABEL) for x in lines]
# other tasks
else:
lab2id = {x: i for i, x in enumerate(sorted(set(lines)))}
y = [lab2id[x] for x in lines]
data[splt]['y'] = torch.LongTensor(y)
assert len(data[splt]['x']) == len(data[splt]['y'])
# compute weights for weighted training
if task != 'STS-B' and params.weighted_training:
weights = torch.FloatTensor([
1.0 / (data['train']['y'] == i).sum().item()
for i in range(len(lab2id))
]).cuda()
self.weights = weights / weights.sum()
else:
self.weights = None
return data