Pedro's dev blog

BERT: Understanding Language with Deep Bidirectionality

Published on
Published on
/9 mins read/---

BERT: Understanding Language with Deep Bidirectionality

BERT, que significa Bidirectional Encoder Representations from Transformers, é um modelo de representação de linguagem desenvolvido pelo Google AI e apresentado em 2018 (artigo original). Ele causou um impacto sísmico no campo do PLN, estabelecendo novos recordes em uma vasta gama de tarefas de compreensão de linguagem.

A principal inovação do BERT não foi criar uma nova arquitetura do zero, mas sim propor uma maneira inteligente de pré-treinar um modelo baseado na arquitetura Encoder do Transformer, de forma que ele aprendesse representações contextuais profundas e bidirecionais da linguagem.

What Does "Bidirectional" Mean and Why Does It Matter?

Modelos anteriores, como as primeiras versões do GPT ou [[LSTMs]] tradicionais, eram geralmente unidirecionais. Ao processar uma palavra, eles só consideravam o contexto que vinha antes dela (da esquerda para a direita) ou, em algumas variantes (como BiLSTMs), combinavam representações separadas da esquerda para a direita e da direita para a esquerda.

BERT, usando o mecanismo de Self-Attention do Transformer Encoder, consegue considerar simultaneamente o contexto à esquerda e à direita de uma palavra em todas as suas camadas.

Analogia: Imagine ler uma frase com uma palavra faltando: "Eu fui ao ______ comprar pão." Para adivinhar a palavra que falta (provavelmente "mercado" ou "padaria"), você olha tanto para "Eu fui ao" quanto para "comprar pão". Você usa o contexto dos dois lados! BERT faz isso de forma nativa e profunda.

Essa capacidade bidirecional permite que o BERT capture nuances da linguagem que eram mais difíceis para modelos unidirecionais.

Architecture: Pure Transformer Encoder

Diferente do Transformer original (Encoder-Decoder), BERT utiliza apenas a pilha de Encoders.

  • Não há Decoder. O objetivo principal do BERT não é gerar texto (como o GPT) ou traduzir (como o Transformer original), mas sim gerar representações numéricas (embeddings) ricas em contexto para palavras e frases.
  • Ele empilha várias camadas de Encoder Transformer (ex: BERT-Base tem 12 camadas, BERT-Large tem 24). Cada camada contém:
    • Multi-Head Self-Attention
    • Add & Norm
    • Position-wise Feed-Forward Network
    • Add & Norm
graph TD
    Input[Input Embeddings<br/>(Token + Segment + Position)] --> Layer1;
 
    subgraph BERT Encoder Stack (N Layers)
        direction TB
        subgraph Encoder Layer 1
            Layer1 --> MHA1(Multi-Head Self-Attention);
            MHA1 --> AddNorm1_1(Add & Norm);
            AddNorm1_1 --> FFN1(Feed-Forward);
            FFN1 --> AddNorm1_2(Add & Norm);
        end
        AddNorm1_2 --> Layer2(...) --> LayerN;
    end
 
    LayerN --> Output[Contextualized Word Embeddings];
 
    style Input fill:#f9f, stroke:#333
    style Output fill:#ccf, stroke:#333
    style MHA1 fill:#9cf, stroke:#333
    style FFN1 fill:#9fc, stroke:#333

Training: Pre-training and Fine-Tuning

A genialidade do BERT reside em seu processo de treinamento em duas etapas:

1. Pre-training

Nesta fase, o BERT é treinado em uma quantidade massiva de texto não rotulado (como toda a Wikipédia e grandes coleções de livros). O objetivo é fazer o modelo aprender a "entender" a estrutura e o significado da linguagem de forma geral. Isso é feito através de duas tarefas auto-supervisionadas (não precisam de rótulos humanos):

a) Masked Language Model (MLM)

  • Como funciona: Antes de alimentar uma sequência de texto no BERT, cerca de 15% das palavras (tokens) são substituídas aleatoriamente por um token especial [MASK], ou por outra palavra aleatória, ou mantidas como estão (com uma certa probabilidade para cada caso).
  • Objetivo: O modelo deve prever qual era a palavra original que foi mascarada, usando apenas o contexto das palavras não mascaradas ao redor.
  • Por que é importante: Isso força o BERT a usar o contexto bidirecional. Para adivinhar a palavra em [MASK], ele precisa olhar para as palavras antes e depois.

