Spaces:
Runtime error
Runtime error
| """ | |
| Advanced Training Script with Robustness Analysis, Uncertainty Estimation, and Multiclass Classification | |
| """ | |
| import argparse | |
| import sys | |
| import numpy as np | |
| import tensorflow as tf | |
| from pathlib import Path | |
| import json | |
| import os | |
| from datetime import datetime | |
| _REPO_ROOT = Path(__file__).resolve().parents[1] | |
| if str(_REPO_ROOT) not in sys.path: | |
| sys.path.append(str(_REPO_ROOT)) | |
| from src.segmentation_models import build_unet, build_attention_unet, dice_loss, combined_loss | |
| from src.robustness_analysis import RobustnessAnalyzer, UncertaintyEstimator, MulticlassSegmentationModel | |
| from src.kfold_validation import SegmentationKFoldValidator | |
| def train_with_robustness_analysis(config): | |
| """ | |
| Train model and perform robustness analysis | |
| Args: | |
| config: Configuration dictionary | |
| """ | |
| print("="*60) | |
| print("Training with Robustness Analysis") | |
| print("="*60) | |
| # Load data | |
| X_train, y_train, X_val, y_val, X_test, y_test = load_data(config) | |
| # Build and train model | |
| model = build_attention_unet( | |
| input_shape=tuple(config.get('image_size', [224, 224])) + (3,), | |
| num_classes=1 | |
| ) | |
| model.compile( | |
| optimizer=tf.keras.optimizers.Adam(learning_rate=config.get('learning_rate', 1e-4)), | |
| loss=combined_loss(), | |
| metrics=['accuracy'] | |
| ) | |
| # Train | |
| history = model.fit( | |
| X_train, y_train, | |
| validation_data=(X_val, y_val), | |
| epochs=config.get('epochs', 100), | |
| batch_size=config.get('batch_size', 16), | |
| callbacks=[ | |
| tf.keras.callbacks.EarlyStopping( | |
| monitor='val_loss', | |
| patience=15, | |
| restore_best_weights=True | |
| ) | |
| ] | |
| ) | |
| # Perform robustness analysis | |
| analyzer = RobustnessAnalyzer(model, input_shape=tuple(config.get('image_size', [224, 224])) + (3,)) | |
| corruption_types = config.get('corruption_types', [ | |
| 'gaussian_noise', 'salt_pepper_noise', 'gaussian_blur', | |
| 'brightness', 'contrast', 'rotation' | |
| ]) | |
| corruption_levels = config.get('corruption_levels', [0.01, 0.05, 0.1, 0.2, 0.3, 0.5]) | |
| robustness_results = analyzer.evaluate_all_corruptions( | |
| X_test, y_test, | |
| corruption_types=corruption_types, | |
| corruption_levels=corruption_levels | |
| ) | |
| # Save results | |
| save_dir = Path(config.get('save_dir', './robustness_training_results')) | |
| save_dir.mkdir(parents=True, exist_ok=True) | |
| analyzer.save_results(robustness_results, str(save_dir)) | |
| model.save(save_dir / 'robust_model.h5') | |
| print(f"\nRobustness Analysis Results:") | |
| for corruption_type, result in robustness_results.items(): | |
| print(f" {corruption_type}: Robustness Index = {result['robustness_index']:.3f}") | |
| return model, robustness_results | |
| def train_with_uncertainty_estimation(config): | |
| """ | |
| Train model and perform uncertainty estimation | |
| Args: | |
| config: Configuration dictionary | |
| """ | |
| print("="*60) | |
| print("Training with Uncertainty Estimation") | |
| print("="*60) | |
| # Load data | |
| X_train, y_train, X_val, y_val, X_test, y_test = load_data(config) | |
| # Build model with dropout for MC Dropout | |
| model = build_attention_unet( | |
| input_shape=tuple(config.get('image_size', [224, 224])) + (3,), | |
| num_classes=1, | |
| dropout_rate=config.get('dropout_rate', 0.3) # Higher dropout for better uncertainty | |
| ) | |
| model.compile( | |
| optimizer=tf.keras.optimizers.Adam(learning_rate=config.get('learning_rate', 1e-4)), | |
| loss=combined_loss(), | |
| metrics=['accuracy'] | |
| ) | |
| # Train | |
| history = model.fit( | |
| X_train, y_train, | |
| validation_data=(X_val, y_val), | |
| epochs=config.get('epochs', 100), | |
| batch_size=config.get('batch_size', 16), | |
| callbacks=[ | |
| tf.keras.callbacks.EarlyStopping( | |
| monitor='val_loss', | |
| patience=15, | |
| restore_best_weights=True | |
| ) | |
| ] | |
| ) | |
| # Perform uncertainty estimation | |
| num_samples = config.get('num_samples', 50) | |
| estimator = UncertaintyEstimator(model, num_samples=num_samples) | |
| # MC Dropout predictions | |
| mean_pred, uncertainty, predictions_array = estimator.mc_dropout_predict(X_test) | |
| # Get confidence intervals | |
| lower_bound, upper_bound = estimator.get_confidence_intervals( | |
| predictions_array, | |
| confidence_level=config.get('confidence_level', 0.95) | |
| ) | |
| # Save results | |
| save_dir = Path(config.get('save_dir', './uncertainty_training_results')) | |
| save_dir.mkdir(parents=True, exist_ok=True) | |
| # Visualize uncertainty for first few samples | |
| for i in range(min(5, len(X_test))): | |
| estimator.visualize_uncertainty( | |
| X_test[i], mean_pred[i], uncertainty[i], | |
| save_path=save_dir / f'uncertainty_sample_{i}.png' | |
| ) | |
| estimator.save_uncertainty_results(mean_pred, uncertainty, X_test[0], str(save_dir)) | |
| model.save(save_dir / 'uncertainty_model.h5') | |
| # Print uncertainty statistics | |
| print(f"\nUncertainty Statistics:") | |
| print(f" Mean uncertainty: {np.mean(uncertainty):.4f}") | |
| print(f" Max uncertainty: {np.max(uncertainty):.4f}") | |
| print(f" High uncertainty pixels (>0.5): {np.mean(uncertainty > 0.5):.2%}") | |
| print(f" Medium uncertainty pixels (0.2-0.5): {np.mean((uncertainty > 0.2) & (uncertainty <= 0.5)):.2%}") | |
| print(f" Low uncertainty pixels (<0.2): {np.mean(uncertainty <= 0.2):.2%}") | |
| return model, mean_pred, uncertainty | |
| def train_multiclass_model(config): | |
| """ | |
| Train multiclass segmentation model | |
| Args: | |
| config: Configuration dictionary | |
| """ | |
| print("="*60) | |
| print("Training Multiclass Segmentation Model") | |
| print("="*60) | |
| # Load multiclass data | |
| X_train, y_train, X_val, y_val, X_test, y_test = load_multiclass_data(config) | |
| # Create multiclass model | |
| num_classes = config.get('num_classes', 4) | |
| model = MulticlassSegmentationModel( | |
| input_shape=tuple(config.get('image_size', [224, 224])) + (3,), | |
| num_classes=num_classes, | |
| base_filters=config.get('base_filters', 64), | |
| dropout_rate=config.get('dropout_rate', 0.2) | |
| ) | |
| # Build and train | |
| model.build_model(use_attention=config.get('use_attention', True)) | |
| model.compile_model(learning_rate=config.get('learning_rate', 1e-4)) | |
| # Train | |
| history = model.train( | |
| X_train, y_train, | |
| X_val, y_val, | |
| epochs=config.get('epochs', 100), | |
| batch_size=config.get('batch_size', 16) | |
| ) | |
| # Evaluate | |
| metrics = model.evaluate_multiclass(X_test, y_test) | |
| # Save results | |
| save_dir = Path(config.get('save_dir', './multiclass_results')) | |
| save_dir.mkdir(parents=True, exist_ok=True) | |
| # Save metrics | |
| with open(save_dir / 'multiclass_metrics.json', 'w') as f: | |
| json.dump(metrics, f, indent=2) | |
| # Visualize predictions | |
| for i in range(min(5, len(X_test))): | |
| model.visualize_multiclass_prediction( | |
| X_test[i], y_test[i], model.predict(X_test[i:i+1])[0], | |
| save_path=save_dir / f'multiclass_prediction_{i}.png' | |
| ) | |
| model.model.save(save_dir / 'multiclass_model.h5') | |
| # Print results | |
| print(f"\nMulticlass Evaluation Results:") | |
| print(f" Overall Accuracy: {metrics['overall_accuracy']:.4f}") | |
| print(f"\nPer-Class Metrics:") | |
| for class_name, class_metrics in metrics['class_metrics'].items(): | |
| print(f" {class_name}:") | |
| print(f" Precision: {class_metrics['precision']:.4f}") | |
| print(f" Recall: {class_metrics['recall']:.4f}") | |
| print(f" F1 Score: {class_metrics['f1_score']:.4f}") | |
| print(f" IoU: {class_metrics['iou']:.4f}") | |
| print(f" Dice: {class_metrics['dice']:.4f}") | |
| print(f"\nMean Metrics:") | |
| for metric_name, value in metrics['mean_metrics'].items(): | |
| print(f" {metric_name}: {value:.4f}") | |
| return model, metrics | |
| def _load_split_images_masks(split_dir, image_size): | |
| """Load (image, mask) pairs from a split directory. | |
| Expects either: | |
| <split_dir>/images/*.jpg|png and <split_dir>/masks/*.jpg|png (paired by sorted order) | |
| or, if no masks dir is present, falls back to loading classification-style | |
| folders (tumor / no_tumor) and synthesising binary masks via Otsu thresholding | |
| on the intensity channel for tumor images (zero mask for no_tumor). | |
| """ | |
| import cv2 | |
| split_dir = Path(split_dir) | |
| images_dir = split_dir / 'images' | |
| masks_dir = split_dir / 'masks' | |
| def _read_image(path): | |
| img = cv2.imread(str(path)) | |
| if img is None: | |
| raise FileNotFoundError(f'Could not read image: {path}') | |
| img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| img = cv2.resize(img, image_size) | |
| return img | |
| def _read_mask(path): | |
| m = cv2.imread(str(path), cv2.IMREAD_GRAYSCALE) | |
| if m is None: | |
| raise FileNotFoundError(f'Could not read mask: {path}') | |
| m = cv2.resize(m, image_size, interpolation=cv2.INTER_NEAREST) | |
| m = (m.astype(np.float32) / 255.0 > 0.5).astype(np.float32) | |
| return np.expand_dims(m, axis=-1) | |
| if images_dir.exists() and masks_dir.exists(): | |
| image_paths = sorted([*images_dir.glob('*.png'), *images_dir.glob('*.jpg'), *images_dir.glob('*.jpeg')]) | |
| mask_paths = sorted([*masks_dir.glob('*.png'), *masks_dir.glob('*.jpg'), *masks_dir.glob('*.jpeg')]) | |
| if len(image_paths) != len(mask_paths): | |
| raise ValueError(f'Image/mask count mismatch in {split_dir}: {len(image_paths)} vs {len(mask_paths)}') | |
| X = np.stack([_read_image(p).astype(np.float32) for p in image_paths]) if image_paths else np.zeros((0, *image_size, 3), np.float32) | |
| y = np.stack([_read_mask(p) for p in mask_paths]) if mask_paths else np.zeros((0, *image_size, 1), np.float32) | |
| return X, y | |
| # Fallback: classification folders with synthesised masks via Otsu thresholding. | |
| tumor_dir = split_dir / 'tumor' | |
| no_tumor_dir = split_dir / 'no_tumor' | |
| if not tumor_dir.exists() and not no_tumor_dir.exists(): | |
| raise FileNotFoundError( | |
| f'No images/masks/ or tumor/no_tumor/ subfolders found under {split_dir}.' | |
| ) | |
| X_list = [] | |
| y_list = [] | |
| if tumor_dir.exists(): | |
| for p in sorted([*tumor_dir.glob('*.png'), *tumor_dir.glob('*.jpg'), *tumor_dir.glob('*.jpeg')]): | |
| img = _read_image(p) | |
| gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) | |
| _, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) | |
| mask = (mask.astype(np.float32) / 255.0) | |
| X_list.append(img.astype(np.float32)) | |
| y_list.append(np.expand_dims(mask, axis=-1)) | |
| if no_tumor_dir.exists(): | |
| for p in sorted([*no_tumor_dir.glob('*.png'), *no_tumor_dir.glob('*.jpg'), *no_tumor_dir.glob('*.jpeg')]): | |
| img = _read_image(p) | |
| X_list.append(img.astype(np.float32)) | |
| y_list.append(np.zeros((*image_size, 1), np.float32)) | |
| if not X_list: | |
| raise ValueError(f'No images found under {split_dir}.') | |
| return np.stack(X_list), np.stack(y_list) | |
| def load_data(config): | |
| """Load binary segmentation data from the real dataset directory. | |
| Reads dataset_real/{train,val,test}/. If ground-truth masks are absent, | |
| pseudo-masks are synthesised via Otsu thresholding (see | |
| _load_split_images_masks). This was previously a random-noise placeholder. | |
| """ | |
| data_dir = Path(config.get('data_dir', './dataset_real')) | |
| image_size = tuple(config.get('image_size', [224, 224])) | |
| train_dir = data_dir / 'train' | |
| val_dir = data_dir / 'val' | |
| test_dir = data_dir / 'test' | |
| if not train_dir.exists(): | |
| raise FileNotFoundError( | |
| f'Training directory not found: {train_dir}. ' | |
| 'Run prepare_real_dataset.py or point --data_dir to a directory with train/, val/, test/.' | |
| ) | |
| X_train, y_train = _load_split_images_masks(train_dir, image_size) | |
| if val_dir.exists(): | |
| X_val, y_val = _load_split_images_masks(val_dir, image_size) | |
| else: | |
| from sklearn.model_selection import train_test_split | |
| X_train, X_val, y_train, y_val = train_test_split( | |
| X_train, y_train, test_size=0.15, random_state=config.get('random_seed', 42) | |
| ) | |
| if test_dir.exists(): | |
| X_test, y_test = _load_split_images_masks(test_dir, image_size) | |
| else: | |
| X_test, y_test = X_val, y_val | |
| return X_train, y_train, X_val, y_val, X_test, y_test | |
| def load_multiclass_data(config): | |
| """Load multiclass segmentation data. | |
| Expects <data_dir>/<split>/images/*.png and <data_dir>/<split>/masks/*.png | |
| where mask pixel values encode the class id (0..num_classes-1). No real | |
| multiclass-segmentation data ships with this repo; this function will raise | |
| a clear error rather than silently train on noise (previous behaviour). | |
| """ | |
| import cv2 | |
| data_dir = Path(config.get('multiclass_data_dir', config.get('data_dir', './multiclass_dataset'))) | |
| image_size = tuple(config.get('image_size', [224, 224])) | |
| num_classes = config.get('num_classes', 4) | |
| def _read_split(split): | |
| split_dir = data_dir / split | |
| images_dir = split_dir / 'images' | |
| masks_dir = split_dir / 'masks' | |
| if not (images_dir.exists() and masks_dir.exists()): | |
| raise FileNotFoundError( | |
| f'Multiclass split missing images/ or masks/ at {split_dir}. ' | |
| 'Provide a dataset with per-pixel class id masks before training the multiclass model.' | |
| ) | |
| image_paths = sorted([*images_dir.glob('*.png'), *images_dir.glob('*.jpg')]) | |
| mask_paths = sorted([*masks_dir.glob('*.png'), *masks_dir.glob('*.jpg')]) | |
| if len(image_paths) != len(mask_paths): | |
| raise ValueError(f'{split}: {len(image_paths)} images vs {len(mask_paths)} masks.') | |
| Xs, ys = [], [] | |
| for ip, mp in zip(image_paths, mask_paths): | |
| img = cv2.cvtColor(cv2.imread(str(ip)), cv2.COLOR_BGR2RGB) | |
| img = cv2.resize(img, image_size).astype(np.float32) | |
| m = cv2.imread(str(mp), cv2.IMREAD_GRAYSCALE) | |
| m = cv2.resize(m, image_size, interpolation=cv2.INTER_NEAREST).astype(np.int32) | |
| m = np.clip(m, 0, num_classes - 1) | |
| Xs.append(img) | |
| ys.append(m) | |
| return np.stack(Xs), np.stack(ys) | |
| X_train, y_train = _read_split('train') | |
| X_val, y_val = _read_split('val') if (data_dir / 'val').exists() else (None, None) | |
| X_test, y_test = _read_split('test') if (data_dir / 'test').exists() else (None, None) | |
| if X_val is None: | |
| from sklearn.model_selection import train_test_split | |
| X_train, X_val, y_train, y_val = train_test_split( | |
| X_train, y_train, test_size=0.15, random_state=config.get('random_seed', 42) | |
| ) | |
| if X_test is None: | |
| X_test, y_test = X_val, y_val | |
| return X_train, y_train, X_val, y_val, X_test, y_test | |
| def main(): | |
| parser = argparse.ArgumentParser(description='Advanced Training with Robustness, Uncertainty, and Multiclass') | |
| # Data arguments | |
| parser.add_argument('--data_dir', type=str, default='./dataset', | |
| help='Directory containing training data') | |
| parser.add_argument('--image_size', type=int, nargs=2, default=[224, 224], | |
| help='Image size (height width)') | |
| # Model arguments | |
| parser.add_argument('--model_type', type=str, default='attention_unet', | |
| choices=['unet', 'attention_unet', 'res_unet'], | |
| help='Type of model to train') | |
| parser.add_argument('--base_filters', type=int, default=64, | |
| help='Number of base filters in model') | |
| parser.add_argument('--dropout_rate', type=float, default=0.2, | |
| help='Dropout rate') | |
| # Training arguments | |
| parser.add_argument('--epochs', type=int, default=100, | |
| help='Number of training epochs') | |
| parser.add_argument('--batch_size', type=int, default=16, | |
| help='Batch size') | |
| parser.add_argument('--learning_rate', type=float, default=1e-4, | |
| help='Learning rate') | |
| # Task-specific arguments | |
| parser.add_argument('--task', type=str, default='robustness', | |
| choices=['robustness', 'uncertainty', 'multiclass'], | |
| help='Task to perform') | |
| parser.add_argument('--num_classes', type=int, default=4, | |
| help='Number of classes for multiclass segmentation') | |
| parser.add_argument('--num_samples', type=int, default=50, | |
| help='Number of MC samples for uncertainty estimation') | |
| parser.add_argument('--corruption_types', type=str, nargs='+', | |
| default=['gaussian_noise', 'salt_pepper_noise', 'gaussian_blur', | |
| 'brightness', 'contrast', 'rotation'], | |
| help='Types of corruptions for robustness analysis') | |
| # General arguments | |
| parser.add_argument('--save_dir', type=str, default='./advanced_results', | |
| help='Directory to save models and results') | |
| parser.add_argument('--random_seed', type=int, default=42, | |
| help='Random seed') | |
| args = parser.parse_args() | |
| config = vars(args) | |
| # Set random seeds | |
| np.random.seed(args.random_seed) | |
| tf.random.set_seed(args.random_seed) | |
| # Run appropriate task | |
| if args.task == 'robustness': | |
| model, results = train_with_robustness_analysis(config) | |
| elif args.task == 'uncertainty': | |
| model, mean_pred, uncertainty = train_with_uncertainty_estimation(config) | |
| elif args.task == 'multiclass': | |
| model, metrics = train_multiclass_model(config) | |
| else: | |
| raise ValueError(f"Unknown task: {args.task}") | |
| print(f"\n{'='*60}") | |
| print(f"Training completed! Results saved to {args.save_dir}") | |
| print(f"{'='*60}") | |
| if __name__ == '__main__': | |
| main() |