Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- data/fairseq/examples/adaptive_span/README.md +90 -0
- data/fairseq/examples/adaptive_span/__init__.py +19 -0
- data/fairseq/examples/adaptive_span/adagrad_with_grad_clip.py +128 -0
- data/fairseq/examples/adaptive_span/adaptive_span_attention.py +160 -0
- data/fairseq/examples/adaptive_span/adaptive_span_loss.py +107 -0
- data/fairseq/examples/adaptive_span/adaptive_span_model.py +263 -0
- data/fairseq/examples/adaptive_span/adaptive_span_model_wrapper.py +145 -0
- data/fairseq/examples/adaptive_span/truncated_bptt_lm_task.py +285 -0
- data/fairseq/examples/backtranslation/README.md +297 -0
- data/fairseq/examples/backtranslation/deduplicate_lines.py +41 -0
- data/fairseq/examples/backtranslation/extract_bt_data.py +72 -0
- data/fairseq/examples/backtranslation/prepare-de-monolingual.sh +98 -0
- data/fairseq/examples/backtranslation/prepare-wmt18en2de.sh +135 -0
- data/fairseq/examples/backtranslation/sacrebleu.sh +37 -0
- data/fairseq/examples/backtranslation/tokenized_bleu.sh +46 -0
- data/fairseq/examples/cross_lingual_language_model/README.md +77 -0
- data/fairseq/examples/discriminative_reranking_nmt/README.md +202 -0
- data/fairseq/examples/discriminative_reranking_nmt/__init__.py +1 -0
- data/fairseq/examples/discriminative_reranking_nmt/config/deen.yaml +56 -0
- data/fairseq/examples/discriminative_reranking_nmt/criterions/__init__.py +6 -0
- data/fairseq/examples/discriminative_reranking_nmt/criterions/discriminative_reranking_criterion.py +139 -0
- data/fairseq/examples/discriminative_reranking_nmt/drnmt_rerank.py +364 -0
- data/fairseq/examples/discriminative_reranking_nmt/models/__init__.py +6 -0
- data/fairseq/examples/discriminative_reranking_nmt/models/discriminative_reranking_model.py +365 -0
- data/fairseq/examples/discriminative_reranking_nmt/scripts/prep_data.py +136 -0
- data/fairseq/examples/discriminative_reranking_nmt/tasks/__init__.py +6 -0
- data/fairseq/examples/discriminative_reranking_nmt/tasks/discriminative_reranking_task.py +490 -0
- data/fairseq/examples/mbart/README.md +123 -0
- data/fairseq/examples/normformer/README.md +70 -0
- data/fairseq/examples/normformer/train_lm.sh +78 -0
- data/fairseq/examples/shuffled_word_order/README.finetuning.md +135 -0
- data/fairseq/examples/shuffled_word_order/README.md +94 -0
- data/fairseq/examples/simultaneous_translation/README.md +5 -0
- data/fairseq/examples/simultaneous_translation/__init__.py +6 -0
- data/fairseq/examples/simultaneous_translation/docs/ende-mma.md +74 -0
- data/fairseq/examples/simultaneous_translation/docs/enja-waitk.md +106 -0
- data/fairseq/examples/simultaneous_translation/eval/agents/simul_t2t_enja.py +226 -0
- data/fairseq/examples/simultaneous_translation/models/__init__.py +15 -0
- data/fairseq/examples/simultaneous_translation/models/convtransformer_simul_trans.py +204 -0
- data/fairseq/examples/simultaneous_translation/models/transformer_monotonic_attention.py +302 -0
- data/fairseq/examples/simultaneous_translation/modules/__init__.py +23 -0
- data/fairseq/examples/simultaneous_translation/modules/fixed_pre_decision.py +190 -0
- data/fairseq/examples/simultaneous_translation/modules/monotonic_multihead_attention.py +520 -0
- data/fairseq/examples/simultaneous_translation/modules/monotonic_transformer_layer.py +182 -0
- data/fairseq/examples/simultaneous_translation/tests/test_alignment_train.py +88 -0
- data/fairseq/examples/simultaneous_translation/tests/test_text_models.py +407 -0
- data/fairseq/examples/simultaneous_translation/utils/__init__.py +14 -0
- data/fairseq/examples/simultaneous_translation/utils/functions.py +125 -0
- data/fairseq/examples/simultaneous_translation/utils/monotonic_attention.py +180 -0
- data/fairseq/examples/simultaneous_translation/utils/p_choose_strategy.py +126 -0
data/fairseq/examples/adaptive_span/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adaptive Span
|
| 2 |
+
|
| 3 |
+
Adaptive Span is a novel self-attention mechanism that can learn its optimal
|
| 4 |
+
attention span. This allows us to extend significantly the maximum context size
|
| 5 |
+
used in Transformer, while maintaining control over their memory footprint
|
| 6 |
+
and computational time. It uses the Truncated BPTT technique for training,
|
| 7 |
+
as in [transformerXL](https://github.com/pytorch/fairseq/blob/main/examples/truncated_bptt/README.md).
|
| 8 |
+
|
| 9 |
+
Adaptive Span was introduced by paper:
|
| 10 |
+
[Adaptive Attention Span in Transformers](https://arxiv.org/abs/1905.07799),
|
| 11 |
+
which achieved state-of-the-art language modeling results at the time of publication.
|
| 12 |
+
|
| 13 |
+
We manage to reproduce their result in fairseq and keep most of the
|
| 14 |
+
[original implementation](https://github.com/facebookresearch/adaptive-span) untouched.
|
| 15 |
+
You can refer to the their sweep file as well if any combination of hyperparameter is not clear.
|
| 16 |
+
|
| 17 |
+
##### 0. Setup
|
| 18 |
+
|
| 19 |
+
First you need to process the Enwik8 dataset, we use the pre-tokenized dataset
|
| 20 |
+
from [adaptive span paper](https://github.com/facebookresearch/adaptive-span/blob/master/get_data.sh).
|
| 21 |
+
You can download the dataset, and then run:
|
| 22 |
+
```bash
|
| 23 |
+
fairseq-preprocess --only-source --trainpref ~/data/enwik8/train.txt \
|
| 24 |
+
--validpref ~/data/enwik8/valid.txt --testpref ~/data/enwik8/test.txt \
|
| 25 |
+
--destdir ~/data/enwik8/data-bin/ --joined-dictionary --workers 20
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
##### 1. Train a Adaptive Span model on Enwik8
|
| 29 |
+
|
| 30 |
+
We will train a 12-layer Adaptive Span model following the [hyperparameters
|
| 31 |
+
used in the original
|
| 32 |
+
paper](https://github.com/facebookresearch/adaptive-span/blob/master/experiments/enwik8.sh).
|
| 33 |
+
|
| 34 |
+
The following command assumes 4 GPUs, so that the total batch size is 64
|
| 35 |
+
sequences (4 x 16). Training should take 2-3 days on 4 V100 GPUs:
|
| 36 |
+
```bash
|
| 37 |
+
CUDA_VISIBLE_DEVICES=0,1,2,3 fairseq-train \
|
| 38 |
+
--user-dir examples/adaptive_span \
|
| 39 |
+
--data ~/data/enwik8/data-bin/ \
|
| 40 |
+
--fp16 --fp16-no-flatten-grads --max-update 600000 \
|
| 41 |
+
--task truncated_bptt_lm --tokens-per-sample 512 --arch adaptive_span \
|
| 42 |
+
--n-layer 12 --d-model 512 --n-head 8 --d-inner 2048 --dropout 0.3 \
|
| 43 |
+
--attn-span 8192 --optimizer adagrad_with_grad_clip --adagrad-clip 0.03 \
|
| 44 |
+
--validate-interval-updates 1000 \
|
| 45 |
+
--lr-scheduler fixed --warmup-updates 32000 --batch-size-valid 32 \
|
| 46 |
+
--lr 0.07 --criterion adaptive_span_loss --batch-size 16 --update-freq 1 \
|
| 47 |
+
--seed 2 --log-format json --log-interval 25 --aux-loss-scaler 5e-07
|
| 48 |
+
```
|
| 49 |
+
This should land around 1.05 on validation, 1.03 on test. You can lower the
|
| 50 |
+
--aux-loss-scaler for better performance (longer span). It gives ~0.03 bpc
|
| 51 |
+
improvement to the transformerXL baseline here.
|
| 52 |
+
If training on a single GPU, set `--update-freq=4` to accumulate 4x gradients
|
| 53 |
+
and simulate training on 4 GPUs.
|
| 54 |
+
You can also reproduce the transformerXL result on enwik8 using this code base.
|
| 55 |
+
It should land around 1.06 on test,matching the [original paper](https://github.com/kimiyoung/transformer-xl/blob/master/pytorch/run_enwik8_base.sh).
|
| 56 |
+
You can try by
|
| 57 |
+
```bash
|
| 58 |
+
CUDA_VISIBLE_DEVICES=0,1,2,3 fairseq-train \
|
| 59 |
+
--user-dir examples/truncated_bptt \
|
| 60 |
+
~/data/enwik8/data-bin/ \
|
| 61 |
+
--task truncated_bptt_lm --fp16 --max-update 400000 \
|
| 62 |
+
--tokens-per-sample 512 --arch transformer_xl --n-layer 12 \
|
| 63 |
+
--d-model 512 --n-head 8 --d-head 64 --d-inner 2048 --dropout 0.1 \
|
| 64 |
+
--dropatt 0.0 --mem-len 512 --optimizer adam --clip-norm 0.25 \
|
| 65 |
+
--lr-scheduler cosine --warmup-updates 0 \
|
| 66 |
+
--lr 0.0 --lr 0.00025 --batch-size 15 \
|
| 67 |
+
--update-freq 1 --seed 2 --log-format json --log-interval 25 \
|
| 68 |
+
--fp16
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
##### 2. Evaluate
|
| 72 |
+
For Adaptive Span:
|
| 73 |
+
```bash
|
| 74 |
+
fairseq-eval-lm ~/data/enwik8/data-bin/ --path model/checkpoint_best.pt \
|
| 75 |
+
--user-dir examples/adaptive_span \
|
| 76 |
+
--task truncated_bptt_lm --batch-size 8 --tokens-per-sample 512 --gen-subset test
|
| 77 |
+
```
|
| 78 |
+
For Transformer-XL evaluation:
|
| 79 |
+
```bash
|
| 80 |
+
fairseq-eval-lm ~/data/enwik8/data-bin/ --path model/checkpoint_best.pt \
|
| 81 |
+
--user-dir examples/truncated_bptt/ --task truncated_bptt_lm --batch-size 8 \
|
| 82 |
+
--tokens-per-sample 80 \
|
| 83 |
+
--model-overrides '{"mem_len":2100,"clamp_len":820,"same_length":True}' \
|
| 84 |
+
--gen-subset valid
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
*Note:* During training the model saw 512 tokens of context
|
| 88 |
+
(``--tokens-per-sample=512``), with batch size 8. These settings match the evaluation
|
| 89 |
+
settings from [the original
|
| 90 |
+
paper](https://github.com/facebookresearch/adaptive-span/blob/master/experiments/enwik8.sh).
|
data/fairseq/examples/adaptive_span/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import importlib
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# automatically import any Python files in the current directory
|
| 10 |
+
cur_dir = os.path.dirname(__file__)
|
| 11 |
+
for file in os.listdir(cur_dir):
|
| 12 |
+
path = os.path.join(cur_dir, file)
|
| 13 |
+
if (
|
| 14 |
+
not file.startswith("_")
|
| 15 |
+
and not file.startswith(".")
|
| 16 |
+
and (file.endswith(".py") or os.path.isdir(path))
|
| 17 |
+
):
|
| 18 |
+
mod_name = file[: file.find(".py")] if file.endswith(".py") else file
|
| 19 |
+
module = importlib.import_module(__name__ + "." + mod_name)
|
data/fairseq/examples/adaptive_span/adagrad_with_grad_clip.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from torch.optim import Adagrad
|
| 7 |
+
|
| 8 |
+
from fairseq.optim import LegacyFairseqOptimizer, register_optimizer
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@register_optimizer("adagrad_with_grad_clip")
|
| 12 |
+
class FairseqAdagradWithGradClip(LegacyFairseqOptimizer):
|
| 13 |
+
def __init__(self, args, params):
|
| 14 |
+
super().__init__(args)
|
| 15 |
+
self._optimizer = AdagradWithGradClip(params, **self.optimizer_config)
|
| 16 |
+
|
| 17 |
+
@staticmethod
|
| 18 |
+
def add_args(parser):
|
| 19 |
+
"""Add optimizer-specific arguments to the parser."""
|
| 20 |
+
# fmt: off
|
| 21 |
+
parser.add_argument('--weight-decay', '--wd', default=0.0, type=float, metavar='WD',
|
| 22 |
+
help='weight decay')
|
| 23 |
+
parser.add_argument('--adagrad-clip', default=0.0, type=float, metavar='D',
|
| 24 |
+
help='internal grad clip')
|
| 25 |
+
# fmt: on
|
| 26 |
+
|
| 27 |
+
@property
|
| 28 |
+
def optimizer_config(self):
|
| 29 |
+
"""
|
| 30 |
+
Return a kwarg dictionary that will be used to override optimizer
|
| 31 |
+
args stored in checkpoints. This allows us to load a checkpoint and
|
| 32 |
+
resume training using a different set of optimizer args, e.g., with a
|
| 33 |
+
different learning rate.
|
| 34 |
+
"""
|
| 35 |
+
return {
|
| 36 |
+
"lr": self.args.lr[0],
|
| 37 |
+
"weight_decay": self.args.weight_decay,
|
| 38 |
+
"grad_clip": self.args.adagrad_clip,
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
@property
|
| 42 |
+
def supports_flat_params(self):
|
| 43 |
+
return False
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _clip_grad(clr, grad, group_grad_clip):
|
| 47 |
+
if group_grad_clip > 0:
|
| 48 |
+
norm = grad.norm(2).item()
|
| 49 |
+
if norm > group_grad_clip:
|
| 50 |
+
clr *= group_grad_clip / (norm + 1e-10)
|
| 51 |
+
return clr
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class AdagradWithGradClip(Adagrad):
|
| 55 |
+
"""Adagrad algorithm with custom gradient clipping"""
|
| 56 |
+
|
| 57 |
+
def __init__(
|
| 58 |
+
self,
|
| 59 |
+
params,
|
| 60 |
+
lr=1e-2,
|
| 61 |
+
lr_decay=0,
|
| 62 |
+
weight_decay=0,
|
| 63 |
+
initial_accumulator_value=0,
|
| 64 |
+
grad_clip=0,
|
| 65 |
+
):
|
| 66 |
+
Adagrad.__init__(
|
| 67 |
+
self,
|
| 68 |
+
params,
|
| 69 |
+
lr=lr,
|
| 70 |
+
lr_decay=lr_decay,
|
| 71 |
+
weight_decay=weight_decay,
|
| 72 |
+
initial_accumulator_value=initial_accumulator_value,
|
| 73 |
+
)
|
| 74 |
+
self.defaults["grad_clip"] = grad_clip
|
| 75 |
+
self.param_groups[0].setdefault("grad_clip", grad_clip)
|
| 76 |
+
|
| 77 |
+
def step(self, closure=None):
|
| 78 |
+
loss = None
|
| 79 |
+
if closure is not None:
|
| 80 |
+
loss = closure()
|
| 81 |
+
|
| 82 |
+
for group in self.param_groups:
|
| 83 |
+
for p in group["params"]:
|
| 84 |
+
if p.grad is None:
|
| 85 |
+
continue
|
| 86 |
+
|
| 87 |
+
grad = p.grad.data
|
| 88 |
+
state = self.state[p]
|
| 89 |
+
|
| 90 |
+
state["step"] += 1
|
| 91 |
+
|
| 92 |
+
if group["weight_decay"] != 0:
|
| 93 |
+
if p.grad.data.is_sparse:
|
| 94 |
+
raise RuntimeError(
|
| 95 |
+
"weight_decay option is "
|
| 96 |
+
"not compatible with sparse "
|
| 97 |
+
"gradients"
|
| 98 |
+
)
|
| 99 |
+
grad = grad.add(group["weight_decay"], p.data)
|
| 100 |
+
|
| 101 |
+
clr = group["lr"] / (1 + (state["step"] - 1) * group["lr_decay"])
|
| 102 |
+
|
| 103 |
+
# clip
|
| 104 |
+
clr = _clip_grad(clr=clr, grad=grad, group_grad_clip=group["grad_clip"])
|
| 105 |
+
|
| 106 |
+
if grad.is_sparse:
|
| 107 |
+
# the update is non-linear so indices must be unique
|
| 108 |
+
grad = grad.coalesce()
|
| 109 |
+
grad_indices = grad._indices()
|
| 110 |
+
grad_values = grad._values()
|
| 111 |
+
size = grad.size()
|
| 112 |
+
|
| 113 |
+
def make_sparse(values):
|
| 114 |
+
constructor = grad.new
|
| 115 |
+
if grad_indices.dim() == 0 or values.dim() == 0:
|
| 116 |
+
return constructor().resize_as_(grad)
|
| 117 |
+
return constructor(grad_indices, values, size)
|
| 118 |
+
|
| 119 |
+
state["sum"].add_(make_sparse(grad_values.pow(2)))
|
| 120 |
+
std = state["sum"]._sparse_mask(grad)
|
| 121 |
+
std_values = std._values().sqrt_().add_(1e-10)
|
| 122 |
+
p.data.add_(-clr, make_sparse(grad_values / std_values))
|
| 123 |
+
else:
|
| 124 |
+
state["sum"].addcmul_(1, grad, grad)
|
| 125 |
+
std = state["sum"].sqrt().add_(1e-10)
|
| 126 |
+
p.data.addcdiv_(-clr, grad, std)
|
| 127 |
+
|
| 128 |
+
return loss
|
data/fairseq/examples/adaptive_span/adaptive_span_attention.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
import math
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class AdaptiveMask(nn.Module):
|
| 13 |
+
"""Soft masking function for adaptive size.
|
| 14 |
+
It masks out the last K values of an input. The masking value
|
| 15 |
+
goes from 1 to 0 gradually, so K can be learned with
|
| 16 |
+
back-propagation.
|
| 17 |
+
Args:
|
| 18 |
+
max_size: maximum size (i.e. input dimension)
|
| 19 |
+
ramp_size: size of the ramp going from 0 to 1
|
| 20 |
+
init_val: initial size proportion not to be masked out
|
| 21 |
+
shape: learn multiple sizes independent of each other
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, max_size, ramp_size, init_val=0, shape=(1,)):
|
| 25 |
+
nn.Module.__init__(self)
|
| 26 |
+
self._max_size = max_size
|
| 27 |
+
self._ramp_size = ramp_size
|
| 28 |
+
self.current_val = nn.Parameter(torch.zeros(*shape) + init_val)
|
| 29 |
+
mask_template = torch.linspace(1 - max_size, 0, steps=max_size)
|
| 30 |
+
self.register_buffer("mask_template", mask_template)
|
| 31 |
+
|
| 32 |
+
def forward(self, x):
|
| 33 |
+
mask = self.mask_template.float() + self.current_val.float() * self._max_size
|
| 34 |
+
mask = mask / self._ramp_size + 1
|
| 35 |
+
mask = mask.clamp(0, 1)
|
| 36 |
+
if x.size(-1) < self._max_size:
|
| 37 |
+
# the input could have been trimmed beforehand to save computation
|
| 38 |
+
mask = mask.narrow(-1, self._max_size - x.size(-1), x.size(-1))
|
| 39 |
+
x = (x * mask).type_as(x)
|
| 40 |
+
return x
|
| 41 |
+
|
| 42 |
+
def get_current_max_size(self, include_ramp=True):
|
| 43 |
+
current_size = math.ceil(self.current_val.max().item() * self._max_size)
|
| 44 |
+
if include_ramp:
|
| 45 |
+
current_size += self._ramp_size
|
| 46 |
+
current_size = max(0, min(self._max_size, current_size))
|
| 47 |
+
return current_size
|
| 48 |
+
|
| 49 |
+
def get_current_avg_size(self, include_ramp=True):
|
| 50 |
+
current_size = math.ceil(
|
| 51 |
+
self.current_val.float().mean().item() * self._max_size
|
| 52 |
+
)
|
| 53 |
+
if include_ramp:
|
| 54 |
+
current_size += self._ramp_size
|
| 55 |
+
current_size = max(0, min(self._max_size, current_size))
|
| 56 |
+
return current_size
|
| 57 |
+
|
| 58 |
+
def clamp_param(self):
|
| 59 |
+
"""this need to be called after each update"""
|
| 60 |
+
self.current_val.data.clamp_(0, 1)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class AdaptiveSpan(nn.Module):
|
| 64 |
+
"""Adaptive attention span for Transformerself.
|
| 65 |
+
This module learns an attention span length from data for each
|
| 66 |
+
self-attention head.
|
| 67 |
+
Args:
|
| 68 |
+
attn_span: maximum attention span
|
| 69 |
+
adapt_span_loss: loss coefficient for the span length
|
| 70 |
+
adapt_span_ramp: length of the masking ramp
|
| 71 |
+
adapt_span_init: initial size ratio
|
| 72 |
+
adapt_span_cache: adapt cache size to reduce memory usage
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
def __init__(
|
| 76 |
+
self,
|
| 77 |
+
attn_span,
|
| 78 |
+
adapt_span_ramp,
|
| 79 |
+
adapt_span_init,
|
| 80 |
+
n_head,
|
| 81 |
+
adapt_span_layer,
|
| 82 |
+
**kargs
|
| 83 |
+
):
|
| 84 |
+
nn.Module.__init__(self)
|
| 85 |
+
self._max_span = attn_span
|
| 86 |
+
self._n_head = n_head
|
| 87 |
+
self._adapt_span_layer = adapt_span_layer
|
| 88 |
+
if self._adapt_span_layer:
|
| 89 |
+
self._mask = AdaptiveMask(
|
| 90 |
+
max_size=self._max_span,
|
| 91 |
+
ramp_size=adapt_span_ramp,
|
| 92 |
+
init_val=adapt_span_init,
|
| 93 |
+
)
|
| 94 |
+
else:
|
| 95 |
+
self._mask = AdaptiveMask(
|
| 96 |
+
max_size=self._max_span,
|
| 97 |
+
ramp_size=adapt_span_ramp,
|
| 98 |
+
init_val=adapt_span_init,
|
| 99 |
+
shape=(n_head, 1, 1),
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
def forward(self, attn, normalize=True):
|
| 103 |
+
"""mask attention with the right span"""
|
| 104 |
+
# batch and head dimensions are merged together, so separate them first
|
| 105 |
+
self.clamp_param()
|
| 106 |
+
if self._adapt_span_layer:
|
| 107 |
+
attn = self._mask(attn)
|
| 108 |
+
else:
|
| 109 |
+
B = attn.size(0) # batch size
|
| 110 |
+
M = attn.size(1) # block size
|
| 111 |
+
attn = attn.reshape(B // self._n_head, self._n_head, M, -1)
|
| 112 |
+
attn = self._mask(attn)
|
| 113 |
+
attn = attn.view(B, M, -1)
|
| 114 |
+
return attn
|
| 115 |
+
|
| 116 |
+
def get_trim_len(self):
|
| 117 |
+
"""how much of memory can be trimmed to reduce computation"""
|
| 118 |
+
L = self._max_span
|
| 119 |
+
trim_len = min(L - 1, L - self._mask.get_current_max_size())
|
| 120 |
+
# too fine granularity might be bad for the memory management
|
| 121 |
+
trim_len = math.floor(trim_len / 64) * 64
|
| 122 |
+
return trim_len
|
| 123 |
+
|
| 124 |
+
def trim_memory(self, query, key, value, key_pe):
|
| 125 |
+
"""trim out unnecessary memory beforehand to reduce computation"""
|
| 126 |
+
trim_len = self.get_trim_len()
|
| 127 |
+
cache_size = key.size(1) - query.size(1)
|
| 128 |
+
trim_len_cache = trim_len - (self._max_span - cache_size)
|
| 129 |
+
if trim_len_cache > 0:
|
| 130 |
+
key = key[:, trim_len_cache:, :]
|
| 131 |
+
value = value[:, trim_len_cache:, :]
|
| 132 |
+
elif trim_len_cache < 0:
|
| 133 |
+
# cache is too short! this happens when validation resumes
|
| 134 |
+
# after a lot of updates.
|
| 135 |
+
key = F.pad(key, [0, 0, -trim_len_cache, 0])
|
| 136 |
+
value = F.pad(value, [0, 0, -trim_len_cache, 0])
|
| 137 |
+
if trim_len > 0:
|
| 138 |
+
if key_pe is not None:
|
| 139 |
+
key_pe = key_pe[:, :, trim_len:]
|
| 140 |
+
return key, value, key_pe
|
| 141 |
+
|
| 142 |
+
def get_cache_size(self):
|
| 143 |
+
"""determine how long the cache should be"""
|
| 144 |
+
trim_len = self.get_trim_len()
|
| 145 |
+
# give a buffer of 64 steps since a span might increase
|
| 146 |
+
# in future updates
|
| 147 |
+
return min(self._max_span, self._max_span - trim_len + 64)
|
| 148 |
+
|
| 149 |
+
def get_loss(self):
|
| 150 |
+
"""a loss term for regularizing the span length"""
|
| 151 |
+
return self._max_span * self._mask.current_val.float().mean()
|
| 152 |
+
|
| 153 |
+
def get_current_max_span(self):
|
| 154 |
+
return self._mask.get_current_max_size()
|
| 155 |
+
|
| 156 |
+
def get_current_avg_span(self):
|
| 157 |
+
return self._mask.get_current_avg_size()
|
| 158 |
+
|
| 159 |
+
def clamp_param(self):
|
| 160 |
+
self._mask.clamp_param()
|
data/fairseq/examples/adaptive_span/adaptive_span_loss.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from fairseq import utils
|
| 11 |
+
from fairseq.logging import metrics
|
| 12 |
+
from fairseq.criterions import register_criterion
|
| 13 |
+
from fairseq.criterions.cross_entropy import CrossEntropyCriterion
|
| 14 |
+
from fairseq.dataclass import FairseqDataclass
|
| 15 |
+
from omegaconf import II
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class AdaptiveSpanCriterionConfig(FairseqDataclass):
|
| 20 |
+
sentence_avg: bool = II("optimization.sentence_avg")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@register_criterion("adaptive_span_loss", dataclass=AdaptiveSpanCriterionConfig)
|
| 24 |
+
class AdaptiveSpanCriterion(CrossEntropyCriterion):
|
| 25 |
+
def __init__(self, task, sentence_avg):
|
| 26 |
+
super().__init__(task, sentence_avg)
|
| 27 |
+
|
| 28 |
+
def forward(self, model, sample, reduce=True):
|
| 29 |
+
"""Compute the loss for the given sample.
|
| 30 |
+
|
| 31 |
+
Returns a tuple with three elements:
|
| 32 |
+
1) the loss here is summed, different from the adaptive span code
|
| 33 |
+
2) the sample size, which is used as the denominator for the gradient
|
| 34 |
+
3) logging outputs to display while training
|
| 35 |
+
"""
|
| 36 |
+
net_output = model(**sample["net_input"])
|
| 37 |
+
loss, aux_loss, avg_span, max_span = self.compute_loss(
|
| 38 |
+
model, net_output, sample, reduce=reduce
|
| 39 |
+
)
|
| 40 |
+
sample_size = (
|
| 41 |
+
sample["target"].size(0) if self.sentence_avg else sample["ntokens"]
|
| 42 |
+
)
|
| 43 |
+
loss /= sample_size
|
| 44 |
+
total_loss = loss + aux_loss
|
| 45 |
+
sample_size = 1
|
| 46 |
+
|
| 47 |
+
logging_output = {
|
| 48 |
+
"loss": loss.data,
|
| 49 |
+
"ntokens": sample["ntokens"],
|
| 50 |
+
"nsentences": sample["target"].size(0),
|
| 51 |
+
"sample_size": sample_size,
|
| 52 |
+
"total_loss": total_loss.data,
|
| 53 |
+
"avg_span": avg_span * sample_size,
|
| 54 |
+
"max_span": max_span * sample_size,
|
| 55 |
+
}
|
| 56 |
+
return total_loss, sample_size, logging_output
|
| 57 |
+
|
| 58 |
+
def compute_loss(self, model, net_output, sample, reduce=True):
|
| 59 |
+
loss, _ = super().compute_loss(model, net_output, sample, reduce)
|
| 60 |
+
aux_loss = model.get_aux_loss()
|
| 61 |
+
avg_span = model.get_current_avg_span()
|
| 62 |
+
max_span = model.get_current_max_span()
|
| 63 |
+
return loss, aux_loss, avg_span, max_span
|
| 64 |
+
|
| 65 |
+
@staticmethod
|
| 66 |
+
def reduce_metrics(logging_outputs) -> None:
|
| 67 |
+
"""Aggregate logging outputs from data parallel training."""
|
| 68 |
+
loss_sum = sum(log.get("loss", 0) for log in logging_outputs)
|
| 69 |
+
ntokens = sum(log.get("ntokens", 0) for log in logging_outputs)
|
| 70 |
+
sample_size = sum(log.get("sample_size", 0) for log in logging_outputs)
|
| 71 |
+
total_loss_sum = sum(log.get("total_loss", 0) for log in logging_outputs)
|
| 72 |
+
avg_span_sum = sum(log.get("avg_span", 0) for log in logging_outputs)
|
| 73 |
+
max_span_sum = sum(log.get("max_span", 0) for log in logging_outputs)
|
| 74 |
+
|
| 75 |
+
# we divide by log(2) to convert the loss from base e to base 2
|
| 76 |
+
metrics.log_scalar(
|
| 77 |
+
"loss", loss_sum / sample_size / math.log(2), sample_size, round=3
|
| 78 |
+
)
|
| 79 |
+
metrics.log_scalar("avg_span", avg_span_sum / sample_size, sample_size, round=3)
|
| 80 |
+
metrics.log_scalar("max_span", max_span_sum / sample_size, sample_size, round=3)
|
| 81 |
+
# total loss contains the L1 norm on adaptive-span
|
| 82 |
+
metrics.log_scalar(
|
| 83 |
+
"total_loss",
|
| 84 |
+
total_loss_sum / sample_size / math.log(2),
|
| 85 |
+
sample_size,
|
| 86 |
+
round=3,
|
| 87 |
+
)
|
| 88 |
+
if sample_size != ntokens:
|
| 89 |
+
metrics.log_scalar(
|
| 90 |
+
"nll_loss", loss_sum / ntokens / math.log(2), ntokens, round=3
|
| 91 |
+
)
|
| 92 |
+
metrics.log_derived(
|
| 93 |
+
"ppl", lambda meters: utils.get_perplexity(meters["nll_loss"].avg)
|
| 94 |
+
)
|
| 95 |
+
else:
|
| 96 |
+
metrics.log_derived(
|
| 97 |
+
"ppl", lambda meters: utils.get_perplexity(meters["loss"].avg)
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
@staticmethod
|
| 101 |
+
def logging_outputs_can_be_summed() -> bool:
|
| 102 |
+
"""
|
| 103 |
+
Whether the logging outputs returned by `forward` can be summed
|
| 104 |
+
across workers prior to calling `reduce_metrics`. Setting this
|
| 105 |
+
to True will improves distributed training speed.
|
| 106 |
+
"""
|
| 107 |
+
return True
|
data/fairseq/examples/adaptive_span/adaptive_span_model.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import math
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
import torch.nn.functional as F
|
| 12 |
+
|
| 13 |
+
from fairseq.modules.layer_norm import LayerNorm
|
| 14 |
+
|
| 15 |
+
from .adaptive_span_attention import AdaptiveSpan
|
| 16 |
+
|
| 17 |
+
# Size notations:
|
| 18 |
+
# B = batch_size, H = d_model, M = block_size, L = attn_span
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _skew(X, pad_value):
|
| 22 |
+
"""shift every row 1 step to right"""
|
| 23 |
+
# X = B x M x L
|
| 24 |
+
B, M, L = X.size()
|
| 25 |
+
X = F.pad(X, (0, M + 1), value=pad_value) # B x M x (L+M+1)
|
| 26 |
+
X = X.view(B, -1) # B x ML+MM+M
|
| 27 |
+
X = X[:, :-M] # B x ML+MM
|
| 28 |
+
X = X.view(B, M, M + L) # B x M x L+M
|
| 29 |
+
return X
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _unskew(X):
|
| 33 |
+
"""reverse _skew operation"""
|
| 34 |
+
# X = B x M x L+M
|
| 35 |
+
B, M, L = X.size()
|
| 36 |
+
L -= M
|
| 37 |
+
X = X.view(B, -1) # B x ML+MM
|
| 38 |
+
X = F.pad(X, (0, M)) # B x ML+MM+M
|
| 39 |
+
X = X.view(B, M, M + L + 1) # B x M x L+M+1
|
| 40 |
+
X = X[:, :, :L] # B x M x L
|
| 41 |
+
return X
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class SeqAttention(nn.Module):
|
| 45 |
+
"""Sequential self-attention layer.
|
| 46 |
+
Each token will attend to its previous fixed number of steps.
|
| 47 |
+
Note that attention doesn't include the current step itself.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
def __init__(self, d_model, n_head, attn_span, dropout, adapt_span_layer, **kargs):
|
| 51 |
+
nn.Module.__init__(self)
|
| 52 |
+
self.dropout = nn.Dropout(dropout)
|
| 53 |
+
self.d_model = d_model # size of a single head
|
| 54 |
+
self.attn_span = attn_span
|
| 55 |
+
self.adaptive_span = AdaptiveSpan(
|
| 56 |
+
attn_span=attn_span,
|
| 57 |
+
n_head=n_head,
|
| 58 |
+
adapt_span_layer=adapt_span_layer,
|
| 59 |
+
**kargs
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
def forward(self, query, key, value, key_pe):
|
| 63 |
+
# query size = B x M x H
|
| 64 |
+
# key, value sizes = B x (M+L) x H
|
| 65 |
+
|
| 66 |
+
key, value, key_pe = self.adaptive_span.trim_memory(query, key, value, key_pe)
|
| 67 |
+
|
| 68 |
+
# compute attention from context
|
| 69 |
+
# B x M (dest) x (M+L) (src)
|
| 70 |
+
attn_cont = torch.matmul(query, key.transpose(-1, -2))
|
| 71 |
+
attn_cont = _unskew(attn_cont) # B x M x L
|
| 72 |
+
|
| 73 |
+
# compute the effect of position embedding
|
| 74 |
+
attn_pos = torch.matmul(query, key_pe) # B x M x L_pos
|
| 75 |
+
attn = attn_cont + attn_pos
|
| 76 |
+
|
| 77 |
+
attn = attn / math.sqrt(self.d_model) # B x M X L_pos
|
| 78 |
+
|
| 79 |
+
attn = F.softmax(attn.float(), dim=-1).type_as(attn)
|
| 80 |
+
|
| 81 |
+
# trim attention lengths according to the learned span
|
| 82 |
+
attn = self.adaptive_span(attn)
|
| 83 |
+
|
| 84 |
+
attn = self.dropout(attn) # B x M X L_pos
|
| 85 |
+
|
| 86 |
+
attn_cont = _skew(attn, 0) # B x M X (L+M)
|
| 87 |
+
out = torch.matmul(attn_cont, value) # B x M x H
|
| 88 |
+
return out
|
| 89 |
+
|
| 90 |
+
def get_cache_size(self):
|
| 91 |
+
return self.adaptive_span.get_cache_size()
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class MultiHeadSeqAttention(nn.Module):
|
| 95 |
+
def __init__(self, d_model, n_head, **kargs):
|
| 96 |
+
nn.Module.__init__(self)
|
| 97 |
+
assert d_model % n_head == 0
|
| 98 |
+
self.n_head = n_head
|
| 99 |
+
self.head_dim = d_model // n_head
|
| 100 |
+
self.attn = SeqAttention(d_model=self.head_dim, n_head=n_head, **kargs)
|
| 101 |
+
self.proj_query = nn.Linear(d_model, d_model, bias=False)
|
| 102 |
+
nn.init.xavier_normal_(self.proj_query.weight)
|
| 103 |
+
self.proj_out = nn.Linear(d_model, d_model, bias=False)
|
| 104 |
+
nn.init.xavier_normal_(self.proj_out.weight)
|
| 105 |
+
self.proj_val = nn.Linear(d_model, d_model, bias=False)
|
| 106 |
+
nn.init.xavier_normal_(self.proj_val.weight)
|
| 107 |
+
self.proj_key = nn.Linear(d_model, d_model, bias=False)
|
| 108 |
+
nn.init.xavier_normal_(self.proj_key.weight)
|
| 109 |
+
|
| 110 |
+
def head_reshape(self, x):
|
| 111 |
+
K = self.n_head
|
| 112 |
+
D = self.head_dim
|
| 113 |
+
x = x.view(x.size()[:-1] + (K, D)) # B x (M+L) x K x D
|
| 114 |
+
x = x.transpose(1, 2).contiguous() # B x K x (M+L) x D
|
| 115 |
+
x = x.view(-1, x.size(-2), x.size(-1)) # B_K x (M+L) x D
|
| 116 |
+
return x
|
| 117 |
+
|
| 118 |
+
def forward(self, query, key, value, key_pe):
|
| 119 |
+
B = query.size(0)
|
| 120 |
+
K = self.n_head
|
| 121 |
+
D = self.head_dim
|
| 122 |
+
M = query.size(1)
|
| 123 |
+
|
| 124 |
+
query = self.proj_query(query)
|
| 125 |
+
query = self.head_reshape(query)
|
| 126 |
+
value = self.proj_val(value)
|
| 127 |
+
value = self.head_reshape(value)
|
| 128 |
+
key = self.proj_key(key)
|
| 129 |
+
key = self.head_reshape(key)
|
| 130 |
+
|
| 131 |
+
out = self.attn(query, key, value, key_pe) # B_K x M x D
|
| 132 |
+
out = out.view(B, K, M, D) # B x K x M x D
|
| 133 |
+
out = out.transpose(1, 2).contiguous() # B x M x K x D
|
| 134 |
+
out = out.view(B, M, -1) # B x M x K_D
|
| 135 |
+
out = self.proj_out(out)
|
| 136 |
+
return out
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
class FeedForwardLayer(nn.Module):
|
| 140 |
+
def __init__(self, d_model, d_inner, dropout, **kargs):
|
| 141 |
+
nn.Module.__init__(self)
|
| 142 |
+
self.fc1 = nn.Linear(d_model, d_inner)
|
| 143 |
+
self.fc2 = nn.Linear(d_inner, d_model)
|
| 144 |
+
nn.init.xavier_uniform_(self.fc1.weight)
|
| 145 |
+
nn.init.xavier_uniform_(self.fc2.weight)
|
| 146 |
+
self.dropout = nn.Dropout(dropout)
|
| 147 |
+
|
| 148 |
+
def forward(self, h):
|
| 149 |
+
h1 = F.relu(self.fc1(h))
|
| 150 |
+
h1 = self.dropout(h1)
|
| 151 |
+
h2 = self.fc2(h1)
|
| 152 |
+
return h2
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class TransformerSeqLayer(nn.Module):
|
| 156 |
+
def __init__(self, d_model, **kargs):
|
| 157 |
+
nn.Module.__init__(self)
|
| 158 |
+
self.attn = MultiHeadSeqAttention(d_model=d_model, **kargs)
|
| 159 |
+
self.norm1 = LayerNorm(d_model)
|
| 160 |
+
self.ff = FeedForwardLayer(d_model=d_model, **kargs)
|
| 161 |
+
self.norm2 = LayerNorm(d_model)
|
| 162 |
+
|
| 163 |
+
def forward(self, h, h_cache, key_pe):
|
| 164 |
+
# h = B x M x H
|
| 165 |
+
# h_cache = B x L x H
|
| 166 |
+
h_all = torch.cat([h_cache, h], dim=1) # B x (M+L) x H
|
| 167 |
+
attn_out = self.attn(h, h_all, h_all, key_pe)
|
| 168 |
+
h = self.norm1(h + attn_out) # B x M x H
|
| 169 |
+
if self.ff is not None:
|
| 170 |
+
ff_out = self.ff(h)
|
| 171 |
+
out = self.norm2(h + ff_out) # B x M x H
|
| 172 |
+
else:
|
| 173 |
+
out = h
|
| 174 |
+
return out
|
| 175 |
+
|
| 176 |
+
def get_cache_size(self):
|
| 177 |
+
return self.attn.attn.get_cache_size()
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
class TransformerSeq(nn.Module):
|
| 181 |
+
def __init__(
|
| 182 |
+
self,
|
| 183 |
+
vocab_size,
|
| 184 |
+
d_model,
|
| 185 |
+
n_head,
|
| 186 |
+
n_layer,
|
| 187 |
+
attn_span,
|
| 188 |
+
emb_dropout,
|
| 189 |
+
aux_loss_scaler,
|
| 190 |
+
adapt_span_layer,
|
| 191 |
+
**kargs
|
| 192 |
+
):
|
| 193 |
+
nn.Module.__init__(self)
|
| 194 |
+
# token embeddings
|
| 195 |
+
self.in_emb = nn.Embedding(vocab_size, d_model)
|
| 196 |
+
nn.init.normal_(self.in_emb.weight, mean=0, std=d_model ** -0.5)
|
| 197 |
+
self.out_emb = nn.Linear(d_model, vocab_size)
|
| 198 |
+
self.aux_loss_scaler = aux_loss_scaler
|
| 199 |
+
if emb_dropout > 0:
|
| 200 |
+
self.emb_dropout = nn.Dropout(emb_dropout)
|
| 201 |
+
else:
|
| 202 |
+
self.emb_dropout = None
|
| 203 |
+
# position embeddings
|
| 204 |
+
self.key_pe = nn.Parameter(torch.randn(1, d_model // n_head, attn_span))
|
| 205 |
+
|
| 206 |
+
self.layers = nn.ModuleList()
|
| 207 |
+
self.layers.extend(
|
| 208 |
+
TransformerSeqLayer(
|
| 209 |
+
d_model=d_model,
|
| 210 |
+
n_head=n_head,
|
| 211 |
+
attn_span=attn_span,
|
| 212 |
+
adapt_span_layer=adapt_span_layer,
|
| 213 |
+
**kargs
|
| 214 |
+
)
|
| 215 |
+
for _ in range(n_layer)
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
def forward(self, x, h_cache, target=None):
|
| 219 |
+
# x size = B x M
|
| 220 |
+
block_size = x.size(1)
|
| 221 |
+
h = self.in_emb(x) # B x M x H
|
| 222 |
+
if self.emb_dropout is not None:
|
| 223 |
+
h = self.emb_dropout(h)
|
| 224 |
+
|
| 225 |
+
h_cache_next = []
|
| 226 |
+
for l, layer in enumerate(self.layers):
|
| 227 |
+
cache_size = layer.attn.attn.get_cache_size()
|
| 228 |
+
if cache_size > block_size:
|
| 229 |
+
h_cache_next_l = torch.cat(
|
| 230 |
+
[h_cache[l][:, -cache_size + block_size :, :], h], dim=1
|
| 231 |
+
).detach()
|
| 232 |
+
else:
|
| 233 |
+
h_cache_next_l = h[:, -cache_size:, :].detach()
|
| 234 |
+
h_cache_next.append(h_cache_next_l)
|
| 235 |
+
h = layer(h, h_cache[l], self.key_pe) # B x M x H
|
| 236 |
+
|
| 237 |
+
if self.emb_dropout is not None:
|
| 238 |
+
h = self.emb_dropout(h)
|
| 239 |
+
|
| 240 |
+
out = F.log_softmax(self.out_emb(h).float(), dim=-1).type_as(h)
|
| 241 |
+
dummy_loss = None
|
| 242 |
+
|
| 243 |
+
return out, h_cache_next, dummy_loss
|
| 244 |
+
|
| 245 |
+
def get_aux_loss(self):
|
| 246 |
+
loss = 0.0
|
| 247 |
+
for layer in self.layers:
|
| 248 |
+
loss += layer.attn.attn.adaptive_span.get_loss()
|
| 249 |
+
return self.aux_loss_scaler * loss
|
| 250 |
+
|
| 251 |
+
def get_current_max_span(self):
|
| 252 |
+
max_span = 0.0
|
| 253 |
+
for layer in self.layers:
|
| 254 |
+
max_span = max(
|
| 255 |
+
max_span, layer.attn.attn.adaptive_span.get_current_max_span()
|
| 256 |
+
)
|
| 257 |
+
return max_span
|
| 258 |
+
|
| 259 |
+
def get_current_avg_span(self):
|
| 260 |
+
avg_span = 0.0
|
| 261 |
+
for layer in self.layers:
|
| 262 |
+
avg_span += layer.attn.attn.adaptive_span.get_current_avg_span()
|
| 263 |
+
return avg_span / len(self.layers)
|
data/fairseq/examples/adaptive_span/adaptive_span_model_wrapper.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from typing import Dict, List, Optional
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from fairseq.dataclass import FairseqDataclass
|
| 12 |
+
from fairseq.models import (
|
| 13 |
+
FairseqIncrementalDecoder,
|
| 14 |
+
FairseqLanguageModel,
|
| 15 |
+
register_model,
|
| 16 |
+
)
|
| 17 |
+
from .adaptive_span_model import TransformerSeq as AdaptiveSpanTransformerModel
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
logger = logging.getLogger(__name__)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@dataclass
|
| 24 |
+
class AdaptiveSpanSmallConfig(FairseqDataclass):
|
| 25 |
+
# defaults come from https://github.com/facebookresearch/adaptive-span/blob/master/experiments/enwik8_small.sh
|
| 26 |
+
vocab_size: int = 50
|
| 27 |
+
d_model: int = 256
|
| 28 |
+
n_head: int = 4
|
| 29 |
+
d_inner: int = 1024
|
| 30 |
+
n_layer: int = 8
|
| 31 |
+
attn_span: int = 1024
|
| 32 |
+
dropout: float = 0.0
|
| 33 |
+
emb_dropout: float = 0.0
|
| 34 |
+
adapt_span_ramp: int = 32
|
| 35 |
+
adapt_span_init: float = 0.0
|
| 36 |
+
aux_loss_scaler: float = 0.000002
|
| 37 |
+
adapt_span_layer: bool = False
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@register_model("adaptive_span", dataclass=AdaptiveSpanSmallConfig)
|
| 41 |
+
class AdaptiveSpanTransformer(FairseqLanguageModel):
|
| 42 |
+
@classmethod
|
| 43 |
+
def build_model(cls, cfg: AdaptiveSpanSmallConfig, task):
|
| 44 |
+
return cls(AdaptiveSpanDecoder(cfg, task))
|
| 45 |
+
|
| 46 |
+
def get_aux_loss(self):
|
| 47 |
+
return self.decoder.get_aux_loss()
|
| 48 |
+
|
| 49 |
+
def get_current_max_span(self):
|
| 50 |
+
return self.decoder.get_current_max_span()
|
| 51 |
+
|
| 52 |
+
def get_current_avg_span(self):
|
| 53 |
+
return self.decoder.get_current_avg_span()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class AdaptiveSpanDecoder(FairseqIncrementalDecoder):
|
| 57 |
+
def __init__(self, cfg, task):
|
| 58 |
+
|
| 59 |
+
super().__init__(task.target_dictionary)
|
| 60 |
+
|
| 61 |
+
self.config = cfg
|
| 62 |
+
config = AdaptiveSpanSmallConfig(
|
| 63 |
+
vocab_size=len(task.target_dictionary),
|
| 64 |
+
d_model=cfg.d_model,
|
| 65 |
+
n_head=cfg.n_head,
|
| 66 |
+
d_inner=cfg.d_inner,
|
| 67 |
+
n_layer=cfg.n_layer,
|
| 68 |
+
attn_span=cfg.attn_span,
|
| 69 |
+
dropout=cfg.dropout,
|
| 70 |
+
emb_dropout=cfg.emb_dropout,
|
| 71 |
+
adapt_span_ramp=cfg.adapt_span_ramp,
|
| 72 |
+
adapt_span_init=cfg.adapt_span_init,
|
| 73 |
+
aux_loss_scaler=cfg.aux_loss_scaler,
|
| 74 |
+
adapt_span_layer=cfg.adapt_span_layer,
|
| 75 |
+
)
|
| 76 |
+
logger.info(config)
|
| 77 |
+
self.model = AdaptiveSpanTransformerModel(**config.__dict__)
|
| 78 |
+
|
| 79 |
+
self._mems = None
|
| 80 |
+
|
| 81 |
+
def forward(
|
| 82 |
+
self,
|
| 83 |
+
src_tokens,
|
| 84 |
+
incremental_state: Optional[Dict[str, List[torch.Tensor]]] = None,
|
| 85 |
+
encoder_out=None,
|
| 86 |
+
):
|
| 87 |
+
bsz = src_tokens.size(0)
|
| 88 |
+
if incremental_state is not None: # used during inference
|
| 89 |
+
mems = self.get_incremental_state("mems")
|
| 90 |
+
src_tokens = src_tokens[:, -1:] # only keep the most recent token
|
| 91 |
+
else:
|
| 92 |
+
mems = self._mems
|
| 93 |
+
|
| 94 |
+
if mems is None:
|
| 95 |
+
# first time init
|
| 96 |
+
mems = self.init_hid_cache(bsz)
|
| 97 |
+
output = self.model(x=src_tokens, h_cache=mems,)
|
| 98 |
+
if incremental_state is not None:
|
| 99 |
+
self.set_incremental_state(incremental_state, "mems", output[1])
|
| 100 |
+
else:
|
| 101 |
+
self._mems = output[1]
|
| 102 |
+
return (output[0],)
|
| 103 |
+
|
| 104 |
+
def max_positions(self):
|
| 105 |
+
return self.config.attn_span
|
| 106 |
+
|
| 107 |
+
def init_hid_cache(self, batch_sz):
|
| 108 |
+
hid = []
|
| 109 |
+
for layer in self.model.layers:
|
| 110 |
+
param = next(self.model.parameters())
|
| 111 |
+
h = torch.zeros(
|
| 112 |
+
batch_sz,
|
| 113 |
+
layer.get_cache_size(),
|
| 114 |
+
self.config.d_model,
|
| 115 |
+
dtype=param.dtype,
|
| 116 |
+
device=param.device,
|
| 117 |
+
)
|
| 118 |
+
hid.append(h)
|
| 119 |
+
return hid
|
| 120 |
+
|
| 121 |
+
def get_aux_loss(self):
|
| 122 |
+
return self.model.get_aux_loss()
|
| 123 |
+
|
| 124 |
+
def get_current_max_span(self):
|
| 125 |
+
return self.model.get_current_max_span()
|
| 126 |
+
|
| 127 |
+
def get_current_avg_span(self):
|
| 128 |
+
return self.model.get_current_avg_span()
|
| 129 |
+
|
| 130 |
+
def reorder_incremental_state(
|
| 131 |
+
self,
|
| 132 |
+
incremental_state: Dict[str, Dict[str, Optional[torch.Tensor]]],
|
| 133 |
+
new_order: torch.Tensor,
|
| 134 |
+
):
|
| 135 |
+
"""Reorder incremental state.
|
| 136 |
+
|
| 137 |
+
This will be called when the order of the input has changed from the
|
| 138 |
+
previous time step. A typical use case is beam search, where the input
|
| 139 |
+
order changes between time steps based on the selection of beams.
|
| 140 |
+
"""
|
| 141 |
+
raise NotImplementedError("This is required for generation/beam search")
|
| 142 |
+
# mems = self.get_incremental_state(incremental_state, "mems")
|
| 143 |
+
# if mems is not None:
|
| 144 |
+
# new_mems = [mems_i.index_select(1, new_order) for mems_i in mems]
|
| 145 |
+
# self.set_incremental_state(incremental_state, "mems", new_mems)
|
data/fairseq/examples/adaptive_span/truncated_bptt_lm_task.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import os
|
| 8 |
+
from dataclasses import dataclass, field
|
| 9 |
+
from typing import List, Optional, Tuple
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from fairseq import utils
|
| 13 |
+
from fairseq.data import (
|
| 14 |
+
Dictionary,
|
| 15 |
+
TokenBlockDataset,
|
| 16 |
+
data_utils,
|
| 17 |
+
iterators,
|
| 18 |
+
)
|
| 19 |
+
from fairseq.dataclass import FairseqDataclass
|
| 20 |
+
from fairseq.distributed import utils as dist_utils
|
| 21 |
+
from fairseq.tasks import FairseqTask, register_task
|
| 22 |
+
from omegaconf import II
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@dataclass
|
| 29 |
+
class TruncatedBPTTLMConfig(FairseqDataclass):
|
| 30 |
+
data: str = field(default="???", metadata={"help": "path to data directory"})
|
| 31 |
+
tokens_per_sample: int = field(
|
| 32 |
+
default=1024, metadata={"help": "max number of tokens per sequence"},
|
| 33 |
+
)
|
| 34 |
+
batch_size: int = II("dataset.batch_size")
|
| 35 |
+
# Some models use *max_target_positions* to know how many positional
|
| 36 |
+
# embeddings to learn. We use II(...) to make it default to
|
| 37 |
+
# *tokens_per_sample*, but in principle there could be more positional
|
| 38 |
+
# embeddings than tokens in a single batch. This may also be irrelevant for
|
| 39 |
+
# custom model implementations.
|
| 40 |
+
max_target_positions: int = II("task.tokens_per_sample")
|
| 41 |
+
# these will be populated automatically if not provided
|
| 42 |
+
data_parallel_rank: Optional[int] = None
|
| 43 |
+
data_parallel_size: Optional[int] = None
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@register_task("truncated_bptt_lm", dataclass=TruncatedBPTTLMConfig)
|
| 47 |
+
class TruncatedBPTTLMTask(FairseqTask):
|
| 48 |
+
def __init__(self, cfg: TruncatedBPTTLMConfig):
|
| 49 |
+
super().__init__(cfg)
|
| 50 |
+
|
| 51 |
+
if cfg.data_parallel_rank is None or cfg.data_parallel_size is None:
|
| 52 |
+
if torch.distributed.is_initialized():
|
| 53 |
+
cfg.data_parallel_rank = dist_utils.get_data_parallel_rank()
|
| 54 |
+
cfg.data_parallel_size = dist_utils.get_data_parallel_world_size()
|
| 55 |
+
else:
|
| 56 |
+
cfg.data_parallel_rank = 0
|
| 57 |
+
cfg.data_parallel_size = 1
|
| 58 |
+
|
| 59 |
+
# load the dictionary
|
| 60 |
+
paths = utils.split_paths(cfg.data)
|
| 61 |
+
assert len(paths) > 0
|
| 62 |
+
self.dictionary = Dictionary.load(os.path.join(paths[0], "dict.txt"))
|
| 63 |
+
logger.info("dictionary: {} types".format(len(self.dictionary)))
|
| 64 |
+
|
| 65 |
+
def load_dataset(self, split, epoch=1, combine=False, **kwargs):
|
| 66 |
+
"""Load a given dataset split (e.g., train, valid, test)"""
|
| 67 |
+
|
| 68 |
+
# support sharded datasets
|
| 69 |
+
paths = utils.split_paths(self.cfg.data)
|
| 70 |
+
assert len(paths) > 0
|
| 71 |
+
data_path = paths[(epoch - 1) % len(paths)]
|
| 72 |
+
split_path = os.path.join(data_path, split)
|
| 73 |
+
|
| 74 |
+
# each element of *data* will be a tensorized line from the original
|
| 75 |
+
# text dataset, similar to ``open(split_path).readlines()``
|
| 76 |
+
data = data_utils.load_indexed_dataset(
|
| 77 |
+
split_path, self.dictionary, combine=combine
|
| 78 |
+
)
|
| 79 |
+
if data is None:
|
| 80 |
+
raise FileNotFoundError(
|
| 81 |
+
"Dataset not found: {} ({})".format(split, split_path)
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# this is similar to ``data.view(-1).split(tokens_per_sample)``
|
| 85 |
+
data = TokenBlockDataset(
|
| 86 |
+
data,
|
| 87 |
+
data.sizes,
|
| 88 |
+
block_size=self.cfg.tokens_per_sample,
|
| 89 |
+
pad=None, # unused
|
| 90 |
+
eos=None, # unused
|
| 91 |
+
break_mode="none",
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
self.datasets[split] = TruncatedBPTTDataset(
|
| 95 |
+
data=data,
|
| 96 |
+
bsz_per_shard=self.cfg.batch_size,
|
| 97 |
+
shard_id=self.cfg.data_parallel_rank,
|
| 98 |
+
num_shards=self.cfg.data_parallel_size,
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
def dataset(self, split):
|
| 102 |
+
return self.datasets[split]
|
| 103 |
+
|
| 104 |
+
def get_batch_iterator(
|
| 105 |
+
self,
|
| 106 |
+
dataset,
|
| 107 |
+
num_workers=0,
|
| 108 |
+
epoch=1,
|
| 109 |
+
data_buffer_size=0,
|
| 110 |
+
skip_remainder_batch=False,
|
| 111 |
+
**kwargs
|
| 112 |
+
):
|
| 113 |
+
return iterators.EpochBatchIterator(
|
| 114 |
+
dataset=dataset,
|
| 115 |
+
collate_fn=self._collate_fn,
|
| 116 |
+
num_workers=num_workers,
|
| 117 |
+
epoch=epoch,
|
| 118 |
+
buffer_size=data_buffer_size,
|
| 119 |
+
# we don't use the batching functionality from EpochBatchIterator;
|
| 120 |
+
# instead every item in *dataset* is a whole batch
|
| 121 |
+
batch_sampler=[[i] for i in range(len(dataset))],
|
| 122 |
+
disable_shuffling=True,
|
| 123 |
+
skip_remainder_batch=skip_remainder_batch,
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
def _collate_fn(self, items: List[List[torch.Tensor]]):
|
| 127 |
+
# we don't use fairseq's batching functionality, so we expect a single
|
| 128 |
+
# Tensor of type List[torch.Tensor]
|
| 129 |
+
assert len(items) == 1
|
| 130 |
+
|
| 131 |
+
# item will have shape B x T (the last batch may have length < T)
|
| 132 |
+
id, item = items[0]
|
| 133 |
+
item = data_utils.collate_tokens(item, pad_idx=self.source_dictionary.pad())
|
| 134 |
+
B, T = item.size()
|
| 135 |
+
|
| 136 |
+
# shift item one position over and append a padding token for the target
|
| 137 |
+
target = torch.nn.functional.pad(
|
| 138 |
+
item[:, 1:], (0, 1, 0, 0), value=self.target_dictionary.pad()
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# fairseq expects batches to have the following structure
|
| 142 |
+
return {
|
| 143 |
+
"id": torch.tensor([id] * item.size(0)),
|
| 144 |
+
"net_input": {"src_tokens": item,},
|
| 145 |
+
"target": target,
|
| 146 |
+
"nsentences": item.size(0),
|
| 147 |
+
"ntokens": item.numel(),
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
def build_dataset_for_inference(
|
| 151 |
+
self, src_tokens: List[torch.Tensor], src_lengths: List[int], **kwargs
|
| 152 |
+
) -> torch.utils.data.Dataset:
|
| 153 |
+
eos = self.source_dictionary.eos()
|
| 154 |
+
dataset = TokenBlockDataset(
|
| 155 |
+
src_tokens,
|
| 156 |
+
src_lengths,
|
| 157 |
+
block_size=None, # ignored for "eos" break mode
|
| 158 |
+
pad=self.source_dictionary.pad(),
|
| 159 |
+
eos=eos,
|
| 160 |
+
break_mode="eos",
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
class Dataset(torch.utils.data.Dataset):
|
| 164 |
+
def __getitem__(self, i):
|
| 165 |
+
item = dataset[i]
|
| 166 |
+
if item[-1] == eos:
|
| 167 |
+
# remove eos to support generating with a prefix
|
| 168 |
+
item = item[:-1]
|
| 169 |
+
return (i, [item])
|
| 170 |
+
|
| 171 |
+
def __len__(self):
|
| 172 |
+
return len(dataset)
|
| 173 |
+
|
| 174 |
+
return Dataset()
|
| 175 |
+
|
| 176 |
+
def inference_step(
|
| 177 |
+
self, generator, models, sample, prefix_tokens=None, constraints=None
|
| 178 |
+
):
|
| 179 |
+
with torch.no_grad():
|
| 180 |
+
if constraints is not None:
|
| 181 |
+
raise NotImplementedError
|
| 182 |
+
|
| 183 |
+
# SequenceGenerator doesn't use *src_tokens* directly, we need to
|
| 184 |
+
# pass the *prefix_tokens* argument instead.
|
| 185 |
+
if prefix_tokens is None and sample["net_input"]["src_tokens"].nelement():
|
| 186 |
+
prefix_tokens = sample["net_input"]["src_tokens"]
|
| 187 |
+
|
| 188 |
+
# begin generation with the end-of-sentence token
|
| 189 |
+
bos_token = self.source_dictionary.eos()
|
| 190 |
+
|
| 191 |
+
return generator.generate(
|
| 192 |
+
models, sample, prefix_tokens=prefix_tokens, bos_token=bos_token
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
def eval_lm_dataloader(
|
| 196 |
+
self,
|
| 197 |
+
dataset,
|
| 198 |
+
max_tokens: Optional[int] = 36000,
|
| 199 |
+
batch_size: Optional[int] = None,
|
| 200 |
+
max_positions: Optional[int] = None,
|
| 201 |
+
num_shards: int = 1,
|
| 202 |
+
shard_id: int = 0,
|
| 203 |
+
num_workers: int = 1,
|
| 204 |
+
data_buffer_size: int = 10,
|
| 205 |
+
context_window: int = 0,
|
| 206 |
+
):
|
| 207 |
+
if context_window > 0:
|
| 208 |
+
raise NotImplementedError(
|
| 209 |
+
"Transformer-XL doesn't need --context-window, try "
|
| 210 |
+
"--model-overrides '{\"mem_len\":42}' instead "
|
| 211 |
+
)
|
| 212 |
+
return self.get_batch_iterator(
|
| 213 |
+
dataset=dataset,
|
| 214 |
+
max_tokens=max_tokens,
|
| 215 |
+
max_sentences=batch_size,
|
| 216 |
+
max_positions=max_positions,
|
| 217 |
+
ignore_invalid_inputs=True,
|
| 218 |
+
num_shards=num_shards,
|
| 219 |
+
shard_id=shard_id,
|
| 220 |
+
num_workers=num_workers,
|
| 221 |
+
data_buffer_size=data_buffer_size,
|
| 222 |
+
).next_epoch_itr(shuffle=False)
|
| 223 |
+
|
| 224 |
+
@property
|
| 225 |
+
def source_dictionary(self):
|
| 226 |
+
return self.dictionary
|
| 227 |
+
|
| 228 |
+
@property
|
| 229 |
+
def target_dictionary(self):
|
| 230 |
+
return self.dictionary
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class TruncatedBPTTDataset(torch.utils.data.Dataset):
|
| 234 |
+
def __init__(
|
| 235 |
+
self,
|
| 236 |
+
data: List[torch.Tensor], # ordered list of items
|
| 237 |
+
bsz_per_shard, # number of items processed per GPUs per forward
|
| 238 |
+
shard_id, # current GPU ID
|
| 239 |
+
num_shards, # number of GPUs
|
| 240 |
+
):
|
| 241 |
+
super().__init__()
|
| 242 |
+
self.data = data
|
| 243 |
+
|
| 244 |
+
def batchify(data, bsz):
|
| 245 |
+
# Work out how cleanly we can divide the dataset into bsz parts.
|
| 246 |
+
nbatch = data.size(0) // bsz
|
| 247 |
+
# Trim off any extra elements that wouldn't cleanly fit (remainders).
|
| 248 |
+
data = data.narrow(0, 0, nbatch * bsz)
|
| 249 |
+
# Evenly divide the data across the bsz batches.
|
| 250 |
+
data = data.view(bsz, -1).contiguous()
|
| 251 |
+
return data
|
| 252 |
+
|
| 253 |
+
# total number of sequences processed by all GPUs in each forward pass
|
| 254 |
+
global_batch_size = bsz_per_shard * num_shards
|
| 255 |
+
|
| 256 |
+
"""
|
| 257 |
+
With a 16 item dataset, bsz_per_shard=2 and num_shards=3,
|
| 258 |
+
*indices* might look like:
|
| 259 |
+
|
| 260 |
+
indices = [[0, 1],
|
| 261 |
+
[2, 3],
|
| 262 |
+
[4, 5],
|
| 263 |
+
[6, 7],
|
| 264 |
+
[8, 9],
|
| 265 |
+
[10, 11]]
|
| 266 |
+
|
| 267 |
+
The size of the TruncatedBPTTDataset instance will be 2,
|
| 268 |
+
and shard 1 will see items:
|
| 269 |
+
|
| 270 |
+
[(0, [data[4], data[6]]),
|
| 271 |
+
(1, [data[5], data[7]])]
|
| 272 |
+
"""
|
| 273 |
+
indices = batchify(torch.arange(len(data)), global_batch_size)
|
| 274 |
+
assert indices.size(0) == global_batch_size
|
| 275 |
+
|
| 276 |
+
self.my_indices = indices[
|
| 277 |
+
shard_id * bsz_per_shard : (shard_id + 1) * bsz_per_shard
|
| 278 |
+
]
|
| 279 |
+
assert self.my_indices.size(0) == bsz_per_shard
|
| 280 |
+
|
| 281 |
+
def __len__(self):
|
| 282 |
+
return self.my_indices.size(1)
|
| 283 |
+
|
| 284 |
+
def __getitem__(self, i) -> Tuple[int, List[torch.Tensor]]:
|
| 285 |
+
return (i, [self.data[idx] for idx in self.my_indices[:, i]])
|
data/fairseq/examples/backtranslation/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Understanding Back-Translation at Scale (Edunov et al., 2018)
|
| 2 |
+
|
| 3 |
+
This page includes pre-trained models from the paper [Understanding Back-Translation at Scale (Edunov et al., 2018)](https://arxiv.org/abs/1808.09381).
|
| 4 |
+
|
| 5 |
+
## Pre-trained models
|
| 6 |
+
|
| 7 |
+
Model | Description | Dataset | Download
|
| 8 |
+
---|---|---|---
|
| 9 |
+
`transformer.wmt18.en-de` | Transformer <br> ([Edunov et al., 2018](https://arxiv.org/abs/1808.09381)) <br> WMT'18 winner | [WMT'18 English-German](http://www.statmt.org/wmt18/translation-task.html) | [download (.tar.gz)](https://dl.fbaipublicfiles.com/fairseq/models/wmt18.en-de.ensemble.tar.gz) <br> See NOTE in the archive
|
| 10 |
+
|
| 11 |
+
## Example usage (torch.hub)
|
| 12 |
+
|
| 13 |
+
We require a few additional Python dependencies for preprocessing:
|
| 14 |
+
```bash
|
| 15 |
+
pip install subword_nmt sacremoses
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
Then to generate translations from the full model ensemble:
|
| 19 |
+
```python
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
# List available models
|
| 23 |
+
torch.hub.list('pytorch/fairseq') # [..., 'transformer.wmt18.en-de', ... ]
|
| 24 |
+
|
| 25 |
+
# Load the WMT'18 En-De ensemble
|
| 26 |
+
en2de_ensemble = torch.hub.load(
|
| 27 |
+
'pytorch/fairseq', 'transformer.wmt18.en-de',
|
| 28 |
+
checkpoint_file='wmt18.model1.pt:wmt18.model2.pt:wmt18.model3.pt:wmt18.model4.pt:wmt18.model5.pt',
|
| 29 |
+
tokenizer='moses', bpe='subword_nmt')
|
| 30 |
+
|
| 31 |
+
# The ensemble contains 5 models
|
| 32 |
+
len(en2de_ensemble.models)
|
| 33 |
+
# 5
|
| 34 |
+
|
| 35 |
+
# Translate
|
| 36 |
+
en2de_ensemble.translate('Hello world!')
|
| 37 |
+
# 'Hallo Welt!'
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Training your own model (WMT'18 English-German)
|
| 41 |
+
|
| 42 |
+
The following instructions can be adapted to reproduce the models from the paper.
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
#### Step 1. Prepare parallel data and optionally train a baseline (English-German) model
|
| 46 |
+
|
| 47 |
+
First download and preprocess the data:
|
| 48 |
+
```bash
|
| 49 |
+
# Download and prepare the data
|
| 50 |
+
cd examples/backtranslation/
|
| 51 |
+
bash prepare-wmt18en2de.sh
|
| 52 |
+
cd ../..
|
| 53 |
+
|
| 54 |
+
# Binarize the data
|
| 55 |
+
TEXT=examples/backtranslation/wmt18_en_de
|
| 56 |
+
fairseq-preprocess \
|
| 57 |
+
--joined-dictionary \
|
| 58 |
+
--source-lang en --target-lang de \
|
| 59 |
+
--trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \
|
| 60 |
+
--destdir data-bin/wmt18_en_de --thresholdtgt 0 --thresholdsrc 0 \
|
| 61 |
+
--workers 20
|
| 62 |
+
|
| 63 |
+
# Copy the BPE code into the data-bin directory for future use
|
| 64 |
+
cp examples/backtranslation/wmt18_en_de/code data-bin/wmt18_en_de/code
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
(Optionally) Train a baseline model (English-German) using just the parallel data:
|
| 68 |
+
```bash
|
| 69 |
+
CHECKPOINT_DIR=checkpoints_en_de_parallel
|
| 70 |
+
fairseq-train --fp16 \
|
| 71 |
+
data-bin/wmt18_en_de \
|
| 72 |
+
--source-lang en --target-lang de \
|
| 73 |
+
--arch transformer_wmt_en_de_big --share-all-embeddings \
|
| 74 |
+
--dropout 0.3 --weight-decay 0.0 \
|
| 75 |
+
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
| 76 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
| 77 |
+
--lr 0.001 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
| 78 |
+
--max-tokens 3584 --update-freq 16 \
|
| 79 |
+
--max-update 30000 \
|
| 80 |
+
--save-dir $CHECKPOINT_DIR
|
| 81 |
+
# Note: the above command assumes 8 GPUs. Adjust `--update-freq` if you have a
|
| 82 |
+
# different number of GPUs.
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
Average the last 10 checkpoints:
|
| 86 |
+
```bash
|
| 87 |
+
python scripts/average_checkpoints.py \
|
| 88 |
+
--inputs $CHECKPOINT_DIR \
|
| 89 |
+
--num-epoch-checkpoints 10 \
|
| 90 |
+
--output $CHECKPOINT_DIR/checkpoint.avg10.pt
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
Evaluate BLEU:
|
| 94 |
+
```bash
|
| 95 |
+
# tokenized BLEU on newstest2017:
|
| 96 |
+
bash examples/backtranslation/tokenized_bleu.sh \
|
| 97 |
+
wmt17 \
|
| 98 |
+
en-de \
|
| 99 |
+
data-bin/wmt18_en_de \
|
| 100 |
+
data-bin/wmt18_en_de/code \
|
| 101 |
+
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
| 102 |
+
# BLEU4 = 29.57, 60.9/35.4/22.9/15.5 (BP=1.000, ratio=1.014, syslen=63049, reflen=62152)
|
| 103 |
+
# compare to 29.46 in Table 1, which is also for tokenized BLEU
|
| 104 |
+
|
| 105 |
+
# generally it's better to report (detokenized) sacrebleu though:
|
| 106 |
+
bash examples/backtranslation/sacrebleu.sh \
|
| 107 |
+
wmt17 \
|
| 108 |
+
en-de \
|
| 109 |
+
data-bin/wmt18_en_de \
|
| 110 |
+
data-bin/wmt18_en_de/code \
|
| 111 |
+
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
| 112 |
+
# BLEU+case.mixed+lang.en-de+numrefs.1+smooth.exp+test.wmt17+tok.13a+version.1.4.3 = 29.0 60.6/34.7/22.4/14.9 (BP = 1.000 ratio = 1.013 hyp_len = 62099 ref_len = 61287)
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
#### Step 2. Back-translate monolingual German data
|
| 117 |
+
|
| 118 |
+
Train a reverse model (German-English) to do the back-translation:
|
| 119 |
+
```bash
|
| 120 |
+
CHECKPOINT_DIR=checkpoints_de_en_parallel
|
| 121 |
+
fairseq-train --fp16 \
|
| 122 |
+
data-bin/wmt18_en_de \
|
| 123 |
+
--source-lang de --target-lang en \
|
| 124 |
+
--arch transformer_wmt_en_de_big --share-all-embeddings \
|
| 125 |
+
--dropout 0.3 --weight-decay 0.0 \
|
| 126 |
+
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
| 127 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
| 128 |
+
--lr 0.001 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
| 129 |
+
--max-tokens 3584 --update-freq 16 \
|
| 130 |
+
--max-update 30000 \
|
| 131 |
+
--save-dir $CHECKPOINT_DIR
|
| 132 |
+
# Note: the above command assumes 8 GPUs. Adjust `--update-freq` if you have a
|
| 133 |
+
# different number of GPUs.
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
Let's evaluate the back-translation (BT) model to make sure it is well trained:
|
| 137 |
+
```bash
|
| 138 |
+
bash examples/backtranslation/sacrebleu.sh \
|
| 139 |
+
wmt17 \
|
| 140 |
+
de-en \
|
| 141 |
+
data-bin/wmt18_en_de \
|
| 142 |
+
data-bin/wmt18_en_de/code \
|
| 143 |
+
$CHECKPOINT_DIR/checkpoint_best.py
|
| 144 |
+
# BLEU+case.mixed+lang.de-en+numrefs.1+smooth.exp+test.wmt17+tok.13a+version.1.4.3 = 34.9 66.9/41.8/28.5/19.9 (BP = 0.983 ratio = 0.984 hyp_len = 63342 ref_len = 64399)
|
| 145 |
+
# compare to the best system from WMT'17 which scored 35.1: http://matrix.statmt.org/matrix/systems_list/1868
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
Next prepare the monolingual data:
|
| 149 |
+
```bash
|
| 150 |
+
# Download and prepare the monolingual data
|
| 151 |
+
# By default the script samples 25M monolingual sentences, which after
|
| 152 |
+
# deduplication should be just over 24M sentences. These are split into 25
|
| 153 |
+
# shards, each with 1M sentences (except for the last shard).
|
| 154 |
+
cd examples/backtranslation/
|
| 155 |
+
bash prepare-de-monolingual.sh
|
| 156 |
+
cd ../..
|
| 157 |
+
|
| 158 |
+
# Binarize each shard of the monolingual data
|
| 159 |
+
TEXT=examples/backtranslation/wmt18_de_mono
|
| 160 |
+
for SHARD in $(seq -f "%02g" 0 24); do \
|
| 161 |
+
fairseq-preprocess \
|
| 162 |
+
--only-source \
|
| 163 |
+
--source-lang de --target-lang en \
|
| 164 |
+
--joined-dictionary \
|
| 165 |
+
--srcdict data-bin/wmt18_en_de/dict.de.txt \
|
| 166 |
+
--testpref $TEXT/bpe.monolingual.dedup.${SHARD} \
|
| 167 |
+
--destdir data-bin/wmt18_de_mono/shard${SHARD} \
|
| 168 |
+
--workers 20; \
|
| 169 |
+
cp data-bin/wmt18_en_de/dict.en.txt data-bin/wmt18_de_mono/shard${SHARD}/; \
|
| 170 |
+
done
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
Now we're ready to perform back-translation over the monolingual data. The
|
| 174 |
+
following command generates via sampling, but it's possible to use greedy
|
| 175 |
+
decoding (`--beam 1`), beam search (`--beam 5`),
|
| 176 |
+
top-k sampling (`--sampling --beam 1 --sampling-topk 10`), etc.:
|
| 177 |
+
```bash
|
| 178 |
+
mkdir backtranslation_output
|
| 179 |
+
for SHARD in $(seq -f "%02g" 0 24); do \
|
| 180 |
+
fairseq-generate --fp16 \
|
| 181 |
+
data-bin/wmt18_de_mono/shard${SHARD} \
|
| 182 |
+
--path $CHECKPOINT_DIR/checkpoint_best.pt \
|
| 183 |
+
--skip-invalid-size-inputs-valid-test \
|
| 184 |
+
--max-tokens 4096 \
|
| 185 |
+
--sampling --beam 1 \
|
| 186 |
+
> backtranslation_output/sampling.shard${SHARD}.out; \
|
| 187 |
+
done
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
After BT, use the `extract_bt_data.py` script to re-combine the shards, extract
|
| 191 |
+
the back-translations and apply length ratio filters:
|
| 192 |
+
```bash
|
| 193 |
+
python examples/backtranslation/extract_bt_data.py \
|
| 194 |
+
--minlen 1 --maxlen 250 --ratio 1.5 \
|
| 195 |
+
--output backtranslation_output/bt_data --srclang en --tgtlang de \
|
| 196 |
+
backtranslation_output/sampling.shard*.out
|
| 197 |
+
|
| 198 |
+
# Ensure lengths are the same:
|
| 199 |
+
# wc -l backtranslation_output/bt_data.{en,de}
|
| 200 |
+
# 21795614 backtranslation_output/bt_data.en
|
| 201 |
+
# 21795614 backtranslation_output/bt_data.de
|
| 202 |
+
# 43591228 total
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
Binarize the filtered BT data and combine it with the parallel data:
|
| 206 |
+
```bash
|
| 207 |
+
TEXT=backtranslation_output
|
| 208 |
+
fairseq-preprocess \
|
| 209 |
+
--source-lang en --target-lang de \
|
| 210 |
+
--joined-dictionary \
|
| 211 |
+
--srcdict data-bin/wmt18_en_de/dict.en.txt \
|
| 212 |
+
--trainpref $TEXT/bt_data \
|
| 213 |
+
--destdir data-bin/wmt18_en_de_bt \
|
| 214 |
+
--workers 20
|
| 215 |
+
|
| 216 |
+
# We want to train on the combined data, so we'll symlink the parallel + BT data
|
| 217 |
+
# in the wmt18_en_de_para_plus_bt directory. We link the parallel data as "train"
|
| 218 |
+
# and the BT data as "train1", so that fairseq will combine them automatically
|
| 219 |
+
# and so that we can use the `--upsample-primary` option to upsample the
|
| 220 |
+
# parallel data (if desired).
|
| 221 |
+
PARA_DATA=$(readlink -f data-bin/wmt18_en_de)
|
| 222 |
+
BT_DATA=$(readlink -f data-bin/wmt18_en_de_bt)
|
| 223 |
+
COMB_DATA=data-bin/wmt18_en_de_para_plus_bt
|
| 224 |
+
mkdir -p $COMB_DATA
|
| 225 |
+
for LANG in en de; do \
|
| 226 |
+
ln -s ${PARA_DATA}/dict.$LANG.txt ${COMB_DATA}/dict.$LANG.txt; \
|
| 227 |
+
for EXT in bin idx; do \
|
| 228 |
+
ln -s ${PARA_DATA}/train.en-de.$LANG.$EXT ${COMB_DATA}/train.en-de.$LANG.$EXT; \
|
| 229 |
+
ln -s ${BT_DATA}/train.en-de.$LANG.$EXT ${COMB_DATA}/train1.en-de.$LANG.$EXT; \
|
| 230 |
+
ln -s ${PARA_DATA}/valid.en-de.$LANG.$EXT ${COMB_DATA}/valid.en-de.$LANG.$EXT; \
|
| 231 |
+
ln -s ${PARA_DATA}/test.en-de.$LANG.$EXT ${COMB_DATA}/test.en-de.$LANG.$EXT; \
|
| 232 |
+
done; \
|
| 233 |
+
done
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
#### 3. Train an English-German model over the combined parallel + BT data
|
| 238 |
+
|
| 239 |
+
Finally we can train a model over the parallel + BT data:
|
| 240 |
+
```bash
|
| 241 |
+
CHECKPOINT_DIR=checkpoints_en_de_parallel_plus_bt
|
| 242 |
+
fairseq-train --fp16 \
|
| 243 |
+
data-bin/wmt18_en_de_para_plus_bt \
|
| 244 |
+
--upsample-primary 16 \
|
| 245 |
+
--source-lang en --target-lang de \
|
| 246 |
+
--arch transformer_wmt_en_de_big --share-all-embeddings \
|
| 247 |
+
--dropout 0.3 --weight-decay 0.0 \
|
| 248 |
+
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
| 249 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
| 250 |
+
--lr 0.0007 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
| 251 |
+
--max-tokens 3584 --update-freq 16 \
|
| 252 |
+
--max-update 100000 \
|
| 253 |
+
--save-dir $CHECKPOINT_DIR
|
| 254 |
+
# Note: the above command assumes 8 GPUs. Adjust `--update-freq` if you have a
|
| 255 |
+
# different number of GPUs.
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
Average the last 10 checkpoints:
|
| 259 |
+
```bash
|
| 260 |
+
python scripts/average_checkpoints.py \
|
| 261 |
+
--inputs $CHECKPOINT_DIR \
|
| 262 |
+
--num-epoch-checkpoints 10 \
|
| 263 |
+
--output $CHECKPOINT_DIR/checkpoint.avg10.pt
|
| 264 |
+
```
|
| 265 |
+
|
| 266 |
+
Evaluate BLEU:
|
| 267 |
+
```bash
|
| 268 |
+
# tokenized BLEU on newstest2017:
|
| 269 |
+
bash examples/backtranslation/tokenized_bleu.sh \
|
| 270 |
+
wmt17 \
|
| 271 |
+
en-de \
|
| 272 |
+
data-bin/wmt18_en_de \
|
| 273 |
+
data-bin/wmt18_en_de/code \
|
| 274 |
+
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
| 275 |
+
# BLEU4 = 32.35, 64.4/38.9/26.2/18.3 (BP=0.977, ratio=0.977, syslen=60729, reflen=62152)
|
| 276 |
+
# compare to 32.35 in Table 1, which is also for tokenized BLEU
|
| 277 |
+
|
| 278 |
+
# generally it's better to report (detokenized) sacrebleu:
|
| 279 |
+
bash examples/backtranslation/sacrebleu.sh \
|
| 280 |
+
wmt17 \
|
| 281 |
+
en-de \
|
| 282 |
+
data-bin/wmt18_en_de \
|
| 283 |
+
data-bin/wmt18_en_de/code \
|
| 284 |
+
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
| 285 |
+
# BLEU+case.mixed+lang.en-de+numrefs.1+smooth.exp+test.wmt17+tok.13a+version.1.4.3 = 31.5 64.3/38.2/25.6/17.6 (BP = 0.971 ratio = 0.971 hyp_len = 59515 ref_len = 61287)
|
| 286 |
+
```
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
## Citation
|
| 290 |
+
```bibtex
|
| 291 |
+
@inproceedings{edunov2018backtranslation,
|
| 292 |
+
title = {Understanding Back-Translation at Scale},
|
| 293 |
+
author = {Edunov, Sergey and Ott, Myle and Auli, Michael and Grangier, David},
|
| 294 |
+
booktitle = {Conference of the Association for Computational Linguistics (ACL)},
|
| 295 |
+
year = 2018,
|
| 296 |
+
}
|
| 297 |
+
```
|
data/fairseq/examples/backtranslation/deduplicate_lines.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/python3
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the MIT license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import fileinput
|
| 9 |
+
import hashlib
|
| 10 |
+
import sys
|
| 11 |
+
from multiprocessing import Pool
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def get_hashes_and_lines(raw_line):
|
| 15 |
+
hash = hashlib.md5(raw_line).hexdigest()
|
| 16 |
+
return hash, raw_line
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def main():
|
| 20 |
+
parser = argparse.ArgumentParser()
|
| 21 |
+
parser.add_argument("--workers", type=int, default=10)
|
| 22 |
+
parser.add_argument("files", nargs="*", help="input files")
|
| 23 |
+
args = parser.parse_args()
|
| 24 |
+
|
| 25 |
+
seen = set()
|
| 26 |
+
with fileinput.input(args.files, mode="rb") as h:
|
| 27 |
+
pool = Pool(args.workers)
|
| 28 |
+
results = pool.imap_unordered(get_hashes_and_lines, h, 1000)
|
| 29 |
+
for i, (hash, raw_line) in enumerate(results):
|
| 30 |
+
if hash not in seen:
|
| 31 |
+
seen.add(hash)
|
| 32 |
+
sys.stdout.buffer.write(raw_line)
|
| 33 |
+
if i % 1000000 == 0:
|
| 34 |
+
print(i, file=sys.stderr, end="", flush=True)
|
| 35 |
+
elif i % 100000 == 0:
|
| 36 |
+
print(".", file=sys.stderr, end="", flush=True)
|
| 37 |
+
print(file=sys.stderr, flush=True)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
main()
|
data/fairseq/examples/backtranslation/extract_bt_data.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the MIT license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import fileinput
|
| 9 |
+
|
| 10 |
+
from tqdm import tqdm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def main():
|
| 14 |
+
parser = argparse.ArgumentParser(
|
| 15 |
+
description=(
|
| 16 |
+
"Extract back-translations from the stdout of fairseq-generate. "
|
| 17 |
+
"If there are multiply hypotheses for a source, we only keep the first one. "
|
| 18 |
+
)
|
| 19 |
+
)
|
| 20 |
+
parser.add_argument("--output", required=True, help="output prefix")
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
"--srclang", required=True, help="source language (extracted from H-* lines)"
|
| 23 |
+
)
|
| 24 |
+
parser.add_argument(
|
| 25 |
+
"--tgtlang", required=True, help="target language (extracted from S-* lines)"
|
| 26 |
+
)
|
| 27 |
+
parser.add_argument("--minlen", type=int, help="min length filter")
|
| 28 |
+
parser.add_argument("--maxlen", type=int, help="max length filter")
|
| 29 |
+
parser.add_argument("--ratio", type=float, help="ratio filter")
|
| 30 |
+
parser.add_argument("files", nargs="*", help="input files")
|
| 31 |
+
args = parser.parse_args()
|
| 32 |
+
|
| 33 |
+
def validate(src, tgt):
|
| 34 |
+
srclen = len(src.split(" ")) if src != "" else 0
|
| 35 |
+
tgtlen = len(tgt.split(" ")) if tgt != "" else 0
|
| 36 |
+
if (
|
| 37 |
+
(args.minlen is not None and (srclen < args.minlen or tgtlen < args.minlen))
|
| 38 |
+
or (
|
| 39 |
+
args.maxlen is not None
|
| 40 |
+
and (srclen > args.maxlen or tgtlen > args.maxlen)
|
| 41 |
+
)
|
| 42 |
+
or (
|
| 43 |
+
args.ratio is not None
|
| 44 |
+
and (max(srclen, tgtlen) / float(min(srclen, tgtlen)) > args.ratio)
|
| 45 |
+
)
|
| 46 |
+
):
|
| 47 |
+
return False
|
| 48 |
+
return True
|
| 49 |
+
|
| 50 |
+
def safe_index(toks, index, default):
|
| 51 |
+
try:
|
| 52 |
+
return toks[index]
|
| 53 |
+
except IndexError:
|
| 54 |
+
return default
|
| 55 |
+
|
| 56 |
+
with open(args.output + "." + args.srclang, "w") as src_h, open(
|
| 57 |
+
args.output + "." + args.tgtlang, "w"
|
| 58 |
+
) as tgt_h:
|
| 59 |
+
for line in tqdm(fileinput.input(args.files)):
|
| 60 |
+
if line.startswith("S-"):
|
| 61 |
+
tgt = safe_index(line.rstrip().split("\t"), 1, "")
|
| 62 |
+
elif line.startswith("H-"):
|
| 63 |
+
if tgt is not None:
|
| 64 |
+
src = safe_index(line.rstrip().split("\t"), 2, "")
|
| 65 |
+
if validate(src, tgt):
|
| 66 |
+
print(src, file=src_h)
|
| 67 |
+
print(tgt, file=tgt_h)
|
| 68 |
+
tgt = None
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
main()
|
data/fairseq/examples/backtranslation/prepare-de-monolingual.sh
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
SCRIPTS=mosesdecoder/scripts
|
| 4 |
+
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
|
| 5 |
+
NORM_PUNC=$SCRIPTS/tokenizer/normalize-punctuation.perl
|
| 6 |
+
REM_NON_PRINT_CHAR=$SCRIPTS/tokenizer/remove-non-printing-char.perl
|
| 7 |
+
BPEROOT=subword-nmt/subword_nmt
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
BPE_CODE=wmt18_en_de/code
|
| 11 |
+
SUBSAMPLE_SIZE=25000000
|
| 12 |
+
LANG=de
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
OUTDIR=wmt18_${LANG}_mono
|
| 16 |
+
orig=orig
|
| 17 |
+
tmp=$OUTDIR/tmp
|
| 18 |
+
mkdir -p $OUTDIR $tmp
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
URLS=(
|
| 22 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2007.de.shuffled.gz"
|
| 23 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2008.de.shuffled.gz"
|
| 24 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2009.de.shuffled.gz"
|
| 25 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2010.de.shuffled.gz"
|
| 26 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2011.de.shuffled.gz"
|
| 27 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2012.de.shuffled.gz"
|
| 28 |
+
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2013.de.shuffled.gz"
|
| 29 |
+
"http://www.statmt.org/wmt15/training-monolingual-news-crawl-v2/news.2014.de.shuffled.v2.gz"
|
| 30 |
+
"http://data.statmt.org/wmt16/translation-task/news.2015.de.shuffled.gz"
|
| 31 |
+
"http://data.statmt.org/wmt17/translation-task/news.2016.de.shuffled.gz"
|
| 32 |
+
"http://data.statmt.org/wmt18/translation-task/news.2017.de.shuffled.deduped.gz"
|
| 33 |
+
)
|
| 34 |
+
FILES=(
|
| 35 |
+
"news.2007.de.shuffled.gz"
|
| 36 |
+
"news.2008.de.shuffled.gz"
|
| 37 |
+
"news.2009.de.shuffled.gz"
|
| 38 |
+
"news.2010.de.shuffled.gz"
|
| 39 |
+
"news.2011.de.shuffled.gz"
|
| 40 |
+
"news.2012.de.shuffled.gz"
|
| 41 |
+
"news.2013.de.shuffled.gz"
|
| 42 |
+
"news.2014.de.shuffled.v2.gz"
|
| 43 |
+
"news.2015.de.shuffled.gz"
|
| 44 |
+
"news.2016.de.shuffled.gz"
|
| 45 |
+
"news.2017.de.shuffled.deduped.gz"
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
cd $orig
|
| 50 |
+
for ((i=0;i<${#URLS[@]};++i)); do
|
| 51 |
+
file=${FILES[i]}
|
| 52 |
+
if [ -f $file ]; then
|
| 53 |
+
echo "$file already exists, skipping download"
|
| 54 |
+
else
|
| 55 |
+
url=${URLS[i]}
|
| 56 |
+
wget "$url"
|
| 57 |
+
fi
|
| 58 |
+
done
|
| 59 |
+
cd ..
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
if [ -f $tmp/monolingual.${SUBSAMPLE_SIZE}.${LANG} ]; then
|
| 63 |
+
echo "found monolingual sample, skipping shuffle/sample/tokenize"
|
| 64 |
+
else
|
| 65 |
+
gzip -c -d -k $(for FILE in "${FILES[@]}"; do echo $orig/$FILE; done) \
|
| 66 |
+
| shuf -n $SUBSAMPLE_SIZE \
|
| 67 |
+
| perl $NORM_PUNC $LANG \
|
| 68 |
+
| perl $REM_NON_PRINT_CHAR \
|
| 69 |
+
| perl $TOKENIZER -threads 8 -a -l $LANG \
|
| 70 |
+
> $tmp/monolingual.${SUBSAMPLE_SIZE}.${LANG}
|
| 71 |
+
fi
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if [ -f $tmp/bpe.monolingual.${SUBSAMPLE_SIZE}.${LANG} ]; then
|
| 75 |
+
echo "found BPE monolingual sample, skipping BPE step"
|
| 76 |
+
else
|
| 77 |
+
python $BPEROOT/apply_bpe.py -c $BPE_CODE \
|
| 78 |
+
< $tmp/monolingual.${SUBSAMPLE_SIZE}.${LANG} \
|
| 79 |
+
> $tmp/bpe.monolingual.${SUBSAMPLE_SIZE}.${LANG}
|
| 80 |
+
fi
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
if [ -f $tmp/bpe.monolingual.dedup.${SUBSAMPLE_SIZE}.${LANG} ]; then
|
| 84 |
+
echo "found deduplicated monolingual sample, skipping deduplication step"
|
| 85 |
+
else
|
| 86 |
+
python deduplicate_lines.py $tmp/bpe.monolingual.${SUBSAMPLE_SIZE}.${LANG} \
|
| 87 |
+
> $tmp/bpe.monolingual.dedup.${SUBSAMPLE_SIZE}.${LANG}
|
| 88 |
+
fi
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if [ -f $OUTDIR/bpe.monolingual.dedup.00.de ]; then
|
| 92 |
+
echo "found sharded data, skipping sharding step"
|
| 93 |
+
else
|
| 94 |
+
split --lines 1000000 --numeric-suffixes \
|
| 95 |
+
--additional-suffix .${LANG} \
|
| 96 |
+
$tmp/bpe.monolingual.dedup.${SUBSAMPLE_SIZE}.${LANG} \
|
| 97 |
+
$OUTDIR/bpe.monolingual.dedup.
|
| 98 |
+
fi
|
data/fairseq/examples/backtranslation/prepare-wmt18en2de.sh
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Adapted from https://github.com/facebookresearch/MIXER/blob/master/prepareData.sh
|
| 3 |
+
|
| 4 |
+
echo 'Cloning Moses github repository (for tokenization scripts)...'
|
| 5 |
+
git clone https://github.com/moses-smt/mosesdecoder.git
|
| 6 |
+
|
| 7 |
+
echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
|
| 8 |
+
git clone https://github.com/rsennrich/subword-nmt.git
|
| 9 |
+
|
| 10 |
+
SCRIPTS=mosesdecoder/scripts
|
| 11 |
+
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
|
| 12 |
+
CLEAN=$SCRIPTS/training/clean-corpus-n.perl
|
| 13 |
+
NORM_PUNC=$SCRIPTS/tokenizer/normalize-punctuation.perl
|
| 14 |
+
REM_NON_PRINT_CHAR=$SCRIPTS/tokenizer/remove-non-printing-char.perl
|
| 15 |
+
BPEROOT=subword-nmt/subword_nmt
|
| 16 |
+
BPE_TOKENS=32000
|
| 17 |
+
|
| 18 |
+
URLS=(
|
| 19 |
+
"http://statmt.org/wmt13/training-parallel-europarl-v7.tgz"
|
| 20 |
+
"http://statmt.org/wmt13/training-parallel-commoncrawl.tgz"
|
| 21 |
+
"http://data.statmt.org/wmt18/translation-task/training-parallel-nc-v13.tgz"
|
| 22 |
+
"http://data.statmt.org/wmt18/translation-task/rapid2016.tgz"
|
| 23 |
+
"http://data.statmt.org/wmt17/translation-task/dev.tgz"
|
| 24 |
+
"http://statmt.org/wmt14/test-full.tgz"
|
| 25 |
+
)
|
| 26 |
+
FILES=(
|
| 27 |
+
"training-parallel-europarl-v7.tgz"
|
| 28 |
+
"training-parallel-commoncrawl.tgz"
|
| 29 |
+
"training-parallel-nc-v13.tgz"
|
| 30 |
+
"rapid2016.tgz"
|
| 31 |
+
"dev.tgz"
|
| 32 |
+
"test-full.tgz"
|
| 33 |
+
)
|
| 34 |
+
CORPORA=(
|
| 35 |
+
"training/europarl-v7.de-en"
|
| 36 |
+
"commoncrawl.de-en"
|
| 37 |
+
"training-parallel-nc-v13/news-commentary-v13.de-en"
|
| 38 |
+
"rapid2016.de-en"
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
if [ ! -d "$SCRIPTS" ]; then
|
| 42 |
+
echo "Please set SCRIPTS variable correctly to point to Moses scripts."
|
| 43 |
+
exit 1
|
| 44 |
+
fi
|
| 45 |
+
|
| 46 |
+
OUTDIR=wmt18_en_de
|
| 47 |
+
|
| 48 |
+
src=en
|
| 49 |
+
tgt=de
|
| 50 |
+
lang=en-de
|
| 51 |
+
prep=$OUTDIR
|
| 52 |
+
tmp=$prep/tmp
|
| 53 |
+
orig=orig
|
| 54 |
+
|
| 55 |
+
mkdir -p $orig $tmp $prep
|
| 56 |
+
|
| 57 |
+
cd $orig
|
| 58 |
+
|
| 59 |
+
for ((i=0;i<${#URLS[@]};++i)); do
|
| 60 |
+
file=${FILES[i]}
|
| 61 |
+
if [ -f $file ]; then
|
| 62 |
+
echo "$file already exists, skipping download"
|
| 63 |
+
else
|
| 64 |
+
url=${URLS[i]}
|
| 65 |
+
wget "$url"
|
| 66 |
+
if [ -f $file ]; then
|
| 67 |
+
echo "$url successfully downloaded."
|
| 68 |
+
else
|
| 69 |
+
echo "$url not successfully downloaded."
|
| 70 |
+
exit 1
|
| 71 |
+
fi
|
| 72 |
+
if [ ${file: -4} == ".tgz" ]; then
|
| 73 |
+
tar zxvf $file
|
| 74 |
+
elif [ ${file: -4} == ".tar" ]; then
|
| 75 |
+
tar xvf $file
|
| 76 |
+
fi
|
| 77 |
+
fi
|
| 78 |
+
done
|
| 79 |
+
cd ..
|
| 80 |
+
|
| 81 |
+
echo "pre-processing train data..."
|
| 82 |
+
for l in $src $tgt; do
|
| 83 |
+
rm $tmp/train.tags.$lang.tok.$l
|
| 84 |
+
for f in "${CORPORA[@]}"; do
|
| 85 |
+
cat $orig/$f.$l | \
|
| 86 |
+
perl $NORM_PUNC $l | \
|
| 87 |
+
perl $REM_NON_PRINT_CHAR | \
|
| 88 |
+
perl $TOKENIZER -threads 8 -a -l $l >> $tmp/train.tags.$lang.tok.$l
|
| 89 |
+
done
|
| 90 |
+
done
|
| 91 |
+
|
| 92 |
+
echo "pre-processing test data..."
|
| 93 |
+
for l in $src $tgt; do
|
| 94 |
+
if [ "$l" == "$src" ]; then
|
| 95 |
+
t="src"
|
| 96 |
+
else
|
| 97 |
+
t="ref"
|
| 98 |
+
fi
|
| 99 |
+
grep '<seg id' $orig/test-full/newstest2014-deen-$t.$l.sgm | \
|
| 100 |
+
sed -e 's/<seg id="[0-9]*">\s*//g' | \
|
| 101 |
+
sed -e 's/\s*<\/seg>\s*//g' | \
|
| 102 |
+
sed -e "s/\’/\'/g" | \
|
| 103 |
+
perl $TOKENIZER -threads 8 -a -l $l > $tmp/test.$l
|
| 104 |
+
echo ""
|
| 105 |
+
done
|
| 106 |
+
|
| 107 |
+
echo "splitting train and valid..."
|
| 108 |
+
for l in $src $tgt; do
|
| 109 |
+
awk '{if (NR%100 == 0) print $0; }' $tmp/train.tags.$lang.tok.$l > $tmp/valid.$l
|
| 110 |
+
awk '{if (NR%100 != 0) print $0; }' $tmp/train.tags.$lang.tok.$l > $tmp/train.$l
|
| 111 |
+
done
|
| 112 |
+
|
| 113 |
+
TRAIN=$tmp/train.de-en
|
| 114 |
+
BPE_CODE=$prep/code
|
| 115 |
+
rm -f $TRAIN
|
| 116 |
+
for l in $src $tgt; do
|
| 117 |
+
cat $tmp/train.$l >> $TRAIN
|
| 118 |
+
done
|
| 119 |
+
|
| 120 |
+
echo "learn_bpe.py on ${TRAIN}..."
|
| 121 |
+
python $BPEROOT/learn_bpe.py -s $BPE_TOKENS < $TRAIN > $BPE_CODE
|
| 122 |
+
|
| 123 |
+
for L in $src $tgt; do
|
| 124 |
+
for f in train.$L valid.$L test.$L; do
|
| 125 |
+
echo "apply_bpe.py to ${f}..."
|
| 126 |
+
python $BPEROOT/apply_bpe.py -c $BPE_CODE < $tmp/$f > $tmp/bpe.$f
|
| 127 |
+
done
|
| 128 |
+
done
|
| 129 |
+
|
| 130 |
+
perl $CLEAN -ratio 1.5 $tmp/bpe.train $src $tgt $prep/train 1 250
|
| 131 |
+
perl $CLEAN -ratio 1.5 $tmp/bpe.valid $src $tgt $prep/valid 1 250
|
| 132 |
+
|
| 133 |
+
for L in $src $tgt; do
|
| 134 |
+
cp $tmp/bpe.test.$L $prep/test.$L
|
| 135 |
+
done
|
data/fairseq/examples/backtranslation/sacrebleu.sh
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
if [ $# -ne 5 ]; then
|
| 4 |
+
echo "usage: $0 [dataset=wmt14/full] [langpair=en-de] [databin] [bpecode] [model]"
|
| 5 |
+
exit
|
| 6 |
+
fi
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
DATASET=$1
|
| 10 |
+
LANGPAIR=$2
|
| 11 |
+
DATABIN=$3
|
| 12 |
+
BPECODE=$4
|
| 13 |
+
MODEL=$5
|
| 14 |
+
|
| 15 |
+
SRCLANG=$(echo $LANGPAIR | cut -d '-' -f 1)
|
| 16 |
+
TGTLANG=$(echo $LANGPAIR | cut -d '-' -f 2)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
BPEROOT=examples/backtranslation/subword-nmt/subword_nmt
|
| 20 |
+
if [ ! -e $BPEROOT ]; then
|
| 21 |
+
BPEROOT=subword-nmt/subword_nmt
|
| 22 |
+
if [ ! -e $BPEROOT ]; then
|
| 23 |
+
echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
|
| 24 |
+
git clone https://github.com/rsennrich/subword-nmt.git
|
| 25 |
+
fi
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
sacrebleu -t $DATASET -l $LANGPAIR --echo src \
|
| 30 |
+
| sacremoses tokenize -a -l $SRCLANG -q \
|
| 31 |
+
| python $BPEROOT/apply_bpe.py -c $BPECODE \
|
| 32 |
+
| fairseq-interactive $DATABIN --path $MODEL \
|
| 33 |
+
-s $SRCLANG -t $TGTLANG \
|
| 34 |
+
--beam 5 --remove-bpe --buffer-size 1024 --max-tokens 8000 \
|
| 35 |
+
| grep ^H- | cut -f 3- \
|
| 36 |
+
| sacremoses detokenize -l $TGTLANG -q \
|
| 37 |
+
| sacrebleu -t $DATASET -l $LANGPAIR
|
data/fairseq/examples/backtranslation/tokenized_bleu.sh
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
if [ $# -ne 5 ]; then
|
| 4 |
+
echo "usage: $0 [dataset=wmt14/full] [langpair=en-de] [databin] [bpecode] [model]"
|
| 5 |
+
exit
|
| 6 |
+
fi
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
DATASET=$1
|
| 10 |
+
LANGPAIR=$2
|
| 11 |
+
DATABIN=$3
|
| 12 |
+
BPECODE=$4
|
| 13 |
+
MODEL=$5
|
| 14 |
+
|
| 15 |
+
SRCLANG=$(echo $LANGPAIR | cut -d '-' -f 1)
|
| 16 |
+
TGTLANG=$(echo $LANGPAIR | cut -d '-' -f 2)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
BPEROOT=examples/backtranslation/subword-nmt/subword_nmt
|
| 20 |
+
if [ ! -e $BPEROOT ]; then
|
| 21 |
+
BPEROOT=subword-nmt/subword_nmt
|
| 22 |
+
if [ ! -e $BPEROOT ]; then
|
| 23 |
+
echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
|
| 24 |
+
git clone https://github.com/rsennrich/subword-nmt.git
|
| 25 |
+
fi
|
| 26 |
+
fi
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
TMP_REF=$(mktemp)
|
| 30 |
+
|
| 31 |
+
sacrebleu -t $DATASET -l $LANGPAIR --echo ref -q \
|
| 32 |
+
| sacremoses normalize -l $TGTLANG -q \
|
| 33 |
+
| sacremoses tokenize -a -l $TGTLANG -q \
|
| 34 |
+
> $TMP_REF
|
| 35 |
+
|
| 36 |
+
sacrebleu -t $DATASET -l $LANGPAIR --echo src -q \
|
| 37 |
+
| sacremoses normalize -l $SRCLANG -q \
|
| 38 |
+
| sacremoses tokenize -a -l $SRCLANG -q \
|
| 39 |
+
| python $BPEROOT/apply_bpe.py -c $BPECODE \
|
| 40 |
+
| fairseq-interactive $DATABIN --path $MODEL \
|
| 41 |
+
-s $SRCLANG -t $TGTLANG \
|
| 42 |
+
--beam 5 --remove-bpe --buffer-size 1024 --max-tokens 8000 \
|
| 43 |
+
| grep ^H- | cut -f 3- \
|
| 44 |
+
| fairseq-score --ref $TMP_REF
|
| 45 |
+
|
| 46 |
+
rm -f $TMP_REF
|
data/fairseq/examples/cross_lingual_language_model/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Cross-Lingual Language Model Pre-training
|
| 2 |
+
|
| 3 |
+
Below are some details for training Cross-Lingual Language Models (XLM) - similar to the ones presented in [Lample & Conneau, 2019](https://arxiv.org/pdf/1901.07291.pdf) - in Fairseq. The current implementation only supports the Masked Language Model (MLM) from the paper above.
|
| 4 |
+
|
| 5 |
+
## Downloading and Tokenizing Monolingual Data
|
| 6 |
+
|
| 7 |
+
Pointers to the monolingual data from wikipedia, used for training the XLM-style MLM model as well as details on processing (tokenization and BPE) it can be found in the [XLM Github Repository](https://github.com/facebookresearch/XLM#download--preprocess-monolingual-data).
|
| 8 |
+
|
| 9 |
+
Let's assume the following for the code snippets in later sections to work
|
| 10 |
+
- Processed data is in the folder: monolingual_data/processed
|
| 11 |
+
- Each language has 3 files for train, test and validation. For example we have the following files for English:
|
| 12 |
+
train.en, valid.en
|
| 13 |
+
- We are training a model for 5 languages: Arabic (ar), German (de), English (en), Hindi (hi) and French (fr)
|
| 14 |
+
- The vocabulary file is monolingual_data/processed/vocab_mlm
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
## Fairseq Pre-processing and Binarization
|
| 18 |
+
|
| 19 |
+
Pre-process and binarize the data with the MaskedLMDictionary and cross_lingual_lm task
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
# Ensure the output directory exists
|
| 23 |
+
DATA_DIR=monolingual_data/fairseq_processed
|
| 24 |
+
mkdir -p "$DATA_DIR"
|
| 25 |
+
|
| 26 |
+
for lg in ar de en hi fr
|
| 27 |
+
do
|
| 28 |
+
|
| 29 |
+
fairseq-preprocess \
|
| 30 |
+
--task cross_lingual_lm \
|
| 31 |
+
--srcdict monolingual_data/processed/vocab_mlm \
|
| 32 |
+
--only-source \
|
| 33 |
+
--trainpref monolingual_data/processed/train \
|
| 34 |
+
--validpref monolingual_data/processed/valid \
|
| 35 |
+
--testpref monolingual_data/processed/test \
|
| 36 |
+
--destdir monolingual_data/fairseq_processed \
|
| 37 |
+
--workers 20 \
|
| 38 |
+
--source-lang $lg
|
| 39 |
+
|
| 40 |
+
# Since we only have a source language, the output file has a None for the
|
| 41 |
+
# target language. Remove this
|
| 42 |
+
|
| 43 |
+
for stage in train test valid
|
| 44 |
+
|
| 45 |
+
sudo mv "$DATA_DIR/$stage.$lg-None.$lg.bin" "$stage.$lg.bin"
|
| 46 |
+
sudo mv "$DATA_DIR/$stage.$lg-None.$lg.idx" "$stage.$lg.idx"
|
| 47 |
+
|
| 48 |
+
done
|
| 49 |
+
|
| 50 |
+
done
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
## Train a Cross-lingual Language Model similar to the XLM MLM model
|
| 54 |
+
|
| 55 |
+
Use the following command to train the model on 5 languages.
|
| 56 |
+
|
| 57 |
+
```
|
| 58 |
+
fairseq-train \
|
| 59 |
+
--task cross_lingual_lm monolingual_data/fairseq_processed \
|
| 60 |
+
--save-dir checkpoints/mlm \
|
| 61 |
+
--max-update 2400000 --save-interval 1 --no-epoch-checkpoints \
|
| 62 |
+
--arch xlm_base \
|
| 63 |
+
--optimizer adam --lr-scheduler reduce_lr_on_plateau \
|
| 64 |
+
--lr-shrink 0.5 --lr 0.0001 --stop-min-lr 1e-09 \
|
| 65 |
+
--dropout 0.1 \
|
| 66 |
+
--criterion legacy_masked_lm_loss \
|
| 67 |
+
--max-tokens 2048 --tokens-per-sample 256 --attention-dropout 0.1 \
|
| 68 |
+
--dataset-impl lazy --seed 0 \
|
| 69 |
+
--masked-lm-only \
|
| 70 |
+
--monolingual-langs 'ar,de,en,hi,fr' --num-segment 5 \
|
| 71 |
+
--ddp-backend=legacy_ddp
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
Some Notes:
|
| 75 |
+
- Using tokens_per_sample greater than 256 can cause OOM (out-of-memory) issues. Usually since MLM packs in streams of text, this parameter doesn't need much tuning.
|
| 76 |
+
- The Evaluation workflow for computing MLM Perplexity on test data is in progress.
|
| 77 |
+
- Finetuning this model on a downstream task is something which is not currently available.
|
data/fairseq/examples/discriminative_reranking_nmt/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Discriminative Reranking for Neural Machine Translation
|
| 2 |
+
https://aclanthology.org/2021.acl-long.563/
|
| 3 |
+
|
| 4 |
+
This folder contains source code for training DrNMT, a discriminatively trained reranker for neural machine translation.
|
| 5 |
+
|
| 6 |
+
## Data preparation
|
| 7 |
+
1. Follow the instructions under `examples/translation` to build a base MT model. Prepare three files, one with source sentences, one with ground truth target sentences, and one with hypotheses generated from the base MT model. Each line in the file contains one sentence in raw text (i.e. no sentencepiece, etc.). Below is an example of the files with _N_ hypotheses for each source sentence.
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
# Example of the source sentence file: (The file should contain L lines.)
|
| 11 |
+
|
| 12 |
+
source_sentence_1
|
| 13 |
+
source_sentence_2
|
| 14 |
+
source_sentence_3
|
| 15 |
+
...
|
| 16 |
+
source_sentence_L
|
| 17 |
+
|
| 18 |
+
# Example of the target sentence file: (The file should contain L lines.)
|
| 19 |
+
|
| 20 |
+
target_sentence_1
|
| 21 |
+
target_sentence_2
|
| 22 |
+
target_sentence_3
|
| 23 |
+
...
|
| 24 |
+
target_sentence_L
|
| 25 |
+
|
| 26 |
+
# Example of the hypotheses file: (The file should contain L*N lines.)
|
| 27 |
+
|
| 28 |
+
source_sentence_1_hypo_1
|
| 29 |
+
source_sentence_1_hypo_2
|
| 30 |
+
...
|
| 31 |
+
source_sentence_1_hypo_N
|
| 32 |
+
source_sentence_2_hypo_1
|
| 33 |
+
...
|
| 34 |
+
source_sentence_2_hypo_N
|
| 35 |
+
...
|
| 36 |
+
source_sentence_L_hypo_1
|
| 37 |
+
...
|
| 38 |
+
source_sentence_L_hypo_N
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
2. Download the [XLMR model](https://github.com/fairinternal/fairseq-py/tree/main/examples/xlmr#pre-trained-models).
|
| 42 |
+
```
|
| 43 |
+
wget https://dl.fbaipublicfiles.com/fairseq/models/xlmr.base.tar.gz
|
| 44 |
+
tar zxvf xlmr.base.tar.gz
|
| 45 |
+
|
| 46 |
+
# The folder should contain dict.txt, model.pt and sentencepiece.bpe.model.
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
3. Prepare scores and BPE data.
|
| 50 |
+
* `N`: Number of hypotheses per each source sentence. We use 50 in the paper.
|
| 51 |
+
* `SPLIT`: Name of the data split, i.e. train, valid, test. Use split_name, split_name1, split_name2, ..., if there are multiple datasets for a split, e.g. train, train1, valid, valid1.
|
| 52 |
+
* `NUM_SHARDS`: Number of shards. Set this to 1 for non-train splits.
|
| 53 |
+
* `METRIC`: The metric for DrNMT to optimize for. We support either `bleu` or `ter`.
|
| 54 |
+
```
|
| 55 |
+
# For each data split, e.g. train, valid, test, etc., run the following:
|
| 56 |
+
|
| 57 |
+
SOURCE_FILE=/path/to/source_sentence_file
|
| 58 |
+
TARGET_FILE=/path/to/target_sentence_file
|
| 59 |
+
HYPO_FILE=/path/to/hypo_file
|
| 60 |
+
XLMR_DIR=/path/to/xlmr
|
| 61 |
+
OUTPUT_DIR=/path/to/output
|
| 62 |
+
|
| 63 |
+
python scripts/prep_data.py \
|
| 64 |
+
--input-source ${SOURCE_FILE} \
|
| 65 |
+
--input-target ${TARGET_FILE} \
|
| 66 |
+
--input-hypo ${HYPO_FILE} \
|
| 67 |
+
--output-dir ${OUTPUT_DIR} \
|
| 68 |
+
--split $SPLIT
|
| 69 |
+
--beam $N \
|
| 70 |
+
--sentencepiece-model ${XLMR_DIR}/sentencepiece.bpe.model \
|
| 71 |
+
--metric $METRIC \
|
| 72 |
+
--num-shards ${NUM_SHARDS}
|
| 73 |
+
|
| 74 |
+
# The script will create ${OUTPUT_DIR}/$METRIC with ${NUM_SHARDS} splits.
|
| 75 |
+
# Under split*/input_src, split*/input_tgt and split*/$METRIC, there will be $SPLIT.bpe and $SPLIT.$METRIC files, respectively.
|
| 76 |
+
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
4. Pre-process the data into fairseq format.
|
| 80 |
+
```
|
| 81 |
+
# use comma to separate if there are more than one train or valid set
|
| 82 |
+
for suffix in src tgt ; do
|
| 83 |
+
fairseq-preprocess --only-source \
|
| 84 |
+
--trainpref ${OUTPUT_DIR}/$METRIC/split1/input_${suffix}/train.bpe \
|
| 85 |
+
--validpref ${OUTPUT_DIR}/$METRIC/split1/input_${suffix}/valid.bpe \
|
| 86 |
+
--destdir ${OUTPUT_DIR}/$METRIC/split1/input_${suffix} \
|
| 87 |
+
--workers 60 \
|
| 88 |
+
--srcdict ${XLMR_DIR}/dict.txt
|
| 89 |
+
done
|
| 90 |
+
|
| 91 |
+
for i in `seq 2 ${NUM_SHARDS}`; do
|
| 92 |
+
for suffix in src tgt ; do
|
| 93 |
+
fairseq-preprocess --only-source \
|
| 94 |
+
--trainpref ${OUTPUT_DIR}/$METRIC/split${i}/input_${suffix}/train.bpe \
|
| 95 |
+
--destdir ${OUTPUT_DIR}/$METRIC/split${i}/input_${suffix} \
|
| 96 |
+
--workers 60 \
|
| 97 |
+
--srcdict ${XLMR_DIR}/dict.txt
|
| 98 |
+
|
| 99 |
+
ln -s ${OUTPUT_DIR}/$METRIC/split1/input_${suffix}/valid* ${OUTPUT_DIR}/$METRIC/split${i}/input_${suffix}/.
|
| 100 |
+
done
|
| 101 |
+
|
| 102 |
+
ln -s ${OUTPUT_DIR}/$METRIC/split1/$METRIC/valid* ${OUTPUT_DIR}/$METRIC/split${i}/$METRIC/.
|
| 103 |
+
done
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
## Training
|
| 107 |
+
|
| 108 |
+
```
|
| 109 |
+
EXP_DIR=/path/to/exp
|
| 110 |
+
|
| 111 |
+
# An example of training the model with the config for De-En experiment in the paper.
|
| 112 |
+
# The config uses 16 GPUs and 50 hypotheses.
|
| 113 |
+
# For training with fewer number of GPUs, set
|
| 114 |
+
# distributed_training.distributed_world_size=k +optimization.update_freq='[x]' where x = 16/k
|
| 115 |
+
# For training with fewer number of hypotheses, set
|
| 116 |
+
# task.mt_beam=N dataset.batch_size=N dataset.required_batch_size_multiple=N
|
| 117 |
+
|
| 118 |
+
fairseq-hydra-train -m \
|
| 119 |
+
--config-dir config/ --config-name deen \
|
| 120 |
+
task.data=${OUTPUT_DIR}/$METRIC/split1/ \
|
| 121 |
+
task.num_data_splits=${NUM_SHARDS} \
|
| 122 |
+
model.pretrained_model=${XLMR_DIR}/model.pt \
|
| 123 |
+
common.user_dir=${FAIRSEQ_ROOT}/examples/discriminative_reranking_nmt \
|
| 124 |
+
checkpoint.save_dir=${EXP_DIR}
|
| 125 |
+
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
## Inference & scoring
|
| 129 |
+
Perform DrNMT reranking (fw + reranker score)
|
| 130 |
+
1. Tune weights on valid sets.
|
| 131 |
+
```
|
| 132 |
+
# genrate N hypotheses with the base MT model (fw score)
|
| 133 |
+
VALID_SOURCE_FILE=/path/to/source_sentences # one sentence per line, converted to the sentencepiece used by the base MT model
|
| 134 |
+
VALID_TARGET_FILE=/path/to/target_sentences # one sentence per line in raw text, i.e. no sentencepiece and tokenization
|
| 135 |
+
MT_MODEL=/path/to/mt_model
|
| 136 |
+
MT_DATA_PATH=/path/to/mt_data
|
| 137 |
+
|
| 138 |
+
cat ${VALID_SOURCE_FILE} | \
|
| 139 |
+
fairseq-interactive ${MT_DATA_PATH} \
|
| 140 |
+
--max-tokens 4000 --buffer-size 16 \
|
| 141 |
+
--num-workers 32 --path ${MT_MODEL} \
|
| 142 |
+
--beam $N --nbest $N \
|
| 143 |
+
--post-process sentencepiece &> valid-hypo.out
|
| 144 |
+
|
| 145 |
+
# replace "bleu" with "ter" to optimize for TER
|
| 146 |
+
python drnmt_rerank.py \
|
| 147 |
+
${OUTPUT_DIR}/$METRIC/split1/ \
|
| 148 |
+
--path ${EXP_DIR}/checkpoint_best.pt \
|
| 149 |
+
--in-text valid-hypo.out \
|
| 150 |
+
--results-path ${EXP_DIR} \
|
| 151 |
+
--gen-subset valid \
|
| 152 |
+
--target-text ${VALID_TARGET_FILE} \
|
| 153 |
+
--user-dir ${FAIRSEQ_ROOT}/examples/discriminative_reranking_nmt \
|
| 154 |
+
--bpe sentencepiece \
|
| 155 |
+
--sentencepiece-model ${XLMR_DIR}/sentencepiece.bpe.model \
|
| 156 |
+
--beam $N \
|
| 157 |
+
--batch-size $N \
|
| 158 |
+
--metric bleu \
|
| 159 |
+
--tune
|
| 160 |
+
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
2. Apply best weights on test sets
|
| 164 |
+
```
|
| 165 |
+
# genrate N hypotheses with the base MT model (fw score)
|
| 166 |
+
TEST_SOURCE_FILE=/path/to/source_sentences # one sentence per line, converted to the sentencepiece used by the base MT model
|
| 167 |
+
|
| 168 |
+
cat ${TEST_SOURCE_FILE} | \
|
| 169 |
+
fairseq-interactive ${MT_DATA_PATH} \
|
| 170 |
+
--max-tokens 4000 --buffer-size 16 \
|
| 171 |
+
--num-workers 32 --path ${MT_MODEL} \
|
| 172 |
+
--beam $N --nbest $N \
|
| 173 |
+
--post-process sentencepiece &> test-hypo.out
|
| 174 |
+
|
| 175 |
+
# replace "bleu" with "ter" to evaluate TER
|
| 176 |
+
# Add --target-text for evaluating BLEU/TER,
|
| 177 |
+
# otherwise the script will only generate the hypotheses with the highest scores only.
|
| 178 |
+
python drnmt_rerank.py \
|
| 179 |
+
${OUTPUT_DIR}/$METRIC/split1/ \
|
| 180 |
+
--path ${EXP_DIR}/checkpoint_best.pt \
|
| 181 |
+
--in-text test-hypo.out \
|
| 182 |
+
--results-path ${EXP_DIR} \
|
| 183 |
+
--gen-subset test \
|
| 184 |
+
--user-dir ${FAIRSEQ_ROOT}/examples/discriminative_reranking_nmt \
|
| 185 |
+
--bpe sentencepiece \
|
| 186 |
+
--sentencepiece-model ${XLMR_DIR}/sentencepiece.bpe.model \
|
| 187 |
+
--beam $N \
|
| 188 |
+
--batch-size $N \
|
| 189 |
+
--metric bleu \
|
| 190 |
+
--fw-weight ${BEST_FW_WEIGHT} \
|
| 191 |
+
--lenpen ${BEST_LENPEN}
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
## Citation
|
| 195 |
+
```bibtex
|
| 196 |
+
@inproceedings{lee2021discriminative,
|
| 197 |
+
title={Discriminative Reranking for Neural Machine Translation},
|
| 198 |
+
author={Lee, Ann and Auli, Michael and Ranzato, Marc'Aurelio},
|
| 199 |
+
booktitle={ACL},
|
| 200 |
+
year={2021}
|
| 201 |
+
}
|
| 202 |
+
```
|
data/fairseq/examples/discriminative_reranking_nmt/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from . import criterions, models, tasks # noqa
|
data/fairseq/examples/discriminative_reranking_nmt/config/deen.yaml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _group_
|
| 2 |
+
|
| 3 |
+
common:
|
| 4 |
+
fp16: true
|
| 5 |
+
log_format: json
|
| 6 |
+
log_interval: 50
|
| 7 |
+
seed: 2
|
| 8 |
+
|
| 9 |
+
checkpoint:
|
| 10 |
+
no_epoch_checkpoints: true
|
| 11 |
+
best_checkpoint_metric: bleu
|
| 12 |
+
maximize_best_checkpoint_metric: true
|
| 13 |
+
|
| 14 |
+
task:
|
| 15 |
+
_name: discriminative_reranking_nmt
|
| 16 |
+
data: ???
|
| 17 |
+
num_data_splits: ???
|
| 18 |
+
include_src: true
|
| 19 |
+
mt_beam: 50
|
| 20 |
+
eval_target_metric: true
|
| 21 |
+
target_metric: bleu
|
| 22 |
+
|
| 23 |
+
dataset:
|
| 24 |
+
batch_size: 50
|
| 25 |
+
num_workers: 6
|
| 26 |
+
required_batch_size_multiple: 50
|
| 27 |
+
valid_subset: ???
|
| 28 |
+
|
| 29 |
+
criterion:
|
| 30 |
+
_name: kl_divergence_rereanking
|
| 31 |
+
target_dist_norm: minmax
|
| 32 |
+
temperature: 0.5
|
| 33 |
+
|
| 34 |
+
optimization:
|
| 35 |
+
max_epoch: 200
|
| 36 |
+
lr: [0.00005]
|
| 37 |
+
update_freq: [32]
|
| 38 |
+
|
| 39 |
+
optimizer:
|
| 40 |
+
_name: adam
|
| 41 |
+
adam_betas: (0.9,0.98)
|
| 42 |
+
adam_eps: 1e-06
|
| 43 |
+
|
| 44 |
+
lr_scheduler:
|
| 45 |
+
_name: polynomial_decay
|
| 46 |
+
warmup_updates: 8000
|
| 47 |
+
total_num_update: 320000
|
| 48 |
+
|
| 49 |
+
model:
|
| 50 |
+
_name: discriminative_nmt_reranker
|
| 51 |
+
pretrained_model: ???
|
| 52 |
+
classifier_dropout: 0.2
|
| 53 |
+
|
| 54 |
+
distributed_training:
|
| 55 |
+
ddp_backend: no_c10d
|
| 56 |
+
distributed_world_size: 16
|
data/fairseq/examples/discriminative_reranking_nmt/criterions/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .discriminative_reranking_criterion import KLDivergenceRerankingCriterion
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
__all__ = [
|
| 5 |
+
"KLDivergenceRerankingCriterion",
|
| 6 |
+
]
|
data/fairseq/examples/discriminative_reranking_nmt/criterions/discriminative_reranking_criterion.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
|
| 12 |
+
from fairseq import utils
|
| 13 |
+
from fairseq.logging import metrics
|
| 14 |
+
from fairseq.criterions import FairseqCriterion, register_criterion
|
| 15 |
+
from fairseq.dataclass import ChoiceEnum, FairseqDataclass
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_EPSILON = torch.finfo(torch.float32).eps
|
| 19 |
+
TARGET_DIST_NORM_CHOICES = ChoiceEnum(["none", "minmax"])
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class KLDivergenceRerankingCriterionConfig(FairseqDataclass):
|
| 24 |
+
target_dist_norm: TARGET_DIST_NORM_CHOICES = field(
|
| 25 |
+
default="none",
|
| 26 |
+
metadata={"help": "method to normalize the range of target scores"},
|
| 27 |
+
)
|
| 28 |
+
temperature: float = field(
|
| 29 |
+
default=1.0,
|
| 30 |
+
metadata={"help": "temperature in softmax for target distributions"},
|
| 31 |
+
)
|
| 32 |
+
forward_batch_size: int = field(
|
| 33 |
+
default=32,
|
| 34 |
+
metadata={
|
| 35 |
+
"help": "number of hypotheses per batch for model forward (set a value smaller than --mt-beam to avoid OOM when training with a large beam size)"
|
| 36 |
+
},
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@register_criterion(
|
| 41 |
+
"kl_divergence_rereanking", dataclass=KLDivergenceRerankingCriterionConfig
|
| 42 |
+
)
|
| 43 |
+
class KLDivergenceRerankingCriterion(FairseqCriterion):
|
| 44 |
+
def __init__(
|
| 45 |
+
self, task, target_dist_norm, temperature, forward_batch_size,
|
| 46 |
+
):
|
| 47 |
+
super().__init__(task)
|
| 48 |
+
self.target_dist_norm = target_dist_norm
|
| 49 |
+
self.temperature = temperature
|
| 50 |
+
self.forward_batch_size = forward_batch_size
|
| 51 |
+
|
| 52 |
+
def forward(self, model, sample, reduce=True):
|
| 53 |
+
"""Compute the loss for the given sample.
|
| 54 |
+
|
| 55 |
+
Returns a tuple with three elements:
|
| 56 |
+
1) the loss
|
| 57 |
+
2) the sample size, which is used as the denominator for the gradient
|
| 58 |
+
3) logging outputs to display while training
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
sample_size = sample["id"].numel()
|
| 62 |
+
assert sample_size % self.task.cfg.mt_beam == 0, (
|
| 63 |
+
f"sample_size ({sample_size}) cannot be divided by beam size ({self.task.cfg.mt_beam})."
|
| 64 |
+
f"Please set --required-batch-size-multiple={self.task.cfg.mt_beam}."
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# split into smaller batches for model forward
|
| 68 |
+
batch_out = []
|
| 69 |
+
for i in range(0, sample_size, self.forward_batch_size):
|
| 70 |
+
j = min(i + self.forward_batch_size, sample_size)
|
| 71 |
+
|
| 72 |
+
out = model(
|
| 73 |
+
src_tokens=sample["net_input"]["src_tokens"][i:j, :],
|
| 74 |
+
src_lengths=sample["net_input"]["src_lengths"][i:j],
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
batch_out.append(
|
| 78 |
+
model.sentence_forward(out, sample["net_input"]["src_tokens"][i:j, :])
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
batch_out = torch.cat(batch_out, dim=0).view(
|
| 82 |
+
self.task.cfg.mt_beam, sample_size // self.task.cfg.mt_beam, -1
|
| 83 |
+
) # T x B x C
|
| 84 |
+
if model.joint_classification == "sent":
|
| 85 |
+
batch_out = model.joint_forward(batch_out)
|
| 86 |
+
scores = model.classification_forward(batch_out.view(sample_size, 1, -1)).view(
|
| 87 |
+
-1, self.task.cfg.mt_beam
|
| 88 |
+
) # input: B x T x C
|
| 89 |
+
|
| 90 |
+
loss = self.compute_kl_loss(
|
| 91 |
+
scores, sample["target"][:, 0].view(-1, self.task.cfg.mt_beam)
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
sample_size = sample_size // self.task.cfg.mt_beam
|
| 95 |
+
|
| 96 |
+
logging_output = {
|
| 97 |
+
"loss": loss.detach(),
|
| 98 |
+
"ntokens": sample["ntokens"],
|
| 99 |
+
"nsentences": sample_size * self.task.cfg.mt_beam,
|
| 100 |
+
"sample_size": sample_size,
|
| 101 |
+
"scores": scores.detach(),
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
return loss, sample_size, logging_output
|
| 105 |
+
|
| 106 |
+
def compute_kl_loss(self, logits, target):
|
| 107 |
+
norm_target = target
|
| 108 |
+
if self.target_dist_norm == "minmax":
|
| 109 |
+
min_v = torch.min(target, 1, keepdim=True).values
|
| 110 |
+
max_v = torch.max(target, 1, keepdim=True).values
|
| 111 |
+
norm_target = (target - min_v) / (max_v - min_v + _EPSILON)
|
| 112 |
+
|
| 113 |
+
target_dist = F.softmax(
|
| 114 |
+
norm_target / self.temperature, dim=-1, dtype=torch.float32
|
| 115 |
+
)
|
| 116 |
+
model_dist = F.log_softmax(logits, dim=-1, dtype=torch.float32)
|
| 117 |
+
loss = -(target_dist * model_dist - target_dist * target_dist.log()).sum()
|
| 118 |
+
return loss
|
| 119 |
+
|
| 120 |
+
@staticmethod
|
| 121 |
+
def reduce_metrics(logging_outputs) -> None:
|
| 122 |
+
"""Aggregate logging outputs from data parallel training."""
|
| 123 |
+
loss_sum = utils.item(sum(log.get("loss", 0) for log in logging_outputs))
|
| 124 |
+
|
| 125 |
+
sample_size = utils.item(
|
| 126 |
+
sum(log.get("sample_size", 0) for log in logging_outputs)
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
loss = loss_sum / sample_size / math.log(2)
|
| 130 |
+
metrics.log_scalar("loss", loss, sample_size, round=3)
|
| 131 |
+
|
| 132 |
+
@staticmethod
|
| 133 |
+
def logging_outputs_can_be_summed() -> bool:
|
| 134 |
+
"""
|
| 135 |
+
Whether the logging outputs returned by `forward` can be summed
|
| 136 |
+
across workers prior to calling `reduce_metrics`. Setting this
|
| 137 |
+
to True will improves distributed training speed.
|
| 138 |
+
"""
|
| 139 |
+
return True
|
data/fairseq/examples/discriminative_reranking_nmt/drnmt_rerank.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3 -u
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the MIT license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""
|
| 7 |
+
Score raw text with a trained model.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from collections import namedtuple
|
| 11 |
+
import logging
|
| 12 |
+
from multiprocessing import Pool
|
| 13 |
+
import sys
|
| 14 |
+
import os
|
| 15 |
+
import random
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import sacrebleu
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from fairseq import checkpoint_utils, options, utils
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
logger = logging.getLogger("fairseq_cli.drnmt_rerank")
|
| 25 |
+
logger.setLevel(logging.INFO)
|
| 26 |
+
|
| 27 |
+
Batch = namedtuple("Batch", "ids src_tokens src_lengths")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
pool_init_variables = {}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def init_loaded_scores(mt_scores, model_scores, hyp, ref):
|
| 34 |
+
global pool_init_variables
|
| 35 |
+
pool_init_variables["mt_scores"] = mt_scores
|
| 36 |
+
pool_init_variables["model_scores"] = model_scores
|
| 37 |
+
pool_init_variables["hyp"] = hyp
|
| 38 |
+
pool_init_variables["ref"] = ref
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def parse_fairseq_gen(filename, task):
|
| 42 |
+
source = {}
|
| 43 |
+
hypos = {}
|
| 44 |
+
scores = {}
|
| 45 |
+
with open(filename, "r", encoding="utf-8") as f:
|
| 46 |
+
for line in f:
|
| 47 |
+
line = line.strip()
|
| 48 |
+
if line.startswith("S-"): # source
|
| 49 |
+
uid, text = line.split("\t", 1)
|
| 50 |
+
uid = int(uid[2:])
|
| 51 |
+
source[uid] = text
|
| 52 |
+
elif line.startswith("D-"): # hypo
|
| 53 |
+
uid, score, text = line.split("\t", 2)
|
| 54 |
+
uid = int(uid[2:])
|
| 55 |
+
if uid not in hypos:
|
| 56 |
+
hypos[uid] = []
|
| 57 |
+
scores[uid] = []
|
| 58 |
+
hypos[uid].append(text)
|
| 59 |
+
scores[uid].append(float(score))
|
| 60 |
+
else:
|
| 61 |
+
continue
|
| 62 |
+
|
| 63 |
+
source_out = [source[i] for i in range(len(hypos))]
|
| 64 |
+
hypos_out = [h for i in range(len(hypos)) for h in hypos[i]]
|
| 65 |
+
scores_out = [s for i in range(len(scores)) for s in scores[i]]
|
| 66 |
+
|
| 67 |
+
return source_out, hypos_out, scores_out
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def read_target(filename):
|
| 71 |
+
with open(filename, "r", encoding="utf-8") as f:
|
| 72 |
+
output = [line.strip() for line in f]
|
| 73 |
+
return output
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def make_batches(args, src, hyp, task, max_positions, encode_fn):
|
| 77 |
+
assert len(src) * args.beam == len(
|
| 78 |
+
hyp
|
| 79 |
+
), f"Expect {len(src) * args.beam} hypotheses for {len(src)} source sentences with beam size {args.beam}. Got {len(hyp)} hypotheses intead."
|
| 80 |
+
hyp_encode = [
|
| 81 |
+
task.source_dictionary.encode_line(encode_fn(h), add_if_not_exist=False).long()
|
| 82 |
+
for h in hyp
|
| 83 |
+
]
|
| 84 |
+
if task.cfg.include_src:
|
| 85 |
+
src_encode = [
|
| 86 |
+
task.source_dictionary.encode_line(
|
| 87 |
+
encode_fn(s), add_if_not_exist=False
|
| 88 |
+
).long()
|
| 89 |
+
for s in src
|
| 90 |
+
]
|
| 91 |
+
tokens = [(src_encode[i // args.beam], h) for i, h in enumerate(hyp_encode)]
|
| 92 |
+
lengths = [(t1.numel(), t2.numel()) for t1, t2 in tokens]
|
| 93 |
+
else:
|
| 94 |
+
tokens = [(h,) for h in hyp_encode]
|
| 95 |
+
lengths = [(h.numel(),) for h in hyp_encode]
|
| 96 |
+
|
| 97 |
+
itr = task.get_batch_iterator(
|
| 98 |
+
dataset=task.build_dataset_for_inference(tokens, lengths),
|
| 99 |
+
max_tokens=args.max_tokens,
|
| 100 |
+
max_sentences=args.batch_size,
|
| 101 |
+
max_positions=max_positions,
|
| 102 |
+
ignore_invalid_inputs=args.skip_invalid_size_inputs_valid_test,
|
| 103 |
+
).next_epoch_itr(shuffle=False)
|
| 104 |
+
|
| 105 |
+
for batch in itr:
|
| 106 |
+
yield Batch(
|
| 107 |
+
ids=batch["id"],
|
| 108 |
+
src_tokens=batch["net_input"]["src_tokens"],
|
| 109 |
+
src_lengths=batch["net_input"]["src_lengths"],
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def decode_rerank_scores(args):
|
| 114 |
+
if args.max_tokens is None and args.batch_size is None:
|
| 115 |
+
args.batch_size = 1
|
| 116 |
+
|
| 117 |
+
logger.info(args)
|
| 118 |
+
|
| 119 |
+
use_cuda = torch.cuda.is_available() and not args.cpu
|
| 120 |
+
|
| 121 |
+
# Load ensemble
|
| 122 |
+
logger.info("loading model(s) from {}".format(args.path))
|
| 123 |
+
models, _model_args, task = checkpoint_utils.load_model_ensemble_and_task(
|
| 124 |
+
[args.path], arg_overrides=eval(args.model_overrides),
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
for model in models:
|
| 128 |
+
if args.fp16:
|
| 129 |
+
model.half()
|
| 130 |
+
if use_cuda:
|
| 131 |
+
model.cuda()
|
| 132 |
+
|
| 133 |
+
# Initialize generator
|
| 134 |
+
generator = task.build_generator(args)
|
| 135 |
+
|
| 136 |
+
# Handle tokenization and BPE
|
| 137 |
+
tokenizer = task.build_tokenizer(args)
|
| 138 |
+
bpe = task.build_bpe(args)
|
| 139 |
+
|
| 140 |
+
def encode_fn(x):
|
| 141 |
+
if tokenizer is not None:
|
| 142 |
+
x = tokenizer.encode(x)
|
| 143 |
+
if bpe is not None:
|
| 144 |
+
x = bpe.encode(x)
|
| 145 |
+
return x
|
| 146 |
+
|
| 147 |
+
max_positions = utils.resolve_max_positions(
|
| 148 |
+
task.max_positions(), *[model.max_positions() for model in models]
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
src, hyp, mt_scores = parse_fairseq_gen(args.in_text, task)
|
| 152 |
+
model_scores = {}
|
| 153 |
+
logger.info("decode reranker score")
|
| 154 |
+
for batch in make_batches(args, src, hyp, task, max_positions, encode_fn):
|
| 155 |
+
src_tokens = batch.src_tokens
|
| 156 |
+
src_lengths = batch.src_lengths
|
| 157 |
+
if use_cuda:
|
| 158 |
+
src_tokens = src_tokens.cuda()
|
| 159 |
+
src_lengths = src_lengths.cuda()
|
| 160 |
+
|
| 161 |
+
sample = {
|
| 162 |
+
"net_input": {"src_tokens": src_tokens, "src_lengths": src_lengths},
|
| 163 |
+
}
|
| 164 |
+
scores = task.inference_step(generator, models, sample)
|
| 165 |
+
|
| 166 |
+
for id, sc in zip(batch.ids.tolist(), scores.tolist()):
|
| 167 |
+
model_scores[id] = sc[0]
|
| 168 |
+
|
| 169 |
+
model_scores = [model_scores[i] for i in range(len(model_scores))]
|
| 170 |
+
|
| 171 |
+
return src, hyp, mt_scores, model_scores
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def get_score(mt_s, md_s, w1, lp, tgt_len):
|
| 175 |
+
return mt_s / (tgt_len ** lp) * w1 + md_s
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def get_best_hyps(mt_scores, md_scores, hypos, fw_weight, lenpen, beam):
|
| 179 |
+
assert len(mt_scores) == len(md_scores) and len(mt_scores) == len(hypos)
|
| 180 |
+
hypo_scores = []
|
| 181 |
+
best_hypos = []
|
| 182 |
+
best_scores = []
|
| 183 |
+
offset = 0
|
| 184 |
+
for i in range(len(hypos)):
|
| 185 |
+
tgt_len = len(hypos[i].split())
|
| 186 |
+
hypo_scores.append(
|
| 187 |
+
get_score(mt_scores[i], md_scores[i], fw_weight, lenpen, tgt_len)
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
if (i + 1) % beam == 0:
|
| 191 |
+
max_i = np.argmax(hypo_scores)
|
| 192 |
+
best_hypos.append(hypos[offset + max_i])
|
| 193 |
+
best_scores.append(hypo_scores[max_i])
|
| 194 |
+
hypo_scores = []
|
| 195 |
+
offset += beam
|
| 196 |
+
return best_hypos, best_scores
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def eval_metric(args, hypos, ref):
|
| 200 |
+
if args.metric == "bleu":
|
| 201 |
+
score = sacrebleu.corpus_bleu(hypos, [ref]).score
|
| 202 |
+
else:
|
| 203 |
+
score = sacrebleu.corpus_ter(hypos, [ref]).score
|
| 204 |
+
|
| 205 |
+
return score
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def score_target_hypo(args, fw_weight, lp):
|
| 209 |
+
mt_scores = pool_init_variables["mt_scores"]
|
| 210 |
+
model_scores = pool_init_variables["model_scores"]
|
| 211 |
+
hyp = pool_init_variables["hyp"]
|
| 212 |
+
ref = pool_init_variables["ref"]
|
| 213 |
+
best_hypos, _ = get_best_hyps(
|
| 214 |
+
mt_scores, model_scores, hyp, fw_weight, lp, args.beam
|
| 215 |
+
)
|
| 216 |
+
rerank_eval = None
|
| 217 |
+
if ref:
|
| 218 |
+
rerank_eval = eval_metric(args, best_hypos, ref)
|
| 219 |
+
print(f"fw_weight {fw_weight}, lenpen {lp}, eval {rerank_eval}")
|
| 220 |
+
|
| 221 |
+
return rerank_eval
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def print_result(best_scores, best_hypos, output_file):
|
| 225 |
+
for i, (s, h) in enumerate(zip(best_scores, best_hypos)):
|
| 226 |
+
print(f"{i}\t{s}\t{h}", file=output_file)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def main(args):
|
| 230 |
+
utils.import_user_module(args)
|
| 231 |
+
|
| 232 |
+
src, hyp, mt_scores, model_scores = decode_rerank_scores(args)
|
| 233 |
+
|
| 234 |
+
assert (
|
| 235 |
+
not args.tune or args.target_text is not None
|
| 236 |
+
), "--target-text has to be set when tuning weights"
|
| 237 |
+
if args.target_text:
|
| 238 |
+
ref = read_target(args.target_text)
|
| 239 |
+
assert len(src) == len(
|
| 240 |
+
ref
|
| 241 |
+
), f"different numbers of source and target sentences ({len(src)} vs. {len(ref)})"
|
| 242 |
+
|
| 243 |
+
orig_best_hypos = [hyp[i] for i in range(0, len(hyp), args.beam)]
|
| 244 |
+
orig_eval = eval_metric(args, orig_best_hypos, ref)
|
| 245 |
+
|
| 246 |
+
if args.tune:
|
| 247 |
+
logger.info("tune weights for reranking")
|
| 248 |
+
|
| 249 |
+
random_params = np.array(
|
| 250 |
+
[
|
| 251 |
+
[
|
| 252 |
+
random.uniform(
|
| 253 |
+
args.lower_bound_fw_weight, args.upper_bound_fw_weight
|
| 254 |
+
),
|
| 255 |
+
random.uniform(args.lower_bound_lenpen, args.upper_bound_lenpen),
|
| 256 |
+
]
|
| 257 |
+
for k in range(args.num_trials)
|
| 258 |
+
]
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
logger.info("launching pool")
|
| 262 |
+
with Pool(
|
| 263 |
+
32,
|
| 264 |
+
initializer=init_loaded_scores,
|
| 265 |
+
initargs=(mt_scores, model_scores, hyp, ref),
|
| 266 |
+
) as p:
|
| 267 |
+
rerank_scores = p.starmap(
|
| 268 |
+
score_target_hypo,
|
| 269 |
+
[
|
| 270 |
+
(args, random_params[i][0], random_params[i][1],)
|
| 271 |
+
for i in range(args.num_trials)
|
| 272 |
+
],
|
| 273 |
+
)
|
| 274 |
+
if args.metric == "bleu":
|
| 275 |
+
best_index = np.argmax(rerank_scores)
|
| 276 |
+
else:
|
| 277 |
+
best_index = np.argmin(rerank_scores)
|
| 278 |
+
best_fw_weight = random_params[best_index][0]
|
| 279 |
+
best_lenpen = random_params[best_index][1]
|
| 280 |
+
else:
|
| 281 |
+
assert (
|
| 282 |
+
args.lenpen is not None and args.fw_weight is not None
|
| 283 |
+
), "--lenpen and --fw-weight should be set"
|
| 284 |
+
best_fw_weight, best_lenpen = args.fw_weight, args.lenpen
|
| 285 |
+
|
| 286 |
+
best_hypos, best_scores = get_best_hyps(
|
| 287 |
+
mt_scores, model_scores, hyp, best_fw_weight, best_lenpen, args.beam
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
if args.results_path is not None:
|
| 291 |
+
os.makedirs(args.results_path, exist_ok=True)
|
| 292 |
+
output_path = os.path.join(
|
| 293 |
+
args.results_path, "generate-{}.txt".format(args.gen_subset),
|
| 294 |
+
)
|
| 295 |
+
with open(output_path, "w", buffering=1, encoding="utf-8") as o:
|
| 296 |
+
print_result(best_scores, best_hypos, o)
|
| 297 |
+
else:
|
| 298 |
+
print_result(best_scores, best_hypos, sys.stdout)
|
| 299 |
+
|
| 300 |
+
if args.target_text:
|
| 301 |
+
rerank_eval = eval_metric(args, best_hypos, ref)
|
| 302 |
+
print(f"before reranking, {args.metric.upper()}:", orig_eval)
|
| 303 |
+
print(
|
| 304 |
+
f"after reranking with fw_weight={best_fw_weight}, lenpen={best_lenpen}, {args.metric.upper()}:",
|
| 305 |
+
rerank_eval,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def cli_main():
|
| 310 |
+
parser = options.get_generation_parser(interactive=True)
|
| 311 |
+
|
| 312 |
+
parser.add_argument(
|
| 313 |
+
"--in-text",
|
| 314 |
+
default=None,
|
| 315 |
+
required=True,
|
| 316 |
+
help="text from fairseq-interactive output, containing source sentences and hypotheses",
|
| 317 |
+
)
|
| 318 |
+
parser.add_argument("--target-text", default=None, help="reference text")
|
| 319 |
+
parser.add_argument("--metric", type=str, choices=["bleu", "ter"], default="bleu")
|
| 320 |
+
parser.add_argument(
|
| 321 |
+
"--tune",
|
| 322 |
+
action="store_true",
|
| 323 |
+
help="if set, tune weights on fw scores and lenpen instead of applying fixed weights for reranking",
|
| 324 |
+
)
|
| 325 |
+
parser.add_argument(
|
| 326 |
+
"--lower-bound-fw-weight",
|
| 327 |
+
default=0.0,
|
| 328 |
+
type=float,
|
| 329 |
+
help="lower bound of search space",
|
| 330 |
+
)
|
| 331 |
+
parser.add_argument(
|
| 332 |
+
"--upper-bound-fw-weight",
|
| 333 |
+
default=3,
|
| 334 |
+
type=float,
|
| 335 |
+
help="upper bound of search space",
|
| 336 |
+
)
|
| 337 |
+
parser.add_argument(
|
| 338 |
+
"--lower-bound-lenpen",
|
| 339 |
+
default=0.0,
|
| 340 |
+
type=float,
|
| 341 |
+
help="lower bound of search space",
|
| 342 |
+
)
|
| 343 |
+
parser.add_argument(
|
| 344 |
+
"--upper-bound-lenpen",
|
| 345 |
+
default=3,
|
| 346 |
+
type=float,
|
| 347 |
+
help="upper bound of search space",
|
| 348 |
+
)
|
| 349 |
+
parser.add_argument(
|
| 350 |
+
"--fw-weight", type=float, default=None, help="weight on the fw model score"
|
| 351 |
+
)
|
| 352 |
+
parser.add_argument(
|
| 353 |
+
"--num-trials",
|
| 354 |
+
default=1000,
|
| 355 |
+
type=int,
|
| 356 |
+
help="number of trials to do for random search",
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
args = options.parse_args_and_arch(parser)
|
| 360 |
+
main(args)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
if __name__ == "__main__":
|
| 364 |
+
cli_main()
|
data/fairseq/examples/discriminative_reranking_nmt/models/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .discriminative_reranking_model import DiscriminativeNMTReranker
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
__all__ = [
|
| 5 |
+
"DiscriminativeNMTReranker",
|
| 6 |
+
]
|
data/fairseq/examples/discriminative_reranking_nmt/models/discriminative_reranking_model.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass, field
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from fairseq import utils
|
| 8 |
+
from fairseq.dataclass import ChoiceEnum, FairseqDataclass
|
| 9 |
+
from fairseq.models import (
|
| 10 |
+
BaseFairseqModel,
|
| 11 |
+
register_model,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
from fairseq.models.roberta.model import RobertaClassificationHead
|
| 15 |
+
|
| 16 |
+
from fairseq.modules import (
|
| 17 |
+
LayerNorm,
|
| 18 |
+
TransformerSentenceEncoder,
|
| 19 |
+
TransformerSentenceEncoderLayer,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
ACTIVATION_FN_CHOICES = ChoiceEnum(utils.get_available_activation_fns())
|
| 24 |
+
JOINT_CLASSIFICATION_CHOICES = ChoiceEnum(["none", "sent"])
|
| 25 |
+
SENTENCE_REP_CHOICES = ChoiceEnum(["head", "meanpool", "maxpool"])
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def update_init_roberta_model_state(state):
|
| 29 |
+
"""
|
| 30 |
+
update the state_dict of a Roberta model for initializing
|
| 31 |
+
weights of the BertRanker
|
| 32 |
+
"""
|
| 33 |
+
for k in list(state.keys()):
|
| 34 |
+
if ".lm_head." in k or "version" in k:
|
| 35 |
+
del state[k]
|
| 36 |
+
continue
|
| 37 |
+
# remove 'encoder/decoder.sentence_encoder.' from the key
|
| 38 |
+
assert k.startswith("encoder.sentence_encoder.") or k.startswith(
|
| 39 |
+
"decoder.sentence_encoder."
|
| 40 |
+
), f"Cannot recognize parameter name {k}"
|
| 41 |
+
if "layernorm_embedding" in k:
|
| 42 |
+
new_k = k.replace(".layernorm_embedding.", ".emb_layer_norm.")
|
| 43 |
+
state[new_k[25:]] = state[k]
|
| 44 |
+
else:
|
| 45 |
+
state[k[25:]] = state[k]
|
| 46 |
+
del state[k]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class BaseRanker(nn.Module):
|
| 50 |
+
def __init__(self, args, task):
|
| 51 |
+
super().__init__()
|
| 52 |
+
|
| 53 |
+
self.separator_token = task.dictionary.eos()
|
| 54 |
+
self.padding_idx = task.dictionary.pad()
|
| 55 |
+
|
| 56 |
+
def forward(self, src_tokens):
|
| 57 |
+
raise NotImplementedError
|
| 58 |
+
|
| 59 |
+
def get_segment_labels(self, src_tokens):
|
| 60 |
+
segment_boundary = (src_tokens == self.separator_token).long()
|
| 61 |
+
segment_labels = (
|
| 62 |
+
segment_boundary.cumsum(dim=1)
|
| 63 |
+
- segment_boundary
|
| 64 |
+
- (src_tokens == self.padding_idx).long()
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
return segment_labels
|
| 68 |
+
|
| 69 |
+
def get_positions(self, src_tokens, segment_labels):
|
| 70 |
+
segment_positions = (
|
| 71 |
+
torch.arange(src_tokens.shape[1])
|
| 72 |
+
.to(src_tokens.device)
|
| 73 |
+
.repeat(src_tokens.shape[0], 1)
|
| 74 |
+
)
|
| 75 |
+
segment_boundary = (src_tokens == self.separator_token).long()
|
| 76 |
+
_, col_idx = (segment_positions * segment_boundary).nonzero(as_tuple=True)
|
| 77 |
+
col_idx = torch.cat([torch.zeros(1).type_as(col_idx), col_idx])
|
| 78 |
+
offset = torch.cat(
|
| 79 |
+
[
|
| 80 |
+
torch.zeros(1).type_as(segment_boundary),
|
| 81 |
+
segment_boundary.sum(dim=1).cumsum(dim=0)[:-1],
|
| 82 |
+
]
|
| 83 |
+
)
|
| 84 |
+
segment_positions -= col_idx[segment_labels + offset.unsqueeze(1)] * (
|
| 85 |
+
segment_labels != 0
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
padding_mask = src_tokens.ne(self.padding_idx)
|
| 89 |
+
segment_positions = (segment_positions + 1) * padding_mask.type_as(
|
| 90 |
+
segment_positions
|
| 91 |
+
) + self.padding_idx
|
| 92 |
+
|
| 93 |
+
return segment_positions
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class BertRanker(BaseRanker):
|
| 97 |
+
def __init__(self, args, task):
|
| 98 |
+
super(BertRanker, self).__init__(args, task)
|
| 99 |
+
|
| 100 |
+
init_model = getattr(args, "pretrained_model", "")
|
| 101 |
+
self.joint_layers = nn.ModuleList()
|
| 102 |
+
if os.path.isfile(init_model):
|
| 103 |
+
print(f"initialize weight from {init_model}")
|
| 104 |
+
|
| 105 |
+
from fairseq import hub_utils
|
| 106 |
+
|
| 107 |
+
x = hub_utils.from_pretrained(
|
| 108 |
+
os.path.dirname(init_model),
|
| 109 |
+
checkpoint_file=os.path.basename(init_model),
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
in_state_dict = x["models"][0].state_dict()
|
| 113 |
+
init_args = x["args"].model
|
| 114 |
+
|
| 115 |
+
num_positional_emb = init_args.max_positions + task.dictionary.pad() + 1
|
| 116 |
+
|
| 117 |
+
# follow the setup in roberta
|
| 118 |
+
self.model = TransformerSentenceEncoder(
|
| 119 |
+
padding_idx=task.dictionary.pad(),
|
| 120 |
+
vocab_size=len(task.dictionary),
|
| 121 |
+
num_encoder_layers=getattr(
|
| 122 |
+
args, "encoder_layers", init_args.encoder_layers
|
| 123 |
+
),
|
| 124 |
+
embedding_dim=init_args.encoder_embed_dim,
|
| 125 |
+
ffn_embedding_dim=init_args.encoder_ffn_embed_dim,
|
| 126 |
+
num_attention_heads=init_args.encoder_attention_heads,
|
| 127 |
+
dropout=init_args.dropout,
|
| 128 |
+
attention_dropout=init_args.attention_dropout,
|
| 129 |
+
activation_dropout=init_args.activation_dropout,
|
| 130 |
+
num_segments=2, # add language embeddings
|
| 131 |
+
max_seq_len=num_positional_emb,
|
| 132 |
+
offset_positions_by_padding=False,
|
| 133 |
+
encoder_normalize_before=True,
|
| 134 |
+
apply_bert_init=True,
|
| 135 |
+
activation_fn=init_args.activation_fn,
|
| 136 |
+
freeze_embeddings=args.freeze_embeddings,
|
| 137 |
+
n_trans_layers_to_freeze=args.n_trans_layers_to_freeze,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# still need to learn segment embeddings as we added a second language embedding
|
| 141 |
+
if args.freeze_embeddings:
|
| 142 |
+
for p in self.model.segment_embeddings.parameters():
|
| 143 |
+
p.requires_grad = False
|
| 144 |
+
|
| 145 |
+
update_init_roberta_model_state(in_state_dict)
|
| 146 |
+
print("loading weights from the pretrained model")
|
| 147 |
+
self.model.load_state_dict(
|
| 148 |
+
in_state_dict, strict=False
|
| 149 |
+
) # ignore mismatch in language embeddings
|
| 150 |
+
|
| 151 |
+
ffn_embedding_dim = init_args.encoder_ffn_embed_dim
|
| 152 |
+
num_attention_heads = init_args.encoder_attention_heads
|
| 153 |
+
dropout = init_args.dropout
|
| 154 |
+
attention_dropout = init_args.attention_dropout
|
| 155 |
+
activation_dropout = init_args.activation_dropout
|
| 156 |
+
activation_fn = init_args.activation_fn
|
| 157 |
+
|
| 158 |
+
classifier_embed_dim = getattr(
|
| 159 |
+
args, "embed_dim", init_args.encoder_embed_dim
|
| 160 |
+
)
|
| 161 |
+
if classifier_embed_dim != init_args.encoder_embed_dim:
|
| 162 |
+
self.transform_layer = nn.Linear(
|
| 163 |
+
init_args.encoder_embed_dim, classifier_embed_dim
|
| 164 |
+
)
|
| 165 |
+
else:
|
| 166 |
+
self.model = TransformerSentenceEncoder(
|
| 167 |
+
padding_idx=task.dictionary.pad(),
|
| 168 |
+
vocab_size=len(task.dictionary),
|
| 169 |
+
num_encoder_layers=args.encoder_layers,
|
| 170 |
+
embedding_dim=args.embed_dim,
|
| 171 |
+
ffn_embedding_dim=args.ffn_embed_dim,
|
| 172 |
+
num_attention_heads=args.attention_heads,
|
| 173 |
+
dropout=args.dropout,
|
| 174 |
+
attention_dropout=args.attention_dropout,
|
| 175 |
+
activation_dropout=args.activation_dropout,
|
| 176 |
+
max_seq_len=task.max_positions()
|
| 177 |
+
if task.max_positions()
|
| 178 |
+
else args.tokens_per_sample,
|
| 179 |
+
num_segments=2,
|
| 180 |
+
offset_positions_by_padding=False,
|
| 181 |
+
encoder_normalize_before=args.encoder_normalize_before,
|
| 182 |
+
apply_bert_init=args.apply_bert_init,
|
| 183 |
+
activation_fn=args.activation_fn,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
classifier_embed_dim = args.embed_dim
|
| 187 |
+
ffn_embedding_dim = args.ffn_embed_dim
|
| 188 |
+
num_attention_heads = args.attention_heads
|
| 189 |
+
dropout = args.dropout
|
| 190 |
+
attention_dropout = args.attention_dropout
|
| 191 |
+
activation_dropout = args.activation_dropout
|
| 192 |
+
activation_fn = args.activation_fn
|
| 193 |
+
|
| 194 |
+
self.joint_classification = args.joint_classification
|
| 195 |
+
if args.joint_classification == "sent":
|
| 196 |
+
if args.joint_normalize_before:
|
| 197 |
+
self.joint_layer_norm = LayerNorm(classifier_embed_dim)
|
| 198 |
+
else:
|
| 199 |
+
self.joint_layer_norm = None
|
| 200 |
+
|
| 201 |
+
self.joint_layers = nn.ModuleList(
|
| 202 |
+
[
|
| 203 |
+
TransformerSentenceEncoderLayer(
|
| 204 |
+
embedding_dim=classifier_embed_dim,
|
| 205 |
+
ffn_embedding_dim=ffn_embedding_dim,
|
| 206 |
+
num_attention_heads=num_attention_heads,
|
| 207 |
+
dropout=dropout,
|
| 208 |
+
attention_dropout=attention_dropout,
|
| 209 |
+
activation_dropout=activation_dropout,
|
| 210 |
+
activation_fn=activation_fn,
|
| 211 |
+
)
|
| 212 |
+
for _ in range(args.num_joint_layers)
|
| 213 |
+
]
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
self.classifier = RobertaClassificationHead(
|
| 217 |
+
classifier_embed_dim,
|
| 218 |
+
classifier_embed_dim,
|
| 219 |
+
1, # num_classes
|
| 220 |
+
"tanh",
|
| 221 |
+
args.classifier_dropout,
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
def forward(self, src_tokens, src_lengths):
|
| 225 |
+
segment_labels = self.get_segment_labels(src_tokens)
|
| 226 |
+
positions = self.get_positions(src_tokens, segment_labels)
|
| 227 |
+
|
| 228 |
+
inner_states, _ = self.model(
|
| 229 |
+
tokens=src_tokens,
|
| 230 |
+
segment_labels=segment_labels,
|
| 231 |
+
last_state_only=True,
|
| 232 |
+
positions=positions,
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
return inner_states[-1].transpose(0, 1) # T x B x C -> B x T x C
|
| 236 |
+
|
| 237 |
+
def sentence_forward(self, encoder_out, src_tokens=None, sentence_rep="head"):
|
| 238 |
+
# encoder_out: B x T x C
|
| 239 |
+
if sentence_rep == "head":
|
| 240 |
+
x = encoder_out[:, :1, :]
|
| 241 |
+
else: # 'meanpool', 'maxpool'
|
| 242 |
+
assert src_tokens is not None, "meanpool requires src_tokens input"
|
| 243 |
+
segment_labels = self.get_segment_labels(src_tokens)
|
| 244 |
+
padding_mask = src_tokens.ne(self.padding_idx)
|
| 245 |
+
encoder_mask = segment_labels * padding_mask.type_as(segment_labels)
|
| 246 |
+
|
| 247 |
+
if sentence_rep == "meanpool":
|
| 248 |
+
ntokens = torch.sum(encoder_mask, dim=1, keepdim=True)
|
| 249 |
+
x = torch.sum(
|
| 250 |
+
encoder_out * encoder_mask.unsqueeze(2), dim=1, keepdim=True
|
| 251 |
+
) / ntokens.unsqueeze(2).type_as(encoder_out)
|
| 252 |
+
else: # 'maxpool'
|
| 253 |
+
encoder_out[
|
| 254 |
+
(encoder_mask == 0).unsqueeze(2).repeat(1, 1, encoder_out.shape[-1])
|
| 255 |
+
] = -float("inf")
|
| 256 |
+
x, _ = torch.max(encoder_out, dim=1, keepdim=True)
|
| 257 |
+
|
| 258 |
+
if hasattr(self, "transform_layer"):
|
| 259 |
+
x = self.transform_layer(x)
|
| 260 |
+
|
| 261 |
+
return x # B x 1 x C
|
| 262 |
+
|
| 263 |
+
def joint_forward(self, x):
|
| 264 |
+
# x: T x B x C
|
| 265 |
+
if self.joint_layer_norm:
|
| 266 |
+
x = self.joint_layer_norm(x.transpose(0, 1))
|
| 267 |
+
x = x.transpose(0, 1)
|
| 268 |
+
|
| 269 |
+
for layer in self.joint_layers:
|
| 270 |
+
x, _ = layer(x, self_attn_padding_mask=None)
|
| 271 |
+
return x
|
| 272 |
+
|
| 273 |
+
def classification_forward(self, x):
|
| 274 |
+
# x: B x T x C
|
| 275 |
+
return self.classifier(x)
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
@dataclass
|
| 279 |
+
class DiscriminativeNMTRerankerConfig(FairseqDataclass):
|
| 280 |
+
pretrained_model: str = field(
|
| 281 |
+
default="", metadata={"help": "pretrained model to load"}
|
| 282 |
+
)
|
| 283 |
+
sentence_rep: SENTENCE_REP_CHOICES = field(
|
| 284 |
+
default="head",
|
| 285 |
+
metadata={
|
| 286 |
+
"help": "method to transform the output of the transformer stack to a sentence-level representation"
|
| 287 |
+
},
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
dropout: float = field(default=0.1, metadata={"help": "dropout probability"})
|
| 291 |
+
attention_dropout: float = field(
|
| 292 |
+
default=0.0, metadata={"help": "dropout probability for attention weights"}
|
| 293 |
+
)
|
| 294 |
+
activation_dropout: float = field(
|
| 295 |
+
default=0.0, metadata={"help": "dropout probability after activation in FFN"}
|
| 296 |
+
)
|
| 297 |
+
classifier_dropout: float = field(
|
| 298 |
+
default=0.0, metadata={"help": "classifier dropout probability"}
|
| 299 |
+
)
|
| 300 |
+
embed_dim: int = field(default=768, metadata={"help": "embedding dimension"})
|
| 301 |
+
ffn_embed_dim: int = field(
|
| 302 |
+
default=2048, metadata={"help": "embedding dimension for FFN"}
|
| 303 |
+
)
|
| 304 |
+
encoder_layers: int = field(default=12, metadata={"help": "num encoder layers"})
|
| 305 |
+
attention_heads: int = field(default=8, metadata={"help": "num attention heads"})
|
| 306 |
+
encoder_normalize_before: bool = field(
|
| 307 |
+
default=False, metadata={"help": "apply layernorm before each encoder block"}
|
| 308 |
+
)
|
| 309 |
+
apply_bert_init: bool = field(
|
| 310 |
+
default=False, metadata={"help": "use custom param initialization for BERT"}
|
| 311 |
+
)
|
| 312 |
+
activation_fn: ACTIVATION_FN_CHOICES = field(
|
| 313 |
+
default="relu", metadata={"help": "activation function to use"}
|
| 314 |
+
)
|
| 315 |
+
freeze_embeddings: bool = field(
|
| 316 |
+
default=False, metadata={"help": "freeze embeddings in the pretrained model"}
|
| 317 |
+
)
|
| 318 |
+
n_trans_layers_to_freeze: int = field(
|
| 319 |
+
default=0,
|
| 320 |
+
metadata={
|
| 321 |
+
"help": "number of layers to freeze in the pretrained transformer model"
|
| 322 |
+
},
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
# joint classfication
|
| 326 |
+
joint_classification: JOINT_CLASSIFICATION_CHOICES = field(
|
| 327 |
+
default="none",
|
| 328 |
+
metadata={"help": "method to compute joint features for classification"},
|
| 329 |
+
)
|
| 330 |
+
num_joint_layers: int = field(
|
| 331 |
+
default=1, metadata={"help": "number of joint layers"}
|
| 332 |
+
)
|
| 333 |
+
joint_normalize_before: bool = field(
|
| 334 |
+
default=False,
|
| 335 |
+
metadata={"help": "apply layer norm on the input to the joint layer"},
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
@register_model(
|
| 340 |
+
"discriminative_nmt_reranker", dataclass=DiscriminativeNMTRerankerConfig
|
| 341 |
+
)
|
| 342 |
+
class DiscriminativeNMTReranker(BaseFairseqModel):
|
| 343 |
+
@classmethod
|
| 344 |
+
def build_model(cls, args, task):
|
| 345 |
+
model = BertRanker(args, task)
|
| 346 |
+
return DiscriminativeNMTReranker(args, model)
|
| 347 |
+
|
| 348 |
+
def __init__(self, args, model):
|
| 349 |
+
super().__init__()
|
| 350 |
+
|
| 351 |
+
self.model = model
|
| 352 |
+
self.sentence_rep = args.sentence_rep
|
| 353 |
+
self.joint_classification = args.joint_classification
|
| 354 |
+
|
| 355 |
+
def forward(self, src_tokens, src_lengths, **kwargs):
|
| 356 |
+
return self.model(src_tokens, src_lengths)
|
| 357 |
+
|
| 358 |
+
def sentence_forward(self, encoder_out, src_tokens):
|
| 359 |
+
return self.model.sentence_forward(encoder_out, src_tokens, self.sentence_rep)
|
| 360 |
+
|
| 361 |
+
def joint_forward(self, x):
|
| 362 |
+
return self.model.joint_forward(x)
|
| 363 |
+
|
| 364 |
+
def classification_forward(self, x):
|
| 365 |
+
return self.model.classification_forward(x)
|
data/fairseq/examples/discriminative_reranking_nmt/scripts/prep_data.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
from multiprocessing import Pool
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import sacrebleu
|
| 8 |
+
import sentencepiece as spm
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def read_text_file(filename):
|
| 12 |
+
with open(filename, "r") as f:
|
| 13 |
+
output = [line.strip() for line in f]
|
| 14 |
+
|
| 15 |
+
return output
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def get_bleu(in_sent, target_sent):
|
| 19 |
+
bleu = sacrebleu.corpus_bleu([in_sent], [[target_sent]])
|
| 20 |
+
out = " ".join(
|
| 21 |
+
map(str, [bleu.score, bleu.sys_len, bleu.ref_len] + bleu.counts + bleu.totals)
|
| 22 |
+
)
|
| 23 |
+
return out
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def get_ter(in_sent, target_sent):
|
| 27 |
+
ter = sacrebleu.corpus_ter([in_sent], [[target_sent]])
|
| 28 |
+
out = " ".join(map(str, [ter.score, ter.num_edits, ter.ref_length]))
|
| 29 |
+
return out
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def init(sp_model):
|
| 33 |
+
global sp
|
| 34 |
+
sp = spm.SentencePieceProcessor()
|
| 35 |
+
sp.Load(sp_model)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def process(source_sent, target_sent, hypo_sent, metric):
|
| 39 |
+
source_bpe = " ".join(sp.EncodeAsPieces(source_sent))
|
| 40 |
+
hypo_bpe = [" ".join(sp.EncodeAsPieces(h)) for h in hypo_sent]
|
| 41 |
+
|
| 42 |
+
if metric == "bleu":
|
| 43 |
+
score_str = [get_bleu(h, target_sent) for h in hypo_sent]
|
| 44 |
+
else: # ter
|
| 45 |
+
score_str = [get_ter(h, target_sent) for h in hypo_sent]
|
| 46 |
+
|
| 47 |
+
return source_bpe, hypo_bpe, score_str
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def main(args):
|
| 51 |
+
assert (
|
| 52 |
+
args.split.startswith("train") or args.num_shards == 1
|
| 53 |
+
), "--num-shards should be set to 1 for valid and test sets"
|
| 54 |
+
assert (
|
| 55 |
+
args.split.startswith("train")
|
| 56 |
+
or args.split.startswith("valid")
|
| 57 |
+
or args.split.startswith("test")
|
| 58 |
+
), "--split should be set to train[n]/valid[n]/test[n]"
|
| 59 |
+
|
| 60 |
+
source_sents = read_text_file(args.input_source)
|
| 61 |
+
target_sents = read_text_file(args.input_target)
|
| 62 |
+
|
| 63 |
+
num_sents = len(source_sents)
|
| 64 |
+
assert num_sents == len(
|
| 65 |
+
target_sents
|
| 66 |
+
), f"{args.input_source} and {args.input_target} should have the same number of sentences."
|
| 67 |
+
|
| 68 |
+
hypo_sents = read_text_file(args.input_hypo)
|
| 69 |
+
assert (
|
| 70 |
+
len(hypo_sents) % args.beam == 0
|
| 71 |
+
), f"Number of hypotheses ({len(hypo_sents)}) cannot be divided by beam size ({args.beam})."
|
| 72 |
+
|
| 73 |
+
hypo_sents = [
|
| 74 |
+
hypo_sents[i : i + args.beam] for i in range(0, len(hypo_sents), args.beam)
|
| 75 |
+
]
|
| 76 |
+
assert num_sents == len(
|
| 77 |
+
hypo_sents
|
| 78 |
+
), f"{args.input_hypo} should contain {num_sents * args.beam} hypotheses but only has {len(hypo_sents) * args.beam}. (--beam={args.beam})"
|
| 79 |
+
|
| 80 |
+
output_dir = args.output_dir / args.metric
|
| 81 |
+
for ns in range(args.num_shards):
|
| 82 |
+
print(f"processing shard {ns+1}/{args.num_shards}")
|
| 83 |
+
shard_output_dir = output_dir / f"split{ns+1}"
|
| 84 |
+
source_output_dir = shard_output_dir / "input_src"
|
| 85 |
+
hypo_output_dir = shard_output_dir / "input_tgt"
|
| 86 |
+
metric_output_dir = shard_output_dir / args.metric
|
| 87 |
+
|
| 88 |
+
source_output_dir.mkdir(parents=True, exist_ok=True)
|
| 89 |
+
hypo_output_dir.mkdir(parents=True, exist_ok=True)
|
| 90 |
+
metric_output_dir.mkdir(parents=True, exist_ok=True)
|
| 91 |
+
|
| 92 |
+
if args.n_proc > 1:
|
| 93 |
+
with Pool(
|
| 94 |
+
args.n_proc, initializer=init, initargs=(args.sentencepiece_model,)
|
| 95 |
+
) as p:
|
| 96 |
+
output = p.starmap(
|
| 97 |
+
process,
|
| 98 |
+
[
|
| 99 |
+
(source_sents[i], target_sents[i], hypo_sents[i], args.metric)
|
| 100 |
+
for i in range(ns, num_sents, args.num_shards)
|
| 101 |
+
],
|
| 102 |
+
)
|
| 103 |
+
else:
|
| 104 |
+
init(args.sentencepiece_model)
|
| 105 |
+
output = [
|
| 106 |
+
process(source_sents[i], target_sents[i], hypo_sents[i], args.metric)
|
| 107 |
+
for i in range(ns, num_sents, args.num_shards)
|
| 108 |
+
]
|
| 109 |
+
|
| 110 |
+
with open(source_output_dir / f"{args.split}.bpe", "w") as s_o, open(
|
| 111 |
+
hypo_output_dir / f"{args.split}.bpe", "w"
|
| 112 |
+
) as h_o, open(metric_output_dir / f"{args.split}.{args.metric}", "w") as m_o:
|
| 113 |
+
for source_bpe, hypo_bpe, score_str in output:
|
| 114 |
+
assert len(hypo_bpe) == len(score_str)
|
| 115 |
+
for h, m in zip(hypo_bpe, score_str):
|
| 116 |
+
s_o.write(f"{source_bpe}\n")
|
| 117 |
+
h_o.write(f"{h}\n")
|
| 118 |
+
m_o.write(f"{m}\n")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
if __name__ == "__main__":
|
| 122 |
+
parser = argparse.ArgumentParser()
|
| 123 |
+
parser.add_argument("--input-source", type=Path, required=True)
|
| 124 |
+
parser.add_argument("--input-target", type=Path, required=True)
|
| 125 |
+
parser.add_argument("--input-hypo", type=Path, required=True)
|
| 126 |
+
parser.add_argument("--output-dir", type=Path, required=True)
|
| 127 |
+
parser.add_argument("--split", type=str, required=True)
|
| 128 |
+
parser.add_argument("--beam", type=int, required=True)
|
| 129 |
+
parser.add_argument("--sentencepiece-model", type=str, required=True)
|
| 130 |
+
parser.add_argument("--metric", type=str, choices=["bleu", "ter"], default="bleu")
|
| 131 |
+
parser.add_argument("--num-shards", type=int, default=1)
|
| 132 |
+
parser.add_argument("--n-proc", type=int, default=8)
|
| 133 |
+
|
| 134 |
+
args = parser.parse_args()
|
| 135 |
+
|
| 136 |
+
main(args)
|
data/fairseq/examples/discriminative_reranking_nmt/tasks/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .discriminative_reranking_task import DiscriminativeRerankingNMTTask
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
__all__ = [
|
| 5 |
+
"DiscriminativeRerankingNMTTask",
|
| 6 |
+
]
|
data/fairseq/examples/discriminative_reranking_nmt/tasks/discriminative_reranking_task.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from dataclasses import dataclass, field
|
| 7 |
+
|
| 8 |
+
import itertools
|
| 9 |
+
import logging
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
from fairseq.logging import metrics
|
| 16 |
+
from fairseq.data import (
|
| 17 |
+
ConcatDataset,
|
| 18 |
+
ConcatSentencesDataset,
|
| 19 |
+
data_utils,
|
| 20 |
+
Dictionary,
|
| 21 |
+
IdDataset,
|
| 22 |
+
indexed_dataset,
|
| 23 |
+
NestedDictionaryDataset,
|
| 24 |
+
NumSamplesDataset,
|
| 25 |
+
NumelDataset,
|
| 26 |
+
PrependTokenDataset,
|
| 27 |
+
RawLabelDataset,
|
| 28 |
+
RightPadDataset,
|
| 29 |
+
SortDataset,
|
| 30 |
+
TruncateDataset,
|
| 31 |
+
TokenBlockDataset,
|
| 32 |
+
)
|
| 33 |
+
from fairseq.dataclass import ChoiceEnum, FairseqDataclass
|
| 34 |
+
from fairseq.tasks import FairseqTask, register_task
|
| 35 |
+
from omegaconf import II, MISSING
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
EVAL_BLEU_ORDER = 4
|
| 39 |
+
TARGET_METRIC_CHOICES = ChoiceEnum(["bleu", "ter"])
|
| 40 |
+
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@dataclass
|
| 45 |
+
class DiscriminativeRerankingNMTConfig(FairseqDataclass):
|
| 46 |
+
data: str = field(default=MISSING, metadata={"help": "path to data directory"})
|
| 47 |
+
num_data_splits: int = field(
|
| 48 |
+
default=1, metadata={"help": "total number of data splits"}
|
| 49 |
+
)
|
| 50 |
+
no_shuffle: bool = field(
|
| 51 |
+
default=False, metadata={"help": "do not shuffle training data"}
|
| 52 |
+
)
|
| 53 |
+
max_positions: int = field(
|
| 54 |
+
default=512, metadata={"help": "number of positional embeddings to learn"}
|
| 55 |
+
)
|
| 56 |
+
include_src: bool = field(
|
| 57 |
+
default=False, metadata={"help": "include source sentence"}
|
| 58 |
+
)
|
| 59 |
+
mt_beam: int = field(default=50, metadata={"help": "beam size of input hypotheses"})
|
| 60 |
+
eval_target_metric: bool = field(
|
| 61 |
+
default=False,
|
| 62 |
+
metadata={"help": "evaluation with the target metric during validation"},
|
| 63 |
+
)
|
| 64 |
+
target_metric: TARGET_METRIC_CHOICES = field(
|
| 65 |
+
default="bleu", metadata={"help": "name of the target metric to optimize for"}
|
| 66 |
+
)
|
| 67 |
+
train_subset: str = field(
|
| 68 |
+
default=II("dataset.train_subset"),
|
| 69 |
+
metadata={"help": "data subset to use for training (e.g. train, valid, test)"},
|
| 70 |
+
)
|
| 71 |
+
seed: int = field(
|
| 72 |
+
default=II("common.seed"),
|
| 73 |
+
metadata={"help": "pseudo random number generator seed"},
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class RerankerScorer(object):
|
| 78 |
+
"""Scores the target for a given (source (optional), target) input."""
|
| 79 |
+
|
| 80 |
+
def __init__(self, args, mt_beam):
|
| 81 |
+
self.mt_beam = mt_beam
|
| 82 |
+
|
| 83 |
+
@torch.no_grad()
|
| 84 |
+
def generate(self, models, sample, **kwargs):
|
| 85 |
+
"""Score a batch of translations."""
|
| 86 |
+
net_input = sample["net_input"]
|
| 87 |
+
|
| 88 |
+
assert len(models) == 1, "does not support model ensemble"
|
| 89 |
+
model = models[0]
|
| 90 |
+
|
| 91 |
+
bs = net_input["src_tokens"].shape[0]
|
| 92 |
+
assert (
|
| 93 |
+
model.joint_classification == "none" or bs % self.mt_beam == 0
|
| 94 |
+
), f"invalid batch size ({bs}) for joint classification with beam size ({self.mt_beam})"
|
| 95 |
+
|
| 96 |
+
model.eval()
|
| 97 |
+
logits = model(**net_input)
|
| 98 |
+
|
| 99 |
+
batch_out = model.sentence_forward(logits, net_input["src_tokens"])
|
| 100 |
+
if model.joint_classification == "sent":
|
| 101 |
+
batch_out = model.joint_forward(
|
| 102 |
+
batch_out.view(self.mt_beam, bs // self.mt_beam, -1)
|
| 103 |
+
)
|
| 104 |
+
scores = model.classification_forward(
|
| 105 |
+
batch_out.view(bs, 1, -1)
|
| 106 |
+
) # input: B x T x C
|
| 107 |
+
|
| 108 |
+
return scores
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@register_task(
|
| 112 |
+
"discriminative_reranking_nmt", dataclass=DiscriminativeRerankingNMTConfig
|
| 113 |
+
)
|
| 114 |
+
class DiscriminativeRerankingNMTTask(FairseqTask):
|
| 115 |
+
"""
|
| 116 |
+
Translation rerank task.
|
| 117 |
+
The input can be either (src, tgt) sentence pairs or tgt sentence only.
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
cfg: DiscriminativeRerankingNMTConfig
|
| 121 |
+
|
| 122 |
+
def __init__(self, cfg: DiscriminativeRerankingNMTConfig, data_dictionary=None):
|
| 123 |
+
super().__init__(cfg)
|
| 124 |
+
self.dictionary = data_dictionary
|
| 125 |
+
self._max_positions = cfg.max_positions
|
| 126 |
+
# args.tokens_per_sample = self._max_positions
|
| 127 |
+
# self.num_classes = 1 # for model
|
| 128 |
+
|
| 129 |
+
@classmethod
|
| 130 |
+
def load_dictionary(cls, cfg, filename):
|
| 131 |
+
"""Load the dictionary from the filename"""
|
| 132 |
+
dictionary = Dictionary.load(filename)
|
| 133 |
+
dictionary.add_symbol("<mask>") # for loading pretrained XLMR model
|
| 134 |
+
|
| 135 |
+
return dictionary
|
| 136 |
+
|
| 137 |
+
@classmethod
|
| 138 |
+
def setup_task(cls, cfg: DiscriminativeRerankingNMTConfig, **kwargs):
|
| 139 |
+
# load data dictionary (assume joint dictionary)
|
| 140 |
+
data_path = cfg.data
|
| 141 |
+
data_dict = cls.load_dictionary(
|
| 142 |
+
cfg, os.path.join(data_path, "input_src/dict.txt")
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
logger.info("[input] src dictionary: {} types".format(len(data_dict)))
|
| 146 |
+
|
| 147 |
+
return DiscriminativeRerankingNMTTask(cfg, data_dict)
|
| 148 |
+
|
| 149 |
+
def load_dataset(self, split, epoch=0, combine=False, **kwargs):
|
| 150 |
+
"""Load a given dataset split (e.g., train, valid, test)."""
|
| 151 |
+
if self.cfg.data.endswith("1"):
|
| 152 |
+
data_shard = (epoch - 1) % self.cfg.num_data_splits + 1
|
| 153 |
+
data_path = self.cfg.data[:-1] + str(data_shard)
|
| 154 |
+
else:
|
| 155 |
+
data_path = self.cfg.data
|
| 156 |
+
|
| 157 |
+
def get_path(type, data_split):
|
| 158 |
+
return os.path.join(data_path, str(type), data_split)
|
| 159 |
+
|
| 160 |
+
def make_dataset(type, dictionary, data_split, combine):
|
| 161 |
+
split_path = get_path(type, data_split)
|
| 162 |
+
|
| 163 |
+
dataset = data_utils.load_indexed_dataset(
|
| 164 |
+
split_path,
|
| 165 |
+
dictionary,
|
| 166 |
+
combine=combine,
|
| 167 |
+
)
|
| 168 |
+
return dataset
|
| 169 |
+
|
| 170 |
+
def load_split(data_split, metric):
|
| 171 |
+
input_src = None
|
| 172 |
+
if self.cfg.include_src:
|
| 173 |
+
input_src = make_dataset(
|
| 174 |
+
"input_src", self.dictionary, data_split, combine=False
|
| 175 |
+
)
|
| 176 |
+
assert input_src is not None, "could not find dataset: {}".format(
|
| 177 |
+
get_path("input_src", data_split)
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
input_tgt = make_dataset(
|
| 181 |
+
"input_tgt", self.dictionary, data_split, combine=False
|
| 182 |
+
)
|
| 183 |
+
assert input_tgt is not None, "could not find dataset: {}".format(
|
| 184 |
+
get_path("input_tgt", data_split)
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
label_path = f"{get_path(metric, data_split)}.{metric}"
|
| 188 |
+
assert os.path.exists(label_path), f"could not find dataset: {label_path}"
|
| 189 |
+
|
| 190 |
+
np_labels = np.loadtxt(label_path)
|
| 191 |
+
if self.cfg.target_metric == "ter":
|
| 192 |
+
np_labels = -np_labels
|
| 193 |
+
label = RawLabelDataset(np_labels)
|
| 194 |
+
|
| 195 |
+
return input_src, input_tgt, label
|
| 196 |
+
|
| 197 |
+
src_datasets = []
|
| 198 |
+
tgt_datasets = []
|
| 199 |
+
label_datasets = []
|
| 200 |
+
|
| 201 |
+
if split == self.cfg.train_subset:
|
| 202 |
+
for k in itertools.count():
|
| 203 |
+
split_k = "train" + (str(k) if k > 0 else "")
|
| 204 |
+
prefix = os.path.join(data_path, "input_tgt", split_k)
|
| 205 |
+
if not indexed_dataset.dataset_exists(prefix, impl=None):
|
| 206 |
+
if k > 0:
|
| 207 |
+
break
|
| 208 |
+
else:
|
| 209 |
+
raise FileNotFoundError(f"Dataset not found: {prefix}")
|
| 210 |
+
input_src, input_tgt, label = load_split(
|
| 211 |
+
split_k, self.cfg.target_metric
|
| 212 |
+
)
|
| 213 |
+
src_datasets.append(input_src)
|
| 214 |
+
tgt_datasets.append(input_tgt)
|
| 215 |
+
label_datasets.append(label)
|
| 216 |
+
else:
|
| 217 |
+
input_src, input_tgt, label = load_split(split, self.cfg.target_metric)
|
| 218 |
+
src_datasets.append(input_src)
|
| 219 |
+
tgt_datasets.append(input_tgt)
|
| 220 |
+
label_datasets.append(label)
|
| 221 |
+
|
| 222 |
+
if len(tgt_datasets) == 1:
|
| 223 |
+
input_tgt, label = tgt_datasets[0], label_datasets[0]
|
| 224 |
+
if self.cfg.include_src:
|
| 225 |
+
input_src = src_datasets[0]
|
| 226 |
+
else:
|
| 227 |
+
input_tgt = ConcatDataset(tgt_datasets)
|
| 228 |
+
label = ConcatDataset(label_datasets)
|
| 229 |
+
if self.cfg.include_src:
|
| 230 |
+
input_src = ConcatDataset(src_datasets)
|
| 231 |
+
|
| 232 |
+
input_tgt = TruncateDataset(input_tgt, self.cfg.max_positions)
|
| 233 |
+
if self.cfg.include_src:
|
| 234 |
+
input_src = PrependTokenDataset(input_src, self.dictionary.bos())
|
| 235 |
+
input_src = TruncateDataset(input_src, self.cfg.max_positions)
|
| 236 |
+
src_lengths = NumelDataset(input_src, reduce=False)
|
| 237 |
+
src_tokens = ConcatSentencesDataset(input_src, input_tgt)
|
| 238 |
+
else:
|
| 239 |
+
src_tokens = PrependTokenDataset(input_tgt, self.dictionary.bos())
|
| 240 |
+
src_lengths = NumelDataset(src_tokens, reduce=False)
|
| 241 |
+
|
| 242 |
+
dataset = {
|
| 243 |
+
"id": IdDataset(),
|
| 244 |
+
"net_input": {
|
| 245 |
+
"src_tokens": RightPadDataset(
|
| 246 |
+
src_tokens,
|
| 247 |
+
pad_idx=self.source_dictionary.pad(),
|
| 248 |
+
),
|
| 249 |
+
"src_lengths": src_lengths,
|
| 250 |
+
},
|
| 251 |
+
"nsentences": NumSamplesDataset(),
|
| 252 |
+
"ntokens": NumelDataset(src_tokens, reduce=True),
|
| 253 |
+
"target": label,
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
dataset = NestedDictionaryDataset(
|
| 257 |
+
dataset,
|
| 258 |
+
sizes=[src_tokens.sizes],
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
assert (
|
| 262 |
+
len(dataset) % self.cfg.mt_beam == 0
|
| 263 |
+
), "dataset size (%d) is not a multiple of beam size (%d)" % (
|
| 264 |
+
len(dataset),
|
| 265 |
+
self.cfg.mt_beam,
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
# no need to shuffle valid/test sets
|
| 269 |
+
if not self.cfg.no_shuffle and split == self.cfg.train_subset:
|
| 270 |
+
|
| 271 |
+
# need to keep all hypothese together
|
| 272 |
+
start_idx = np.arange(0, len(dataset), self.cfg.mt_beam)
|
| 273 |
+
with data_utils.numpy_seed(self.cfg.seed + epoch):
|
| 274 |
+
np.random.shuffle(start_idx)
|
| 275 |
+
|
| 276 |
+
idx = np.arange(0, self.cfg.mt_beam)
|
| 277 |
+
shuffle = np.tile(idx, (len(start_idx), 1)).reshape(-1) + np.tile(
|
| 278 |
+
start_idx, (self.cfg.mt_beam, 1)
|
| 279 |
+
).transpose().reshape(-1)
|
| 280 |
+
|
| 281 |
+
dataset = SortDataset(
|
| 282 |
+
dataset,
|
| 283 |
+
sort_order=[shuffle],
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
logger.info(f"Loaded {split} with #samples: {len(dataset)}")
|
| 287 |
+
|
| 288 |
+
self.datasets[split] = dataset
|
| 289 |
+
return self.datasets[split]
|
| 290 |
+
|
| 291 |
+
def build_dataset_for_inference(self, src_tokens, src_lengths, **kwargs):
|
| 292 |
+
assert not self.cfg.include_src or len(src_tokens[0]) == 2
|
| 293 |
+
input_src = None
|
| 294 |
+
if self.cfg.include_src:
|
| 295 |
+
input_src = TokenBlockDataset(
|
| 296 |
+
[t[0] for t in src_tokens],
|
| 297 |
+
[l[0] for l in src_lengths],
|
| 298 |
+
block_size=None, # ignored for "eos" break mode
|
| 299 |
+
pad=self.source_dictionary.pad(),
|
| 300 |
+
eos=self.source_dictionary.eos(),
|
| 301 |
+
break_mode="eos",
|
| 302 |
+
)
|
| 303 |
+
input_src = PrependTokenDataset(input_src, self.dictionary.bos())
|
| 304 |
+
input_src = TruncateDataset(input_src, self.cfg.max_positions)
|
| 305 |
+
|
| 306 |
+
input_tgt = TokenBlockDataset(
|
| 307 |
+
[t[-1] for t in src_tokens],
|
| 308 |
+
[l[-1] for l in src_lengths],
|
| 309 |
+
block_size=None, # ignored for "eos" break mode
|
| 310 |
+
pad=self.source_dictionary.pad(),
|
| 311 |
+
eos=self.source_dictionary.eos(),
|
| 312 |
+
break_mode="eos",
|
| 313 |
+
)
|
| 314 |
+
input_tgt = TruncateDataset(input_tgt, self.cfg.max_positions)
|
| 315 |
+
if self.cfg.include_src:
|
| 316 |
+
src_tokens = ConcatSentencesDataset(input_src, input_tgt)
|
| 317 |
+
src_lengths = NumelDataset(input_src, reduce=False)
|
| 318 |
+
else:
|
| 319 |
+
input_tgt = PrependTokenDataset(input_tgt, self.dictionary.bos())
|
| 320 |
+
src_tokens = input_tgt
|
| 321 |
+
src_lengths = NumelDataset(src_tokens, reduce=False)
|
| 322 |
+
|
| 323 |
+
dataset = {
|
| 324 |
+
"id": IdDataset(),
|
| 325 |
+
"net_input": {
|
| 326 |
+
"src_tokens": RightPadDataset(
|
| 327 |
+
src_tokens,
|
| 328 |
+
pad_idx=self.source_dictionary.pad(),
|
| 329 |
+
),
|
| 330 |
+
"src_lengths": src_lengths,
|
| 331 |
+
},
|
| 332 |
+
"nsentences": NumSamplesDataset(),
|
| 333 |
+
"ntokens": NumelDataset(src_tokens, reduce=True),
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
return NestedDictionaryDataset(
|
| 337 |
+
dataset,
|
| 338 |
+
sizes=[src_tokens.sizes],
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
def build_model(self, cfg: FairseqDataclass, from_checkpoint: bool = False):
|
| 342 |
+
return super().build_model(cfg)
|
| 343 |
+
|
| 344 |
+
def build_generator(self, args):
|
| 345 |
+
return RerankerScorer(args, mt_beam=self.cfg.mt_beam)
|
| 346 |
+
|
| 347 |
+
def max_positions(self):
|
| 348 |
+
return self._max_positions
|
| 349 |
+
|
| 350 |
+
@property
|
| 351 |
+
def source_dictionary(self):
|
| 352 |
+
return self.dictionary
|
| 353 |
+
|
| 354 |
+
@property
|
| 355 |
+
def target_dictionary(self):
|
| 356 |
+
return self.dictionary
|
| 357 |
+
|
| 358 |
+
def create_dummy_batch(self, device):
|
| 359 |
+
dummy_target = (
|
| 360 |
+
torch.zeros(self.cfg.mt_beam, EVAL_BLEU_ORDER * 2 + 3).long().to(device)
|
| 361 |
+
if not self.cfg.eval_ter
|
| 362 |
+
else torch.zeros(self.cfg.mt_beam, 3).long().to(device)
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
return {
|
| 366 |
+
"id": torch.zeros(self.cfg.mt_beam, 1).long().to(device),
|
| 367 |
+
"net_input": {
|
| 368 |
+
"src_tokens": torch.zeros(self.cfg.mt_beam, 4).long().to(device),
|
| 369 |
+
"src_lengths": torch.ones(self.cfg.mt_beam, 1).long().to(device),
|
| 370 |
+
},
|
| 371 |
+
"nsentences": 0,
|
| 372 |
+
"ntokens": 0,
|
| 373 |
+
"target": dummy_target,
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
def train_step(
|
| 377 |
+
self, sample, model, criterion, optimizer, update_num, ignore_grad=False
|
| 378 |
+
):
|
| 379 |
+
if ignore_grad and sample is None:
|
| 380 |
+
sample = self.create_dummy_batch(model.device)
|
| 381 |
+
|
| 382 |
+
return super().train_step(
|
| 383 |
+
sample, model, criterion, optimizer, update_num, ignore_grad
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
def valid_step(self, sample, model, criterion):
|
| 387 |
+
if sample is None:
|
| 388 |
+
sample = self.create_dummy_batch(model.device)
|
| 389 |
+
|
| 390 |
+
loss, sample_size, logging_output = super().valid_step(sample, model, criterion)
|
| 391 |
+
|
| 392 |
+
if not self.cfg.eval_target_metric:
|
| 393 |
+
return loss, sample_size, logging_output
|
| 394 |
+
|
| 395 |
+
scores = logging_output["scores"]
|
| 396 |
+
|
| 397 |
+
if self.cfg.target_metric == "bleu":
|
| 398 |
+
assert sample["target"].shape[1] == EVAL_BLEU_ORDER * 2 + 3, (
|
| 399 |
+
"target does not contain enough information ("
|
| 400 |
+
+ str(sample["target"].shape[1])
|
| 401 |
+
+ "for evaluating BLEU"
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
max_id = torch.argmax(scores, dim=1)
|
| 405 |
+
select_id = max_id + torch.arange(
|
| 406 |
+
0, sample_size * self.cfg.mt_beam, self.cfg.mt_beam
|
| 407 |
+
).to(max_id.device)
|
| 408 |
+
bleu_data = sample["target"][select_id, 1:].sum(0).data
|
| 409 |
+
|
| 410 |
+
logging_output["_bleu_sys_len"] = bleu_data[0]
|
| 411 |
+
logging_output["_bleu_ref_len"] = bleu_data[1]
|
| 412 |
+
|
| 413 |
+
for i in range(EVAL_BLEU_ORDER):
|
| 414 |
+
logging_output["_bleu_counts_" + str(i)] = bleu_data[2 + i]
|
| 415 |
+
logging_output["_bleu_totals_" + str(i)] = bleu_data[
|
| 416 |
+
2 + EVAL_BLEU_ORDER + i
|
| 417 |
+
]
|
| 418 |
+
|
| 419 |
+
elif self.cfg.target_metric == "ter":
|
| 420 |
+
assert sample["target"].shape[1] == 3, (
|
| 421 |
+
"target does not contain enough information ("
|
| 422 |
+
+ str(sample["target"].shape[1])
|
| 423 |
+
+ "for evaluating TER"
|
| 424 |
+
)
|
| 425 |
+
|
| 426 |
+
max_id = torch.argmax(scores, dim=1)
|
| 427 |
+
select_id = max_id + torch.arange(
|
| 428 |
+
0, sample_size * self.cfg.mt_beam, self.cfg.mt_beam
|
| 429 |
+
).to(max_id.device)
|
| 430 |
+
ter_data = sample["target"][select_id, 1:].sum(0).data
|
| 431 |
+
|
| 432 |
+
logging_output["_ter_num_edits"] = -ter_data[0]
|
| 433 |
+
logging_output["_ter_ref_len"] = -ter_data[1]
|
| 434 |
+
|
| 435 |
+
return loss, sample_size, logging_output
|
| 436 |
+
|
| 437 |
+
def reduce_metrics(self, logging_outputs, criterion):
|
| 438 |
+
super().reduce_metrics(logging_outputs, criterion)
|
| 439 |
+
|
| 440 |
+
if not self.cfg.eval_target_metric:
|
| 441 |
+
return
|
| 442 |
+
|
| 443 |
+
def sum_logs(key):
|
| 444 |
+
return sum(log.get(key, 0) for log in logging_outputs)
|
| 445 |
+
|
| 446 |
+
if self.cfg.target_metric == "bleu":
|
| 447 |
+
counts, totals = [], []
|
| 448 |
+
for i in range(EVAL_BLEU_ORDER):
|
| 449 |
+
counts.append(sum_logs("_bleu_counts_" + str(i)))
|
| 450 |
+
totals.append(sum_logs("_bleu_totals_" + str(i)))
|
| 451 |
+
|
| 452 |
+
if max(totals) > 0:
|
| 453 |
+
# log counts as numpy arrays -- log_scalar will sum them correctly
|
| 454 |
+
metrics.log_scalar("_bleu_counts", np.array(counts))
|
| 455 |
+
metrics.log_scalar("_bleu_totals", np.array(totals))
|
| 456 |
+
metrics.log_scalar("_bleu_sys_len", sum_logs("_bleu_sys_len"))
|
| 457 |
+
metrics.log_scalar("_bleu_ref_len", sum_logs("_bleu_ref_len"))
|
| 458 |
+
|
| 459 |
+
def compute_bleu(meters):
|
| 460 |
+
import inspect
|
| 461 |
+
import sacrebleu
|
| 462 |
+
|
| 463 |
+
fn_sig = inspect.getfullargspec(sacrebleu.compute_bleu)[0]
|
| 464 |
+
if "smooth_method" in fn_sig:
|
| 465 |
+
smooth = {"smooth_method": "exp"}
|
| 466 |
+
else:
|
| 467 |
+
smooth = {"smooth": "exp"}
|
| 468 |
+
bleu = sacrebleu.compute_bleu(
|
| 469 |
+
correct=meters["_bleu_counts"].sum,
|
| 470 |
+
total=meters["_bleu_totals"].sum,
|
| 471 |
+
sys_len=meters["_bleu_sys_len"].sum,
|
| 472 |
+
ref_len=meters["_bleu_ref_len"].sum,
|
| 473 |
+
**smooth,
|
| 474 |
+
)
|
| 475 |
+
return round(bleu.score, 2)
|
| 476 |
+
|
| 477 |
+
metrics.log_derived("bleu", compute_bleu)
|
| 478 |
+
elif self.cfg.target_metric == "ter":
|
| 479 |
+
num_edits = sum_logs("_ter_num_edits")
|
| 480 |
+
ref_len = sum_logs("_ter_ref_len")
|
| 481 |
+
|
| 482 |
+
if ref_len > 0:
|
| 483 |
+
metrics.log_scalar("_ter_num_edits", num_edits)
|
| 484 |
+
metrics.log_scalar("_ter_ref_len", ref_len)
|
| 485 |
+
|
| 486 |
+
def compute_ter(meters):
|
| 487 |
+
score = meters["_ter_num_edits"].sum / meters["_ter_ref_len"].sum
|
| 488 |
+
return round(score.item(), 2)
|
| 489 |
+
|
| 490 |
+
metrics.log_derived("ter", compute_ter)
|
data/fairseq/examples/mbart/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MBART: Multilingual Denoising Pre-training for Neural Machine Translation
|
| 2 |
+
[https://arxiv.org/abs/2001.08210]
|
| 3 |
+
|
| 4 |
+
## Introduction
|
| 5 |
+
|
| 6 |
+
MBART is a sequence-to-sequence denoising auto-encoder pre-trained on large-scale monolingual corpora in many languages using the BART objective. mBART is one of the first methods for pre-training a complete sequence-to-sequence model by denoising full texts in multiple languages, while previous approaches have focused only on the encoder, decoder, or reconstructing parts of the text.
|
| 7 |
+
|
| 8 |
+
## Pre-trained models
|
| 9 |
+
|
| 10 |
+
Model | Description | # params | Download
|
| 11 |
+
---|---|---|---
|
| 12 |
+
`mbart.CC25` | mBART model with 12 encoder and decoder layers trained on 25 languages' monolingual corpus | 610M | [mbart.CC25.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/mbart/mbart.cc25.v2.tar.gz)
|
| 13 |
+
`mbart.ft.ro_en` | finetune mBART cc25 model on ro-en language pairs | 610M | [mbart.cc25.ft.enro.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/mbart/mbart.cc25.ft.enro.tar.gz)
|
| 14 |
+
|
| 15 |
+
## Results
|
| 16 |
+
|
| 17 |
+
**[WMT16 EN-RO](https://www.statmt.org/wmt16/translation-task.html)**
|
| 18 |
+
|
| 19 |
+
_(test set, no additional data used)_
|
| 20 |
+
|
| 21 |
+
Model | en-ro | ro-en
|
| 22 |
+
---|---|---
|
| 23 |
+
`Random` | 34.3 | 34.0
|
| 24 |
+
`mbart.cc25` | 37.7 | 37.8
|
| 25 |
+
`mbart.enro.bilingual` | 38.5 | 38.5
|
| 26 |
+
|
| 27 |
+
## BPE data
|
| 28 |
+
# download model
|
| 29 |
+
wget https://dl.fbaipublicfiles.com/fairseq/models/mbart/mbart.cc25.v2.tar.gz
|
| 30 |
+
tar -xzvf mbart.CC25.tar.gz
|
| 31 |
+
# bpe data
|
| 32 |
+
install SPM [here](https://github.com/google/sentencepiece)
|
| 33 |
+
```bash
|
| 34 |
+
SPM=/path/to/sentencepiece/build/src/spm_encode
|
| 35 |
+
MODEL=sentence.bpe.model
|
| 36 |
+
${SPM} --model=${MODEL} < ${DATA}/${TRAIN}.${SRC} > ${DATA}/${TRAIN}.spm.${SRC} &
|
| 37 |
+
${SPM} --model=${MODEL} < ${DATA}/${TRAIN}.${TGT} > ${DATA}/${TRAIN}.spm.${TGT} &
|
| 38 |
+
${SPM} --model=${MODEL} < ${DATA}/${VALID}.${SRC} > ${DATA}/${VALID}.spm.${SRC} &
|
| 39 |
+
${SPM} --model=${MODEL} < ${DATA}/${VALID}.${TGT} > ${DATA}/${VALID}.spm.${TGT} &
|
| 40 |
+
${SPM} --model=${MODEL} < ${DATA}/${TEST}.${SRC} > ${DATA}/${TEST}.spm.${SRC} &
|
| 41 |
+
${SPM} --model=${MODEL} < ${DATA}/${TEST}.${TGT} > ${DATA}/${TEST}.spm.${TGT} &
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
## Preprocess data
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
DICT=dict.txt
|
| 48 |
+
fairseq-preprocess \
|
| 49 |
+
--source-lang ${SRC} \
|
| 50 |
+
--target-lang ${TGT} \
|
| 51 |
+
--trainpref ${DATA}/${TRAIN}.spm \
|
| 52 |
+
--validpref ${DATA}/${VALID}.spm \
|
| 53 |
+
--testpref ${DATA}/${TEST}.spm \
|
| 54 |
+
--destdir ${DEST}/${NAME} \
|
| 55 |
+
--thresholdtgt 0 \
|
| 56 |
+
--thresholdsrc 0 \
|
| 57 |
+
--srcdict ${DICT} \
|
| 58 |
+
--tgtdict ${DICT} \
|
| 59 |
+
--workers 70
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
## Finetune on EN-RO
|
| 63 |
+
Finetune on mbart CC25
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
PRETRAIN=mbart.cc25 # fix if you moved the downloaded checkpoint
|
| 67 |
+
langs=ar_AR,cs_CZ,de_DE,en_XX,es_XX,et_EE,fi_FI,fr_XX,gu_IN,hi_IN,it_IT,ja_XX,kk_KZ,ko_KR,lt_LT,lv_LV,my_MM,ne_NP,nl_XX,ro_RO,ru_RU,si_LK,tr_TR,vi_VN,zh_CN
|
| 68 |
+
|
| 69 |
+
fairseq-train path_2_data \
|
| 70 |
+
--encoder-normalize-before --decoder-normalize-before \
|
| 71 |
+
--arch mbart_large --layernorm-embedding \
|
| 72 |
+
--task translation_from_pretrained_bart \
|
| 73 |
+
--source-lang en_XX --target-lang ro_RO \
|
| 74 |
+
--criterion label_smoothed_cross_entropy --label-smoothing 0.2 \
|
| 75 |
+
--optimizer adam --adam-eps 1e-06 --adam-betas '(0.9, 0.98)' \
|
| 76 |
+
--lr-scheduler polynomial_decay --lr 3e-05 --warmup-updates 2500 --total-num-update 40000 \
|
| 77 |
+
--dropout 0.3 --attention-dropout 0.1 --weight-decay 0.0 \
|
| 78 |
+
--max-tokens 1024 --update-freq 2 \
|
| 79 |
+
--save-interval 1 --save-interval-updates 5000 --keep-interval-updates 10 --no-epoch-checkpoints \
|
| 80 |
+
--seed 222 --log-format simple --log-interval 2 \
|
| 81 |
+
--restore-file $PRETRAIN \
|
| 82 |
+
--reset-optimizer --reset-meters --reset-dataloader --reset-lr-scheduler \
|
| 83 |
+
--langs $langs \
|
| 84 |
+
--ddp-backend legacy_ddp
|
| 85 |
+
```
|
| 86 |
+
## Generate on EN-RO
|
| 87 |
+
Get sacrebleu on finetuned en-ro model
|
| 88 |
+
|
| 89 |
+
get tokenizer [here](https://github.com/rsennrich/wmt16-scripts)
|
| 90 |
+
```bash
|
| 91 |
+
wget https://dl.fbaipublicfiles.com/fairseq/models/mbart/mbart.cc25.ft.enro.tar.gz
|
| 92 |
+
tar -xzvf mbart.cc25.ft.enro.tar.gz
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
model_dir=MBART_finetuned_enro # fix if you moved the checkpoint
|
| 97 |
+
|
| 98 |
+
fairseq-generate path_2_data \
|
| 99 |
+
--path $model_dir/model.pt \
|
| 100 |
+
--task translation_from_pretrained_bart \
|
| 101 |
+
--gen-subset test \
|
| 102 |
+
-t ro_RO -s en_XX \
|
| 103 |
+
--bpe 'sentencepiece' --sentencepiece-model $model_dir/sentence.bpe.model \
|
| 104 |
+
--sacrebleu --remove-bpe 'sentencepiece' \
|
| 105 |
+
--batch-size 32 --langs $langs > en_ro
|
| 106 |
+
|
| 107 |
+
cat en_ro | grep -P "^H" |sort -V |cut -f 3- | sed 's/\[ro_RO\]//g' |$TOKENIZER ro > en_ro.hyp
|
| 108 |
+
cat en_ro | grep -P "^T" |sort -V |cut -f 2- | sed 's/\[ro_RO\]//g' |$TOKENIZER ro > en_ro.ref
|
| 109 |
+
sacrebleu -tok 'none' -s 'none' en_ro.ref < en_ro.hyp
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
## Citation
|
| 113 |
+
|
| 114 |
+
```bibtex
|
| 115 |
+
@article{liu2020multilingual,
|
| 116 |
+
title={Multilingual Denoising Pre-training for Neural Machine Translation},
|
| 117 |
+
author={Yinhan Liu and Jiatao Gu and Naman Goyal and Xian Li and Sergey Edunov and Marjan Ghazvininejad and Mike Lewis and Luke Zettlemoyer},
|
| 118 |
+
year={2020},
|
| 119 |
+
eprint={2001.08210},
|
| 120 |
+
archivePrefix={arXiv},
|
| 121 |
+
primaryClass={cs.CL}
|
| 122 |
+
}
|
| 123 |
+
```
|
data/fairseq/examples/normformer/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
### NormFormer
|
| 2 |
+
This is the code for the ["NormFormer: Improved Transformer Pretraining with Extra Normalization"](https://arxiv.org/abs/2110.09456)
|
| 3 |
+
- 2021-10-19: Commands for CLM Experiments
|
| 4 |
+
- Coming soon: Commands for MLM experiments
|
| 5 |
+
|
| 6 |
+
If you have any issues or questions please post a github issue and tag `@sshleifer`.
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
### Data
|
| 10 |
+
- To preprocess language modeling data, see [here](https://github.com/pytorch/fairseq/blob/d0fbcb0baef6f6ff3425ded62d8daea0e8b12114/examples/language_model/README.md#1-preprocess-the-data).
|
| 11 |
+
- The replication commands below expect `$DATA` to be the path to the binarized data directory.
|
| 12 |
+
- Note that NormFormer results in Table 2 use a much larger private dataset, and to get good results you should adapt the pre-processing instructions to your dataset and compare to a baseline on the same data, rather than Table 2.
|
| 13 |
+
- The code uses `FSDP`, which requires `pip install fairscale>=0.4.0`.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
### Modify existing Command
|
| 17 |
+
To modify an existing `fairseq-train` command to use NormFormer, simply add the following flags:
|
| 18 |
+
```bash
|
| 19 |
+
fairseq-train ... \
|
| 20 |
+
--scale-attn --scale-fc --scale-heads
|
| 21 |
+
```
|
| 22 |
+
- you probably also want to increase your learning rate
|
| 23 |
+
- if your model is small, you may want to add `--scale-resids`
|
| 24 |
+
|
| 25 |
+
### Exact Training Commands
|
| 26 |
+
|
| 27 |
+
- Note that NormFormer results in Table 2 use a much larger private dataset, and to get good results you should adapt the pre-processing instructions to your dataset.
|
| 28 |
+
The full commands are functions defined here, so to run them you must `source examples/normformer/train_lm.sh`.
|
| 29 |
+
- We default `--distributed-world-size 8`. You should adjust `--update-freq` and `--batch-size` and such that the effective batch size is (1024x1024x0.5) tokens for 125M and 355M,
|
| 30 |
+
and (1024x1024) for 1.3B parameter and above. For small models, `--update-freq`=256/`global_bs`. For large models, `--update-freq`=512/`global_bs`, where `global_bs` = `--batch-size` * `--distributed-world-size`
|
| 31 |
+
- The small models will all train on as few as 8 GPUs.
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
train_125M --lr 6e-4 # GPT-3 Replicated
|
| 35 |
+
train_125M --lr 1e-3 # stronger high-lr baseline
|
| 36 |
+
train_125M --lr 3e-3 --scale-attn --scale-fc --scale-heads # No scale-resids
|
| 37 |
+
train_125M --lr 3e-3 --scale-attn --scale-fc --scale-heads --scale-resids # Best command
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
train_355M --lr 6e-4 # GPT-3 Replicated
|
| 42 |
+
train_355M --lr 1e-3 # stronger high-lr baseline
|
| 43 |
+
train_355M --lr 1e-3 --scale-attn --scale-fc --scale-heads # No scale-resids
|
| 44 |
+
train_355M --lr 1e-3 --scale-attn --scale-fc --scale-heads --scale-resids # Slightly better
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
train_1.3B --lr 2e-4 # GPT-3 Replicated
|
| 49 |
+
train_1.3B --lr 6e-4 # stronger high-lr baseline
|
| 50 |
+
train_1.3B --lr 6e-4 --scale-attn --scale-fc --scale-heads # NormFormer
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
train_2.7B --lr 1.6e-4 # GPT-3 Replicated
|
| 55 |
+
train_2.7B --lr 1.6e-4 --activation-fn relu_squared # stronger Relu^2 baseline
|
| 56 |
+
train_2.7B --lr 6e-4 --activation-fn relu_squared --scale-attn --scale-fc --scale-heads # NormFormer 2.7B
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
### Citation
|
| 61 |
+
```bibtex
|
| 62 |
+
@misc{shleifer2021normformer,
|
| 63 |
+
title={NormFormer: Improved Transformer Pretraining with Extra Normalization},
|
| 64 |
+
author={Sam Shleifer and Jason Weston and Myle Ott},
|
| 65 |
+
year={2021},
|
| 66 |
+
eprint={2110.09456},
|
| 67 |
+
archivePrefix={arXiv},
|
| 68 |
+
primaryClass={cs.CL}
|
| 69 |
+
}
|
| 70 |
+
```
|
data/fairseq/examples/normformer/train_lm.sh
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
train_common () {
|
| 3 |
+
fairseq-train "$DATA" \
|
| 4 |
+
--combine-val \
|
| 5 |
+
--train-subset train \
|
| 6 |
+
--num-workers 2 \
|
| 7 |
+
--validate-interval-updates 1000 \
|
| 8 |
+
--save-interval-updates 1000 \
|
| 9 |
+
--no-epoch-checkpoints \
|
| 10 |
+
--ddp-backend fully_sharded \
|
| 11 |
+
--memory-efficient-fp16 \
|
| 12 |
+
--fp16-init-scale 4 \
|
| 13 |
+
--checkpoint-activations \
|
| 14 |
+
--arch transformer_lm_gpt \
|
| 15 |
+
--activation-fn gelu \
|
| 16 |
+
--share-decoder-input-output-embed \
|
| 17 |
+
--task language_modeling \
|
| 18 |
+
--sample-break-mode none \
|
| 19 |
+
--tokens-per-sample 2048 \
|
| 20 |
+
--optimizer adam --adam-betas "(0.9, 0.98)" \
|
| 21 |
+
--adam-eps 1e-08 \
|
| 22 |
+
--clip-norm 0.0 \
|
| 23 |
+
--lr-scheduler polynomial_decay \
|
| 24 |
+
--warmup-updates 750 \
|
| 25 |
+
--dropout 0.1 \
|
| 26 |
+
--attention-dropout 0.1 \
|
| 27 |
+
--weight-decay 0.01 \
|
| 28 |
+
--batch-size 16 \
|
| 29 |
+
--update-freq 2 \
|
| 30 |
+
--required-batch-size-multiple 1 \
|
| 31 |
+
--total-num-update 572204 \
|
| 32 |
+
--max-update 572204 \
|
| 33 |
+
--seed 1 \
|
| 34 |
+
--log-format json --log-interval 1 \
|
| 35 |
+
--distributed-world-size 8 --distributed-port 13177 \
|
| 36 |
+
"$@"
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
train_125M () {
|
| 40 |
+
train_common --decoder-layers 12 \
|
| 41 |
+
--decoder-embed-dim 768 \
|
| 42 |
+
--decoder-ffn-embed-dim 3072 \
|
| 43 |
+
--decoder-attention-heads 12 "$@"
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
train_355M () {
|
| 47 |
+
train_common --decoder-layers 24 \
|
| 48 |
+
--decoder-embed-dim 1024\
|
| 49 |
+
--decoder-ffn-embed-dim 4096 \
|
| 50 |
+
--decoder-attention-heads 16 \
|
| 51 |
+
--dropout 0.0 \
|
| 52 |
+
--attention-dropout 0.0 \
|
| 53 |
+
"$@"
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
train_1.3B () {
|
| 57 |
+
train_common --decoder-layers 24 \
|
| 58 |
+
--decoder-embed-dim 2048 \
|
| 59 |
+
--decoder-ffn-embed-dim 8192 \
|
| 60 |
+
--decoder-attention-heads 32 \
|
| 61 |
+
--batch-size 4 \
|
| 62 |
+
--update-freq 16 \
|
| 63 |
+
--total-num-update 286102 \
|
| 64 |
+
--max-update 286102 \
|
| 65 |
+
"$@"
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
train_2.7B () {
|
| 69 |
+
train_common --decoder-layers 32 \
|
| 70 |
+
--decoder-embed-dim 2560 \
|
| 71 |
+
--decoder-ffn-embed-dim 10240 \
|
| 72 |
+
--decoder-attention-heads 32 \
|
| 73 |
+
--batch-size 4 \
|
| 74 |
+
--update-freq 16 \
|
| 75 |
+
--total-num-update 286102 \
|
| 76 |
+
--max-update 286102 \
|
| 77 |
+
"$@"
|
| 78 |
+
}
|
data/fairseq/examples/shuffled_word_order/README.finetuning.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fine-tuning details
|
| 2 |
+
|
| 3 |
+
For each task (GLUE and PAWS), we perform hyperparam search for each model, and report the mean and standard deviation across 5 seeds of the best model. First, get the datasets following the instructions in [RoBERTa fine-tuning README](../roberta/README.glue.md). Alternatively, you can use [huggingface datasets](https://huggingface.co/docs/datasets/) to get the task data:
|
| 4 |
+
|
| 5 |
+
```python
|
| 6 |
+
from datasets import load_dataset
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
key2file = {
|
| 11 |
+
"paws": {
|
| 12 |
+
"loc": "paws_data",
|
| 13 |
+
"columns": ["id", "sentence1", "sentence2", "label"],
|
| 14 |
+
"train": "train.tsv",
|
| 15 |
+
"validation": "dev.tsv",
|
| 16 |
+
"test": "test.tsv"
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
task_data = load_dataset("paws", "labeled_final")
|
| 21 |
+
task_config = key2file["paws"]
|
| 22 |
+
save_path = Path(task_config["loc"])
|
| 23 |
+
save_path.mkdir(exist_ok=True, parents=True)
|
| 24 |
+
for key, fl in task_config.items():
|
| 25 |
+
if key in ["loc", "columns"]:
|
| 26 |
+
continue
|
| 27 |
+
print(f"Reading {key}")
|
| 28 |
+
columns = task_config["columns"]
|
| 29 |
+
df = pd.DataFrame(task_data[key])
|
| 30 |
+
print(df.columns)
|
| 31 |
+
df = df[columns]
|
| 32 |
+
print(f"Got {len(df)} records")
|
| 33 |
+
save_loc = save_path / fl
|
| 34 |
+
print(f"Saving to : {save_loc}")
|
| 35 |
+
df.to_csv(save_loc, sep="\t", header=None, index=None)
|
| 36 |
+
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
- Preprocess using RoBERTa GLUE preprocessing script, while keeping in mind the column numbers for `sentence1`, `sentence2` and `label` (which is 0,1,2 if you save the data according to the above example.)
|
| 40 |
+
- Then, fine-tuning is performed similarly to RoBERTa (for example, in case of RTE):
|
| 41 |
+
|
| 42 |
+
```bash
|
| 43 |
+
TOTAL_NUM_UPDATES=30875 # 10 epochs through RTE for bsz 16
|
| 44 |
+
WARMUP_UPDATES=1852 # 6 percent of the number of updates
|
| 45 |
+
LR=2e-05 # Peak LR for polynomial LR scheduler.
|
| 46 |
+
NUM_CLASSES=2
|
| 47 |
+
MAX_SENTENCES=16 # Batch size.
|
| 48 |
+
SHUFFLED_ROBERTA_PATH=/path/to/shuffled_roberta/model.pt
|
| 49 |
+
|
| 50 |
+
CUDA_VISIBLE_DEVICES=0 fairseq-train RTE-bin/ \
|
| 51 |
+
--restore-file $SHUFFLED_ROBERTA_PATH \
|
| 52 |
+
--max-positions 512 \
|
| 53 |
+
--batch-size $MAX_SENTENCES \
|
| 54 |
+
--max-tokens 4400 \
|
| 55 |
+
--task sentence_prediction \
|
| 56 |
+
--reset-optimizer --reset-dataloader --reset-meters \
|
| 57 |
+
--required-batch-size-multiple 1 \
|
| 58 |
+
--init-token 0 --separator-token 2 \
|
| 59 |
+
--arch roberta_large \
|
| 60 |
+
--criterion sentence_prediction \
|
| 61 |
+
--num-classes $NUM_CLASSES \
|
| 62 |
+
--dropout 0.1 --attention-dropout 0.1 \
|
| 63 |
+
--weight-decay 0.1 --optimizer adam --adam-betas "(0.9, 0.98)" --adam-eps 1e-06 \
|
| 64 |
+
--clip-norm 0.0 \
|
| 65 |
+
--lr-scheduler polynomial_decay --lr $LR --total-num-update $TOTAL_NUM_UPDATES --warmup-updates $WARMUP_UPDATES \
|
| 66 |
+
--fp16 --fp16-init-scale 4 --threshold-loss-scale 1 --fp16-scale-window 128 \
|
| 67 |
+
--max-epoch 10 \
|
| 68 |
+
--find-unused-parameters \
|
| 69 |
+
--best-checkpoint-metric accuracy --maximize-best-checkpoint-metric;
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
- `TOTAL_NUM_UPDATES` is computed based on the `--batch_size` value and the dataset size.
|
| 73 |
+
- `WARMUP_UPDATES` is computed as 6% of `TOTAL_NUM_UPDATES`
|
| 74 |
+
- Best hyperparam of `--lr` and `--batch_size` is reported below:
|
| 75 |
+
|
| 76 |
+
## `--lr`
|
| 77 |
+
|
| 78 |
+
| | name | RTE | MRPC | SST-2 | CoLA | QQP | QNLI | MNLI | PAWS |
|
| 79 |
+
| --: | :----------- | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: |
|
| 80 |
+
| 0 | original | 2e-05 | 2e-05 | 1e-05 | 2e-05 | 1e-05 | 1e-05 | 1e-05 | 2e-05 |
|
| 81 |
+
| 1 | n_1 | 2e-05 | 1e-05 | 1e-05 | 1e-05 | 3e-05 | 1e-05 | 2e-05 | 2e-05 |
|
| 82 |
+
| 2 | n_2 | 2e-05 | 2e-05 | 1e-05 | 1e-05 | 2e-05 | 1e-05 | 1e-05 | 3e-05 |
|
| 83 |
+
| 3 | n_3 | 3e-05 | 1e-05 | 2e-05 | 2e-05 | 3e-05 | 1e-05 | 1e-05 | 2e-05 |
|
| 84 |
+
| 4 | n_4 | 3e-05 | 1e-05 | 2e-05 | 2e-05 | 2e-05 | 1e-05 | 1e-05 | 2e-05 |
|
| 85 |
+
| 5 | r512 | 1e-05 | 3e-05 | 2e-05 | 2e-05 | 3e-05 | 2e-05 | 3e-05 | 2e-05 |
|
| 86 |
+
| 6 | rand_corpus | 2e-05 | 1e-05 | 3e-05 | 1e-05 | 3e-05 | 3e-05 | 3e-05 | 2e-05 |
|
| 87 |
+
| 7 | rand_uniform | 2e-05 | 1e-05 | 3e-05 | 2e-05 | 3e-05 | 3e-05 | 3e-05 | 1e-05 |
|
| 88 |
+
| 8 | rand_init | 1e-05 | 1e-05 | 3e-05 | 1e-05 | 1e-05 | 1e-05 | 2e-05 | 1e-05 |
|
| 89 |
+
| 9 | no_pos | 1e-05 | 3e-05 | 2e-05 | 1e-05 | 1e-05 | 1e-05 | 1e-05 | 1e-05 |
|
| 90 |
+
|
| 91 |
+
## `--batch_size`
|
| 92 |
+
|
| 93 |
+
| | name | RTE | MRPC | SST-2 | CoLA | QQP | QNLI | MNLI | PAWS |
|
| 94 |
+
| --: | :----------- | --: | ---: | ----: | ---: | --: | ---: | ---: | ---: |
|
| 95 |
+
| 0 | orig | 16 | 16 | 32 | 16 | 16 | 32 | 32 | 16 |
|
| 96 |
+
| 1 | n_1 | 32 | 32 | 16 | 32 | 32 | 16 | 32 | 16 |
|
| 97 |
+
| 2 | n_2 | 32 | 16 | 32 | 16 | 32 | 32 | 16 | 32 |
|
| 98 |
+
| 3 | n_3 | 32 | 32 | 16 | 32 | 32 | 16 | 32 | 32 |
|
| 99 |
+
| 4 | n_4 | 32 | 16 | 32 | 16 | 32 | 32 | 32 | 32 |
|
| 100 |
+
| 5 | r512 | 32 | 16 | 16 | 32 | 32 | 16 | 16 | 16 |
|
| 101 |
+
| 6 | rand_corpus | 16 | 16 | 16 | 16 | 32 | 16 | 16 | 32 |
|
| 102 |
+
| 7 | rand_uniform | 16 | 32 | 16 | 16 | 32 | 16 | 16 | 16 |
|
| 103 |
+
| 8 | rand_init | 16 | 16 | 32 | 16 | 16 | 16 | 32 | 16 |
|
| 104 |
+
| 9 | no_pos | 16 | 32 | 16 | 16 | 32 | 16 | 16 | 16 |
|
| 105 |
+
|
| 106 |
+
- Perform inference similar to RoBERTa as well:
|
| 107 |
+
|
| 108 |
+
```python
|
| 109 |
+
from fairseq.models.roberta import RobertaModel
|
| 110 |
+
|
| 111 |
+
roberta = RobertaModel.from_pretrained(
|
| 112 |
+
'checkpoints/',
|
| 113 |
+
checkpoint_file='checkpoint_best.pt',
|
| 114 |
+
data_name_or_path='PAWS-bin'
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
label_fn = lambda label: roberta.task.label_dictionary.string(
|
| 118 |
+
[label + roberta.task.label_dictionary.nspecial]
|
| 119 |
+
)
|
| 120 |
+
ncorrect, nsamples = 0, 0
|
| 121 |
+
roberta.cuda()
|
| 122 |
+
roberta.eval()
|
| 123 |
+
with open('paws_data/dev.tsv') as fin:
|
| 124 |
+
fin.readline()
|
| 125 |
+
for index, line in enumerate(fin):
|
| 126 |
+
tokens = line.strip().split('\t')
|
| 127 |
+
sent1, sent2, target = tokens[0], tokens[1], tokens[2]
|
| 128 |
+
tokens = roberta.encode(sent1, sent2)
|
| 129 |
+
prediction = roberta.predict('sentence_classification_head', tokens).argmax().item()
|
| 130 |
+
prediction_label = label_fn(prediction)
|
| 131 |
+
ncorrect += int(prediction_label == target)
|
| 132 |
+
nsamples += 1
|
| 133 |
+
print('| Accuracy: ', float(ncorrect)/float(nsamples))
|
| 134 |
+
|
| 135 |
+
```
|
data/fairseq/examples/shuffled_word_order/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Masked Language Modeling and the Distributional Hypothesis: Order Word Matters Pre-training for Little
|
| 2 |
+
|
| 3 |
+
[https://arxiv.org/abs/2104.06644](https://arxiv.org/abs/2104.06644)
|
| 4 |
+
|
| 5 |
+
## Introduction
|
| 6 |
+
|
| 7 |
+
In this work, we pre-train [RoBERTa](../roberta) base on various word shuffled variants of BookWiki corpus (16GB). We observe that a word shuffled pre-trained model achieves surprisingly good scores on GLUE, PAWS and several parametric probing tasks. Please read our paper for more details on the experiments.
|
| 8 |
+
|
| 9 |
+
## Pre-trained models
|
| 10 |
+
|
| 11 |
+
| Model | Description | Download |
|
| 12 |
+
| ------------------------------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
| 13 |
+
| `roberta.base.orig` | RoBERTa (base) trained on natural corpus | [roberta.base.orig.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.orig.tar.gz) |
|
| 14 |
+
| `roberta.base.shuffle.n1` | RoBERTa (base) trained on n=1 gram sentence word shuffled data | [roberta.base.shuffle.n1.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n1.tar.gz) |
|
| 15 |
+
| `roberta.base.shuffle.n2` | RoBERTa (base) trained on n=2 gram sentence word shuffled data | [roberta.base.shuffle.n2.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n2.tar.gz) |
|
| 16 |
+
| `roberta.base.shuffle.n3` | RoBERTa (base) trained on n=3 gram sentence word shuffled data | [roberta.base.shuffle.n3.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n3.tar.gz) |
|
| 17 |
+
| `roberta.base.shuffle.n4` | RoBERTa (base) trained on n=4 gram sentence word shuffled data | [roberta.base.shuffle.n4.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n4.tar.gz) |
|
| 18 |
+
| `roberta.base.shuffle.512` | RoBERTa (base) trained on unigram 512 word block shuffled data | [roberta.base.shuffle.512.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.512.tar.gz) |
|
| 19 |
+
| `roberta.base.shuffle.corpus` | RoBERTa (base) trained on unigram corpus word shuffled data | [roberta.base.shuffle.corpus.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.corpus.tar.gz) |
|
| 20 |
+
| `roberta.base.shuffle.corpus_uniform` | RoBERTa (base) trained on unigram corpus word shuffled data, where all words are uniformly sampled | [roberta.base.shuffle.corpus_uniform.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.corpus_uniform.tar.gz) |
|
| 21 |
+
| `roberta.base.nopos` | RoBERTa (base) without positional embeddings, trained on natural corpus | [roberta.base.nopos.tar.gz](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.nopos.tar.gz) |
|
| 22 |
+
|
| 23 |
+
## Results
|
| 24 |
+
|
| 25 |
+
[GLUE (Wang et al, 2019)](https://gluebenchmark.com/) & [PAWS (Zhang et al, 2019)](https://github.com/google-research-datasets/paws) _(dev set, single model, single-task fine-tuning, median of 5 seeds)_
|
| 26 |
+
|
| 27 |
+
| name | CoLA | MNLI | MRPC | PAWS | QNLI | QQP | RTE | SST-2 |
|
| 28 |
+
| :----------------------------------- | ----: | ----: | ----: | ----: | ----: | ----: | ----: | ----: |
|
| 29 |
+
| `roberta.base.orig` | 61.4 | 86.11 | 89.19 | 94.46 | 92.53 | 91.26 | 74.64 | 93.92 |
|
| 30 |
+
| `roberta.base.shuffle.n1` | 35.15 | 82.64 | 86 | 89.97 | 89.02 | 91.01 | 69.02 | 90.47 |
|
| 31 |
+
| `roberta.base.shuffle.n2` | 54.37 | 83.43 | 86.24 | 93.46 | 90.44 | 91.36 | 70.83 | 91.79 |
|
| 32 |
+
| `roberta.base.shuffle.n3` | 48.72 | 83.85 | 86.36 | 94.05 | 91.69 | 91.24 | 70.65 | 92.02 |
|
| 33 |
+
| `roberta.base.shuffle.n4` | 58.64 | 83.77 | 86.98 | 94.32 | 91.69 | 91.4 | 70.83 | 92.48 |
|
| 34 |
+
| `roberta.base.shuffle.512` | 12.76 | 77.52 | 79.61 | 84.77 | 85.19 | 90.2 | 56.52 | 86.34 |
|
| 35 |
+
| `roberta.base.shuffle.corpus` | 0 | 71.9 | 70.52 | 58.52 | 71.11 | 85.52 | 53.99 | 83.35 |
|
| 36 |
+
| `roberta.base.shuffle.corpus_random` | 9.19 | 72.33 | 70.76 | 58.42 | 77.76 | 85.93 | 53.99 | 84.04 |
|
| 37 |
+
| `roberta.base.nopos` | 0 | 63.5 | 72.73 | 57.08 | 77.72 | 87.87 | 54.35 | 83.24 |
|
| 38 |
+
|
| 39 |
+
For more results on probing tasks, please refer to [our paper](https://arxiv.org/abs/2104.06644).
|
| 40 |
+
|
| 41 |
+
## Example Usage
|
| 42 |
+
|
| 43 |
+
Follow the same usage as in [RoBERTa](https://github.com/pytorch/fairseq/tree/main/examples/roberta) to load and test your models:
|
| 44 |
+
|
| 45 |
+
```python
|
| 46 |
+
# Download roberta.base.shuffle.n1 model
|
| 47 |
+
wget https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n1.tar.gz
|
| 48 |
+
tar -xzvf roberta.base.shuffle.n1.tar.gz
|
| 49 |
+
# Copy the dictionary files
|
| 50 |
+
cd roberta.base.shuffle.n1.tar.gz
|
| 51 |
+
wget -O dict.txt https://dl.fbaipublicfiles.com/fairseq/gpt2_bpe/dict.txt && wget -O encoder.json https://dl.fbaipublicfiles.com/fairseq/gpt2_bpe/encoder.json && wget -O vocab.bpe https://dl.fbaipublicfiles.com/fairseq/gpt2_bpe/vocab.bpe
|
| 52 |
+
cd ..
|
| 53 |
+
|
| 54 |
+
# Load the model in fairseq
|
| 55 |
+
from fairseq.models.roberta import RobertaModel
|
| 56 |
+
roberta = RobertaModel.from_pretrained('/path/to/roberta.base.shuffle.n1', checkpoint_file='model.pt')
|
| 57 |
+
roberta.eval() # disable dropout (or leave in train mode to finetune)
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
We have also provided a [Google Colab](https://colab.research.google.com/drive/1IJDVfNVWdvRfLjphQKBGzmob84t-OXpm) notebook to demonstrate the loading of the model. The models were trained on top of Fairseq from the following commit: [62cff008ebeeed855093837507d5e6bf52065ee6](https://github.com/pytorch/fairseq/commit/62cff008ebeeed855093837507d5e6bf52065ee6).
|
| 61 |
+
|
| 62 |
+
**Note**: The model trained without positional embeddings (`roberta.base.nopos`) is a modified `RoBERTa` model, where the positional embeddings are not used. Thus, the typical `from_pretrained` method on fairseq version of RoBERTa will not be able to load the above model weights. To do so, construct a new `RoBERTaModel` object by setting the flag `use_positional_embeddings` to `False` (or [in the latest code](https://github.com/pytorch/fairseq/blob/main/fairseq/models/roberta/model.py#L543), set `no_token_positional_embeddings` to `True`), and then load the individual weights.
|
| 63 |
+
|
| 64 |
+
## Fine-tuning Evaluation
|
| 65 |
+
|
| 66 |
+
We provide the trained fine-tuned models on MNLI here for each model above for quick evaluation (1 seed for each model). Please refer to [finetuning details](README.finetuning.md) for the parameters of these models. Follow [RoBERTa](https://github.com/pytorch/fairseq/tree/main/examples/roberta) instructions to evaluate these models.
|
| 67 |
+
|
| 68 |
+
| Model | MNLI M Dev Accuracy | Link |
|
| 69 |
+
| :----------------------------------------- | :------------------ | :--------------------------------------------------------------------------------------------------------------- |
|
| 70 |
+
| `roberta.base.orig.mnli` | 86.14 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.orig.mnli.tar.gz) |
|
| 71 |
+
| `roberta.base.shuffle.n1.mnli` | 82.55 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n1.mnli.tar.gz) |
|
| 72 |
+
| `roberta.base.shuffle.n2.mnli` | 83.21 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n2.mnli.tar.gz) |
|
| 73 |
+
| `roberta.base.shuffle.n3.mnli` | 83.89 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n3.mnli.tar.gz) |
|
| 74 |
+
| `roberta.base.shuffle.n4.mnli` | 84.00 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.n4.mnli.tar.gz) |
|
| 75 |
+
| `roberta.base.shuffle.512.mnli` | 77.22 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.512.mnli.tar.gz) |
|
| 76 |
+
| `roberta.base.shuffle.corpus.mnli` | 71.88 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.corpus.mnli.tar.gz) |
|
| 77 |
+
| `roberta.base.shuffle.corpus_uniform.mnli` | 72.46 | [Download](https://dl.fbaipublicfiles.com/unnatural_pretraining/roberta.base.shuffle.corpus_uniform.mnli.tar.gz) |
|
| 78 |
+
|
| 79 |
+
## Citation
|
| 80 |
+
|
| 81 |
+
```bibtex
|
| 82 |
+
@misc{sinha2021masked,
|
| 83 |
+
title={Masked Language Modeling and the Distributional Hypothesis: Order Word Matters Pre-training for Little},
|
| 84 |
+
author={Koustuv Sinha and Robin Jia and Dieuwke Hupkes and Joelle Pineau and Adina Williams and Douwe Kiela},
|
| 85 |
+
year={2021},
|
| 86 |
+
eprint={2104.06644},
|
| 87 |
+
archivePrefix={arXiv},
|
| 88 |
+
primaryClass={cs.CL}
|
| 89 |
+
}
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Contact
|
| 93 |
+
|
| 94 |
+
For questions and comments, please reach out to Koustuv Sinha (koustuv.sinha@mail.mcgill.ca).
|
data/fairseq/examples/simultaneous_translation/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Simultaneous Translation
|
| 2 |
+
Examples of simultaneous translation in fairseq
|
| 3 |
+
- [English-to-Japanese text-to-text wait-k model](docs/enja-waitk.md)
|
| 4 |
+
- [English-to-Germen text-to-text monotonic multihead attention model](docs/ende-mma.md)
|
| 5 |
+
- [English-to-Germen speech-to-text simultaneous translation model](../speech_to_text/docs/simulst_mustc_example.md)
|
data/fairseq/examples/simultaneous_translation/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from . import models # noqa
|
data/fairseq/examples/simultaneous_translation/docs/ende-mma.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Simultaneous Machine Translation
|
| 2 |
+
|
| 3 |
+
This directory contains the code for the paper [Monotonic Multihead Attention](https://openreview.net/forum?id=Hyg96gBKPS)
|
| 4 |
+
|
| 5 |
+
## Prepare Data
|
| 6 |
+
|
| 7 |
+
[Please follow the instructions to download and preprocess the WMT'15 En-De dataset.](https://github.com/pytorch/fairseq/tree/simulastsharedtask/examples/translation#prepare-wmt14en2desh)
|
| 8 |
+
|
| 9 |
+
Another example of training an English to Japanese model can be found [here](docs/enja.md)
|
| 10 |
+
|
| 11 |
+
## Training
|
| 12 |
+
|
| 13 |
+
- MMA-IL
|
| 14 |
+
|
| 15 |
+
```shell
|
| 16 |
+
fairseq-train \
|
| 17 |
+
data-bin/wmt15_en_de_32k \
|
| 18 |
+
--simul-type infinite_lookback \
|
| 19 |
+
--user-dir $FAIRSEQ/example/simultaneous_translation \
|
| 20 |
+
--mass-preservation \
|
| 21 |
+
--criterion latency_augmented_label_smoothed_cross_entropy \
|
| 22 |
+
--latency-weight-avg 0.1 \
|
| 23 |
+
--max-update 50000 \
|
| 24 |
+
--arch transformer_monotonic_iwslt_de_en save_dir_key=lambda \
|
| 25 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' \
|
| 26 |
+
--lr-scheduler 'inverse_sqrt' \
|
| 27 |
+
--warmup-init-lr 1e-7 --warmup-updates 4000 \
|
| 28 |
+
--lr 5e-4 --stop-min-lr 1e-9 --clip-norm 0.0 --weight-decay 0.0001\
|
| 29 |
+
--dropout 0.3 \
|
| 30 |
+
--label-smoothing 0.1\
|
| 31 |
+
--max-tokens 3584
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
- MMA-H
|
| 35 |
+
|
| 36 |
+
```shell
|
| 37 |
+
fairseq-train \
|
| 38 |
+
data-bin/wmt15_en_de_32k \
|
| 39 |
+
--simul-type hard_aligned \
|
| 40 |
+
--user-dir $FAIRSEQ/example/simultaneous_translation \
|
| 41 |
+
--mass-preservation \
|
| 42 |
+
--criterion latency_augmented_label_smoothed_cross_entropy \
|
| 43 |
+
--latency-weight-var 0.1 \
|
| 44 |
+
--max-update 50000 \
|
| 45 |
+
--arch transformer_monotonic_iwslt_de_en save_dir_key=lambda \
|
| 46 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' \
|
| 47 |
+
--lr-scheduler 'inverse_sqrt' \
|
| 48 |
+
--warmup-init-lr 1e-7 --warmup-updates 4000 \
|
| 49 |
+
--lr 5e-4 --stop-min-lr 1e-9 --clip-norm 0.0 --weight-decay 0.0001\
|
| 50 |
+
--dropout 0.3 \
|
| 51 |
+
--label-smoothing 0.1\
|
| 52 |
+
--max-tokens 3584
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
- wait-k
|
| 56 |
+
|
| 57 |
+
```shell
|
| 58 |
+
fairseq-train \
|
| 59 |
+
data-bin/wmt15_en_de_32k \
|
| 60 |
+
--simul-type wait-k \
|
| 61 |
+
--waitk-lagging 3 \
|
| 62 |
+
--user-dir $FAIRSEQ/example/simultaneous_translation \
|
| 63 |
+
--mass-preservation \
|
| 64 |
+
--criterion latency_augmented_label_smoothed_cross_entropy \
|
| 65 |
+
--max-update 50000 \
|
| 66 |
+
--arch transformer_monotonic_iwslt_de_en save_dir_key=lambda \
|
| 67 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' \
|
| 68 |
+
--lr-scheduler 'inverse_sqrt' \
|
| 69 |
+
--warmup-init-lr 1e-7 --warmup-updates 4000 \
|
| 70 |
+
--lr 5e-4 --stop-min-lr 1e-9 --clip-norm 0.0 --weight-decay 0.0001\
|
| 71 |
+
--dropout 0.3 \
|
| 72 |
+
--label-smoothing 0.1\
|
| 73 |
+
--max-tokens 3584
|
| 74 |
+
```
|
data/fairseq/examples/simultaneous_translation/docs/enja-waitk.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# An example of English to Japaneses Simultaneous Translation System
|
| 2 |
+
|
| 3 |
+
This is an example of training and evaluating a transformer *wait-k* English to Japanese simultaneous text-to-text translation model.
|
| 4 |
+
|
| 5 |
+
## Data Preparation
|
| 6 |
+
This section introduces the data preparation for training and evaluation.
|
| 7 |
+
If you only want to evaluate the model, please jump to [Inference & Evaluation](#inference-&-evaluation)
|
| 8 |
+
|
| 9 |
+
For illustration, we only use the following subsets of the available data from [WMT20 news translation task](http://www.statmt.org/wmt20/translation-task.html), which results in 7,815,391 sentence pairs.
|
| 10 |
+
- News Commentary v16
|
| 11 |
+
- Wiki Titles v3
|
| 12 |
+
- WikiMatrix V1
|
| 13 |
+
- Japanese-English Subtitle Corpus
|
| 14 |
+
- The Kyoto Free Translation Task Corpus
|
| 15 |
+
|
| 16 |
+
We use WMT20 development data as development set. Training `transformer_vaswani_wmt_en_de_big` model on such amount of data will result in 17.3 BLEU with greedy search and 19.7 with beam (10) search. Notice that a better performance can be achieved with the full WMT training data.
|
| 17 |
+
|
| 18 |
+
We use [sentencepiece](https://github.com/google/sentencepiece) toolkit to tokenize the data with a vocabulary size of 32000.
|
| 19 |
+
Additionally, we filtered out the sentences longer than 200 words after tokenization.
|
| 20 |
+
Assuming the tokenized text data is saved at `${DATA_DIR}`,
|
| 21 |
+
we prepare the data binary with the following command.
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
fairseq-preprocess \
|
| 25 |
+
--source-lang en --target-lang ja \
|
| 26 |
+
--trainpref ${DATA_DIR}/train \
|
| 27 |
+
--validpref ${DATA_DIR}/dev \
|
| 28 |
+
--testpref ${DATA_DIR}/test \
|
| 29 |
+
--destdir ${WMT20_ENJA_DATA_BIN} \
|
| 30 |
+
--nwordstgt 32000 --nwordssrc 32000 \
|
| 31 |
+
--workers 20
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## Simultaneous Translation Model Training
|
| 35 |
+
To train a wait-k `(k=10)` model.
|
| 36 |
+
```bash
|
| 37 |
+
fairseq-train ${WMT20_ENJA_DATA_BIN} \
|
| 38 |
+
--save-dir ${SAVEDIR}
|
| 39 |
+
--simul-type waitk \
|
| 40 |
+
--waitk-lagging 10 \
|
| 41 |
+
--max-epoch 70 \
|
| 42 |
+
--arch transformer_monotonic_vaswani_wmt_en_de_big \
|
| 43 |
+
--optimizer adam \
|
| 44 |
+
--adam-betas '(0.9, 0.98)' \
|
| 45 |
+
--lr-scheduler inverse_sqrt \
|
| 46 |
+
--warmup-init-lr 1e-07 \
|
| 47 |
+
--warmup-updates 4000 \
|
| 48 |
+
--lr 0.0005 \
|
| 49 |
+
--stop-min-lr 1e-09 \
|
| 50 |
+
--clip-norm 10.0 \
|
| 51 |
+
--dropout 0.3 \
|
| 52 |
+
--weight-decay 0.0 \
|
| 53 |
+
--criterion label_smoothed_cross_entropy \
|
| 54 |
+
--label-smoothing 0.1 \
|
| 55 |
+
--max-tokens 3584
|
| 56 |
+
```
|
| 57 |
+
This command is for training on 8 GPUs. Equivalently, the model can be trained on one GPU with `--update-freq 8`.
|
| 58 |
+
|
| 59 |
+
## Inference & Evaluation
|
| 60 |
+
First of all, install [SimulEval](https://github.com/facebookresearch/SimulEval) for evaluation.
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
git clone https://github.com/facebookresearch/SimulEval.git
|
| 64 |
+
cd SimulEval
|
| 65 |
+
pip install -e .
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
The following command is for the evaluation.
|
| 69 |
+
Assuming the source and reference files are `${SRC_FILE}` and `${REF_FILE}`, the sentencepiece model file for English is saved at `${SRC_SPM_PATH}`
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
simuleval \
|
| 74 |
+
--source ${SRC_FILE} \
|
| 75 |
+
--target ${TGT_FILE} \
|
| 76 |
+
--data-bin ${WMT20_ENJA_DATA_BIN} \
|
| 77 |
+
--sacrebleu-tokenizer ja-mecab \
|
| 78 |
+
--eval-latency-unit char \
|
| 79 |
+
--no-space \
|
| 80 |
+
--src-splitter-type sentencepiecemodel \
|
| 81 |
+
--src-splitter-path ${SRC_SPM_PATH} \
|
| 82 |
+
--agent ${FAIRSEQ}/examples/simultaneous_translation/agents/simul_trans_text_agent_enja.py \
|
| 83 |
+
--model-path ${SAVE_DIR}/${CHECKPOINT_FILENAME} \
|
| 84 |
+
--output ${OUTPUT} \
|
| 85 |
+
--scores
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
The `--data-bin` should be the same in previous sections if you prepare the data from the scratch.
|
| 89 |
+
If only for evaluation, a prepared data directory can be found [here](https://dl.fbaipublicfiles.com/simultaneous_translation/wmt20_enja_medium_databin.tgz) and a pretrained checkpoint (wait-k=10 model) can be downloaded from [here](https://dl.fbaipublicfiles.com/simultaneous_translation/wmt20_enja_medium_wait10_ckpt.pt).
|
| 90 |
+
|
| 91 |
+
The output should look like this:
|
| 92 |
+
```bash
|
| 93 |
+
{
|
| 94 |
+
"Quality": {
|
| 95 |
+
"BLEU": 11.442253287568398
|
| 96 |
+
},
|
| 97 |
+
"Latency": {
|
| 98 |
+
"AL": 8.6587861866951,
|
| 99 |
+
"AP": 0.7863304776251316,
|
| 100 |
+
"DAL": 9.477850951194764
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
```
|
| 104 |
+
The latency is evaluated by characters (`--eval-latency-unit`) on the target side. The latency is evaluated with `sacrebleu` with `MeCab` tokenizer `--sacrebleu-tokenizer ja-mecab`. `--no-space` indicates that do not add space when merging the predicted words.
|
| 105 |
+
|
| 106 |
+
If `--output ${OUTPUT}` option is used, the detailed log and scores will be stored under the `${OUTPUT}` directory.
|
data/fairseq/examples/simultaneous_translation/eval/agents/simul_t2t_enja.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
from fairseq import checkpoint_utils, tasks
|
| 9 |
+
import sentencepiece as spm
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
from simuleval import READ_ACTION, WRITE_ACTION, DEFAULT_EOS
|
| 14 |
+
from simuleval.agents import TextAgent
|
| 15 |
+
except ImportError:
|
| 16 |
+
print("Please install simuleval 'pip install simuleval'")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
BOS_PREFIX = "\u2581"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class SimulTransTextAgentJA(TextAgent):
|
| 23 |
+
"""
|
| 24 |
+
Simultaneous Translation
|
| 25 |
+
Text agent for Japanese
|
| 26 |
+
"""
|
| 27 |
+
def __init__(self, args):
|
| 28 |
+
|
| 29 |
+
# Whether use gpu
|
| 30 |
+
self.gpu = getattr(args, "gpu", False)
|
| 31 |
+
|
| 32 |
+
# Max len
|
| 33 |
+
self.max_len = args.max_len
|
| 34 |
+
|
| 35 |
+
# Load Model
|
| 36 |
+
self.load_model_vocab(args)
|
| 37 |
+
|
| 38 |
+
# build word splitter
|
| 39 |
+
self.build_word_splitter(args)
|
| 40 |
+
|
| 41 |
+
self.eos = DEFAULT_EOS
|
| 42 |
+
|
| 43 |
+
def initialize_states(self, states):
|
| 44 |
+
states.incremental_states = dict()
|
| 45 |
+
states.incremental_states["online"] = dict()
|
| 46 |
+
|
| 47 |
+
def to_device(self, tensor):
|
| 48 |
+
if self.gpu:
|
| 49 |
+
return tensor.cuda()
|
| 50 |
+
else:
|
| 51 |
+
return tensor.cpu()
|
| 52 |
+
|
| 53 |
+
def load_model_vocab(self, args):
|
| 54 |
+
|
| 55 |
+
filename = args.model_path
|
| 56 |
+
if not os.path.exists(filename):
|
| 57 |
+
raise IOError("Model file not found: {}".format(filename))
|
| 58 |
+
|
| 59 |
+
state = checkpoint_utils.load_checkpoint_to_cpu(filename)
|
| 60 |
+
|
| 61 |
+
task_args = state["cfg"]["task"]
|
| 62 |
+
task_args.data = args.data_bin
|
| 63 |
+
|
| 64 |
+
task = tasks.setup_task(task_args)
|
| 65 |
+
|
| 66 |
+
# build model for ensemble
|
| 67 |
+
state["cfg"]["model"].load_pretrained_encoder_from = None
|
| 68 |
+
state["cfg"]["model"].load_pretrained_decoder_from = None
|
| 69 |
+
|
| 70 |
+
self.model = task.build_model(state["cfg"]["model"])
|
| 71 |
+
self.model.load_state_dict(state["model"], strict=True)
|
| 72 |
+
self.model.eval()
|
| 73 |
+
self.model.share_memory()
|
| 74 |
+
|
| 75 |
+
if self.gpu:
|
| 76 |
+
self.model.cuda()
|
| 77 |
+
|
| 78 |
+
# Set dictionary
|
| 79 |
+
self.dict = {}
|
| 80 |
+
self.dict["tgt"] = task.target_dictionary
|
| 81 |
+
self.dict["src"] = task.source_dictionary
|
| 82 |
+
|
| 83 |
+
@staticmethod
|
| 84 |
+
def add_args(parser):
|
| 85 |
+
# fmt: off
|
| 86 |
+
parser.add_argument('--model-path', type=str, required=True,
|
| 87 |
+
help='path to your pretrained model.')
|
| 88 |
+
parser.add_argument("--data-bin", type=str, required=True,
|
| 89 |
+
help="Path of data binary")
|
| 90 |
+
parser.add_argument("--max-len", type=int, default=100,
|
| 91 |
+
help="Max length of translation")
|
| 92 |
+
parser.add_argument("--tgt-splitter-type", type=str, default="SentencePiece",
|
| 93 |
+
help="Subword splitter type for target text.")
|
| 94 |
+
parser.add_argument("--tgt-splitter-path", type=str, default=None,
|
| 95 |
+
help="Subword splitter model path for target text.")
|
| 96 |
+
parser.add_argument("--src-splitter-type", type=str, default="SentencePiece",
|
| 97 |
+
help="Subword splitter type for source text.")
|
| 98 |
+
parser.add_argument("--src-splitter-path", type=str, default=None,
|
| 99 |
+
help="Subword splitter model path for source text.")
|
| 100 |
+
# fmt: on
|
| 101 |
+
return parser
|
| 102 |
+
|
| 103 |
+
def build_word_splitter(self, args):
|
| 104 |
+
self.spm = {}
|
| 105 |
+
for lang in ['src', 'tgt']:
|
| 106 |
+
if getattr(args, f'{lang}_splitter_type', None):
|
| 107 |
+
path = getattr(args, f'{lang}_splitter_path', None)
|
| 108 |
+
if path:
|
| 109 |
+
self.spm[lang] = spm.SentencePieceProcessor()
|
| 110 |
+
self.spm[lang].Load(path)
|
| 111 |
+
|
| 112 |
+
def segment_to_units(self, segment, states):
|
| 113 |
+
# Split a full word (segment) into subwords (units)
|
| 114 |
+
return self.spm['src'].EncodeAsPieces(segment)
|
| 115 |
+
|
| 116 |
+
def update_model_encoder(self, states):
|
| 117 |
+
if len(states.units.source) == 0:
|
| 118 |
+
return
|
| 119 |
+
|
| 120 |
+
src_indices = [
|
| 121 |
+
self.dict['src'].index(x)
|
| 122 |
+
for x in states.units.source.value
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
if states.finish_read():
|
| 126 |
+
# Append the eos index when the prediction is over
|
| 127 |
+
src_indices += [self.dict["tgt"].eos_index]
|
| 128 |
+
|
| 129 |
+
src_indices = self.to_device(
|
| 130 |
+
torch.LongTensor(src_indices).unsqueeze(0)
|
| 131 |
+
)
|
| 132 |
+
src_lengths = self.to_device(
|
| 133 |
+
torch.LongTensor([src_indices.size(1)])
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
states.encoder_states = self.model.encoder(src_indices, src_lengths)
|
| 137 |
+
|
| 138 |
+
torch.cuda.empty_cache()
|
| 139 |
+
|
| 140 |
+
def update_states_read(self, states):
|
| 141 |
+
# Happens after a read action.
|
| 142 |
+
self.update_model_encoder(states)
|
| 143 |
+
|
| 144 |
+
def units_to_segment(self, units, states):
|
| 145 |
+
# Merge sub words (units) to full word (segment).
|
| 146 |
+
# For Japanese, we can directly send
|
| 147 |
+
# the untokenized token to server except the BOS token
|
| 148 |
+
# with following option
|
| 149 |
+
# --sacrebleu-tokenizer MeCab
|
| 150 |
+
# --eval-latency-unit char
|
| 151 |
+
# --no-space
|
| 152 |
+
token = units.value.pop()
|
| 153 |
+
|
| 154 |
+
if (
|
| 155 |
+
token == self.dict["tgt"].eos_word
|
| 156 |
+
or len(states.segments.target) > self.max_len
|
| 157 |
+
):
|
| 158 |
+
return DEFAULT_EOS
|
| 159 |
+
|
| 160 |
+
if BOS_PREFIX == token:
|
| 161 |
+
return None
|
| 162 |
+
if token[0] == BOS_PREFIX:
|
| 163 |
+
return token[1:]
|
| 164 |
+
else:
|
| 165 |
+
return token
|
| 166 |
+
|
| 167 |
+
def policy(self, states):
|
| 168 |
+
|
| 169 |
+
if not getattr(states, "encoder_states", None):
|
| 170 |
+
# No encoder states, read a token first
|
| 171 |
+
return READ_ACTION
|
| 172 |
+
|
| 173 |
+
# encode previous predicted target tokens
|
| 174 |
+
tgt_indices = self.to_device(
|
| 175 |
+
torch.LongTensor(
|
| 176 |
+
[self.model.decoder.dictionary.eos()]
|
| 177 |
+
+ [
|
| 178 |
+
self.dict['tgt'].index(x)
|
| 179 |
+
for x in states.units.target.value
|
| 180 |
+
if x is not None
|
| 181 |
+
]
|
| 182 |
+
).unsqueeze(0)
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
# Current steps
|
| 186 |
+
states.incremental_states["steps"] = {
|
| 187 |
+
"src": states.encoder_states["encoder_out"][0].size(0),
|
| 188 |
+
"tgt": 1 + len(states.units.target),
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
# Online only means the reading is not finished
|
| 192 |
+
states.incremental_states["online"]["only"] = (
|
| 193 |
+
torch.BoolTensor([not states.finish_read()])
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
x, outputs = self.model.decoder.forward(
|
| 197 |
+
prev_output_tokens=tgt_indices,
|
| 198 |
+
encoder_out=states.encoder_states,
|
| 199 |
+
incremental_state=states.incremental_states,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
states.decoder_out = x
|
| 203 |
+
|
| 204 |
+
torch.cuda.empty_cache()
|
| 205 |
+
|
| 206 |
+
if outputs.action == 0:
|
| 207 |
+
return READ_ACTION
|
| 208 |
+
else:
|
| 209 |
+
return WRITE_ACTION
|
| 210 |
+
|
| 211 |
+
def predict(self, states):
|
| 212 |
+
# Predict target token from decoder states
|
| 213 |
+
decoder_states = states.decoder_out
|
| 214 |
+
|
| 215 |
+
lprobs = self.model.get_normalized_probs(
|
| 216 |
+
[decoder_states[:, -1:]], log_probs=True
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
index = lprobs.argmax(dim=-1)[0, 0].item()
|
| 220 |
+
|
| 221 |
+
if index != self.dict['tgt'].eos_index:
|
| 222 |
+
token = self.dict['tgt'].string([index])
|
| 223 |
+
else:
|
| 224 |
+
token = self.dict['tgt'].eos_word
|
| 225 |
+
|
| 226 |
+
return token
|
data/fairseq/examples/simultaneous_translation/models/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import importlib
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
for file in sorted(os.listdir(os.path.dirname(__file__))):
|
| 11 |
+
if file.endswith(".py") and not file.startswith("_"):
|
| 12 |
+
model_name = file[: file.find(".py")]
|
| 13 |
+
importlib.import_module(
|
| 14 |
+
"examples.simultaneous_translation.models." + model_name
|
| 15 |
+
)
|
data/fairseq/examples/simultaneous_translation/models/convtransformer_simul_trans.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2017-present, Facebook, Inc.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the LICENSE file in
|
| 5 |
+
# the root directory of this source tree. An additional grant of patent rights
|
| 6 |
+
# can be found in the PATENTS file in the same directory.
|
| 7 |
+
|
| 8 |
+
from fairseq import checkpoint_utils
|
| 9 |
+
from fairseq.models import (
|
| 10 |
+
register_model,
|
| 11 |
+
register_model_architecture,
|
| 12 |
+
)
|
| 13 |
+
from fairseq.models.speech_to_text import (
|
| 14 |
+
ConvTransformerModel,
|
| 15 |
+
convtransformer_espnet,
|
| 16 |
+
ConvTransformerEncoder,
|
| 17 |
+
)
|
| 18 |
+
from fairseq.models.speech_to_text.modules.augmented_memory_attention import (
|
| 19 |
+
augmented_memory,
|
| 20 |
+
SequenceEncoder,
|
| 21 |
+
AugmentedMemoryConvTransformerEncoder,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
from torch import nn, Tensor
|
| 25 |
+
from typing import Dict, List
|
| 26 |
+
from fairseq.models.speech_to_text.modules.emformer import NoSegAugmentedMemoryTransformerEncoderLayer
|
| 27 |
+
|
| 28 |
+
@register_model("convtransformer_simul_trans")
|
| 29 |
+
class SimulConvTransformerModel(ConvTransformerModel):
|
| 30 |
+
"""
|
| 31 |
+
Implementation of the paper:
|
| 32 |
+
|
| 33 |
+
SimulMT to SimulST: Adapting Simultaneous Text Translation to
|
| 34 |
+
End-to-End Simultaneous Speech Translation
|
| 35 |
+
|
| 36 |
+
https://www.aclweb.org/anthology/2020.aacl-main.58.pdf
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
@staticmethod
|
| 40 |
+
def add_args(parser):
|
| 41 |
+
super(SimulConvTransformerModel, SimulConvTransformerModel).add_args(parser)
|
| 42 |
+
parser.add_argument(
|
| 43 |
+
"--train-monotonic-only",
|
| 44 |
+
action="store_true",
|
| 45 |
+
default=False,
|
| 46 |
+
help="Only train monotonic attention",
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
@classmethod
|
| 50 |
+
def build_decoder(cls, args, task, embed_tokens):
|
| 51 |
+
tgt_dict = task.tgt_dict
|
| 52 |
+
|
| 53 |
+
from examples.simultaneous_translation.models.transformer_monotonic_attention import (
|
| 54 |
+
TransformerMonotonicDecoder,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
decoder = TransformerMonotonicDecoder(args, tgt_dict, embed_tokens)
|
| 58 |
+
|
| 59 |
+
if getattr(args, "load_pretrained_decoder_from", None):
|
| 60 |
+
decoder = checkpoint_utils.load_pretrained_component_from_model(
|
| 61 |
+
component=decoder, checkpoint=args.load_pretrained_decoder_from
|
| 62 |
+
)
|
| 63 |
+
return decoder
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@register_model_architecture(
|
| 67 |
+
"convtransformer_simul_trans", "convtransformer_simul_trans_espnet"
|
| 68 |
+
)
|
| 69 |
+
def convtransformer_simul_trans_espnet(args):
|
| 70 |
+
convtransformer_espnet(args)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
@register_model("convtransformer_augmented_memory")
|
| 74 |
+
@augmented_memory
|
| 75 |
+
class AugmentedMemoryConvTransformerModel(SimulConvTransformerModel):
|
| 76 |
+
@classmethod
|
| 77 |
+
def build_encoder(cls, args):
|
| 78 |
+
encoder = SequenceEncoder(args, AugmentedMemoryConvTransformerEncoder(args))
|
| 79 |
+
|
| 80 |
+
if getattr(args, "load_pretrained_encoder_from", None) is not None:
|
| 81 |
+
encoder = checkpoint_utils.load_pretrained_component_from_model(
|
| 82 |
+
component=encoder, checkpoint=args.load_pretrained_encoder_from
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
return encoder
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
@register_model_architecture(
|
| 89 |
+
"convtransformer_augmented_memory", "convtransformer_augmented_memory"
|
| 90 |
+
)
|
| 91 |
+
def augmented_memory_convtransformer_espnet(args):
|
| 92 |
+
convtransformer_espnet(args)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# ============================================================================ #
|
| 96 |
+
# Convtransformer
|
| 97 |
+
# with monotonic attention decoder
|
| 98 |
+
# with emformer encoder
|
| 99 |
+
# ============================================================================ #
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class ConvTransformerEmformerEncoder(ConvTransformerEncoder):
|
| 103 |
+
def __init__(self, args):
|
| 104 |
+
super().__init__(args)
|
| 105 |
+
stride = self.conv_layer_stride(args)
|
| 106 |
+
trf_left_context = args.segment_left_context // stride
|
| 107 |
+
trf_right_context = args.segment_right_context // stride
|
| 108 |
+
context_config = [trf_left_context, trf_right_context]
|
| 109 |
+
self.transformer_layers = nn.ModuleList(
|
| 110 |
+
[
|
| 111 |
+
NoSegAugmentedMemoryTransformerEncoderLayer(
|
| 112 |
+
input_dim=args.encoder_embed_dim,
|
| 113 |
+
num_heads=args.encoder_attention_heads,
|
| 114 |
+
ffn_dim=args.encoder_ffn_embed_dim,
|
| 115 |
+
num_layers=args.encoder_layers,
|
| 116 |
+
dropout_in_attn=args.dropout,
|
| 117 |
+
dropout_on_attn=args.dropout,
|
| 118 |
+
dropout_on_fc1=args.dropout,
|
| 119 |
+
dropout_on_fc2=args.dropout,
|
| 120 |
+
activation_fn=args.activation_fn,
|
| 121 |
+
context_config=context_config,
|
| 122 |
+
segment_size=args.segment_length,
|
| 123 |
+
max_memory_size=args.max_memory_size,
|
| 124 |
+
scaled_init=True, # TODO: use constant for now.
|
| 125 |
+
tanh_on_mem=args.amtrf_tanh_on_mem,
|
| 126 |
+
)
|
| 127 |
+
]
|
| 128 |
+
)
|
| 129 |
+
self.conv_transformer_encoder = ConvTransformerEncoder(args)
|
| 130 |
+
|
| 131 |
+
def forward(self, src_tokens, src_lengths):
|
| 132 |
+
encoder_out: Dict[str, List[Tensor]] = self.conv_transformer_encoder(src_tokens, src_lengths.to(src_tokens.device))
|
| 133 |
+
output = encoder_out["encoder_out"][0]
|
| 134 |
+
encoder_padding_masks = encoder_out["encoder_padding_mask"]
|
| 135 |
+
|
| 136 |
+
return {
|
| 137 |
+
"encoder_out": [output],
|
| 138 |
+
# This is because that in the original implementation
|
| 139 |
+
# the output didn't consider the last segment as right context.
|
| 140 |
+
"encoder_padding_mask": [encoder_padding_masks[0][:, : output.size(0)]] if len(encoder_padding_masks) > 0
|
| 141 |
+
else [],
|
| 142 |
+
"encoder_embedding": [],
|
| 143 |
+
"encoder_states": [],
|
| 144 |
+
"src_tokens": [],
|
| 145 |
+
"src_lengths": [],
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
@staticmethod
|
| 149 |
+
def conv_layer_stride(args):
|
| 150 |
+
# TODO: make it configurable from the args
|
| 151 |
+
return 4
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@register_model("convtransformer_emformer")
|
| 155 |
+
class ConvtransformerEmformer(SimulConvTransformerModel):
|
| 156 |
+
@staticmethod
|
| 157 |
+
def add_args(parser):
|
| 158 |
+
super(ConvtransformerEmformer, ConvtransformerEmformer).add_args(parser)
|
| 159 |
+
|
| 160 |
+
parser.add_argument(
|
| 161 |
+
"--segment-length",
|
| 162 |
+
type=int,
|
| 163 |
+
metavar="N",
|
| 164 |
+
help="length of each segment (not including left context / right context)",
|
| 165 |
+
)
|
| 166 |
+
parser.add_argument(
|
| 167 |
+
"--segment-left-context",
|
| 168 |
+
type=int,
|
| 169 |
+
help="length of left context in a segment",
|
| 170 |
+
)
|
| 171 |
+
parser.add_argument(
|
| 172 |
+
"--segment-right-context",
|
| 173 |
+
type=int,
|
| 174 |
+
help="length of right context in a segment",
|
| 175 |
+
)
|
| 176 |
+
parser.add_argument(
|
| 177 |
+
"--max-memory-size",
|
| 178 |
+
type=int,
|
| 179 |
+
default=-1,
|
| 180 |
+
help="Right context for the segment.",
|
| 181 |
+
)
|
| 182 |
+
parser.add_argument(
|
| 183 |
+
"--amtrf-tanh-on-mem",
|
| 184 |
+
default=False,
|
| 185 |
+
action="store_true",
|
| 186 |
+
help="whether to use tanh on memory vector",
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
@classmethod
|
| 190 |
+
def build_encoder(cls, args):
|
| 191 |
+
encoder = ConvTransformerEmformerEncoder(args)
|
| 192 |
+
if getattr(args, "load_pretrained_encoder_from", None):
|
| 193 |
+
encoder = checkpoint_utils.load_pretrained_component_from_model(
|
| 194 |
+
component=encoder, checkpoint=args.load_pretrained_encoder_from
|
| 195 |
+
)
|
| 196 |
+
return encoder
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
@register_model_architecture(
|
| 200 |
+
"convtransformer_emformer",
|
| 201 |
+
"convtransformer_emformer",
|
| 202 |
+
)
|
| 203 |
+
def convtransformer_emformer_base(args):
|
| 204 |
+
convtransformer_espnet(args)
|
data/fairseq/examples/simultaneous_translation/models/transformer_monotonic_attention.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from typing import Dict, List, NamedTuple, Optional
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
from examples.simultaneous_translation.modules.monotonic_transformer_layer import (
|
| 11 |
+
TransformerMonotonicDecoderLayer,
|
| 12 |
+
TransformerMonotonicEncoderLayer,
|
| 13 |
+
)
|
| 14 |
+
from fairseq.models import (
|
| 15 |
+
register_model,
|
| 16 |
+
register_model_architecture,
|
| 17 |
+
)
|
| 18 |
+
from fairseq.models.transformer import (
|
| 19 |
+
TransformerModel,
|
| 20 |
+
TransformerEncoder,
|
| 21 |
+
TransformerDecoder,
|
| 22 |
+
base_architecture,
|
| 23 |
+
transformer_iwslt_de_en,
|
| 24 |
+
transformer_vaswani_wmt_en_de_big,
|
| 25 |
+
tiny_architecture
|
| 26 |
+
)
|
| 27 |
+
from torch import Tensor
|
| 28 |
+
|
| 29 |
+
DEFAULT_MAX_SOURCE_POSITIONS = 1024
|
| 30 |
+
DEFAULT_MAX_TARGET_POSITIONS = 1024
|
| 31 |
+
READ_ACTION = 0
|
| 32 |
+
WRITE_ACTION = 1
|
| 33 |
+
|
| 34 |
+
TransformerMonotonicDecoderOut = NamedTuple(
|
| 35 |
+
"TransformerMonotonicDecoderOut",
|
| 36 |
+
[
|
| 37 |
+
("action", int),
|
| 38 |
+
("p_choose", Optional[Tensor]),
|
| 39 |
+
("attn_list", Optional[List[Optional[Dict[str, Tensor]]]]),
|
| 40 |
+
("encoder_out", Optional[Dict[str, List[Tensor]]]),
|
| 41 |
+
("encoder_padding_mask", Optional[Tensor]),
|
| 42 |
+
],
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@register_model("transformer_unidirectional")
|
| 47 |
+
class TransformerUnidirectionalModel(TransformerModel):
|
| 48 |
+
@classmethod
|
| 49 |
+
def build_encoder(cls, args, src_dict, embed_tokens):
|
| 50 |
+
return TransformerMonotonicEncoder(args, src_dict, embed_tokens)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@register_model("transformer_monotonic")
|
| 54 |
+
class TransformerModelSimulTrans(TransformerModel):
|
| 55 |
+
@classmethod
|
| 56 |
+
def build_encoder(cls, args, src_dict, embed_tokens):
|
| 57 |
+
return TransformerMonotonicEncoder(args, src_dict, embed_tokens)
|
| 58 |
+
|
| 59 |
+
@classmethod
|
| 60 |
+
def build_decoder(cls, args, tgt_dict, embed_tokens):
|
| 61 |
+
return TransformerMonotonicDecoder(args, tgt_dict, embed_tokens)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class TransformerMonotonicEncoder(TransformerEncoder):
|
| 65 |
+
def __init__(self, args, dictionary, embed_tokens):
|
| 66 |
+
super().__init__(args, dictionary, embed_tokens)
|
| 67 |
+
|
| 68 |
+
self.dictionary = dictionary
|
| 69 |
+
self.layers = nn.ModuleList([])
|
| 70 |
+
self.layers.extend(
|
| 71 |
+
[
|
| 72 |
+
TransformerMonotonicEncoderLayer(args)
|
| 73 |
+
for i in range(args.encoder_layers)
|
| 74 |
+
]
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class TransformerMonotonicDecoder(TransformerDecoder):
|
| 79 |
+
"""
|
| 80 |
+
Transformer decoder consisting of *args.decoder_layers* layers. Each layer
|
| 81 |
+
is a :class:`TransformerDecoderLayer`.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
args (argparse.Namespace): parsed command-line arguments
|
| 85 |
+
dictionary (~fairseq.data.Dictionary): decoding dictionary
|
| 86 |
+
embed_tokens (torch.nn.Embedding): output embedding
|
| 87 |
+
no_encoder_attn (bool, optional): whether to attend to encoder outputs
|
| 88 |
+
(default: False).
|
| 89 |
+
"""
|
| 90 |
+
|
| 91 |
+
def __init__(self, args, dictionary, embed_tokens, no_encoder_attn=False):
|
| 92 |
+
super().__init__(args, dictionary, embed_tokens, no_encoder_attn=False)
|
| 93 |
+
|
| 94 |
+
self.dictionary = dictionary
|
| 95 |
+
self.layers = nn.ModuleList([])
|
| 96 |
+
self.layers.extend(
|
| 97 |
+
[
|
| 98 |
+
TransformerMonotonicDecoderLayer(args)
|
| 99 |
+
for _ in range(args.decoder_layers)
|
| 100 |
+
]
|
| 101 |
+
)
|
| 102 |
+
self.policy_criterion = getattr(args, "policy_criterion", "any")
|
| 103 |
+
self.num_updates = None
|
| 104 |
+
|
| 105 |
+
def set_num_updates(self, num_updates):
|
| 106 |
+
self.num_updates = num_updates
|
| 107 |
+
|
| 108 |
+
def pre_attention(
|
| 109 |
+
self,
|
| 110 |
+
prev_output_tokens,
|
| 111 |
+
encoder_out_dict: Dict[str, List[Tensor]],
|
| 112 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
| 113 |
+
):
|
| 114 |
+
positions = (
|
| 115 |
+
self.embed_positions(
|
| 116 |
+
prev_output_tokens,
|
| 117 |
+
incremental_state=incremental_state,
|
| 118 |
+
)
|
| 119 |
+
if self.embed_positions is not None
|
| 120 |
+
else None
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
if incremental_state is not None:
|
| 124 |
+
prev_output_tokens = prev_output_tokens[:, -1:]
|
| 125 |
+
if positions is not None:
|
| 126 |
+
positions = positions[:, -1:]
|
| 127 |
+
# embed tokens and positions
|
| 128 |
+
x = self.embed_scale * self.embed_tokens(prev_output_tokens)
|
| 129 |
+
|
| 130 |
+
if self.project_in_dim is not None:
|
| 131 |
+
x = self.project_in_dim(x)
|
| 132 |
+
|
| 133 |
+
if positions is not None:
|
| 134 |
+
x += positions
|
| 135 |
+
|
| 136 |
+
x = self.dropout_module(x)
|
| 137 |
+
|
| 138 |
+
# B x T x C -> T x B x C
|
| 139 |
+
x = x.transpose(0, 1)
|
| 140 |
+
|
| 141 |
+
encoder_out = encoder_out_dict["encoder_out"][0]
|
| 142 |
+
|
| 143 |
+
if "encoder_padding_mask" in encoder_out_dict:
|
| 144 |
+
encoder_padding_mask = (
|
| 145 |
+
encoder_out_dict["encoder_padding_mask"][0]
|
| 146 |
+
if encoder_out_dict["encoder_padding_mask"]
|
| 147 |
+
and len(encoder_out_dict["encoder_padding_mask"]) > 0
|
| 148 |
+
else None
|
| 149 |
+
)
|
| 150 |
+
else:
|
| 151 |
+
encoder_padding_mask = None
|
| 152 |
+
|
| 153 |
+
return x, encoder_out, encoder_padding_mask
|
| 154 |
+
|
| 155 |
+
def post_attention(self, x):
|
| 156 |
+
if self.layer_norm is not None:
|
| 157 |
+
x = self.layer_norm(x)
|
| 158 |
+
|
| 159 |
+
# T x B x C -> B x T x C
|
| 160 |
+
x = x.transpose(0, 1)
|
| 161 |
+
|
| 162 |
+
if self.project_out_dim is not None:
|
| 163 |
+
x = self.project_out_dim(x)
|
| 164 |
+
|
| 165 |
+
return x
|
| 166 |
+
|
| 167 |
+
def clean_cache(
|
| 168 |
+
self,
|
| 169 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]],
|
| 170 |
+
end_id: Optional[int] = None,
|
| 171 |
+
):
|
| 172 |
+
"""
|
| 173 |
+
Clean cache in the monotonic layers.
|
| 174 |
+
The cache is generated because of a forward pass of decoder has run but no prediction,
|
| 175 |
+
so that the self attention key value in decoder is written in the incremental state.
|
| 176 |
+
end_id is the last idx of the layers
|
| 177 |
+
"""
|
| 178 |
+
if end_id is None:
|
| 179 |
+
end_id = len(self.layers)
|
| 180 |
+
|
| 181 |
+
for index, layer in enumerate(self.layers):
|
| 182 |
+
if index < end_id:
|
| 183 |
+
layer.prune_incremental_state(incremental_state)
|
| 184 |
+
|
| 185 |
+
def extract_features(
|
| 186 |
+
self,
|
| 187 |
+
prev_output_tokens,
|
| 188 |
+
encoder_out: Optional[Dict[str, List[Tensor]]],
|
| 189 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
| 190 |
+
full_context_alignment: bool = False, # unused
|
| 191 |
+
alignment_layer: Optional[int] = None, # unused
|
| 192 |
+
alignment_heads: Optional[int] = None, # unsed
|
| 193 |
+
):
|
| 194 |
+
"""
|
| 195 |
+
Similar to *forward* but only return features.
|
| 196 |
+
|
| 197 |
+
Returns:
|
| 198 |
+
tuple:
|
| 199 |
+
- the decoder's features of shape `(batch, tgt_len, embed_dim)`
|
| 200 |
+
- a dictionary with any model-specific outputs
|
| 201 |
+
"""
|
| 202 |
+
# incremental_state = None
|
| 203 |
+
assert encoder_out is not None
|
| 204 |
+
(x, encoder_outs, encoder_padding_mask) = self.pre_attention(
|
| 205 |
+
prev_output_tokens, encoder_out, incremental_state
|
| 206 |
+
)
|
| 207 |
+
attn = None
|
| 208 |
+
inner_states = [x]
|
| 209 |
+
attn_list: List[Optional[Dict[str, Tensor]]] = []
|
| 210 |
+
|
| 211 |
+
p_choose = torch.tensor([1.0])
|
| 212 |
+
|
| 213 |
+
for i, layer in enumerate(self.layers):
|
| 214 |
+
|
| 215 |
+
x, attn, _ = layer(
|
| 216 |
+
x=x,
|
| 217 |
+
encoder_out=encoder_outs,
|
| 218 |
+
encoder_padding_mask=encoder_padding_mask,
|
| 219 |
+
incremental_state=incremental_state,
|
| 220 |
+
self_attn_mask=self.buffered_future_mask(x)
|
| 221 |
+
if incremental_state is None
|
| 222 |
+
else None,
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
inner_states.append(x)
|
| 226 |
+
attn_list.append(attn)
|
| 227 |
+
|
| 228 |
+
if incremental_state is not None:
|
| 229 |
+
if_online = incremental_state["online"]["only"]
|
| 230 |
+
assert if_online is not None
|
| 231 |
+
if if_online.to(torch.bool):
|
| 232 |
+
# Online indicates that the encoder states are still changing
|
| 233 |
+
assert attn is not None
|
| 234 |
+
if self.policy_criterion == "any":
|
| 235 |
+
# Any head decide to read than read
|
| 236 |
+
head_read = layer.encoder_attn._get_monotonic_buffer(incremental_state)["head_read"]
|
| 237 |
+
assert head_read is not None
|
| 238 |
+
if head_read.any():
|
| 239 |
+
# We need to prune the last self_attn saved_state
|
| 240 |
+
# if model decide not to read
|
| 241 |
+
# otherwise there will be duplicated saved_state
|
| 242 |
+
self.clean_cache(incremental_state, i + 1)
|
| 243 |
+
|
| 244 |
+
return x, TransformerMonotonicDecoderOut(
|
| 245 |
+
action=0,
|
| 246 |
+
p_choose=p_choose,
|
| 247 |
+
attn_list=None,
|
| 248 |
+
encoder_out=None,
|
| 249 |
+
encoder_padding_mask=None,
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
x = self.post_attention(x)
|
| 253 |
+
|
| 254 |
+
return x, TransformerMonotonicDecoderOut(
|
| 255 |
+
action=1,
|
| 256 |
+
p_choose=p_choose,
|
| 257 |
+
attn_list=attn_list,
|
| 258 |
+
encoder_out=encoder_out,
|
| 259 |
+
encoder_padding_mask=encoder_padding_mask,
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
@register_model_architecture("transformer_monotonic", "transformer_monotonic")
|
| 264 |
+
def base_monotonic_architecture(args):
|
| 265 |
+
base_architecture(args)
|
| 266 |
+
args.encoder_unidirectional = getattr(args, "encoder_unidirectional", False)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
@register_model_architecture(
|
| 270 |
+
"transformer_monotonic", "transformer_monotonic_iwslt_de_en"
|
| 271 |
+
)
|
| 272 |
+
def transformer_monotonic_iwslt_de_en(args):
|
| 273 |
+
transformer_iwslt_de_en(args)
|
| 274 |
+
base_monotonic_architecture(args)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# parameters used in the "Attention Is All You Need" paper (Vaswani et al., 2017)
|
| 278 |
+
@register_model_architecture(
|
| 279 |
+
"transformer_monotonic", "transformer_monotonic_vaswani_wmt_en_de_big"
|
| 280 |
+
)
|
| 281 |
+
def transformer_monotonic_vaswani_wmt_en_de_big(args):
|
| 282 |
+
transformer_vaswani_wmt_en_de_big(args)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
@register_model_architecture(
|
| 286 |
+
"transformer_monotonic", "transformer_monotonic_vaswani_wmt_en_fr_big"
|
| 287 |
+
)
|
| 288 |
+
def transformer_monotonic_vaswani_wmt_en_fr_big(args):
|
| 289 |
+
transformer_monotonic_vaswani_wmt_en_fr_big(args)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
@register_model_architecture(
|
| 293 |
+
"transformer_unidirectional", "transformer_unidirectional_iwslt_de_en"
|
| 294 |
+
)
|
| 295 |
+
def transformer_unidirectional_iwslt_de_en(args):
|
| 296 |
+
transformer_iwslt_de_en(args)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
@register_model_architecture("transformer_monotonic", "transformer_monotonic_tiny")
|
| 300 |
+
def monotonic_tiny_architecture(args):
|
| 301 |
+
tiny_architecture(args)
|
| 302 |
+
base_monotonic_architecture(args)
|
data/fairseq/examples/simultaneous_translation/modules/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import importlib
|
| 9 |
+
from fairseq import registry
|
| 10 |
+
|
| 11 |
+
(
|
| 12 |
+
build_monotonic_attention,
|
| 13 |
+
register_monotonic_attention,
|
| 14 |
+
MONOTONIC_ATTENTION_REGISTRY,
|
| 15 |
+
_,
|
| 16 |
+
) = registry.setup_registry("--simul-type")
|
| 17 |
+
|
| 18 |
+
for file in sorted(os.listdir(os.path.dirname(__file__))):
|
| 19 |
+
if file.endswith(".py") and not file.startswith("_"):
|
| 20 |
+
model_name = file[: file.find(".py")]
|
| 21 |
+
importlib.import_module(
|
| 22 |
+
"examples.simultaneous_translation.modules." + model_name
|
| 23 |
+
)
|
data/fairseq/examples/simultaneous_translation/modules/fixed_pre_decision.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import partial
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import Tensor
|
| 5 |
+
import math
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from . import register_monotonic_attention
|
| 9 |
+
from .monotonic_multihead_attention import (
|
| 10 |
+
MonotonicAttention,
|
| 11 |
+
MonotonicInfiniteLookbackAttention,
|
| 12 |
+
WaitKAttention
|
| 13 |
+
)
|
| 14 |
+
from typing import Dict, Optional
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def fixed_pooling_monotonic_attention(monotonic_attention):
|
| 18 |
+
def create_model(monotonic_attention, klass):
|
| 19 |
+
class FixedStrideMonotonicAttention(monotonic_attention):
|
| 20 |
+
def __init__(self, args):
|
| 21 |
+
self.waitk_lagging = 0
|
| 22 |
+
self.num_heads = 0
|
| 23 |
+
self.noise_mean = 0.0
|
| 24 |
+
self.noise_var = 0.0
|
| 25 |
+
super().__init__(args)
|
| 26 |
+
self.pre_decision_type = args.fixed_pre_decision_type
|
| 27 |
+
self.pre_decision_ratio = args.fixed_pre_decision_ratio
|
| 28 |
+
self.pre_decision_pad_threshold = args.fixed_pre_decision_pad_threshold
|
| 29 |
+
assert self.pre_decision_ratio > 1
|
| 30 |
+
|
| 31 |
+
if args.fixed_pre_decision_type == "average":
|
| 32 |
+
self.pooling_layer = torch.nn.AvgPool1d(
|
| 33 |
+
kernel_size=self.pre_decision_ratio,
|
| 34 |
+
stride=self.pre_decision_ratio,
|
| 35 |
+
ceil_mode=True,
|
| 36 |
+
)
|
| 37 |
+
elif args.fixed_pre_decision_type == "last":
|
| 38 |
+
|
| 39 |
+
def last(key):
|
| 40 |
+
if key.size(2) < self.pre_decision_ratio:
|
| 41 |
+
return key
|
| 42 |
+
else:
|
| 43 |
+
k = key[
|
| 44 |
+
:,
|
| 45 |
+
:,
|
| 46 |
+
self.pre_decision_ratio - 1:: self.pre_decision_ratio,
|
| 47 |
+
].contiguous()
|
| 48 |
+
if key.size(-1) % self.pre_decision_ratio != 0:
|
| 49 |
+
k = torch.cat([k, key[:, :, -1:]], dim=-1).contiguous()
|
| 50 |
+
return k
|
| 51 |
+
|
| 52 |
+
self.pooling_layer = last
|
| 53 |
+
else:
|
| 54 |
+
raise NotImplementedError
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def add_args(parser):
|
| 58 |
+
super(
|
| 59 |
+
FixedStrideMonotonicAttention, FixedStrideMonotonicAttention
|
| 60 |
+
).add_args(parser)
|
| 61 |
+
parser.add_argument(
|
| 62 |
+
"--fixed-pre-decision-ratio",
|
| 63 |
+
type=int,
|
| 64 |
+
required=True,
|
| 65 |
+
help=(
|
| 66 |
+
"Ratio for the fixed pre-decision,"
|
| 67 |
+
"indicating how many encoder steps will start"
|
| 68 |
+
"simultaneous decision making process."
|
| 69 |
+
),
|
| 70 |
+
)
|
| 71 |
+
parser.add_argument(
|
| 72 |
+
"--fixed-pre-decision-type",
|
| 73 |
+
default="average",
|
| 74 |
+
choices=["average", "last"],
|
| 75 |
+
help="Pooling type",
|
| 76 |
+
)
|
| 77 |
+
parser.add_argument(
|
| 78 |
+
"--fixed-pre-decision-pad-threshold",
|
| 79 |
+
type=float,
|
| 80 |
+
default=0.3,
|
| 81 |
+
help="If a part of the sequence has pad"
|
| 82 |
+
",the threshold the pooled part is a pad.",
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
def insert_zeros(self, x):
|
| 86 |
+
bsz_num_heads, tgt_len, src_len = x.size()
|
| 87 |
+
stride = self.pre_decision_ratio
|
| 88 |
+
weight = F.pad(torch.ones(1, 1, 1).to(x), (stride - 1, 0))
|
| 89 |
+
x_upsample = F.conv_transpose1d(
|
| 90 |
+
x.view(-1, src_len).unsqueeze(1),
|
| 91 |
+
weight,
|
| 92 |
+
stride=stride,
|
| 93 |
+
padding=0,
|
| 94 |
+
)
|
| 95 |
+
return x_upsample.squeeze(1).view(bsz_num_heads, tgt_len, -1)
|
| 96 |
+
|
| 97 |
+
def p_choose(
|
| 98 |
+
self,
|
| 99 |
+
query: Optional[Tensor],
|
| 100 |
+
key: Optional[Tensor],
|
| 101 |
+
key_padding_mask: Optional[Tensor] = None,
|
| 102 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
| 103 |
+
):
|
| 104 |
+
assert key is not None
|
| 105 |
+
assert query is not None
|
| 106 |
+
src_len = key.size(0)
|
| 107 |
+
tgt_len = query.size(0)
|
| 108 |
+
batch_size = query.size(1)
|
| 109 |
+
|
| 110 |
+
key_pool = self.pooling_layer(key.transpose(0, 2)).transpose(0, 2)
|
| 111 |
+
|
| 112 |
+
if key_padding_mask is not None:
|
| 113 |
+
key_padding_mask_pool = (
|
| 114 |
+
self.pooling_layer(key_padding_mask.unsqueeze(0).float())
|
| 115 |
+
.squeeze(0)
|
| 116 |
+
.gt(self.pre_decision_pad_threshold)
|
| 117 |
+
)
|
| 118 |
+
# Make sure at least one element is not pad
|
| 119 |
+
key_padding_mask_pool[:, 0] = 0
|
| 120 |
+
else:
|
| 121 |
+
key_padding_mask_pool = None
|
| 122 |
+
|
| 123 |
+
if incremental_state is not None:
|
| 124 |
+
# The floor instead of ceil is used for inference
|
| 125 |
+
# But make sure the length key_pool at least 1
|
| 126 |
+
if (
|
| 127 |
+
max(1, math.floor(key.size(0) / self.pre_decision_ratio))
|
| 128 |
+
) < key_pool.size(0):
|
| 129 |
+
key_pool = key_pool[:-1]
|
| 130 |
+
if key_padding_mask_pool is not None:
|
| 131 |
+
key_padding_mask_pool = key_padding_mask_pool[:-1]
|
| 132 |
+
|
| 133 |
+
p_choose_pooled = self.p_choose_from_qk(
|
| 134 |
+
query,
|
| 135 |
+
key_pool,
|
| 136 |
+
key_padding_mask_pool,
|
| 137 |
+
incremental_state=incremental_state,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# Upsample, interpolate zeros
|
| 141 |
+
p_choose = self.insert_zeros(p_choose_pooled)
|
| 142 |
+
|
| 143 |
+
if p_choose.size(-1) < src_len:
|
| 144 |
+
# Append zeros if the upsampled p_choose is shorter than src_len
|
| 145 |
+
p_choose = torch.cat(
|
| 146 |
+
[
|
| 147 |
+
p_choose,
|
| 148 |
+
torch.zeros(
|
| 149 |
+
p_choose.size(0),
|
| 150 |
+
tgt_len,
|
| 151 |
+
src_len - p_choose.size(-1)
|
| 152 |
+
).to(p_choose)
|
| 153 |
+
],
|
| 154 |
+
dim=2
|
| 155 |
+
)
|
| 156 |
+
else:
|
| 157 |
+
# can be larger than src_len because we used ceil before
|
| 158 |
+
p_choose = p_choose[:, :, :src_len]
|
| 159 |
+
p_choose[:, :, -1] = p_choose_pooled[:, :, -1]
|
| 160 |
+
|
| 161 |
+
assert list(p_choose.size()) == [
|
| 162 |
+
batch_size * self.num_heads,
|
| 163 |
+
tgt_len,
|
| 164 |
+
src_len,
|
| 165 |
+
]
|
| 166 |
+
|
| 167 |
+
return p_choose
|
| 168 |
+
|
| 169 |
+
FixedStrideMonotonicAttention.__name__ = klass.__name__
|
| 170 |
+
return FixedStrideMonotonicAttention
|
| 171 |
+
|
| 172 |
+
return partial(create_model, monotonic_attention)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
@register_monotonic_attention("waitk_fixed_pre_decision")
|
| 176 |
+
@fixed_pooling_monotonic_attention(WaitKAttention)
|
| 177 |
+
class WaitKAttentionFixedStride:
|
| 178 |
+
pass
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
@register_monotonic_attention("hard_aligned_fixed_pre_decision")
|
| 182 |
+
@fixed_pooling_monotonic_attention(MonotonicAttention)
|
| 183 |
+
class MonotonicAttentionFixedStride:
|
| 184 |
+
pass
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
@register_monotonic_attention("infinite_lookback_fixed_pre_decision")
|
| 188 |
+
@fixed_pooling_monotonic_attention(MonotonicInfiniteLookbackAttention)
|
| 189 |
+
class MonotonicInfiniteLookbackAttentionFixedStride:
|
| 190 |
+
pass
|
data/fairseq/examples/simultaneous_translation/modules/monotonic_multihead_attention.py
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from torch import Tensor
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
|
| 12 |
+
from examples.simultaneous_translation.utils.p_choose_strategy import (
|
| 13 |
+
learnable_p_choose,
|
| 14 |
+
waitk_p_choose
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
from examples.simultaneous_translation.utils.monotonic_attention import (
|
| 18 |
+
expected_alignment_from_p_choose,
|
| 19 |
+
expected_soft_attention,
|
| 20 |
+
mass_preservation,
|
| 21 |
+
)
|
| 22 |
+
from fairseq.modules import MultiheadAttention
|
| 23 |
+
|
| 24 |
+
from . import register_monotonic_attention
|
| 25 |
+
from typing import Dict, Optional
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@register_monotonic_attention("hard_aligned")
|
| 29 |
+
class MonotonicAttention(MultiheadAttention):
|
| 30 |
+
"""
|
| 31 |
+
Abstract class of monotonic attentions
|
| 32 |
+
"""
|
| 33 |
+
k_in_proj: Dict[str, nn.Linear]
|
| 34 |
+
q_in_proj: Dict[str, nn.Linear]
|
| 35 |
+
|
| 36 |
+
def __init__(self, args):
|
| 37 |
+
super().__init__(
|
| 38 |
+
embed_dim=args.decoder_embed_dim,
|
| 39 |
+
num_heads=args.decoder_attention_heads,
|
| 40 |
+
kdim=getattr(args, "encoder_embed_dim", None),
|
| 41 |
+
vdim=getattr(args, "encoder_embed_dim", None),
|
| 42 |
+
dropout=args.attention_dropout,
|
| 43 |
+
encoder_decoder_attention=True,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
self.soft_attention = False
|
| 47 |
+
|
| 48 |
+
self.eps = getattr(args, "attention_eps", True)
|
| 49 |
+
self.mass_preservation = getattr(args, "mass_preservation", True)
|
| 50 |
+
|
| 51 |
+
self.noise_type = args.noise_type
|
| 52 |
+
self.noise_mean = args.noise_mean
|
| 53 |
+
self.noise_var = args.noise_var
|
| 54 |
+
|
| 55 |
+
self.energy_bias_init = args.energy_bias_init
|
| 56 |
+
self.energy_bias = (
|
| 57 |
+
nn.Parameter(self.energy_bias_init * torch.ones([1]))
|
| 58 |
+
if args.energy_bias is True
|
| 59 |
+
else 0
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
self.k_in_proj = {"monotonic": self.k_proj}
|
| 63 |
+
self.q_in_proj = {"monotonic": self.q_proj}
|
| 64 |
+
self.chunk_size = None
|
| 65 |
+
|
| 66 |
+
@staticmethod
|
| 67 |
+
def add_args(parser):
|
| 68 |
+
# fmt: off
|
| 69 |
+
parser.add_argument('--no-mass-preservation', action="store_false",
|
| 70 |
+
dest="mass_preservation",
|
| 71 |
+
help='Do not stay on the last token when decoding')
|
| 72 |
+
parser.add_argument('--mass-preservation', action="store_true",
|
| 73 |
+
dest="mass_preservation",
|
| 74 |
+
help='Stay on the last token when decoding')
|
| 75 |
+
parser.set_defaults(mass_preservation=True)
|
| 76 |
+
parser.add_argument('--noise-var', type=float, default=1.0,
|
| 77 |
+
help='Variance of discretness noise')
|
| 78 |
+
parser.add_argument('--noise-mean', type=float, default=0.0,
|
| 79 |
+
help='Mean of discretness noise')
|
| 80 |
+
parser.add_argument('--noise-type', type=str, default="flat",
|
| 81 |
+
help='Type of discretness noise')
|
| 82 |
+
parser.add_argument('--energy-bias', action="store_true",
|
| 83 |
+
default=False,
|
| 84 |
+
help='Bias for energy')
|
| 85 |
+
parser.add_argument('--energy-bias-init', type=float, default=-2.0,
|
| 86 |
+
help='Initial value of the bias for energy')
|
| 87 |
+
parser.add_argument('--attention-eps', type=float, default=1e-6,
|
| 88 |
+
help='Epsilon when calculating expected attention')
|
| 89 |
+
|
| 90 |
+
def energy_from_qk(
|
| 91 |
+
self,
|
| 92 |
+
query: Tensor,
|
| 93 |
+
key: Tensor,
|
| 94 |
+
energy_type: str,
|
| 95 |
+
key_padding_mask: Optional[Tensor] = None,
|
| 96 |
+
bias: int = 0
|
| 97 |
+
):
|
| 98 |
+
"""
|
| 99 |
+
Compute energy from query and key
|
| 100 |
+
q_func_value is a tuple looks like
|
| 101 |
+
(q_proj_func, q_tensor)
|
| 102 |
+
q_tensor size: bsz, tgt_len, emb_dim
|
| 103 |
+
k_tensor size: bsz, src_len, emb_dim
|
| 104 |
+
key_padding_mask size: bsz, src_len
|
| 105 |
+
attn_mask: bsz, src_len
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
length, bsz, _ = query.size()
|
| 109 |
+
q = self.q_in_proj[energy_type].forward(query)
|
| 110 |
+
q = (
|
| 111 |
+
q.contiguous()
|
| 112 |
+
.view(length, bsz * self.num_heads, self.head_dim)
|
| 113 |
+
.transpose(0, 1)
|
| 114 |
+
)
|
| 115 |
+
q = q * self.scaling
|
| 116 |
+
length, bsz, _ = key.size()
|
| 117 |
+
k = self.k_in_proj[energy_type].forward(key)
|
| 118 |
+
k = (
|
| 119 |
+
k.contiguous()
|
| 120 |
+
.view(length, bsz * self.num_heads, self.head_dim)
|
| 121 |
+
.transpose(0, 1)
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
energy = torch.bmm(q, k.transpose(1, 2)) + bias
|
| 125 |
+
|
| 126 |
+
if key_padding_mask is not None:
|
| 127 |
+
energy = energy.masked_fill(
|
| 128 |
+
key_padding_mask.unsqueeze(1).to(torch.bool),
|
| 129 |
+
- float("inf")
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
return energy
|
| 133 |
+
|
| 134 |
+
def p_choose_from_qk(self, query, key, key_padding_mask, incremental_states=None):
|
| 135 |
+
monotonic_energy = self.energy_from_qk(
|
| 136 |
+
query,
|
| 137 |
+
key,
|
| 138 |
+
"monotonic",
|
| 139 |
+
key_padding_mask=key_padding_mask,
|
| 140 |
+
bias=self.energy_bias,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
p_choose = learnable_p_choose(
|
| 144 |
+
monotonic_energy,
|
| 145 |
+
self.noise_mean,
|
| 146 |
+
self.noise_var,
|
| 147 |
+
self.training
|
| 148 |
+
)
|
| 149 |
+
return p_choose
|
| 150 |
+
|
| 151 |
+
def p_choose(self, query, key, key_padding_mask, incremental_states=None):
|
| 152 |
+
return self.p_choose_from_qk(self, query, key, key_padding_mask)
|
| 153 |
+
|
| 154 |
+
def monotonic_attention_process_infer(
|
| 155 |
+
self,
|
| 156 |
+
query: Optional[Tensor],
|
| 157 |
+
key: Optional[Tensor],
|
| 158 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]],
|
| 159 |
+
):
|
| 160 |
+
"""
|
| 161 |
+
Monotonic attention at inference time
|
| 162 |
+
Notice that this function is designed for simuleval not sequence_generator
|
| 163 |
+
"""
|
| 164 |
+
assert query is not None
|
| 165 |
+
assert key is not None
|
| 166 |
+
|
| 167 |
+
if query.size(1) != 1:
|
| 168 |
+
raise RuntimeError(
|
| 169 |
+
"Simultaneous translation models don't support batch decoding."
|
| 170 |
+
)
|
| 171 |
+
# 1. compute stepwise probability
|
| 172 |
+
p_choose = self.p_choose(
|
| 173 |
+
query, key, None, incremental_state
|
| 174 |
+
).squeeze(1)
|
| 175 |
+
|
| 176 |
+
# 2. Compute the alpha
|
| 177 |
+
src_len = key.size(0)
|
| 178 |
+
# Maximum steps allows in this iteration
|
| 179 |
+
max_steps = src_len - 1 if self.mass_preservation else src_len
|
| 180 |
+
monotonic_cache = self._get_monotonic_buffer(incremental_state)
|
| 181 |
+
# Step for each head
|
| 182 |
+
monotonic_step = monotonic_cache.get(
|
| 183 |
+
'head_step',
|
| 184 |
+
p_choose.new_zeros(1, self.num_heads).long()
|
| 185 |
+
)
|
| 186 |
+
assert monotonic_step is not None
|
| 187 |
+
finish_read = monotonic_step.eq(max_steps)
|
| 188 |
+
p_choose_i = torch.tensor(1)
|
| 189 |
+
|
| 190 |
+
while finish_read.sum().item() < self.num_heads:
|
| 191 |
+
# p_choose: self.num_heads, src_len
|
| 192 |
+
# only choose the p at monotonic steps
|
| 193 |
+
# p_choose_i: 1, self.num_heads
|
| 194 |
+
p_choose_i = (
|
| 195 |
+
p_choose.gather(
|
| 196 |
+
1,
|
| 197 |
+
monotonic_step
|
| 198 |
+
.clamp(0, src_len - 1),
|
| 199 |
+
)
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
read_one_step = (
|
| 203 |
+
(p_choose_i < 0.5)
|
| 204 |
+
.type_as(monotonic_step)
|
| 205 |
+
.masked_fill(finish_read, 0)
|
| 206 |
+
)
|
| 207 |
+
# 1 x bsz
|
| 208 |
+
# sample actions on unfinished seq
|
| 209 |
+
# 0 means stay, finish reading
|
| 210 |
+
# 1 means leave, continue reading
|
| 211 |
+
|
| 212 |
+
monotonic_step += read_one_step
|
| 213 |
+
|
| 214 |
+
finish_read = monotonic_step.eq(max_steps) | (read_one_step == 0)
|
| 215 |
+
|
| 216 |
+
# p_choose at last steps
|
| 217 |
+
p_choose_i = (
|
| 218 |
+
p_choose.gather(
|
| 219 |
+
1,
|
| 220 |
+
monotonic_step
|
| 221 |
+
.clamp(0, src_len - 1),
|
| 222 |
+
)
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
monotonic_cache["head_step"] = monotonic_step
|
| 226 |
+
# Whether a head is looking for new input
|
| 227 |
+
monotonic_cache["head_read"] = (
|
| 228 |
+
monotonic_step.eq(max_steps) & (p_choose_i < 0.5)
|
| 229 |
+
)
|
| 230 |
+
self._set_monotonic_buffer(incremental_state, monotonic_cache)
|
| 231 |
+
|
| 232 |
+
# 2. Update alpha
|
| 233 |
+
alpha = (
|
| 234 |
+
p_choose
|
| 235 |
+
.new_zeros([self.num_heads, src_len])
|
| 236 |
+
.scatter(
|
| 237 |
+
1,
|
| 238 |
+
(monotonic_step)
|
| 239 |
+
.view(self.num_heads, 1).clamp(0, src_len - 1),
|
| 240 |
+
1
|
| 241 |
+
)
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
if not self.mass_preservation:
|
| 245 |
+
alpha = alpha.masked_fill(
|
| 246 |
+
(monotonic_step == max_steps)
|
| 247 |
+
.view(self.num_heads, 1),
|
| 248 |
+
0
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
# 4. Compute Beta
|
| 252 |
+
if self.soft_attention:
|
| 253 |
+
monotonic_step = monotonic_step.t()
|
| 254 |
+
beta_mask = torch.arange(src_len).expand_as(alpha).gt(monotonic_step).unsqueeze(1)
|
| 255 |
+
# If it's soft attention just do softmax on current context
|
| 256 |
+
soft_energy = self.energy_from_qk(
|
| 257 |
+
query,
|
| 258 |
+
key,
|
| 259 |
+
"soft"
|
| 260 |
+
)
|
| 261 |
+
beta = torch.nn.functional.softmax(
|
| 262 |
+
soft_energy.masked_fill(beta_mask, -float("inf")), dim=-1
|
| 263 |
+
)
|
| 264 |
+
# It could happen that a head doesn't move at all
|
| 265 |
+
beta = beta.masked_fill(monotonic_step.eq(0).unsqueeze(1), 0)
|
| 266 |
+
else:
|
| 267 |
+
# If it's hard attention just select the last state
|
| 268 |
+
beta = alpha
|
| 269 |
+
|
| 270 |
+
return p_choose, alpha, beta
|
| 271 |
+
|
| 272 |
+
def monotonic_attention_process_train(
|
| 273 |
+
self,
|
| 274 |
+
query: Optional[Tensor],
|
| 275 |
+
key: Optional[Tensor],
|
| 276 |
+
key_padding_mask: Optional[Tensor] = None,
|
| 277 |
+
):
|
| 278 |
+
"""
|
| 279 |
+
Calculating monotonic attention process for training
|
| 280 |
+
Including:
|
| 281 |
+
stepwise probability: p_choose
|
| 282 |
+
expected hard alignment: alpha
|
| 283 |
+
expected soft attention: beta
|
| 284 |
+
"""
|
| 285 |
+
assert query is not None
|
| 286 |
+
assert key is not None
|
| 287 |
+
|
| 288 |
+
# 1. compute stepwise probability
|
| 289 |
+
p_choose = self.p_choose_from_qk(query, key, key_padding_mask)
|
| 290 |
+
|
| 291 |
+
# 2. compute expected_alignment
|
| 292 |
+
alpha = expected_alignment_from_p_choose(
|
| 293 |
+
p_choose,
|
| 294 |
+
key_padding_mask,
|
| 295 |
+
eps=self.eps,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
if self.mass_preservation:
|
| 299 |
+
alpha = mass_preservation(
|
| 300 |
+
alpha, key_padding_mask
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
# 3. compute expected soft attention (soft aligned model only)
|
| 304 |
+
if self.soft_attention:
|
| 305 |
+
soft_energy = self.energy_from_qk(
|
| 306 |
+
query,
|
| 307 |
+
key,
|
| 308 |
+
"soft",
|
| 309 |
+
key_padding_mask=None,
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
beta = expected_soft_attention(
|
| 313 |
+
alpha,
|
| 314 |
+
soft_energy,
|
| 315 |
+
padding_mask=key_padding_mask,
|
| 316 |
+
chunk_size=self.chunk_size,
|
| 317 |
+
eps=self.eps,
|
| 318 |
+
)
|
| 319 |
+
else:
|
| 320 |
+
beta = alpha
|
| 321 |
+
soft_energy = alpha
|
| 322 |
+
|
| 323 |
+
return p_choose, alpha, beta, soft_energy
|
| 324 |
+
|
| 325 |
+
def forward(
|
| 326 |
+
self,
|
| 327 |
+
query: Optional[Tensor],
|
| 328 |
+
key: Optional[Tensor],
|
| 329 |
+
value: Optional[Tensor],
|
| 330 |
+
key_padding_mask: Optional[Tensor] = None,
|
| 331 |
+
attn_mask: Optional[Tensor] = None,
|
| 332 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
| 333 |
+
need_weights: bool = True, static_kv: bool = False, need_head_weights: bool = False,
|
| 334 |
+
):
|
| 335 |
+
"""
|
| 336 |
+
query: tgt_len, bsz, embed_dim
|
| 337 |
+
key: src_len, bsz, embed_dim
|
| 338 |
+
value: src_len, bsz, embed_dim
|
| 339 |
+
"""
|
| 340 |
+
|
| 341 |
+
assert attn_mask is None
|
| 342 |
+
assert query is not None
|
| 343 |
+
assert key is not None
|
| 344 |
+
assert value is not None
|
| 345 |
+
|
| 346 |
+
tgt_len, bsz, embed_dim = query.size()
|
| 347 |
+
src_len = value.size(0)
|
| 348 |
+
|
| 349 |
+
if key_padding_mask is not None:
|
| 350 |
+
assert not key_padding_mask[:, 0].any(), (
|
| 351 |
+
"Only right padding is supported."
|
| 352 |
+
)
|
| 353 |
+
key_padding_mask = (
|
| 354 |
+
key_padding_mask
|
| 355 |
+
.unsqueeze(1)
|
| 356 |
+
.expand([bsz, self.num_heads, src_len])
|
| 357 |
+
.contiguous()
|
| 358 |
+
.view(-1, src_len)
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
if incremental_state is not None:
|
| 362 |
+
# Inference
|
| 363 |
+
(
|
| 364 |
+
p_choose, alpha, beta
|
| 365 |
+
) = self.monotonic_attention_process_infer(
|
| 366 |
+
query, key, incremental_state
|
| 367 |
+
)
|
| 368 |
+
soft_energy = beta
|
| 369 |
+
else:
|
| 370 |
+
# Train
|
| 371 |
+
(
|
| 372 |
+
p_choose, alpha, beta, soft_energy
|
| 373 |
+
) = self.monotonic_attention_process_train(
|
| 374 |
+
query, key, key_padding_mask
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
v = self.v_proj(value)
|
| 378 |
+
length, bsz, _ = v.size()
|
| 379 |
+
v = (
|
| 380 |
+
v.contiguous()
|
| 381 |
+
.view(length, bsz * self.num_heads, self.head_dim)
|
| 382 |
+
.transpose(0, 1)
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
attn = torch.bmm(beta.type_as(v), v)
|
| 386 |
+
|
| 387 |
+
attn = attn.transpose(0, 1).contiguous().view(tgt_len, bsz, embed_dim)
|
| 388 |
+
|
| 389 |
+
attn = self.out_proj(attn)
|
| 390 |
+
|
| 391 |
+
p_choose = p_choose.view(bsz, self.num_heads, tgt_len, src_len)
|
| 392 |
+
alpha = alpha.view(bsz, self.num_heads, tgt_len, src_len)
|
| 393 |
+
beta = beta.view(bsz, self.num_heads, tgt_len, src_len)
|
| 394 |
+
|
| 395 |
+
return attn, {
|
| 396 |
+
"p_choose": p_choose,
|
| 397 |
+
"alpha": alpha,
|
| 398 |
+
"beta": beta,
|
| 399 |
+
"soft_energy": soft_energy,
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
def _get_monotonic_buffer(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]]):
|
| 403 |
+
maybe_incremental_state = self.get_incremental_state(
|
| 404 |
+
incremental_state,
|
| 405 |
+
'monotonic',
|
| 406 |
+
)
|
| 407 |
+
if maybe_incremental_state is None:
|
| 408 |
+
typed_empty_dict: Dict[str, Optional[Tensor]] = {}
|
| 409 |
+
return typed_empty_dict
|
| 410 |
+
else:
|
| 411 |
+
return maybe_incremental_state
|
| 412 |
+
|
| 413 |
+
def _set_monotonic_buffer(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]], buffer: Dict[str, Optional[Tensor]]):
|
| 414 |
+
self.set_incremental_state(
|
| 415 |
+
incremental_state,
|
| 416 |
+
'monotonic',
|
| 417 |
+
buffer,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
@register_monotonic_attention("infinite_lookback")
|
| 422 |
+
class MonotonicInfiniteLookbackAttention(
|
| 423 |
+
MonotonicAttention
|
| 424 |
+
):
|
| 425 |
+
def __init__(self, args):
|
| 426 |
+
super().__init__(args)
|
| 427 |
+
self.soft_attention = True
|
| 428 |
+
self.init_soft_attention()
|
| 429 |
+
|
| 430 |
+
def init_soft_attention(self):
|
| 431 |
+
self.k_proj_soft = nn.Linear(self.kdim, self.embed_dim, bias=True)
|
| 432 |
+
self.q_proj_soft = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
|
| 433 |
+
self.k_in_proj["soft"] = self.k_proj_soft
|
| 434 |
+
self.q_in_proj["soft"] = self.q_proj_soft
|
| 435 |
+
|
| 436 |
+
if self.qkv_same_dim:
|
| 437 |
+
# Empirically observed the convergence to be much better with
|
| 438 |
+
# the scaled initialization
|
| 439 |
+
nn.init.xavier_uniform_(
|
| 440 |
+
self.k_in_proj["soft"].weight, gain=1 / math.sqrt(2)
|
| 441 |
+
)
|
| 442 |
+
nn.init.xavier_uniform_(
|
| 443 |
+
self.q_in_proj["soft"].weight, gain=1 / math.sqrt(2)
|
| 444 |
+
)
|
| 445 |
+
else:
|
| 446 |
+
nn.init.xavier_uniform_(self.k_in_proj["soft"].weight)
|
| 447 |
+
nn.init.xavier_uniform_(self.q_in_proj["soft"].weight)
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
@register_monotonic_attention("waitk")
|
| 451 |
+
class WaitKAttention(
|
| 452 |
+
MonotonicInfiniteLookbackAttention
|
| 453 |
+
):
|
| 454 |
+
"""
|
| 455 |
+
STACL: Simultaneous Translation with Implicit Anticipation and
|
| 456 |
+
Controllable Latency using Prefix-to-Prefix Framework
|
| 457 |
+
https://www.aclweb.org/anthology/P19-1289/
|
| 458 |
+
"""
|
| 459 |
+
def __init__(self, args):
|
| 460 |
+
super().__init__(args)
|
| 461 |
+
self.q_in_proj["soft"] = self.q_in_proj["monotonic"]
|
| 462 |
+
self.k_in_proj["soft"] = self.k_in_proj["monotonic"]
|
| 463 |
+
|
| 464 |
+
self.waitk_lagging = args.waitk_lagging
|
| 465 |
+
assert self.waitk_lagging > 0, (
|
| 466 |
+
f"Lagging has to been larger than 0, get {self.waitk_lagging}."
|
| 467 |
+
)
|
| 468 |
+
|
| 469 |
+
@staticmethod
|
| 470 |
+
def add_args(parser):
|
| 471 |
+
super(
|
| 472 |
+
MonotonicInfiniteLookbackAttention,
|
| 473 |
+
MonotonicInfiniteLookbackAttention
|
| 474 |
+
).add_args(parser)
|
| 475 |
+
|
| 476 |
+
parser.add_argument(
|
| 477 |
+
"--waitk-lagging", type=int, required=True, help="Wait K lagging"
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
def p_choose_from_qk(
|
| 481 |
+
self,
|
| 482 |
+
query: Optional[Tensor],
|
| 483 |
+
key: Optional[Tensor],
|
| 484 |
+
key_padding_mask: Optional[Tensor] = None,
|
| 485 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
| 486 |
+
):
|
| 487 |
+
assert query is not None
|
| 488 |
+
assert key is not None
|
| 489 |
+
|
| 490 |
+
p_choose = waitk_p_choose(
|
| 491 |
+
tgt_len=query.size(0),
|
| 492 |
+
src_len=key.size(0),
|
| 493 |
+
bsz=query.size(1) * self.num_heads,
|
| 494 |
+
waitk_lagging=self.waitk_lagging,
|
| 495 |
+
key_padding_mask=key_padding_mask,
|
| 496 |
+
incremental_state=incremental_state,
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
return p_choose.to(query)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
@register_monotonic_attention("chunkwise")
|
| 503 |
+
class ChunkwiseAttention(
|
| 504 |
+
MonotonicInfiniteLookbackAttention
|
| 505 |
+
):
|
| 506 |
+
def __init__(self, args):
|
| 507 |
+
super().__init__(args)
|
| 508 |
+
self.chunk_size = args.mocha_chunk_size
|
| 509 |
+
assert self.chunk_size > 1
|
| 510 |
+
|
| 511 |
+
@staticmethod
|
| 512 |
+
def add_args(parser):
|
| 513 |
+
super(
|
| 514 |
+
MonotonicInfiniteLookbackAttention
|
| 515 |
+
).add_args(parser)
|
| 516 |
+
|
| 517 |
+
parser.add_argument(
|
| 518 |
+
"--mocha-chunk-size", type=int,
|
| 519 |
+
required=True, help="Mocha chunk size"
|
| 520 |
+
)
|
data/fairseq/examples/simultaneous_translation/modules/monotonic_transformer_layer.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
from fairseq.modules import TransformerDecoderLayer, TransformerEncoderLayer
|
| 7 |
+
|
| 8 |
+
from . import build_monotonic_attention
|
| 9 |
+
|
| 10 |
+
from typing import Dict, Optional, List
|
| 11 |
+
|
| 12 |
+
from torch import Tensor
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TransformerMonotonicEncoderLayer(TransformerEncoderLayer):
|
| 17 |
+
def forward(self, x, encoder_padding_mask):
|
| 18 |
+
seq_len, _, _ = x.size()
|
| 19 |
+
attn_mask = x.new_ones([seq_len, seq_len]).triu(1)
|
| 20 |
+
attn_mask = attn_mask.masked_fill(attn_mask.bool(), float("-inf"))
|
| 21 |
+
return super().forward(x, encoder_padding_mask, attn_mask)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class TransformerMonotonicDecoderLayer(TransformerDecoderLayer):
|
| 25 |
+
def __init__(self, args):
|
| 26 |
+
super().__init__(args)
|
| 27 |
+
|
| 28 |
+
assert args.simul_type is not None, "A --simul-type is needed."
|
| 29 |
+
self.encoder_attn = build_monotonic_attention(args)
|
| 30 |
+
|
| 31 |
+
def prune_incremental_state(
|
| 32 |
+
self,
|
| 33 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]]
|
| 34 |
+
):
|
| 35 |
+
input_buffer = self.self_attn._get_input_buffer(incremental_state)
|
| 36 |
+
for key in ["prev_key", "prev_value"]:
|
| 37 |
+
input_buffer_key = input_buffer[key]
|
| 38 |
+
assert input_buffer_key is not None
|
| 39 |
+
if input_buffer_key.size(2) > 1:
|
| 40 |
+
input_buffer[key] = input_buffer_key[:, :, :-1, :]
|
| 41 |
+
else:
|
| 42 |
+
typed_empty_dict: Dict[str, Optional[Tensor]] = {}
|
| 43 |
+
input_buffer = typed_empty_dict
|
| 44 |
+
break
|
| 45 |
+
assert incremental_state is not None
|
| 46 |
+
self.self_attn._set_input_buffer(incremental_state, input_buffer)
|
| 47 |
+
|
| 48 |
+
def forward(
|
| 49 |
+
self,
|
| 50 |
+
x,
|
| 51 |
+
encoder_out: Optional[Tensor] = None,
|
| 52 |
+
encoder_padding_mask: Optional[Tensor] = None,
|
| 53 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
| 54 |
+
prev_self_attn_state: Optional[List[Tensor]] = None,
|
| 55 |
+
prev_attn_state: Optional[List[Tensor]] = None,
|
| 56 |
+
self_attn_mask: Optional[Tensor] = None,
|
| 57 |
+
self_attn_padding_mask: Optional[Tensor] = None,
|
| 58 |
+
need_attn: bool = False,
|
| 59 |
+
need_head_weights: bool = False,
|
| 60 |
+
):
|
| 61 |
+
"""
|
| 62 |
+
Args:
|
| 63 |
+
x (Tensor): input to the layer of shape `(seq_len, batch, embed_dim)`
|
| 64 |
+
encoder_padding_mask (ByteTensor, optional): binary
|
| 65 |
+
ByteTensor of shape `(batch, src_len)` where padding
|
| 66 |
+
elements are indicated by ``1``.
|
| 67 |
+
need_attn (bool, optional): return attention weights
|
| 68 |
+
need_head_weights (bool, optional): return attention weights
|
| 69 |
+
for each head (default: return average over heads).
|
| 70 |
+
|
| 71 |
+
Returns:
|
| 72 |
+
encoded output of shape `(seq_len, batch, embed_dim)`
|
| 73 |
+
"""
|
| 74 |
+
if need_head_weights:
|
| 75 |
+
need_attn = True
|
| 76 |
+
|
| 77 |
+
residual = x
|
| 78 |
+
if self.normalize_before:
|
| 79 |
+
x = self.self_attn_layer_norm(x)
|
| 80 |
+
if prev_self_attn_state is not None:
|
| 81 |
+
prev_key, prev_value = prev_self_attn_state[:2]
|
| 82 |
+
saved_state: Dict[str, Optional[Tensor]] = {
|
| 83 |
+
"prev_key": prev_key,
|
| 84 |
+
"prev_value": prev_value,
|
| 85 |
+
}
|
| 86 |
+
if len(prev_self_attn_state) >= 3:
|
| 87 |
+
saved_state["prev_key_padding_mask"] = prev_self_attn_state[2]
|
| 88 |
+
assert incremental_state is not None
|
| 89 |
+
self.self_attn._set_input_buffer(incremental_state, saved_state)
|
| 90 |
+
_self_attn_input_buffer = self.self_attn._get_input_buffer(incremental_state)
|
| 91 |
+
if self.cross_self_attention and not (
|
| 92 |
+
incremental_state is not None
|
| 93 |
+
and _self_attn_input_buffer is not None
|
| 94 |
+
and "prev_key" in _self_attn_input_buffer
|
| 95 |
+
):
|
| 96 |
+
if self_attn_mask is not None:
|
| 97 |
+
assert encoder_out is not None
|
| 98 |
+
self_attn_mask = torch.cat(
|
| 99 |
+
(x.new_zeros(x.size(0), encoder_out.size(0)), self_attn_mask), dim=1
|
| 100 |
+
)
|
| 101 |
+
if self_attn_padding_mask is not None:
|
| 102 |
+
if encoder_padding_mask is None:
|
| 103 |
+
assert encoder_out is not None
|
| 104 |
+
encoder_padding_mask = self_attn_padding_mask.new_zeros(
|
| 105 |
+
encoder_out.size(1), encoder_out.size(0)
|
| 106 |
+
)
|
| 107 |
+
self_attn_padding_mask = torch.cat(
|
| 108 |
+
(encoder_padding_mask, self_attn_padding_mask), dim=1
|
| 109 |
+
)
|
| 110 |
+
assert encoder_out is not None
|
| 111 |
+
y = torch.cat((encoder_out, x), dim=0)
|
| 112 |
+
else:
|
| 113 |
+
y = x
|
| 114 |
+
|
| 115 |
+
x, attn = self.self_attn(
|
| 116 |
+
query=x,
|
| 117 |
+
key=y,
|
| 118 |
+
value=y,
|
| 119 |
+
key_padding_mask=self_attn_padding_mask,
|
| 120 |
+
incremental_state=incremental_state,
|
| 121 |
+
need_weights=False,
|
| 122 |
+
attn_mask=self_attn_mask,
|
| 123 |
+
)
|
| 124 |
+
x = self.dropout_module(x)
|
| 125 |
+
x = self.residual_connection(x, residual)
|
| 126 |
+
if not self.normalize_before:
|
| 127 |
+
x = self.self_attn_layer_norm(x)
|
| 128 |
+
|
| 129 |
+
assert self.encoder_attn is not None
|
| 130 |
+
residual = x
|
| 131 |
+
if self.normalize_before:
|
| 132 |
+
x = self.encoder_attn_layer_norm(x)
|
| 133 |
+
if prev_attn_state is not None:
|
| 134 |
+
prev_key, prev_value = prev_attn_state[:2]
|
| 135 |
+
saved_state: Dict[str, Optional[Tensor]] = {
|
| 136 |
+
"prev_key": prev_key,
|
| 137 |
+
"prev_value": prev_value,
|
| 138 |
+
}
|
| 139 |
+
if len(prev_attn_state) >= 3:
|
| 140 |
+
saved_state["prev_key_padding_mask"] = prev_attn_state[2]
|
| 141 |
+
assert incremental_state is not None
|
| 142 |
+
self.encoder_attn._set_input_buffer(incremental_state, saved_state)
|
| 143 |
+
|
| 144 |
+
x, attn = self.encoder_attn(
|
| 145 |
+
query=x,
|
| 146 |
+
key=encoder_out,
|
| 147 |
+
value=encoder_out,
|
| 148 |
+
key_padding_mask=encoder_padding_mask,
|
| 149 |
+
incremental_state=incremental_state,
|
| 150 |
+
static_kv=True,
|
| 151 |
+
need_weights=need_attn or (not self.training and self.need_attn),
|
| 152 |
+
need_head_weights=need_head_weights,
|
| 153 |
+
)
|
| 154 |
+
x = self.dropout_module(x)
|
| 155 |
+
x = self.residual_connection(x, residual)
|
| 156 |
+
if not self.normalize_before:
|
| 157 |
+
x = self.encoder_attn_layer_norm(x)
|
| 158 |
+
|
| 159 |
+
residual = x
|
| 160 |
+
if self.normalize_before:
|
| 161 |
+
x = self.final_layer_norm(x)
|
| 162 |
+
|
| 163 |
+
x = self.activation_fn(self.fc1(x))
|
| 164 |
+
x = self.activation_dropout_module(x)
|
| 165 |
+
x = self.fc2(x)
|
| 166 |
+
x = self.dropout_module(x)
|
| 167 |
+
x = self.residual_connection(x, residual)
|
| 168 |
+
if not self.normalize_before:
|
| 169 |
+
x = self.final_layer_norm(x)
|
| 170 |
+
if self.onnx_trace and incremental_state is not None:
|
| 171 |
+
saved_state = self.self_attn._get_input_buffer(incremental_state)
|
| 172 |
+
assert saved_state is not None
|
| 173 |
+
if self_attn_padding_mask is not None:
|
| 174 |
+
self_attn_state = [
|
| 175 |
+
saved_state["prev_key"],
|
| 176 |
+
saved_state["prev_value"],
|
| 177 |
+
saved_state["prev_key_padding_mask"],
|
| 178 |
+
]
|
| 179 |
+
else:
|
| 180 |
+
self_attn_state = [saved_state["prev_key"], saved_state["prev_value"]]
|
| 181 |
+
return x, attn, self_attn_state
|
| 182 |
+
return x, attn, None
|
data/fairseq/examples/simultaneous_translation/tests/test_alignment_train.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unittest
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
import hypothesis.strategies as st
|
| 7 |
+
from hypothesis import assume, given, settings
|
| 8 |
+
from torch.testing._internal.common_utils import TestCase
|
| 9 |
+
from examples.simultaneous_translation.utils.functions import exclusive_cumprod
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
TEST_CUDA = torch.cuda.is_available()
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class AlignmentTrainTest(TestCase):
|
| 16 |
+
def _test_custom_alignment_train_ref(self, p_choose, eps):
|
| 17 |
+
cumprod_1mp = exclusive_cumprod(1 - p_choose, dim=2, eps=eps)
|
| 18 |
+
cumprod_1mp_clamp = torch.clamp(cumprod_1mp, eps, 1.0)
|
| 19 |
+
|
| 20 |
+
bsz = p_choose.size(0)
|
| 21 |
+
tgt_len = p_choose.size(1)
|
| 22 |
+
src_len = p_choose.size(2)
|
| 23 |
+
|
| 24 |
+
alpha_0 = p_choose.new_zeros([bsz, 1, src_len])
|
| 25 |
+
alpha_0[:, :, 0] = 1.0
|
| 26 |
+
|
| 27 |
+
previous_alpha = [alpha_0]
|
| 28 |
+
|
| 29 |
+
for i in range(tgt_len):
|
| 30 |
+
# p_choose: bsz , tgt_len, src_len
|
| 31 |
+
# cumprod_1mp_clamp : bsz, tgt_len, src_len
|
| 32 |
+
# previous_alpha[i]: bsz, 1, src_len
|
| 33 |
+
# alpha_i: bsz, src_len
|
| 34 |
+
alpha_i = (
|
| 35 |
+
p_choose[:, i]
|
| 36 |
+
* cumprod_1mp[:, i]
|
| 37 |
+
* torch.cumsum(
|
| 38 |
+
previous_alpha[i][:, 0] / cumprod_1mp_clamp[:, i], dim=1
|
| 39 |
+
)
|
| 40 |
+
).clamp(0, 1.0)
|
| 41 |
+
|
| 42 |
+
previous_alpha.append(alpha_i.unsqueeze(1))
|
| 43 |
+
|
| 44 |
+
# alpha: bsz * num_heads, tgt_len, src_len
|
| 45 |
+
alpha = torch.cat(previous_alpha[1:], dim=1)
|
| 46 |
+
return alpha
|
| 47 |
+
|
| 48 |
+
def _test_custom_alignment_train_impl(self, p_choose, alpha, eps):
|
| 49 |
+
if p_choose.is_cuda:
|
| 50 |
+
from alignment_train_cuda_binding import alignment_train_cuda # @manual=//deeplearning/projects/fairseq-py:alignment_train_cuda_binding
|
| 51 |
+
alignment_train_cuda(p_choose, alpha, eps)
|
| 52 |
+
else:
|
| 53 |
+
from alignment_train_cpu_binding import alignment_train_cpu # @manual=//deeplearning/projects/fairseq-py:alignment_train_cpu_binding
|
| 54 |
+
alignment_train_cpu(p_choose, alpha, eps)
|
| 55 |
+
|
| 56 |
+
@settings(deadline=None)
|
| 57 |
+
@given(
|
| 58 |
+
bsz=st.integers(1, 100),
|
| 59 |
+
tgt_len=st.integers(1, 100),
|
| 60 |
+
src_len=st.integers(1, 550),
|
| 61 |
+
device=st.sampled_from(["cpu", "cuda"]),
|
| 62 |
+
)
|
| 63 |
+
def test_alignment_train(self, bsz, tgt_len, src_len, device):
|
| 64 |
+
eps = 1e-6
|
| 65 |
+
|
| 66 |
+
assume(device == "cpu" or TEST_CUDA)
|
| 67 |
+
p_choose = torch.rand(bsz, tgt_len, src_len, device=device)
|
| 68 |
+
|
| 69 |
+
# run the alignment with the custom operator
|
| 70 |
+
alpha_act = p_choose.new_zeros([bsz, tgt_len, src_len])
|
| 71 |
+
self._test_custom_alignment_train_impl(p_choose, alpha_act, eps)
|
| 72 |
+
|
| 73 |
+
# runu the alignment with the ref implementation
|
| 74 |
+
alpha_ref = self._test_custom_alignment_train_ref(p_choose, eps)
|
| 75 |
+
|
| 76 |
+
# verify the results
|
| 77 |
+
alpha_act = alpha_act.cpu().detach().numpy()
|
| 78 |
+
alpha_ref = alpha_ref.cpu().detach().numpy()
|
| 79 |
+
np.testing.assert_allclose(
|
| 80 |
+
alpha_act,
|
| 81 |
+
alpha_ref,
|
| 82 |
+
atol=1e-3,
|
| 83 |
+
rtol=1e-3,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
if __name__ == "__main__":
|
| 88 |
+
unittest.main()
|
data/fairseq/examples/simultaneous_translation/tests/test_text_models.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import unittest
|
| 3 |
+
from typing import Any, Dict
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from examples.simultaneous_translation.models import (
|
| 7 |
+
transformer_monotonic_attention
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
from tests.test_roberta import FakeTask
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
DEFAULT_CONFIG = {
|
| 15 |
+
"attention_eps": 1e-6,
|
| 16 |
+
"mass_preservation": True,
|
| 17 |
+
"noise_type": "flat",
|
| 18 |
+
"noise_mean": 0.0,
|
| 19 |
+
"noise_var": 1.0,
|
| 20 |
+
"energy_bias_init": -2,
|
| 21 |
+
"energy_bias": True
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
PAD_INDEX = 1
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def generate_config(overrides_kv):
|
| 29 |
+
new_dict = {key: value for key, value in DEFAULT_CONFIG.items()}
|
| 30 |
+
for key, value in overrides_kv.items():
|
| 31 |
+
new_dict[key] = value
|
| 32 |
+
return new_dict
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def make_sample_with_padding(longer_src=False) -> Dict[str, Any]:
|
| 36 |
+
tokens_1 = torch.LongTensor(
|
| 37 |
+
[
|
| 38 |
+
[2, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, 2],
|
| 39 |
+
[
|
| 40 |
+
2, 11, 12, 14, 15, 10, 11, 12, 13, 14, 15, 2,
|
| 41 |
+
PAD_INDEX, PAD_INDEX
|
| 42 |
+
],
|
| 43 |
+
]
|
| 44 |
+
)
|
| 45 |
+
tokens_2 = torch.LongTensor(
|
| 46 |
+
[
|
| 47 |
+
[2, 11, 12, 13, 14, 2, PAD_INDEX, PAD_INDEX],
|
| 48 |
+
[2, 11, 22, 33, 2, PAD_INDEX, PAD_INDEX, PAD_INDEX]
|
| 49 |
+
]
|
| 50 |
+
)
|
| 51 |
+
if longer_src:
|
| 52 |
+
src_tokens = tokens_1[:, 1:]
|
| 53 |
+
prev_output_tokens = tokens_2
|
| 54 |
+
else:
|
| 55 |
+
src_tokens = tokens_2[:, 1:8]
|
| 56 |
+
prev_output_tokens = tokens_1
|
| 57 |
+
|
| 58 |
+
src_lengths = src_tokens.ne(PAD_INDEX).sum(dim=1).long()
|
| 59 |
+
|
| 60 |
+
sample = {
|
| 61 |
+
"net_input": {
|
| 62 |
+
"src_tokens": src_tokens,
|
| 63 |
+
"prev_output_tokens": prev_output_tokens,
|
| 64 |
+
"src_lengths": src_lengths,
|
| 65 |
+
},
|
| 66 |
+
"target": prev_output_tokens[:, 1:],
|
| 67 |
+
}
|
| 68 |
+
return sample
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def build_transformer_monotonic_attention(**extra_args: Any):
|
| 72 |
+
overrides = {
|
| 73 |
+
# Use characteristics dimensions
|
| 74 |
+
"encoder_embed_dim": 12,
|
| 75 |
+
"encoder_ffn_embed_dim": 14,
|
| 76 |
+
"decoder_embed_dim": 12,
|
| 77 |
+
"decoder_ffn_embed_dim": 14,
|
| 78 |
+
# Disable dropout so we have comparable tests.
|
| 79 |
+
"dropout": 0,
|
| 80 |
+
"attention_dropout": 0,
|
| 81 |
+
"activation_dropout": 0,
|
| 82 |
+
"encoder_layerdrop": 0,
|
| 83 |
+
}
|
| 84 |
+
overrides.update(extra_args)
|
| 85 |
+
# Overrides the defaults from the parser
|
| 86 |
+
args = argparse.Namespace(**overrides)
|
| 87 |
+
transformer_monotonic_attention.monotonic_tiny_architecture(args)
|
| 88 |
+
|
| 89 |
+
torch.manual_seed(0)
|
| 90 |
+
task = FakeTask(args)
|
| 91 |
+
return (
|
| 92 |
+
transformer_monotonic_attention
|
| 93 |
+
.TransformerModelSimulTrans
|
| 94 |
+
.build_model(args, task)
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def expected_alignment_formula(
|
| 99 |
+
p_choose,
|
| 100 |
+
mass_perservation=True,
|
| 101 |
+
padding_mask=None
|
| 102 |
+
):
|
| 103 |
+
# Online and Linear-Time Attention by Enforcing Monotonic Alignments
|
| 104 |
+
# https://arxiv.org/pdf/1704.00784.pdf
|
| 105 |
+
# Eq 18, 19
|
| 106 |
+
bsz, tgt_len, src_len = p_choose.size()
|
| 107 |
+
alpha = torch.zeros_like(p_choose)
|
| 108 |
+
|
| 109 |
+
if padding_mask is not None:
|
| 110 |
+
bsz_pad = padding_mask.size(0)
|
| 111 |
+
num_heads = int(bsz / bsz_pad)
|
| 112 |
+
padding_mask = (
|
| 113 |
+
padding_mask
|
| 114 |
+
.unsqueeze(1)
|
| 115 |
+
.expand([bsz_pad, num_heads, src_len])
|
| 116 |
+
.contiguous()
|
| 117 |
+
.view(-1, src_len)
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
p_choose = p_choose.masked_fill(padding_mask.unsqueeze(1), 0)
|
| 121 |
+
|
| 122 |
+
for bsz_i in range(bsz):
|
| 123 |
+
for i in range(tgt_len):
|
| 124 |
+
for j in range(src_len):
|
| 125 |
+
if i == 0:
|
| 126 |
+
if j == 0:
|
| 127 |
+
# First source token
|
| 128 |
+
alpha[bsz_i, i, j] = p_choose[bsz_i, i, j]
|
| 129 |
+
else:
|
| 130 |
+
# First target token
|
| 131 |
+
alpha[bsz_i, i, j] = (
|
| 132 |
+
p_choose[bsz_i, i, j]
|
| 133 |
+
* torch.prod(
|
| 134 |
+
1 - p_choose[bsz_i, i, :j]
|
| 135 |
+
)
|
| 136 |
+
)
|
| 137 |
+
else:
|
| 138 |
+
alpha[bsz_i, i, j] = alpha[bsz_i, i - 1, j]
|
| 139 |
+
for k in range(j):
|
| 140 |
+
alpha[bsz_i, i, j] += (
|
| 141 |
+
alpha[bsz_i, i - 1, k]
|
| 142 |
+
* torch.prod(
|
| 143 |
+
1 - p_choose[bsz_i, i, k:j]
|
| 144 |
+
)
|
| 145 |
+
)
|
| 146 |
+
alpha[bsz_i, i, j] *= p_choose[bsz_i, i, j]
|
| 147 |
+
|
| 148 |
+
alpha = alpha.masked_fill(padding_mask.unsqueeze(1), 0)
|
| 149 |
+
|
| 150 |
+
if mass_perservation:
|
| 151 |
+
alpha = mass_perservation_formula(alpha, False, padding_mask)
|
| 152 |
+
|
| 153 |
+
return alpha
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def mass_perservation_formula(alpha, left_padding=False, padding_mask=None):
|
| 157 |
+
if padding_mask is None or alpha.size(-1) == 1:
|
| 158 |
+
if alpha.size(-1) > 1:
|
| 159 |
+
alpha[:, :, -1] = 1 - alpha[:, :, :-1].sum(dim=-1)
|
| 160 |
+
return alpha
|
| 161 |
+
|
| 162 |
+
src_lens = (padding_mask.logical_not()).sum(dim=1).long()
|
| 163 |
+
|
| 164 |
+
bsz, tgt_len, src_len = alpha.size()
|
| 165 |
+
|
| 166 |
+
assert (
|
| 167 |
+
not left_padding
|
| 168 |
+
or (left_padding and (not padding_mask[:, 0].any()))
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
alpha = alpha.masked_fill(padding_mask.unsqueeze(1), 0)
|
| 172 |
+
|
| 173 |
+
for bsz_i in range(bsz):
|
| 174 |
+
if left_padding:
|
| 175 |
+
alpha[bsz_i, :, -1] = (
|
| 176 |
+
1 - alpha[bsz_i, :, :-1].sum(dim=-1)
|
| 177 |
+
)
|
| 178 |
+
else:
|
| 179 |
+
alpha[bsz_i, :, src_lens[bsz_i] - 1] = (
|
| 180 |
+
1 - alpha[bsz_i, :, :src_lens[bsz_i] - 1].sum(dim=-1)
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
return alpha
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def expected_soft_attention_formula(
|
| 187 |
+
alpha,
|
| 188 |
+
soft_energy,
|
| 189 |
+
padding_mask=None,
|
| 190 |
+
chunksize=1e10,
|
| 191 |
+
):
|
| 192 |
+
# Monotonic Infinite Lookback Attention for Simultaneous Machine Translation
|
| 193 |
+
# https://arxiv.org/pdf/1906.05218.pdf
|
| 194 |
+
# Eq 14
|
| 195 |
+
|
| 196 |
+
# Monotonic Chunkwise Attention
|
| 197 |
+
# https://arxiv.org/abs/1712.05382
|
| 198 |
+
# Eq 17
|
| 199 |
+
bsz, tgt_len, src_len = alpha.size()
|
| 200 |
+
beta = torch.zeros_like(alpha)
|
| 201 |
+
|
| 202 |
+
if padding_mask is not None:
|
| 203 |
+
bsz_pad = padding_mask.size(0)
|
| 204 |
+
num_heads = int(bsz / bsz_pad)
|
| 205 |
+
# Expanding for potential head dimension
|
| 206 |
+
padding_mask = (
|
| 207 |
+
padding_mask
|
| 208 |
+
.unsqueeze(1)
|
| 209 |
+
.expand([bsz_pad, num_heads, src_len])
|
| 210 |
+
.contiguous()
|
| 211 |
+
.view(-1, src_len)
|
| 212 |
+
)
|
| 213 |
+
soft_energy = soft_energy.masked_fill(padding_mask.unsqueeze(1), float('-inf'))
|
| 214 |
+
|
| 215 |
+
for bsz_i in range(bsz):
|
| 216 |
+
for i in range(tgt_len):
|
| 217 |
+
for j in range(src_len):
|
| 218 |
+
for k in range(j, min([src_len, j + chunksize])):
|
| 219 |
+
if not padding_mask[bsz_i, j]:
|
| 220 |
+
beta[bsz_i, i, j] += (
|
| 221 |
+
alpha[bsz_i, i, k] * torch.exp(soft_energy[bsz_i, i, j])
|
| 222 |
+
/ torch.sum(torch.exp(soft_energy[bsz_i, i, max([0, k - chunksize + 1]):k + 1]))
|
| 223 |
+
)
|
| 224 |
+
return beta
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
class MonotonicAttentionTestAbstractClass(object):
|
| 228 |
+
def test_forward(self):
|
| 229 |
+
sample = make_sample_with_padding()
|
| 230 |
+
out, _ = self.model.forward(**sample["net_input"])
|
| 231 |
+
loss = out.sum()
|
| 232 |
+
loss.backward()
|
| 233 |
+
|
| 234 |
+
def test_p_choose(self):
|
| 235 |
+
sample = make_sample_with_padding()
|
| 236 |
+
_, extra_out = self.model.forward(**sample["net_input"])
|
| 237 |
+
for item in extra_out.attn_list:
|
| 238 |
+
p_choose = item["p_choose"]
|
| 239 |
+
self.assertTrue(p_choose.le(1.0).all())
|
| 240 |
+
self.assertTrue(p_choose.ge(0.0).all())
|
| 241 |
+
|
| 242 |
+
def test_expected_alignment(self):
|
| 243 |
+
for longer_src in [True, False]:
|
| 244 |
+
sample = make_sample_with_padding(longer_src)
|
| 245 |
+
_, extra_out = self.model.forward(**sample["net_input"])
|
| 246 |
+
for item in extra_out.attn_list:
|
| 247 |
+
p_choose = item["p_choose"]
|
| 248 |
+
alpha_system = item["alpha"]
|
| 249 |
+
self.assertTrue(p_choose.size() == alpha_system.size())
|
| 250 |
+
bsz, num_head, tgt_len, src_len = alpha_system.size()
|
| 251 |
+
alpha_system = alpha_system.view(-1, tgt_len, src_len)
|
| 252 |
+
p_choose = p_choose.view(-1, tgt_len, src_len)
|
| 253 |
+
|
| 254 |
+
alpha_real = expected_alignment_formula(
|
| 255 |
+
p_choose,
|
| 256 |
+
self.model.decoder.layers[0].encoder_attn.mass_preservation,
|
| 257 |
+
sample["net_input"]["src_tokens"].eq(PAD_INDEX)
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
self.assertTrue(
|
| 261 |
+
torch.abs(alpha_system - alpha_real).le(5e-5).all(),
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class HardMonotonicAttentionTestCase(
|
| 266 |
+
unittest.TestCase,
|
| 267 |
+
MonotonicAttentionTestAbstractClass
|
| 268 |
+
):
|
| 269 |
+
def setUp(self):
|
| 270 |
+
self.model = build_transformer_monotonic_attention(
|
| 271 |
+
**generate_config({"simul_type": "hard_aligned"})
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
class InfiniteLookbackTestCase(
|
| 276 |
+
unittest.TestCase,
|
| 277 |
+
MonotonicAttentionTestAbstractClass
|
| 278 |
+
):
|
| 279 |
+
def setUp(self):
|
| 280 |
+
self.model = build_transformer_monotonic_attention(
|
| 281 |
+
**generate_config(
|
| 282 |
+
{
|
| 283 |
+
"simul_type": "infinite_lookback"
|
| 284 |
+
}
|
| 285 |
+
)
|
| 286 |
+
)
|
| 287 |
+
self.model.train()
|
| 288 |
+
|
| 289 |
+
def test_fp16_for_long_input(self):
|
| 290 |
+
sample = {
|
| 291 |
+
"net_input": {
|
| 292 |
+
"src_tokens": torch.LongTensor([7] * 1000 + [2]).cuda().unsqueeze(0),
|
| 293 |
+
"prev_output_tokens": torch.LongTensor([7] * 1000 + [2]).cuda().unsqueeze(0),
|
| 294 |
+
"src_lengths": torch.LongTensor([1000]).cuda(),
|
| 295 |
+
},
|
| 296 |
+
"target": torch.LongTensor([2] + [7] * 1000).unsqueeze(0).cuda()
|
| 297 |
+
}
|
| 298 |
+
self.model.cuda().half()
|
| 299 |
+
_, extra_out = self.model.forward(**sample["net_input"])
|
| 300 |
+
for item in extra_out.attn_list:
|
| 301 |
+
for key in ["p_choose", "alpha", "beta", "soft_energy"]:
|
| 302 |
+
self.assertFalse(torch.isnan(item[key]).any())
|
| 303 |
+
|
| 304 |
+
def test_expected_attention(self):
|
| 305 |
+
for longer_src in [True, False]:
|
| 306 |
+
sample = make_sample_with_padding(longer_src)
|
| 307 |
+
_, extra_out = self.model.forward(**sample["net_input"])
|
| 308 |
+
for item in extra_out.attn_list:
|
| 309 |
+
p_choose = item["p_choose"]
|
| 310 |
+
alpha_system = item["alpha"]
|
| 311 |
+
beta_system = item["beta"]
|
| 312 |
+
soft_energy_system = item["soft_energy"]
|
| 313 |
+
self.assertTrue(beta_system.size() == alpha_system.size())
|
| 314 |
+
self.assertTrue(p_choose.size() == alpha_system.size())
|
| 315 |
+
|
| 316 |
+
bsz, num_head, tgt_len, src_len = alpha_system.size()
|
| 317 |
+
|
| 318 |
+
alpha_system = alpha_system.view(-1, tgt_len, src_len)
|
| 319 |
+
beta_system = beta_system.view(-1, tgt_len, src_len)
|
| 320 |
+
p_choose = p_choose.view(-1, tgt_len, src_len)
|
| 321 |
+
soft_energy_system = soft_energy_system.view(-1, tgt_len, src_len)
|
| 322 |
+
|
| 323 |
+
alpha_real = expected_alignment_formula(
|
| 324 |
+
p_choose,
|
| 325 |
+
self.model.decoder.layers[0].encoder_attn.mass_preservation,
|
| 326 |
+
sample["net_input"]["src_tokens"].eq(PAD_INDEX)
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
beta_real = expected_soft_attention_formula(
|
| 330 |
+
alpha_real,
|
| 331 |
+
soft_energy_system,
|
| 332 |
+
sample["net_input"]["src_tokens"].eq(PAD_INDEX),
|
| 333 |
+
chunksize=getattr(
|
| 334 |
+
self.model.decoder.layers[0].encoder_attn,
|
| 335 |
+
"chunk_size",
|
| 336 |
+
int(1e10)
|
| 337 |
+
) or int(1e10)
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
self.assertTrue(
|
| 341 |
+
torch.abs(beta_system - beta_real).le(1e-5).all(),
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
class ChunkwiswTestCase(
|
| 346 |
+
InfiniteLookbackTestCase
|
| 347 |
+
):
|
| 348 |
+
def setUp(self):
|
| 349 |
+
self.model = build_transformer_monotonic_attention(
|
| 350 |
+
**generate_config(
|
| 351 |
+
{
|
| 352 |
+
"simul_type": "chunkwise",
|
| 353 |
+
"mocha_chunk_size": 3
|
| 354 |
+
}
|
| 355 |
+
)
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class WaitkTestCase(InfiniteLookbackTestCase):
|
| 360 |
+
def setUp(self):
|
| 361 |
+
self.model = build_transformer_monotonic_attention(
|
| 362 |
+
**generate_config(
|
| 363 |
+
{
|
| 364 |
+
"simul_type": "waitk",
|
| 365 |
+
"waitk_lagging": 3,
|
| 366 |
+
}
|
| 367 |
+
)
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
def check_waitk(self, p_choose, lagging, padding_mask):
|
| 371 |
+
bsz, tgt_len, src_len = p_choose.size()
|
| 372 |
+
for bsz_i in range(bsz):
|
| 373 |
+
for i in range(tgt_len):
|
| 374 |
+
for j in range(src_len):
|
| 375 |
+
if not padding_mask[bsz_i, j]:
|
| 376 |
+
if j - i == lagging - 1:
|
| 377 |
+
self.assertTrue(p_choose[bsz_i, i, j] == 1)
|
| 378 |
+
else:
|
| 379 |
+
self.assertTrue(p_choose[bsz_i, i, j] == 0)
|
| 380 |
+
|
| 381 |
+
def test_waitk_p_choose(self):
|
| 382 |
+
for longer_src in [True, False]:
|
| 383 |
+
for k in [1, 3, 10, 20, 100]:
|
| 384 |
+
sample = make_sample_with_padding(longer_src)
|
| 385 |
+
model = build_transformer_monotonic_attention(
|
| 386 |
+
**generate_config(
|
| 387 |
+
{
|
| 388 |
+
"simul_type": "waitk",
|
| 389 |
+
"waitk_lagging": k,
|
| 390 |
+
}
|
| 391 |
+
)
|
| 392 |
+
)
|
| 393 |
+
model.train()
|
| 394 |
+
_, extra_out = model.forward(**sample["net_input"])
|
| 395 |
+
for item in extra_out.attn_list:
|
| 396 |
+
p_choose = item["p_choose"]
|
| 397 |
+
bsz, num_heads, tgt_len, src_len = p_choose.size()
|
| 398 |
+
padding_mask = sample["net_input"]["src_tokens"].eq(PAD_INDEX)
|
| 399 |
+
padding_mask = (
|
| 400 |
+
padding_mask
|
| 401 |
+
.unsqueeze(1)
|
| 402 |
+
.expand([bsz, num_heads, src_len])
|
| 403 |
+
.contiguous()
|
| 404 |
+
.view(-1, src_len)
|
| 405 |
+
)
|
| 406 |
+
p_choose = p_choose.view(bsz * num_heads, tgt_len, src_len)
|
| 407 |
+
self.check_waitk(p_choose, k, padding_mask)
|
data/fairseq/examples/simultaneous_translation/utils/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import importlib
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# automatically import any Python files in the criterions/ directory
|
| 11 |
+
for file in sorted(os.listdir(os.path.dirname(__file__))):
|
| 12 |
+
if file.endswith(".py") and not file.startswith("_"):
|
| 13 |
+
module = file[: file.find(".py")]
|
| 14 |
+
importlib.import_module("examples.simultaneous_translation.utils." + module)
|
data/fairseq/examples/simultaneous_translation/utils/functions.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def prob_check(tensor, eps=1e-10):
|
| 10 |
+
assert not torch.isnan(tensor).any(), (
|
| 11 |
+
"Nan in a probability tensor."
|
| 12 |
+
)
|
| 13 |
+
# Add the eps here to prevent errors introduced by precision
|
| 14 |
+
assert tensor.le(1.0 + eps).all() and tensor.ge(0.0 - eps).all(), (
|
| 15 |
+
"Incorrect values in a probability tensor"
|
| 16 |
+
", 0.0 <= tensor <= 1.0"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def exclusive_cumprod(tensor, dim: int, eps: float = 1e-10):
|
| 21 |
+
"""
|
| 22 |
+
Implementing exclusive cumprod.
|
| 23 |
+
There is cumprod in pytorch, however there is no exclusive mode.
|
| 24 |
+
cumprod(x) = [x1, x1x2, x2x3x4, ..., prod_{i=1}^n x_i]
|
| 25 |
+
exclusive means
|
| 26 |
+
cumprod(x) = [1, x1, x1x2, x1x2x3, ..., prod_{i=1}^{n-1} x_i]
|
| 27 |
+
"""
|
| 28 |
+
tensor_size = list(tensor.size())
|
| 29 |
+
tensor_size[dim] = 1
|
| 30 |
+
return_tensor = safe_cumprod(
|
| 31 |
+
torch.cat([torch.ones(tensor_size).type_as(tensor), tensor], dim=dim),
|
| 32 |
+
dim=dim,
|
| 33 |
+
eps=eps,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
if dim == 0:
|
| 37 |
+
return return_tensor[:-1]
|
| 38 |
+
elif dim == 1:
|
| 39 |
+
return return_tensor[:, :-1]
|
| 40 |
+
elif dim == 2:
|
| 41 |
+
return return_tensor[:, :, :-1]
|
| 42 |
+
else:
|
| 43 |
+
raise RuntimeError(
|
| 44 |
+
"Cumprod on dimension 3 and more is not implemented"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def safe_cumprod(tensor, dim: int, eps: float = 1e-10):
|
| 49 |
+
"""
|
| 50 |
+
An implementation of cumprod to prevent precision issue.
|
| 51 |
+
cumprod(x)
|
| 52 |
+
= [x1, x1x2, x1x2x3, ....]
|
| 53 |
+
= [exp(log(x1)), exp(log(x1) + log(x2)), exp(log(x1) + log(x2) + log(x3)), ...]
|
| 54 |
+
= exp(cumsum(log(x)))
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
if (tensor + eps < 0).any().item():
|
| 58 |
+
raise RuntimeError(
|
| 59 |
+
"Safe cumprod can only take non-negative tensors as input."
|
| 60 |
+
"Consider use torch.cumprod if you want to calculate negative values."
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
log_tensor = torch.log(tensor + eps)
|
| 64 |
+
cumsum_log_tensor = torch.cumsum(log_tensor, dim)
|
| 65 |
+
exp_cumsum_log_tensor = torch.exp(cumsum_log_tensor)
|
| 66 |
+
return exp_cumsum_log_tensor
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def moving_sum(x, start_idx: int, end_idx: int):
|
| 70 |
+
"""
|
| 71 |
+
From MONOTONIC CHUNKWISE ATTENTION
|
| 72 |
+
https://arxiv.org/pdf/1712.05382.pdf
|
| 73 |
+
Equation (18)
|
| 74 |
+
|
| 75 |
+
x = [x_1, x_2, ..., x_N]
|
| 76 |
+
MovingSum(x, start_idx, end_idx)_n = Sigma_{m=n−(start_idx−1)}^{n+end_idx-1} x_m
|
| 77 |
+
for n in {1, 2, 3, ..., N}
|
| 78 |
+
|
| 79 |
+
x : src_len, batch_size
|
| 80 |
+
start_idx : start idx
|
| 81 |
+
end_idx : end idx
|
| 82 |
+
|
| 83 |
+
Example
|
| 84 |
+
src_len = 5
|
| 85 |
+
batch_size = 3
|
| 86 |
+
x =
|
| 87 |
+
[[ 0, 5, 10],
|
| 88 |
+
[ 1, 6, 11],
|
| 89 |
+
[ 2, 7, 12],
|
| 90 |
+
[ 3, 8, 13],
|
| 91 |
+
[ 4, 9, 14]]
|
| 92 |
+
|
| 93 |
+
MovingSum(x, 3, 1) =
|
| 94 |
+
[[ 0, 5, 10],
|
| 95 |
+
[ 1, 11, 21],
|
| 96 |
+
[ 3, 18, 33],
|
| 97 |
+
[ 6, 21, 36],
|
| 98 |
+
[ 9, 24, 39]]
|
| 99 |
+
|
| 100 |
+
MovingSum(x, 1, 3) =
|
| 101 |
+
[[ 3, 18, 33],
|
| 102 |
+
[ 6, 21, 36],
|
| 103 |
+
[ 9, 24, 39],
|
| 104 |
+
[ 7, 17, 27],
|
| 105 |
+
[ 4, 9, 14]]
|
| 106 |
+
"""
|
| 107 |
+
# TODO: Make dimension configurable
|
| 108 |
+
assert start_idx > 0 and end_idx > 0
|
| 109 |
+
batch_size, tgt_len, src_len = x.size()
|
| 110 |
+
x = x.view(-1, src_len).unsqueeze(1)
|
| 111 |
+
# batch_size, 1, src_len
|
| 112 |
+
moving_sum_weight = torch.ones([1, 1, end_idx + start_idx - 1]).type_as(x)
|
| 113 |
+
|
| 114 |
+
moving_sum = torch.nn.functional.conv1d(
|
| 115 |
+
x, moving_sum_weight, padding=start_idx + end_idx - 1
|
| 116 |
+
).squeeze(1)
|
| 117 |
+
|
| 118 |
+
moving_sum = moving_sum[:, end_idx:-start_idx]
|
| 119 |
+
|
| 120 |
+
assert src_len == moving_sum.size(1)
|
| 121 |
+
assert batch_size * tgt_len == moving_sum.size(0)
|
| 122 |
+
|
| 123 |
+
moving_sum = moving_sum.view(batch_size, tgt_len, src_len)
|
| 124 |
+
|
| 125 |
+
return moving_sum
|
data/fairseq/examples/simultaneous_translation/utils/monotonic_attention.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
import torch
|
| 3 |
+
from torch import Tensor
|
| 4 |
+
|
| 5 |
+
from examples.simultaneous_translation.utils.functions import (
|
| 6 |
+
exclusive_cumprod,
|
| 7 |
+
prob_check,
|
| 8 |
+
moving_sum,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def expected_alignment_from_p_choose(
|
| 13 |
+
p_choose: Tensor,
|
| 14 |
+
padding_mask: Optional[Tensor] = None,
|
| 15 |
+
eps: float = 1e-6
|
| 16 |
+
):
|
| 17 |
+
"""
|
| 18 |
+
Calculating expected alignment for from stepwise probability
|
| 19 |
+
|
| 20 |
+
Reference:
|
| 21 |
+
Online and Linear-Time Attention by Enforcing Monotonic Alignments
|
| 22 |
+
https://arxiv.org/pdf/1704.00784.pdf
|
| 23 |
+
|
| 24 |
+
q_ij = (1 − p_{ij−1})q_{ij−1} + a+{i−1j}
|
| 25 |
+
a_ij = p_ij q_ij
|
| 26 |
+
|
| 27 |
+
Parallel solution:
|
| 28 |
+
ai = p_i * cumprod(1 − pi) * cumsum(a_i / cumprod(1 − pi))
|
| 29 |
+
|
| 30 |
+
============================================================
|
| 31 |
+
Expected input size
|
| 32 |
+
p_choose: bsz, tgt_len, src_len
|
| 33 |
+
"""
|
| 34 |
+
prob_check(p_choose)
|
| 35 |
+
|
| 36 |
+
# p_choose: bsz, tgt_len, src_len
|
| 37 |
+
bsz, tgt_len, src_len = p_choose.size()
|
| 38 |
+
dtype = p_choose.dtype
|
| 39 |
+
|
| 40 |
+
p_choose = p_choose.float()
|
| 41 |
+
|
| 42 |
+
if padding_mask is not None:
|
| 43 |
+
p_choose = p_choose.masked_fill(padding_mask.unsqueeze(1), 0.0)
|
| 44 |
+
|
| 45 |
+
if p_choose.is_cuda:
|
| 46 |
+
p_choose = p_choose.contiguous()
|
| 47 |
+
from alignment_train_cuda_binding import alignment_train_cuda as alignment_train
|
| 48 |
+
else:
|
| 49 |
+
from alignment_train_cpu_binding import alignment_train_cpu as alignment_train
|
| 50 |
+
|
| 51 |
+
alpha = p_choose.new_zeros([bsz, tgt_len, src_len])
|
| 52 |
+
alignment_train(p_choose, alpha, eps)
|
| 53 |
+
|
| 54 |
+
# Mix precision to prevent overflow for fp16
|
| 55 |
+
alpha = alpha.type(dtype)
|
| 56 |
+
|
| 57 |
+
prob_check(alpha)
|
| 58 |
+
|
| 59 |
+
return alpha
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def expected_soft_attention(
|
| 63 |
+
alpha: Tensor,
|
| 64 |
+
soft_energy: Tensor,
|
| 65 |
+
padding_mask: Optional[Tensor] = None,
|
| 66 |
+
chunk_size: Optional[int] = None,
|
| 67 |
+
eps: float = 1e-10
|
| 68 |
+
):
|
| 69 |
+
"""
|
| 70 |
+
Function to compute expected soft attention for
|
| 71 |
+
monotonic infinite lookback attention from
|
| 72 |
+
expected alignment and soft energy.
|
| 73 |
+
|
| 74 |
+
Reference:
|
| 75 |
+
Monotonic Chunkwise Attention
|
| 76 |
+
https://arxiv.org/abs/1712.05382
|
| 77 |
+
|
| 78 |
+
Monotonic Infinite Lookback Attention for Simultaneous Machine Translation
|
| 79 |
+
https://arxiv.org/abs/1906.05218
|
| 80 |
+
|
| 81 |
+
alpha: bsz, tgt_len, src_len
|
| 82 |
+
soft_energy: bsz, tgt_len, src_len
|
| 83 |
+
padding_mask: bsz, src_len
|
| 84 |
+
left_padding: bool
|
| 85 |
+
"""
|
| 86 |
+
if padding_mask is not None:
|
| 87 |
+
alpha = alpha.masked_fill(padding_mask.unsqueeze(1), 0.0)
|
| 88 |
+
soft_energy = soft_energy.masked_fill(
|
| 89 |
+
padding_mask.unsqueeze(1), -float("inf")
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
prob_check(alpha)
|
| 93 |
+
|
| 94 |
+
dtype = alpha.dtype
|
| 95 |
+
|
| 96 |
+
alpha = alpha.float()
|
| 97 |
+
soft_energy = soft_energy.float()
|
| 98 |
+
|
| 99 |
+
soft_energy = soft_energy - soft_energy.max(dim=2, keepdim=True)[0]
|
| 100 |
+
exp_soft_energy = torch.exp(soft_energy) + eps
|
| 101 |
+
|
| 102 |
+
if chunk_size is not None:
|
| 103 |
+
# Chunkwise
|
| 104 |
+
beta = (
|
| 105 |
+
exp_soft_energy
|
| 106 |
+
* moving_sum(
|
| 107 |
+
alpha / (eps + moving_sum(exp_soft_energy, chunk_size, 1)),
|
| 108 |
+
1, chunk_size
|
| 109 |
+
)
|
| 110 |
+
)
|
| 111 |
+
else:
|
| 112 |
+
# Infinite lookback
|
| 113 |
+
# Notice that infinite lookback is a special case of chunkwise
|
| 114 |
+
# where chunksize = inf
|
| 115 |
+
inner_items = alpha / (eps + torch.cumsum(exp_soft_energy, dim=2))
|
| 116 |
+
|
| 117 |
+
beta = (
|
| 118 |
+
exp_soft_energy
|
| 119 |
+
* torch.cumsum(inner_items.flip(dims=[2]), dim=2)
|
| 120 |
+
.flip(dims=[2])
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
if padding_mask is not None:
|
| 124 |
+
beta = beta.masked_fill(
|
| 125 |
+
padding_mask.unsqueeze(1).to(torch.bool), 0.0)
|
| 126 |
+
|
| 127 |
+
# Mix precision to prevent overflow for fp16
|
| 128 |
+
beta = beta.type(dtype)
|
| 129 |
+
|
| 130 |
+
beta = beta.clamp(0, 1)
|
| 131 |
+
|
| 132 |
+
prob_check(beta)
|
| 133 |
+
|
| 134 |
+
return beta
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def mass_preservation(
|
| 138 |
+
alpha: Tensor,
|
| 139 |
+
padding_mask: Optional[Tensor] = None,
|
| 140 |
+
left_padding: bool = False
|
| 141 |
+
):
|
| 142 |
+
"""
|
| 143 |
+
Function to compute the mass perservation for alpha.
|
| 144 |
+
This means that the residual weights of alpha will be assigned
|
| 145 |
+
to the last token.
|
| 146 |
+
|
| 147 |
+
Reference:
|
| 148 |
+
Monotonic Infinite Lookback Attention for Simultaneous Machine Translation
|
| 149 |
+
https://arxiv.org/abs/1906.05218
|
| 150 |
+
|
| 151 |
+
alpha: bsz, tgt_len, src_len
|
| 152 |
+
padding_mask: bsz, src_len
|
| 153 |
+
left_padding: bool
|
| 154 |
+
"""
|
| 155 |
+
|
| 156 |
+
prob_check(alpha)
|
| 157 |
+
|
| 158 |
+
if padding_mask is not None:
|
| 159 |
+
if not left_padding:
|
| 160 |
+
assert not padding_mask[:, 0].any(), (
|
| 161 |
+
"Find padding on the beginning of the sequence."
|
| 162 |
+
)
|
| 163 |
+
alpha = alpha.masked_fill(padding_mask.unsqueeze(1), 0.0)
|
| 164 |
+
|
| 165 |
+
if left_padding or padding_mask is None:
|
| 166 |
+
residuals = 1 - alpha[:, :, :-1].sum(dim=-1).clamp(0, 1)
|
| 167 |
+
alpha[:, :, -1] = residuals
|
| 168 |
+
else:
|
| 169 |
+
# right padding
|
| 170 |
+
_, tgt_len, src_len = alpha.size()
|
| 171 |
+
residuals = 1 - alpha.sum(dim=-1, keepdim=True).clamp(0, 1)
|
| 172 |
+
src_lens = src_len - padding_mask.sum(dim=1, keepdim=True)
|
| 173 |
+
src_lens = src_lens.expand(-1, tgt_len).contiguous()
|
| 174 |
+
# add back the last value
|
| 175 |
+
residuals += alpha.gather(2, src_lens.unsqueeze(2) - 1)
|
| 176 |
+
alpha = alpha.scatter(2, src_lens.unsqueeze(2) - 1, residuals)
|
| 177 |
+
|
| 178 |
+
prob_check(alpha)
|
| 179 |
+
|
| 180 |
+
return alpha
|
data/fairseq/examples/simultaneous_translation/utils/p_choose_strategy.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Dict
|
| 2 |
+
from torch import Tensor
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def waitk_p_choose(
|
| 7 |
+
tgt_len: int,
|
| 8 |
+
src_len: int,
|
| 9 |
+
bsz: int,
|
| 10 |
+
waitk_lagging: int,
|
| 11 |
+
key_padding_mask: Optional[Tensor] = None,
|
| 12 |
+
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None
|
| 13 |
+
):
|
| 14 |
+
|
| 15 |
+
max_src_len = src_len
|
| 16 |
+
if incremental_state is not None:
|
| 17 |
+
# Retrieve target length from incremental states
|
| 18 |
+
# For inference the length of query is always 1
|
| 19 |
+
max_tgt_len = incremental_state["steps"]["tgt"]
|
| 20 |
+
assert max_tgt_len is not None
|
| 21 |
+
max_tgt_len = int(max_tgt_len)
|
| 22 |
+
else:
|
| 23 |
+
max_tgt_len = tgt_len
|
| 24 |
+
|
| 25 |
+
if max_src_len < waitk_lagging:
|
| 26 |
+
if incremental_state is not None:
|
| 27 |
+
max_tgt_len = 1
|
| 28 |
+
return torch.zeros(
|
| 29 |
+
bsz, max_tgt_len, max_src_len
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
# Assuming the p_choose looks like this for wait k=3
|
| 33 |
+
# src_len = 6, max_tgt_len = 5
|
| 34 |
+
# [0, 0, 1, 0, 0, 0, 0]
|
| 35 |
+
# [0, 0, 0, 1, 0, 0, 0]
|
| 36 |
+
# [0, 0, 0, 0, 1, 0, 0]
|
| 37 |
+
# [0, 0, 0, 0, 0, 1, 0]
|
| 38 |
+
# [0, 0, 0, 0, 0, 0, 1]
|
| 39 |
+
# linearize the p_choose matrix:
|
| 40 |
+
# [0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0...]
|
| 41 |
+
# The indices of linearized matrix that equals 1 is
|
| 42 |
+
# 2 + 6 * 0
|
| 43 |
+
# 3 + 6 * 1
|
| 44 |
+
# ...
|
| 45 |
+
# n + src_len * n + k - 1 = n * (src_len + 1) + k - 1
|
| 46 |
+
# n from 0 to max_tgt_len - 1
|
| 47 |
+
#
|
| 48 |
+
# First, generate the indices (activate_indices_offset: bsz, max_tgt_len)
|
| 49 |
+
# Second, scatter a zeros tensor (bsz, max_tgt_len * src_len)
|
| 50 |
+
# with activate_indices_offset
|
| 51 |
+
# Third, resize the tensor to (bsz, max_tgt_len, src_len)
|
| 52 |
+
|
| 53 |
+
activate_indices_offset = (
|
| 54 |
+
(
|
| 55 |
+
torch.arange(max_tgt_len) * (max_src_len + 1)
|
| 56 |
+
+ waitk_lagging - 1
|
| 57 |
+
)
|
| 58 |
+
.unsqueeze(0)
|
| 59 |
+
.expand(bsz, max_tgt_len)
|
| 60 |
+
.long()
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
if key_padding_mask is not None:
|
| 64 |
+
if key_padding_mask[:, 0].any():
|
| 65 |
+
# Left padding
|
| 66 |
+
activate_indices_offset += (
|
| 67 |
+
key_padding_mask.sum(dim=1, keepdim=True)
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
# Need to clamp the indices that are too large
|
| 71 |
+
activate_indices_offset = (
|
| 72 |
+
activate_indices_offset
|
| 73 |
+
.clamp(
|
| 74 |
+
0,
|
| 75 |
+
min(
|
| 76 |
+
[
|
| 77 |
+
max_tgt_len,
|
| 78 |
+
max_src_len - waitk_lagging + 1
|
| 79 |
+
]
|
| 80 |
+
) * max_src_len - 1
|
| 81 |
+
)
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
p_choose = torch.zeros(bsz, max_tgt_len * max_src_len)
|
| 85 |
+
|
| 86 |
+
p_choose = p_choose.scatter(
|
| 87 |
+
1,
|
| 88 |
+
activate_indices_offset,
|
| 89 |
+
1.0
|
| 90 |
+
).view(bsz, max_tgt_len, max_src_len)
|
| 91 |
+
|
| 92 |
+
if key_padding_mask is not None:
|
| 93 |
+
p_choose = p_choose.to(key_padding_mask)
|
| 94 |
+
p_choose = p_choose.masked_fill(key_padding_mask.unsqueeze(1), 0)
|
| 95 |
+
|
| 96 |
+
if incremental_state is not None:
|
| 97 |
+
p_choose = p_choose[:, -1:]
|
| 98 |
+
|
| 99 |
+
return p_choose.float()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def learnable_p_choose(
|
| 103 |
+
energy,
|
| 104 |
+
noise_mean: float = 0.0,
|
| 105 |
+
noise_var: float = 0.0,
|
| 106 |
+
training: bool = True
|
| 107 |
+
):
|
| 108 |
+
"""
|
| 109 |
+
Calculating step wise prob for reading and writing
|
| 110 |
+
1 to read, 0 to write
|
| 111 |
+
energy: bsz, tgt_len, src_len
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
noise = 0
|
| 115 |
+
if training:
|
| 116 |
+
# add noise here to encourage discretness
|
| 117 |
+
noise = (
|
| 118 |
+
torch.normal(noise_mean, noise_var, energy.size())
|
| 119 |
+
.type_as(energy)
|
| 120 |
+
.to(energy.device)
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
p_choose = torch.sigmoid(energy + noise)
|
| 124 |
+
|
| 125 |
+
# p_choose: bsz * self.num_heads, tgt_len, src_len
|
| 126 |
+
return p_choose
|