BERT: O Encoder que Lê dos Dois Lados

por Frank de Alcantara em 28/07/2026

BERT: O Encoder que Lê dos Dois Lados

O BERT não inventou a autoatenção, não inventou o Transformer e nem sequer inventou a ideia de pré-treinar uma rede sobre texto. Sua contribuição foi encaixar peças conhecidas numa receita em que uma mesma pilha de codificadores lê contexto dos dois lados, aprende com texto sem rótulos e depois aceita uma nova tarefa com alterações mínimas. Parece pouco quando enumerado depois do sucesso. Antes dele, era justamente a combinação que faltava.

No artigo sobre masked language modeling, construímos a tarefa que sustenta essa receita: escolher posições, corrompê-las pela regra 80/10/10 e minimizar a entropia cruzada apenas nos alvos selecionados. O BERT, de Bidirectional Encoder Representations from Transformers (representações bidirecionais de codificador baseadas em Transformers), coloca esse objetivo dentro de uma pilha de codificadores. Cada posição pode usar contexto à esquerda e à direita. Depois do pré-treino, retiramos os cabeçalhos originais, acrescentamos uma camada pequena e ajustamos a rede para classificação, reconhecimento de entidades, inferência textual ou resposta extrativa a perguntas.

O BERT não aprende uma resposta para cada tarefa. Aprende uma representação que aceita novas perguntas.

O artigo original de 2019 tornou popular uma receita que hoje parece óbvia porque funcionou bem demais: pré-treinar uma arquitetura geral sobre texto sem rótulos e adaptar os mesmos pesos a muitas tarefas. Para entender por que essa transferência funciona, precisamos separar quatro objetos que frequentemente aparecem colados: arquitetura, entrada, objetivo de pré-treino e cabeçalho posterior.

1. BERT é um codificador, não um objetivo

Uma camada de codificador recebe uma sequência $X\in\mathbb{R}^{L\times D}$, aplica autoatenção multi-cabeça, caminho residual, normalização e uma rede feed-forward. Empilhar $N$ camadas produz uma representação contextual $H\in\mathbb{R}^{L\times D}$:

\[H=\operatorname{EncoderStack}(X).\]

Cada linha $h_i$ depende da sequência inteira, exceto das posições removidas pela máscara de preenchimento. Não existe a máscara triangular causal que impede acesso ao futuro. A arquitetura-base já foi construída em Prestando Atenção e Do Código à Geração; o que o BERT acrescenta é uma receita concreta de entrada, escalas de modelo e pré-treinamento.

As duas configurações originais são:

Configuração Camadas $N$ Largura $D$ Cabeças Parâmetros
BERT Base 12 768 12 110 milhões
BERT Large 24 1024 16 340 milhões

No BERT Base, cada cabeça recebe dimensão $d_h=D/12=64$. No BERT Large, $d_h=1024/16=64$ novamente. Preservar a dimensão por cabeça mantém comparável a escala do produto interno, enquanto largura e profundidade aumentam a capacidade.

Chamar o BERT de “MLM” seria como chamar uma CPU de “multiplicação”. O objetivo é um trabalho que a arquitetura executa durante o pré-treino; a arquitetura continua útil depois que esse trabalho é removido.

2. Três embeddings formam cada entrada

O BERT recebe sequências de uma ou duas partes. A forma geral usa dois símbolos especiais:

\[[\texttt{CLS}],\quad \text{sequência A},\quad [\texttt{SEP}],\quad \text{sequência B},\quad [\texttt{SEP}].\]

[CLS] ocupa a posição inicial. O artigo original usa sua representação final $h_0$ como entrada de tarefas de classificação de sequência. [SEP] marca o fim de uma parte. Para cada posição $i$, o vetor de entrada soma três componentes:

\[x_i=e^{\mathrm{token}}_i+e^{\mathrm{segmento}}_i+e^{\mathrm{posição}}_i.\]

O embedding de token representa a subpalavra ou o símbolo especial. O embedding de segmento distingue a parte A da parte B. O embedding de posição distingue posições que, sem ele, seriam permutáveis para a autoatenção.

Tomemos a entrada

