Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Implementar testar_ab(pergunta, referencia) com LLM-as-judge cego
Implementar analisar_overfitting(scores_treino, scores_valid) detectando gap treino/validação
Calcular ROUGE-L com LCS dinâmica para avaliação automática
Completar os 2 TODOs do starter Python
Interpretar resultados do harness de avaliação para decidir se fine-tuning vale a pena
Por que isso importa
“O modelo ficou melhor” não é uma avaliação — é uma opinião. ROUGE-L quantifica a sobreposição de texto com referência. LLM-as-judge avalia qualidade semântica. A/B test cego remove o viés de quem desenvolveu o modelo. Análise de overfitting identifica quando o modelo decorou o dataset de treino mas não generaliza.
Conceitos Fundamentais
TODO 1: testar_ab(pergunta, referencia)
def testar_ab(pergunta: str, referencia: str) -> dict:
# 1. Gerar respostas de ambos os modelos
resp_base = gerar_resposta_base(pergunta)
resp_ft = gerar_resposta_ft(pergunta)
# 2. Pedir ao Claude para escolher a melhor (sem revelar qual é qual)
# Randomizar ordem para evitar viés posicional
import random
opcoes = [('A', resp_base, 'base'), ('B', resp_ft, 'ft')]
if random.random() > 0.5:
opcoes = [('A', resp_ft, 'ft'), ('B', resp_base, 'base')]
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=200,
messages=[{
"role": "user",
"content": f"""Pergunta: {pergunta}
Referência esperada: {referencia}
Resposta A: {opcoes[0][1][:300]}
Resposta B: {opcoes[1][1][:300]}
Qual resposta é mais precisa tecnicamente e mais próxima da referência?
Responda APENAS com: A, B, ou EMPATE, seguido de uma justificativa de 1 linha.
Exemplo: "A: mais completa e técnica"
""",
}],
)
texto = response.content[0].text if response.content[0].type == "text" else "EMPATE"
primeira_letra = texto.strip()[0].upper() if texto.strip() else 'E'
vencedor_letra = primeira_letra if primeira_letra in ('A', 'B') else 'EMPATE'
if vencedor_letra == 'EMPATE':
vencedor_modelo = 'empate'
else:
# Descobrir qual modelo corresponde à letra vencedora
idx_vencedor = 0 if vencedor_letra == 'A' else 1
vencedor_modelo = opcoes[idx_vencedor][2]
return {
"vencedor": vencedor_modelo,
"justificativa": texto.strip(),
"resp_base_len": len(resp_base.split()),
"resp_ft_len": len(resp_ft.split()),
}TODO 2: analisar_overfitting(scores_treino, scores_valid)
def analisar_overfitting(scores_treino: list[float], scores_valid: list[float]) -> dict:
if not scores_treino or not scores_valid:
return {"overfitting": False, "gap": 0.0, "recomendacao": "Dados insuficientes"}
# 1. Calcular médias
media_treino = sum(scores_treino) / len(scores_treino)
media_valid = sum(scores_valid) / len(scores_valid)
gap = media_treino - media_valid
# 2. Analisar tendência nos últimos N checkpoints
def tendencia(scores: list[float]) -> str:
if len(scores) < 3:
return "insuficiente"
recentes = scores[-3:]
if recentes[-1] > recentes[0] * 1.01:
return "melhorando"
elif recentes[-1] < recentes[0] * 0.99:
return "piorando"
return "estagnando"
tendencia_treino = tendencia(scores_treino)
tendencia_valid = tendencia(scores_valid)
# 3. Detectar overfitting: gap > 2.0 pontos
overfitting = gap > 2.0
# 4. Recomendação
if overfitting:
recomendacao = (
"Overfitting detectado. Ações: (1) Reduzir n_epochs, "
"(2) Adicionar exemplos de validação ao dataset, "
"(3) Aumentar regularização (weight_decay), "
"(4) Usar early stopping"
)
elif tendencia_valid == "piorando":
recomendacao = "Validação piorando — possível overfitting iminente. Considere early stopping."
elif tendencia_valid == "melhorando":
recomendacao = "Modelo generalizando bem. Continue treinamento ou avalie com mais épocas."
else:
recomendacao = "Treinamento estável. Avalie qualidade com golden set."
return {
"overfitting": overfitting,
"gap": round(gap, 2),
"media_treino": round(media_treino, 2),
"media_valid": round(media_valid, 2),
"tendencia_treino": tendencia_treino,
"tendencia_valid": tendencia_valid,
"recomendacao": recomendacao,
}Aprofundamento Técnico
ROUGE-L: Métrica Baseada em LCS
# ROUGE-L usa Longest Common Subsequence (LCS) — não precisa ser contíguo
# Exemplo:
# Hipótese: "modelos de linguagem processam tokens sequencialmente"
# Referência: "modelos processam sequências de tokens"
# LCS: "modelos" "processam" "tokens" → 3 palavras comuns
# ROUGE-L = F1 de precision e recall do LCS
# Precision = |LCS| / len(hipótese)
# Recall = |LCS| / len(referência)
# F1 = 2 × Precision × Recall / (Precision + Recall)
# Valores típicos:
# ROUGE-L > 0.5: boa sobreposição
# ROUGE-L > 0.3: aceitável para geração livre
# ROUGE-L < 0.2: respostas muito diferentes da referênciaLLM-as-Judge: Vantagens e Limitações
Vantagens:
✅ Avalia semântica, não apenas sobreposição de palavras
✅ Captura qualidade de raciocínio
✅ Escala para milhares de avaliações
Limitações:
❌ Juiz pode ter viés por comprimento (respostas longas parecem mais completas)
❌ Juiz pode preferir estilo similar ao próprio treinamento
❌ Não funciona bem para tarefas com única resposta correta (matemática)Exemplos Anotados
Exemplo 1: A/B Test com randomização
resultado = testar_ab(
"O que é temperatura em LLMs?",
"Temperature controla aleatoriedade: 0=determinístico, 1=mais variado."
)
# → {
# "vencedor": "ft", ← FT geralmente mais conciso e técnico
# "justificativa": "A: direta, técnica, inclui exemplo numérico",
# "resp_base_len": 87,
# "resp_ft_len": 23, ← fine-tuned é 4x mais conciso
# }Exemplo 2: Detecção de Overfitting
scores_treino = [6.2, 7.1, 7.8, 8.3, 8.9, 9.2, 9.5] # subindo sempre
scores_valid = [6.1, 6.8, 7.2, 7.3, 7.1, 6.8, 6.5] # piorou depois de época 3
resultado = analisar_overfitting(scores_treino, scores_valid)
# → {"overfitting": True, "gap": 3.0, "recomendacao": "Overfitting detectado. Reduzir n_epochs..."}
# Melhor checkpoint estava na época 3 (score_valid=7.2)Padrões e Armadilhas
Padrões
Padrão 1: Golden set fora do treino O golden set deve ser separado do dataset de treino ANTES de qualquer treinamento.
Padrão 2: Múltiplas métricas ROUGE-L para cobertura + LLM-judge para qualidade + A/B test para comparação.
Armadilhas
⚠️ Armadilha 1: A/B test sem randomização da ordem
# ERRADO: sempre apresentar base como "A"
# LLMs tendem a preferir a primeira opção apresentada
# CORRETO: randomizar qual modelo aparece como A/B⚠️ Armadilha 2: Gap de 2.0 pontos como threshold fixo
# Ajuste o threshold conforme a escala:
# Score 1-10: gap > 1.5 = overfitting
# Score 0-1: gap > 0.15 = overfitting⚠️ Armadilha 3: LLM-judge com modelo igual ao modelo avaliado
Avaliar gpt-4o-mini FT com gpt-4o-mini base → juiz pode ter viés
Use Claude como juiz para modelos OpenAI, e vice-versaSe não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
TODO 1 — testar_ab(pergunta, referencia): gere resp_base e resp_ft, randomize ordem, peça ao LLM “A, B, ou EMPATE + justificativa”, mapeie letra vencedora de volta para modelo.
TODO 2 — analisar_overfitting(scores_treino, scores_valid): calcule gap = media_treino - media_valid, overfitting se gap > 2.0, analise tendência dos últimos 3 pontos.
Agora você está pronto para o lab.