BERT: O Encoder que Lê dos Dois Lados
por Frank de Alcantara em 28/07/2026
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.
Índice da Série: Transformers
- 1. Você Pensa Como Fala
- 2. A Temida Matemática
- 3. A Probabilidade da Linguagem
- 4. A Vetorização Básica
- 5. Redes Neurais Artificiais para Word Embedding
- 6. Embeddings Distribuídos e CBoW
- 7. SkipGram e Otimizações do Word2Vec
- 8. Word2Vec, a Ponte para o Contexto
- 9. Desvendando a Modelagem de Sequências
- 10. Prestando Atenção
- 11. Do Código à Geração
- 12. Masked Language Modeling: Esconder Tokens para Aprender Contexto
- 13. BERT: O Encoder que Lê dos Dois Lados (Você está aqui)
- 14. O Cisma e a Batalha da Eficiência
- 15. GEMM o Coração Matemático
- 16. Do Cosseno à Borda: Busca Semântica em Produção
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:
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:
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.
Índice da Série: Transformers
- 1. Você Pensa Como Fala
- 2. A Temida Matemática
- 3. A Probabilidade da Linguagem
- 4. A Vetorização Básica
- 5. Redes Neurais Artificiais para Word Embedding
- 6. Embeddings Distribuídos e CBoW
- 7. SkipGram e Otimizações do Word2Vec
- 8. Word2Vec, a Ponte para o Contexto
- 9. Desvendando a Modelagem de Sequências
- 10. Prestando Atenção
- 11. Do Código à Geração
- 12. Masked Language Modeling: Esconder Tokens para Aprender Contexto
- 13. BERT: O Encoder que Lê dos Dois Lados (Você está aqui)
- 14. O Cisma e a Batalha da Eficiência
- 15. GEMM o Coração Matemático
- 16. Do Cosseno à Borda: Busca Semântica em Produção
(Updated: )