\[[\texttt{CLS}],\ \texttt{a},\ \texttt{gata},\ \texttt{dorme},\ [\texttt{SEP}],\ \texttt{ela},\ \texttt{ronrona},\ [\texttt{SEP}].\]

As cinco primeiras posições recebem segmento A; as três últimas, segmento B. As posições são $0,\ldots,7$. Se gata e ela aparecem em sentenças diferentes, seus vetores iniciais já carregam essa diferença de segmento e posição; depois das camadas, seus vetores contextuais incorporam relações com todos os demais tokens permitidos.

O BERT original usa WordPiece com vocabulário de $30\,000$ itens. A tokenização pode dividir uma palavra rara em subpalavras. O MLM prevê identificadores desse vocabulário, não palavras abstratas. Uma palavra dividida em três peças pode fornecer três alvos distintos, e versões posteriores estudaram mascaramento por palavra inteira e por intervalos para reduzir atalhos entre fragmentos vizinhos.

3. Bidirecionalidade é permissão, não garantia

Para uma cabeça de atenção, os scores são

\[S=\frac{QK^\top}{\sqrt{d_h}}+A,\]

na qual $A$ é uma matriz aditiva de máscara. No codificador do BERT, $A_{ij}=0$ quando a posição $j$ está disponível e $A_{ij}$ recebe um valor muito negativo quando $j$ é preenchimento. Não há bloqueio baseado em $j>i$.

No decodificador causal, acrescentamos a condição

\[A_{ij}= \begin{cases} 0,&j\le i,\\ -\infty,&j>i. \end{cases}\]

Essa é a diferença mecânica entre poder consultar os dois lados e consultar apenas o prefixo. Durante o treinamento de um modelo causal, todas as posições ainda podem ser processadas em paralelo, porque a máscara triangular impede vazamento. Durante a geração, porém, cada novo token depende dos anteriores e os passos tornam-se sequenciais. O BERT não possui esse laço de geração autorregressiva no uso normal.

Bidirecionalidade também não garante que cada cabeça use igualmente os dois lados, nem que a rede compreenda toda dependência. Ela apenas torna a informação acessível. O que será extraído depende do objetivo, dos dados, da otimização e da capacidade.

4. Os dois objetivos originais

O primeiro objetivo é o MLM do artigo anterior. O BERT escolhe $15\%$ dos tokens, aplica a corrupção 80/10/10 e calcula

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

O segundo é NSP, de next sentence prediction (previsão da próxima sentença). Metade dos exemplos usa uma sequência B que realmente sucede A no corpus; a outra metade usa uma sequência escolhida ao acaso. Um classificador sobre $h_0$, a representação final de [CLS], estima IsNext ou NotNext:

\[\mathcal{L}_{\mathrm{NSP}} =-\log p_\theta(y_{\mathrm{NSP}}\mid h_0).\]

O objetivo conjunto é

\[\mathcal{L} =\mathcal{L}_{\mathrm{MLM}}+\mathcal{L}_{\mathrm{NSP}}.\]

O NSP tentava ensinar relações entre sentenças úteis para inferência e perguntas. A história posterior exige cuidado. O RoBERTa removeu NSP e melhorou resultados ao mesmo tempo em que alterou dados, duração do treino, tamanho dos lotes e mascaramento. Isso demonstrou que NSP não era necessário naquela receita otimizada; não permite atribuir todo ganho a uma única mudança. O ALBERT substituiu NSP por uma tarefa de coerência de ordem entre segmentos. A conclusão segura é que MLM permaneceu central, enquanto a melhor forma de ensinar relações entre trechos continuou aberta.

O pré-treino original combinou BooksCorpus, com cerca de $800$ milhões de palavras, e a Wikipédia em inglês, com cerca de $2{,}5$ bilhões, totalizando aproximadamente $3{,}3$ bilhões de palavras. Esses números descrevem palavras no relatório do artigo, não tokens WordPiece nem exemplos independentes.

5. Ajuste fino: a mesma coluna, outro instrumento

Depois do pré-treino, removemos os cabeçalhos MLM e NSP e conectamos um cabeçalho adequado à tarefa. A pilha de codificadores é inicializada com os pesos aprendidos e normalmente ajustada junto com a nova camada.

Para classificação de sequência, um classificador recebe $h_0$:

\[\hat{y}=\operatorname{softmax}(Wh_0+b).\]

