Masked Language Modeling: Esconder Tokens para Aprender Contexto

por Frank de Alcantara em 28/07/2026

Masked Language Modeling: Esconder Tokens para Aprender Contexto

Apague uma palavra no meio de uma frase e a leitora provavelmente tentará recuperá-la olhando o que veio antes e o que veio depois. Se escrevermos a leitora encontrou a ___ no contexto, a lacuna não pede uma continuação livre: pede uma hipótese compatível com a frase inteira. O MLM, de masked language modeling (modelagem de linguagem mascarada), transforma esse gesto banal numa tarefa de pré-treinamento repetida bilhões de vezes.

No artigo anterior sobre a arquitetura completa, montamos codificadores e decodificadores Transformer e vimos que uma máscara causal impede o decodificador de consultar posições futuras. Agora faremos uma escolha quase oposta. Permitiremos que um codificador consulte os dois lados, esconderemos alguns tokens e cobraremos a identidade original apenas nas posições escolhidas. O próprio texto fornece a pergunta e o gabarito. Há um professor econômico em algum lugar respirando aliviado.

O MLM aprende contexto retirando exatamente a informação que pedirá ao modelo para recuperar.

Este artigo é sobre a tarefa, não sobre uma arquitetura específica. O próximo artigo mostrará como o BERT combina MLM, um codificador Transformer e transferência para tarefas posteriores. Separar objetivo de arquitetura evita um erro comum: BERT usa MLM, mas MLM não é sinônimo de BERT.

1. Da previsão causal à lacuna no meio

Considere a sequência de tokens

\[x = [\texttt{a},\ \texttt{leitora},\ \texttt{encontra},\ \texttt{a},\ \texttt{pista},\ \texttt{no},\ \texttt{contexto}].\]

Um modelo causal que prediz o próximo token estima distribuições como

\[p(x_i\mid x_0,\ldots,x_{i-1}).\]

Para prever pista, ele pode usar a leitora encontra a, mas não no contexto, porque essa parte ainda pertence ao futuro. O MLM escolhe um conjunto de posições $M$ e estima cada token escolhido a partir da sequência corrompida $\tilde{x}$:

\[p(x_i\mid \tilde{x}), \qquad i\in M.\]

Se escolhermos a posição de pista, a entrada pode tornar-se

\[\tilde{x} = [\texttt{a},\ \texttt{leitora},\ \texttt{encontra},\ \texttt{a},\ \texttt{[MASK]},\ \texttt{no},\ \texttt{contexto}].\]

Agora a e no contexto ajudam ao mesmo tempo. O objetivo não pede que a rede gere a frase da esquerda para a direita. Pede que reconheça qual item do vocabulário completa uma posição usando contexto bidirecional. Essa diferença muda o tipo de representação que o treinamento favorece.

2. Escolher uma posição não basta

O BERT original seleciona $15\%$ das posições de tokens para previsão. Se toda posição escolhida fosse sempre substituída por [MASK], o pré-treino apresentaria um símbolo artificial que não aparece durante o uso normal do modelo. A rede poderia aprender a depender dessa placa luminosa: “há um buraco exatamente aqui”. O ajuste fino, porém, receberia texto sem a placa.

Para reduzir esse descompasso, o procedimento do BERT faz três coisas entre as posições escolhidas:

  1. Em $80\%$ delas, troca o token por [MASK].
  2. Em $10\%$, troca o token por um item sorteado do vocabulário.
  3. Nos $10\%$ restantes, conserva o token original.

Essas porcentagens são condicionais à seleção. Sobre todos os tokens elegíveis, as frações esperadas são

\[0{,}15\cdot0{,}80=0{,}12,\qquad 0{,}15\cdot0{,}10=0{,}015,\qquad 0{,}15\cdot0{,}10=0{,}015.\]

Portanto, em média, $12\%$ de todas as posições viram [MASK], $1{,}5\%$ recebem um item aleatório e $1{,}5\%$ permanecem visíveis apesar de continuarem pertencendo ao alvo. A soma retorna os $15\%$ selecionados.

Por que manter alguns tokens e ainda cobrar sua previsão? Porque o modelo não recebe uma marca externa dizendo quais posições entrarão na perda. Para acertar também os casos preservados, precisa produzir uma representação contextual útil em todas as posições. Por que usar substituições aleatórias? Porque elas ensinam a desconfiar de um item plausível no formato, mas incompatível com o contexto. Nenhuma das duas escolhas elimina completamente a diferença entre pré-treino e uso; ambas a tornam menos grosseira.

