{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# =======================\n# IMPORTS & GPU SETUP\n# =======================\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.metrics import classification_report, confusion_matrix, roc_auc_score, roc_curve\n\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras import regularizers\nfrom tensorflow.keras import mixed_precision\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# =======================\n# GPU CHECK & CONFIG\n# =======================\n# Set TensorFlow to use mixed precision for faster GPU training\nmixed_precision.set_global_policy('mixed_float16')\n\n# Check available GPUs\ngpus = tf.config.list_physical_devices('GPU')\nprint(\"Num GPUs Available:\", len(gpus))\nif gpus:\n    print(\"GPU Name:\", gpus[0].name)\nelse:\n    print(\"No GPU detected. Please enable GPU in your runtime.\")\n\n# =======================\n# REPRODUCIBILITY\n# =======================\ntf.random.set_seed(42)\nnp.random.seed(42)\n\n# =======================\n# NOTES\n# =======================\n# - TensorFlow will automatically utilize GPU if available\n# - Mixed precision reduces memory usage and speeds up training\n# - Use batch sizes appropriate for your GPU (usually 16-64 for EfficientNetB0)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:26:12.517759Z","iopub.execute_input":"2025-12-29T14:26:12.518022Z","iopub.status.idle":"2025-12-29T14:26:26.672320Z","shell.execute_reply.started":"2025-12-29T14:26:12.517998Z","shell.execute_reply":"2025-12-29T14:26:26.670759Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"# =============================================================================\n# 2️⃣ LOAD AND PREPROCESS DATA\n# =============================================================================\nimport os\nimport pandas as pd\nimport numpy as np\n\n# Dataset paths\nBASE_DIR = \"/kaggle/input/siim-isic-melanoma-classification\"\nTRAIN_IMAGES_DIR = os.path.join(BASE_DIR, \"jpeg/train\")\nTEST_IMAGES_DIR = os.path.join(BASE_DIR, \"jpeg/test\")\nTRAIN_CSV_PATH = os.path.join(BASE_DIR, \"train.csv\")\nTEST_CSV_PATH = os.path.join(BASE_DIR, \"test.csv\")\n\n# Load metadata\nprint(\"📊 Loading dataset metadata...\")\ntrain_df = pd.read_csv(TRAIN_CSV_PATH)\ntest_df = pd.read_csv(TEST_CSV_PATH)\n\n# Create full image paths\ntrain_df['image_path'] = train_df['image_name'].apply(lambda x: os.path.join(TRAIN_IMAGES_DIR, f\"{x}.jpg\"))\ntest_df['image_path'] = test_df['image_name'].apply(lambda x: os.path.join(TEST_IMAGES_DIR, f\"{x}.jpg\"))\n\n# Handle missing values\ntrain_df['age_approx'] = train_df['age_approx'].fillna(train_df['age_approx'].median()).astype(np.float32)\ntrain_df['sex'] = train_df['sex'].fillna('unknown')\ntrain_df['anatom_site_general_challenge'] = train_df['anatom_site_general_challenge'].fillna('unknown')\n\n# Basic dataset info\nprint(f\"✅ Training set size: {len(train_df)} images\")\nprint(f\"✅ Test set size: {len(test_df)} images\")\nprint(f\"✅ Malignant cases: {train_df['target'].sum()} ({train_df['target'].mean()*100:.2f}%)\")\n\n# Optional: Quick visualization of class distribution\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nsns.countplot(x='target', data=train_df)\nplt.title(\"Class Distribution\")\nplt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:26:31.274723Z","iopub.execute_input":"2025-12-29T14:26:31.275685Z","iopub.status.idle":"2025-12-29T14:26:31.625019Z","shell.execute_reply.started":"2025-12-29T14:26:31.275659Z","shell.execute_reply":"2025-12-29T14:26:31.624080Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 3️⃣ EXPLORATORY DATA ANALYSIS (EDA)\n# =============================================================================\nprint(\"\\n🔍 Performing Exploratory Data Analysis...\")\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nplt.figure(figsize=(18, 10))\n\n# Plot 1: Class distribution (pie chart)\nplt.subplot(2, 3, 1)\nclass_counts = train_df['target'].value_counts()\nplt.pie(class_counts.values, labels=['Benign (0)', 'Malignant (1)'], autopct='%1.1f%%', startangle=90)\nplt.title('Class Distribution')\n\n# Plot 2: Age distribution by diagnosis\nplt.subplot(2, 3, 2)\nsns.histplot(data=train_df, x='age_approx', hue='target', bins=30, alpha=0.6, palette=['green','red'])\nplt.title('Age Distribution by Diagnosis')\nplt.xlabel('Age')\n\n# Plot 3: Anatomical site distribution\nplt.subplot(2, 3, 3)\nsite_counts = train_df['anatom_site_general_challenge'].value_counts().head(6)\nsns.barplot(x=site_counts.index, y=site_counts.values, palette='viridis')\nplt.title('Top Anatomical Sites')\nplt.xticks(rotation=45)\n\n# Plot 4: Sex distribution by diagnosis\nplt.subplot(2, 3, 4)\nsex_diagnosis = pd.crosstab(train_df['sex'], train_df['target'])\nsex_diagnosis.plot(kind='bar', stacked=True, ax=plt.gca(), color=['green','red'])\nplt.title('Sex Distribution by Diagnosis')\nplt.legend(['Benign', 'Malignant'])\nplt.xticks(rotation=0)\n\n# Plot 5: Sample images placeholder\nplt.subplot(2, 3, 5)\nplt.text(0.5, 0.5, 'Sample Images Preview\\n(2 benign + 2 malignant)', \n         ha='center', va='center', fontsize=12, transform=plt.gca().transAxes)\nplt.axis('off')\nplt.title('Sample Images Preview')\n\n# Plot 6: Patient-level lesions distribution\nplt.subplot(2, 3, 6)\npatient_lesions = train_df['patient_id'].value_counts()\nsns.histplot(patient_lesions.values, bins=30, color='purple')\nplt.title('Lesions per Patient Distribution')\nplt.xlabel('Number of Lesions')\n\nplt.tight_layout()\nplt.show()\n\n# Print detailed statistics\nprint(\"\\n📈 Dataset Statistics:\")\nprint(f\"Class distribution:\\n{train_df['target'].value_counts()}\")\nprint(f\"\\nAge statistics:\\n{train_df['age_approx'].describe()}\")\nprint(f\"\\nSex distribution:\\n{train_df['sex'].value_counts()}\")\nprint(f\"\\nTop anatomical sites:\\n{train_df['anatom_site_general_challenge'].value_counts().head()}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:26:41.925382Z","iopub.execute_input":"2025-12-29T14:26:41.925733Z","iopub.status.idle":"2025-12-29T14:26:43.365119Z","shell.execute_reply.started":"2025-12-29T14:26:41.925710Z","shell.execute_reply":"2025-12-29T14:26:43.364311Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 4️⃣ DATA PREPARATION WITH IMBALANCE HANDLING (CRITICAL FIX)\n# =============================================================================\nprint(\"\\n🔄 Preparing data with imbalance handling...\")\n\nimport tensorflow as tf\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\nfrom sklearn.utils.class_weight import compute_class_weight\nimport numpy as np\nimport pandas as pd\nimport os\n\n# ============================================\n# FIRST: Reload or use existing train_df\n# ============================================\n# If train_df is not defined, reload it\ntry:\n    train_df\n    print(\"✓ train_df already loaded\")\nexcept NameError:\n    print(\"⚠️  train_df not found. Reloading data...\")\n    BASE_DIR = \"/kaggle/input/siim-isic-melanoma-classification\"\n    TRAIN_IMAGES_DIR = os.path.join(BASE_DIR, \"jpeg/train\")\n    TRAIN_CSV_PATH = os.path.join(BASE_DIR, \"train.csv\")\n    \n    train_df = pd.read_csv(TRAIN_CSV_PATH)\n    train_df['image_path'] = train_df['image_name'].apply(\n        lambda x: os.path.join(TRAIN_IMAGES_DIR, f\"{x}.jpg\")\n    )\n    \n    # Handle missing values\n    train_df['age_approx'] = train_df['age_approx'].fillna(train_df['age_approx'].median()).astype(np.float32)\n    train_df['sex'] = train_df['sex'].fillna('unknown')\n    train_df['anatom_site_general_challenge'] = train_df['anatom_site_general_challenge'].fillna('unknown')\n    \n    print(f\"✅ Reloaded training set: {len(train_df)} images\")\n\n# ============================================\n# CRITICAL STEP 1: Handle Extreme Imbalance\n# ============================================\nprint(\"\\n⚖️  Balancing the dataset...\")\nprint(f\"Original class distribution: {train_df['target'].value_counts().to_dict()}\")\nprint(f\"Malignant percentage: {(train_df['target'].sum()/len(train_df))*100:.2f}%\")\n\n# Strategy: Undersample majority class (benign) \nbenign_samples = train_df[train_df['target'] == 0]\nmalignant_samples = train_df[train_df['target'] == 1]\n\n# Create balanced dataset (1:3 ratio - malignant:benign)\n# This gives us 3 benign for every 1 malignant\nbenign_downsampled = benign_samples.sample(n=len(malignant_samples)*3, random_state=42)\nbalanced_df = pd.concat([benign_downsampled, malignant_samples])\n\n# Shuffle the balanced dataset\nbalanced_df = balanced_df.sample(frac=1, random_state=42).reset_index(drop=True)\n\nprint(f\"\\n📊 After balancing:\")\nprint(f\"  Balanced dataset size: {len(balanced_df)}\")\nprint(f\"  Balanced class distribution: {balanced_df['target'].value_counts().to_dict()}\")\nprint(f\"  New class ratio: 1:{len(benign_downsampled)/len(malignant_samples):.1f}\")\nprint(f\"  Malignant percentage: {(balanced_df['target'].sum()/len(balanced_df))*100:.2f}%\")\n\n# ============================================\n# CRITICAL STEP 2: Use Stratified Split\n# ============================================\nprint(\"\\n📂 Creating train/validation split...\")\ntrain_data, val_data = train_test_split(\n    balanced_df, \n    test_size=0.15, \n    random_state=42, \n    stratify=balanced_df['target']  # THIS IS CRITICAL\n)\n\nprint(f\"  Training samples: {len(train_data)}\")\nprint(f\"  Validation samples: {len(val_data)}\")\nprint(f\"  Training class dist: {train_data['target'].value_counts().to_dict()}\")\nprint(f\"  Validation class dist: {val_data['target'].value_counts().to_dict()}\")\n\n# ============================================\n# CRITICAL STEP 3: Adjust Class Weights\n# ============================================\n# Use MORE REASONABLE class weights for balanced data\nclass_weights = compute_class_weight(\n    'balanced',\n    classes=np.unique(train_data['target']),\n    y=train_data['target']\n)\nclass_weight_dict = dict(enumerate(class_weights))\nprint(f\"\\n🎯 Class weights for balanced data: {class_weight_dict}\")\n\n# ============================================\n# CRITICAL STEP 4: Enhanced Data Augmentation\n# ============================================\n# Parameters\nIMG_SIZE = (224, 224)\nBATCH_SIZE = 32\nAUTOTUNE = tf.data.AUTOTUNE\n\n# Function to load and preprocess image\ndef process_image(file_path, label):\n    img = tf.io.read_file(file_path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, IMG_SIZE)\n    img = img / 255.0  # normalize to [0,1]\n    return img, label\n\n# ENHANCED AUGMENTATION for MALIGNANT cases only\ndef augment_malignant(img, label):\n    # Apply more aggressive augmentation to malignant cases\n    if label == 1:  # Only augment malignant cases\n        img = tf.image.random_flip_left_right(img)\n        img = tf.image.random_flip_up_down(img)\n        img = tf.image.random_brightness(img, max_delta=0.3)\n        img = tf.image.random_contrast(img, lower=0.7, upper=1.3)\n        img = tf.image.random_saturation(img, lower=0.7, upper=1.3)\n        # Add rotation for malignant cases only\n        img = tf.image.rot90(img, k=tf.random.uniform(shape=[], minval=0, maxval=4, dtype=tf.int32))\n    # Mild augmentation for all cases\n    else:\n        img = tf.image.random_flip_left_right(img)\n        img = tf.image.random_brightness(img, max_delta=0.1)\n    return img, label\n\n# ============================================\n# CRITICAL STEP 5: Create Dataset Pipelines\n# ============================================\nprint(\"\\n🔧 Creating TensorFlow datasets...\")\n\n# Training dataset with targeted augmentation\ntrain_dataset = tf.data.Dataset.from_tensor_slices(\n    (train_data['image_path'].values, train_data['target'].values)\n)\ntrain_dataset = train_dataset.shuffle(buffer_size=len(train_data), seed=42, reshuffle_each_iteration=True)\ntrain_dataset = train_dataset.map(process_image, num_parallel_calls=AUTOTUNE)\ntrain_dataset = train_dataset.map(augment_malignant, num_parallel_calls=AUTOTUNE)  # Targeted augmentation\ntrain_dataset = train_dataset.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n\n# Validation dataset (NO augmentation, only basic preprocessing)\nval_dataset = tf.data.Dataset.from_tensor_slices(\n    (val_data['image_path'].values, val_data['target'].values)\n)\nval_dataset = val_dataset.map(process_image, num_parallel_calls=AUTOTUNE)\nval_dataset = val_dataset.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n\nprint(f\"✅ Training batches: {len(train_dataset)}\")\nprint(f\"✅ Validation batches: {len(val_dataset)}\")\nprint(f\"✅ Samples per batch: {BATCH_SIZE}\")\n\n# ============================================\n# Quick sanity check: Show sample distribution\n# ============================================\nprint(\"\\n📋 Final dataset summary:\")\nprint(f\"  Original dataset: {len(train_df)} samples\")\nprint(f\"    - Benign: {len(benign_samples)}\")\nprint(f\"    - Malignant: {len(malignant_samples)}\")\nprint(f\"  Balanced dataset: {len(balanced_df)} samples\")\nprint(f\"    - Benign: {len(benign_downsampled)}\")\nprint(f\"    - Malignant: {len(malignant_samples)}\")\nprint(f\"  Training set: {len(train_data)} samples\")\nprint(f\"  Validation set: {len(val_data)} samples\")\nprint(f\"  Image size: {IMG_SIZE}\")\nprint(f\"  Class weights: {class_weight_dict}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:26:53.110279Z","iopub.execute_input":"2025-12-29T14:26:53.110639Z","iopub.status.idle":"2025-12-29T14:26:55.224494Z","shell.execute_reply.started":"2025-12-29T14:26:53.110607Z","shell.execute_reply":"2025-12-29T14:26:55.223775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 5️⃣ BUILD RESNET50 MODEL (for balanced dataset)\n# =============================================================================\nprint(\"\\n🧠 Building ResNet50 Model for balanced data...\")\n\nfrom tensorflow.keras.models import Sequential, Model\nfrom tensorflow.keras.layers import Dense, Dropout, BatchNormalization, GlobalAveragePooling2D\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras import mixed_precision\nfrom tensorflow.keras.applications import ResNet50\n\n# Ensure mixed precision is enabled\nmixed_precision.set_global_policy('mixed_float16')\n\ndef create_resnet_model(input_shape=(224, 224, 3)):\n    # Load pre-trained ResNet50\n    base_model = ResNet50(\n        include_top=False,\n        weights='imagenet',\n        input_shape=input_shape\n    )\n    \n    # Freeze base model layers (optional)\n    # base_model.trainable = False\n    \n    # Add custom layers on top\n    model = Sequential([\n        base_model,\n        GlobalAveragePooling2D(),\n        BatchNormalization(),\n        Dropout(0.5),\n        Dense(256, activation='relu'),\n        BatchNormalization(),\n        Dropout(0.3),\n        Dense(1, activation='sigmoid', dtype='float32')\n    ])\n    \n    return model\n\n# Create and compile model\nmodel = create_resnet_model()\nmodel.compile(\n    optimizer=Adam(learning_rate=0.0001),  # Lower learning rate is better for transfer learning\n    loss='binary_crossentropy',\n    metrics=['accuracy', 'precision', 'recall']  # ← lowercase!\n)\nprint(\"✅ Model architecture (ResNet50 based):\")\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:27:07.359232Z","iopub.execute_input":"2025-12-29T14:27:07.359528Z","iopub.status.idle":"2025-12-29T14:27:10.382990Z","shell.execute_reply.started":"2025-12-29T14:27:07.359504Z","shell.execute_reply":"2025-12-29T14:27:10.382401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 6️⃣ CALLBACKS AND TRAINING PREPARATION (GPU-Optimized)\n# =============================================================================\nprint(\"\\n⏰ Setting up training callbacks...\")\n\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\n\n# Callbacks for efficient GPU training\ncallbacks = [\n    EarlyStopping(\n        monitor='val_loss',       # Stop training when validation loss stops improving\n        patience=10,\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor='val_loss',       # Reduce learning rate when validation loss plateaus\n        factor=0.5,\n        patience=5,\n        min_lr=1e-7,\n        verbose=1\n    ),\n    ModelCheckpoint(\n        'best_melanoma_model.h5',  # Save the best model during training\n        monitor='val_accuracy',     # Track validation accuracy\n        save_best_only=True,\n        mode='max',\n        verbose=1\n    )\n]\n\nprint(\"✅ Callbacks ready for training!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T14:27:16.404169Z","iopub.execute_input":"2025-12-29T14:27:16.404517Z","iopub.status.idle":"2025-12-29T14:27:16.410229Z","shell.execute_reply.started":"2025-12-29T14:27:16.404497Z","shell.execute_reply":"2025-12-29T14:27:16.409446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 7️⃣ TRAIN THE MODEL (GPU-Optimized)\n# =============================================================================\nprint(\"\\n🚀 Starting GPU-optimized model training...\")\n\n# Parameters\nEPOCHS = 50\n\n# Train the model using tf.data.Dataset pipelines\nhistory = model.fit(\n    train_dataset,                      # GPU-optimized training dataset\n    validation_data=val_dataset,        # Validation dataset\n    epochs=EPOCHS,\n    callbacks=callbacks,\n    class_weight=class_weight_dict,\n    verbose=1\n)\n\nprint(\"✅ Training completed!\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 8️⃣ MODEL EVALUATION (GPU-Optimized) - FIXED VERSION\n# =============================================================================\nprint(\"\\n📊 Evaluating model performance on GPU...\")\n\nfrom sklearn.metrics import classification_report, roc_auc_score, confusion_matrix, f1_score\nimport numpy as np\n\n# Load best saved model\nmodel.load_weights('best_melanoma_model.h5')\n\n# ============================================\n# FIX: Evaluate model (returns 5 values now)\n# ============================================\neval_results = model.evaluate(val_dataset, verbose=1)\n\n# Unpack based on number of metrics\nif len(eval_results) == 5:  # loss + 4 metrics (accuracy, precision, recall, auc)\n    val_loss, val_accuracy, val_precision, val_recall, val_auc_metric = eval_results\n    print(f\"🎯 Validation Loss: {val_loss:.4f}\")\n    print(f\"🎯 Validation Accuracy: {val_accuracy:.4f}\")\n    print(f\"🎯 Validation Precision: {val_precision:.4f}\")\n    print(f\"🎯 Validation Recall: {val_recall:.4f}\")\n    print(f\"🎯 Validation AUC (from metric): {val_auc_metric:.4f}\")\nelif len(eval_results) == 4:  # loss + 3 metrics\n    val_loss, val_accuracy, val_precision, val_recall = eval_results\n    print(f\"🎯 Validation Loss: {val_loss:.4f}\")\n    print(f\"🎯 Validation Accuracy: {val_accuracy:.4f}\")\n    print(f\"🎯 Validation Precision: {val_precision:.4f}\")\n    print(f\"🎯 Validation Recall: {val_recall:.4f}\")\nelse:\n    print(f\"⚠️ Unexpected number of evaluation metrics: {len(eval_results)}\")\n\n# ============================================\n# Make predictions for additional metrics\n# ============================================\nprint(\"\\n📈 Making predictions for detailed analysis...\")\nval_predictions = model.predict(val_dataset)\nval_pred_binary = (val_predictions > 0.5).astype(int).flatten()\n\n# True labels\nval_true = np.concatenate([y for x, y in val_dataset], axis=0)\n\n# Calculate AUC-ROC (better to calculate it ourselves for consistency)\nauc_roc = roc_auc_score(val_true, val_predictions)\nprint(f\"🎯 AUC-ROC Score (calculated): {auc_roc:.4f}\")\n\n# Calculate F1-Score\nf1 = f1_score(val_true, val_pred_binary)\nprint(f\"🎯 F1-Score: {f1:.4f}\")\n\n# Classification report\nprint(\"\\n📋 Classification Report:\")\nprint(classification_report(val_true, val_pred_binary, target_names=['Benign', 'Malignant']))\n\n# Confusion matrix\ncm = confusion_matrix(val_true, val_pred_binary)\nprint(\"\\n🗂 Confusion Matrix:\")\nprint(cm)\n\n# Calculate additional metrics from confusion matrix\ntn, fp, fn, tp = cm.ravel()\nprint(f\"\\n📊 Detailed Metrics from Confusion Matrix:\")\nprint(f\"  True Negatives (Benign correctly identified): {tn}\")\nprint(f\"  False Positives (Benign misclassified as Malignant): {fp}\")\nprint(f\"  False Negatives (Malignant missed): {fn}\")\nprint(f\"  True Positives (Malignant correctly identified): {tp}\")\n\n# Calculate sensitivity and specificity\nsensitivity = tp / (tp + fn) if (tp + fn) > 0 else 0\nspecificity = tn / (tn + fp) if (tn + fp) > 0 else 0\nprint(f\"  Sensitivity (Recall): {sensitivity:.4f}\")\nprint(f\"  Specificity: {specificity:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T15:43:32.414591Z","iopub.execute_input":"2025-12-29T15:43:32.415430Z","iopub.status.idle":"2025-12-29T15:43:57.149546Z","shell.execute_reply.started":"2025-12-29T15:43:32.415404Z","shell.execute_reply":"2025-12-29T15:43:57.148827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 9️⃣ VISUALIZE RESULTS\n# =============================================================================\nprint(\"\\n📈 Visualizing training results and model performance...\")\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import roc_curve, confusion_matrix\n\n# --- Training history plots ---\nplt.figure(figsize=(18, 5))\n\n# 1️⃣ Accuracy\nplt.subplot(1, 3, 1)\nplt.plot(history.history['accuracy'], label='Training Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.title('Model Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend()\n\n# 2️⃣ Loss\nplt.subplot(1, 3, 2)\nplt.plot(history.history['loss'], label='Training Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.title('Model Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\n\n# 3️⃣ ROC Curve\nplt.subplot(1, 3, 3)\nfpr, tpr, _ = roc_curve(val_true, val_predictions)\nplt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {auc_roc:.4f})')\nplt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('False Positive Rate')\nplt.ylabel('True Positive Rate')\nplt.title('Receiver Operating Characteristic')\nplt.legend(loc=\"lower right\")\n\nplt.tight_layout()\nplt.show()\n\n# --- Confusion Matrix ---\nplt.figure(figsize=(6, 5))\ncm = confusion_matrix(val_true, val_pred_binary)\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=['Benign', 'Malignant'],\n            yticklabels=['Benign', 'Malignant'])\nplt.title('Confusion Matrix')\nplt.ylabel('True Label')\nplt.xlabel('Predicted Label')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T15:48:20.337085Z","iopub.execute_input":"2025-12-29T15:48:20.337383Z","iopub.status.idle":"2025-12-29T15:48:21.007194Z","shell.execute_reply.started":"2025-12-29T15:48:20.337361Z","shell.execute_reply":"2025-12-29T15:48:21.006627Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =============================================================================\n# 🔟 MODEL INTERPRETATION AND FINAL RESULTS\n# =============================================================================\nprint(\"\\n💡 Model Performance Summary:\")\nprint(\"=\"*50)\nprint(f\"✅ Final Validation Accuracy : {val_accuracy:.4f}\")\nprint(f\"✅ Final Validation Precision: {val_precision:.4f}\")\nprint(f\"✅ Final Validation Recall   : {val_recall:.4f}\")\nprint(f\"✅ AUC-ROC Score            : {auc_roc:.4f}\")\nprint(f\"✅ Training Samples         : {len(train_data)}\")\nprint(f\"✅ Validation Samples       : {len(val_data)}\")\nprint(f\"✅ Class Weights Applied    : {class_weight_dict}\")\n\n# Additional metric: F1-Score\nfrom sklearn.metrics import f1_score\nf1 = f1_score(val_true, val_pred_binary)\nprint(f\"✅ F1-Score                 : {f1:.4f}\")\n\n# Save the final model\nmodel.save('melanoma_classification_final_model.h5')\nprint(\"💾 Model saved as 'melanoma_classification_final_model.h5'\")\n\nprint(\"\\n🎉 Training, evaluation, and final results completed successfully!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-29T15:48:28.074789Z","iopub.execute_input":"2025-12-29T15:48:28.075326Z","iopub.status.idle":"2025-12-29T15:48:29.167622Z","shell.execute_reply.started":"2025-12-29T15:48:28.075303Z","shell.execute_reply":"2025-12-29T15:48:29.166882Z"}},"outputs":[],"execution_count":null}]}