Para classificação por token, como reconhecimento de entidades, aplicamos a mesma projeção a cada $h_i$. Para resposta extrativa, duas projeções produzem logits de início e fim sobre as posições da passagem. A resposta é um intervalo do próprio texto, não uma frase gerada.

Essa uniformidade foi uma das forças do BERT: mudam o cabeçalho e os dados rotulados; a arquitetura principal permanece. No artigo original, o BERT Large atingiu novos melhores resultados em onze tarefas de NLP. Os números históricos importam menos do que a mudança metodológica que sobreviveria a eles: pré-treino amplo, depois adaptação pequena.

Há uma ressalva importante para este blog. O vetor [CLS] do BERT original não é automaticamente um bom embedding de sentença para comparação por cosseno. Ele foi treinado dentro das tarefas MLM e NSP e pode funcionar após ajuste para classificação, mas busca semântica eficiente pede geometria explícita entre sentenças. O Sentence-BERT introduziu redes siamesas e objetivos adequados a essa comparação. Foi essa linhagem que reencontramos em Do Cosseno à Borda.

6. Construindo a entrada em C++23

Antes do código, fixemos a operação. Para cada posição, somamos três vetores de mesma dimensão. Símbolos especiais participam como tokens normais, com identificadores e embeddings próprios. O programa completo abaixo usa dimensão $4$ apenas para tornar o resultado legível. Uma implementação real usa tabelas aprendidas e largura $768$ ou $1024$.

#include <array>
#include <cstddef>
#include <iomanip>
#include <iostream>
#include <span>
#include <string_view>
#include <vector>

using Vec = std::array<double, 4>;

constexpr Vec add(const Vec& a, const Vec& b, const Vec& c) {
    Vec result{};
    for (std::size_t d = 0; d < result.size(); ++d) {
        result[d] = a[d] + b[d] + c[d];
    }
    return result;
}

struct InputItem {
    std::string_view token;
    std::size_t token_id{};
    std::size_t segment_id{};
};

int main() {
    const std::array<InputItem, 8> input{{
        {"[CLS]", 0, 0}, {"a", 1, 0}, {"gata", 2, 0}, {"dorme", 3, 0},
        {"[SEP]", 4, 0}, {"ela", 5, 1}, {"ronrona", 6, 1}, {"[SEP]", 4, 1}
    }};

    const std::array<Vec, 7> token_embedding{{
        Vec{0.1, 0.0, 0.2, 0.0}, Vec{0.0, 0.1, 0.0, 0.2},
        Vec{0.3, 0.1, 0.0, 0.0}, Vec{0.0, 0.2, 0.3, 0.0},
        Vec{0.1, 0.1, 0.1, 0.1}, Vec{0.2, 0.0, 0.0, 0.3},
        Vec{0.0, 0.3, 0.1, 0.2}
    }};
    const std::array<Vec, 2> segment_embedding{{
        Vec{0.01, 0.01, 0.01, 0.01},
        Vec{-0.01, -0.01, -0.01, -0.01}
    }};
    const std::array<Vec, 8> position_embedding{{
        Vec{0.00, 0.00, 0.00, 0.00}, Vec{0.01, 0.00, 0.00, 0.00},
        Vec{0.02, 0.00, 0.00, 0.00}, Vec{0.03, 0.00, 0.00, 0.00},
        Vec{0.04, 0.00, 0.00, 0.00}, Vec{0.05, 0.00, 0.00, 0.00},
        Vec{0.06, 0.00, 0.00, 0.00}, Vec{0.07, 0.00, 0.00, 0.00}
    }};

    std::cout << std::fixed << std::setprecision(2);
    for (std::size_t i = 0; i < input.size(); ++i) {
        const auto vector = add(
            token_embedding[input[i].token_id],
            segment_embedding[input[i].segment_id],
            position_embedding[i]);
        std::cout << i << ' ' << input[i].token << " segmento="
                  << input[i].segment_id << " -> [";
        for (std::size_t d = 0; d < vector.size(); ++d) {
            std::cout << vector[d] << (d + 1 == vector.size() ? "]\n" : ", ");
        }
    }
}