Exemplo Simples: Entrada: O [MASK] rápido [MASK] sobre o cão preguiçoso. Saída Esperada: Prever "gato" para o primeiro [MASK] e "pulou" para o segundo [MASK].

graph LR
    subgraph MLM Task
        Input("O [MASK] rápido [MASK] sobre o cão preguiçoso.") --> BERT;
        BERT --> P1(Previsão para 1º [MASK]);
        BERT --> P2(Previsão para 2º [MASK]);
        P1 -- Comparar --> T1("gato");
        P2 -- Comparar --> T2("pulou");
        T1 -- Calcular Erro --> Loss(Função de Perda);
        T2 -- Calcular Erro --> Loss;
        Loss -- Backpropagate --> BERT(Ajustar Pesos);
    end
    style Input fill:#f9f
    style T1 fill:#9f9
    style T2 fill:#9f9

b) Next Sentence Prediction (NSP)

  • Como funciona: O modelo recebe pares de frases (A e B) e deve prever se a frase B é a frase que realmente segue a frase A no texto original (rótulo IsNext) ou se é apenas uma frase aleatória do corpus (rótulo NotNext).
  • Objetivo: Ensinar o BERT a entender as relações entre frases, o que é crucial para tarefas como Question Answering (QA) e Natural Language Inference (NLI).
  • Como é usado: A representação do token especial [CLS] (veja abaixo) é usada para fazer essa previsão binária.

Exemplo:

  • Par 1: (A: "Abri a geladeira.", B: "Peguei o leite.") -> IsNext
  • Par 2: (A: "Abri a geladeira.", B: "O sol brilha forte.") -> NotNext

Nota: Pesquisas posteriores (como RoBERTa) questionaram a real necessidade e eficácia do NSP, mas ele foi parte fundamental do BERT original.

2. Fine-Tuning

Após o pré-treinamento, o BERT já possui um conhecimento profundo da linguagem. Para usá-lo em uma tarefa específica (como análise de sentimento, classificação de texto, resposta a perguntas), fazemos o seguinte:

  1. Adicionar uma Camada: Adicionamos uma pequena camada de saída específica para a tarefa no topo da arquitetura BERT pré-treinada.
  2. Treinar com Dados Rotulados: Treinamos todo o modelo (ou apenas a camada adicionada) usando dados rotulados específicos para a tarefa desejada.
  3. Convergência Rápida: Como o BERT já está pré-treinado, o fine-tuning geralmente requer muito menos dados rotulados e converge muito mais rápido do que treinar um modelo do zero.
graph TD
    subgraph Fine-Tuning Process
        direction LR
        InputData[Dados Rotulados da Tarefa Específica] --> FineTuneBERT;
        PreTrainedBERT[BERT Pré-treinado] --> FineTuneBERT{BERT + Camada Específica};
        FineTuneBERT --> Output[Resultados da Tarefa<br/>(Ex: Sentimento Positivo/Negativo)];
    end
 
    style PreTrainedBERT fill:#lightblue
    style InputData fill:#lightyellow
    style Output fill:#lightgreen

Input Representation

Para que o BERT funcione corretamente, a entrada precisa ser formatada de uma maneira específica:

  1. Tokenização: O texto é dividido em tokens usando WordPiece ou SentencePiece (que podem quebrar palavras em sub-palavras, ex: "jogando" -> "jog", "##ando").
  2. Tokens Especiais:
    • [CLS]: Adicionado no início de cada sequência. Sua representação final (output embedding) é frequentemente usada como a representação agregada de toda a sequência para tarefas de classificação.
    • [SEP]: Adicionado para separar frases (no caso de tarefas com pares de frases como NSP ou QA) ou no final de uma única sequência.
  3. Embeddings: Cada token de entrada é representado pela soma de três embeddings:
    • Token Embeddings: O embedding do próprio token (palavra/sub-palavra).
    • Segment Embeddings: Um embedding que indica se o token pertence à primeira frase (A) ou à segunda frase (B) em tarefas que usam pares de frases.
    • Position Embeddings: Um embedding aprendido que indica a posição do token na sequência (diferente das funções seno/cosseno do Transformer original).
