eda_trainning_lora / scripts /analyze_training_metrics.py
Ademir
Initial clean commit: scripts and config without logs
d4a00b2
#!/usr/bin/env python3
"""
Script para analisar métricas de treinamento do TensorBoard e gerar relatório.
Uso:
python analyze_training_metrics.py [diretório_com_eventos]
Exemplo:
python analyze_training_metrics.py ./results
python analyze_training_metrics.py .
"""
import os
import sys
import json
import glob
from pathlib import Path
from typing import Dict, List, Optional
from datetime import datetime
try:
from tensorboard.backend.event_processing.event_accumulator import EventAccumulator
TENSORBOARD_AVAILABLE = True
except ImportError:
TENSORBOARD_AVAILABLE = False
print("⚠️ TensorBoard não instalado. Instalando...")
print("Execute: pip install tensorboard")
def find_event_files(directory: str) -> List[str]:
"""Encontra arquivos de eventos do TensorBoard."""
pattern = os.path.join(directory, "events.out.tfevents.*")
files = glob.glob(pattern)
return sorted(files, key=os.path.getmtime, reverse=True)
def extract_config_from_event_file(filepath: str) -> Dict:
"""Extrai configuração do arquivo de eventos (formato binário com texto)."""
config = {}
try:
with open(filepath, 'rb') as f:
content = f.read()
text_content = content.decode('utf-8', errors='ignore')
# Extrair args - método mais robusto
if 'args/text_summary' in text_content:
# Encontrar início do JSON
start_idx = text_content.find('args/text_summary')
if start_idx != -1:
# Procurar pela primeira chave {
json_start = text_content.find('{', start_idx)
if json_start != -1:
# Encontrar o final do JSON (última })
brace_count = 0
json_end = json_start
for i in range(json_start, len(text_content)):
if text_content[i] == '{':
brace_count += 1
elif text_content[i] == '}':
brace_count -= 1
if brace_count == 0:
json_end = i + 1
break
if json_end > json_start:
try:
args_json = text_content[json_start:json_end]
config['training_args'] = json.loads(args_json)
except json.JSONDecodeError as e:
print(f"⚠️ Erro ao parsear args: {e}")
# Extrair model_config - método mais robusto
if 'model_config/text_summary' in text_content:
start_idx = text_content.find('model_config/text_summary')
if start_idx != -1:
json_start = text_content.find('{', start_idx)
if json_start != -1:
brace_count = 0
json_end = json_start
for i in range(json_start, len(text_content)):
if text_content[i] == '{':
brace_count += 1
elif text_content[i] == '}':
brace_count -= 1
if brace_count == 0:
json_end = i + 1
break
if json_end > json_start:
try:
model_json = text_content[json_start:json_end]
config['model_config'] = json.loads(model_json)
except json.JSONDecodeError as e:
print(f"⚠️ Erro ao parsear model_config: {e}")
except Exception as e:
print(f"⚠️ Erro ao ler arquivo {filepath}: {e}")
return config
def analyze_metrics_with_tensorboard(directory: str) -> Dict:
"""Analisa métricas usando TensorBoard EventAccumulator."""
if not TENSORBOARD_AVAILABLE:
return {}
try:
ea = EventAccumulator(directory)
ea.Reload()
metrics = {
'scalars': {},
'tags': ea.Tags()
}
# Extrair métricas escalares
for tag in ea.Tags().get('scalars', []):
scalars = ea.Scalars(tag)
if scalars:
metrics['scalars'][tag] = {
'count': len(scalars),
'latest': scalars[-1].value if scalars else None,
'latest_step': scalars[-1].step if scalars else None,
'first': scalars[0].value if scalars else None,
'first_step': scalars[0].step if scalars else None,
'min': min(s.value for s in scalars) if scalars else None,
'max': max(s.value for s in scalars) if scalars else None,
'all_values': [s.value for s in scalars],
'all_steps': [s.step for s in scalars]
}
return metrics
except Exception as e:
print(f"⚠️ Erro ao analisar com TensorBoard: {e}")
return {}
def calculate_training_stats(metrics: Dict) -> Dict:
"""Calcula estatísticas úteis sobre o treinamento."""
stats = {}
scalars = metrics.get('scalars', {})
# Loss de treinamento
if 'train/loss' in scalars:
train_loss = scalars['train/loss']
stats['train_loss'] = {
'current': train_loss['latest'],
'initial': train_loss['first'],
'improvement': train_loss['first'] - train_loss['latest'] if train_loss['first'] and train_loss['latest'] else None,
'improvement_pct': ((train_loss['first'] - train_loss['latest']) / train_loss['first'] * 100) if train_loss['first'] and train_loss['latest'] and train_loss['first'] > 0 else None,
'min': train_loss['min'],
'max': train_loss['max'],
'steps': train_loss['count']
}
# Loss de validação
if 'eval/loss' in scalars:
eval_loss = scalars['eval/loss']
stats['eval_loss'] = {
'current': eval_loss['latest'],
'initial': eval_loss['first'],
'improvement': eval_loss['first'] - eval_loss['latest'] if eval_loss['first'] and eval_loss['latest'] else None,
'improvement_pct': ((eval_loss['first'] - eval_loss['latest']) / eval_loss['first'] * 100) if eval_loss['first'] and eval_loss['latest'] and eval_loss['first'] > 0 else None,
'min': eval_loss['min'],
'max': eval_loss['max'],
'steps': eval_loss['count']
}
# Learning rate
if 'train/learning_rate' in scalars:
lr = scalars['train/learning_rate']
stats['learning_rate'] = {
'current': lr['latest'],
'initial': lr['first'],
'final': lr['latest'],
'steps': lr['count']
}
# Overfitting check
if 'train/loss' in scalars and 'eval/loss' in scalars:
train_latest = scalars['train/loss']['latest']
eval_latest = scalars['eval/loss']['latest']
if train_latest and eval_latest:
gap = eval_latest - train_latest
stats['overfitting'] = {
'gap': gap,
'gap_pct': (gap / train_latest * 100) if train_latest > 0 else None,
'status': 'overfitting' if gap > train_latest * 0.2 else 'ok' if gap > 0 else 'underfitting'
}
return stats
def generate_report(config: Dict, metrics: Dict, stats: Dict, output_file: Optional[str] = None) -> str:
"""Gera relatório formatado das métricas."""
report = []
report.append("=" * 80)
report.append("RELATÓRIO DE MÉTRICAS DE TREINAMENTO")
report.append("=" * 80)
report.append(f"Gerado em: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
report.append("")
# Configuração do Modelo
model_config = config.get('model_config', {})
if model_config:
report.append("🤖 CONFIGURAÇÃO DO MODELO")
report.append("-" * 80)
report.append(f" Arquitetura: {model_config.get('architectures', ['N/A'])[0]}")
report.append(f" Tipo: {model_config.get('model_type', 'N/A')}")
report.append(f" Dimensões Ocultas: {model_config.get('hidden_size', 'N/A')}")
report.append(f" Camadas: {model_config.get('num_hidden_layers', 'N/A')}")
report.append(f" Cabeças de Atenção: {model_config.get('num_attention_heads', 'N/A')}")
report.append(f" Contexto Máximo: {model_config.get('max_position_embeddings', 'N/A')} tokens")
report.append(f" Vocabulário: {model_config.get('vocab_size', 'N/A')} tokens")
quant = model_config.get('quantization_config', {})
if quant:
report.append(f" Quantização: {'4-bit NF4' if quant.get('load_in_4bit') else '8-bit' if quant.get('load_in_8bit') else 'Nenhuma'}")
report.append("")
# Configuração de Treinamento
training_args = config.get('training_args', {})
if training_args:
report.append("⚙️ CONFIGURAÇÃO DE TREINAMENTO")
report.append("-" * 80)
report.append(f" Learning Rate: {training_args.get('learning_rate', 'N/A')}")
report.append(f" Batch Size: {training_args.get('per_device_train_batch_size', 'N/A')} × {training_args.get('gradient_accumulation_steps', 1)} = {training_args.get('per_device_train_batch_size', 0) * training_args.get('gradient_accumulation_steps', 1)}")
report.append(f" Épocas: {training_args.get('num_train_epochs', 'N/A')}")
report.append(f" Warmup Steps: {training_args.get('warmup_steps', 'N/A')}")
report.append(f" Logging Steps: {training_args.get('logging_steps', 'N/A')}")
report.append(f" Eval Steps: {training_args.get('eval_steps', 'N/A')}")
report.append(f" Save Steps: {training_args.get('save_steps', 'N/A')}")
report.append(f" FP16: {'✅' if training_args.get('fp16') else '❌'}")
report.append(f" Push to Hub: {'✅' if training_args.get('push_to_hub') else '❌'}")
if training_args.get('hub_model_id'):
report.append(f" Hub Model ID: {training_args.get('hub_model_id')}")
report.append("")
# Estatísticas de Treinamento
if stats:
report.append("📊 ESTATÍSTICAS DE TREINAMENTO")
report.append("-" * 80)
if 'train_loss' in stats:
tl = stats['train_loss']
report.append(" 📉 Loss de Treinamento:")
report.append(f" • Atual: {tl['current']:.6f}" if tl['current'] else " • Atual: N/A")
report.append(f" • Inicial: {tl['initial']:.6f}" if tl['initial'] else " • Inicial: N/A")
if tl.get('improvement'):
report.append(f" • Melhoria: {tl['improvement']:.6f} ({tl['improvement_pct']:.2f}%)")
report.append(f" • Mínimo: {tl['min']:.6f}" if tl['min'] else " • Mínimo: N/A")
report.append(f" • Máximo: {tl['max']:.6f}" if tl['max'] else " • Máximo: N/A")
report.append(f" • Steps registrados: {tl['steps']}")
report.append("")
if 'eval_loss' in stats:
el = stats['eval_loss']
report.append(" 📉 Loss de Validação:")
report.append(f" • Atual: {el['current']:.6f}" if el['current'] else " • Atual: N/A")
report.append(f" • Inicial: {el['initial']:.6f}" if el['initial'] else " • Inicial: N/A")
if el.get('improvement'):
report.append(f" • Melhoria: {el['improvement']:.6f} ({el['improvement_pct']:.2f}%)")
report.append(f" • Mínimo: {el['min']:.6f}" if el['min'] else " • Mínimo: N/A")
report.append(f" • Steps registrados: {el['steps']}")
report.append("")
if 'learning_rate' in stats:
lr = stats['learning_rate']
report.append(" 📈 Learning Rate:")
report.append(f" • Atual: {lr['current']:.2e}" if lr['current'] else " • Atual: N/A")
report.append(f" • Inicial: {lr['initial']:.2e}" if lr['initial'] else " • Inicial: N/A")
report.append("")
if 'overfitting' in stats:
of = stats['overfitting']
report.append(" ⚠️ Análise de Overfitting:")
report.append(f" • Gap (eval - train): {of['gap']:.6f}" if of['gap'] else " • Gap: N/A")
if of.get('gap_pct'):
report.append(f" • Gap percentual: {of['gap_pct']:.2f}%")
report.append(f" • Status: {of['status'].upper()}")
if of['status'] == 'overfitting':
report.append(" • ⚠️ ATENÇÃO: Modelo pode estar overfitting!")
report.append(" • Considere: early stopping, mais dados, ou regularização")
elif of['status'] == 'underfitting':
report.append(" • ⚠️ ATENÇÃO: Modelo pode estar underfitting!")
report.append(" • Considere: mais épocas, modelo maior, ou learning rate maior")
report.append("")
# Métricas Disponíveis
tags = metrics.get('tags', {})
if tags.get('scalars'):
report.append("📋 MÉTRICAS DISPONÍVEIS")
report.append("-" * 80)
for tag in sorted(tags['scalars']):
scalar = metrics['scalars'].get(tag, {})
latest = scalar.get('latest')
if latest is not None:
report.append(f" • {tag}: {latest:.6f} (step {scalar.get('latest_step', 'N/A')})")
report.append("")
# Recomendações
report.append("💡 RECOMENDAÇÕES")
report.append("-" * 80)
if not stats:
report.append(" ⚠️ Nenhuma métrica de treinamento encontrada ainda.")
report.append(" • Aguarde o treinamento avançar alguns steps")
report.append(" • Os logs são salvos a cada step (logging_steps=1)")
else:
if 'train_loss' in stats and stats['train_loss'].get('current'):
current_loss = stats['train_loss']['current']
if current_loss > 2.0:
report.append(" • Loss ainda alto (>2.0). Treinamento pode precisar mais tempo.")
elif current_loss < 0.5:
report.append(" • Loss baixo (<0.5). Modelo pode estar convergindo bem.")
if 'overfitting' in stats:
if stats['overfitting']['status'] == 'overfitting':
report.append(" • ⚠️ Detecção de overfitting. Considere:")
report.append(" - Early stopping")
report.append(" - Aumentar dataset de treinamento")
report.append(" - Aumentar dropout ou regularização")
elif stats['overfitting']['status'] == 'underfitting':
report.append(" • ⚠️ Possível underfitting. Considere:")
report.append(" - Mais épocas de treinamento")
report.append(" - Aumentar learning rate")
report.append(" - Modelo com mais parâmetros")
report.append("")
report.append("=" * 80)
report_text = "\n".join(report)
# Salvar em arquivo se especificado
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(report_text)
print(f"✅ Relatório salvo em: {output_file}")
return report_text
def main():
"""Função principal."""
if len(sys.argv) > 1:
directory = sys.argv[1]
else:
directory = "."
if not os.path.isdir(directory):
print(f"❌ Erro: Diretório '{directory}' não encontrado")
sys.exit(1)
print(f"🔍 Analisando métricas em: {os.path.abspath(directory)}")
# Encontrar arquivos de eventos
event_files = find_event_files(directory)
if not event_files:
print(f"⚠️ Nenhum arquivo de eventos encontrado em '{directory}'")
print(" Procure por arquivos: events.out.tfevents.*")
sys.exit(1)
print(f"📄 Encontrados {len(event_files)} arquivo(s) de eventos")
# Extrair configuração do primeiro arquivo
config = extract_config_from_event_file(event_files[0])
# Analisar métricas com TensorBoard
if TENSORBOARD_AVAILABLE:
print("📊 Analisando métricas com TensorBoard...")
metrics = analyze_metrics_with_tensorboard(directory)
else:
print("⚠️ TensorBoard não disponível. Instale com: pip install tensorboard")
metrics = {}
# Calcular estatísticas
stats = calculate_training_stats(metrics)
# Gerar relatório
output_file = os.path.join(directory, "training_metrics_report.txt")
report = generate_report(config, metrics, stats, output_file)
# Exibir relatório
print("\n" + report)
# Gerar também JSON para uso programático
json_output = {
'config': config,
'metrics': {
'scalars': {
k: {
'latest': v.get('latest'),
'latest_step': v.get('latest_step'),
'count': v.get('count')
}
for k, v in metrics.get('scalars', {}).items()
}
},
'stats': stats,
'generated_at': datetime.now().isoformat()
}
json_file = os.path.join(directory, "training_metrics.json")
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(json_output, f, indent=2, ensure_ascii=False)
print(f"✅ Métricas em JSON salvas em: {json_file}")
if __name__ == "__main__":
main()