🛠️ MÓDULO 6: Projeto Guiado — Construa Seu Primeiro Sistema de QA (Question Answering)

Duração estimada deste módulo: 1.5 - 2 horas
Objetivo: Construir um sistema completo de question answering extrativo que recebe um texto de contexto e uma pergunta, e retorna a resposta mais provável extraída diretamente do texto.
Requisitos: Apenas o que você aprendeu nos módulos anteriores + um editor de código (ou Google Colab).


Lição 6.1 — O Que É QA Extrativo? A Diferença Entre "Gerar" e "Extrair"

Antes de começar, vamos esclarecer o tipo de QA que construiremos.

🔹 QA Gerativo:
O modelo inventa uma nova resposta, com suas próprias palavras.

Pergunta: "O que é um Transformer?"
Resposta Gerada: "Um Transformer é uma arquitetura de rede neural que usa mecanismos de atenção para processar sequências..."

🔹 QA Extrativo (o que faremos):
O modelo extrai um fragmento literal do texto de contexto.

Pergunta: "O que é um Transformer?"
Contexto: "...o Transformer, introduzido em 2017, é uma arquitetura baseada em atenção que processa todas as palavras simultaneamente..."
Resposta Extraída: "uma arquitetura baseada em atenção que processa todas as palavras simultaneamente"

Vantagens do QA Extrativo:

  • Não requer fine-tuning (existem modelos pré-treinados excelentes).
  • A resposta é sempre fiel ao texto fonte (não alucina).
  • Ideal para técnicos, jurídicos, manuais, artigos, etc.

Lição 6.2 — Escolhendo o Modelo: Nosso Aliado Será "deepset/roberta-base-squad2"

Para este projeto, usaremos um modelo de tipo encoder-only, especificamente treinado para QA extrativo.

🔹 Modelo Escolhido: deepset/roberta-base-squad2

  • Baseado em RoBERTa (uma variante aprimorada do BERT).
  • Treinado no SQuAD 2.0 (um dataset de QA com perguntas que às vezes não têm resposta).
  • Suporta português razoavelmente bem (embora tenha sido treinado principalmente em inglês).
  • Leve (base), roda suavemente na CPU.

🌐 Você pode vê-lo no Model Hub


Lição 6.3 — Passo 1: Configuração Inicial e Carregamento do Modelo

Primeiro, instale (se ainda não tiver feito) e carregue o modelo e o tokenizer.

from transformers import AutoTokenizer, AutoModelForQuestionAnswering, pipeline

# Nome do modelo
model_name = "deepset/roberta-base-squad2"

# Carregar tokenizer e modelo
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForQuestionAnswering.from_pretrained(model_name)

# Opcional: criar um pipeline para simplificar
qa_pipeline = pipeline("question-answering", model=model, tokenizer=tokenizer)

Lição 6.4 — Passo 2: Preparar o Contexto e a Pergunta

Usaremos um texto de exemplo. Pode ser qualquer coisa: um artigo, um trecho de livro, um manual, etc.

context = """
Transformers são uma arquitetura de rede neural introduzida em 2017 por Vaswani et al. 
no artigo "Attention Is All You Need". Ao contrário de redes recorrentes, 
Transformers processam todas as palavras de uma sequência simultaneamente, 
usando um mecanismo chamado "atenção" que permite a cada palavra se relacionar 
com qualquer outra na frase. Essa arquitetura é a base de modelos como 
BERT, GPT, T5 e muitos outros que dominam hoje o processamento de linguagem natural.
"""

question = "Que mecanismo os Transformers usam para relacionar palavras?"

Lição 6.5 — Passo 3: Usar o Pipeline (A Forma Mais Fácil)

result = qa_pipeline(question=question, context=context)

print("Pergunta:", question)
print("Resposta:", result['answer'])
print("Score:", result['score'])
print("Início:", result['start'])
print("Fim:", result['end'])

Saída esperada:

Pergunta: Que mecanismo os Transformers usam para relacionar palavras?
Resposta: atenção
Score: 0.9321
Início: 234
Fim: 242

Funciona! O modelo extraiu a palavra "atenção" como resposta.


Lição 6.6 — Passo 4: Fazer Manualmente (Para Entender o Processo)

Agora, vamos fazer passo a passo, como no Módulo 5, para ver o que acontece internamente.

# Tokenizar pergunta + contexto (juntos)
inputs = tokenizer(question, context, return_tensors="pt", truncation=True)

# Passar pelo modelo
outputs = model(**inputs)

# Obter logits de início e fim da resposta
start_logits = outputs.start_logits
end_logits = outputs.end_logits

# Encontrar as posições com maior probabilidade
start_index = torch.argmax(start_logits)
end_index = torch.argmax(end_logits)

# Converter os tokens de volta para texto
answer_tokens = inputs.input_ids[0][start_index:end_index + 1]
answer = tokenizer.decode(answer_tokens)

print("Resposta (manual):", answer)

Saída:

Resposta (manual): atenção

🔹 O que o modelo faz?
Prediz duas coisas:

  • A posição (token) onde a resposta começa.
  • A posição (token) onde a resposta termina.

Depois, extrai todos os tokens entre essas duas posições.