graph TD
    subgraph Input Representation for BERT
        direction TB
        InputText("Frase A [SEP] Frase B") --> Tokenizer;
        Tokenizer --> Tokens("[CLS] tokA1 tokA2 [SEP] tokB1 tokB2 [SEP]");
 
        Tokens -- Para cada Token --> Sum(Soma);
 
        subgraph Embeddings
            TokEmb(Token Embeddings<br/>Ex: 'tokA1') --> Sum;
            SegEmb(Segment Embeddings<br/>Ex: 'Segmento A') --> Sum;
            PosEmb(Position Embeddings<br/>Ex: 'Posição 1') --> Sum;
        end
 
        Sum --> FinalInputVector[Vetor de Entrada para BERT];
    end
    style FinalInputVector fill:#f9f

Example: Question Answering

Imagine a tarefa de responder perguntas sobre um texto (como o dataset SQuAD).

Contexto: "A Floresta Amazônica é a maior floresta tropical do mundo, cobrindo a maior parte da Bacia Amazônica da América do Sul. A bacia abrange 7.000.000 km², dos quais 5.500.000 km² são cobertos pela floresta tropical."

Pergunta: "Qual a área coberta pela floresta tropical?"

Como BERT abordaria isso (no fine-tuning):

  1. Entrada Formatada:

    [CLS] Qual a área coberta pela floresta tropical? [SEP] A Floresta Amazônica é a maior ... pela floresta tropical. [SEP]
    

    (Aqui, a Pergunta é o Segmento A e o Contexto é o Segmento B).

  2. Processamento BERT: O modelo processa essa entrada concatenada, usando a atenção bidirecional para entender as relações entre a pergunta e o contexto.

  3. Camada de Saída QA: Durante o fine-tuning para QA, duas pequenas redes neurais são adicionadas sobre os embeddings de saída do BERT:

    • Uma para prever a probabilidade de cada token do contexto ser o início da resposta.
    • Outra para prever a probabilidade de cada token do contexto ser o fim da resposta.
  4. Previsão: O modelo provavelmente atribuiria altas probabilidades de "início" ao token "5.500.000" e altas probabilidades de "fim" ao token "km²" (ou tokens correspondentes após a tokenização WordPiece).

  5. Resultado: A resposta extraída seria "5.500.000 km²".

Advantages

  • Compreensão Contextual Profunda: A bidirecionalidade real permite um entendimento mais rico das nuances da linguagem.
  • Transfer Learning Poderoso: O paradigma pré-treinamento/fine-tuning é extremamente eficaz, permitindo ótimos resultados em diversas tarefas com menos dados rotulados.
  • Performance State-of-the-Art: Estabeleceu novos padrões em muitos benchmarks de PLN quando foi lançado.
  • Versatilidade: Aplicável a uma ampla gama de tarefas de compreensão de linguagem (classificação, QA, NER, etc.).

Limitations

  • Custo Computacional: O pré-treinamento é extremamente caro e demorado, exigindo hardware massivo (TPUs/GPUs).
  • Discrepância Pré-treino/Fine-tuning: O token [MASK] usado no MLM não aparece durante o fine-tuning, criando uma pequena diferença entre as fases.
  • Não Ideal para Geração: Por ser baseado apenas no Encoder, não é naturalmente adequado para tarefas de geração de texto livre (como GPT).
  • NSP Debatido: A utilidade da tarefa NSP foi questionada e removida em modelos posteriores como RoBERTa e ALBERT, muitas vezes sem perda de performance.
  • Independência das Previsões Mascaradas: Ao prever múltiplos tokens mascarados em uma sequência, o BERT os prevê independentemente, sem modelar a relação entre eles (diferente de modelos autorregressivos).

Conclusion

BERT foi um marco fundamental na evolução do PLN. Ao aplicar de forma inteligente a arquitetura Encoder do Transformer e introduzir as tarefas de pré-treinamento MLM e NSP, ele demonstrou o poder das representações de linguagem bidirecionais profundas e popularizou maciçamente a abordagem de pré-treinamento e fine-tuning, que se tornou o padrão para muitos modelos de linguagem modernos. Embora modelos mais recentes tenham surgido, o impacto e os conceitos introduzidos por BERT continuam a influenciar o campo da Inteligência Artificial.

ConnectionsFull graph →