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
- Hugging Face Datasets Documentation — Guia oficial para carregar, processar e versionar datasets para fine-tuning.
- DVC: Data Version Control — Tutorial completo sobre versionamento de dados e pipelines de ML.
- OpenAI Fine-Tuning Guide — Documentação oficial sobre preparação de datasets e boas práticas.
- Self-Instruct: Aligning Language Models with Self-Generated Instructions — Artigo seminal sobre geração de dados sintéticos para fine-tuning.
- Apache Airflow: Pipelines as Code — Tutorial oficial para orquestração de pipelines de dados.