| |
| |
| |
| |
| |
| |
|
|
| from logging import getLogger |
| import os |
| import subprocess |
| from collections import OrderedDict |
| import numpy as np |
| import torch |
|
|
| from ..utils import to_cuda, restore_segmentation, concat_batches |
| from ..model.memory import HashingMemory |
|
|
|
|
| BLEU_SCRIPT_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'multi-bleu.perl') |
| assert os.path.isfile(BLEU_SCRIPT_PATH) |
|
|
|
|
| logger = getLogger() |
|
|
|
|
| def kl_score(x): |
| |
| _x = x.copy() |
| _x[x == 0] = 1 |
| return np.log(len(x)) + (x * np.log(_x)).sum() |
|
|
|
|
| def gini_score(x): |
| |
| B = np.cumsum(np.sort(x)).mean() |
| return 1 - 2 * B |
|
|
|
|
| def tops(x): |
| |
| y = np.cumsum(np.sort(x)) |
| top50, top90, top99 = y.shape[0] - np.searchsorted(y, [0.5, 0.1, 0.01]) |
| return top50, top90, top99 |
|
|
|
|
| def eval_memory_usage(scores, name, mem_att, mem_size): |
| """ |
| Evaluate memory usage (HashingMemory / FFN). |
| """ |
| |
| assert mem_size > 0 |
| mem_scores_w = np.zeros(mem_size, dtype=np.float32) |
| mem_scores_u = np.zeros(mem_size, dtype=np.float32) |
|
|
| |
| for indices, weights in mem_att: |
| np.add.at(mem_scores_w, indices, weights) |
| np.add.at(mem_scores_u, indices, 1) |
|
|
| |
| mem_scores_w = mem_scores_w / mem_scores_w.sum() |
| mem_scores_u = mem_scores_u / mem_scores_u.sum() |
|
|
| |
| scores['%s_mem_used' % name] = float(100 * (mem_scores_w != 0).sum() / len(mem_scores_w)) |
|
|
| scores['%s_mem_kl_w' % name] = float(kl_score(mem_scores_w)) |
| scores['%s_mem_kl_u' % name] = float(kl_score(mem_scores_u)) |
|
|
| scores['%s_mem_gini_w' % name] = float(gini_score(mem_scores_w)) |
| scores['%s_mem_gini_u' % name] = float(gini_score(mem_scores_u)) |
|
|
| top50, top90, top99 = tops(mem_scores_w) |
| scores['%s_mem_top50_w' % name] = float(top50) |
| scores['%s_mem_top90_w' % name] = float(top90) |
| scores['%s_mem_top99_w' % name] = float(top99) |
|
|
| top50, top90, top99 = tops(mem_scores_u) |
| scores['%s_mem_top50_u' % name] = float(top50) |
| scores['%s_mem_top90_u' % name] = float(top90) |
| scores['%s_mem_top99_u' % name] = float(top99) |
|
|
|
|
| def mean_num_words(filename): |
| """ |
| Computes the average number of words per line/example/generation. |
| stackoverflow.com/questions/41504428/find-the-number-of-characters-in-a-file-using-python |
| """ |
| with open(filename) as infile: |
| words = 0 |
| characters = 0 |
| for lineno, line in enumerate(infile, 1): |
| wordslist = line.split() |
| words += len(wordslist) |
| characters += sum(len(word) for word in wordslist) |
| return float(words) / float(lineno) |
|
|
|
|
| def read_lines_from_path(path): |
| """ |
| Utility to read stripped lines from specified filepath |
| """ |
| with open(path) as f: |
| lines = f.readlines() |
| return [line.strip() for line in lines] |
|
|
|
|
| def add_eval_stats(trainer, scores): |
| |
| lang1, lang2 = trainer.params.langs[:2] |
| bt1_keys = {key for key in scores.keys() if f'_{lang1}-{lang2}-{lang1}_' in key} |
| for bt1_key in bt1_keys: |
| bt2_key = bt1_key.replace(f'_{lang1}-{lang2}-{lang1}_', f'_{lang2}-{lang1}-{lang2}_') |
| if bt2_key in scores.keys(): |
| avg_bt_key = bt1_key.replace(f'_{lang1}-{lang2}-{lang1}_', f'_{lang1}-{lang2}-{lang1}--{lang2}-{lang1}-{lang2}_') |
| scores[avg_bt_key] = (scores[bt1_key] + scores[bt2_key]) / 2. |
| mt1_keys = {key for key in scores.keys() if f'_{lang1}-{lang2}_' in key} |
| for mt1_key in mt1_keys: |
| mt2_key = mt1_key.replace(f'_{lang1}-{lang2}_', f'_{lang2}-{lang1}_') |
| if mt2_key in scores.keys(): |
| avg_mt_key = mt1_key.replace(f'_{lang1}-{lang2}_', f'_{lang1}-{lang2}--{lang2}-{lang1}_') |
| scores[avg_mt_key] = (scores[mt1_key] + scores[mt2_key]) / 2. |
|
|
| |
| test_weight = 1. - trainer.params.validation_weight |
| valid_metrics = {key for key in scores.keys() if key.startswith('valid')} |
| for valid_metric in valid_metrics: |
| test_metric = valid_metric.replace('valid', 'test') |
| scores[valid_metric.replace('valid', 'validtest')] = (trainer.params.validation_weight * scores[valid_metric]) + (test_weight * scores[test_metric]) |
|
|
| for k, v in scores.items(): |
| logger.info("%s -> %.6f" % (k, v)) |
| if trainer.tb_writer is not None: |
| logk = k.replace(">", "-").replace("(", "I").replace(")", "I").replace(",", "_") |
| if 'validtest' in k: |
| trainer.tb_writer.add_scalar(f'validtest/{logk}', v, trainer.epoch) |
| elif 'valid' in k: |
| trainer.tb_writer.add_scalar(f'valid/{logk}', v, trainer.epoch) |
| elif 'test' in k: |
| trainer.tb_writer.add_scalar(f'test/{logk}', v, trainer.epoch) |
| else: |
| trainer.tb_writer.add_scalar(f'eval/{logk}', v, trainer.epoch) |
|
|
| return scores |
|
|
|
|
| class Evaluator(object): |
|
|
| def __init__(self, trainer, data, params): |
| """ |
| Initialize evaluator. |
| """ |
| self.trainer = trainer |
| self.data = data |
| self.dico = data['dico'] |
| self.params = params |
| self.memory_list = trainer.memory_list |
|
|
| |
| if self.params.is_master: |
| params.hyp_path = os.path.join(params.dump_path, 'hypotheses') |
| subprocess.Popen('mkdir -p %s' % params.hyp_path, shell=True).wait() |
| self.create_reference_files() |
|
|
| def get_iterator(self, data_set, lang1, lang2=None, stream=False): |
| """ |
| Create a new iterator for a dataset. |
| """ |
| assert data_set in ['valid', 'test'] |
| assert lang1 in self.params.langs |
| assert lang2 is None or lang2 in self.params.langs |
| assert stream is False or lang2 is None |
|
|
| |
| if len(self.params.langs) > 30: |
| eval_lgs = set(["ar", "bg", "de", "el", "en", "es", "fr", "hi", "ru", "sw", "th", "tr", "ur", "vi", "zh", "ab", "ay", "bug", "ha", "ko", "ln", "min", "nds", "pap", "pt", "tg", "to", "udm", "uk", "zh_classical"]) |
| eval_lgs = set(["ar", "bg", "de", "el", "en", "es", "fr", "hi", "ru", "sw", "th", "tr", "ur", "vi", "zh"]) |
| subsample = 10 if (data_set == 'test' or lang1 not in eval_lgs) else 5 |
| n_sentences = 600 if (data_set == 'test' or lang1 not in eval_lgs) else 1500 |
| elif len(self.params.langs) > 5: |
| subsample = 10 if data_set == 'test' else 5 |
| n_sentences = 300 if data_set == 'test' else 1500 |
| else: |
| |
| n_sentences = -1 |
| subsample = 1 |
|
|
| if lang2 is None: |
| if stream: |
| iterator = self.data['mono_stream'][lang1][data_set].get_iterator(shuffle=False, subsample=subsample) |
| else: |
| iterator = self.data['mono'][lang1][data_set].get_iterator( |
| shuffle=False, |
| group_by_size=True, |
| n_sentences=n_sentences, |
| ) |
| else: |
| assert stream is False |
| _lang1, _lang2 = (lang1, lang2) if lang1 < lang2 else (lang2, lang1) |
| iterator = self.data['para'][(_lang1, _lang2)][data_set].get_iterator( |
| shuffle=False, |
| group_by_size=True, |
| n_sentences=n_sentences |
| ) |
|
|
| for batch in iterator: |
| yield batch if lang2 is None or lang1 < lang2 else batch[::-1] |
|
|
| def create_reference_files(self): |
| """ |
| Create reference files for BLEU evaluation. |
| """ |
| params = self.params |
| params.ref_paths = {} |
|
|
| for (lang1, lang2), v in self.data['para'].items(): |
|
|
| assert lang1 < lang2 |
|
|
| for data_set in ['valid', 'test']: |
|
|
| |
| lang1_path = os.path.join(params.hyp_path, 'ref.{0}-{1}.{2}.txt'.format(lang2, lang1, data_set)) |
| lang2_path = os.path.join(params.hyp_path, 'ref.{0}-{1}.{2}.txt'.format(lang1, lang2, data_set)) |
|
|
| |
| params.ref_paths[(lang2, lang1, data_set)] = lang1_path |
| params.ref_paths[(lang1, lang2, data_set)] = lang2_path |
|
|
| |
| lang1_txt = [] |
| lang2_txt = [] |
|
|
| |
| for (sent1, len1), (sent2, len2) in self.get_iterator(data_set, lang1, lang2): |
| lang1_txt.extend(convert_to_text(sent1, len1, self.dico, params)) |
| lang2_txt.extend(convert_to_text(sent2, len2, self.dico, params)) |
|
|
| |
| lang1_txt = [x.replace('<unk>', '<<unk>>') for x in lang1_txt] |
| lang2_txt = [x.replace('<unk>', '<<unk>>') for x in lang2_txt] |
|
|
| |
| with open(lang1_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(lang1_txt) + '\n') |
| with open(lang2_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(lang2_txt) + '\n') |
|
|
| |
| restore_segmentation(lang1_path) |
| restore_segmentation(lang2_path) |
|
|
| def mask_out(self, x, lengths, rng): |
| """ |
| Decide of random words to mask out. |
| We specify the random generator to ensure that the test is the same at each epoch. |
| """ |
| params = self.params |
| slen, bs = x.size() |
|
|
| |
| to_predict = rng.rand(slen, bs) <= params.word_pred |
| to_predict[0] = 0 |
| for i in range(bs): |
| to_predict[lengths[i] - 1:, i] = 0 |
| if not np.any(to_predict[:lengths[i] - 1, i]): |
| v = rng.randint(1, lengths[i] - 1) |
| to_predict[v, i] = 1 |
| pred_mask = torch.from_numpy(to_predict.astype(np.uint8)) |
|
|
| |
| _x_real = x[pred_mask] |
| _x_mask = _x_real.clone().fill_(params.mask_index) |
| x = x.masked_scatter(pred_mask, _x_mask) |
|
|
| assert 0 <= x.min() <= x.max() < params.n_words |
| assert x.size() == (slen, bs) |
| assert pred_mask.size() == (slen, bs) |
|
|
| return x, _x_real, pred_mask |
|
|
| def run_all_evals(self, trainer): |
| """ |
| Run all evaluations. |
| """ |
| params = self.params |
| scores = OrderedDict({'epoch': trainer.epoch}) |
|
|
| with torch.no_grad(): |
|
|
| for data_set in ['valid', 'test']: |
|
|
| |
| for lang1, lang2 in params.clm_steps: |
| self.evaluate_clm(scores, data_set, lang1, lang2) |
|
|
| |
| for lang1, lang2 in params.mlm_steps: |
| self.evaluate_mlm(scores, data_set, lang1, lang2) |
|
|
| |
| for lang1, lang2 in set(params.mt_steps + [(l2, l3) for _, l2, l3 in params.bt_steps]): |
| eval_bleu = params.eval_bleu and params.is_master |
| self.evaluate_mt(scores, data_set, lang1, lang2, eval_bleu) |
|
|
| |
| _clm_mono = [l1 for (l1, l2) in params.clm_steps if l2 is None] |
| if len(_clm_mono) > 0: |
| scores['%s_clm_ppl' % data_set] = np.mean([scores['%s_%s_clm_ppl' % (data_set, lang)] for lang in _clm_mono]) |
| scores['%s_clm_acc' % data_set] = np.mean([scores['%s_%s_clm_acc' % (data_set, lang)] for lang in _clm_mono]) |
| _mlm_mono = [l1 for (l1, l2) in params.mlm_steps if l2 is None] |
| if len(_mlm_mono) > 0: |
| scores['%s_mlm_ppl' % data_set] = np.mean([scores['%s_%s_mlm_ppl' % (data_set, lang)] for lang in _mlm_mono]) |
| scores['%s_mlm_acc' % data_set] = np.mean([scores['%s_%s_mlm_acc' % (data_set, lang)] for lang in _mlm_mono]) |
|
|
| return add_eval_stats(trainer, scores) |
|
|
| def evaluate_clm(self, scores, data_set, lang1, lang2): |
| """ |
| Evaluate perplexity and next word prediction accuracy. |
| """ |
| params = self.params |
| assert data_set in ['valid', 'test'] |
| assert lang1 in params.langs |
| assert lang2 in params.langs or lang2 is None |
|
|
| model = self.model if params.encoder_only else self.decoder |
| model.eval() |
| model = model.module if params.multi_gpu else model |
|
|
| lang1_id = params.lang2id[lang1] |
| lang2_id = params.lang2id[lang2] if lang2 is not None else None |
| l1l2 = lang1 if lang2 is None else f"{lang1}-{lang2}" |
|
|
| n_words = 0 |
| xe_loss = 0 |
| n_valid = 0 |
|
|
| |
| eval_memory = params.use_memory and data_set == 'valid' and self.params.is_master |
| HashingMemory.EVAL_MEMORY = eval_memory |
| if eval_memory: |
| all_mem_att = {k: [] for k, _ in self.memory_list} |
|
|
| for batch in self.get_iterator(data_set, lang1, lang2, stream=(lang2 is None)): |
|
|
| |
| if lang2 is None: |
| x, lengths = batch |
| positions = None |
| langs = x.clone().fill_(lang1_id) if params.n_langs > 1 else None |
| else: |
| (sent1, len1), (sent2, len2) = batch |
| x, lengths, positions, langs = concat_batches(sent1, len1, lang1_id, sent2, len2, lang2_id, params.pad_index, params.eos_index, reset_positions=True) |
|
|
| |
| alen = torch.arange(lengths.max(), dtype=torch.long, device=lengths.device) |
| pred_mask = alen[:, None] < lengths[None] - 1 |
| y = x[1:].masked_select(pred_mask[:-1]) |
| assert pred_mask.sum().item() == y.size(0) |
|
|
| |
| x, lengths, positions, langs, pred_mask, y = to_cuda(x, lengths, positions, langs, pred_mask, y) |
|
|
| |
| tensor = model('fwd', x=x, lengths=lengths, positions=positions, langs=langs, causal=True) |
| word_scores, loss = model('predict', tensor=tensor, pred_mask=pred_mask, y=y, get_scores=True) |
|
|
| |
| n_words += y.size(0) |
| xe_loss += loss.item() * len(y) |
| n_valid += (word_scores.max(1)[1] == y).sum().item() |
| if eval_memory: |
| for k, v in self.memory_list: |
| all_mem_att[k].append((v.last_indices, v.last_scores)) |
|
|
| |
| logger.info("Found %i words in %s. %i were predicted correctly." % (n_words, data_set, n_valid)) |
|
|
| |
| ppl_name = '%s_%s_clm_ppl' % (data_set, l1l2) |
| acc_name = '%s_%s_clm_acc' % (data_set, l1l2) |
| scores[ppl_name] = np.exp(xe_loss / n_words) |
| scores[acc_name] = 100. * n_valid / n_words |
|
|
| |
| if eval_memory: |
| for mem_name, mem_att in all_mem_att.items(): |
| eval_memory_usage(scores, '%s_%s_%s' % (data_set, l1l2, mem_name), mem_att, params.mem_size) |
|
|
| def evaluate_mlm(self, scores, data_set, lang1, lang2): |
| """ |
| Evaluate perplexity and next word prediction accuracy. |
| """ |
| params = self.params |
| assert data_set in ['valid', 'test'] |
| assert lang1 in params.langs |
| assert lang2 in params.langs or lang2 is None |
|
|
| model = self.model if params.encoder_only else self.encoder |
| model.eval() |
| model = model.module if params.multi_gpu else model |
|
|
| rng = np.random.RandomState(0) |
|
|
| lang1_id = params.lang2id[lang1] |
| lang2_id = params.lang2id[lang2] if lang2 is not None else None |
| l1l2 = lang1 if lang2 is None else f"{lang1}_{lang2}" |
|
|
| n_words = 0 |
| xe_loss = 0 |
| n_valid = 0 |
|
|
| |
| eval_memory = params.use_memory and data_set == 'valid' and self.params.is_master |
| HashingMemory.EVAL_MEMORY = eval_memory |
| if eval_memory: |
| all_mem_att = {k: [] for k, _ in self.memory_list} |
|
|
| for batch in self.get_iterator(data_set, lang1, lang2, stream=(lang2 is None)): |
|
|
| |
| if lang2 is None: |
| x, lengths = batch |
| positions = None |
| langs = x.clone().fill_(lang1_id) if params.n_langs > 1 else None |
| else: |
| (sent1, len1), (sent2, len2) = batch |
| x, lengths, positions, langs = concat_batches(sent1, len1, lang1_id, sent2, len2, lang2_id, params.pad_index, params.eos_index, reset_positions=True) |
|
|
| |
| x, y, pred_mask = self.mask_out(x, lengths, rng) |
|
|
| |
| x, y, pred_mask, lengths, positions, langs = to_cuda(x, y, pred_mask, lengths, positions, langs) |
|
|
| |
| tensor = model('fwd', x=x, lengths=lengths, positions=positions, langs=langs, causal=False) |
| word_scores, loss = model('predict', tensor=tensor, pred_mask=pred_mask, y=y, get_scores=True) |
|
|
| |
| n_words += len(y) |
| xe_loss += loss.item() * len(y) |
| n_valid += (word_scores.max(1)[1] == y).sum().item() |
| if eval_memory: |
| for k, v in self.memory_list: |
| all_mem_att[k].append((v.last_indices, v.last_scores)) |
|
|
| |
| ppl_name = '%s_%s_mlm_ppl' % (data_set, l1l2) |
| acc_name = '%s_%s_mlm_acc' % (data_set, l1l2) |
| scores[ppl_name] = np.exp(xe_loss / n_words) if n_words > 0 else 1e9 |
| scores[acc_name] = 100. * n_valid / n_words if n_words > 0 else 0. |
|
|
| |
| if eval_memory: |
| for mem_name, mem_att in all_mem_att.items(): |
| eval_memory_usage(scores, '%s_%s_%s' % (data_set, l1l2, mem_name), mem_att, params.mem_size) |
|
|
|
|
| class SingleEvaluator(Evaluator): |
|
|
| def __init__(self, trainer, data, params): |
| """ |
| Build language model evaluator. |
| """ |
| super().__init__(trainer, data, params) |
| self.model = trainer.model |
|
|
|
|
| class EncDecEvaluator(Evaluator): |
|
|
| def __init__(self, trainer, data, params): |
| """ |
| Build encoder / decoder evaluator. |
| """ |
| super().__init__(trainer, data, params) |
| self.encoder = trainer.encoder |
| self.decoder = trainer.decoder |
|
|
| def evaluate_mt(self, scores, data_set, lang1, lang2, eval_bleu): |
| """ |
| Evaluate perplexity and next word prediction accuracy. |
| """ |
| params = self.params |
| assert data_set in ['valid', 'test'] |
| assert lang1 in params.langs |
| assert lang2 in params.langs |
|
|
| self.encoder.eval() |
| self.decoder.eval() |
| encoder = self.encoder.module if params.multi_gpu else self.encoder |
| decoder = self.decoder.module if params.multi_gpu else self.decoder |
|
|
| params = params |
| lang1_id = params.lang2id[lang1] |
| lang2_id = params.lang2id[lang2] |
|
|
| n_words = 0 |
| xe_loss = 0 |
| n_valid = 0 |
|
|
| |
| eval_memory = params.use_memory and data_set == 'valid' and self.params.is_master |
| HashingMemory.EVAL_MEMORY = eval_memory |
| if eval_memory: |
| all_mem_att = {k: [] for k, _ in self.memory_list} |
|
|
| |
| if eval_bleu: |
| hypothesis = [] |
| back_hypothesis = [] |
|
|
| for batch in self.get_iterator(data_set, lang1, lang2): |
|
|
| |
| (x1, len1), (x2, len2) = batch |
| langs1 = x1.clone().fill_(lang1_id) |
| langs2 = x2.clone().fill_(lang2_id) |
|
|
| |
| alen = torch.arange(len2.max(), dtype=torch.long, device=len2.device) |
| pred_mask = alen[:, None] < len2[None] - 1 |
| y = x2[1:].masked_select(pred_mask[:-1]) |
| assert len(y) == (len2 - 1).sum().item() |
|
|
| |
| x1, len1, langs1, x2, len2, langs2, y = to_cuda(x1, len1, langs1, x2, len2, langs2, y) |
|
|
| |
| enc1 = encoder('fwd', x=x1, lengths=len1, langs=langs1, causal=False) |
| enc1 = enc1.transpose(0, 1) |
| enc1 = enc1.half() if params.fp16 else enc1 |
|
|
| |
| dec2 = decoder('fwd', x=x2, lengths=len2, langs=langs2, causal=True, src_enc=enc1, src_len=len1) |
|
|
| |
| word_scores, loss = decoder('predict', tensor=dec2, pred_mask=pred_mask, y=y, get_scores=True) |
|
|
| |
| n_words += y.size(0) |
| xe_loss += loss.item() * len(y) |
| n_valid += (word_scores.max(1)[1] == y).sum().item() |
| if eval_memory: |
| for k, v in self.memory_list: |
| all_mem_att[k].append((v.last_indices, v.last_scores)) |
|
|
| |
| if eval_bleu: |
| max_len = int(1.5 * len1.max().item() + 10) |
| if params.beam_size == 1: |
| generated, lengths = decoder.generate(enc1, len1, lang2_id, max_len=max_len) |
| else: |
| generated, lengths = decoder.generate_beam( |
| enc1, len1, lang2_id, beam_size=params.beam_size, |
| length_penalty=params.length_penalty, |
| early_stopping=params.early_stopping, |
| max_len=max_len |
| ) |
| hypothesis.extend(convert_to_text(generated, lengths, self.dico, params)) |
|
|
| |
| langs2_generated = generated.clone().fill_(lang2_id) |
| enc2 = encoder('fwd', x=generated, lengths=lengths, langs=langs2_generated, causal=False) |
| enc2 = enc2.transpose(0, 1) |
| enc2 = enc2.half() if params.fp16 else enc2 |
|
|
| if params.beam_size == 1: |
| back_generated, back_lengths = decoder.generate(enc2, lengths, lang1_id, max_len=max_len) |
| else: |
| back_generated, back_lengths = decoder.generate_beam( |
| enc2, lengths, lang1_id, beam_size=params.beam_size, |
| length_penalty=params.length_penalty, |
| early_stopping=params.early_stopping, |
| max_len=max_len |
| ) |
| back_hypothesis.extend(convert_to_text(back_generated, back_lengths, self.dico, params)) |
|
|
| |
| scores['%s_%s-%s_mt_ppl' % (data_set, lang1, lang2)] = np.exp(xe_loss / n_words) |
| scores['%s_%s-%s_mt_acc' % (data_set, lang1, lang2)] = 100. * n_valid / n_words |
|
|
| |
| if eval_memory: |
| for mem_name, mem_att in all_mem_att.items(): |
| eval_memory_usage(scores, '%s_%s-%s_%s' % (data_set, lang1, lang2, mem_name), mem_att, params.mem_size) |
|
|
| |
| if eval_bleu: |
|
|
| |
| hyp_name = 'hyp{0}.{1}-{2}.{3}.txt'.format(scores['epoch'], lang1, lang2, data_set) |
| hyp_path = os.path.join(params.hyp_path, hyp_name) |
| back_hyp_name = 'hyp{0}.{1}-{2}-{3}.{4}.txt'.format(scores['epoch'], lang1, lang2, lang1, data_set) |
| back_hyp_path = os.path.join(params.hyp_path, back_hyp_name) |
| ref_path = params.ref_paths[(lang1, lang2, data_set)] |
| input_path = params.ref_paths[(lang2, lang1, data_set)] |
|
|
| |
| with open(hyp_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(hypothesis) + '\n') |
| restore_segmentation(hyp_path) |
|
|
| with open(back_hyp_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(back_hypothesis) + '\n') |
| restore_segmentation(back_hyp_path) |
|
|
| |
| bleu = eval_moses_bleu(ref_path, hyp_path) |
| logger.info("BLEU %s %s : %f" % (hyp_path, ref_path, bleu)) |
| scores['%s_%s-%s_mt_bleu' % (data_set, lang1, lang2)] = bleu |
|
|
| |
| back_bleu = eval_moses_bleu(input_path, back_hyp_path) |
| logger.info("Back-BLEU %s %s : %f" % (back_hyp_path, input_path, back_bleu)) |
| scores['%s_%s-%s-%s_mt_back_bleu' % (data_set, lang1, lang2, lang1)] = back_bleu |
|
|
| |
| hyp_mean_num_words = mean_num_words(hyp_path) |
| train_tgt_path = f"{params.data_path.rstrip('/').rsplit('/', 1)[0]}/train.{lang2}.tok" |
| if os.path.exists(train_tgt_path): |
| train_tgt_mean_num_words = mean_num_words(train_tgt_path) |
| scores['%s_%s-%s_mt_hyp2train_num_words_ratio' % (data_set, lang1, lang2)] = hyp_mean_num_words / train_tgt_mean_num_words |
|
|
| |
| input_bleu = eval_moses_bleu(input_path, hyp_path) |
| logger.info("Input BLEU %s %s : %f" % (hyp_path, input_path, input_bleu)) |
| scores['%s_%s-%s_mt_input_bleu' % (data_set, lang1, lang2)] = input_bleu |
|
|
| |
| hyp_lines = read_lines_from_path(hyp_path) |
| input_lines = read_lines_from_path(input_path) |
| back_hyp_lines = read_lines_from_path(back_hyp_path) |
|
|
| doubles, contains, unchanged, too_few_qs, too_many_qs, all_q_words_in_subq, subq_longer_than_q, bads = 0, 0, 0, 0, 0, 0, 0, 0 |
| good_inps, good_hyps, good_back_hyps = [], [], [] |
| for inp, hyp, back_hyp in zip(input_lines, hyp_lines, back_hyp_lines): |
| bad = False |
| if hyp.count('?') == 2: |
| l, r, _ = hyp.split('?') |
| l = l + '?' |
| r = r + '?' |
| if l == r: |
| doubles += 1 |
| bad = True |
| l_toks = l.split() |
| r_toks = r.split() |
| inp_toks = inp.split() |
| for subq_toks in [l_toks, r_toks]: |
| if set(inp_toks).issubset(set(subq_toks)): |
| all_q_words_in_subq += 1 |
| bad = True |
| break |
| for subq_toks in [l_toks, r_toks]: |
| if len(subq_toks) >= len(inp_toks): |
| subq_longer_than_q += 1 |
| bad = True |
| break |
| elif hyp.count('?') < 2: |
| too_few_qs += 1 |
| bad = True |
| else: |
| too_many_qs += 1 |
| if not self.params.one_to_variable: |
| bad = True |
| if inp in hyp: |
| contains += 1 |
| bad = True |
| if inp == hyp: |
| unchanged += 1 |
| bads += bad |
| if not bad: |
| good_inps.append(inp) |
| good_hyps.append(hyp) |
| good_back_hyps.append(back_hyp) |
| scores['%s_%s-%s_mt_doubles' % (data_set, lang1, lang2)] = 100. * doubles / len(hyp_lines) |
| scores['%s_%s-%s_mt_contains' % (data_set, lang1, lang2)] = 100. * contains / len(hyp_lines) |
| scores['%s_%s-%s_mt_unchanged' % (data_set, lang1, lang2)] = 100. * unchanged / len(hyp_lines) |
| scores['%s_%s-%s_mt_too_few_qs' % (data_set, lang1, lang2)] = 100. * too_few_qs / len(hyp_lines) |
| scores['%s_%s-%s_mt_too_many_qs' % (data_set, lang1, lang2)] = 100. * too_many_qs / len(hyp_lines) |
| scores['%s_%s-%s_mt_all_q_words_in_subq' % (data_set, lang1, lang2)] = 100. * all_q_words_in_subq / len(hyp_lines) |
| scores['%s_%s-%s_mt_subq_longer_than_q' % (data_set, lang1, lang2)] = 100. * subq_longer_than_q / len(hyp_lines) |
| scores['%s_%s-%s_mt_bads' % (data_set, lang1, lang2)] = 100. * bads / len(hyp_lines) |
|
|
| |
| good_hyp_path = hyp_path.replace('.txt', '.good.txt') |
| with open(good_hyp_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(good_hyps) + '\n') |
|
|
| good_inp_path = good_hyp_path.replace(f'/hyp{scores["epoch"]}', f'/ref{scores["epoch"]}') |
| with open(good_inp_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(good_inps) + '\n') |
|
|
| good_back_hyp_path = back_hyp_path.replace('.txt', '.good.txt') |
| with open(good_back_hyp_path, 'w', encoding='utf-8') as f: |
| f.write('\n'.join(good_back_hyps) + '\n') |
|
|
| goods_frac = 1. - (bads / len(hyp_lines)) |
|
|
| goods_input_bleu = eval_moses_bleu(good_inp_path, good_hyp_path) |
| logger.info("Input BLEU on Good Hyps %s %s : %f" % (good_hyp_path, good_inp_path, goods_input_bleu)) |
| scores['%s_%s-%s_mt_goods_input_bleu' % (data_set, lang1, lang2)] = goods_input_bleu |
| scores['%s_%s-%s_mt_effective_goods_input_bleu' % (data_set, lang1, lang2)] = goods_input_bleu * goods_frac |
|
|
| goods_back_bleu = eval_moses_bleu(good_inp_path, good_back_hyp_path) |
| logger.info("Input BLEU on Good Hyps %s %s : %f" % (good_back_hyp_path, good_inp_path, goods_back_bleu)) |
| scores['%s_%s-%s-%s_mt_goods_back_bleu' % (data_set, lang1, lang2, lang1)] = goods_back_bleu |
| scores['%s_%s-%s-%s_mt_effective_goods_back_bleu' % (data_set, lang1, lang2, lang1)] = goods_back_bleu * goods_frac |
|
|
|
|
| def convert_to_text(batch, lengths, dico, params): |
| """ |
| Convert a batch of sentences to a list of text sentences. |
| """ |
| batch = batch.cpu().numpy() |
| lengths = lengths.cpu().numpy() |
|
|
| slen, bs = batch.shape |
| assert lengths.max() == slen and lengths.shape[0] == bs |
| assert (batch[0] == params.eos_index).sum() == bs |
| assert (batch == params.eos_index).sum() == 2 * bs |
| sentences = [] |
|
|
| for j in range(bs): |
| words = [] |
| for k in range(1, lengths[j]): |
| if batch[k, j] == params.eos_index: |
| break |
| words.append(dico[batch[k, j]]) |
| sentences.append(" ".join(words)) |
| return sentences |
|
|
|
|
| def eval_moses_bleu(ref, hyp): |
| """ |
| Given a file of hypothesis and reference files, |
| evaluate the BLEU score using Moses scripts. |
| """ |
| assert os.path.isfile(hyp) |
| assert os.path.isfile(ref) or os.path.isfile(ref + '0') |
| assert os.path.isfile(BLEU_SCRIPT_PATH) |
| command = BLEU_SCRIPT_PATH + ' %s < %s' |
| p = subprocess.Popen(command % (ref, hyp), stdout=subprocess.PIPE, shell=True) |
| result = p.communicate()[0].decode("utf-8") |
| if result.startswith('BLEU'): |
| print(hyp + ' ' + ref + ' ' + result) |
| return float(result[7:result.index(',')]) |
| else: |
| logger.warning('Impossible to parse BLEU score! "%s"' % result) |
| return -1 |
|
|