Em sequências curtas, a realização observada não precisa conter exatamente $80\%$, $10\%$ e $10\%$. Se apenas duas posições forem selecionadas, não existe como produzir $1{,}6$ máscaras. As proporções aparecem quando acumulamos muitas seleções.

3. A perda mora apenas nas posições escolhidas

Seja $V$ o tamanho do vocabulário. Para cada posição $i$, o codificador produz um vetor contextual $h_i\in\mathbb{R}^D$. Uma projeção de saída transforma $h_i$ em um vetor de logits $z_i\in\mathbb{R}^V$. A probabilidade do item $v$ naquela posição é

\[p_\theta(v\mid\tilde{x},i)= \frac{\exp(z_{iv})}{\sum_{u=0}^{V-1}\exp(z_{iu})}.\]

Com $M$ como conjunto das posições escolhidas, a perda MLM é a entropia cruzada média apenas sobre elas:

\[\mathcal{L}_{\mathrm{MLM}} =-\frac{1}{|M|} \sum_{i\in M} \log p_\theta(x_i\mid\tilde{x},i).\]

As posições fora de $M$ atravessam o codificador e influenciam a atenção, mas não recebem um termo direto nessa soma. Elas são contexto. As posições em $M$ são contexto corrompido e alvo supervisionado ao mesmo tempo.

Um exemplo com quatro candidatos torna a conta visível. Suponha que, na posição ocultada, os logits para [pista, janela, livro, ruído] sejam

\[z=[2,\ 1,\ 0,\ -1].\]

Subtrair o máximo antes da exponenciação não muda a softmax e evita estouro:

\[z-\max(z)=[0,\ -1,\ -2,\ -3].\]

A probabilidade atribuída a pista é

\[p(\texttt{pista})= \frac{1}{1+e^{-1}+e^{-2}+e^{-3}} \approx 0{,}644,\]

e a perda dessa posição é

\[-\log 0{,}644\approx0{,}440.\]

Se a rede elevasse o logit correto sem alterar os demais, a probabilidade subiria e a perda cairia. A geometria da atualização é a mesma entropia cruzada que construímos em A Probabilidade da Linguagem; mudou o conjunto de posições às quais ela se aplica.

4. Bidirecional não significa sem máscara

É tentador dizer que o MLM “não usa máscara de atenção”. A frase é imprecisa. O codificador do BERT não usa a máscara triangular causal que bloqueia o futuro, mas ainda pode usar uma máscara de preenchimento para impedir atenção sobre [PAD]. Além disso, [MASK] é um token de corrupção na entrada, não a matriz aditiva aplicada aos scores de atenção.

Convém separar os dois objetos:

\[\text{token [MASK]} \neq \text{máscara de atenção}.\]

O primeiro ocupa uma posição da sequência e possui um embedding aprendido. A segunda altera quais pares de posições podem trocar informação antes da softmax. Misturar os dois porque ambos se chamam “máscara” é uma pequena vingança da terminologia contra a leitora apressada.

No MLM bidirecional, a representação $h_i$ pode depender de posições à esquerda e à direita. Isso só é válido porque a identidade original de $x_i$ foi removida ou perturbada na maioria dos casos selecionados. Se entregássemos o token original em todas as posições-alvo, a tarefa admitiria a solução trivial de copiá-lo pela conexão residual.

5. Uma implementação C++23 da corrupção e da perda

O programa abaixo reproduz as duas partes matemáticas que acabamos de definir. Primeiro seleciona posições elegíveis e aplica a regra 80/10/10. Depois calcula a entropia cruzada estável de uma posição mascarada. Não implementa um Transformer; implementa o contrato de dados e a função de perda que qualquer Transformer MLM precisa respeitar.

#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <random>
#include <span>
#include <stdexcept>
#include <string>
#include <vector>

enum class Corruption { mask, random_token, unchanged };

struct SelectedToken {
    std::size_t position{};
    std::string original;
    std::string observed;
    Corruption kind{};
};

std::vector<std::size_t> choose_positions(
    const std::size_t token_count,
    const double selection_rate,
    std::mt19937& rng) {
    if (token_count == 0) {
        return {};
    }

    std::vector<std::size_t> positions(token_count);
    std::iota(positions.begin(), positions.end(), std::size_t{0});
    std::shuffle(positions.begin(), positions.end(), rng);

    const auto selected = std::min(
        token_count,
        std::max<std::size_t>(
            1,
            static_cast<std::size_t>(
                std::llround(selection_rate * token_count))));
    positions.resize(selected);
    std::sort(positions.begin(), positions.end());
    return positions;
}

