Spaces:
Sleeping
Sleeping
| # tgn_data.py — Gerador de eventos temporais de e-commerce | |
| import numpy as np | |
| import torch | |
| import pandas as pd | |
| from datetime import datetime, timedelta | |
| import random | |
| def gerar_eventos_ecommerce( | |
| n_usuarios=300, n_comerciantes=80, n_eventos=3000, | |
| taxa_fraude=0.08, seed=42 | |
| ): | |
| """ | |
| Gera stream de transações de cartão de crédito e-commerce. | |
| Padrões de fraude embutidos: | |
| - Velocity attack: muitas transações em pouco tempo | |
| - Card testing: pequenas compras antes de compra grande | |
| - Geo anomaly: transações em locais impossíveis | |
| - Merchant anomaly: categorias incomuns para o usuário | |
| - Night transactions: compras em horário incomum | |
| """ | |
| random.seed(seed) | |
| np.random.seed(seed) | |
| # Perfis de usuário | |
| usuarios = [] | |
| for i in range(n_usuarios): | |
| is_fraudster = np.random.random() < 0.15 # 15% são alvos/fraudadores | |
| usuarios.append({ | |
| 'id': i, | |
| 'idade': np.random.randint(18, 75), | |
| 'limite': np.random.lognormal(8, 0.8), | |
| 'score_credito': np.random.beta(5, 2) if not is_fraudster else np.random.beta(2, 5), | |
| 'tempo_cliente_dias': np.random.exponential(500), | |
| 'regiao': np.random.choice(['SP', 'RJ', 'MG', 'RS', 'BA'], p=[0.35,0.25,0.15,0.15,0.10]), | |
| 'is_alvo': is_fraudster, | |
| 'ticket_medio': np.random.lognormal(5, 1), | |
| 'horario_tipico': np.random.randint(8, 22), # hora típica de compra | |
| }) | |
| # Perfis de comerciante | |
| categorias = ['eletronicos', 'viagem', 'alimentacao', 'roupas', 'jogos', 'farmacia'] | |
| comerciantes = [] | |
| for i in range(n_comerciantes): | |
| comerciantes.append({ | |
| 'id': i, | |
| 'categoria': np.random.choice(categorias, p=[0.20,0.15,0.25,0.20,0.10,0.10]), | |
| 'ticket_medio': np.random.lognormal(5.5, 1.2), | |
| 'risco_setor': np.random.beta(2, 8), | |
| }) | |
| # Gera eventos temporais | |
| inicio = datetime(2024, 1, 1) | |
| eventos = [] | |
| # Histórico por usuário para detectar padrões | |
| historico_usuario = {i: [] for i in range(n_usuarios)} | |
| for _ in range(n_eventos): | |
| usuario = random.choice(usuarios) | |
| uid = usuario['id'] | |
| comerciante = random.choice(comerciantes) | |
| mid = comerciante['id'] | |
| # Timestamp realista | |
| dias_offset = np.random.uniform(0, 180) | |
| hora_base = usuario['horario_tipico'] | |
| hora = int(np.clip(np.random.normal(hora_base, 3), 0, 23)) | |
| ts = inicio + timedelta(days=dias_offset, hours=hora, | |
| minutes=np.random.randint(0, 60)) | |
| timestamp = ts.timestamp() | |
| # Valor da transação | |
| valor_base = usuario['ticket_medio'] * comerciante['ticket_medio'] / 100 | |
| valor = max(1.0, np.random.lognormal(np.log(valor_base), 0.5)) | |
| # Features da transação | |
| n_tx_recentes = sum(1 for t in historico_usuario[uid] | |
| if timestamp - t['ts'] < 3600) # última hora | |
| # Determinar se é fraude | |
| prob_fraude = 0.0 | |
| fatores = [] | |
| if usuario['is_alvo']: | |
| prob_fraude += 0.15 | |
| # Velocity attack | |
| if n_tx_recentes >= 3: | |
| prob_fraude += 0.4 | |
| fatores.append('velocity') | |
| # Card testing (valor muito baixo seguido de alto) | |
| if len(historico_usuario[uid]) > 0: | |
| ultima = historico_usuario[uid][-1] | |
| if ultima['valor'] < 10 and valor > 500: | |
| prob_fraude += 0.35 | |
| fatores.append('card_testing') | |
| # Horário anômalo (madrugada) | |
| if hora < 4: | |
| prob_fraude += 0.25 | |
| fatores.append('night') | |
| # Categoria incomum | |
| cats_usuario = [h['categoria'] for h in historico_usuario[uid][-10:]] | |
| if cats_usuario and comerciante['categoria'] not in cats_usuario: | |
| if comerciante['categoria'] in ['eletronicos', 'viagem']: | |
| prob_fraude += 0.15 | |
| fatores.append('categoria_anomala') | |
| is_fraude = np.random.random() < min(prob_fraude, 0.95) | |
| evento = { | |
| 'evento_id': len(eventos), | |
| 'src': uid, # usuário | |
| 'dst': mid + n_usuarios, # comerciante (ids separados) | |
| 'timestamp': timestamp, | |
| 'valor': valor, | |
| 'valor_norm': np.log1p(valor) / 12.0, | |
| 'hora': hora, | |
| 'hora_norm': hora / 24.0, | |
| 'hora_anomala': 1.0 if hora < 6 else 0.0, | |
| 'categoria': comerciante['categoria'], | |
| 'cat_eletronicos': 1.0 if comerciante['categoria'] == 'eletronicos' else 0.0, | |
| 'cat_viagem': 1.0 if comerciante['categoria'] == 'viagem' else 0.0, | |
| 'n_tx_recentes_norm': min(n_tx_recentes / 10.0, 1.0), | |
| 'score_usuario': usuario['score_credito'], | |
| 'tempo_cliente_norm': min(usuario['tempo_cliente_dias'] / 1000.0, 1.0), | |
| 'risco_comerciante': comerciante['risco_setor'], | |
| 'fatores': fatores, | |
| 'label': int(is_fraude), | |
| } | |
| eventos.append(evento) | |
| historico_usuario[uid].append({ | |
| 'ts': timestamp, 'valor': valor, | |
| 'categoria': comerciante['categoria'] | |
| }) | |
| # Ordena por timestamp | |
| eventos.sort(key=lambda x: x['timestamp']) | |
| # Garante taxa de fraude aproximada | |
| df = pd.DataFrame(eventos) | |
| return df, usuarios, comerciantes, n_usuarios | |
| def df_para_tensores(df, n_usuarios, n_comerciantes): | |
| """Converte DataFrame de eventos para tensores PyTorch.""" | |
| n_nos = n_usuarios + n_comerciantes | |
| # Features de aresta (transação) | |
| edge_feats = torch.FloatTensor(df[[ | |
| 'valor_norm', 'hora_norm', 'hora_anomala', | |
| 'cat_eletronicos', 'cat_viagem', | |
| 'n_tx_recentes_norm', 'score_usuario', | |
| 'tempo_cliente_norm', 'risco_comerciante' | |
| ]].values) | |
| src = torch.LongTensor(df['src'].values) | |
| dst = torch.LongTensor(df['dst'].values) | |
| timestamps = torch.FloatTensor(df['timestamp'].values) | |
| labels = torch.LongTensor(df['label'].values) | |
| # Normaliza timestamps para [0, 1] | |
| t_min = timestamps.min() | |
| t_max = timestamps.max() | |
| timestamps_norm = (timestamps - t_min) / (t_max - t_min + 1e-8) | |
| # Split temporal: 60% train, 20% val, 20% test | |
| n = len(df) | |
| n_train = int(0.6 * n) | |
| n_val = int(0.8 * n) | |
| splits = { | |
| 'train': (0, n_train), | |
| 'val': (n_train, n_val), | |
| 'test': (n_val, n), | |
| } | |
| return { | |
| 'src': src, 'dst': dst, | |
| 'timestamps': timestamps_norm, | |
| 'timestamps_raw': timestamps, | |
| 'edge_feats': edge_feats, | |
| 'labels': labels, | |
| 'n_nos': n_nos, | |
| 'n_usuarios': n_usuarios, | |
| 'n_comerciantes': n_comerciantes, | |
| 'splits': splits, | |
| 'df': df, | |
| } |