amarorn commited on
Commit
d0ac8a6
·
1 Parent(s): 6889ee6

feat: atualizar train.py com novo formato de prompt EDA e função de inferência

Browse files

- Adicionar EDA_SYSTEM_PROMPT com regras analíticas focadas em insights
- Simplificar format_prompt() para usar apenas input/output
- Adicionar função analyze_schema() para inferência após treinamento
- Corrigir erros de sintaxe (true -> True)
- Remover lógica complexa de autenticação, manter push_to_hub simples

Files changed (6) hide show
  1. .gitattributes +0 -35
  2. Dockerfile +0 -42
  3. README.md +0 -73
  4. app.py +0 -94
  5. requirements.txt +0 -9
  6. train.py +105 -395
.gitattributes DELETED
@@ -1,35 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Dockerfile DELETED
@@ -1,42 +0,0 @@
1
- FROM huggingface/transformers-pytorch-gpu:latest
2
-
3
- WORKDIR /app
4
-
5
- # Instalar dependências do sistema
6
- # python-is-python3 cria automaticamente o symlink python -> python3
7
- RUN apt-get update && apt-get install -y --no-install-recommends \
8
- git \
9
- python3 \
10
- python3-pip \
11
- python-is-python3 \
12
- && rm -rf /var/lib/apt/lists/*
13
-
14
- # Verificar que python está disponível (entrypoint do NVIDIA precisa)
15
- RUN python --version && \
16
- python3 --version && \
17
- echo "✅ Python disponível: $(which python)"
18
-
19
- # Instalar dependências Python
20
- COPY requirements.txt .
21
- RUN python3 -m pip install --no-cache-dir --upgrade pip && \
22
- python3 -m pip install --no-cache-dir -r requirements.txt
23
-
24
- # Copiar scripts de treinamento
25
- COPY train.py /app/train.py
26
- COPY app.py /app/app.py
27
-
28
- # Criar diretório de logs
29
- RUN mkdir -p /app/logs
30
-
31
- # Configurar variáveis de ambiente padrão (podem ser sobrescritas)
32
- ENV MODEL_NAME=microsoft/Phi-3-mini-4k-instruct
33
- ENV DATASET_REPO=beAnalytic/eda-training-dataset
34
- ENV OUTPUT_REPO=beAnalytic/eda-llm-model
35
- ENV OMP_NUM_THREADS=1
36
- ENV PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
37
-
38
- # Executar treinamento
39
- # Usar 'python' (que será o symlink para python3 criado acima)
40
- # O entrypoint do NVIDIA espera 'python' estar disponível
41
- CMD ["python", "/app/app.py"]
42
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md DELETED
@@ -1,73 +0,0 @@
1
- ---
2
- title: EDA Model Training
3
- emoji: 🤖
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: docker
7
- sdk_version: "latest"
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- # Treinamento do Modelo EDA
13
-
14
- Este Space contém o script de treinamento para o modelo de Análise Exploratória de Dados (EDA).
15
-
16
- ## Configuração
17
-
18
- ### Variáveis de Ambiente Obrigatórias
19
-
20
- **⚠️ IMPORTANTE**: Configure a variável de ambiente `HF_TOKEN` no Settings do Space para habilitar o push automático dos checkpoints para o Hub.
21
-
22
- ### Variáveis de Ambiente
23
-
24
- Configure as seguintes variáveis de ambiente no Settings do Space:
25
-
26
- - **`HF_TOKEN`** (OBRIGATÓRIO): Seu token do HuggingFace com permissões de escrita
27
- - Gere em: https://huggingface.co/settings/tokens
28
- - Permissões necessárias: `write`
29
- - Sem este token, o treinamento funcionará mas os checkpoints não serão enviados ao Hub
30
-
31
- - `MODEL_NAME`: Modelo base (padrão: `microsoft/Phi-3-mini-4k-instruct`)
32
- - `DATASET_REPO`: ID do dataset (padrão: `beAnalytic/eda-training-dataset`)
33
- - `OUTPUT_REPO`: ID do modelo de saída (padrão: `beAnalytic/eda-llm-model`)
34
-
35
- ### Como Configurar HF_TOKEN no Space
36
-
37
- 1. Acesse: https://huggingface.co/spaces/beAnalytic/Training/settings
38
- 2. Vá para a seção **"Repository secrets"**
39
- 3. Clique em **"New secret"**
40
- 4. Nome: `HF_TOKEN`
41
- 5. Valor: Cole seu token do HuggingFace
42
- 6. Clique em **"Add secret"**
43
-
44
- **Nota**: O token será usado automaticamente pelo script durante o treinamento.
45
-
46
- ### Execução
47
-
48
- O script `train.py` será executado automaticamente quando o Space for iniciado.
49
-
50
- ## Estrutura
51
-
52
- - `train.py`: Script principal de treinamento
53
- - `training_config.json`: Configurações de treinamento
54
- - `requirements.txt`: Dependências Python
55
-
56
- ## Monitoramento
57
-
58
- Acompanhe o progresso do treinamento através dos logs do Space na aba "Logs".
59
-
60
- ### TensorBoard
61
-
62
- O TensorBoard está configurado e rodando na porta 6006 dentro do container. No HuggingFace Space com Docker SDK, apenas a porta 7860 é exposta publicamente, então o TensorBoard não é acessível diretamente via URL.
63
-
64
- **Para visualizar métricas**:
65
- - Durante o treinamento: Acompanhe os logs na aba "Logs"
66
- - Após o treinamento: Baixe os logs de `./results/` e execute `tensorboard --logdir=./results` localmente
67
- - Acesse: http://localhost:6006 (após baixar os logs)
68
-
69
- Para mais detalhes, consulte [ACESSAR_TENSORBOARD.md](../huggingface_training_config/docs/ACESSAR_TENSORBOARD.md).
70
-
71
- ## Resultados
72
-
73
- O modelo treinado será salvo automaticamente no HuggingFace Hub no repositório especificado em `OUTPUT_REPO`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py DELETED
@@ -1,94 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- App principal para execução no HuggingFace Space.
4
- Este arquivo executa o treinamento do modelo EDA e inicia o TensorBoard.
5
- """
6
-
7
- import os
8
- import subprocess
9
- import sys
10
- import threading
11
- import time
12
-
13
- def start_tensorboard():
14
- """
15
- Inicia o TensorBoard em background, verificando se já está rodando.
16
- """
17
- # Os logs do TensorBoard são salvos no output_dir (./results) quando report_to=["tensorboard"]
18
- # Criar diretório de resultados se não existir
19
- results_dir = os.path.join(os.path.dirname(__file__), "results")
20
- os.makedirs(results_dir, exist_ok=True)
21
-
22
- # Verificar se TensorBoard já está rodando na porta 6006
23
- import socket
24
- port_in_use = False
25
- try:
26
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
27
- result = sock.connect_ex(('0.0.0.0', 6006))
28
- if result == 0:
29
- port_in_use = True
30
- sock.close()
31
- except Exception:
32
- pass
33
-
34
- if port_in_use:
35
- print("=" * 60)
36
- print("⚠️ TensorBoard já está rodando na porta 6006")
37
- print("=" * 60)
38
- return
39
-
40
- print("=" * 60)
41
- print("Iniciando TensorBoard...")
42
- print(f"Logdir: {results_dir}")
43
- print("TensorBoard estará disponível na interface do HuggingFace Space")
44
- print("=" * 60)
45
-
46
- # Matar qualquer processo TensorBoard anterior (se houver)
47
- os.system("pkill -f 'tensorboard.*6006' 2>/dev/null || true")
48
- time.sleep(1)
49
-
50
- # Iniciar TensorBoard em background
51
- # IMPORTANTE: Os logs são salvos em ./results quando report_to=["tensorboard"]
52
- # Porta 6006 é a porta padrão do TensorBoard
53
- os.system("tensorboard --logdir=results --host=0.0.0.0 --port=6006 > /dev/null 2>&1 &")
54
-
55
- # Aguardar um pouco para garantir que o TensorBoard iniciou
56
- time.sleep(2)
57
- print("✅ TensorBoard iniciado em background")
58
-
59
- def main():
60
- """
61
- Executa o script de treinamento.
62
- """
63
- # Iniciar TensorBoard em thread separada
64
- tensorboard_thread = threading.Thread(target=start_tensorboard, daemon=True)
65
- tensorboard_thread.start()
66
-
67
- print("=" * 60)
68
- print("Iniciando treinamento do modelo EDA")
69
- print("=" * 60)
70
-
71
- script_path = os.path.join(os.path.dirname(__file__), "train.py")
72
-
73
- if not os.path.exists(script_path):
74
- print(f"❌ Erro: Arquivo {script_path} não encontrado!")
75
- sys.exit(1)
76
-
77
- try:
78
- result = subprocess.run(
79
- [sys.executable, script_path],
80
- check=True,
81
- capture_output=False,
82
- )
83
- print("\n✅ Treinamento concluído com sucesso!")
84
- return result.returncode
85
- except subprocess.CalledProcessError as e:
86
- print(f"\n❌ Erro durante o treinamento: {e}")
87
- sys.exit(e.returncode)
88
- except KeyboardInterrupt:
89
- print("\n⚠️ Treinamento interrompido pelo usuário")
90
- sys.exit(130)
91
-
92
- if __name__ == "__main__":
93
- sys.exit(main())
94
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt DELETED
@@ -1,9 +0,0 @@
1
- torch>=2.0.0
2
- transformers>=4.45.0
3
- datasets>=2.14.0
4
- peft>=0.8.0
5
- accelerate>=0.27.0
6
- bitsandbytes>=0.41.0
7
- huggingface_hub>=0.20.0
8
- tensorboard>=2.15.0
9
-
 
 
 
 
 
 
 
 
 
 
train.py CHANGED
@@ -14,107 +14,16 @@ from transformers import (
14
  )
15
  from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
16
  from transformers import BitsAndBytesConfig
17
- from huggingface_hub import login as hf_login, logout as hf_logout
18
  import torch
19
- import os
20
- import json
21
- from datetime import datetime
22
- from pathlib import Path
23
 
24
- # Configuração (pode ser sobrescrita por variáveis de ambiente)
25
- MODEL_NAME = os.getenv("MODEL_NAME", "microsoft/Phi-3-mini-4k-instruct")
26
- DATASET_REPO = os.getenv("DATASET_REPO", "beAnalytic/eda-training-dataset")
27
- OUTPUT_REPO = os.getenv("OUTPUT_REPO", "beAnalytic/eda-llm-model")
28
- HF_TOKEN = os.getenv("HF_TOKEN")
29
-
30
- # Autenticar no HuggingFace se token estiver disponível
31
- # IMPORTANTE: Limpar qualquer token do ambiente se não estiver configurado explicitamente
32
- push_to_hub_enabled = False
33
-
34
- # Primeiro, limpar qualquer autenticação existente para garantir estado limpo
35
- try:
36
- hf_logout()
37
- except Exception:
38
- pass
39
-
40
- # Limpar tokens alternativos do ambiente (mantém HF_TOKEN que será usado depois)
41
- tokens_to_remove = ["HUGGING_FACE_HUB_TOKEN", "HF_HUB_TOKEN", "HUGGINGFACE_HUB_TOKEN"]
42
- for token_var in tokens_to_remove:
43
- if token_var in os.environ:
44
- del os.environ[token_var]
45
-
46
- # Limpar cache de autenticação (hf_logout já faz isso)
47
- try:
48
- hf_logout()
49
- except Exception:
50
- pass
51
-
52
- if HF_TOKEN and HF_TOKEN.strip():
53
- print("Autenticando no HuggingFace Hub...")
54
- try:
55
- hf_login(token=HF_TOKEN, add_to_git_credential=False)
56
- print("✅ Autenticação bem-sucedida!")
57
- push_to_hub_enabled = True
58
- except Exception as e:
59
- print(f"⚠️ Aviso: Erro ao autenticar no HuggingFace: {e}")
60
- print("O treinamento continuará, mas o push para o Hub será desabilitado.")
61
- push_to_hub_enabled = False
62
- # Limpar novamente após falha
63
- try:
64
- hf_logout()
65
- except Exception:
66
- pass
67
- else:
68
- print("⚠️ Aviso: HF_TOKEN não encontrado ou vazio. O push para o Hub será desabilitado.")
69
- print("Configure a variável de ambiente HF_TOKEN no Space para habilitar push automático.")
70
- push_to_hub_enabled = False
71
 
72
  # Carregar dataset
73
  print(f"Carregando dataset: {DATASET_REPO}")
74
- try:
75
- # Tentar carregar o arquivo JSONL expandido diretamente
76
- print("Carregando dataset_balanceado_messages_expandido.jsonl...")
77
- dataset_raw = load_dataset(
78
- DATASET_REPO,
79
- data_files={"train": "dataset_balanceado_messages_expandido.jsonl"},
80
- verification_mode="no_checks",
81
- split="train"
82
- )
83
- print(f"✅ Arquivo carregado: {len(dataset_raw)} exemplos")
84
-
85
- # Dividir em train/validation (80/20)
86
- print("Dividindo em train/validation (80/20)...")
87
- dataset_split = dataset_raw.train_test_split(test_size=0.2, seed=42)
88
- dataset = {
89
- "train": dataset_split["train"],
90
- "validation": dataset_split["test"]
91
- }
92
- print(f"✅ Dataset preparado: {len(dataset['train'])} exemplos de treino, {len(dataset['validation'])} exemplos de validação")
93
-
94
- except Exception as e:
95
- print(f"⚠️ Erro ao carregar arquivo específico: {e}")
96
- print("Tentando carregar dataset padrão do repositório...")
97
- try:
98
- dataset = load_dataset(DATASET_REPO)
99
- print(f"✅ Dataset padrão carregado")
100
- if "train" not in dataset:
101
- # Se não tiver split, criar
102
- if len(dataset) == 1:
103
- split_name = list(dataset.keys())[0]
104
- dataset_raw = dataset[split_name]
105
- dataset = dataset_raw.train_test_split(test_size=0.2, seed=42)
106
- dataset = {
107
- "train": dataset["train"],
108
- "validation": dataset["test"]
109
- }
110
- print(f"✅ Dataset dividido: {len(dataset['train'])} treino, {len(dataset['validation'])} validação")
111
- except Exception as e2:
112
- print(f"❌ Erro ao carregar dataset: {e2}")
113
- raise
114
-
115
- # Configurar variáveis de ambiente para evitar problemas de memória
116
- os.environ["OMP_NUM_THREADS"] = "1"
117
- os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
118
 
119
  # Carregar modelo e tokenizer
120
  print(f"Carregando modelo: {MODEL_NAME}")
@@ -134,7 +43,6 @@ model = AutoModelForCausalLM.from_pretrained(
134
  quantization_config=bnb_config,
135
  device_map="auto",
136
  trust_remote_code=True,
137
- use_cache=False,
138
  )
139
 
140
  # Preparar modelo para LoRA
@@ -153,50 +61,36 @@ peft_config = LoraConfig(
153
  model = get_peft_model(model, peft_config)
154
 
155
  # Formatar prompts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  def format_prompt(example):
157
- # Suporta dois formatos: messages (ChatML) ou instruction/input/output
158
- if "messages" in example:
159
- # Formato messages (ChatML)
160
- messages = example["messages"]
161
- system_content = ""
162
- user_content = ""
163
- assistant_content = ""
164
-
165
- for msg in messages:
166
- role = msg.get("role", "")
167
- content = msg.get("content", "")
168
- if role == "system":
169
- system_content = content
170
- elif role == "user":
171
- user_content = content
172
- elif role == "assistant":
173
- assistant_content = content
174
-
175
- # Se não houver system, usar padrão
176
- if not system_content:
177
- system_content = (
178
- "Você é um analista de dados sênior realizando uma Análise Exploratória de Dados (EDA) "
179
- "com rigor estatístico, honestidade analítica e pensamento crítico.\n\n"
180
- "Seu objetivo não é gerar insights a qualquer custo, mas avaliar se os dados possuem "
181
- "estrutura informativa, comportamento emergente ou apenas relações estruturais triviais."
182
- )
183
-
184
- prompt = f"<|system|>\n{system_content}\n<|user|>\n{user_content}\n<|assistant|>\n{assistant_content}<|end|>"
185
- else:
186
- # Formato instruction/input/output (legado)
187
- system_prompt = (
188
- "Você é um analista de dados sênior realizando uma Análise Exploratória de Dados (EDA) "
189
- "com rigor estatístico, honestidade analítica e pensamento crítico.\n\n"
190
- "Seu objetivo não é gerar insights a qualquer custo, mas avaliar se os dados possuem "
191
- "estrutura informativa, comportamento emergente ou apenas relações estruturais triviais."
192
- )
193
-
194
- instruction = example.get("instruction", "")
195
- input_text = example.get("input", "")
196
- output_text = example.get("output", "")
197
-
198
- prompt = f"<|system|>\n{system_prompt}\n<|user|>\n{instruction}\n\n{input_text}\n<|assistant|>\n{output_text}<|end|>"
199
 
 
200
  return {"text": prompt}
201
 
202
  # Aplicar formatação
@@ -226,50 +120,25 @@ train_dataset = train_dataset.map(tokenize_function, batched=True, remove_column
226
  eval_dataset = eval_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
227
 
228
  # Configurar argumentos de treinamento
229
- # push_to_hub_enabled já foi definido acima durante a autenticação
230
-
231
- # Argumentos base de treinamento
232
- training_args_dict = {
233
- "output_dir": "./results",
234
- "num_train_epochs": 3,
235
- "per_device_train_batch_size": 2,
236
- "per_device_eval_batch_size": 2,
237
- "learning_rate": 3e-05,
238
- "warmup_steps": 100,
239
- "logging_steps": 10,
240
- "save_steps": 500,
241
- "eval_strategy": "steps",
242
- "eval_steps": 500,
243
- "save_total_limit": 3,
244
- "load_best_model_at_end": True,
245
- "fp16": True,
246
- "gradient_accumulation_steps": 4,
247
- "dataloader_pin_memory": False,
248
- "ddp_find_unused_parameters": False,
249
- }
250
-
251
- # Adicionar parâmetros do Hub apenas se autenticado
252
- # IMPORTANTE: Não passar NENHUM parâmetro relacionado ao Hub quando não há token
253
- # para evitar que o Trainer tente inicializar o repositório
254
- if push_to_hub_enabled:
255
- training_args_dict.update({
256
- "push_to_hub": True,
257
- "hub_model_id": OUTPUT_REPO,
258
- "hub_strategy": "checkpoint",
259
- })
260
- else:
261
- # Garantir explicitamente que push_to_hub está desabilitado
262
- # E que hub_model_id é None (não passar o parâmetro pode fazer o Trainer usar um valor padrão)
263
- training_args_dict["push_to_hub"] = False
264
- training_args_dict["hub_model_id"] = None
265
-
266
- training_args = TrainingArguments(**training_args_dict)
267
-
268
- if push_to_hub_enabled:
269
- print(f"✅ Push para Hub habilitado: {OUTPUT_REPO}")
270
- else:
271
- print("ℹ️ Push para Hub desabilitado (HF_TOKEN não configurado)")
272
- print("Os checkpoints serão salvos apenas localmente em ./results")
273
 
274
  # Data collator
275
  data_collator = DataCollatorForLanguageModeling(
@@ -278,228 +147,69 @@ data_collator = DataCollatorForLanguageModeling(
278
  )
279
 
280
  # Trainer
281
- # IMPORTANTE: Garantir que não há token no ambiente quando push_to_hub está desabilitado
282
- # para evitar que o Trainer tente inicializar o repositório durante __init__
283
- if not push_to_hub_enabled:
284
- # Limpar todos os possíveis tokens do ambiente
285
- tokens_to_remove = ["HUGGING_FACE_HUB_TOKEN", "HF_HUB_TOKEN", "HUGGINGFACE_HUB_TOKEN"]
286
- for token_var in tokens_to_remove:
287
- if token_var in os.environ:
288
- del os.environ[token_var]
289
-
290
- # Fazer logout para garantir que não há token no cache
291
- try:
292
- hf_logout()
293
- except Exception:
294
- pass
295
-
296
- # Verificação de segurança - garantir que push_to_hub está False
297
- if training_args.push_to_hub:
298
- print("⚠️ AVISO: push_to_hub está True mas não há token! Forçando False...")
299
- training_args.push_to_hub = False
300
- if training_args.hub_model_id is not None:
301
- print("⚠️ AVISO: hub_model_id está definido mas não há token! Removendo...")
302
- training_args.hub_model_id = None
303
-
304
- print(f"🔍 Debug: push_to_hub={training_args.push_to_hub}, hub_model_id={training_args.hub_model_id}")
305
- print(f"🔍 Debug: push_to_hub_enabled={push_to_hub_enabled}")
306
-
307
- # Verificação final: se push_to_hub está False, garantir que não há token no cache
308
- if not push_to_hub_enabled:
309
- # Limpar qualquer token residual do cache
310
- try:
311
- hf_logout()
312
- except Exception:
313
- pass
314
-
315
- # Verificação final dos argumentos
316
- if training_args.push_to_hub or training_args.hub_model_id:
317
- print("❌ ERRO: push_to_hub ou hub_model_id ainda está definido! Corrigindo...")
318
- training_args.push_to_hub = False
319
- training_args.hub_model_id = None
320
-
321
- print(f"✅ Criando Trainer com push_to_hub={training_args.push_to_hub}, hub_model_id={training_args.hub_model_id}")
322
-
323
- # Criar Trainer
324
- # Se push_to_hub está False, garantir que não há token no cache antes de criar
325
- if not push_to_hub_enabled:
326
- # Última verificação: limpar qualquer token residual
327
- try:
328
- hf_logout()
329
- except Exception:
330
- pass
331
-
332
- try:
333
- trainer = Trainer(
334
- model=model,
335
- args=training_args,
336
- train_dataset=train_dataset,
337
- eval_dataset=eval_dataset,
338
- data_collator=data_collator,
339
- )
340
- print("✅ Trainer criado com sucesso!")
341
- except Exception as e:
342
- if "401" in str(e) or "Unauthorized" in str(e):
343
- print("❌ ERRO: Trainer tentou autenticar sem token válido!")
344
- print("Isso não deveria acontecer. Verificando configuração...")
345
- print(f"push_to_hub={training_args.push_to_hub}")
346
- print(f"hub_model_id={training_args.hub_model_id}")
347
- print(f"push_to_hub_enabled={push_to_hub_enabled}")
348
- # Tentar novamente após limpar tudo
349
- try:
350
- hf_logout()
351
- except Exception:
352
- pass
353
- # Forçar push_to_hub=False novamente
354
- training_args.push_to_hub = False
355
- training_args.hub_model_id = None
356
- print("Tentando criar Trainer novamente com push_to_hub=False...")
357
- trainer = Trainer(
358
- model=model,
359
- args=training_args,
360
- train_dataset=train_dataset,
361
- eval_dataset=eval_dataset,
362
- data_collator=data_collator,
363
- )
364
- else:
365
- raise
366
-
367
- # Criar diretório de logs
368
- logs_dir = Path("./logs")
369
- logs_dir.mkdir(exist_ok=True)
370
 
371
  # Treinar
372
  print("Iniciando treinamento...")
373
- train_output = trainer.train()
374
-
375
- # Coletar métricas finais do estado do trainer
376
- state = trainer.state
377
- final_log_history = state.log_history if hasattr(state, 'log_history') and state.log_history else []
378
-
379
- # Tentar obter loss final de diferentes fontes
380
- final_train_loss = None
381
- if hasattr(train_output, 'training_loss'):
382
- final_train_loss = train_output.training_loss
383
- elif final_log_history:
384
- for log_entry in reversed(final_log_history):
385
- if 'loss' in log_entry and 'eval_loss' not in log_entry:
386
- final_train_loss = log_entry.get('loss')
387
- break
388
 
389
- # Buscar últimas métricas de validação
390
- last_eval_metrics = {}
391
- if final_log_history:
392
- for log_entry in reversed(final_log_history):
393
- if 'eval_loss' in log_entry:
394
- last_eval_metrics = {k: v for k, v in log_entry.items() if k.startswith('eval_')}
395
- break
396
 
397
- # Coletar informações do treinamento
398
- training_info = {
399
- "timestamp": datetime.utcnow().isoformat() + "Z",
400
- "model_name": MODEL_NAME,
401
- "dataset_repo": DATASET_REPO,
402
- "output_repo": OUTPUT_REPO,
403
- "training_config": {
404
- "num_train_epochs": training_args.num_train_epochs,
405
- "per_device_train_batch_size": training_args.per_device_train_batch_size,
406
- "per_device_eval_batch_size": training_args.per_device_eval_batch_size,
407
- "gradient_accumulation_steps": training_args.gradient_accumulation_steps,
408
- "learning_rate": training_args.learning_rate,
409
- "warmup_steps": training_args.warmup_steps,
410
- "fp16": training_args.fp16,
411
- },
412
- "dataset_info": {
413
- "train_samples": len(train_dataset),
414
- "eval_samples": len(eval_dataset) if eval_dataset else 0,
415
- },
416
- "training_results": {
417
- "final_train_loss": final_train_loss,
418
- "final_eval_metrics": last_eval_metrics,
419
- "total_steps": len(final_log_history) if final_log_history else 0,
420
- "log_history": final_log_history[-50:], # Últimas 50 entradas
421
- },
422
- "status": "completed",
423
- "push_to_hub_enabled": push_to_hub_enabled,
424
- }
425
 
426
- # Salvar resultados em JSON
427
- results_file = logs_dir / f"training_results_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json"
428
- with open(results_file, 'w', encoding='utf-8') as f:
429
- json.dump(training_info, f, indent=2, ensure_ascii=False)
430
- print(f"✅ Resultados salvos em: {results_file}")
431
 
432
- # Criar resumo em texto legível
433
- summary_file = logs_dir / f"training_summary_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.txt"
434
- with open(summary_file, 'w', encoding='utf-8') as f:
435
- f.write("=" * 80 + "\n")
436
- f.write("RESUMO DO TREINAMENTO\n")
437
- f.write("=" * 80 + "\n\n")
438
- f.write(f"Data/Hora: {training_info['timestamp']}\n")
439
- f.write(f"Modelo: {MODEL_NAME}\n")
440
- f.write(f"Dataset: {DATASET_REPO}\n")
441
- f.write(f"Output: {OUTPUT_REPO}\n\n")
442
-
443
- f.write("CONFIGURAÇÃO DE TREINAMENTO:\n")
444
- f.write("-" * 80 + "\n")
445
- config = training_info['training_config']
446
- f.write(f"Épocas: {config['num_train_epochs']}\n")
447
- f.write(f"Batch Size (train): {config['per_device_train_batch_size']}\n")
448
- f.write(f"Batch Size (eval): {config['per_device_eval_batch_size']}\n")
449
- f.write(f"Gradient Accumulation Steps: {config['gradient_accumulation_steps']}\n")
450
- f.write(f"Learning Rate: {config['learning_rate']}\n")
451
- f.write(f"Warmup Steps: {config['warmup_steps']}\n")
452
- f.write(f"FP16: {config['fp16']}\n\n")
453
-
454
- f.write("DATASET:\n")
455
- f.write("-" * 80 + "\n")
456
- dataset_info = training_info['dataset_info']
457
- f.write(f"Amostras de Treino: {dataset_info['train_samples']}\n")
458
- f.write(f"Amostras de Validação: {dataset_info['eval_samples']}\n\n")
459
 
460
- f.write("RESULTADOS:\n")
461
- f.write("-" * 80 + "\n")
462
- results = training_info['training_results']
463
- if results['final_train_loss'] is not None:
464
- f.write(f"Loss Final (Treino): {results['final_train_loss']:.6f}\n")
465
-
466
- if results['final_eval_metrics']:
467
- f.write("\nMétricas Finais de Validação:\n")
468
- for key, value in results['final_eval_metrics'].items():
469
- if isinstance(value, float):
470
- f.write(f" {key}: {value:.6f}\n")
471
- else:
472
- f.write(f" {key}: {value}\n")
473
 
474
- f.write(f"\nTotal de Steps: {results['total_steps']}\n")
475
- f.write(f"Status: {training_info['status']}\n")
476
- f.write(f"Push para Hub: {'Sim' if training_info['push_to_hub_enabled'] else 'Não'}\n")
 
 
 
 
 
477
 
478
- if results['log_history']:
479
- f.write("\n" + "=" * 80 + "\n")
480
- f.write("ÚLTIMAS MÉTRICAS DO LOG:\n")
481
- f.write("=" * 80 + "\n")
482
- for i, log_entry in enumerate(results['log_history'][-10:], 1):
483
- f.write(f"\nLog Entry {i}:\n")
484
- for key, value in log_entry.items():
485
- if isinstance(value, float):
486
- f.write(f" {key}: {value:.6f}\n")
487
- else:
488
- f.write(f" {key}: {value}\n")
489
 
490
- print(f"✅ Resumo salvo em: {summary_file}")
491
 
492
- # Fazer push final apenas se autenticado
493
- if push_to_hub_enabled:
494
- print(f"Fazendo push do modelo final para {OUTPUT_REPO}")
495
- try:
496
- trainer.push_to_hub()
497
- print("✅ Push para Hub concluído!")
498
- except Exception as e:
499
- print(f"❌ Erro ao fazer push para Hub: {e}")
500
- print("Os checkpoints estão salvos localmente em ./results")
501
- else:
502
- print("ℹ️ Push para Hub pulado (HF_TOKEN não configurado)")
503
- print("Os checkpoints estão salvos em ./results")
504
 
505
- print("✅ Treinamento concluído!")
 
 
 
 
 
 
14
  )
15
  from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
16
  from transformers import BitsAndBytesConfig
 
17
  import torch
 
 
 
 
18
 
19
+ # Configuração
20
+ MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct"
21
+ DATASET_REPO = "beAnalytic/eda-training-dataset"
22
+ OUTPUT_REPO = "beAnalytic/eda-llm-model"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  # Carregar dataset
25
  print(f"Carregando dataset: {DATASET_REPO}")
26
+ dataset = load_dataset(DATASET_REPO)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  # Carregar modelo e tokenizer
29
  print(f"Carregando modelo: {MODEL_NAME}")
 
43
  quantization_config=bnb_config,
44
  device_map="auto",
45
  trust_remote_code=True,
 
46
  )
47
 
48
  # Preparar modelo para LoRA
 
61
  model = get_peft_model(model, peft_config)
62
 
63
  # Formatar prompts
64
+ EDA_SYSTEM_PROMPT = (
65
+ "Você é um analista de dados experiente, focado em gerar INSIGHTS e não em descrever processos técnicos.\n\n"
66
+ "Sua tarefa é realizar uma Análise Exploratória de Dados (EDA) extraindo padrões, tendências e comportamentos relevantes dos dados.\n\n"
67
+ "REGRAS OBRIGATÓRIAS:\n\n"
68
+ "1. NÃO descreva etapas técnicas, bibliotecas, código ou ferramentas (pandas, Python, gráficos, etc.).\n"
69
+ "2. NÃO explique \"como fazer\" a análise.\n"
70
+ "3. Extraia padrões, tendências e comportamentos relevantes dos dados.\n"
71
+ "4. Diferencie claramente:\n"
72
+ " • Observação (o que é visível nos dados)\n"
73
+ " • Interpretação (o que isso pode significar)\n"
74
+ " • Insight (qual a implicação prática ou de negócio)\n"
75
+ "5. Declare explicitamente o nível de confiança de cada insight (alto / médio / baixo).\n"
76
+ "6. Quando não houver dados suficientes, diga claramente \"não é possível afirmar\".\n\n"
77
+ "FORMATO OBRIGATÓRIO DA RESPOSTA:\n\n"
78
+ "Observações:\n"
79
+ "- …\n\n"
80
+ "Interpretações:\n"
81
+ "- …\n\n"
82
+ "Insights:\n"
83
+ "- …\n\n"
84
+ "Nível de confiança:\n"
85
+ "- …\n\n"
86
+ "OBJETIVO: Entregar conclusões úteis, claras e acionáveis, como um analista humano experiente faria."
87
+ )
88
+
89
  def format_prompt(example):
90
+ input_text = example.get("input", "")
91
+ output_text = example.get("output", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ prompt = f"<|system|>\n{EDA_SYSTEM_PROMPT}\n<|user|>\n{input_text}\n<|assistant|>\n{output_text}"
94
  return {"text": prompt}
95
 
96
  # Aplicar formatação
 
120
  eval_dataset = eval_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
121
 
122
  # Configurar argumentos de treinamento
123
+ training_args = TrainingArguments(
124
+ output_dir="./results",
125
+ num_train_epochs=3,
126
+ per_device_train_batch_size=4,
127
+ per_device_eval_batch_size=4,
128
+ learning_rate=3e-05,
129
+ warmup_steps=100,
130
+ logging_steps=10,
131
+ save_steps=500,
132
+ eval_strategy="steps",
133
+ eval_steps=500,
134
+ save_total_limit=3,
135
+ load_best_model_at_end=True,
136
+ fp16=True,
137
+ gradient_accumulation_steps=2,
138
+ push_to_hub=True,
139
+ hub_model_id=OUTPUT_REPO,
140
+ hub_strategy="checkpoint",
141
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  # Data collator
144
  data_collator = DataCollatorForLanguageModeling(
 
147
  )
148
 
149
  # Trainer
150
+ trainer = Trainer(
151
+ model=model,
152
+ args=training_args,
153
+ train_dataset=train_dataset,
154
+ eval_dataset=eval_dataset,
155
+ data_collator=data_collator,
156
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  # Treinar
159
  print("Iniciando treinamento...")
160
+ trainer.train()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ # Fazer push final
163
+ print(f"Fazendo push do modelo final para {OUTPUT_REPO}")
164
+ trainer.push_to_hub()
 
 
 
 
165
 
166
+ print("✅ Treinamento concluído!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
 
 
 
 
 
168
 
169
+ def analyze_schema(csv_description: str, model_path: str = None):
170
+ """
171
+ Função de inferência - modelo já 'obrigado' a pensar certo.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
+ Args:
174
+ csv_description: Descrição do dataset CSV para análise
175
+ model_path: Caminho para o modelo treinado (opcional, usa modelo atual se None)
176
+
177
+ Returns:
178
+ Análise EDA gerada pelo modelo treinado
179
+ """
180
+ # Se model_path for fornecido, carregar modelo treinado
181
+ inference_model = model
182
+ inference_tokenizer = tokenizer
 
 
 
183
 
184
+ if model_path:
185
+ print(f"Carregando modelo treinado de: {model_path}")
186
+ inference_tokenizer = AutoTokenizer.from_pretrained(model_path)
187
+ inference_model = AutoModelForCausalLM.from_pretrained(
188
+ model_path,
189
+ device_map="auto",
190
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
191
+ )
192
 
193
+ prompt = f"""<|system|>
194
+ {EDA_SYSTEM_PROMPT}
195
+ <|user|>
196
+ Analise o seguinte dataset:
197
+ {csv_description}
198
+ <|assistant|>
199
+ """
 
 
 
 
200
 
201
+ inputs = inference_tokenizer(prompt, return_tensors="pt").to(inference_model.device)
202
 
203
+ output = inference_model.generate(
204
+ **inputs,
205
+ max_new_tokens=1200,
206
+ temperature=0.2,
207
+ do_sample=False
208
+ )
 
 
 
 
 
 
209
 
210
+ return inference_tokenizer.decode(output[0], skip_special_tokens=True)
211
+
212
+
213
+ # Exemplo de uso após o treinamento:
214
+ # resultado = analyze_schema("Descrição do seu dataset aqui...")
215
+ # print(resultado)