sonithoiningombam commited on
Commit
4379c4e
·
verified ·
1 Parent(s): 0f2a414

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. data/fairseq/examples/adaptive_span/README.md +90 -0
  2. data/fairseq/examples/adaptive_span/__init__.py +19 -0
  3. data/fairseq/examples/adaptive_span/adagrad_with_grad_clip.py +128 -0
  4. data/fairseq/examples/adaptive_span/adaptive_span_attention.py +160 -0
  5. data/fairseq/examples/adaptive_span/adaptive_span_loss.py +107 -0
  6. data/fairseq/examples/adaptive_span/adaptive_span_model.py +263 -0
  7. data/fairseq/examples/adaptive_span/adaptive_span_model_wrapper.py +145 -0
  8. data/fairseq/examples/adaptive_span/truncated_bptt_lm_task.py +285 -0
  9. data/fairseq/examples/backtranslation/README.md +297 -0
  10. data/fairseq/examples/backtranslation/deduplicate_lines.py +41 -0
  11. data/fairseq/examples/backtranslation/extract_bt_data.py +72 -0
  12. data/fairseq/examples/backtranslation/prepare-de-monolingual.sh +98 -0
  13. data/fairseq/examples/backtranslation/prepare-wmt18en2de.sh +135 -0
  14. data/fairseq/examples/backtranslation/sacrebleu.sh +37 -0
  15. data/fairseq/examples/backtranslation/tokenized_bleu.sh +46 -0
  16. data/fairseq/examples/cross_lingual_language_model/README.md +77 -0
  17. data/fairseq/examples/discriminative_reranking_nmt/README.md +202 -0
  18. data/fairseq/examples/discriminative_reranking_nmt/__init__.py +1 -0
  19. data/fairseq/examples/discriminative_reranking_nmt/config/deen.yaml +56 -0
  20. data/fairseq/examples/discriminative_reranking_nmt/criterions/__init__.py +6 -0
  21. data/fairseq/examples/discriminative_reranking_nmt/criterions/discriminative_reranking_criterion.py +139 -0
  22. data/fairseq/examples/discriminative_reranking_nmt/drnmt_rerank.py +364 -0
  23. data/fairseq/examples/discriminative_reranking_nmt/models/__init__.py +6 -0
  24. data/fairseq/examples/discriminative_reranking_nmt/models/discriminative_reranking_model.py +365 -0
  25. data/fairseq/examples/discriminative_reranking_nmt/scripts/prep_data.py +136 -0
  26. data/fairseq/examples/discriminative_reranking_nmt/tasks/__init__.py +6 -0
  27. data/fairseq/examples/discriminative_reranking_nmt/tasks/discriminative_reranking_task.py +490 -0
  28. data/fairseq/examples/mbart/README.md +123 -0
  29. data/fairseq/examples/normformer/README.md +70 -0
  30. data/fairseq/examples/normformer/train_lm.sh +78 -0
  31. data/fairseq/examples/shuffled_word_order/README.finetuning.md +135 -0
  32. data/fairseq/examples/shuffled_word_order/README.md +94 -0
  33. data/fairseq/examples/simultaneous_translation/README.md +5 -0
  34. data/fairseq/examples/simultaneous_translation/__init__.py +6 -0
  35. data/fairseq/examples/simultaneous_translation/docs/ende-mma.md +74 -0
  36. data/fairseq/examples/simultaneous_translation/docs/enja-waitk.md +106 -0
  37. data/fairseq/examples/simultaneous_translation/eval/agents/simul_t2t_enja.py +226 -0
  38. data/fairseq/examples/simultaneous_translation/models/__init__.py +15 -0
  39. data/fairseq/examples/simultaneous_translation/models/convtransformer_simul_trans.py +204 -0
  40. data/fairseq/examples/simultaneous_translation/models/transformer_monotonic_attention.py +302 -0
  41. data/fairseq/examples/simultaneous_translation/modules/__init__.py +23 -0
  42. data/fairseq/examples/simultaneous_translation/modules/fixed_pre_decision.py +190 -0
  43. data/fairseq/examples/simultaneous_translation/modules/monotonic_multihead_attention.py +520 -0
  44. data/fairseq/examples/simultaneous_translation/modules/monotonic_transformer_layer.py +182 -0
  45. data/fairseq/examples/simultaneous_translation/tests/test_alignment_train.py +88 -0
  46. data/fairseq/examples/simultaneous_translation/tests/test_text_models.py +407 -0
  47. data/fairseq/examples/simultaneous_translation/utils/__init__.py +14 -0
  48. data/fairseq/examples/simultaneous_translation/utils/functions.py +125 -0
  49. data/fairseq/examples/simultaneous_translation/utils/monotonic_attention.py +180 -0
  50. 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