std::vector<SelectedToken> corrupt(
    const std::span<const std::string> tokens,
    const std::span<const std::string> vocabulary,
    std::mt19937& rng) {
    if (vocabulary.empty()) {
        throw std::invalid_argument("o vocabulário não pode ser vazio");
    }

    std::uniform_real_distribution<double> unit(0.0, 1.0);
    std::uniform_int_distribution<std::size_t> vocabulary_index(
        0, vocabulary.size() - 1);

    std::vector<SelectedToken> result;
    for (const auto position : choose_positions(tokens.size(), 0.15, rng)) {
        const double draw = unit(rng);
        if (draw < 0.80) {
            result.push_back(
                {position, tokens[position], "[MASK]", Corruption::mask});
        } else if (draw < 0.90) {
            result.push_back({
                position,
                tokens[position],
                vocabulary[vocabulary_index(rng)],
                Corruption::random_token});
        } else {
            result.push_back({
                position,
                tokens[position],
                tokens[position],
                Corruption::unchanged});
        }
    }
    return result;
}

double cross_entropy(
    const std::span<const double> logits,
    const std::size_t target) {
    if (logits.empty() || target >= logits.size()) {
        throw std::invalid_argument("alvo ou logits inválidos");
    }

    const double maximum = *std::max_element(logits.begin(), logits.end());
    double denominator = 0.0;
    for (const double logit : logits) {
        denominator += std::exp(logit - maximum);
    }
    const double log_probability =
        logits[target] - maximum - std::log(denominator);
    return -log_probability;
}

int main() {
    const std::array<std::string, 10> tokens{
        "a", "leitora", "atenta", "encontra", "a",
        "pista", "correta", "no", "próprio", "contexto"};
    const std::array<std::string, 8> vocabulary{
        "livro", "modelo", "janela", "vetor",
        "máscara", "ruído", "frase", "pista"};

    std::mt19937 rng{19};
    std::cout << std::fixed << std::setprecision(6);
    for (const auto& item : corrupt(tokens, vocabulary, rng)) {
        std::cout << "posição " << item.position
                  << ": observado=" << item.observed
                  << ", alvo=" << item.original << '\n';
    }

    const std::array logits{2.0, 1.0, 0.0, -1.0};
    std::cout << "perda=" << cross_entropy(logits, 0) << '\n';
}

O programa foi escrito para C++23 e usa somente a biblioteca padrão. Em sequências não vazias, choose_positions seleciona ao menos uma posição e limita o resultado ao tamanho da entrada; essa convenção torna exemplos curtos observáveis, mas não substitui a política de arredondamento de um pipeline real. A seleção exclui símbolos especiais no laboratório interativo, enquanto o exemplo C++ recebe apenas os dez tokens elegíveis. A preparação real também preserva alinhamento entre subpalavras, posições, segmentos e rótulos ignorados.

No MSVC 19.51, compilamos com cl /std:c++latest /permissive- /W4 /EHsc /utf-8 /O2 mlm.cpp. A saída depende da semente da corrupção, mas a última linha deve informar perda=0.440190, reproduzindo a conta da Seção 3.

6. Onde o custo realmente aparece

Selecionar $15\%$ das posições não reduz o custo do codificador para $15\%$. Todos os $L$ tokens atravessam todas as camadas, pois as posições não escolhidas fornecem contexto. Para largura $D$, uma camada de atenção densa contém termos proporcionais a $LD^2$ nas projeções e a $L^2D$ nos produtos de atenção. O mascaramento muda o alvo, não encurta a sequência.

A economia aparece na projeção final sobre o vocabulário. Em vez de calcular logits para todas as $BL$ posições de um lote, podemos reunir apenas as $M\approx0{,}15BL$ posições escolhidas e multiplicar uma matriz $M\times D$ por uma matriz $D\times V$. Em uma GPU, essa reunião deve produzir acesso contíguo antes da GEMM; lançar uma multiplicação minúscula por posição desperdiçaria o paralelismo.

Tomemos os números do BERT Base: $B=32$, $L=512$, $D=768$ e vocabulário $V=30\,522$. O lote contém $16\,384$ posições, das quais esperamos

\[M=0{,}15\cdot16\,384\approx2\,458.\]

Projetar somente essas posições produz cerca de $75$ milhões de logits. Em FP32, materializá-los exige aproximadamente $300$ MB. A GEMM realiza cerca de

\[2MDV\approx115\text{ bilhões de FLOPs}.\]

