← Back to DevBytes

Token Masking Strategies in Pre-training vs Fine-Tuning

Introduction to Token Masking

Token masking is a foundational technique in modern Natural Language Processing (NLP), particularly within the realm of self-supervised learning. At its core, token masking involves taking a sequence of text, hiding a certain percentage of the tokens (words or subwords), and training a model to predict the hidden tokens based on the surrounding context. While this concept is universally understood, the strategy for applying token masking shifts dramatically depending on whether a model is in the pre-training phase or the fine-tuning phase.

What is Token Masking?

In the context of models like BERT (Bidirectional Encoder Representations from Transformers), token masking is the primary mechanism of Masked Language Modeling (MLM). A specific token in an input sequence is replaced by a special [MASK] token. The model processes the entire sequence and outputs a probability distribution over the vocabulary for the masked position, attempting to guess the original token. This forces the model to learn deep contextual representations of language.

Why Token Masking Matters

Token masking matters because it creates a dense, bidirectional learning signal. Unlike autoregressive models (like GPT) that only predict the next word in a sequence, masked models see the context on both the left and right sides of the target token. This results in representations that are highly effective for understanding tasks, such as sentiment analysis, named entity recognition, and question answering. However, the way masking is applied during pre-training versus fine-tuning serves entirely different purposes and requires distinct strategies.

Token Masking in Pre-training

During pre-training, the goal is to teach the model the general structure, grammar, and semantics of a language using massive, unlabeled corpora. Token masking is the primary objective function. The model learns by reconstructing the corrupted input.

Common Pre-training Strategies

The original BERT paper introduced a specific masking strategy that has become the industry standard. For a given sequence, 15% of tokens are selected for masking. However, replacing all 15% with the [MASK] token creates a mismatch between pre-training and fine-tuning, as the [MASK] token never appears in downstream real-world tasks. To mitigate this, the 15% selected tokens are processed as follows:

Other pre-training strategies include Span Masking (masking contiguous blocks of tokens rather than individual ones, as used in SpanBERT) and Dynamic Masking (applying masking dynamically during data loading rather than statically preprocessing the dataset, as used in RoBERTa).

How to Implement Pre-training Masking

Below is a PyTorch implementation of the BERT-style masking strategy. This function takes a batch of input IDs and returns the masked inputs alongside the labels (where unmasked tokens are ignored via a -100 index).

import torch

def apply_bert_masking(input_ids, mask_token_id, vocab_size, mask_prob=0.15):
    """
    Applies BERT-style masking to a batch of input_ids.
    """
    labels = input_ids.clone()
    
    # Create a probability matrix for masking
    probability_matrix = torch.full(input_ids.shape, mask_prob)
    masked_indices = torch.bernoulli(probability_matrix).bool()
    
    # We only compute loss on masked tokens
    labels[~masked_indices] = -100 

    # 80% of the time, replace with [MASK] token
    prob_replace = torch.full(input_ids.shape, 0.8)
    indices_replaced = torch.bernoulli(prob_replace).bool() & masked_indices
    input_ids[indices_replaced] = mask_token_id

    # 10% of the time, replace with random word
    prob_random = torch.full(input_ids.shape, 0.5) # 0.5 * 0.2 = 10% of total
    indices_random = torch.bernoulli(prob_random).bool() & masked_indices & ~indices_replaced
    random_words = torch.randint(vocab_size, input_ids.shape, dtype=torch.long)
    input_ids[indices_random] = random_words[indices_random]

    # 10% of the time, keep the original word (do nothing)
    
    return input_ids, labels

# Example usage:
# input_ids = torch.tensor([[101, 2023, 2003, 1037, 3231, 102]])
# mask_token_id = 103
# vocab_size = 30522
# masked_inputs, labels = apply_bert_masking(input_ids, mask_token_id, vocab_size)

Token Masking in Fine-Tuning

When transitioning to fine-tuning, the objective shifts from general language understanding to a specific downstream task, such as text classification or sequence labeling. Consequently, the role of token masking changes entirely.

Why Masking Changes During Fine-Tuning

In standard fine-tuning, token masking is typically disabled. If you are training a model to classify the sentiment of a movie review, you need the model to see the entire, uncorrupted review to make an accurate prediction. Introducing [MASK] tokens during fine-tuning would remove critical information required for the classification task, degrading model performance.

However, there are specific scenarios where masking is intentionally used during fine-tuning, primarily as a form of data augmentation or regularization. By randomly masking a small percentage of tokens in the training data, you force the model to rely on broader context rather than overfitting to specific trigger words. This is particularly useful in domains with limited labeled data or when training models to be robust against typos and missing words.

How to Use Masking for Fine-Tuning Augmentation

If you choose to use token masking during fine-tuning, the strategy is much simpler than in pre-training. You typically use a lower masking probability (e.g., 5% to 10%) and replace the selected tokens entirely with the [MASK] token, omitting the random replacement and unchanged rules.

from transformers import AutoTokenizer
import random

tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def augment_text_for_finetuning(text, mask_prob=0.1):
    """
    Applies simple token masking as data augmentation for fine-tuning.
    """
    tokens = tokenizer.tokenize(text)
    augmented_tokens = []
    
    for token in tokens:
        # Do not mask special tokens like [CLS] or [SEP]
        if token in ["[CLS]", "[SEP]", "[PAD]"]:
            augmented_tokens.append(token)
        elif random.random() < mask_prob:
            augmented_tokens.append("[MASK]")
        else:
            augmented_tokens.append(token)
            
    return tokenizer.convert_tokens_to_string(augmented_tokens)

# Example usage:
original_text = "The quick brown fox jumps over the lazy dog."
augmented_text = augment_text_for_finetuning(original_text, mask_prob=0.15)

print(f"Original: {original_text}")
print(f"Augmented: {augmented_text}")
# Output will vary, e.g.: "the quick [MASK] fox jumps over the lazy dog."

Best Practices for Token Masking

To effectively leverage token masking across the model lifecycle, consider the following best practices:

Conclusion

Token masking is a powerful tool that serves distinct purposes across the lifecycle of a language model. During pre-training, complex masking strategies like the 80/10/10 rule are essential for building robust, bidirectional contextual representations without creating train-time artifacts. During fine-tuning, masking is generally removed to preserve task-critical information, but it can be selectively reintroduced as a lightweight data augmentation technique to improve model generalization. By understanding and correctly implementing these differing strategies, developers can maximize both the foundational knowledge and the task-specific performance of their NLP models.

— Ad —

Google AdSense will appear here after approval

← Back to all articles