O programa não contém autoatenção porque ela já foi implementada nos artigos anteriores. Seu papel é verificar um contrato frequentemente escondido por bibliotecas: token, posição e segmento entram por soma, não por concatenação. Portanto, o resultado preserva largura $D$ e pode seguir diretamente para a primeira camada.

No MSVC 19.51, compilamos com cl /std:c++latest /permissive- /W4 /EHsc /utf-8 /O2 bert_input.cpp. A primeira linha impressa é 0 [CLS] segmento=0 -> [0.11, 0.01, 0.21, 0.01]: o vetor do símbolo especial somado ao segmento A e à posição zero. O exemplo é deliberadamente pequeno, mas não há uma versão especial da soma quando $D=768$; há apenas mais componentes e tabelas aprendidas.

7. CPU, GPU e o preço da bidirecionalidade

O BERT processa as $L$ posições em paralelo dentro de cada camada, mas a atenção densa ainda forma relações entre todos os pares. Por camada, os dois produtos principais de atenção custam aproximadamente $4L^2D$ FLOPs, somando $QK^\top$ e o produto dos pesos por $V$. As projeções QKV, a projeção de saída e a FFN acrescentam termos proporcionais a $LD^2$.

Para BERT Base com $L=512$ e $D=768$, uma camada gasta aproximadamente $0{,}81$ bilhão de FLOPs somente nos dois produtos quadráticos:

\[4L^2D=4\cdot512^2\cdot768\approx8{,}05\times10^8.\]

Em CPUs, GEMMs e blocagem tentam reutilizar pesos e ativações nas caches. Na GPU, centenas de sequências e cabeças fornecem paralelismo, mas a matriz de atenção pressiona memória. A mesma ideia discutida em GEMM o Coração Matemático reaparece: reduzir movimento de dados pode valer mais do que economizar uma multiplicação isolada.

A diferença para um modelo causal não está no custo de uma passada de treinamento com o mesmo $L$ e $D$. Ambos podem executar as posições em paralelo, e a máscara triangular não elimina automaticamente as GEMMs densas. A diferença decisiva aparece no uso: o BERT codifica a entrada completa numa passada; um gerador causal produz novos tokens em passos dependentes, normalmente com cache KV.

8. Laboratório: quem pode olhar para quem

O laboratório permite alternar entre um codificador bidirecional e um decodificador causal. Passe o cursor sobre cada palavra. No BERT, posições dos dois lados estão disponíveis. No GPT, as posições posteriores ficam bloqueadas. A figura mostra permissões de atenção, não pesos aprendidos: uma aresta permitida pode receber peso quase zero depois do treinamento.

9. O caminho que sai do BERT

O BERT consolidou o codificador pré-treinado, mas sua receita não encerrou a discussão. O RoBERTa refinou dados e treinamento e retirou NSP. O ALBERT compartilhou parâmetros entre camadas e fatorou a tabela de embeddings. O ELECTRA trocou a previsão esparsa de tokens pela detecção de substituições em todas as posições. O Sentence-BERT alterou a geometria de sentenças para busca e comparação.

A linhagem também atravessou modalidades. O MAE levaria a ideia de esconder unidades para imagens, ocultando patches e reconstruindo pixels. Depois, na série Representações e Modelos de Mundo, o I-JEPA mascara regiões, mas prevê as representações dos alvos, não os pixels nem identificadores discretos.

Essa passagem exige uma distinção que agora podemos formular sem atalhos:

\[\text{BERT prevê tokens},\qquad \text{MAE reconstrói pixels},\qquad \text{I-JEPA prevê representações}.\]

Arquiteturas parecidas podem receber tarefas profundamente diferentes. E tarefas diferentes decidem o que a representação será punida por esquecer.

O BERT mostrou que esconder parte da entrada pode ensinar contexto. O passo seguinte é perguntar o que, exatamente, vale a pena reconstruir.

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.

LAN, Z. et al. ALBERT: A Lite BERT for Self-supervised Learning of Language Representations. ICLR, 2020. Disponível em: https://openreview.net/forum?id=H1eA7AEtvS. 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.

REIMERS, N.; GUREVYCH, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. Proceedings of EMNLP-IJCNLP 2019, p. 3982-3992, 2019. Disponível em: https://aclanthology.org/D19-1410/. 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: )