Projetar todas as posições multiplicaria esses valores por aproximadamente $1/0{,}15\approx6{,}67$. O cabeçalho de saída não é gratuito, especialmente com vocabulário grande. A mesma ideia, em outra hierarquia: reunir posições mascaradas reduz trabalho aritmético, enquanto blocagem e GEMM otimizada mantêm os dados reutilizados na memória rápida.

7. Laboratório: a regra em movimento

O laboratório abaixo aplica a seleção e a corrupção a uma frase curta. A semente torna cada realização reproduzível. Altere a fração escolhida e gere várias corrupções. Em poucas posições, a proporção observada oscila; acumulada sobre muitos lotes, ela converge para a regra 80/10/10. Repare também nos tokens verdes: continuam visíveis, mas entram na perda.

8. O que o MLM aprende, e o que não promete

O MLM força cada representação a reunir evidências dos dois lados. Isso é especialmente útil para tarefas em que a entrada inteira está disponível, como classificação, reconhecimento de entidades e resposta extrativa a perguntas. Não transforma automaticamente o vetor de uma sentença num bom embedding para busca por cosseno. Essa propriedade exige outro objetivo ou ajuste, como veremos novamente em Do Cosseno à Borda.

O objetivo também é esparso. Apenas as posições escolhidas fornecem rótulos de vocabulário em cada exemplo. O ELECTRA explorou esse limite substituindo tokens e treinando um discriminador em todas as posições. O RoBERTa mostrou que alterar dados, duração, lotes, mascaramento dinâmico e o uso de NSP melhora substancialmente a receita. Esses trabalhos não tornam o MLM irrelevante; mostram que a tarefa possui decisões de engenharia e limitações mensuráveis.

No próximo artigo, colocaremos essa tarefa dentro de uma arquitetura concreta. Veremos o BERT Base com $12$ camadas, largura $768$ e $12$ cabeças; construiremos [CLS], [SEP], embeddings de segmento e posição; e separaremos o que pertence ao codificador, ao MLM, ao objetivo de próxima sentença e ao ajuste fino.

O MLM não ensina a rede a continuar uma frase. Ensina cada posição a responder pelo contexto inteiro.

Acrônimos e Abreviações neste artigo

A seguir está a lista de todos os acrônimos e abreviações identificados no texto, organizados em ordem alfabética com o termo original em inglês e a tradução para o português:

Acrônimo / Abreviação Definição em Inglês Tradução em Português
BLAS Basic Linear Algebra Subprograms Subprogramas Básicos de Álgebra Linear
CPU / CPUs Central Processing Unit Unidade Central de Processamento
FLOPs Floating Point Operations Operações de Ponto Flutuante
FP32 32-bit Floating Point Ponto Flutuante de 32 bits
GEMM General Matrix Multiply Multiplicação Geral de Matrizes
GPU Graphics Processing Unit Unidade de Processamento Gráfico
IA Artificial Intelligence Inteligência Artificial
I-JEPA Image Joint-Embedding Predictive Architecture Arquitetura Preditiva de Incorporação Conjunta de Imagem
JEPA Joint-Embedding Predictive Architecture Arquitetura Preditiva de Incorporação Conjunta
KiB Kibibyte Kibibyte
MAE Masked Autoencoder Autocodificador Mascarado
MSE Mean Squared Error Erro Quadrático Médio
MSVC Microsoft Visual C++ Microsoft Visual C++
PCA Principal Component Analysis Análise de Componentes Principais
SIMD Single Instruction, Multiple Data Instrução Única, Múltiplos Dados
SimCLR Simple Framework for Contrastive Learning of Visual Representations Estrutura Simples para Aprendizado Contrastivo de Representações Visuais

Referências

CLARK, K.; LUONG, M.-T.; LE, Q. V.; MANNING, C. D. ELECTRA: Pre-training Text Encoders as Discriminators Rather Than Generators. ICLR, 2020. Disponível em: https://openreview.net/forum?id=r1xMH1BtvB. Acesso em: 28 jul. 2026.

DEVLIN, J.; CHANG, M.-W.; LEE, K.; TOUTANOVA, K. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. Proceedings of NAACL-HLT 2019, p. 4171-4186, 2019. Disponível em: https://aclanthology.org/N19-1423/. Acesso em: 28 jul. 2026.

LIU, Y. et al. RoBERTa: A Robustly Optimized BERT Pretraining Approach. 2019. Disponível em: https://arxiv.org/abs/1907.11692. Acesso em: 28 jul. 2026.

VASWANI, A. et al. Attention Is All You Need. Advances in Neural Information Processing Systems, v. 30, 2017. Disponível em: https://arxiv.org/abs/1706.03762. Acesso em: 28 jul. 2026.

(Updated: )