Download engine/crog_engine_optimized.py from stumpferWinkel/grasp: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/stumpferWinkel/grasp/resolve/main/engine/crog_engine_optimized.py
- Command line
-
hf download hf://stumpferWinkel/grasp/engine/crog_engine_optimized.py
-
curl -L -o crog_engine_optimized.py https://huggingface.co/stumpferWinkel/grasp/resolve/main/engine/crog_engine_optimized.py
15.6 kB
| """ | |
| Optimized CROG Training Engine | |
| ============================== | |
| This is an optimized version of the CROG training engine with: | |
| 1. Reduced code duplication between train/validate functions | |
| 2. More efficient metric calculation | |
| 3. Better memory management | |
| 4. Cleaner code structure | |
| Key optimizations: | |
| - Unified metric calculation functions | |
| - Batch processing for validation metrics | |
| - Reduced tensor operations | |
| - Memory-efficient gradient accumulation | |
| """ | |
| import os | |
| import time | |
| from tqdm import tqdm | |
| from typing import Dict, Tuple, List, Optional, Any | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| import torch.cuda.amp as amp | |
| import torch.distributed as dist | |
| import torch.nn.functional as F | |
| import wandb | |
| from loguru import logger | |
| from utils.dataset import tokenize | |
| from utils.misc import AverageMeter, ProgressMeter, concat_all_gather | |
| from utils.grasp_eval import detect_grasps, calculate_jacquard_index | |
| class MetricCalculator: | |
| """ | |
| Centralized metric calculation to avoid code duplication. | |
| This class handles all metric computations for both training and validation, | |
| ensuring consistency and reducing redundancy. | |
| """ | |
| def calculate_iou(pred: torch.Tensor, target: torch.Tensor, threshold: float = 0.35) -> torch.Tensor: | |
| """ | |
| Calculate Intersection over Union (IoU) metric. | |
| Args: | |
| pred: Predicted mask (logits or probabilities) | |
| target: Ground truth mask | |
| threshold: Threshold for binarization | |
| Returns: | |
| IoU value | |
| """ | |
| # Ensure predictions are probabilities | |
| if pred.min() < 0 or pred.max() > 1: | |
| pred = torch.sigmoid(pred) | |
| # Binarize masks | |
| pred_binary = pred > threshold | |
| target_binary = target > 0.5 | |
| # Calculate IoU efficiently | |
| intersection = (pred_binary & target_binary).float().sum(dim=(1, 2, 3)) | |
| union = (pred_binary | target_binary).float().sum(dim=(1, 2, 3)) | |
| # Avoid division by zero | |
| iou = intersection / (union + 1e-6) | |
| return iou | |
| def calculate_precision(pred: torch.Tensor, target: torch.Tensor, threshold: float) -> torch.Tensor: | |
| """Calculate precision at a specific threshold.""" | |
| pred_binary = torch.sigmoid(pred) > threshold | |
| target_binary = target > 0.5 | |
| correct = (pred_binary == target_binary).float() | |
| precision = correct.mean() | |
| return precision | |
| def batch_inverse_transform( | |
| masks: List[np.ndarray], | |
| inverse_matrices: List[np.ndarray], | |
| original_sizes: List[Tuple[int, int]] | |
| ) -> List[np.ndarray]: | |
| """ | |
| Batch process inverse transformations for efficiency. | |
| This avoids repeated single-image processing in loops. | |
| """ | |
| transformed_masks = [] | |
| for mask, inv_mat, (h, w) in zip(masks, inverse_matrices, original_sizes): | |
| # Use INTER_LINEAR for masks (faster than INTER_CUBIC for binary data) | |
| inv_mask = cv2.warpAffine( | |
| mask, inv_mat, (w, h), | |
| flags=cv2.INTER_LINEAR, | |
| borderValue=0. | |
| ) | |
| transformed_masks.append(inv_mask) | |
| return transformed_masks | |
| def train_with_grasp_optimized( | |
| train_loader: torch.utils.data.DataLoader, | |
| model: torch.nn.Module, | |
| optimizer: torch.optim.Optimizer, | |
| scheduler: torch.optim.lr_scheduler._LRScheduler, | |
| scaler: amp.GradScaler, | |
| epoch: int, | |
| args: Dict[str, Any] | |
| ) -> Dict[str, float]: | |
| """ | |
| Optimized training function with grasp predictions. | |
| Improvements: | |
| - Unified metric tracking | |
| - Efficient memory usage | |
| - Cleaner code structure | |
| - Better error handling | |
| """ | |
| # Initialize meters for all metrics | |
| meters = { | |
| 'batch_time': AverageMeter('Batch', ':2.2f'), | |
| 'data_time': AverageMeter('Data', ':2.2f'), | |
| 'loss': AverageMeter('Loss', ':2.4f'), | |
| 'loss_qua': AverageMeter('Loss_qua', ':2.4f'), | |
| 'loss_sin': AverageMeter('Loss_sin', ':2.4f'), | |
| 'loss_cos': AverageMeter('Loss_cos', ':2.4f'), | |
| 'loss_wid': AverageMeter('Loss_wid', ':2.4f'), | |
| 'iou': AverageMeter('IoU', ':2.2f'), | |
| 'pr@50': AverageMeter('Prec@50', ':2.2f'), | |
| 'lr': AverageMeter('Lr', ':1.6f') | |
| } | |
| # Create progress meter with all metrics | |
| progress = ProgressMeter( | |
| len(train_loader), | |
| list(meters.values()), | |
| prefix=f"Training: Epoch=[{epoch}/{args.epochs}] " | |
| ) | |
| # Set model to training mode | |
| model.train() | |
| metric_calc = MetricCalculator() | |
| end = time.time() | |
| for i, data in enumerate(train_loader): | |
| # Record data loading time | |
| meters['data_time'].update(time.time() - end) | |
| # Prepare batch data efficiently | |
| batch_data = prepare_batch_data(data, args.gpu) | |
| # Forward pass with automatic mixed precision | |
| with amp.autocast(): | |
| pred, target, loss, loss_dict = model( | |
| batch_data['image'], | |
| batch_data['text'], | |
| batch_data['ins_mask'], | |
| batch_data['grasp_qua'], | |
| batch_data['grasp_sin'], | |
| batch_data['grasp_cos'], | |
| batch_data['grasp_wid'] | |
| ) | |
| # Backward pass with gradient scaling | |
| optimizer.zero_grad() | |
| scaler.scale(loss).backward() | |
| # Gradient clipping if specified | |
| if args.max_norm: | |
| scaler.unscale_(optimizer) | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), args.max_norm) | |
| scaler.step(optimizer) | |
| scaler.update() | |
| # Calculate metrics efficiently | |
| with torch.no_grad(): | |
| # Instance segmentation metrics | |
| iou = metric_calc.calculate_iou(pred[0], target[0], threshold=0.35) | |
| pr50 = metric_calc.calculate_precision(pred[0], target[0], threshold=0.5) | |
| # Reduce metrics across all processes | |
| if dist.is_initialized(): | |
| metrics_tensor = torch.stack([loss.detach(), iou.mean(), pr50]) | |
| dist.all_reduce(metrics_tensor) | |
| metrics_tensor /= dist.get_world_size() | |
| loss_reduced, iou_reduced, pr50_reduced = metrics_tensor | |
| else: | |
| loss_reduced = loss.detach() | |
| iou_reduced = iou.mean() | |
| pr50_reduced = pr50 | |
| # Update meters | |
| batch_size = batch_data['image'].size(0) | |
| meters['loss'].update(loss_reduced.item(), batch_size) | |
| meters['loss_qua'].update(loss_dict.get('m_qua', 0), batch_size) | |
| meters['loss_sin'].update(loss_dict.get('m_sin', 0), batch_size) | |
| meters['loss_cos'].update(loss_dict.get('m_cos', 0), batch_size) | |
| meters['loss_wid'].update(loss_dict.get('m_wid', 0), batch_size) | |
| meters['iou'].update(iou_reduced.item(), batch_size) | |
| meters['pr@50'].update(pr50_reduced.item(), batch_size) | |
| meters['lr'].update(scheduler.get_last_lr()[-1]) | |
| # Record batch time | |
| meters['batch_time'].update(time.time() - end) | |
| end = time.time() | |
| # Log progress | |
| if (i + 1) % args.print_freq == 0: | |
| progress.display(i + 1) | |
| # WandB logging (only on main process) | |
| if dist.get_rank() in [-1, 0]: | |
| log_metrics_to_wandb(meters, epoch, i, len(train_loader)) | |
| # Return epoch averages | |
| return {name: meter.avg for name, meter in meters.items()} | |
| def validate_with_grasp_optimized( | |
| val_loader: torch.utils.data.DataLoader, | |
| model: torch.nn.Module, | |
| epoch: int, | |
| args: Dict[str, Any] | |
| ) -> Tuple[float, Dict[str, float], List[float]]: | |
| """ | |
| Optimized validation function with grasp evaluation. | |
| Improvements: | |
| - Batch processing of inverse transforms | |
| - Vectorized metric calculations | |
| - Memory-efficient processing | |
| """ | |
| model.eval() | |
| metric_calc = MetricCalculator() | |
| # Initialize metric storage | |
| all_ious = [] | |
| j_indices = {1: [], 5: []} # For 1 and 5 grasps | |
| # Use tqdm for progress tracking | |
| pbar = tqdm(val_loader, desc=f'Validation Epoch {epoch}') | |
| for data in pbar: | |
| # Prepare batch data | |
| batch_data = prepare_batch_data(data, torch.cuda.current_device()) | |
| # Forward pass | |
| pred, target = model( | |
| batch_data['image'], | |
| batch_data['text'], | |
| batch_data['ins_mask'], | |
| batch_data['grasp_qua'], | |
| batch_data['grasp_sin'], | |
| batch_data['grasp_cos'], | |
| batch_data['grasp_wid'] | |
| ) | |
| # Process predictions | |
| predictions = process_predictions(pred, batch_data['image'].shape[-2:]) | |
| # Batch process inverse transformations | |
| batch_size = predictions['ins_mask'].shape[0] | |
| # Convert to numpy for CV2 operations | |
| ins_masks_np = predictions['ins_mask'].cpu().numpy() | |
| qua_masks_np = predictions['grasp_qua'].cpu().numpy() | |
| sin_masks_np = predictions['grasp_sin'].cpu().numpy() | |
| cos_masks_np = predictions['grasp_cos'].cpu().numpy() | |
| wid_masks_np = predictions['grasp_wid'].cpu().numpy() | |
| # Process each sample in the batch | |
| for idx in range(batch_size): | |
| # Get sample data | |
| inv_mat = data['inverse'][idx] | |
| h, w = data['ori_size'][idx] | |
| grasp_target = data['grasps'][idx] | |
| # Inverse transform masks | |
| masks_to_transform = [ | |
| ins_masks_np[idx], | |
| qua_masks_np[idx].squeeze(), | |
| sin_masks_np[idx].squeeze(), | |
| cos_masks_np[idx].squeeze(), | |
| wid_masks_np[idx].squeeze() | |
| ] | |
| transformed = metric_calc.batch_inverse_transform( | |
| masks_to_transform, | |
| [inv_mat] * 5, | |
| [(h, w)] * 5 | |
| ) | |
| ins_mask_inv = transformed[0] > 0.35 | |
| # Calculate IoU | |
| ins_target_np = target[0][idx].squeeze().cpu().numpy() | |
| ins_target_inv = cv2.warpAffine(ins_target_np, inv_mat, (w, h)) | |
| inter = np.logical_and(ins_mask_inv, ins_target_inv) | |
| union = np.logical_or(ins_mask_inv, ins_target_inv) | |
| iou = np.sum(inter) / (np.sum(union) + 1e-6) | |
| all_ious.append(iou) | |
| # Calculate grasp metrics if enabled | |
| if args.use_grasp_masks: | |
| for num_grasps in [1, 5]: | |
| grasp_preds, _ = detect_grasps( | |
| transformed[1], transformed[2], | |
| transformed[3], transformed[4], | |
| num_grasps | |
| ) | |
| j_index = calculate_jacquard_index(grasp_preds, grasp_target) | |
| j_indices[num_grasps].append(j_index) | |
| # Aggregate metrics | |
| iou_tensor = torch.tensor(all_ious, device='cuda') | |
| if dist.is_initialized(): | |
| iou_tensor = concat_all_gather(iou_tensor) | |
| # Calculate precision at different thresholds | |
| prec_dict = {} | |
| for threshold in torch.arange(0.5, 1.0, 0.1): | |
| prec = (iou_tensor > threshold).float().mean() | |
| prec_dict[f'Pr@{int(threshold * 100)}'] = prec.item() | |
| # Calculate average metrics | |
| avg_iou = iou_tensor.mean().item() | |
| # Calculate J-indices | |
| j_index_results = [] | |
| if args.use_grasp_masks: | |
| for num_grasps in [1, 5]: | |
| if j_indices[num_grasps]: | |
| avg_j = np.mean(j_indices[num_grasps]) | |
| else: | |
| avg_j = 0.0 | |
| j_index_results.append(avg_j) | |
| else: | |
| j_index_results = [0.0, 0.0] | |
| # Log results | |
| log_validation_results(epoch, args.epochs, avg_iou, prec_dict, j_index_results) | |
| return avg_iou, prec_dict, j_index_results | |
| def prepare_batch_data(data: Dict[str, Any], device: int) -> Dict[str, torch.Tensor]: | |
| """ | |
| Efficiently prepare batch data for GPU processing. | |
| This function consolidates data preparation logic to avoid duplication. | |
| """ | |
| return { | |
| 'image': data['img'].cuda(device, non_blocking=True), | |
| 'text': data['word_vec'].cuda(device, non_blocking=True), | |
| 'ins_mask': data['mask'].cuda(device, non_blocking=True).unsqueeze(1), | |
| 'grasp_qua': data['grasp_masks']['qua'].cuda(device, non_blocking=True).unsqueeze(1), | |
| 'grasp_sin': data['grasp_masks']['sin'].cuda(device, non_blocking=True).unsqueeze(1), | |
| 'grasp_cos': data['grasp_masks']['cos'].cuda(device, non_blocking=True).unsqueeze(1), | |
| 'grasp_wid': data['grasp_masks']['wid'].cuda(device, non_blocking=True).unsqueeze(1) | |
| } | |
| def process_predictions( | |
| pred: Tuple[torch.Tensor, ...], | |
| target_size: Tuple[int, int] | |
| ) -> Dict[str, torch.Tensor]: | |
| """ | |
| Process model predictions with proper interpolation and activation. | |
| This consolidates prediction processing logic. | |
| """ | |
| # Apply sigmoid activation | |
| ins_mask = torch.sigmoid(pred[0]) | |
| grasp_qua = torch.sigmoid(pred[1]) | |
| grasp_wid = torch.sigmoid(pred[4]) | |
| # Interpolate if needed | |
| if ins_mask.shape[-2:] != target_size: | |
| ins_mask = F.interpolate(ins_mask, size=target_size, mode='bicubic', align_corners=True) | |
| grasp_qua = F.interpolate(grasp_qua, size=target_size, mode='bicubic', align_corners=True) | |
| pred_sin = F.interpolate(pred[2], size=target_size, mode='bicubic', align_corners=True) | |
| pred_cos = F.interpolate(pred[3], size=target_size, mode='bicubic', align_corners=True) | |
| grasp_wid = F.interpolate(grasp_wid, size=target_size, mode='bicubic', align_corners=True) | |
| else: | |
| pred_sin = pred[2] | |
| pred_cos = pred[3] | |
| return { | |
| 'ins_mask': ins_mask.squeeze(1), | |
| 'grasp_qua': grasp_qua.squeeze(1), | |
| 'grasp_sin': pred_sin.squeeze(1), | |
| 'grasp_cos': pred_cos.squeeze(1), | |
| 'grasp_wid': grasp_wid.squeeze(1) | |
| } | |
| def log_metrics_to_wandb( | |
| meters: Dict[str, AverageMeter], | |
| epoch: int, | |
| batch_idx: int, | |
| total_batches: int | |
| ): | |
| """Centralized WandB logging function.""" | |
| step = epoch * total_batches + batch_idx + 1 | |
| metrics = { | |
| "time/batch": meters['batch_time'].val, | |
| "time/data": meters['data_time'].val, | |
| "training/lr": meters['lr'].val, | |
| "training/loss": meters['loss'].val, | |
| "training/loss_qua": meters['loss_qua'].val, | |
| "training/loss_sin": meters['loss_sin'].val, | |
| "training/loss_cos": meters['loss_cos'].val, | |
| "training/loss_wid": meters['loss_wid'].val, | |
| "training/iou": meters['iou'].val, | |
| "training/prec@50": meters['pr@50'].val, | |
| } | |
| wandb.log(metrics, step=step) | |
| def log_validation_results( | |
| epoch: int, | |
| total_epochs: int, | |
| iou: float, | |
| prec_dict: Dict[str, float], | |
| j_indices: List[float] | |
| ): | |
| """Log validation results to console.""" | |
| # Build precision string | |
| prec_str = " ".join([f"{k}: {v*100:.2f}" for k, v in prec_dict.items()]) | |
| # Log main metrics | |
| logger.info( | |
| f"Evaluation: Epoch=[{epoch}/{total_epochs}] " | |
| f"IoU={iou*100:.2f} " | |
| f"J_index@1: {j_indices[0]*100:.2f} " | |
| f"J_index@5: {j_indices[1]*100:.2f} " | |
| f"{prec_str}" | |
| ) |