Denoising Objectives › BART-Style Combined Denoising

Michael BrenndoerferUpdated July 14, 20255 min read
Language AI HandbookAll chapters
Part 22 of 67Pre-training Objectives
Chapter 161Denoising Objectives › BART-Style Combined Denoising
Reading section 8 of 15 · BART-Style Combined Denoising · 5 minSection 8 of 15 · 5 min

Book contents

Section 8 of 15 · 5 min read

Denoising Objectives › BART-Style Combined DenoisingOne section · Sections in this chapter8 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

BART combines multiple corruption types to create a versatile denoising objective. The original BART paper explored five transformations:

  • Token masking: Replace tokens with [MASK] (similar to BERT)
  • Token deletion: Remove tokens entirely
  • Text infilling: Replace spans with single [MASK] tokens
  • Sentence permutation: Shuffle sentence order
  • Document rotation: Rotate document around random point

The best-performing configuration used text infilling as the primary corruption, combined with sentence permutation. This combination forces the model to learn both local (word-level) and global (document-level) structure. Text infilling trains the decoder to generate variable-length continuations that fill in masked spans, which is a direct precursor to the generation capability needed for summarization. Sentence permutation trains the encoder to build representations that capture inter-sentence relationships, which supports tasks like coherence scoring and multi-document reasoning. Together, they ensure that neither local nor global language understanding is neglected during pretraining.

In[28]:
Code
class BARTCorruptor:
    """
    Apply BART-style denoising corruption to text.

    Combines multiple corruption strategies:
    - Text infilling (spans replaced with single mask)
    - Sentence permutation (reorder sentences)
    """

    def __init__(
        self,
        mask_prob=0.30,
        mean_span_length=3,
        permute_sentences=True,
        mask_token="[MASK]",
    ):
        self.mask_prob = mask_prob
        self.mean_span_length = mean_span_length
        self.permute_sentences = permute_sentences
        self.mask_token = mask_token

    def _split_sentences(self, tokens):
        """Split token list into sentences (simple period-based)."""
        sentences = []
        current = []

        for token in tokens:
            current.append(token)
            if (
                token.endswith(".")
                or token.endswith("!")
                or token.endswith("?")
            ):
                sentences.append(current)
                current = []

        if current:
            sentences.append(current)

        return sentences

    def _infill_tokens(self, tokens):
        """Apply text infilling to token list."""
        n = len(tokens)
        if n == 0:
            return tokens

        num_to_mask = max(1, int(n * self.mask_prob))

        # Sample span lengths
        p = 1 / self.mean_span_length
        mask = np.zeros(n, dtype=bool)
        span_positions = []
        total_masked = 0

        attempts = 0
        while total_masked < num_to_mask and attempts < 100:
            attempts += 1
            span_length = np.random.geometric(p)
            span_length = min(span_length, num_to_mask - total_masked, n)

            valid_starts = [
                i
                for i in range(n - span_length + 1)
                if not any(mask[i : i + span_length])
            ]

            if not valid_starts:
                continue

            start = np.random.choice(valid_starts)
            mask[start : start + span_length] = True
            span_positions.append((start, start + span_length))
            total_masked += span_length

        span_positions.sort()

        # Build result
        result = []
        last_end = 0
        for start, end in span_positions:
            result.extend(tokens[last_end:start])
            result.append(self.mask_token)
            last_end = end
        result.extend(tokens[last_end:])

        return result

    def corrupt(self, tokens):
        """
        Apply BART-style corruption.

        Args:
            tokens: List of string tokens

        Returns:
            dict with 'input' (corrupted) and 'target' (original) token lists
        """
        working_tokens = list(tokens)

        # Step 1: Sentence permutation (if enabled and multiple sentences)
        if self.permute_sentences:
            sentences = self._split_sentences(working_tokens)
            if len(sentences) > 1:
                order = np.random.permutation(len(sentences))
                working_tokens = []
                for i in order:
                    working_tokens.extend(sentences[i])

        # Step 2: Text infilling
        corrupted = self._infill_tokens(working_tokens)

        return {"input": corrupted, "target": list(tokens)}
In[29]:
Code
# Demonstrate BART corruption
document = """The transformer changed NLP. Attention is all you need.
Models became larger. Performance improved dramatically."""
tokens = document.split()

corruptor = BARTCorruptor(
    mask_prob=0.30, mean_span_length=3, permute_sentences=True
)
result = corruptor.corrupt(tokens)
Out[30]:
Console
Original:
The transformer changed NLP. Attention is all you need. Models became larger. Performance improved dramatically.

Corrupted (13 tokens):
Attention is all [MASK] need. Models became larger. The transformer changed [MASK] dramatically.

Compression: 15 -> 13 tokens (13% reduction)

The corrupted version demonstrates both transformations working together. Sentences appear in a different order, and multiple spans have been replaced with [MASK] tokens. The sequence is noticeably shorter because each masked span, regardless of how many tokens it contained, becomes a single placeholder. This compression is a key efficiency advantage of text infilling: the encoder processes fewer tokens while the decoder still generates the full original.

Out[31]:
Visualization
Bar chart comparing input/output length ratios for different corruption types, showing infilling has the lowest ratio around 0.75.
Encoder input length as a fraction of original document length for each corruption strategy. Token shuffling and sentence permutation preserve length (ratio = 1.0), while deletion and infilling reduce the encoder's input. Text infilling achieves the greatest compression because multiple tokens collapse into single mask placeholders, reducing the quadratic attention cost for the encoder while the decoder still learns to generate the full sequence.

The compression from text infilling provides computational savings during training. The encoder processes shorter sequences, reducing the quadratic attention cost, while the decoder still learns to generate the full original sequence. This asymmetry is intentional: it makes encoder computation cheap while keeping decoder training rich. For long documents, where encoder self-attention over the full sequence would be prohibitively expensive, this compression increases the number of training examples the model can process per hour.