Construindo pipelines de dados para fine-tuning de LLMs

1. Fundamentos do Fine-Tuning e a Importância dos Dados

1.1 Diferença entre fine-tuning, RAG e prompting

Fine-tuning, Retrieval-Augmented Generation (RAG) e prompting são técnicas complementares, mas com aplicações distintas. O prompting ajusta o comportamento do modelo via instruções no contexto, sem alterar pesos. RAG combina recuperação de informações externas com geração, ideal para conhecimento dinâmico. Fine-tuning modifica os pesos do modelo através de treinamento adicional em datasets específicos. Use fine-tuning quando precisar de especialização profunda em domínios (ex.: terminologia médica), consistência de formato (ex.: saída JSON estruturada) ou redução de latência em produção.

1.2 Características essenciais de um dataset de alta qualidade

Um dataset para fine-tuning deve ser:
- Representativo: cobrir a distribuição real de entradas esperadas
- Consistente: exemplos com formatação uniforme e labels sem ambiguidade
- Diverso: evitar viés de repetição de padrões
- Limpo: livre de ruídos, duplicatas e conteúdo tóxico
- Balanceado: classes e domínios proporcionais à relevância

1.3 Visão geral do pipeline de dados

O pipeline típico segue: coleta → limpeza → formatação → validação → versionamento. Cada etapa é crítica; um erro na limpeza pode propagar vieses para o modelo final.

2. Coleta e Curadoria de Dados para Fine-Tuning

2.1 Fontes de dados

Fontes públicas como Common Crawl e The Pile oferecem escala, mas exigem filtragem intensa. Dados proprietários (logs de chat, bases de conhecimento internas) são mais relevantes, porém sensíveis. Dados sintéticos, gerados por LLMs maiores, permitem criar exemplos sob medida.

2.2 Estratégias de amostragem

Para balanceamento, use amostragem estratificada por domínio. Exemplo prático:

# Amostragem estratificada com pandas
import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.read_csv('raw_data.csv')
train, val = train_test_split(df, test_size=0.1, stratify=df['domain'])
print(f'Train: {len(train)}, Val: {len(val)}')

2.3 Remoção de ruídos

Filtros baseados em perplexidade (modelos como KenLM) identificam texto anômalo. Use também detectores de conteúdo tóxico (ex.: detoxify) e remoção de duplicatas exatas.

3. Pré-processamento e Limpeza de Texto

3.1 Normalização de texto

Remova tags HTML, padronize Unicode (NFKC) e corrija codificações quebradas:

import unicodedata
import re

def normalize_text(text):
    text = unicodedata.normalize('NFKC', text)
    text = re.sub(r'<[^>]+>', '', text)  # Remove HTML
    text = re.sub(r'\s+', ' ', text).strip()
    return text

3.2 Deduplicação

MinHash com Locality-Sensitive Hashing (LSH) é eficiente para grandes volumes:

from datasketch import MinHash, MinHashLSH

def deduplicate(documents, threshold=0.8):
    lsh = MinHashLSH(threshold=threshold)
    for idx, doc in enumerate(documents):
        m = MinHash()
        for word in set(doc.split()):
            m.update(word.encode('utf8'))
        lsh.insert(f'doc_{idx}', m)
    return lsh

3.3 Filtragem por idioma e comprimento

Use langdetect para filtrar idioma alvo e descarte documentos com menos de 50 tokens:

from langdetect import detect

def filter_language(text, target_lang='en'):
    try:
        return detect(text) == target_lang
    except:
        return False

4. Formatação de Dados para Fine-Tuning Supervisionado (SFT)

4.1 Estruturas de conversação

O formato Alpaca-style é comum para instruções:

{"instruction": "Explique o conceito de fine-tuning.",
 "input": "",
 "output": "Fine-tuning é o processo de ajustar os pesos de um modelo pré-treinado..."}

Para chat, use template ChatML:

<|im_start|>user
O que é fine-tuning?<|im_end|>
<|im_start|>assistant
Fine-tuning é...<|im_end|>

4.2 Tokenização e padding

Tokenize com padding à direita e truncamento à esquerda (preserva início do contexto):

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-2")
tokenizer.pad_token = tokenizer.eos_token

def tokenize_example(example):
    return tokenizer(
        example["text"],
        truncation=True,
        padding="max_length",
        max_length=512
    )

4.3 Tratamento de exemplos longos

Para documentos extensos, use sliding window com overlap:

def chunk_text(text, chunk_size=512, overlap=50):
    tokens = tokenizer.encode(text)
    chunks = []
    for i in range(0, len(tokens), chunk_size - overlap):
        chunk = tokens[i:i + chunk_size]
        chunks.append(tokenizer.decode(chunk))
    return chunks

5. Geração e Aumento de Dados Sintéticos

5.1 Técnicas de geração

Self-instruct gera pares instrução-resposta a partir de seeds. Exemplo com API:

import openai

def generate_example(seed_instruction):
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "Gere uma resposta detalhada para a instrução."},
            {"role": "user", "content": seed_instruction}
        ]
    )
    return {"instruction": seed_instruction, "output": response.choices[0].message.content}

5.2 Controle de qualidade

Valide consistência gerando múltiplas respostas e medindo similaridade (BLEU, ROUGE). Revisão humana em amostra de 10% é recomendada.

5.3 Evitando overfitting

Diversifique prompts com parafraseamento e introduza adversarial training (ex.: trocar rótulos em 5% dos exemplos para testar robustez).

6. Versionamento, Armazenamento e Orquestração do Pipeline

6.1 Ferramentas de versionamento

DVC (Data Version Control) rastreia datasets como código:

dvc add data/fine_tuning_dataset.parquet
git add data/fine_tuning_dataset.parquet.dvc
git commit -m "Adiciona v1 do dataset de fine-tuning"

Hugging Face Datasets permite versionamento nativo com push_to_hub.

6.2 Orquestração

Apache Airflow gerencia pipelines complexos:

from airflow import DAG
from airflow.operators.python import PythonOperator

with DAG('data_pipeline', schedule_interval='@weekly') as dag:
    collect = PythonOperator(task_id='collect_data', python_callable=collect_data)
    clean = PythonOperator(task_id='clean_data', python_callable=clean_data)
    validate = PythonOperator(task_id='validate_data', python_callable=validate_data)
    collect >> clean >> validate

6.3 Monitoramento de drift

Compare distribuições entre épocas usando KL-divergência ou Population Stability Index (PSI).

7. Validação, Testes e Iteração do Pipeline

7.1 Métricas de qualidade

Calcule diversidade via Type-Token Ratio (TTR) e cobertura de domínios com entropia:

def type_token_ratio(texts):
    tokens = [t for text in texts for t in text.split()]
    return len(set(tokens)) / len(tokens)

7.2 Testes A/B com subsets

Treine modelos com diferentes versões do dataset e compare loss de validação:

# Exemplo conceitual
dataset_v1_loss = 0.45
dataset_v2_loss = 0.38  # Melhor: mais limpo

7.3 Ciclo de feedback

Analise erros do modelo fine-tuned (ex.: confusão em classes específicas) e adicione exemplos corretivos ao dataset. Itere semanalmente.

Referências