#!/usr/bin/env python3 """ Script de treinamento gerado para HuggingFace Training Platform. Execute este script no HuggingFace Training ou localmente. """ from datasets import load_dataset from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, DataCollatorForLanguageModeling, ) from peft import LoraConfig, get_peft_model import torch import os import json from datetime import datetime from pathlib import Path # Configuração MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct" DATASET_REPO = "beAnalytic/eda-training-dataset" OUTPUT_REPO = "beAnalytic/eda-llm-model" # Carregar dataset print(f"Carregando dataset: {DATASET_REPO}") dataset = load_dataset(DATASET_REPO) # Configurar variáveis de ambiente para evitar problemas de memória os.environ["OMP_NUM_THREADS"] = "1" os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") # Carregar modelo e tokenizer print(f"Carregando modelo: {MODEL_NAME}") tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) tokenizer.pad_token = tokenizer.eos_token # Verificar se há GPU disponível device = "cuda" if torch.cuda.is_available() else "cpu" print(f"Usando dispositivo: {device}") # Carregar modelo sem quantização (LoRA é suficiente para reduzir memória) # Quantização 4-bit está causando problemas de GPU RAM no HuggingFace Space print("Carregando modelo (sem quantização, usando LoRA para eficiência)...") model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, torch_dtype=torch.float16 if device == "cuda" else torch.float32, device_map="auto" if device == "cuda" else None, trust_remote_code=True, use_cache=False, ) if device == "cpu": print("⚠️ Modelo carregado em CPU - treinamento será mais lento") # Configurar LoRA peft_config = LoraConfig( r=16, lora_alpha=32, target_modules=['q_proj', 'v_proj', 'k_proj', 'o_proj'], lora_dropout=0.1, bias="none", task_type="CAUSAL_LM", ) model = get_peft_model(model, peft_config) # Formatar prompts EDA_SYSTEM_PROMPT = ( "Você é um analista de dados experiente, focado em gerar INSIGHTS e não em descrever processos técnicos.\n\n" "Sua tarefa é realizar uma Análise Exploratória de Dados (EDA) extraindo padrões, tendências e comportamentos relevantes dos dados.\n\n" "REGRAS OBRIGATÓRIAS:\n\n" "1. NÃO descreva etapas técnicas, bibliotecas, código ou ferramentas (pandas, Python, gráficos, etc.).\n" "2. NÃO explique \"como fazer\" a análise.\n" "3. Extraia padrões, tendências e comportamentos relevantes dos dados.\n" "4. Diferencie claramente:\n" " • Observação (o que é visível nos dados)\n" " • Interpretação (o que isso pode significar)\n" " • Insight (qual a implicação prática ou de negócio)\n" "5. Declare explicitamente o nível de confiança de cada insight (alto / médio / baixo).\n" "6. Quando não houver dados suficientes, diga claramente \"não é possível afirmar\".\n\n" "FORMATO OBRIGATÓRIO DA RESPOSTA:\n\n" "Observações:\n" "- …\n\n" "Interpretações:\n" "- …\n\n" "Insights:\n" "- …\n\n" "Nível de confiança:\n" "- …\n\n" "OBJETIVO: Entregar conclusões úteis, claras e acionáveis, como um analista humano experiente faria." ) def format_prompt(example): input_text = example.get("input", "") output_text = example.get("output", "") prompt = f"<|system|>\n{EDA_SYSTEM_PROMPT}\n<|user|>\n{input_text}\n<|assistant|>\n{output_text}" return {"text": prompt} # Aplicar formatação train_dataset = dataset["train"].map(format_prompt, remove_columns=dataset["train"].column_names) # Verificar se existe dataset de validação, caso contrário criar a partir do train if "validation" in dataset: eval_dataset = dataset["validation"].map(format_prompt, remove_columns=dataset["validation"].column_names) else: print("⚠️ Dataset de validação não encontrado. Criando divisão a partir do dataset de treinamento...") # Dividir o dataset de treinamento em train e validation (80/20) split_dataset = dataset["train"].train_test_split(test_size=0.2, seed=42) train_dataset = split_dataset["train"].map(format_prompt, remove_columns=split_dataset["train"].column_names) eval_dataset = split_dataset["test"].map(format_prompt, remove_columns=split_dataset["test"].column_names) print(f"✅ Dataset dividido: {len(train_dataset)} exemplos de treino, {len(eval_dataset)} exemplos de validação") # Tokenizar def tokenize_function(examples): return tokenizer( examples["text"], truncation=True, max_length=1024, padding="max_length", ) train_dataset = train_dataset.map(tokenize_function, batched=True, remove_columns=["text"]) eval_dataset = eval_dataset.map(tokenize_function, batched=True, remove_columns=["text"]) # Criar diretório de logs logs_dir = Path("./logs") logs_dir.mkdir(exist_ok=True) # Configurar argumentos de treinamento training_args = TrainingArguments( output_dir="./results", num_train_epochs=3, per_device_train_batch_size=2, per_device_eval_batch_size=2, learning_rate=3e-05, warmup_steps=100, logging_steps=10, save_steps=500, eval_strategy="steps", eval_steps=500, save_total_limit=3, load_best_model_at_end=True, fp16=device == "cuda", gradient_accumulation_steps=4, dataloader_pin_memory=False, push_to_hub=True, hub_model_id=OUTPUT_REPO, hub_strategy="checkpoint", ) # Data collator data_collator = DataCollatorForLanguageModeling( tokenizer=tokenizer, mlm=False, ) # Trainer trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, data_collator=data_collator, ) # Treinar print("Iniciando treinamento...") try: train_output = trainer.train() except Exception as e: print(f"❌ Erro durante treinamento: {e}") # Tentar salvar resultados mesmo em caso de erro train_output = None # Coletar estado atual se possível try: state = trainer.state final_log_history = state.log_history if hasattr(state, 'log_history') and state.log_history else [] except: final_log_history = [] # Salvar log de erro error_info = { "timestamp": datetime.utcnow().isoformat() + "Z", "error": str(e), "model_name": MODEL_NAME, "dataset_repo": DATASET_REPO, "status": "failed" } error_file = logs_dir / f"training_error_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json" with open(error_file, 'w', encoding='utf-8') as f: json.dump(error_info, f, indent=2, ensure_ascii=False) print(f"✅ Informações de erro salvas em: {error_file}") raise # Coletar métricas finais do estado do trainer state = trainer.state final_log_history = state.log_history if hasattr(state, 'log_history') and state.log_history else [] # Tentar obter loss final de diferentes fontes final_train_loss = None if train_output and hasattr(train_output, 'training_loss'): final_train_loss = train_output.training_loss elif final_log_history: for log_entry in reversed(final_log_history): if 'loss' in log_entry and 'eval_loss' not in log_entry: final_train_loss = log_entry.get('loss') break # Buscar últimas métricas de validação last_eval_metrics = {} if final_log_history: for log_entry in reversed(final_log_history): if 'eval_loss' in log_entry: last_eval_metrics = {k: v for k, v in log_entry.items() if k.startswith('eval_')} break # Coletar informações do treinamento training_info = { "timestamp": datetime.utcnow().isoformat() + "Z", "model_name": MODEL_NAME, "dataset_repo": DATASET_REPO, "output_repo": OUTPUT_REPO, "training_config": { "num_train_epochs": training_args.num_train_epochs, "per_device_train_batch_size": training_args.per_device_train_batch_size, "per_device_eval_batch_size": training_args.per_device_eval_batch_size, "gradient_accumulation_steps": training_args.gradient_accumulation_steps, "learning_rate": training_args.learning_rate, "warmup_steps": training_args.warmup_steps, "fp16": training_args.fp16, }, "dataset_info": { "train_samples": len(train_dataset), "eval_samples": len(eval_dataset) if eval_dataset else 0, }, "training_results": { "final_train_loss": final_train_loss, "final_eval_metrics": last_eval_metrics, "total_steps": len(final_log_history) if final_log_history else 0, "log_history": final_log_history[-50:], }, "status": "completed", } # Salvar resultados em JSON results_file = logs_dir / f"training_results_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json" with open(results_file, 'w', encoding='utf-8') as f: json.dump(training_info, f, indent=2, ensure_ascii=False) print(f"✅ Resultados salvos em: {results_file}") # Criar resumo em texto legível summary_file = logs_dir / f"training_summary_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.txt" with open(summary_file, 'w', encoding='utf-8') as f: f.write("=" * 80 + "\n") f.write("RESUMO DO TREINAMENTO\n") f.write("=" * 80 + "\n\n") f.write(f"Data/Hora: {training_info['timestamp']}\n") f.write(f"Modelo: {MODEL_NAME}\n") f.write(f"Dataset: {DATASET_REPO}\n") f.write(f"Output: {OUTPUT_REPO}\n\n") f.write("CONFIGURAÇÃO DE TREINAMENTO:\n") f.write("-" * 80 + "\n") config = training_info['training_config'] f.write(f"Épocas: {config['num_train_epochs']}\n") f.write(f"Batch Size (train): {config['per_device_train_batch_size']}\n") f.write(f"Batch Size (eval): {config['per_device_eval_batch_size']}\n") f.write(f"Gradient Accumulation Steps: {config['gradient_accumulation_steps']}\n") f.write(f"Learning Rate: {config['learning_rate']}\n") f.write(f"Warmup Steps: {config['warmup_steps']}\n") f.write(f"FP16: {config['fp16']}\n\n") f.write("DATASET:\n") f.write("-" * 80 + "\n") dataset_info = training_info['dataset_info'] f.write(f"Amostras de Treino: {dataset_info['train_samples']}\n") f.write(f"Amostras de Validação: {dataset_info['eval_samples']}\n\n") f.write("RESULTADOS:\n") f.write("-" * 80 + "\n") results = training_info['training_results'] if results['final_train_loss'] is not None: f.write(f"Loss Final (Treino): {results['final_train_loss']:.6f}\n") if results['final_eval_metrics']: f.write("\nMétricas Finais de Validação:\n") for key, value in results['final_eval_metrics'].items(): if isinstance(value, float): f.write(f" {key}: {value:.6f}\n") else: f.write(f" {key}: {value}\n") f.write(f"\nTotal de Steps: {results['total_steps']}\n") f.write(f"Status: {training_info['status']}\n") print(f"✅ Resumo salvo em: {summary_file}") # Fazer push final print(f"Fazendo push do modelo final para {OUTPUT_REPO}") try: trainer.push_to_hub() print("✅ Push para Hub concluído!") except Exception as e: print(f"⚠️ Aviso: Erro ao fazer push para Hub: {e}") print("Os checkpoints estão salvos localmente em ./results") print("✅ Treinamento concluído!") def analyze_schema(csv_description: str, model_path: str = None): """ Função de inferência - modelo já 'obrigado' a pensar certo. Args: csv_description: Descrição do dataset CSV para análise model_path: Caminho para o modelo treinado (opcional, usa modelo atual se None) Returns: Análise EDA gerada pelo modelo treinado """ # Se model_path for fornecido, carregar modelo treinado inference_model = model inference_tokenizer = tokenizer if model_path: print(f"Carregando modelo treinado de: {model_path}") inference_tokenizer = AutoTokenizer.from_pretrained(model_path) inference_model = AutoModelForCausalLM.from_pretrained( model_path, device_map="auto", torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32 ) prompt = f"""<|system|> {EDA_SYSTEM_PROMPT} <|user|> Analise o seguinte dataset: {csv_description} <|assistant|> """ inputs = inference_tokenizer(prompt, return_tensors="pt").to(inference_model.device) output = inference_model.generate( **inputs, max_new_tokens=1200, temperature=0.2, do_sample=False ) return inference_tokenizer.decode(output[0], skip_special_tokens=True) # Exemplo de uso após o treinamento: # resultado = analyze_schema("Descrição do seu dataset aqui...") # print(resultado)