chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from .criterions import *
|
||||
from .models import *
|
||||
from .tasks import *
|
||||
|
||||
print("GAD plugins loaded...")
|
||||
@@ -0,0 +1 @@
|
||||
from .glat_loss import *
|
||||
@@ -0,0 +1,203 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import math
|
||||
from math import log
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq import metrics, utils
|
||||
from fairseq.criterions import FairseqCriterion, register_criterion
|
||||
from torch import Tensor
|
||||
import numpy as np
|
||||
|
||||
|
||||
@register_criterion("glat_loss")
|
||||
class LabelSmoothedDualImitationCriterion(FairseqCriterion):
|
||||
def __init__(self, task, label_smoothing):
|
||||
super().__init__(task)
|
||||
self.label_smoothing = label_smoothing
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
"""Add criterion-specific arguments to the parser."""
|
||||
parser.add_argument(
|
||||
"--label-smoothing",
|
||||
default=0.0,
|
||||
type=float,
|
||||
metavar="D",
|
||||
help="epsilon for label smoothing, 0 means no label smoothing",
|
||||
)
|
||||
parser.add_argument('--mse-lambda', default=10, type=float, metavar='D')
|
||||
|
||||
def _compute_loss(
|
||||
self, outputs, targets, masks=None, label_smoothing=0.0, name="loss", factor=1.0
|
||||
):
|
||||
"""
|
||||
outputs: batch x len x d_model
|
||||
targets: batch x len
|
||||
masks: batch x len
|
||||
|
||||
policy_logprob: if there is some policy
|
||||
depends on the likelihood score as rewards.
|
||||
"""
|
||||
|
||||
def mean_ds(x: Tensor, dim=None) -> Tensor:
|
||||
return (
|
||||
x.float().mean().type_as(x)
|
||||
if dim is None
|
||||
else x.float().mean(dim).type_as(x)
|
||||
)
|
||||
|
||||
if masks is not None:
|
||||
outputs, targets = outputs[masks], targets[masks]
|
||||
|
||||
if masks is not None and not masks.any():
|
||||
nll_loss = torch.tensor(0)
|
||||
loss = nll_loss
|
||||
else:
|
||||
logits = F.log_softmax(outputs, dim=-1)
|
||||
if targets.dim() == 1:
|
||||
losses = F.nll_loss(logits, targets.to(logits.device), reduction="none")
|
||||
|
||||
else: # soft-labels
|
||||
losses = F.kl_div(logits, targets.to(logits.device), reduction="none")
|
||||
losses = losses.sum(-1)
|
||||
|
||||
nll_loss = mean_ds(losses)
|
||||
if label_smoothing > 0:
|
||||
loss = (
|
||||
nll_loss * (1 - label_smoothing) - mean_ds(logits) * label_smoothing
|
||||
)
|
||||
else:
|
||||
loss = nll_loss
|
||||
|
||||
loss = loss * factor
|
||||
return {"name": name, "loss": loss, "nll_loss": nll_loss, "factor": factor}
|
||||
|
||||
def _custom_loss(self, loss, name="loss", factor=1.0):
|
||||
return {"name": name, "loss": loss, "factor": factor}
|
||||
|
||||
def forward(self, model, sample, reduce=True):
|
||||
"""Compute the loss for the given sample.
|
||||
Returns a tuple with three elements:
|
||||
1) the loss
|
||||
2) the sample size, which is used as the denominator for the gradient
|
||||
3) logging outputs to display while training
|
||||
"""
|
||||
nsentences, ntokens = sample["nsentences"], sample["ntokens"]
|
||||
|
||||
# B x T
|
||||
src_tokens, src_lengths = (
|
||||
sample["net_input"]["src_tokens"],
|
||||
sample["net_input"]["src_lengths"],
|
||||
)
|
||||
tgt_tokens, prev_output_tokens = sample["target"], sample["prev_target"]
|
||||
if 'glat' in sample:
|
||||
glat = sample['glat']
|
||||
else:
|
||||
glat = None
|
||||
|
||||
outputs = model(src_tokens, src_lengths, prev_output_tokens, tgt_tokens, glat)
|
||||
losses, nll_loss = [], []
|
||||
|
||||
for obj in outputs:
|
||||
if obj.startswith('glat'):
|
||||
continue
|
||||
if outputs[obj].get("loss", None) is None:
|
||||
_losses = self._compute_loss(
|
||||
outputs[obj].get("out"),
|
||||
outputs[obj].get("tgt"),
|
||||
outputs[obj].get("mask", None),
|
||||
outputs[obj].get("ls", 0.0),
|
||||
name=obj + "-loss",
|
||||
factor=outputs[obj].get("factor", 1.0),
|
||||
)
|
||||
else:
|
||||
_losses = self._custom_loss(
|
||||
outputs[obj].get("loss"),
|
||||
name=obj + "-loss",
|
||||
factor=outputs[obj].get("factor", 1.0),
|
||||
)
|
||||
|
||||
losses += [_losses]
|
||||
if outputs[obj].get("nll_loss", False):
|
||||
nll_loss += [_losses.get("nll_loss", 0.0)]
|
||||
|
||||
loss = sum(l["loss"] for l in losses)
|
||||
nll_loss = sum(l for l in nll_loss) if len(nll_loss) > 0 else loss.new_tensor(0)
|
||||
|
||||
# NOTE:
|
||||
# we don't need to use sample_size as denominator for the gradient
|
||||
# here sample_size is just used for logging
|
||||
sample_size = 1
|
||||
logging_output = {
|
||||
"loss": loss.data,
|
||||
"nll_loss": nll_loss.data,
|
||||
"ntokens": ntokens,
|
||||
"nsentences": nsentences,
|
||||
"sample_size": sample_size,
|
||||
}
|
||||
if "glat_accu" in outputs:
|
||||
logging_output["glat_accu"] = outputs['glat_accu']
|
||||
if "glat_context_p" in outputs:
|
||||
logging_output['glat_context_p'] = outputs['glat_context_p']
|
||||
|
||||
for l in losses:
|
||||
logging_output[l["name"]] = (
|
||||
utils.item(l["loss"].data / l["factor"])
|
||||
if reduce
|
||||
else l[["loss"]].data / l["factor"]
|
||||
)
|
||||
|
||||
return loss, sample_size, logging_output
|
||||
|
||||
@staticmethod
|
||||
def reduce_metrics(logging_outputs) -> None:
|
||||
"""Aggregate logging outputs from data parallel training."""
|
||||
sample_size = utils.item(
|
||||
sum(log.get("sample_size", 0) for log in logging_outputs)
|
||||
)
|
||||
loss = utils.item(sum(log.get("loss", 0) for log in logging_outputs))
|
||||
nll_loss = utils.item(sum(log.get("nll_loss", 0) for log in logging_outputs))
|
||||
|
||||
metrics.log_scalar(
|
||||
"loss", loss / sample_size / math.log(2), sample_size, round=3
|
||||
)
|
||||
metrics.log_scalar(
|
||||
"nll_loss", nll_loss / sample_size / math.log(2), sample_size, round=3
|
||||
)
|
||||
metrics.log_derived(
|
||||
"ppl", lambda meters: utils.get_perplexity(meters["loss"].avg)
|
||||
)
|
||||
|
||||
log_metric("glat_accu", logging_outputs)
|
||||
log_metric("glat_context_p", logging_outputs)
|
||||
|
||||
for key in logging_outputs[0]:
|
||||
if key[-5:] == "-loss":
|
||||
val = sum(log.get(key, 0) for log in logging_outputs)
|
||||
metrics.log_scalar(
|
||||
key[:-5],
|
||||
val / sample_size / math.log(2) if sample_size > 0 else 0.0,
|
||||
sample_size,
|
||||
round=3,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def logging_outputs_can_be_summed() -> bool:
|
||||
"""
|
||||
Whether the logging outputs returned by `forward` can be summed
|
||||
across workers prior to calling `reduce_metrics`. Setting this
|
||||
to True will improves distributed training speed.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
def log_metric(key, logging_outputs):
|
||||
if len(logging_outputs) > 0 and key in logging_outputs[0]:
|
||||
metrics.log_scalar(
|
||||
key, utils.item(np.mean([log.get(key, 0) for log in logging_outputs])), priority=10, round=3
|
||||
)
|
||||
@@ -0,0 +1,192 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
from fairseq import utils
|
||||
from fairseq.iterative_refinement_generator import DecoderOut
|
||||
from fairseq.models import register_model, register_model_architecture
|
||||
from fairseq.models.nat import FairseqNATModel
|
||||
from fairseq.modules.transformer_sentence_encoder import init_bert_params
|
||||
import torch
|
||||
from fairseq.models.nat.nonautoregressive_transformer import NATransformerEncoder, NATransformerDecoder, NATransformerModel
|
||||
import logging
|
||||
import random
|
||||
from contextlib import contextmanager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def torch_seed(seed):
|
||||
state = torch.random.get_rng_state()
|
||||
state_cuda = torch.cuda.random.get_rng_state()
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed_all(seed)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
torch.random.set_rng_state(state)
|
||||
torch.cuda.random.set_rng_state(state_cuda)
|
||||
|
||||
|
||||
@register_model("block")
|
||||
class BlockNAT(FairseqNATModel):
|
||||
forward_decoder = NATransformerModel.forward_decoder
|
||||
initialize_output_tokens = NATransformerModel.initialize_output_tokens
|
||||
|
||||
def __init__(self, args, encoder, decoder):
|
||||
super().__init__(args, encoder, decoder)
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
FairseqNATModel.add_args(parser)
|
||||
|
||||
parser.add_argument(
|
||||
"--src-embedding-copy",
|
||||
action="store_true",
|
||||
help="copy encoder word embeddings as the initial input of the decoder",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def build_encoder(cls, args, tgt_dict, embed_tokens):
|
||||
encoder = NATransformerEncoder(args, tgt_dict, embed_tokens)
|
||||
if getattr(args, "apply_bert_init", False):
|
||||
encoder.apply(init_bert_params)
|
||||
return encoder
|
||||
|
||||
@classmethod
|
||||
def build_decoder(cls, args, tgt_dict, embed_tokens):
|
||||
decoder = NATransformerDecoder(args, tgt_dict, embed_tokens)
|
||||
if getattr(args, "apply_bert_init", False):
|
||||
decoder.apply(init_bert_params)
|
||||
return decoder
|
||||
|
||||
def forward(
|
||||
self, src_tokens, src_lengths, prev_output_tokens, tgt_tokens, glat=None, **kwargs
|
||||
):
|
||||
# encoding
|
||||
encoder_out = self.encoder(src_tokens, src_lengths=src_lengths, **kwargs)
|
||||
|
||||
nonpad_positions = tgt_tokens.ne(self.pad)
|
||||
mask_positions = prev_output_tokens.eq(self.unk) & nonpad_positions
|
||||
mask_lens = (mask_positions).sum(1)
|
||||
l2r_positions = prev_output_tokens.ne(self.unk) & prev_output_tokens.ne(self.pad)
|
||||
l2r_lens = (l2r_positions).sum(1)
|
||||
rand_seed = random.randint(0, 19260817)
|
||||
glat_info = None
|
||||
if glat and tgt_tokens is not None:
|
||||
with torch.no_grad():
|
||||
with torch_seed(rand_seed):
|
||||
word_ins_out = self.decoder(
|
||||
normalize=False,
|
||||
prev_output_tokens=prev_output_tokens,
|
||||
encoder_out=encoder_out,
|
||||
)
|
||||
pred_tokens = word_ins_out.argmax(-1)
|
||||
same_num = ((pred_tokens == tgt_tokens) & mask_positions).sum(1)
|
||||
input_mask = torch.ones_like(nonpad_positions)
|
||||
bsz, seq_len = tgt_tokens.size()
|
||||
for li in range(bsz):
|
||||
target_num = (((mask_lens[li] - same_num[li].sum()).float()) * glat['context_p']).long()
|
||||
if target_num > 0:
|
||||
input_mask[li].scatter_(dim=0, index=(torch.randperm(mask_lens[li])[:target_num].cuda() +
|
||||
l2r_lens[li]).cuda(), value=0)
|
||||
input_mask = input_mask.eq(1)
|
||||
tgt_mask = input_mask.masked_fill(~mask_positions, False)
|
||||
glat_prev_output_tokens = prev_output_tokens.masked_fill(~input_mask, 0) + tgt_tokens.masked_fill(
|
||||
input_mask, 0)
|
||||
glat_tgt_tokens = tgt_tokens.masked_fill(~tgt_mask, self.pad)
|
||||
|
||||
prev_output_tokens, tgt_tokens = glat_prev_output_tokens, glat_tgt_tokens
|
||||
glat_info = {
|
||||
"glat_accu": (same_num.sum() / mask_lens.sum()).item(),
|
||||
"glat_context_p": glat['context_p'],
|
||||
}
|
||||
with torch_seed(rand_seed):
|
||||
word_ins_out = self.decoder(
|
||||
normalize=False,
|
||||
prev_output_tokens=prev_output_tokens,
|
||||
encoder_out=encoder_out,
|
||||
)
|
||||
|
||||
ret = {
|
||||
"word_ins": {
|
||||
"out": word_ins_out,
|
||||
"tgt": tgt_tokens,
|
||||
"mask": tgt_tokens.ne(self.pad),
|
||||
"ls": self.args.label_smoothing,
|
||||
"nll_loss": True,
|
||||
}
|
||||
}
|
||||
if glat_info is not None:
|
||||
ret.update(glat_info)
|
||||
return ret
|
||||
|
||||
|
||||
@register_model_architecture(
|
||||
"block", "block_6e6d512"
|
||||
)
|
||||
def base_architecture(args):
|
||||
args.encoder_embed_path = getattr(args, "encoder_embed_path", None)
|
||||
args.encoder_embed_dim = getattr(args, "encoder_embed_dim", 512)
|
||||
args.encoder_ffn_embed_dim = getattr(args, "encoder_ffn_embed_dim", 2048)
|
||||
args.encoder_layers = getattr(args, "encoder_layers", 6)
|
||||
args.encoder_attention_heads = getattr(args, "encoder_attention_heads", 8)
|
||||
args.encoder_normalize_before = getattr(args, "encoder_normalize_before", False)
|
||||
args.encoder_learned_pos = getattr(args, "encoder_learned_pos", False)
|
||||
args.decoder_embed_path = getattr(args, "decoder_embed_path", None)
|
||||
args.decoder_embed_dim = getattr(args, "decoder_embed_dim", args.encoder_embed_dim)
|
||||
args.decoder_ffn_embed_dim = getattr(
|
||||
args, "decoder_ffn_embed_dim", args.encoder_ffn_embed_dim
|
||||
)
|
||||
args.decoder_layers = getattr(args, "decoder_layers", 6)
|
||||
args.decoder_attention_heads = getattr(args, "decoder_attention_heads", 8)
|
||||
args.decoder_normalize_before = getattr(args, "decoder_normalize_before", False)
|
||||
args.decoder_learned_pos = getattr(args, "decoder_learned_pos", False)
|
||||
args.attention_dropout = getattr(args, "attention_dropout", 0.0)
|
||||
args.activation_dropout = getattr(args, "activation_dropout", 0.0)
|
||||
args.activation_fn = getattr(args, "activation_fn", "relu")
|
||||
args.dropout = getattr(args, "dropout", 0.1)
|
||||
args.adaptive_softmax_cutoff = getattr(args, "adaptive_softmax_cutoff", None)
|
||||
args.adaptive_softmax_dropout = getattr(args, "adaptive_softmax_dropout", 0)
|
||||
args.share_decoder_input_output_embed = getattr(
|
||||
args, "share_decoder_input_output_embed", False
|
||||
)
|
||||
args.share_all_embeddings = getattr(args, "share_all_embeddings", False)
|
||||
args.no_token_positional_embeddings = getattr(
|
||||
args, "no_token_positional_embeddings", False
|
||||
)
|
||||
args.adaptive_input = getattr(args, "adaptive_input", False)
|
||||
args.apply_bert_init = getattr(args, "apply_bert_init", False)
|
||||
|
||||
args.decoder_output_dim = getattr(
|
||||
args, "decoder_output_dim", args.decoder_embed_dim
|
||||
)
|
||||
args.decoder_input_dim = getattr(args, "decoder_input_dim", args.decoder_embed_dim)
|
||||
|
||||
# --- special arguments ---
|
||||
args.src_embedding_copy = getattr(args, "src_embedding_copy", False)
|
||||
|
||||
|
||||
@register_model_architecture(
|
||||
"block", "block"
|
||||
)
|
||||
def block_architecture(args):
|
||||
args.encoder_layers = getattr(args, "encoder_layers", 6)
|
||||
args.encoder_embed_dim = getattr(args, "encoder_embed_dim", 512)
|
||||
args.encoder_ffn_embed_dim = getattr(args, "encoder_ffn_embed_dim", args.encoder_embed_dim*4)
|
||||
args.encoder_attention_heads = getattr(args, "encoder_attention_heads", args.encoder_embed_dim//64)
|
||||
|
||||
args.decoder_layers = getattr(args, "decoder_layers", 6)
|
||||
args.decoder_embed_dim = getattr(args, "decoder_embed_dim", 512)
|
||||
args.decoder_ffn_embed_dim = getattr(args, "decoder_ffn_embed_dim", args.decoder_embed_dim*4)
|
||||
args.decoder_attention_heads = getattr(args, "decoder_attention_heads", args.decoder_embed_dim//64)
|
||||
base_architecture(args)
|
||||
|
||||
@register_model_architecture(
|
||||
"block", "block_base"
|
||||
)
|
||||
def base_architecture2(args):
|
||||
base_architecture(args)
|
||||
@@ -0,0 +1 @@
|
||||
from .BlockNAT import *
|
||||
@@ -0,0 +1 @@
|
||||
from .translation_lev_modified import *
|
||||
@@ -0,0 +1,289 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from math import log
|
||||
import torch
|
||||
from fairseq import utils
|
||||
from fairseq.data import LanguagePairDataset
|
||||
from fairseq.dataclass import ChoiceEnum
|
||||
from fairseq.tasks import register_task
|
||||
from fairseq.tasks.translation import TranslationConfig, TranslationTask, load_langpair_dataset
|
||||
from fairseq.utils import new_arange
|
||||
import logging
|
||||
from omegaconf import II
|
||||
import numpy as np
|
||||
|
||||
NOISE_CHOICES = ChoiceEnum(["random_delete", "random_mask", "no_noise", "full_mask", "block_mask"])
|
||||
|
||||
|
||||
@dataclass
|
||||
class TranslationLevenshteinConfig(TranslationConfig):
|
||||
noise: NOISE_CHOICES = field(
|
||||
default="random_delete",
|
||||
metadata={
|
||||
"help": "type of noise"
|
||||
},
|
||||
)
|
||||
start_p: float = field(
|
||||
default=0.5, metadata={"help": "minus prob"}
|
||||
)
|
||||
minus_p: float = field(
|
||||
default=0.2, metadata={"help": "minus prob"}
|
||||
)
|
||||
total_up: int = field(
|
||||
default=300000, metadata={"help": "total updates"}
|
||||
)
|
||||
block_size: int = field(
|
||||
default=5, metadata={"help": "block size"}
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register_task("translation_lev_modified", dataclass=TranslationLevenshteinConfig)
|
||||
class TranslationLevenshteinModifiedTask(TranslationTask):
|
||||
"""
|
||||
Translation (Sequence Generation) task for Levenshtein Transformer
|
||||
See `"Levenshtein Transformer" <https://arxiv.org/abs/1905.11006>`_.
|
||||
"""
|
||||
|
||||
cfg: TranslationLevenshteinConfig
|
||||
|
||||
def load_dataset(self, split, epoch=1, combine=False, **kwargs):
|
||||
"""Load a given dataset split.
|
||||
|
||||
Args:
|
||||
split (str): name of the split (e.g., train, valid, test)
|
||||
"""
|
||||
paths = utils.split_paths(self.cfg.data)
|
||||
assert len(paths) > 0
|
||||
data_path = paths[(epoch - 1) % len(paths)]
|
||||
|
||||
# infer langcode
|
||||
src, tgt = self.cfg.source_lang, self.cfg.target_lang
|
||||
|
||||
self.datasets[split] = load_langpair_dataset(
|
||||
data_path,
|
||||
split,
|
||||
src,
|
||||
self.src_dict,
|
||||
tgt,
|
||||
self.tgt_dict,
|
||||
combine=combine,
|
||||
dataset_impl=self.cfg.dataset_impl,
|
||||
upsample_primary=self.cfg.upsample_primary,
|
||||
left_pad_source=self.cfg.left_pad_source,
|
||||
left_pad_target=self.cfg.left_pad_target,
|
||||
max_source_positions=self.cfg.max_source_positions,
|
||||
max_target_positions=self.cfg.max_target_positions,
|
||||
truncate_source=self.cfg.truncate_source,
|
||||
)
|
||||
|
||||
def inject_noise(self, target_tokens):
|
||||
def _random_delete(target_tokens):
|
||||
pad = self.tgt_dict.pad()
|
||||
bos = self.tgt_dict.bos()
|
||||
eos = self.tgt_dict.eos()
|
||||
|
||||
max_len = target_tokens.size(1)
|
||||
target_mask = target_tokens.eq(pad)
|
||||
target_score = target_tokens.clone().float().uniform_()
|
||||
target_score.masked_fill_(
|
||||
target_tokens.eq(bos) | target_tokens.eq(eos), 0.0
|
||||
)
|
||||
target_score.masked_fill_(target_mask, 1)
|
||||
target_score, target_rank = target_score.sort(1)
|
||||
target_length = target_mask.size(1) - target_mask.float().sum(
|
||||
1, keepdim=True
|
||||
)
|
||||
|
||||
# do not delete <bos> and <eos> (we assign 0 score for them)
|
||||
target_cutoff = (
|
||||
2
|
||||
+ (
|
||||
(target_length - 2)
|
||||
* target_score.new_zeros(target_score.size(0), 1).uniform_()
|
||||
).long()
|
||||
)
|
||||
target_cutoff = target_score.sort(1)[1] >= target_cutoff
|
||||
|
||||
prev_target_tokens = (
|
||||
target_tokens.gather(1, target_rank)
|
||||
.masked_fill_(target_cutoff, pad)
|
||||
.gather(1, target_rank.masked_fill_(target_cutoff, max_len).sort(1)[1])
|
||||
)
|
||||
prev_target_tokens = prev_target_tokens[
|
||||
:, : prev_target_tokens.ne(pad).sum(1).max()
|
||||
]
|
||||
|
||||
return prev_target_tokens
|
||||
|
||||
def _random_mask(target_tokens):
|
||||
pad = self.tgt_dict.pad()
|
||||
bos = self.tgt_dict.bos()
|
||||
eos = self.tgt_dict.eos()
|
||||
unk = self.tgt_dict.unk()
|
||||
|
||||
target_masks = (
|
||||
target_tokens.ne(pad) & target_tokens.ne(bos) & target_tokens.ne(eos)
|
||||
)
|
||||
target_score = target_tokens.clone().float().uniform_()
|
||||
target_score.masked_fill_(~target_masks, 2.0)
|
||||
target_length = target_masks.sum(1).float()
|
||||
target_length = target_length * target_length.clone().uniform_()
|
||||
target_length = target_length + 1 # make sure to mask at least one token.
|
||||
|
||||
_, target_rank = target_score.sort(1)
|
||||
target_cutoff = new_arange(target_rank) < target_length[:, None].long()
|
||||
prev_target_tokens = target_tokens.masked_fill(
|
||||
target_cutoff.scatter(1, target_rank, target_cutoff), unk
|
||||
)
|
||||
return prev_target_tokens
|
||||
|
||||
def _full_mask(target_tokens):
|
||||
pad = self.tgt_dict.pad()
|
||||
bos = self.tgt_dict.bos()
|
||||
eos = self.tgt_dict.eos()
|
||||
unk = self.tgt_dict.unk()
|
||||
|
||||
target_mask = (
|
||||
target_tokens.eq(bos) | target_tokens.eq(eos) | target_tokens.eq(pad)
|
||||
)
|
||||
return target_tokens.masked_fill(~target_mask, unk)
|
||||
|
||||
def _block_mask(target_tokens):
|
||||
block_size = self.cfg.block_size
|
||||
pad = self.tgt_dict.pad()
|
||||
unk = self.tgt_dict.unk()
|
||||
target_masks = target_tokens.ne(pad)
|
||||
target_length = target_masks.sum(1).float()
|
||||
cutoff_length = target_length * target_length.clone().uniform_()
|
||||
cutoff_length = cutoff_length.int() + 1 # make sure to mask at least one token.
|
||||
prev_target_tokens = torch.ones((target_tokens.size(0),
|
||||
target_tokens.size(1) + block_size)).to(target_tokens)
|
||||
padded_target_tokens = torch.ones((target_tokens.size(0),
|
||||
target_tokens.size(1) + block_size)).to(target_tokens)
|
||||
for i in range(target_tokens.size(0)):
|
||||
remain_length = target_length[i].int() - cutoff_length[i]
|
||||
prev_target_tokens[i][:remain_length] = target_tokens[i][:remain_length]
|
||||
prev_target_tokens[i][remain_length:block_size + remain_length] = unk
|
||||
padded_target_tokens[i][:target_tokens.size(1)] = target_tokens[i]
|
||||
prev_target_tokens = prev_target_tokens[
|
||||
:, : prev_target_tokens.ne(pad).sum(1).max()
|
||||
]
|
||||
padded_target_tokens = padded_target_tokens[
|
||||
:, : prev_target_tokens.ne(pad).sum(1).max()
|
||||
]
|
||||
return prev_target_tokens, padded_target_tokens
|
||||
|
||||
if self.cfg.noise == "random_delete":
|
||||
return _random_delete(target_tokens)
|
||||
elif self.cfg.noise == "random_mask":
|
||||
return _random_mask(target_tokens)
|
||||
elif self.cfg.noise == "block_mask":
|
||||
return _block_mask(target_tokens)
|
||||
elif self.cfg.noise == "full_mask":
|
||||
return _full_mask(target_tokens)
|
||||
elif self.cfg.noise == "no_noise":
|
||||
return target_tokens
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def build_generator(self, models, args, **unused):
|
||||
# add models input to match the API for SequenceGenerator
|
||||
from fairseq.iterative_refinement_generator import IterativeRefinementGenerator
|
||||
|
||||
return IterativeRefinementGenerator(
|
||||
self.target_dictionary,
|
||||
eos_penalty=getattr(args, "iter_decode_eos_penalty", 0.0),
|
||||
max_iter=getattr(args, "iter_decode_max_iter", 10),
|
||||
beam_size=getattr(args, "iter_decode_with_beam", 1),
|
||||
reranking=getattr(args, "iter_decode_with_external_reranker", False),
|
||||
decoding_format=getattr(args, "decoding_format", None),
|
||||
adaptive=not getattr(args, "iter_decode_force_max_iter", False),
|
||||
retain_history=getattr(args, "retain_iter_history", False),
|
||||
)
|
||||
|
||||
def build_dataset_for_inference(self, src_tokens, src_lengths, constraints=None):
|
||||
if constraints is not None:
|
||||
# Though see Susanto et al. (ACL 2020): https://www.aclweb.org/anthology/2020.acl-main.325/
|
||||
raise NotImplementedError(
|
||||
"Constrained decoding with the translation_lev task is not supported"
|
||||
)
|
||||
|
||||
return LanguagePairDataset(
|
||||
src_tokens, src_lengths, self.source_dictionary, append_bos=False
|
||||
)
|
||||
|
||||
def train_step(
|
||||
self, sample, model, criterion, optimizer, update_num, ignore_grad=False
|
||||
):
|
||||
model.train()
|
||||
train_ratio = max(0, min(1, update_num / self.cfg.total_up))
|
||||
sample["glat"] = {"context_p": self.cfg.start_p - self.cfg.minus_p * train_ratio}
|
||||
sample["prev_target"], sample["target"] = self.inject_noise(sample["target"])
|
||||
with torch.autograd.profiler.record_function("forward"):
|
||||
loss, sample_size, logging_output = criterion(model, sample)
|
||||
if ignore_grad:
|
||||
loss *= 0
|
||||
with torch.autograd.profiler.record_function("backward"):
|
||||
optimizer.backward(loss)
|
||||
return loss, sample_size, logging_output
|
||||
|
||||
def valid_step(self, sample, model, criterion):
|
||||
model.eval()
|
||||
with torch.no_grad():
|
||||
sample["prev_target"], sample["target"] = self.inject_noise(sample["target"])
|
||||
loss, sample_size, logging_output = criterion(model, sample)
|
||||
EVAL_BLEU_ORDER = 4
|
||||
if self.cfg.eval_bleu:
|
||||
bleu = self._inference_with_bleu(self.sequence_generator, sample, model)
|
||||
logging_output["_bleu_sys_len"] = bleu.sys_len
|
||||
logging_output["_bleu_ref_len"] = bleu.ref_len
|
||||
# we split counts into separate entries so that they can be
|
||||
# summed efficiently across workers using fast-stat-sync
|
||||
assert len(bleu.counts) == EVAL_BLEU_ORDER
|
||||
for i in range(EVAL_BLEU_ORDER):
|
||||
logging_output["_bleu_counts_" + str(i)] = bleu.counts[i]
|
||||
logging_output["_bleu_totals_" + str(i)] = bleu.totals[i]
|
||||
return loss, sample_size, logging_output
|
||||
|
||||
def _inference_with_bleu(self, generator, sample, model):
|
||||
import sacrebleu
|
||||
|
||||
def decode(toks, escape_unk=False):
|
||||
s = self.tgt_dict.string(
|
||||
toks.int().cpu(),
|
||||
self.cfg.eval_bleu_remove_bpe,
|
||||
# The default unknown string in fairseq is `<unk>`, but
|
||||
# this is tokenized by sacrebleu as `< unk >`, inflating
|
||||
# BLEU scores. Instead, we use a somewhat more verbose
|
||||
# alternative that is unlikely to appear in the real
|
||||
# reference, but doesn't get split into multiple tokens.
|
||||
unk_string=("UNKNOWNTOKENINREF" if escape_unk else "UNKNOWNTOKENINHYP"),
|
||||
)
|
||||
if self.tokenizer:
|
||||
s = self.tokenizer.decode(s)
|
||||
return s
|
||||
|
||||
gen_out = self.inference_step(generator, [model], sample, prefix_tokens=None)
|
||||
hyps, refs = [], []
|
||||
for i in range(len(gen_out)):
|
||||
hyps.append(decode(gen_out[i][0]["tokens"]))
|
||||
refs.append(
|
||||
decode(
|
||||
utils.strip_pad(sample["target"][i], self.tgt_dict.pad()),
|
||||
escape_unk=True, # don't count <unk> as matches to the hypo
|
||||
)
|
||||
)
|
||||
if self.cfg.eval_bleu_print_samples:
|
||||
logger.info("example hypothesis: " + hyps[0])
|
||||
logger.info("example reference: " + refs[0])
|
||||
if self.cfg.eval_tokenized_bleu:
|
||||
return sacrebleu.corpus_bleu(hyps, [refs], tokenize="none")
|
||||
else:
|
||||
return sacrebleu.corpus_bleu(hyps, [refs])
|
||||
Reference in New Issue
Block a user