Lição 6.7 — Passo 5: Melhorar a Robustez — Lidar com Múltiplos Candidatos

Às vezes, o modelo pode errar se só pegar o start_index e end_index com maior pontuação. Uma prática melhor é considerar combinações válidas (start <= end) e escolher a de maior pontuação combinada.

import torch

def get_best_answer(start_logits, end_logits, input_ids, tokenizer, top_k=5):
    # Obter top_k índices para início e fim
    start_probs, start_indices = torch.topk(start_logits, top_k)
    end_probs, end_indices = torch.topk(end_logits, top_k)

    best_score = -float('inf')
    best_answer = ""

    # Testar combinações válidas
    for i in range(top_k):
        for j in range(top_k):
            start = start_indices[i].item()
            end = end_indices[j].item()
            if start <= end:  # válido
                score = (start_probs[i] + end_probs[j]).item()
                if score > best_score:
                    best_score = score
                    answer_tokens = input_ids[0][start:end+1]
                    best_answer = tokenizer.decode(answer_tokens, skip_special_tokens=True)

    return best_answer, best_score

# Usar a função
answer, score = get_best_answer(start_logits, end_logits, inputs.input_ids, tokenizer)
print("Melhor resposta:", answer)
print("Melhor score:", score)

Isso torna o sistema mais robusto contra erros ocasionais.


Lição 6.8 — Passo 6: Testar com Diferentes Contextos e Perguntas

Agora é sua vez de experimentar! Tente:

context2 = """
A inteligência artificial generativa permite criar novo conteúdo: texto, imagens, música, código. 
Modelos como DALL-E, Stable Diffusion e GPT-4 são exemplos populares. 
Esses modelos aprendem padrões de grandes conjuntos de dados e depois geram saídas originais 
baseadas em prompts ou instruções dadas pelo usuário.
"""

question2 = "Que tipo de conteúdo a IA generativa pode criar?"
result2 = qa_pipeline(question=question2, context=context2)
print(result2['answer'])  # Esperado: "texto, imagens, música, código"

Ou em português:

context_pt = """
São Paulo é uma cidade localizada na região sudeste do Brasil. 
É conhecida por sua arquitetura diversificada, especialmente as obras de Oscar Niemeyer, 
como o Edifício Copan e o Conjunto Nacional. Também é famosa por sua culinária, 
parques e vida noturna vibrante.
"""

question_pt = "Que arquiteto é famoso em São Paulo?"
result_pt = qa_pipeline(question=question_pt, context=context_pt)
print(result_pt['answer'])  # Esperado: "Oscar Niemeyer"

Lição 6.9 — Passo 7: Limitações e Como Superá-las

🔹 Limitação 1: O modelo só pode responder se a resposta estiver no texto.

Se você perguntar "Qual é a capital da França?" e o texto não mencionar Paris, o modelo pode inventar algo ou dar uma resposta errada.

🔹 Solução:

  • Use o parâmetro handle_impossible_answer=True (se o modelo suportar, como SQuAD 2.0).
  • Ou filtre por score: se o score for baixo (< 0.1), responda "Não sei" ou "Não está no texto."
if result['score'] < 0.1:
    print("Não encontrei uma resposta confiável no texto.")
else:
    print("Resposta:", result['answer'])

🔹 Limitação 2: O contexto tem limite de comprimento (~512 tokens).

Se o texto for muito longo, é truncado e informações são perdidas.

🔹 Solução:

  • Divida o texto em pedaços sobrepostos.
  • Faça a pergunta em cada pedaço.
  • Escolha a resposta com maior score.

✍️ Exercício de Reflexão 6.1

Pegue um artigo da Wikipédia (ou um capítulo de livro) que você goste.
Copie 3-4 parágrafos como contexto.
Formule 5 perguntas diferentes (fáceis, difíceis, ambíguas).
Execute o sistema QA e avalie:

  • Quantas respostas estão corretas?
  • Onde ele falha?
  • Como poderia melhorá-lo?

📊 Diagrama Conceitual 6.1 — Fluxo do Sistema QA (descrito)

[Pergunta + Contexto] → Tokenizer → input_ids → Modelo QA → start_logits + end_logits → 
       ↑                   ↑             ↑               ↑
   Texto plano       Converte em    IDs de tokens    Prediz posições
                     tokens +      (pergunta +      de início e fim
                     separadores    contexto)

→ Seleciona melhor combinação início-fim → Extrai tokens → Decodifica → [Resposta final]

🧠 Conclusão do Módulo 6

Parabéns! Você acabou de construir um sistema funcional de inteligência artificial, baseado em um dos modelos mais avançados do mundo (Transformer), sem treinar nada, sem GPUs caras e em menos de 50 linhas de código.

Esse sistema pode ser a base de:

  • Um chatbot para manuais técnicos.
  • Um assistente para ler artigos científicos.
  • Um tutor que responde perguntas sobre um livro.

E o melhor: agora você sabe como ele funciona por dentro! Não é uma caixa preta. Você sabe o que é um embedding, o que a atenção faz, como a posição é codificada e como o modelo escolhe a resposta.


Course Info

Course: AI-course2

Language: PT

Lesson: Module6