| |
| |
| |
| |
| |
| |
|
|
| 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 |
|
|
| |
| self.task = task |
| params.out_features = N_CLASSES[task] |
| self.is_classif = task != 'STS-B' |
|
|
| |
| 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))) |
|
|
| |
| self.embedder = copy.deepcopy(self._embedder) |
| self.embedder.cuda() |
|
|
| |
| self.proj = nn.Sequential(*[ |
| nn.Dropout(params.dropout), |
| nn.Linear(self.embedder.out_dim, params.out_features) |
| ]).cuda() |
|
|
| |
| 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) |
|
|
| |
| for epoch in range(params.n_epochs): |
|
|
| |
| self.epoch = epoch |
|
|
| |
| logger.info("GLUE - %s - Training epoch %i ..." % (task, epoch)) |
| self.train() |
|
|
| |
| 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() |
|
|
| |
| losses = [] |
| ns = 0 |
| nw = 0 |
| t = time.time() |
|
|
| iterator = self.get_iterator('train') |
| lang_id = params.lang2id['en'] |
|
|
| while True: |
|
|
| |
| 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) |
|
|
| |
| x, y, lengths = to_cuda(x, y, lengths) |
|
|
| |
| 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()) |
|
|
| |
| self.optimizer_e.zero_grad() |
| self.optimizer_p.zero_grad() |
| loss.backward() |
| self.optimizer_e.step() |
| self.optimizer_p.step() |
|
|
| |
| ns += bs |
| nw += lengths.sum().item() |
| losses.append(loss.item()) |
|
|
| |
| 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 = [] |
|
|
| |
| 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 = [] |
| prob = [] |
| pred = [] |
| gold = [] |
|
|
| lang_id = params.lang2id['en'] |
|
|
| for batch in self.get_iterator(splt): |
|
|
| |
| if self.n_sent == 1: |
| (x, lengths), idx = batch |
| |
| else: |
| (sent1, len1), (sent2, len2), idx = batch |
| |
| |
| 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 |
|
|
| |
| x, y, lengths = to_cuda(x, y, lengths) |
|
|
| |
| 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()) |
|
|
| |
| 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) |
|
|
| |
| 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)) |
|
|
| |
| 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']: |
|
|
| |
| 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_dico_parameters(params, data, data1['dico']) |
| if self.n_sent == 2: |
| set_dico_parameters(params, data, data2['dico']) |
|
|
| |
| 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 |
| ) |
|
|
| |
| if splt != 'test' or task in ['MRPC']: |
| |
| with open(os.path.join(dpath, '%s.label' % splt), 'r') as f: |
| lines = [l.rstrip() for l in f] |
| |
| if task == 'STS-B': |
| assert all(0 <= float(x) <= 5 for x in lines) |
| y = [float(l) for l in lines] |
| |
| 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] |
| |
| 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']) |
|
|
| |
| 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 |
|
|