DaneshSelwal commited on
Commit
0101dcd
·
verified ·
1 Parent(s): 5cf795d

Clean up codebase files to make it a strict Model Registry

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. adapt_k014.py +0 -252
  2. agent.md +0 -109
  3. baseline/Model_training.ipynb +0 -0
  4. baseline/Model_uncertainty_comparison.ipynb +0 -0
  5. baseline/results/AlexNet_CNN_classification_report.json +0 -63
  6. baseline/results/GFNet_classification_report.json +0 -63
  7. baseline/results/ViT_UNet_classification_report.json +0 -63
  8. baseline/results/classification_summary.csv +0 -4
  9. baseline/results/scene_visualizations/AlexNet_CNN_initial_classification.png +0 -3
  10. baseline/results/scene_visualizations/GFNet_initial_classification.png +0 -3
  11. baseline/results/scene_visualizations/ViT_UNet_initial_classification.png +0 -3
  12. baseline/results/scene_visualizations/combined_initial_classification_overview.png +0 -3
  13. baseline/results/scene_visualizations/ground_truth_label_map.png +0 -3
  14. baseline/results/scene_visualizations/initial_classification_maps.xlsx +0 -3
  15. baseline/results/scene_visualizations/scene_rgb.png +0 -3
  16. baseline/results/training_plots/AlexNet_CNN_training_curves.png +0 -3
  17. baseline/results/training_plots/GFNet_training_curves.png +0 -3
  18. baseline/results/training_plots/ViT_UNet_training_curves.png +0 -3
  19. baseline/results/training_plots/confusion_matrices_side_by_side.png +0 -3
  20. baseline/results/training_plots/model_comparison_metrics.png +0 -3
  21. baseline/results/training_plots/uncertainty_proxy_metrics.png +0 -0
  22. baseline/results/uncertainty_results/conformal_reports_all_models.xlsx +0 -3
  23. baseline/results/uncertainty_results/per_class_coverage.csv +0 -121
  24. baseline/results/uncertainty_results/run_config.json +0 -20
  25. baseline/results/uncertainty_results/summary_metrics.csv +0 -16
  26. baseline/test_m2_challenger.py +0 -261
  27. baseline/verify_m1_dataset.py +0 -375
  28. credit/Model_training_credit.ipynb +0 -2065
  29. credit/results/AlexNet_CNN_CREDIT_spatial_maps.png +0 -3
  30. credit/results/CREDIT_Results.xlsx +0 -3
  31. credit/results/GFNet_CREDIT_spatial_maps.png +0 -3
  32. credit/results/ViT_UNet_CREDIT_spatial_maps.png +0 -3
  33. credit/results/credit_confusion_matrices.png +0 -3
  34. credit/results/credit_evaluation_summary.csv +0 -4
  35. credit/results/credit_train_uncertainty_summary.csv +0 -4
  36. ensemble/Model_training_ensembles.ipynb +0 -2006
  37. ensemble/Model_uncertainty_CreDE.ipynb +0 -1365
  38. ensemble/results/AlexNet_CNN_CreDE_spatial_maps.png +0 -3
  39. ensemble/results/AlexNet_CNN_classification_report.json +0 -63
  40. ensemble/results/AlexNet_CNN_training_curves.png +0 -3
  41. ensemble/results/CreDE_Master_Summary.csv +0 -4
  42. ensemble/results/CreDE_Results.xlsx +0 -3
  43. ensemble/results/GFNet_CreDE_spatial_maps.png +0 -3
  44. ensemble/results/GFNet_classification_report.json +0 -63
  45. ensemble/results/GFNet_training_curves.png +0 -3
  46. ensemble/results/ViT_UNet_CreDE_spatial_maps.png +0 -3
  47. ensemble/results/ViT_UNet_classification_report.json +0 -63
  48. ensemble/results/ViT_UNet_training_curves.png +0 -3
  49. ensemble/results/classification_summary.csv +0 -16
  50. ensemble/results/combined_initial_classification_overview.png +0 -3
adapt_k014.py DELETED
@@ -1,252 +0,0 @@
1
- import ast
2
- import json
3
- from pathlib import Path
4
-
5
- BASE_DIR = Path("/Users/danesh/Documents/AGY/agy/classification")
6
- source_nb_path = BASE_DIR / "ensemble" / "Model_uncertainty_CreDE.ipynb"
7
- staged_nb_path = BASE_DIR / "ensemble" / "staging_crede" / "k014.ipynb"
8
-
9
- with open(source_nb_path, "r", encoding="utf-8") as f:
10
- nb = json.load(f)
11
-
12
- print(f"Original cells: {len(nb['cells'])}")
13
-
14
- # Cell 2 adaptation
15
- cell_2_code = """# Environment detection & optional Colab mount
16
- import sys
17
- if 'google.colab' in sys.modules:
18
- from google.colab import drive
19
- drive.mount('/content/drive', force_remount=False)
20
-
21
- # Standard library
22
- import os
23
- import json
24
- import time
25
- import random
26
- import io
27
- import gc
28
- import glob
29
- import subprocess
30
- from pathlib import Path
31
-
32
- from scipy.io import loadmat
33
-
34
- # Defensive package verification / installation
35
- for pkg in ['openpyxl', 'pillow']:
36
- try:
37
- __import__(pkg)
38
- except ImportError:
39
- subprocess.run([sys.executable, '-m', 'pip', 'install', '-q', pkg], check=True)
40
-
41
- # Numerical / data
42
- import numpy as np
43
- import pandas as pd
44
-
45
- # Visualisation (headless backend configured before importing pyplot)
46
- import matplotlib
47
- matplotlib.use('Agg')
48
- import matplotlib.pyplot as plt
49
- from matplotlib.colors import ListedColormap
50
- from matplotlib.patches import Patch
51
- import seaborn as sns
52
-
53
- # Deep learning
54
- import tensorflow as tf
55
- from tensorflow import keras
56
- from tensorflow.keras import layers
57
-
58
- # GPU Dynamic Memory Growth Configuration
59
- gpus = tf.config.list_physical_devices('GPU')
60
- if gpus:
61
- for gpu in gpus:
62
- try:
63
- tf.config.experimental.set_memory_growth(gpu, True)
64
- except RuntimeError as e:
65
- print(f"Memory growth error: {e}")
66
- print(f"GPU Active: {len(gpus)} device(s) detected: {[g.name for g in gpus]}")
67
- else:
68
- print("WARNING: No physical GPU detected!")
69
-
70
- # Enable Keras unsafe deserialization for custom layer loading
71
- try:
72
- keras.config.enable_unsafe_deserialization()
73
- print("Keras unsafe deserialization enabled.")
74
- except Exception as e:
75
- print(f"Notice: keras.config.enable_unsafe_deserialization(): {e}")
76
-
77
- # Scikit-learn
78
- from sklearn.model_selection import train_test_split
79
- from sklearn.metrics import (
80
- accuracy_score,
81
- cohen_kappa_score,
82
- confusion_matrix,
83
- classification_report,
84
- f1_score,
85
- log_loss,
86
- )
87
-
88
- # Excel export
89
- from openpyxl import Workbook
90
- from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
91
- from openpyxl.utils import get_column_letter
92
- from openpyxl.drawing.image import Image as XLImage
93
-
94
- sns.set_style('whitegrid')
95
- print('Python: ', sys.version.split()[0])
96
- print('TensorFlow:', tf.__version__)
97
- print('Keras: ', keras.__version__)"""
98
-
99
- nb["cells"][2]["source"] = [line + "\n" for line in cell_2_code.split("\n")]
100
- nb["cells"][2]["source"][-1] = nb["cells"][2]["source"][-1].rstrip("\n")
101
-
102
- # Cell 7 adaptation
103
- cell_7_code = """# Reproducibility
104
- SEED = 42
105
- np.random.seed(SEED)
106
- tf.random.set_seed(SEED)
107
-
108
- IN_KAGGLE = Path('/kaggle').exists() or Path('/kaggle/working').exists()
109
- IN_COLAB = 'google.colab' in sys.modules
110
-
111
- if IN_KAGGLE:
112
- k001_primary = Path('/kaggle/input/dias-dataset-k01/k001.mat')
113
- k002_primary = Path('/kaggle/input/dias-dataset-k01/k002.mat')
114
- if k001_primary.exists() and k002_primary.exists():
115
- DATA_FILE = k001_primary
116
- LABEL_FILE = k002_primary
117
- DATA_DIR = k001_primary.parent
118
- else:
119
- dias_candidates = (
120
- list(Path('/kaggle/input').glob('**/k001.mat')) or
121
- list(Path('/kaggle/input').glob('**/DIAS.mat'))
122
- )
123
- ref_candidates = (
124
- list(Path('/kaggle/input').glob('**/k002.mat')) or
125
- list(Path('/kaggle/input').glob('**/DIAS_ref.mat'))
126
- )
127
- DATA_FILE = dias_candidates[0] if dias_candidates else k001_primary
128
- LABEL_FILE = ref_candidates[0] if ref_candidates else k002_primary
129
- DATA_DIR = DATA_FILE.parent
130
-
131
- ensemble_candidates = (
132
- list(Path('/kaggle/input/dias-ensemble-k13').glob('**/ensembles')) or
133
- list(Path('/kaggle/input/dias-ensemble-k13').glob('**/models/ensembles')) or
134
- list(Path('/kaggle/input').glob('**/ensembles'))
135
- )
136
- if ensemble_candidates:
137
- MODEL_DIR = ensemble_candidates[0]
138
- else:
139
- MODEL_DIR = Path('/kaggle/input/dias-ensemble-k13/results/models/ensembles')
140
-
141
- ROOT_DIR = Path('/kaggle/working')
142
- CREDE_OUT_DIR = Path('/kaggle/working/results')
143
-
144
- elif IN_COLAB:
145
- ROOT_DIR = Path('/content/drive/My Drive/Classification')
146
- MODEL_DIR = ROOT_DIR / 'ensemble' / 'models' / 'ensembles'
147
- DATA_DIR = ROOT_DIR / 'data'
148
- DATA_FILE = DATA_DIR / 'DIAS.mat'
149
- LABEL_FILE = DATA_DIR / 'DIAS_ref.mat'
150
- CREDE_OUT_DIR = ROOT_DIR / 'ensemble' / 'results'
151
-
152
- else:
153
- ROOT_DIR = Path.cwd()
154
- DATA_DIR = Path('data') if Path('data').exists() else ROOT_DIR / 'data'
155
- DATA_FILE = DATA_DIR / 'DIAS.mat' if (DATA_DIR / 'DIAS.mat').exists() else DATA_DIR / 'k001.mat'
156
- LABEL_FILE = DATA_DIR / 'DIAS_ref.mat' if (DATA_DIR / 'DIAS_ref.mat').exists() else DATA_DIR / 'k002.mat'
157
- MODEL_DIR = Path('ensemble/results/models/ensembles') if Path('ensemble/results/models/ensembles').exists() else ROOT_DIR / 'ensemble' / 'models' / 'ensembles'
158
- CREDE_OUT_DIR = Path('ensemble/results')
159
-
160
- CREDE_OUT_DIR.mkdir(parents=True, exist_ok=True)
161
-
162
- # Scene geometry — H, W, B are inferred automatically from the .mat cube shape
163
- # once loaded in Section 3.1, so this notebook works unchanged for any scene
164
- # size / band count.
165
- H, W, B = None, None, None
166
- PERCENTILE_CLIP = (2.0, 98.0) # robust per-band stretch range (2nd-98th percentile)
167
- PATCH_SIZE = 9
168
-
169
- # Uncertainty thresholds used for masking
170
- AU_THRESH = 0.5
171
- EU_THRESH = 0.2
172
- TU_THRESH = 0.7
173
-
174
- print('Environment :', 'Kaggle' if IN_KAGGLE else ('Colab' if IN_COLAB else 'Local'))
175
- print('Data file :', DATA_FILE, f'(exists: {DATA_FILE.exists()})')
176
- print('Label file :', LABEL_FILE, f'(exists: {LABEL_FILE.exists()})')
177
- print('Model dir :', MODEL_DIR, f'(exists: {MODEL_DIR.exists()})')
178
- print('CreDE out dir:', CREDE_OUT_DIR)"""
179
-
180
- nb["cells"][7]["source"] = [line + "\n" for line in cell_7_code.split("\n")]
181
- nb["cells"][7]["source"][-1] = nb["cells"][7]["source"][-1].rstrip("\n")
182
-
183
- # Cell 9 adaptation
184
- cell_9_code = """CREDE_OUT_DIR.mkdir(parents=True, exist_ok=True)
185
- print(f"CreDE output directory verified: {CREDE_OUT_DIR}")"""
186
-
187
- nb["cells"][9]["source"] = [line + "\n" for line in cell_9_code.split("\n")]
188
- nb["cells"][9]["source"][-1] = nb["cells"][9]["source"][-1].rstrip("\n")
189
-
190
- # Cell 29 adaptation
191
- cell_29_code = '''def get_ensemble_paths(model_name):
192
- """
193
- Locate all saved `.keras` checkpoint files for a given architecture.
194
- Searches MODEL_DIR, subdirectories, and recursively across /kaggle/input/.
195
- """
196
- search_patterns = [
197
- str(MODEL_DIR / f"{model_name}_ens_*_final.keras"),
198
- str(MODEL_DIR / "ensembles" / f"{model_name}_ens_*_final.keras"),
199
- f"/kaggle/input/dias-ensemble-k13/**/{model_name}_ens_*_final.keras",
200
- f"/kaggle/input/**/{model_name}_ens_*_final.keras",
201
- str(Path("ensemble/results") / "**" / f"{model_name}_ens_*_final.keras"),
202
- ]
203
- for pattern in search_patterns:
204
- matches = sorted(glob.glob(pattern, recursive=True))
205
- valid_matches = [p for p in matches if not p.endswith('.tmp')]
206
- if valid_matches:
207
- print(f" Found {len(valid_matches)} models for {model_name} matching pattern: {pattern}")
208
- return valid_matches
209
-
210
- print(f" WARNING: No ensemble models found for {model_name} across searched patterns.")
211
- return []'''
212
-
213
- nb["cells"][29]["source"] = [line + "\n" for line in cell_29_code.split("\n")]
214
- nb["cells"][29]["source"][-1] = nb["cells"][29]["source"][-1].rstrip("\n")
215
-
216
- # Cell 34 adaptation
217
- cell_34_src = "".join(nb["cells"][34]["source"])
218
- assert "plt.show()" in cell_34_src, "plt.show() not found in cell 34!"
219
- cell_34_adapted = cell_34_src.replace("plt.show()", "plt.close(fig)")
220
- assert "plt.show()" not in cell_34_adapted, "plt.show() still found in cell 34!"
221
- assert "plt.close(fig)" in cell_34_adapted, "plt.close(fig) not found in cell 34!"
222
- nb["cells"][34]["source"] = [line + "\n" for line in cell_34_adapted.split("\n")]
223
- nb["cells"][34]["source"][-1] = nb["cells"][34]["source"][-1].rstrip("\n")
224
-
225
- # Hygiene: outputs: [], execution_count: None across all code cells
226
- code_cell_count = 0
227
- for i, cell in enumerate(nb["cells"]):
228
- if cell["cell_type"] == "code":
229
- code_cell_count += 1
230
- cell["outputs"] = []
231
- cell["execution_count"] = None
232
- # AST validate
233
- src = "".join(cell["source"])
234
- try:
235
- ast.parse(src)
236
- except SyntaxError as e:
237
- print(f"SyntaxError in cell {i}: {e}")
238
- raise
239
-
240
- print(f"Total code cells validated: {code_cell_count}")
241
-
242
- # Root metadata
243
- if "colab" in nb.get("metadata", {}):
244
- del nb["metadata"]["colab"]
245
- nb["metadata"]["accelerator"] = "GPU"
246
- if "kernelspec" not in nb.get("metadata", {}):
247
- nb["metadata"]["kernelspec"] = {"display_name": "Python 3", "name": "python3"}
248
-
249
- with open("/tmp/k014.ipynb", "w", encoding="utf-8") as f:
250
- json.dump(nb, f, indent=1)
251
-
252
- print(f"Successfully wrote staged notebook to /tmp/k014.ipynb")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agent.md DELETED
@@ -1,109 +0,0 @@
1
- # MASTER CONTEXT & WORKFLOW: Hyperspectral Image Classification & UQ (DIAS 65-Bands)
2
-
3
- **Last Updated:** 2026-09-16
4
- **Role:** This is the single authoritative "Master File" for any agent, developer, or automated system working in this workspace. It dictates the architectural setup, pipeline execution process, storage strategy, and historical context of the DIAS 65-bands Uncertainty Quantification (UQ) project.
5
-
6
- ---
7
-
8
- ## 1. Project Domain & Objective
9
- This project implements a generalized framework for **Hyperspectral Remote Sensing Image Classification and Uncertainty Quantification**.
10
- Specifically, this repository is dedicated to processing the **DIAS agricultural dataset (65 spectral bands)**. The goal is to train deep learning models (AlexNet, GFNet, ViT-UNet) and apply various UQ methodologies (Conformal Prediction, Ensembles, Distillation) to measure epistemic and aleatoric uncertainty, yielding reliable spatial maps and metrics.
11
-
12
- ---
13
-
14
- ## 2. Workspace Architecture & Storage Strategy
15
- The environment is split across three primary locations to balance local Mac memory limitations with the need for persistent cloud backups.
16
-
17
- 1. **Active Local Workspace (`/Users/danesh/Documents/AGY/agy/classification/`)**
18
- - The primary staging and development ground.
19
- - **Git Tracking:** Tracks the remote GitHub repository `DaneshSelwal/classification_dias.git`.
20
- - **Constraint:** Do not store redundant `.zip` or unzipped caches here. Clean up `dl_temp/` folders after processing.
21
-
22
- 2. **Google Drive Cold Storage (`/Users/danesh/Documents/AGY/classification_65bands/`)**
23
- - The ultimate local mirror to the Google Drive cloud backup.
24
- - **Syncing Rule:** This folder must strictly mirror the finalized artifacts from the Local Workspace using `rsync`. It is never to be used as a working directory.
25
-
26
- 3. **GitHub Repository (`classification_dias`)**
27
- - **Git LFS:** All heavy `.keras` and `.h5` model weights MUST be tracked via Git Large File Storage (LFS).
28
- - Before pushing, always run `git lfs track "folder/models/*.keras"`.
29
-
30
- ---
31
-
32
- ## 3. The Orchestration Pipeline (How Work Gets Done)
33
- Due to computational constraints on the local Mac, **all model training is orchestrated autonomously on Kaggle GPUs**. Any new pipeline must follow this strict 9-step execution workflow:
34
-
35
- ### Step 1: Notebook Adaptation
36
- The local Jupyter notebook must be adapted for Kaggle. File paths must point to `/kaggle/input/...` and outputs must be written to `/kaggle/working/...`.
37
-
38
- ### Step 2: Obfuscation Strategy (Crucial)
39
- To prevent Kaggle automated bans/flags when uploading datasets:
40
- - Every dataset file and model weight must be renamed to a generic alias (e.g., `k001.mat`, `k016.keras`).
41
- - The mapping must be registered in `/Users/danesh/Documents/AGY/agy/classification/kaggle_name_mapping.json`.
42
-
43
- ### Step 3: Kaggle Upload
44
- - **Data/Models:** Large files must be uploaded as a Kaggle Dataset (`kaggle datasets create`).
45
- - **Code:** The notebook is pushed as a Kaggle Kernel (`kaggle kernels push`).
46
-
47
- ### Step 4: Execution & Monitoring
48
- Run `kaggle kernels status <slug>` and poll until the status is `COMPLETE`.
49
-
50
- ### Step 5: Download Outputs
51
- Use `kaggle kernels output <slug> -p <local_temp_folder> -o` to pull down the generated `.keras` weights and evaluation CSV/Excel files.
52
-
53
- ### Step 6: Local Organization
54
- Move downloaded artifacts from the temp folder to their permanent homes (e.g., `baseline/models/` and `baseline/results/`).
55
-
56
- ### Step 7: Local Cache Cleanup
57
- **Immediately delete** the local `dl_temp/` caches to free up Mac storage memory.
58
-
59
- ### Step 8: Git Commit & Push
60
- Commit the models and results. **Agents must use `BypassSandbox: true`** when executing `git push` to GitHub.
61
-
62
- ### Step 9: Google Drive Sync
63
- Run `rsync -av /Users/danesh/Documents/AGY/agy/classification/ /Users/danesh/Documents/AGY/classification_65bands/ --exclude '.git'` to update the cold storage.
64
-
65
- ---
66
-
67
- ## 4. Completed Pipelines & Methodologies
68
- *All items listed here have been fully trained, evaluated, pushed to GitHub (via Git LFS), and synced to Google Drive.*
69
-
70
- 1. **Baseline (`baseline/`)**
71
- - **Method:** Softmax, MC Dropout, Temperature Scaling.
72
- - **Models:** AlexNet, GFNet, ViT-UNet (both `_best` and `_final`).
73
- 2. **Self-Adaptive Conformal Prediction (`sacp/`)**
74
- - **Method:** Post-hoc calibration evaluated across spatial windows (3, 5, 7, 9).
75
- 3. **Multi-Head CP (`multicp/`)**
76
- - **Method:** Architectural modification outputting multi-head predictions.
77
- - **Models:** 6 multi-head `.keras` variants.
78
- 4. **Deep Ensembles & CreDE (`ensemble/`)**
79
- - **Method:** 15 distinct teacher models trained (5 seeds per architecture).
80
- - **Evaluation:** CreDE spatial uncertainty maps generated.
81
- - **Kaggle Dependency:** These 15 models were uploaded as a dedicated Kaggle Dataset (`daneshselwal/dias-ensemble-models`, obfuscated as `k016-k030`) so other kernels could access them.
82
-
83
- ## 5. Active / Pending Pipelines
84
- *All pipelines are now COMPLETE as of 2026-09-16. No active Kaggle kernels running.*
85
-
86
- ### ✅ CREDIT Distillation (`credit/`) — COMPLETED 2026-09-16
87
- - **Method:** Conformalized Regularized Distillation Training (Student learning from the 15 Ensembles).
88
- - **Models:** AlexNet_CNN_CREDIT_best.keras (260MB), GFNet_CREDIT_best.keras (125MB), ViT_UNet_CREDIT_best.keras (187MB)
89
- - **Results:** CREDIT_Results.xlsx, credit_evaluation_summary.csv, credit_train_uncertainty_summary.csv, 3 spatial maps PNGs, confusion matrices PNG
90
- - **GitHub:** Pushed via Git LFS (commit dcb2cfc)
91
- - **Drive:** Synced to classification_65bands/credit/results/
92
- - *Note for future agents:* Kaggle kernels cannot read large `.keras` files from other kernels via `kernel_sources`. Fix: mount ensemble teachers explicitly as `dataset_sources` (dias-ensemble-models dataset, k016-k030).
93
-
94
- ## 6. Future Skeletons (To Be Implemented)
95
- The original research repository contains several advanced methodologies that currently exist only as empty "skeleton" directories in this workspace. Future iterations of this project will port and execute these:
96
- - `dapm/` (Deep Adaptive Predictive Modeling)
97
- - `dofa/` (Dynamic Wavelength Tokenization)
98
- - `georsclip/` (Vision-Language Model)
99
- - `mambahsi/` (State Space Models)
100
- - `spatialgcn/` (Graph Convolutional Networks)
101
- - `multicp_sacp/` (Spatial Multi-Head CP)
102
-
103
- ---
104
-
105
- ## 7. Hard Rules for Autonomous Agents
106
- 1. **Never delete `.keras` files** from Git-tracked folders unless you are intentionally removing them from the GitHub repository using `git rm`.
107
- 2. **Always use `BypassSandbox: true`** when communicating with network interfaces (Git push, Kaggle CLI commands, Rsync).
108
- 3. **Protect the Google Drive:** Only write to `/Users/danesh/Documents/AGY/classification_65bands/` using `rsync` from the local workspace. Do not treat it as a scratch pad.
109
- 4. **Update this Document:** If a new pipeline is executed, a new Kaggle dataset is created, or a core architectural rule changes, this `agent.md` file MUST be updated to reflect the new state.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/Model_training.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
baseline/Model_uncertainty_comparison.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
baseline/results/AlexNet_CNN_classification_report.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "0": {
3
- "precision": 1.0,
4
- "recall": 1.0,
5
- "f1-score": 1.0,
6
- "support": 2432.0
7
- },
8
- "1": {
9
- "precision": 1.0,
10
- "recall": 1.0,
11
- "f1-score": 1.0,
12
- "support": 1408.0
13
- },
14
- "2": {
15
- "precision": 0.9982363315696648,
16
- "recall": 1.0,
17
- "f1-score": 0.999117387466902,
18
- "support": 566.0
19
- },
20
- "3": {
21
- "precision": 1.0,
22
- "recall": 1.0,
23
- "f1-score": 1.0,
24
- "support": 2729.0
25
- },
26
- "4": {
27
- "precision": 0.9996854356715948,
28
- "recall": 0.9993710691823899,
29
- "f1-score": 0.9995282277087593,
30
- "support": 3180.0
31
- },
32
- "5": {
33
- "precision": 0.9967074317968015,
34
- "recall": 1.0,
35
- "f1-score": 0.9983510011778564,
36
- "support": 2119.0
37
- },
38
- "6": {
39
- "precision": 1.0,
40
- "recall": 0.9824561403508771,
41
- "f1-score": 0.9911504424778761,
42
- "support": 399.0
43
- },
44
- "7": {
45
- "precision": 0.9984399375975039,
46
- "recall": 0.9984399375975039,
47
- "f1-score": 0.9984399375975039,
48
- "support": 641.0
49
- },
50
- "accuracy": 0.9992578298946119,
51
- "macro avg": {
52
- "precision": 0.9991336420794457,
53
- "recall": 0.9975333933913464,
54
- "f1-score": 0.9983233745536122,
55
- "support": 13474.0
56
- },
57
- "weighted avg": {
58
- "precision": 0.9992596479947695,
59
- "recall": 0.9992578298946119,
60
- "f1-score": 0.999255974726486,
61
- "support": 13474.0
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/results/GFNet_classification_report.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "0": {
3
- "precision": 1.0,
4
- "recall": 1.0,
5
- "f1-score": 1.0,
6
- "support": 2432.0
7
- },
8
- "1": {
9
- "precision": 1.0,
10
- "recall": 1.0,
11
- "f1-score": 1.0,
12
- "support": 1408.0
13
- },
14
- "2": {
15
- "precision": 1.0,
16
- "recall": 1.0,
17
- "f1-score": 1.0,
18
- "support": 566.0
19
- },
20
- "3": {
21
- "precision": 1.0,
22
- "recall": 1.0,
23
- "f1-score": 1.0,
24
- "support": 2729.0
25
- },
26
- "4": {
27
- "precision": 1.0,
28
- "recall": 1.0,
29
- "f1-score": 1.0,
30
- "support": 3180.0
31
- },
32
- "5": {
33
- "precision": 0.9995283018867924,
34
- "recall": 1.0,
35
- "f1-score": 0.9997640953054966,
36
- "support": 2119.0
37
- },
38
- "6": {
39
- "precision": 1.0,
40
- "recall": 0.9974937343358395,
41
- "f1-score": 0.998745294855709,
42
- "support": 399.0
43
- },
44
- "7": {
45
- "precision": 1.0,
46
- "recall": 1.0,
47
- "f1-score": 1.0,
48
- "support": 641.0
49
- },
50
- "accuracy": 0.9999257829894612,
51
- "macro avg": {
52
- "precision": 0.9999410377358491,
53
- "recall": 0.99968671679198,
54
- "f1-score": 0.9998136737701507,
55
- "support": 13474.0
56
- },
57
- "weighted avg": {
58
- "precision": 0.999925817997485,
59
- "recall": 0.9999257829894612,
60
- "f1-score": 0.9999257451832994,
61
- "support": 13474.0
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/results/ViT_UNet_classification_report.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "0": {
3
- "precision": 1.0,
4
- "recall": 1.0,
5
- "f1-score": 1.0,
6
- "support": 2432.0
7
- },
8
- "1": {
9
- "precision": 1.0,
10
- "recall": 1.0,
11
- "f1-score": 1.0,
12
- "support": 1408.0
13
- },
14
- "2": {
15
- "precision": 1.0,
16
- "recall": 1.0,
17
- "f1-score": 1.0,
18
- "support": 566.0
19
- },
20
- "3": {
21
- "precision": 1.0,
22
- "recall": 0.9992671308171491,
23
- "f1-score": 0.999633431085044,
24
- "support": 2729.0
25
- },
26
- "4": {
27
- "precision": 0.9990571967316153,
28
- "recall": 0.9996855345911949,
29
- "f1-score": 0.9993712668972021,
30
- "support": 3180.0
31
- },
32
- "5": {
33
- "precision": 0.9990543735224586,
34
- "recall": 0.997168475696083,
35
- "f1-score": 0.9981105337742088,
36
- "support": 2119.0
37
- },
38
- "6": {
39
- "precision": 0.9900744416873449,
40
- "recall": 1.0,
41
- "f1-score": 0.9950124688279302,
42
- "support": 399.0
43
- },
44
- "7": {
45
- "precision": 1.0,
46
- "recall": 1.0,
47
- "f1-score": 1.0,
48
- "support": 641.0
49
- },
50
- "accuracy": 0.9993320469051507,
51
- "macro avg": {
52
- "precision": 0.9985232514926774,
53
- "recall": 0.9995151426380534,
54
- "f1-score": 0.9990159625730481,
55
- "support": 13474.0
56
- },
57
- "weighted avg": {
58
- "precision": 0.9993348527040135,
59
- "recall": 0.9993320469051507,
60
- "f1-score": 0.9993325262204305,
61
- "support": 13474.0
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/results/classification_summary.csv DELETED
@@ -1,4 +0,0 @@
1
- model,capacity_tag,test_accuracy,kappa,macro_f1,weighted_f1,val_nll,test_nll,val_brier,test_brier,test_ece_15bin,epochs_configured,epochs_ran,train_time_sec,best_model_path,final_model_path
2
- GFNet,legacy_arch,0.9999257829894612,0.9999105961705329,0.9998136737701507,0.9999257451832994,0.007947924362124414,0.008106300741815049,0.00013942152948862152,0.00022818487861826534,0.007952897975472044,100,100,1130.2998793399997,/kaggle/working/models/GFNet_best.keras,/kaggle/working/models/GFNet_final.keras
3
- ViT_UNet,legacy_arch,0.9993320469051507,0.9991954066321567,0.9990159625730481,0.9993325262204305,0.04282111958386762,0.04173323245097391,0.0031020371728503766,0.002701069443341945,0.03891381961762844,100,100,2168.4684350519997,/kaggle/working/models/ViT_UNet_best.keras,/kaggle/working/models/ViT_UNet_final.keras
4
- AlexNet_CNN,legacy_arch,0.9992578298946119,0.999105915989467,0.9983233745536122,0.999255974726486,0.0025646521673203223,0.0025646521673203223,0.001327359502401775,0.001327359502401775,0.0004946586948412895,100,100,1132.121482828,/kaggle/working/models/AlexNet_CNN_best.keras,/kaggle/working/models/AlexNet_CNN_final.keras
 
 
 
 
 
baseline/results/scene_visualizations/AlexNet_CNN_initial_classification.png DELETED

Git LFS Details

  • SHA256: c206510647d21b5d500bf22520c0343379cb9871a628bf80e76864a9d3183a07
  • Pointer size: 131 Bytes
  • Size of remote file: 647 kB
baseline/results/scene_visualizations/GFNet_initial_classification.png DELETED

Git LFS Details

  • SHA256: b55f38267481f6e0a904f877b509881be735cb5a864e7ce208d475e019d153c7
  • Pointer size: 131 Bytes
  • Size of remote file: 594 kB
baseline/results/scene_visualizations/ViT_UNet_initial_classification.png DELETED

Git LFS Details

  • SHA256: 0f88a794bd4222bb6579e2110ba6babf8710e23ef17455536ca8a520fc15c468
  • Pointer size: 131 Bytes
  • Size of remote file: 652 kB
baseline/results/scene_visualizations/combined_initial_classification_overview.png DELETED

Git LFS Details

  • SHA256: 71bfb0f98aa21d98b6e414f22b427b215c5bf2d4a17a54c7aedec28526a611ea
  • Pointer size: 132 Bytes
  • Size of remote file: 3.92 MB
baseline/results/scene_visualizations/ground_truth_label_map.png DELETED

Git LFS Details

  • SHA256: 553e71eab89141eed07788a957bcbef7fe74ae16b47f3310f85898b6442fad5d
  • Pointer size: 131 Bytes
  • Size of remote file: 130 kB
baseline/results/scene_visualizations/initial_classification_maps.xlsx DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:cc625a90b81f4bedd73daada1387bf53d113a18f75b0bbe2e7f370025e25b3fa
3
- size 7448141
 
 
 
 
baseline/results/scene_visualizations/scene_rgb.png DELETED

Git LFS Details

  • SHA256: 6e819bae0b271730f06700e9f87602dd49acda02730e25dfdaeeca5679c097b4
  • Pointer size: 132 Bytes
  • Size of remote file: 1.52 MB
baseline/results/training_plots/AlexNet_CNN_training_curves.png DELETED

Git LFS Details

  • SHA256: 8174eb27c6910c56596da030091c38fc888156e3a27e88497a00dedd48e8ae6e
  • Pointer size: 131 Bytes
  • Size of remote file: 165 kB
baseline/results/training_plots/GFNet_training_curves.png DELETED

Git LFS Details

  • SHA256: 1b5f9918f37fa69733d0ef378ed6ce96dcae62f1af379855923b2a8d392d400d
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
baseline/results/training_plots/ViT_UNet_training_curves.png DELETED

Git LFS Details

  • SHA256: 117c1a238adeec0817b416c6f697a6ef5e474591fc7e67f94d79780b9cef14b8
  • Pointer size: 131 Bytes
  • Size of remote file: 155 kB
baseline/results/training_plots/confusion_matrices_side_by_side.png DELETED

Git LFS Details

  • SHA256: ba39221b847b3deb8cc74d000ec4ba0ea033eb5ab764864b420b7a13f443b13b
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
baseline/results/training_plots/model_comparison_metrics.png DELETED

Git LFS Details

  • SHA256: 546cd6c966e3bf0c73244bae97d46bcdce26c1f8a91e1c6c7f95fcda951bcaa7
  • Pointer size: 131 Bytes
  • Size of remote file: 142 kB
baseline/results/training_plots/uncertainty_proxy_metrics.png DELETED
Binary file (71.9 kB)
 
baseline/results/uncertainty_results/conformal_reports_all_models.xlsx DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:7eb1fdbbc6e271c23705080d3e11b04deb82fe68e6c3c031593db00cfd478857
3
- size 16777908
 
 
 
 
baseline/results/uncertainty_results/per_class_coverage.csv DELETED
@@ -1,121 +0,0 @@
1
- class_id,class_coverage,support_count,model_name,method
2
- 0,1.0,1216,AlexNet,SplitConformal
3
- 1,1.0,704,AlexNet,SplitConformal
4
- 2,0.8869257950530035,283,AlexNet,SplitConformal
5
- 3,0.9641025641025641,1365,AlexNet,SplitConformal
6
- 4,0.9679245283018868,1590,AlexNet,SplitConformal
7
- 5,0.8895184135977338,1059,AlexNet,SplitConformal
8
- 6,0.77,200,AlexNet,SplitConformal
9
- 7,0.8875,320,AlexNet,SplitConformal
10
- 0,0.9481907894736842,1216,AlexNet,ClassConditionalConformal
11
- 1,0.9985795454545454,704,AlexNet,ClassConditionalConformal
12
- 2,0.9752650176678446,283,AlexNet,ClassConditionalConformal
13
- 3,0.9443223443223443,1365,AlexNet,ClassConditionalConformal
14
- 4,0.9452830188679245,1590,AlexNet,ClassConditionalConformal
15
- 5,0.9480642115203022,1059,AlexNet,ClassConditionalConformal
16
- 6,0.985,200,AlexNet,ClassConditionalConformal
17
- 7,0.959375,320,AlexNet,ClassConditionalConformal
18
- 0,0.9539473684210527,1216,AlexNet,RC3P
19
- 1,0.9985795454545454,704,AlexNet,RC3P
20
- 2,0.9752650176678446,283,AlexNet,RC3P
21
- 3,0.9509157509157509,1365,AlexNet,RC3P
22
- 4,0.9553459119496855,1590,AlexNet,RC3P
23
- 5,0.9546742209631728,1059,AlexNet,RC3P
24
- 6,0.99,200,AlexNet,RC3P
25
- 7,0.959375,320,AlexNet,RC3P
26
- 0,0.9481907894736842,1216,AlexNet,ClusteredConformal
27
- 1,0.9985795454545454,704,AlexNet,ClusteredConformal
28
- 2,0.9363957597173145,283,AlexNet,ClusteredConformal
29
- 3,0.9692307692307692,1365,AlexNet,ClusteredConformal
30
- 4,0.9716981132075472,1590,AlexNet,ClusteredConformal
31
- 5,0.9480642115203022,1059,AlexNet,ClusteredConformal
32
- 6,0.825,200,AlexNet,ClusteredConformal
33
- 7,0.909375,320,AlexNet,ClusteredConformal
34
- 0,1.0,1216,AlexNet,RAPS
35
- 1,1.0,704,AlexNet,RAPS
36
- 2,1.0,283,AlexNet,RAPS
37
- 3,1.0,1365,AlexNet,RAPS
38
- 4,1.0,1590,AlexNet,RAPS
39
- 5,1.0,1059,AlexNet,RAPS
40
- 6,1.0,200,AlexNet,RAPS
41
- 7,1.0,320,AlexNet,RAPS
42
- 0,0.9161184210526315,1216,GFNet,SplitConformal
43
- 1,0.9985795454545454,704,GFNet,SplitConformal
44
- 2,0.7526501766784452,283,GFNet,SplitConformal
45
- 3,0.9728937728937729,1365,GFNet,SplitConformal
46
- 4,0.9773584905660377,1590,GFNet,SplitConformal
47
- 5,0.9490084985835694,1059,GFNet,SplitConformal
48
- 6,0.93,200,GFNet,SplitConformal
49
- 7,0.940625,320,GFNet,SplitConformal
50
- 0,0.9449013157894737,1216,GFNet,ClassConditionalConformal
51
- 1,0.9588068181818182,704,GFNet,ClassConditionalConformal
52
- 2,0.9646643109540636,283,GFNet,ClassConditionalConformal
53
- 3,0.9443223443223443,1365,GFNet,ClassConditionalConformal
54
- 4,0.9534591194968554,1590,GFNet,ClassConditionalConformal
55
- 5,0.9716713881019831,1059,GFNet,ClassConditionalConformal
56
- 6,0.92,200,GFNet,ClassConditionalConformal
57
- 7,0.928125,320,GFNet,ClassConditionalConformal
58
- 0,0.946546052631579,1216,GFNet,RC3P
59
- 1,0.9630681818181818,704,GFNet,RC3P
60
- 2,0.9717314487632509,283,GFNet,RC3P
61
- 3,0.9457875457875458,1365,GFNet,RC3P
62
- 4,0.9572327044025157,1590,GFNet,RC3P
63
- 5,0.9716713881019831,1059,GFNet,RC3P
64
- 6,0.92,200,GFNet,RC3P
65
- 7,0.95625,320,GFNet,RC3P
66
- 0,0.9827302631578947,1216,GFNet,ClusteredConformal
67
- 1,0.9971590909090909,704,GFNet,ClusteredConformal
68
- 2,0.8127208480565371,283,GFNet,ClusteredConformal
69
- 3,0.9194139194139194,1365,GFNet,ClusteredConformal
70
- 4,0.9572327044025157,1590,GFNet,ClusteredConformal
71
- 5,0.9707271010387157,1059,GFNet,ClusteredConformal
72
- 6,0.94,200,GFNet,ClusteredConformal
73
- 7,0.928125,320,GFNet,ClusteredConformal
74
- 0,1.0,1216,GFNet,RAPS
75
- 1,1.0,704,GFNet,RAPS
76
- 2,1.0,283,GFNet,RAPS
77
- 3,1.0,1365,GFNet,RAPS
78
- 4,1.0,1590,GFNet,RAPS
79
- 5,1.0,1059,GFNet,RAPS
80
- 6,0.995,200,GFNet,RAPS
81
- 7,1.0,320,GFNet,RAPS
82
- 0,0.9983552631578947,1216,ViT,SplitConformal
83
- 1,1.0,704,ViT,SplitConformal
84
- 2,0.9293286219081273,283,ViT,SplitConformal
85
- 3,0.9194139194139194,1365,ViT,SplitConformal
86
- 4,0.9534591194968554,1590,ViT,SplitConformal
87
- 5,0.939565627950897,1059,ViT,SplitConformal
88
- 6,0.765,200,ViT,SplitConformal
89
- 7,0.890625,320,ViT,SplitConformal
90
- 0,0.9605263157894737,1216,ViT,ClassConditionalConformal
91
- 1,0.9801136363636364,704,ViT,ClassConditionalConformal
92
- 2,0.9328621908127208,283,ViT,ClassConditionalConformal
93
- 3,0.9340659340659341,1365,ViT,ClassConditionalConformal
94
- 4,0.9421383647798742,1590,ViT,ClassConditionalConformal
95
- 5,0.95561850802644,1059,ViT,ClassConditionalConformal
96
- 6,0.94,200,ViT,ClassConditionalConformal
97
- 7,0.946875,320,ViT,ClassConditionalConformal
98
- 0,0.962171052631579,1216,ViT,RC3P
99
- 1,0.9801136363636364,704,ViT,RC3P
100
- 2,0.9469964664310954,283,ViT,RC3P
101
- 3,0.9355311355311355,1365,ViT,RC3P
102
- 4,0.9477987421383648,1590,ViT,RC3P
103
- 5,0.9612842304060434,1059,ViT,RC3P
104
- 6,0.94,200,ViT,RC3P
105
- 7,0.95625,320,ViT,RC3P
106
- 0,0.9975328947368421,1216,ViT,ClusteredConformal
107
- 1,1.0,704,ViT,ClusteredConformal
108
- 2,0.9328621908127208,283,ViT,ClusteredConformal
109
- 3,0.8974358974358975,1365,ViT,ClusteredConformal
110
- 4,0.9440251572327044,1590,ViT,ClusteredConformal
111
- 5,0.9178470254957507,1059,ViT,ClusteredConformal
112
- 6,0.94,200,ViT,ClusteredConformal
113
- 7,0.946875,320,ViT,ClusteredConformal
114
- 0,0.9991776315789473,1216,ViT,RAPS
115
- 1,1.0,704,ViT,RAPS
116
- 2,1.0,283,ViT,RAPS
117
- 3,0.9970695970695971,1365,ViT,RAPS
118
- 4,1.0,1590,ViT,RAPS
119
- 5,1.0,1059,ViT,RAPS
120
- 6,1.0,200,ViT,RAPS
121
- 7,1.0,320,ViT,RAPS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/results/uncertainty_results/run_config.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "seed": 42,
3
- "data_file": "/kaggle/input/datasets/daneshselwal/dias-dataset-k01/k001.mat",
4
- "label_file": "/kaggle/input/datasets/daneshselwal/dias-dataset-k01/k002.mat",
5
- "model_dir": "/kaggle/input/datasets/daneshselwal/dias-baseline-models",
6
- "output_dir": "/kaggle/working/results/uncertainty_results",
7
- "results_dir": "/kaggle/working/results/uncertainty_results",
8
- "h": 512,
9
- "w": 512,
10
- "b": 65,
11
- "patch_size": 9,
12
- "train_percent": 0.75,
13
- "calib_fraction_of_test": 0.5,
14
- "alpha": 0.05,
15
- "raps_lam": 0.01,
16
- "raps_k_reg": 1,
17
- "n_clusters": 4,
18
- "batch_size": 128,
19
- "timestamp_utc": "2026-09-10T01:08:25.151909+00:00"
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/results/uncertainty_results/summary_metrics.csv DELETED
@@ -1,16 +0,0 @@
1
- model_name,method,target_coverage,empirical_coverage,avg_set_size,median_set_size,singleton_rate,empty_set_rate,runtime_sec,alpha,lam,n_clusters,mean_per_class_coverage
2
- AlexNet,ClassConditionalConformal,0.95,0.9547276235713226,0.9547276235713226,1.0,0.9547276235713226,0.04527237642867745,2.292357693999975,0.05,,,0.9630099909133306
3
- GFNet,ClassConditionalConformal,0.95,0.9517589431497699,0.9517589431497699,1.0,0.9517589431497699,0.04824105685023007,2.339258706999999,0.05,,,0.9482437871058172
4
- ViT,ClassConditionalConformal,0.95,0.9496808668546831,0.9496808668546831,1.0,0.9496808668546831,0.050319133145316906,2.224427326999944,0.05,,,0.9490249937297599
5
- AlexNet,ClusteredConformal,0.95,0.9572510019296423,0.9572510019296423,1.0,0.9572510019296423,0.04274899807035772,3.0594818560000476,0.05,,4.0,0.9383167735755203
6
- GFNet,ClusteredConformal,0.95,0.9525011132551581,0.9525011132551581,1.0,0.9525011132551581,0.047498886744841914,5.474895679999918,0.05,,4.0,0.9385136158723342
7
- ViT,ClusteredConformal,0.95,0.9455247142645095,0.9455247142645095,1.0,0.9455247142645095,0.054475285735490576,10.67419527800007,0.05,,4.0,0.9470722707142394
8
- AlexNet,RAPS,0.95,1.0,1.0,1.0,1.0,0.0,0.4984293269999398,0.05,0.01,,1.0
9
- GFNet,RAPS,0.95,0.9998515659789223,1.0,1.0,1.0,0.0,0.4831613420000167,0.05,0.01,,0.999375
10
- ViT,RAPS,0.95,0.9992578298946119,1.0,1.0,1.0,0.0,0.46974008299991965,0.05,0.01,,0.999530903581068
11
- AlexNet,RC3P,0.95,0.9606649844144278,0.9606649844144278,1.0,0.9606649844144278,0.03933501558557221,2.347965410000029,0.05,,,0.9672628519215065
12
- GFNet,RC3P,0.95,0.955321359655633,0.955321359655633,1.0,0.955321359655633,0.04467864034436693,2.365494427000044,0.05,,,0.9540359151881321
13
- ViT,RC3P,0.95,0.9535401514027015,0.9535401514027015,1.0,0.9535401514027015,0.0464598485972985,2.2882030419999637,0.05,,,0.9537681579377318
14
- AlexNet,SplitConformal,0.95,0.9508683390233041,0.9508683390233041,1.0,0.9508683390233041,0.04913166097669586,2.4034767729999658,0.05,,,0.9207464126318985
15
- GFNet,SplitConformal,0.95,0.9505714709811489,0.9505714709811489,1.0,0.9505714709811489,0.04942852901885112,2.2923555589999296,0.05,,,0.9296542381536252
16
- ViT,SplitConformal,0.95,0.9477512245806738,0.9477512245806738,1.0,0.9477512245806738,0.05224877541932611,2.25247833800006,0.05,,,0.9244684439909617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/test_m2_challenger.py DELETED
@@ -1,261 +0,0 @@
1
- """
2
- Milestone 2 Empirical Challenge Harness
3
- Executed by teamwork_preview_challenger_m2_2
4
- """
5
- import os
6
- import sys
7
- import json
8
- import ast
9
- import hashlib
10
- from pathlib import Path
11
-
12
- REPO_ROOT = Path('/Users/danesh/Documents/AGY/agy/classification')
13
- ORIGINAL_NB_PATH = REPO_ROOT / 'baseline' / 'Model_uncertainty_comparison.ipynb'
14
- STAGED_NB_PATH = REPO_ROOT / 'baseline' / 'staging_uncertainty' / 'k004.ipynb'
15
- BASELINE_NB_PATH = REPO_ROOT / 'baseline' / 'k004.ipynb'
16
- KERNEL_META_PATH = REPO_ROOT / 'baseline' / 'staging_uncertainty' / 'kernel-metadata.json'
17
- MAPPING_PATH = REPO_ROOT / 'kaggle_name_mapping.json'
18
-
19
- results = {}
20
-
21
- def log_test(name, passed, details=None):
22
- results[name] = {"passed": passed, "details": details}
23
- status = "PASS" if passed else "FAIL"
24
- print(f"[{status}] {name}")
25
- if details:
26
- print(f" Details: {details}")
27
-
28
- def compute_sha256(filepath):
29
- h = hashlib.sha256()
30
- with open(filepath, 'rb') as f:
31
- while chunk := f.read(8192):
32
- h.update(chunk)
33
- return h.hexdigest()
34
-
35
- def test_files_exist_and_hashes():
36
- assert STAGED_NB_PATH.exists(), f"Missing {STAGED_NB_PATH}"
37
- assert BASELINE_NB_PATH.exists(), f"Missing {BASELINE_NB_PATH}"
38
- assert KERNEL_META_PATH.exists(), f"Missing {KERNEL_META_PATH}"
39
- assert MAPPING_PATH.exists(), f"Missing {MAPPING_PATH}"
40
- assert ORIGINAL_NB_PATH.exists(), f"Missing {ORIGINAL_NB_PATH}"
41
-
42
- staged_hash = compute_sha256(STAGED_NB_PATH)
43
- baseline_hash = compute_sha256(BASELINE_NB_PATH)
44
-
45
- staged_size = STAGED_NB_PATH.stat().st_size
46
- baseline_size = BASELINE_NB_PATH.stat().st_size
47
-
48
- is_identical = (staged_hash == baseline_hash and staged_size == baseline_size)
49
- log_test("Staged and Baseline k004.ipynb Byte Parity", is_identical,
50
- f"staged_sha={staged_hash[:12]}..., baseline_sha={baseline_hash[:12]}..., size={staged_size}")
51
- return staged_hash, staged_size
52
-
53
- def test_notebook_json_and_cell_count():
54
- with open(ORIGINAL_NB_PATH, 'r', encoding='utf-8') as f:
55
- orig_nb = json.load(f)
56
- with open(STAGED_NB_PATH, 'r', encoding='utf-8') as f:
57
- staged_nb = json.load(f)
58
-
59
- orig_cells = orig_nb.get('cells', [])
60
- staged_cells = staged_nb.get('cells', [])
61
-
62
- count_match = (len(orig_cells) == 44 and len(staged_cells) == 44)
63
- log_test("Notebook Cell Count Matches Expected (44)", count_match,
64
- f"Original: {len(orig_cells)}, Staged: {len(staged_cells)}")
65
-
66
- orig_types = [c.get('cell_type') for c in orig_cells]
67
- staged_types = [c.get('cell_type') for c in staged_cells]
68
- types_match = (orig_types == staged_types)
69
-
70
- md_count = staged_types.count('markdown')
71
- code_count = staged_types.count('code')
72
- log_test("Cell Types Parity (22 markdown, 22 code)", types_match and md_count == 22 and code_count == 22,
73
- f"Markdown: {md_count}, Code: {code_count}")
74
-
75
- return orig_nb, staged_nb
76
-
77
- def test_cell_diff_strictness(orig_nb, staged_nb):
78
- orig_cells = orig_nb['cells']
79
- staged_cells = staged_nb['cells']
80
-
81
- modified_cells = []
82
- modified_code_cells = []
83
- modified_md_cells = []
84
- for idx, (orig_c, staged_c) in enumerate(zip(orig_cells, staged_cells)):
85
- orig_src = "".join(orig_c.get('source', []))
86
- staged_src = "".join(staged_c.get('source', []))
87
- if orig_src != staged_src:
88
- modified_cells.append(idx)
89
- if orig_c.get('cell_type') == 'code':
90
- modified_code_cells.append(idx)
91
- else:
92
- modified_md_cells.append(idx)
93
-
94
- # Strict check across all cells
95
- strictly_only_1_4_6 = (modified_cells == [1, 4, 6])
96
- code_only_1_4_6 = (modified_code_cells == [1, 4, 6])
97
-
98
- log_test("Modified Cells - Code Cells Strictly 1, 4, 6", code_only_1_4_6,
99
- f"Modified code cells: {modified_code_cells}")
100
- log_test("Modified Cells - All Cells Strictly 1, 4, 6 (Empirical Challenge)", strictly_only_1_4_6,
101
- f"All modified cells: {modified_cells} (Markdown Cell 0 was modified: {modified_md_cells})")
102
-
103
- # Check that remaining cells have identical source content
104
- remaining_identical = True
105
- non_matching = []
106
- for idx in range(44):
107
- if idx not in modified_cells:
108
- o_src = "".join(orig_cells[idx].get('source', []))
109
- s_src = "".join(staged_cells[idx].get('source', []))
110
- if o_src != s_src:
111
- remaining_identical = False
112
- non_matching.append(idx)
113
- log_test("All 40 Unmodified Cells Are 100% Identical", remaining_identical,
114
- f"Non-matching cells: {non_matching}")
115
-
116
- return modified_cells
117
-
118
- def test_execution_hygiene(staged_nb):
119
- cells = staged_nb['cells']
120
- pollution = []
121
- for idx, cell in enumerate(cells):
122
- if cell.get('cell_type') == 'code':
123
- ec = cell.get('execution_count')
124
- outputs = cell.get('outputs', [])
125
- if ec is not None:
126
- pollution.append(f"Cell {idx} execution_count={ec}")
127
- if len(outputs) > 0:
128
- pollution.append(f"Cell {idx} has {len(outputs)} outputs")
129
- meta = cell.get('metadata', {})
130
- if 'execution' in meta:
131
- pollution.append(f"Cell {idx} metadata has 'execution'")
132
- if 'scrolled' in meta or 'collapsed' in meta:
133
- pollution.append(f"Cell {idx} metadata has scroll/collapsed flag")
134
-
135
- is_clean = (len(pollution) == 0)
136
- log_test("Execution Outputs Cleared and Execution Counts Null", is_clean,
137
- pollution if pollution else "All 22 code cells clean")
138
-
139
- def test_ast_validity(staged_nb):
140
- cells = staged_nb['cells']
141
- syntax_errors = []
142
- for idx, cell in enumerate(cells):
143
- if cell.get('cell_type') == 'code':
144
- src = "".join(cell.get('source', []))
145
- try:
146
- ast.parse(src)
147
- except SyntaxError as e:
148
- syntax_errors.append(f"Cell {idx} SyntaxError: {e}")
149
-
150
- no_errors = (len(syntax_errors) == 0)
151
- log_test("All 22 Code Cells Pass AST Syntax Parsing", no_errors,
152
- syntax_errors if syntax_errors else "All 22 code cells valid Python AST")
153
-
154
- def test_modified_cells_semantics(staged_nb):
155
- cells = staged_nb['cells']
156
- c1_src = "".join(cells[1].get('source', []))
157
- c4_src = "".join(cells[4].get('source', []))
158
- c6_src = "".join(cells[6].get('source', []))
159
-
160
- # Cell 1 checks
161
- c1_colab_removed = "google.colab" not in c1_src and "drive.mount" not in c1_src
162
- c1_packages = "xlsxwriter" in c1_src and "openpyxl" in c1_src
163
- log_test("Cell 1: Drive Mount Removed & Excel Packages Handled", c1_colab_removed and c1_packages,
164
- f"Drive mount absent: {c1_colab_removed}, xlsxwriter/openpyxl present: {c1_packages}")
165
-
166
- # Cell 4 checks
167
- c4_kaggle_detection = "kaggle" in c4_src or "KAGGLE" in c4_src or "/kaggle/input" in c4_src
168
- c4_data_resolution = "k001.mat" in c4_src and "k002.mat" in c4_src
169
- c4_model_resolution = "k005.keras" in c4_src and "k006.keras" in c4_src and "k007.keras" in c4_src
170
- c4_keys_intact = "'AlexNet_CNN':" in c4_src and "'GFNet':" in c4_src and "'ViT_UNet':" in c4_src
171
- c4_trusted_roots = "/kaggle/input" in c4_src and "/kaggle/working" in c4_src
172
- c4_results_dir = "/kaggle/working/results" in c4_src
173
-
174
- # Read-only guard check: verify MODEL_DIR.mkdir() is NOT called or is safely guarded against /kaggle/input
175
- c4_readonly_safe = ("MODEL_DIR.mkdir" not in c4_src) or ("if not str(MODEL_DIR).startswith('/kaggle/input')" in c4_src)
176
- log_test("Cell 4: Dynamic Paths, Model Keys, Trusted Roots, Readonly Safety",
177
- c4_kaggle_detection and c4_data_resolution and c4_model_resolution and c4_keys_intact and c4_trusted_roots and c4_results_dir and c4_readonly_safe,
178
- f"Keys intact: {c4_keys_intact}, Trusted roots: {c4_trusted_roots}, Readonly safe: {c4_readonly_safe}")
179
-
180
- # Cell 6 checks
181
- c6_kaggle_guard = "Path('/kaggle').exists()" in c6_src or "/kaggle" in c6_src
182
- log_test("Cell 6: Environment Guard Accepts /kaggle", c6_kaggle_guard,
183
- f"Guard check for /kaggle present: {c6_kaggle_guard}")
184
-
185
- def test_kernel_metadata():
186
- with open(KERNEL_META_PATH, 'r', encoding='utf-8') as f:
187
- meta = json.load(f)
188
-
189
- id_ok = meta.get('id') == "daneshselwal/dias-uncertainty-k04"
190
- code_file_ok = meta.get('code_file') == "k004.ipynb"
191
- gpu_ok = str(meta.get('enable_gpu')).lower() == "true"
192
- sources = meta.get('dataset_sources', [])
193
- expected_sources = ["daneshselwal/dias-dataset-k01", "daneshselwal/dias-baseline-models"]
194
- sources_ok = sources == expected_sources
195
- kernel_type_ok = meta.get('kernel_type') == "notebook"
196
-
197
- meta_ok = id_ok and code_file_ok and gpu_ok and sources_ok and kernel_type_ok
198
- log_test("kernel-metadata.json Contract Compliance", meta_ok,
199
- f"id={meta.get('id')}, code_file={meta.get('code_file')}, gpu={meta.get('enable_gpu')}, datasets={sources}")
200
-
201
- def test_mapping_file(staged_hash, staged_size):
202
- with open(MAPPING_PATH, 'r', encoding='utf-8') as f:
203
- mapping = json.load(f)
204
-
205
- # Check k004 in obfuscated_to_original
206
- obf_to_orig = mapping.get('obfuscated_to_original', {})
207
- orig_to_obf = mapping.get('original_to_obfuscated', {})
208
-
209
- has_staged_obf = ("baseline/staging_uncertainty/k004.ipynb" in obf_to_orig and
210
- obf_to_orig["baseline/staging_uncertainty/k004.ipynb"] == "baseline/Model_uncertainty_comparison.ipynb")
211
- has_orig_to_obf = ("baseline/Model_uncertainty_comparison.ipynb" in orig_to_obf and
212
- orig_to_obf["baseline/Model_uncertainty_comparison.ipynb"] in ["baseline/k004.ipynb", "baseline/staging_uncertainty/k004.ipynb"])
213
-
214
- files = mapping.get('files', [])
215
- k004_entry = next((f for f in files if f.get('code') == 'k004'), None)
216
-
217
- entry_valid = False
218
- if k004_entry:
219
- entry_hash = k004_entry.get('sha256')
220
- entry_size = k004_entry.get('size_bytes')
221
- entry_staging = k004_entry.get('staging_relative_path')
222
- entry_valid = (entry_hash == staged_hash and entry_size == staged_size and entry_staging == "baseline/staging_uncertainty/k004.ipynb")
223
-
224
- mapping_ok = has_staged_obf and has_orig_to_obf and entry_valid
225
- log_test("kaggle_name_mapping.json Bidirectional & Hash Validation", mapping_ok,
226
- f"k004 entry found: {k004_entry is not None}, Hash match: {entry_valid}, File hash: {staged_hash}")
227
-
228
- def test_staging_directory_cleanliness():
229
- staging_dir = REPO_ROOT / 'baseline' / 'staging_uncertainty'
230
- files = list(staging_dir.iterdir())
231
- filenames = sorted([f.name for f in files])
232
- expected_filenames = ["k004.ipynb", "kernel-metadata.json"]
233
- clean = filenames == expected_filenames
234
- log_test("staging_uncertainty Directory Cleanliness (Exact File Match)", clean,
235
- f"Found files: {filenames}, Expected: {expected_filenames}")
236
-
237
- def main():
238
- print("=== STARTING CHALLENGER M2 INDEPENDENT TEST HARNESS ===")
239
- staged_hash, staged_size = test_files_exist_and_hashes()
240
- orig_nb, staged_nb = test_notebook_json_and_cell_count()
241
- test_cell_diff_strictness(orig_nb, staged_nb)
242
- test_execution_hygiene(staged_nb)
243
- test_ast_validity(staged_nb)
244
- test_modified_cells_semantics(staged_nb)
245
- test_kernel_metadata()
246
- test_mapping_file(staged_hash, staged_size)
247
- test_staging_directory_cleanliness()
248
-
249
- all_passed = all(t['passed'] for t in results.values())
250
- print("\n=== SUMMARY OF TESTS ===")
251
- print(f"Total tests: {len(results)}, Passed: {sum(1 for t in results.values() if t['passed'])}, Failed: {sum(1 for t in results.values() if not t['passed'])}")
252
- if not all_passed:
253
- print("FAILED TESTS:")
254
- for name, res in results.items():
255
- if not res['passed']:
256
- print(f" - {name}: {res['details']}")
257
- else:
258
- print("ALL TESTS PASSED EMPIRICALLY.")
259
-
260
- if __name__ == '__main__':
261
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
baseline/verify_m1_dataset.py DELETED
@@ -1,375 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Empirical verification and stress-testing script for Milestone 1:
4
- Baseline Models Upload & Verification.
5
-
6
- Performs:
7
- 1. Local Source vs Staging Byte Parity & SHA-256 Checksum Matching
8
- 2. Keras 3 Model Archive (ZIP) Structure Integrity Verification
9
- 3. Kaggle Name Mapping JSON Cross-Validation
10
- 4. Staging Dataset Metadata & Permission Audit
11
- 5. Remote Kaggle API / CLI Query & Size Verification
12
- 6. Kaggle Input Mounting & Edge Case Simulation
13
- """
14
-
15
- import sys
16
- import os
17
- import json
18
- import hashlib
19
- import zipfile
20
- import subprocess
21
- import stat
22
-
23
- BASE_DIR = "/Users/danesh/Documents/AGY/agy/classification"
24
- STAGING_DIR = os.path.join(BASE_DIR, "baseline/staging_models")
25
- SOURCE_DIR = os.path.join(BASE_DIR, "baseline/results/models")
26
- MAPPING_FILE = os.path.join(BASE_DIR, "kaggle_name_mapping.json")
27
- METADATA_FILE = os.path.join(STAGING_DIR, "dataset-metadata.json")
28
- DATASET_SLUG = "daneshselwal/dias-baseline-models"
29
-
30
- EXPECTED_FILES = {
31
- "k005.keras": {
32
- "source": "AlexNet_CNN_best.keras",
33
- "expected_size": 272550549,
34
- "mapping_code": "k005"
35
- },
36
- "k006.keras": {
37
- "source": "GFNet_best.keras",
38
- "expected_size": 130589582,
39
- "mapping_code": "k006"
40
- },
41
- "k007.keras": {
42
- "source": "ViT_UNet_best.keras",
43
- "expected_size": 195722847,
44
- "mapping_code": "k007"
45
- }
46
- }
47
-
48
- def sha256sum(filepath):
49
- h = hashlib.sha256()
50
- with open(filepath, "rb") as f:
51
- while chunk := f.read(1024 * 1024):
52
- h.update(chunk)
53
- return h.hexdigest()
54
-
55
- def test_local_parity_and_integrity():
56
- print("=== TEST 1: Local Source vs Staging Byte Parity & SHA-256 ===")
57
- results = {}
58
- all_passed = True
59
-
60
- for staged_name, meta in EXPECTED_FILES.items():
61
- staged_path = os.path.join(STAGING_DIR, staged_name)
62
- source_path = os.path.join(SOURCE_DIR, meta["source"])
63
-
64
- staged_exists = os.path.exists(staged_path)
65
- source_exists = os.path.exists(source_path)
66
- print(f"Checking {staged_name} (source: {meta['source']}):")
67
- print(f" Staged file exists: {staged_exists} ({staged_path})")
68
- print(f" Source file exists: {source_exists} ({source_path})")
69
-
70
- if not staged_exists or not source_exists:
71
- print(f" FAIL: File missing!")
72
- all_passed = False
73
- continue
74
-
75
- staged_size = os.path.getsize(staged_path)
76
- source_size = os.path.getsize(source_path)
77
- expected_size = meta["expected_size"]
78
-
79
- print(f" Staged size: {staged_size} bytes")
80
- print(f" Source size: {source_size} bytes")
81
- print(f" Expected size: {expected_size} bytes")
82
-
83
- if staged_size != expected_size or source_size != expected_size:
84
- print(f" FAIL: Size mismatch!")
85
- all_passed = False
86
- else:
87
- print(f" PASS: Size match exact ({staged_size} bytes)")
88
-
89
- staged_hash = sha256sum(staged_path)
90
- source_hash = sha256sum(source_path)
91
- print(f" Staged SHA-256: {staged_hash}")
92
- print(f" Source SHA-256: {source_hash}")
93
-
94
- if staged_hash != source_hash:
95
- print(f" FAIL: SHA-256 mismatch between source and staged!")
96
- all_passed = False
97
- else:
98
- print(f" PASS: Byte-for-byte SHA-256 match")
99
-
100
- final_name = meta["source"].replace("_best.keras", "_final.keras")
101
- final_path = os.path.join(SOURCE_DIR, final_name)
102
- if os.path.exists(final_path):
103
- final_size = os.path.getsize(final_path)
104
- final_hash = sha256sum(final_path)
105
- if final_hash == staged_hash and meta["source"] != final_name:
106
- print(f" NOTE: Final model has identical hash to best model: {final_name}")
107
- else:
108
- print(f" VERIFIED: Staged file matches '{meta['source']}' specifically (distinct from {final_name})")
109
-
110
- results[staged_name] = {
111
- "size": staged_size,
112
- "sha256": staged_hash,
113
- "match": (staged_hash == source_hash) and (staged_size == expected_size)
114
- }
115
-
116
- return all_passed, results
117
-
118
- def test_keras_archive_integrity():
119
- print("\n=== TEST 2: Keras 3 ZIP Archive Structure Integrity ===")
120
- all_passed = True
121
- for staged_name in EXPECTED_FILES:
122
- staged_path = os.path.join(STAGING_DIR, staged_name)
123
- print(f"Testing ZIP integrity of {staged_name}...")
124
- try:
125
- with zipfile.ZipFile(staged_path, 'r') as zf:
126
- bad_file = zf.testzip()
127
- if bad_file is not None:
128
- print(f" FAIL: Corrupt archive entry: {bad_file}")
129
- all_passed = False
130
- else:
131
- namelist = zf.namelist()
132
- print(f" PASS: Valid ZIP archive. Entries ({len(namelist)}): {namelist[:5]}...")
133
- has_config = any("config" in name or "metadata" in name for name in namelist)
134
- print(f" Keras metadata detected in archive: {has_config}")
135
- except Exception as e:
136
- print(f" FAIL: Exception reading ZIP {staged_name}: {e}")
137
- all_passed = False
138
- return all_passed
139
-
140
- def test_mapping_json_consistency(local_hashes):
141
- print("\n=== TEST 3: Cross-Validation with kaggle_name_mapping.json ===")
142
- all_passed = True
143
- if not os.path.exists(MAPPING_FILE):
144
- print(f"FAIL: Mapping file does not exist: {MAPPING_FILE}")
145
- return False
146
-
147
- with open(MAPPING_FILE, 'r') as f:
148
- mapping = json.load(f)
149
-
150
- obf_to_orig = mapping.get("obfuscated_to_original", {})
151
- orig_to_obf = mapping.get("original_to_obfuscated", {})
152
- files_list = {item["code"]: item for item in mapping.get("files", []) if "code" in item}
153
-
154
- for staged_name, meta in EXPECTED_FILES.items():
155
- code = meta["mapping_code"]
156
- print(f"Validating mapping for code '{code}' ({staged_name}):")
157
-
158
- if code not in files_list:
159
- print(f" FAIL: Code '{code}' missing from 'files' array in mapping!")
160
- all_passed = False
161
- continue
162
-
163
- file_entry = files_list[code]
164
- mapped_size = file_entry.get("size_bytes")
165
- mapped_sha256 = file_entry.get("sha256")
166
- local_hash = local_hashes.get(staged_name, {}).get("sha256")
167
-
168
- print(f" Mapping recorded size: {mapped_size}")
169
- print(f" Actual staged size: {local_hashes.get(staged_name, {}).get('size')}")
170
- print(f" Mapping recorded sha256: {mapped_sha256}")
171
- print(f" Actual staged sha256: {local_hash}")
172
-
173
- if mapped_size != meta["expected_size"]:
174
- print(f" FAIL: Mapping size mismatch for {code}")
175
- all_passed = False
176
- if mapped_sha256 != local_hash:
177
- print(f" FAIL: Mapping SHA-256 mismatch for {code}")
178
- all_passed = False
179
- else:
180
- print(f" PASS: Code {code} mapping perfectly matches local staged file")
181
-
182
- staged_rel = f"baseline/staging_models/{staged_name}"
183
- source_rel = f"baseline/results/models/{meta['source']}"
184
-
185
- if obf_to_orig.get(staged_rel) != source_rel and obf_to_orig.get(staged_name) != source_rel:
186
- print(f" FAIL: obf_to_orig missing mapping for {staged_name} -> {source_rel}")
187
- all_passed = False
188
- else:
189
- print(f" PASS: obf_to_orig contains {staged_name} -> {source_rel}")
190
-
191
- if orig_to_obf.get(source_rel) != staged_rel:
192
- print(f" FAIL: orig_to_obf missing mapping for {source_rel} -> {staged_rel}")
193
- all_passed = False
194
- else:
195
- print(f" PASS: orig_to_obf contains {source_rel} -> {staged_rel}")
196
-
197
- return all_passed
198
-
199
- def test_metadata_and_permissions():
200
- print("\n=== TEST 4: Staging Metadata and Permissions Audit ===")
201
- all_passed = True
202
-
203
- if not os.path.exists(METADATA_FILE):
204
- print(f"FAIL: dataset-metadata.json missing at {METADATA_FILE}")
205
- return False
206
-
207
- with open(METADATA_FILE, 'r') as f:
208
- try:
209
- meta = json.load(f)
210
- print(f"Loaded dataset-metadata.json content:\n{json.dumps(meta, indent=2)}")
211
- except Exception as e:
212
- print(f"FAIL: Invalid JSON in dataset-metadata.json: {e}")
213
- return False
214
-
215
- if meta.get("id") != DATASET_SLUG:
216
- print(f"FAIL: Expected id '{DATASET_SLUG}', got '{meta.get('id')}'")
217
- all_passed = False
218
- else:
219
- print(f"PASS: Dataset id matches '{DATASET_SLUG}'")
220
-
221
- if not meta.get("title"):
222
- print("FAIL: Title is empty")
223
- all_passed = False
224
- else:
225
- print(f"PASS: Title is present: '{meta.get('title')}'")
226
-
227
- licenses = meta.get("licenses", [])
228
- if not licenses or not any(l.get("name") for l in licenses):
229
- print("FAIL: Missing license specification")
230
- all_passed = False
231
- else:
232
- print(f"PASS: License specified: {licenses[0].get('name')}")
233
-
234
- for fname in os.listdir(STAGING_DIR):
235
- fpath = os.path.join(STAGING_DIR, fname)
236
- mode = os.stat(fpath).st_mode
237
- is_readable = bool(mode & stat.S_IRUSR)
238
- print(f"File permissions for {fname}: octal {oct(mode)}, owner readable: {is_readable}")
239
- if not is_readable:
240
- print(f"FAIL: File {fname} not readable!")
241
- all_passed = False
242
-
243
- return all_passed
244
-
245
- def get_kaggle_cmd():
246
- import shutil
247
- candidates = [
248
- shutil.which("kaggle"),
249
- "/opt/anaconda3/bin/kaggle",
250
- "/Users/danesh/.local/bin/kaggle",
251
- "/opt/homebrew/bin/kaggle",
252
- "/usr/local/bin/kaggle"
253
- ]
254
- for c in candidates:
255
- if c and os.path.exists(c) and os.access(c, os.X_OK):
256
- return c
257
- return "kaggle"
258
-
259
- def test_kaggle_api_live():
260
- print(f"\n=== TEST 5: Live Kaggle Dataset Query for '{DATASET_SLUG}' ===")
261
- status_passed = False
262
- files_passed = False
263
- remote_version = None
264
- remote_files = {}
265
- kaggle_bin = get_kaggle_cmd()
266
- print(f"Using kaggle binary: {kaggle_bin}")
267
-
268
- # 1. Check status
269
- try:
270
- cmd = [kaggle_bin, "datasets", "status", DATASET_SLUG, "--format", "json"]
271
- proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
272
- print(f"Ran command: {' '.join(cmd)}")
273
- print(f"Return code: {proc.returncode}")
274
- print(f"Stdout: {proc.stdout.strip()}")
275
- print(f"Stderr: {proc.stderr.strip()}")
276
-
277
- if proc.returncode == 0:
278
- status_data = json.loads(proc.stdout)
279
- status = status_data.get("status")
280
- remote_version = status_data.get("current_version_number")
281
- print(f"Kaggle API reported status: {status}, current_version_number: {remote_version}")
282
- if status == "ready" and remote_version == 2:
283
- print("PASS: Remote dataset status is 'ready' and version is 2")
284
- status_passed = True
285
- else:
286
- print(f"FAIL: Expected status 'ready' and version 2, got status={status}, version={remote_version}")
287
- else:
288
- print(f"Kaggle CLI returned error: {proc.stderr}")
289
- except Exception as e:
290
- print(f"Exception invoking kaggle CLI status: {e}")
291
-
292
- # 2. Check files
293
- try:
294
- cmd = [kaggle_bin, "datasets", "files", DATASET_SLUG, "--csv"]
295
- proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
296
- print(f"\nRan command: {' '.join(cmd)}")
297
- print(f"Return code: {proc.returncode}")
298
- print(f"Stdout:\n{proc.stdout.strip()}")
299
- print(f"Stderr: {proc.stderr.strip()}")
300
-
301
- if proc.returncode == 0:
302
- lines = proc.stdout.strip().splitlines()
303
- import csv
304
- reader = csv.DictReader(lines)
305
- for row in reader:
306
- remote_files[row["name"]] = int(row["size"])
307
-
308
- print(f"Parsed remote files ({len(remote_files)}): {remote_files}")
309
-
310
- missing = set(EXPECTED_FILES.keys()) - set(remote_files.keys())
311
- unexpected = set(remote_files.keys()) - set(EXPECTED_FILES.keys())
312
-
313
- if missing:
314
- print(f"FAIL: Missing files on Kaggle: {missing}")
315
- elif unexpected:
316
- print(f"WARNING: Unexpected files on Kaggle: {unexpected}")
317
- else:
318
- print("PASS: Exactly the 3 expected files exist on Kaggle")
319
-
320
- size_mismatch = False
321
- for fname, exp_meta in EXPECTED_FILES.items():
322
- if fname in remote_files:
323
- rem_size = remote_files[fname]
324
- exp_size = exp_meta["expected_size"]
325
- if rem_size == exp_size:
326
- print(f"PASS: {fname} remote size ({rem_size}) matches expected ({exp_size})")
327
- else:
328
- print(f"FAIL: {fname} remote size ({rem_size}) != expected ({exp_size})")
329
- size_mismatch = True
330
- if not missing and not size_mismatch:
331
- files_passed = True
332
- except Exception as e:
333
- print(f"Exception invoking kaggle CLI files: {e}")
334
-
335
- return (status_passed and files_passed), {"version": remote_version, "status_passed": status_passed, "files_passed": files_passed, "remote_files": remote_files}
336
-
337
- def test_kaggle_input_mounting_simulation():
338
- print("\n=== TEST 6: Kaggle /kaggle/input/ Mounting & Path Simulation ===")
339
- simulated_mount = "/kaggle/input/dias-baseline-models"
340
- mount_paths = {fname: f"{simulated_mount}/{fname}" for fname in EXPECTED_FILES}
341
- print(f"Simulated Kaggle mount paths:\n{json.dumps(mount_paths, indent=2)}")
342
-
343
- dataset_name = DATASET_SLUG.split("/")[1]
344
- print(f"Kaggle mount directory name: '{dataset_name}'")
345
- if dataset_name != "dias-baseline-models":
346
- print(f"FAIL: Unexpected mount dir name: {dataset_name}")
347
- return False
348
- print("PASS: Mount folder name matches standard Kaggle input directory convention")
349
- return True
350
-
351
- if __name__ == "__main__":
352
- t1_pass, local_hashes = test_local_parity_and_integrity()
353
- t2_pass = test_keras_archive_integrity()
354
- t3_pass = test_mapping_json_consistency(local_hashes)
355
- t4_pass = test_metadata_and_permissions()
356
- t5_pass, remote_info = test_kaggle_api_live()
357
- t6_pass = test_kaggle_input_mounting_simulation()
358
-
359
- print("\n==================================================")
360
- print(" SUMMARY VERDICT ")
361
- print("==================================================")
362
- print(f"Test 1 (Local Parity & SHA-256): {'PASS' if t1_pass else 'FAIL'}")
363
- print(f"Test 2 (Keras ZIP Integrity): {'PASS' if t2_pass else 'FAIL'}")
364
- print(f"Test 3 (Mapping JSON Consistency): {'PASS' if t3_pass else 'FAIL'}")
365
- print(f"Test 4 (Metadata & Permissions): {'PASS' if t4_pass else 'FAIL'}")
366
- print(f"Test 5 (Kaggle API Live Query): {'PASS' if t5_pass else 'FAIL'}")
367
- print(f"Test 6 (Mounting Simulation): {'PASS' if t6_pass else 'FAIL'}")
368
-
369
- all_tests = [t1_pass, t2_pass, t3_pass, t4_pass, t5_pass, t6_pass]
370
- if all(all_tests):
371
- print("\nOVERALL VERDICT: APPROVE")
372
- sys.exit(0)
373
- else:
374
- print("\nOVERALL VERDICT: REQUEST_CHANGES")
375
- sys.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
credit/Model_training_credit.ipynb DELETED
@@ -1,2065 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "markdown",
5
- "id": "5302be31",
6
- "metadata": {
7
- "id": "5302be31"
8
- },
9
- "source": [
10
- "# 1.0 \u2014 Setup & Imports\n",
11
- "\n",
12
- "Mount Google Drive and import all required standard, scientific, and deep-learning libraries."
13
- ]
14
- },
15
- {
16
- "cell_type": "code",
17
- "execution_count": null,
18
- "id": "e55b14ba",
19
- "metadata": {
20
- "colab": {
21
- "base_uri": "https://localhost:8080/"
22
- },
23
- "id": "e55b14ba",
24
- "executionInfo": {
25
- "status": "ok",
26
- "timestamp": 1780323019276,
27
- "user_tz": -330,
28
- "elapsed": 32248,
29
- "user": {
30
- "displayName": "Randy Orton",
31
- "userId": "11725427482974841050"
32
- }
33
- },
34
- "outputId": "19572730-3129-4f86-b52c-c5723fcaec5b"
35
- },
36
- "outputs": [],
37
- "source": [
38
- "from google.colab import drive\n",
39
- "drive.mount('/content/drive')"
40
- ]
41
- },
42
- {
43
- "cell_type": "code",
44
- "execution_count": null,
45
- "id": "23495159",
46
- "metadata": {
47
- "colab": {
48
- "base_uri": "https://localhost:8080/"
49
- },
50
- "id": "23495159",
51
- "executionInfo": {
52
- "status": "ok",
53
- "timestamp": 1780323025101,
54
- "user_tz": -330,
55
- "elapsed": 5821,
56
- "user": {
57
- "displayName": "Randy Orton",
58
- "userId": "11725427482974841050"
59
- }
60
- },
61
- "outputId": "9aea70f9-9842-46db-ab9e-4fad29f942fe"
62
- },
63
- "outputs": [],
64
- "source": [
65
- "# \u2500\u2500 Standard library \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
66
- "import os\n",
67
- "import glob\n",
68
- "import json\n",
69
- "import time\n",
70
- "import random\n",
71
- "from pathlib import Path\n",
72
- "\n",
73
- "from scipy.io import loadmat\n",
74
- "\n",
75
- "# \u2500\u2500 Scientific / data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
76
- "import numpy as np\n",
77
- "import pandas as pd\n",
78
- "\n",
79
- "# \u2500\u2500 Visualisation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
80
- "import seaborn as sns\n",
81
- "import matplotlib.pyplot as plt\n",
82
- "from matplotlib.colors import ListedColormap\n",
83
- "from matplotlib.patches import Patch\n",
84
- "\n",
85
- "# \u2500\u2500 Deep learning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
86
- "import tensorflow as tf\n",
87
- "from tensorflow import keras\n",
88
- "from tensorflow.keras import layers\n",
89
- "\n",
90
- "# \u2500\u2500 ML utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
91
- "from sklearn.model_selection import train_test_split\n",
92
- "from sklearn.metrics import (\n",
93
- " accuracy_score, cohen_kappa_score, confusion_matrix,\n",
94
- " classification_report, f1_score, log_loss\n",
95
- ")\n",
96
- "\n",
97
- "# \u2500\u2500 Excel export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
98
- "from openpyxl import Workbook\n",
99
- "from openpyxl.styles import Font, PatternFill, Alignment, Border, Side\n",
100
- "from openpyxl.utils import get_column_letter\n",
101
- "from openpyxl.drawing.image import Image as XLImage\n",
102
- "\n",
103
- "sns.set_style('whitegrid')\n",
104
- "print('TensorFlow:', tf.__version__)"
105
- ]
106
- },
107
- {
108
- "cell_type": "markdown",
109
- "id": "90f75990",
110
- "metadata": {
111
- "id": "90f75990"
112
- },
113
- "source": [
114
- "# 2.0 \u2014 Configuration\n",
115
- "\n",
116
- "All paths, seeds, dataset geometry, training hyperparameters, and per-architecture configs in one place. **Edit only this section between experiments.**"
117
- ]
118
- },
119
- {
120
- "cell_type": "code",
121
- "execution_count": null,
122
- "id": "899f34ab",
123
- "metadata": {
124
- "colab": {
125
- "base_uri": "https://localhost:8080/"
126
- },
127
- "id": "899f34ab",
128
- "executionInfo": {
129
- "status": "ok",
130
- "timestamp": 1780323232678,
131
- "user_tz": -330,
132
- "elapsed": 8,
133
- "user": {
134
- "displayName": "Randy Orton",
135
- "userId": "11725427482974841050"
136
- }
137
- },
138
- "outputId": "8afd880f-b7c5-4c09-9afc-91fc3a190656"
139
- },
140
- "outputs": [],
141
- "source": [
142
- "# \u2500\u2500 Reproducibility \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
143
- "SEED = 42\n",
144
- "random.seed(SEED)\n",
145
- "np.random.seed(SEED)\n",
146
- "tf.random.set_seed(SEED)\n",
147
- "\n",
148
- "# \u2500\u2500 Directory layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
149
- "PROJECT_ROOT = Path('/content/drive/My Drive/Classification')\n",
150
- "DATA_DIR = PROJECT_ROOT / 'data'\n",
151
- "MODEL_DIR = PROJECT_ROOT / 'credit' / 'models'\n",
152
- "RESULTS_DIR = PROJECT_ROOT / 'credit' / 'results'\n",
153
- "ENSEMBLE_DIR = PROJECT_ROOT / 'ensemble' / 'models' / 'ensembles'\n",
154
- "\n",
155
- "MODEL_DIR.mkdir(parents=True, exist_ok=True)\n",
156
- "RESULTS_DIR.mkdir(parents=True, exist_ok=True)\n",
157
- "PLOT_DIR = RESULTS_DIR / 'training_plots'\n",
158
- "PLOT_DIR.mkdir(parents=True, exist_ok=True)\n",
159
- "\n",
160
- "DATA_FILE = DATA_DIR / 'DIAS.mat' # hyperspectral cube (H, W, Bands)\n",
161
- "LABEL_FILE = DATA_DIR / 'DIAS_ref.mat' # reference / ground-truth label map\n",
162
- "\n",
163
- "# \u2500\u2500 Dataset geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
164
- "# H, W, B are inferred automatically from the .mat cube shape once loaded in\n",
165
- "# Section 3.2, so this notebook works unchanged for any scene size / band count.\n",
166
- "H, W, B = None, None, None\n",
167
- "PERCENTILE_CLIP = (2.0, 98.0) # robust per-band stretch range (2nd-98th percentile)\n",
168
- "PATCH_SIZE = 9\n",
169
- "INNER_PATCH = 3 # tokenisation patch size for GFNet / ViT\n",
170
- "\n",
171
- "# \u2500\u2500 Split ratios \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
172
- "TRAIN_PERCENT = 0.75\n",
173
- "VAL_SPLIT_FROM_TRAIN = 0.20\n",
174
- "\n",
175
- "# \u2500\u2500 Training hyperparameters \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
176
- "BATCH_SIZE = 128\n",
177
- "EPOCHS = 100\n",
178
- "LEARNING_RATE = 3e-4\n",
179
- "DROPOUT_RATE = 0.25\n",
180
- "\n",
181
- "TRAIN_CFG = {\n",
182
- " 'label_smoothing': 0.05,\n",
183
- " 'weight_decay': 1e-4,\n",
184
- " 'clipnorm': 1.0,\n",
185
- " 'cosine_alpha': 0.05,\n",
186
- "}\n",
187
- "\n",
188
- "# \u2500\u2500 Architecture configs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
189
- "ALEXNET_CFG = {\n",
190
- " 'conv_filters': [96, 256, 384, 384, 256],\n",
191
- " 'dense_units': [4096, 1024, 256, 32],\n",
192
- "}\n",
193
- "GFNET_CFG = {\n",
194
- " 'hidden_dim': 512,\n",
195
- " 'num_blocks': 5,\n",
196
- " 'mlp_ratio': 4,\n",
197
- "}\n",
198
- "VIT_CFG = {\n",
199
- " 'projection_dim': 256,\n",
200
- " 'num_heads': 4,\n",
201
- " 'transformer_layers': 12,\n",
202
- " 'mlp_multiplier': 2,\n",
203
- " 'head_units': [512, 256, 128, 64],\n",
204
- "}\n",
205
- "\n",
206
- "# \u2500\u2500 AlexNet legacy recipe (keeps uncertainty recovery reproducible) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
207
- "ALEXNET_LEGACY_SPLIT_SEED = 10\n",
208
- "ALEXNET_LEGACY_TRAIN_PERCENT = 0.75\n",
209
- "ALEXNET_LR_START = 0.01\n",
210
- "ALEXNET_LR_MAX = 0.02\n",
211
- "ALEXNET_LR_MIN = 0.005\n",
212
- "\n",
213
- "# \u2500\u2500 Sanity print \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
214
- "print('Data :', DATA_FILE)\n",
215
- "print('Labels :', LABEL_FILE)\n",
216
- "print('Models :', MODEL_DIR)\n",
217
- "print('Results:', RESULTS_DIR)"
218
- ]
219
- },
220
- {
221
- "cell_type": "markdown",
222
- "id": "5897552f",
223
- "metadata": {
224
- "id": "5897552f"
225
- },
226
- "source": [
227
- "# 3.0 \u2014 Data Loading & Patch Extraction\n",
228
- "\n",
229
- "Reads the hyperspectral/multispectral scene from .mat files, applies a robust per-band percentile-clip normalisation, extracts fixed-size spatial patches centred on every labelled pixel, and builds both the standard and AlexNet-legacy train/test splits."
230
- ]
231
- },
232
- {
233
- "cell_type": "markdown",
234
- "id": "033bf713",
235
- "metadata": {
236
- "id": "033bf713"
237
- },
238
- "source": [
239
- "## 3.1 \u2014 Helper Functions"
240
- ]
241
- },
242
- {
243
- "cell_type": "code",
244
- "execution_count": null,
245
- "id": "0662fe6e",
246
- "metadata": {
247
- "id": "0662fe6e",
248
- "executionInfo": {
249
- "status": "ok",
250
- "timestamp": 1780323233562,
251
- "user_tz": -330,
252
- "elapsed": 10,
253
- "user": {
254
- "displayName": "Randy Orton",
255
- "userId": "11725427482974841050"
256
- }
257
- }
258
- },
259
- "outputs": [],
260
- "source": [
261
- "def load_scene_from_mat(data_path, label_path, data_key=None, label_key=None):\n",
262
- " \"\"\"Load a hyperspectral/multispectral scene + reference labels from .mat files.\n",
263
- "\n",
264
- " Auto-detects the variable name inside each .mat file if not given (matches\n",
265
- " files such as DIAS.mat / DIAS_ref.mat storing the array under a single named\n",
266
- " variable). Each spectral band is robustly stretched to [0, 1] using a\n",
267
- " percentile clip (PERCENTILE_CLIP) rather than literal min/max \u2014 a handful of\n",
268
- " saturated/no-data pixels (e.g. sensor clipping at int8 range limits -128/127)\n",
269
- " would otherwise dominate the stretch.\n",
270
- " \"\"\"\n",
271
- " def _first_array(mat_dict, key):\n",
272
- " if key is not None:\n",
273
- " return mat_dict[key]\n",
274
- " candidates = [v for k, v in mat_dict.items() if not k.startswith('__')]\n",
275
- " if not candidates:\n",
276
- " raise ValueError(f'No data arrays found in {mat_dict.keys()}')\n",
277
- " return candidates[0]\n",
278
- "\n",
279
- " data_mat = loadmat(data_path)\n",
280
- " label_mat = loadmat(label_path)\n",
281
- "\n",
282
- " x = _first_array(data_mat, data_key).astype(np.float32)\n",
283
- " y = _first_array(label_mat, label_key).astype(np.int32)\n",
284
- "\n",
285
- " if x.ndim != 3:\n",
286
- " raise ValueError(f'Expected a (H, W, Bands) cube, got shape {x.shape}')\n",
287
- " if y.shape != x.shape[:2]:\n",
288
- " raise ValueError(f'Label shape {y.shape} does not match image shape {x.shape[:2]}')\n",
289
- "\n",
290
- " height, width, bands = x.shape\n",
291
- "\n",
292
- " x_norm = np.empty_like(x, dtype=np.float32)\n",
293
- " for b in range(bands):\n",
294
- " band = x[:, :, b]\n",
295
- " lo = np.percentile(band, PERCENTILE_CLIP[0])\n",
296
- " hi = np.percentile(band, PERCENTILE_CLIP[1])\n",
297
- " denom = max(hi - lo, 1e-8)\n",
298
- " x_norm[:, :, b] = np.clip((band - lo) / denom, 0.0, 1.0)\n",
299
- "\n",
300
- " return x_norm, y, height, width, bands\n",
301
- "\n",
302
- "\n",
303
- "def extract_labeled_patches(x, y, patch_size=9):\n",
304
- " \"\"\"Extract spatial patches centred on every labelled (y > 0) pixel.\"\"\"\n",
305
- " pad = patch_size // 2\n",
306
- " x_pad = np.pad(x, ((pad, pad), (pad, pad), (0, 0)), mode='edge')\n",
307
- "\n",
308
- " coords = np.argwhere(y > 0)\n",
309
- " patches = np.empty((len(coords), patch_size, patch_size, x.shape[-1]), dtype=np.float32)\n",
310
- " labels = np.empty((len(coords),), dtype=np.int32)\n",
311
- "\n",
312
- " for i, (r, c) in enumerate(coords):\n",
313
- " patches[i] = x_pad[r:r + patch_size, c:c + patch_size, :]\n",
314
- " labels[i] = int(y[r, c]) - 1 # 1-indexed \u2192 0-indexed\n",
315
- "\n",
316
- " return patches, labels, coords"
317
- ]
318
- },
319
- {
320
- "cell_type": "markdown",
321
- "id": "7ea13597",
322
- "metadata": {
323
- "id": "7ea13597"
324
- },
325
- "source": [
326
- "## 3.2 \u2014 Load Image & Extract Patches"
327
- ]
328
- },
329
- {
330
- "cell_type": "code",
331
- "execution_count": null,
332
- "id": "073305cd",
333
- "metadata": {
334
- "colab": {
335
- "base_uri": "https://localhost:8080/"
336
- },
337
- "id": "073305cd",
338
- "executionInfo": {
339
- "status": "ok",
340
- "timestamp": 1780323233752,
341
- "user_tz": -330,
342
- "elapsed": 178,
343
- "user": {
344
- "displayName": "Randy Orton",
345
- "userId": "11725427482974841050"
346
- }
347
- },
348
- "outputId": "e6e64f91-d536-4561-b6a6-2cb54056b034"
349
- },
350
- "outputs": [],
351
- "source": [
352
- "print('Loading multispectral image...')\n",
353
- "x_img, y_img, H, W, B = load_scene_from_mat(DATA_FILE, LABEL_FILE)\n",
354
- "\n",
355
- "print('Extracting patches...')\n",
356
- "X, y, coords = extract_labeled_patches(x_img, y_img, PATCH_SIZE)\n",
357
- "\n",
358
- "num_classes = int(np.unique(y).size)\n",
359
- "input_shape = (PATCH_SIZE, PATCH_SIZE, B)\n",
360
- "\n",
361
- "print(f'Scene geometry: H={H} W={W} Bands={B}')\n",
362
- "print(f'Image : {x_img.shape} | Labels : {y_img.shape}')\n",
363
- "print(f'Patches: {X.shape} | Classes: {num_classes}')"
364
- ]
365
- },
366
- {
367
- "cell_type": "markdown",
368
- "id": "df01ed90",
369
- "metadata": {
370
- "id": "df01ed90"
371
- },
372
- "source": [
373
- "## 3.3 \u2014 Train / Validation / Test Split\n",
374
- "\n",
375
- "Two independent splits are created: a **standard split** (shared by GFNet and ViT) and an **AlexNet legacy split** (separate seed to preserve reproducibility of uncertainty recovery)."
376
- ]
377
- },
378
- {
379
- "cell_type": "code",
380
- "execution_count": null,
381
- "id": "fe1596a8",
382
- "metadata": {
383
- "colab": {
384
- "base_uri": "https://localhost:8080/"
385
- },
386
- "id": "fe1596a8",
387
- "executionInfo": {
388
- "status": "ok",
389
- "timestamp": 1780323233852,
390
- "user_tz": -330,
391
- "elapsed": 97,
392
- "user": {
393
- "displayName": "Randy Orton",
394
- "userId": "11725427482974841050"
395
- }
396
- },
397
- "outputId": "0091a33f-b2e7-41bc-c442-930ccb45c2f1"
398
- },
399
- "outputs": [],
400
- "source": [
401
- "# \u2500\u2500 Standard split (GFNet & ViT) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
402
- "x_train_full, x_test, y_train_full, y_test = train_test_split(\n",
403
- " X, y, train_size=TRAIN_PERCENT, random_state=SEED, stratify=y\n",
404
- ")\n",
405
- "x_train, x_val, y_train, y_val = train_test_split(\n",
406
- " x_train_full, y_train_full,\n",
407
- " test_size=VAL_SPLIT_FROM_TRAIN, random_state=SEED, stratify=y_train_full\n",
408
- ")\n",
409
- "\n",
410
- "y_train_cat = keras.utils.to_categorical(y_train, num_classes)\n",
411
- "y_val_cat = keras.utils.to_categorical(y_val, num_classes)\n",
412
- "y_test_cat = keras.utils.to_categorical(y_test, num_classes)\n",
413
- "\n",
414
- "print(f'Train : {x_train.shape} Val : {x_val.shape} Test : {x_test.shape}')\n",
415
- "\n",
416
- "# \u2500\u2500 AlexNet legacy split (separate seed for uncertainty recovery) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
417
- "x_train_alex, x_test_alex, y_train_alex, y_test_alex = train_test_split(\n",
418
- " X, y,\n",
419
- " train_size=ALEXNET_LEGACY_TRAIN_PERCENT,\n",
420
- " random_state=ALEXNET_LEGACY_SPLIT_SEED,\n",
421
- " stratify=y\n",
422
- ")\n",
423
- "y_train_alex_cat = keras.utils.to_categorical(y_train_alex, num_classes)\n",
424
- "y_test_alex_cat = keras.utils.to_categorical(y_test_alex, num_classes)\n",
425
- "\n",
426
- "print(f'AlexNet Train : {x_train_alex.shape} Test : {x_test_alex.shape}')"
427
- ]
428
- },
429
- {
430
- "cell_type": "markdown",
431
- "id": "7d481920",
432
- "metadata": {
433
- "id": "7d481920"
434
- },
435
- "source": [
436
- "# 4.0 \u2014 Model Definitions\n",
437
- "\n",
438
- "Defines all three single-head base architectures (AlexNet CNN, GFNet, ViT with U-Net skip connections) plus the shared custom Keras layers used by GFNet and ViT."
439
- ]
440
- },
441
- {
442
- "cell_type": "markdown",
443
- "id": "2347fb89",
444
- "metadata": {
445
- "id": "2347fb89"
446
- },
447
- "source": [
448
- "## 4.1 \u2014 Shared Custom Keras Layers\n",
449
- "\n",
450
- "Four serialisable custom layers: patch extraction, positional encoding (with and without CLS token), and the frequency-domain Global Filter layer used in GFNet."
451
- ]
452
- },
453
- {
454
- "cell_type": "code",
455
- "execution_count": null,
456
- "id": "523b622b",
457
- "metadata": {
458
- "id": "523b622b",
459
- "executionInfo": {
460
- "status": "ok",
461
- "timestamp": 1780323236816,
462
- "user_tz": -330,
463
- "elapsed": 55,
464
- "user": {
465
- "displayName": "Randy Orton",
466
- "userId": "11725427482974841050"
467
- }
468
- }
469
- },
470
- "outputs": [],
471
- "source": [
472
- "@tf.keras.utils.register_keras_serializable()\n",
473
- "class PatchExtractor(layers.Layer):\n",
474
- " \"\"\"Extract non-overlapping patches from a spatial image tensor.\"\"\"\n",
475
- "\n",
476
- " def __init__(self, patch_size=3, **kwargs):\n",
477
- " super().__init__(**kwargs)\n",
478
- " self.patch_size = patch_size\n",
479
- "\n",
480
- " def call(self, images):\n",
481
- " patches = tf.image.extract_patches(\n",
482
- " images=images,\n",
483
- " sizes=[1, self.patch_size, self.patch_size, 1],\n",
484
- " strides=[1, self.patch_size, self.patch_size, 1],\n",
485
- " rates=[1, 1, 1, 1],\n",
486
- " padding='VALID',\n",
487
- " )\n",
488
- " batch = tf.shape(images)[0]\n",
489
- " num_patches = tf.shape(patches)[1] * tf.shape(patches)[2]\n",
490
- " patch_dim = tf.shape(patches)[-1]\n",
491
- " return tf.reshape(patches, [batch, num_patches, patch_dim])\n",
492
- "\n",
493
- " def get_config(self):\n",
494
- " cfg = super().get_config()\n",
495
- " cfg.update({'patch_size': self.patch_size})\n",
496
- " return cfg\n",
497
- "\n",
498
- "\n",
499
- "@tf.keras.utils.register_keras_serializable()\n",
500
- "class PatchPositionEncoder(layers.Layer):\n",
501
- " \"\"\"Linear projection + learnable position embedding (no CLS token).\"\"\"\n",
502
- "\n",
503
- " def __init__(self, num_patches, projection_dim, **kwargs):\n",
504
- " super().__init__(**kwargs)\n",
505
- " self.num_patches = num_patches\n",
506
- " self.projection_dim = projection_dim\n",
507
- " self.projection = layers.Dense(projection_dim)\n",
508
- " self.position_embedding = layers.Embedding(\n",
509
- " input_dim=num_patches, output_dim=projection_dim\n",
510
- " )\n",
511
- "\n",
512
- " def call(self, patches):\n",
513
- " positions = tf.range(start=0, limit=self.num_patches, delta=1)\n",
514
- " return self.projection(patches) + self.position_embedding(positions)\n",
515
- "\n",
516
- " def get_config(self):\n",
517
- " cfg = super().get_config()\n",
518
- " cfg.update({'num_patches': self.num_patches, 'projection_dim': self.projection_dim})\n",
519
- " return cfg\n",
520
- "\n",
521
- "\n",
522
- "@tf.keras.utils.register_keras_serializable()\n",
523
- "class PatchEncoderWithCLS(layers.Layer):\n",
524
- " \"\"\"Linear projection + learnable position embedding WITH a prepended CLS token.\"\"\"\n",
525
- "\n",
526
- " def __init__(self, num_patches, projection_dim, **kwargs):\n",
527
- " super().__init__(**kwargs)\n",
528
- " self.num_patches = num_patches\n",
529
- " self.projection_dim = projection_dim\n",
530
- " self.projection = layers.Dense(projection_dim)\n",
531
- " self.position_embedding = layers.Embedding(\n",
532
- " input_dim=num_patches + 1, output_dim=projection_dim\n",
533
- " )\n",
534
- "\n",
535
- " def build(self, input_shape):\n",
536
- " self.cls_token = self.add_weight(\n",
537
- " name='cls_token', shape=(1, 1, self.projection_dim),\n",
538
- " initializer='zeros', trainable=True\n",
539
- " )\n",
540
- " super().build(input_shape)\n",
541
- "\n",
542
- " def call(self, patches):\n",
543
- " batch = tf.shape(patches)[0]\n",
544
- " patch_proj = self.projection(patches)\n",
545
- " cls_tokens = tf.repeat(self.cls_token, repeats=batch, axis=0)\n",
546
- " x = tf.concat([cls_tokens, patch_proj], axis=1)\n",
547
- " positions = tf.range(start=0, limit=self.num_patches + 1, delta=1)\n",
548
- " return x + self.position_embedding(positions)\n",
549
- "\n",
550
- " def get_config(self):\n",
551
- " cfg = super().get_config()\n",
552
- " cfg.update({'num_patches': self.num_patches, 'projection_dim': self.projection_dim})\n",
553
- " return cfg\n",
554
- "\n",
555
- "\n",
556
- "@tf.keras.utils.register_keras_serializable()\n",
557
- "class GlobalFilterLayer(layers.Layer):\n",
558
- " \"\"\"Learnable complex-valued filter applied in the 2-D frequency domain.\"\"\"\n",
559
- "\n",
560
- " def __init__(self, token_side, **kwargs):\n",
561
- " super().__init__(**kwargs)\n",
562
- " self.token_side = token_side\n",
563
- "\n",
564
- " def build(self, input_shape):\n",
565
- " channels = int(input_shape[-1])\n",
566
- " self.w_real = self.add_weight(\n",
567
- " name='w_real', shape=(self.token_side, self.token_side, channels),\n",
568
- " initializer='glorot_uniform', trainable=True\n",
569
- " )\n",
570
- " self.w_imag = self.add_weight(\n",
571
- " name='w_imag', shape=(self.token_side, self.token_side, channels),\n",
572
- " initializer='zeros', trainable=True\n",
573
- " )\n",
574
- " super().build(input_shape)\n",
575
- "\n",
576
- " def call(self, x):\n",
577
- " batch = tf.shape(x)[0]\n",
578
- " channels = tf.shape(x)[-1]\n",
579
- " x_2d = tf.reshape(x, [batch, self.token_side, self.token_side, channels])\n",
580
- " x_fft = tf.signal.fft2d(tf.cast(x_2d, tf.complex64))\n",
581
- " w_complex = tf.complex(self.w_real, self.w_imag)\n",
582
- " x_filtered = x_fft * w_complex\n",
583
- " x_spatial = tf.math.real(tf.signal.ifft2d(x_filtered))\n",
584
- " return tf.reshape(x_spatial, [batch, self.token_side * self.token_side, channels])\n",
585
- "\n",
586
- " def get_config(self):\n",
587
- " cfg = super().get_config()\n",
588
- " cfg.update({'token_side': self.token_side})\n",
589
- " return cfg"
590
- ]
591
- },
592
- {
593
- "cell_type": "markdown",
594
- "id": "aec99a4d",
595
- "metadata": {
596
- "id": "aec99a4d"
597
- },
598
- "source": [
599
- "## 4.2 \u2014 AlexNet CNN"
600
- ]
601
- },
602
- {
603
- "cell_type": "code",
604
- "execution_count": null,
605
- "id": "bd04d5a2",
606
- "metadata": {
607
- "id": "bd04d5a2",
608
- "executionInfo": {
609
- "status": "ok",
610
- "timestamp": 1780323236835,
611
- "user_tz": -330,
612
- "elapsed": 16,
613
- "user": {
614
- "displayName": "Randy Orton",
615
- "userId": "11725427482974841050"
616
- }
617
- }
618
- },
619
- "outputs": [],
620
- "source": [
621
- "def build_alexnet(input_shape, num_classes, dropout_rate=0.25, cfg=None):\n",
622
- " \"\"\"Build a single-head AlexNet-style CNN with configurable conv and dense units.\"\"\"\n",
623
- " cfg = cfg or ALEXNET_CFG\n",
624
- "\n",
625
- " inputs = keras.Input(shape=input_shape)\n",
626
- " x = inputs\n",
627
- "\n",
628
- " for i, filters in enumerate(cfg['conv_filters'], start=1):\n",
629
- " x = layers.Conv2D(filters, (3, 3), activation='relu',\n",
630
- " padding='same', name=f'alex_conv_{i}')(x)\n",
631
- "\n",
632
- " x = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2),\n",
633
- " padding='same', name='alex_pool')(x)\n",
634
- " x = layers.Flatten(name='alex_flatten')(x)\n",
635
- "\n",
636
- " fc_names = ['alex_fc1', 'alex_fc2', 'alex_fc3', 'alex_fc4']\n",
637
- " drop_names = ['TRAIN_DROPOUT_1', 'TRAIN_DROPOUT_2', 'TRAIN_DROPOUT_3', None]\n",
638
- "\n",
639
- " for units, fc_name, drop_name in zip(cfg['dense_units'], fc_names, drop_names):\n",
640
- " x = layers.Dense(units, activation='relu', name=fc_name)(x)\n",
641
- " if drop_name:\n",
642
- " x = layers.Dropout(dropout_rate, name=drop_name)(x)\n",
643
- "\n",
644
- " outputs = layers.Dense(num_classes, activation='softmax', name='alex_logits')(x)\n",
645
- " return keras.Model(inputs, outputs, name='AlexNet_SingleHead')"
646
- ]
647
- },
648
- {
649
- "cell_type": "markdown",
650
- "id": "c2b2a28e",
651
- "metadata": {
652
- "id": "c2b2a28e"
653
- },
654
- "source": [
655
- "## 4.3 \u2014 Global Filter Network (GFNet)"
656
- ]
657
- },
658
- {
659
- "cell_type": "code",
660
- "execution_count": null,
661
- "id": "41818eea",
662
- "metadata": {
663
- "id": "41818eea",
664
- "executionInfo": {
665
- "status": "ok",
666
- "timestamp": 1780323236838,
667
- "user_tz": -330,
668
- "elapsed": 2,
669
- "user": {
670
- "displayName": "Randy Orton",
671
- "userId": "11725427482974841050"
672
- }
673
- }
674
- },
675
- "outputs": [],
676
- "source": [
677
- "def gf_block(x, token_side, dim, mlp_ratio=4, dropout_rate=0.25, name_prefix='gf'):\n",
678
- " \"\"\"One GFNet block: Global Filter \u2192 LayerNorm \u2192 MLP \u2192 residual add.\"\"\"\n",
679
- " y = layers.LayerNormalization(name=f'{name_prefix}_ln1')(x)\n",
680
- " y = GlobalFilterLayer(token_side, name=f'{name_prefix}_gfilter')(y)\n",
681
- " y = layers.LayerNormalization(name=f'{name_prefix}_ln2')(y)\n",
682
- " y = layers.Dense(dim * mlp_ratio, activation=tf.keras.activations.gelu,\n",
683
- " name=f'{name_prefix}_mlp1')(y)\n",
684
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop1')(y)\n",
685
- " y = layers.Dense(dim, activation=tf.keras.activations.gelu,\n",
686
- " name=f'{name_prefix}_mlp2')(y)\n",
687
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop2')(y)\n",
688
- " return layers.Add(name=f'{name_prefix}_add')([x, y])\n",
689
- "\n",
690
- "\n",
691
- "def build_gfnet(input_shape, num_classes, inner_patch=3,\n",
692
- " hidden_dim=512, num_blocks=5, mlp_ratio=4, dropout_rate=0.25):\n",
693
- " \"\"\"Build a single-head Global Filter Network over tokenised image patches.\"\"\"\n",
694
- " num_patches = (input_shape[0] // inner_patch) * (input_shape[1] // inner_patch)\n",
695
- " token_side = int(np.sqrt(num_patches))\n",
696
- "\n",
697
- " inputs = keras.Input(shape=input_shape)\n",
698
- " x = PatchExtractor(inner_patch, name='gf_patch_extractor')(inputs)\n",
699
- " x = PatchPositionEncoder(num_patches, hidden_dim, name='gf_patch_encoder')(x)\n",
700
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_1')(x)\n",
701
- "\n",
702
- " for i in range(num_blocks):\n",
703
- " x = gf_block(x, token_side, hidden_dim, mlp_ratio,\n",
704
- " dropout_rate, name_prefix=f'gf_block_{i+1}')\n",
705
- "\n",
706
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_2')(x)\n",
707
- " x = layers.LayerNormalization(name='gf_final_ln')(x)\n",
708
- " x = layers.GlobalAveragePooling1D(name='gf_gap')(x)\n",
709
- " x = layers.Flatten(name='gf_flatten')(x)\n",
710
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_3')(x)\n",
711
- " outputs = layers.Dense(num_classes, activation='softmax', name='gf_logits')(x)\n",
712
- "\n",
713
- " return keras.Model(inputs, outputs, name='GFNet_SingleHead')"
714
- ]
715
- },
716
- {
717
- "cell_type": "markdown",
718
- "id": "bdb53de0",
719
- "metadata": {
720
- "id": "bdb53de0"
721
- },
722
- "source": [
723
- "## 4.4 \u2014 Vision Transformer with U-Net Skip Connections"
724
- ]
725
- },
726
- {
727
- "cell_type": "code",
728
- "execution_count": null,
729
- "id": "9fe9b56b",
730
- "metadata": {
731
- "id": "9fe9b56b",
732
- "executionInfo": {
733
- "status": "ok",
734
- "timestamp": 1780323236843,
735
- "user_tz": -330,
736
- "elapsed": 3,
737
- "user": {
738
- "displayName": "Randy Orton",
739
- "userId": "11725427482974841050"
740
- }
741
- }
742
- },
743
- "outputs": [],
744
- "source": [
745
- "def transformer_block(x, num_heads, projection_dim, mlp_dim, dropout_rate, name_prefix):\n",
746
- " \"\"\"Standard pre-LN transformer block: Multi-Head Attention + MLP with residual adds.\"\"\"\n",
747
- " y = layers.LayerNormalization(epsilon=1e-6, name=f'{name_prefix}_ln1')(x)\n",
748
- " y = layers.MultiHeadAttention(\n",
749
- " num_heads=num_heads, key_dim=projection_dim,\n",
750
- " dropout=dropout_rate, name=f'{name_prefix}_mha'\n",
751
- " )(y, y)\n",
752
- " x = layers.Add(name=f'{name_prefix}_add1')([y, x])\n",
753
- "\n",
754
- " y = layers.LayerNormalization(epsilon=1e-6, name=f'{name_prefix}_ln2')(x)\n",
755
- " y = layers.Dense(mlp_dim, activation=tf.keras.activations.gelu,\n",
756
- " name=f'{name_prefix}_mlp1')(y)\n",
757
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop1')(y)\n",
758
- " y = layers.Dense(projection_dim, activation=tf.keras.activations.gelu,\n",
759
- " name=f'{name_prefix}_mlp2')(y)\n",
760
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop2')(y)\n",
761
- " return layers.Add(name=f'{name_prefix}_add2')([y, x])\n",
762
- "\n",
763
- "\n",
764
- "def build_vit_unet_singlehead(\n",
765
- " input_shape, num_classes, inner_patch=3,\n",
766
- " projection_dim=256, num_heads=4, transformer_layers=12,\n",
767
- " mlp_multiplier=2, dropout_rate=0.25, head_units=(512, 256, 128, 64)\n",
768
- "):\n",
769
- " \"\"\"Build a single-head ViT with encoder-decoder skip connections (U-Net style).\"\"\"\n",
770
- " num_patches = (input_shape[0] // inner_patch) * (input_shape[1] // inner_patch)\n",
771
- "\n",
772
- " inputs = keras.Input(shape=input_shape)\n",
773
- " x = PatchExtractor(inner_patch, name='vit_patch_extractor')(inputs)\n",
774
- " x = PatchEncoderWithCLS(num_patches, projection_dim, name='vit_patch_encoder')(x)\n",
775
- "\n",
776
- " block_list = []\n",
777
- " for i in range(transformer_layers):\n",
778
- " x = transformer_block(\n",
779
- " x, num_heads=num_heads, projection_dim=projection_dim,\n",
780
- " mlp_dim=projection_dim * mlp_multiplier,\n",
781
- " dropout_rate=0.1, name_prefix=f'vit_block_{i+1}'\n",
782
- " )\n",
783
- " if i <= transformer_layers // 2:\n",
784
- " block_list.append(x)\n",
785
- " else:\n",
786
- " x = layers.Add(name=f'vit_skip_add_{i+1}')(\n",
787
- " [x, block_list[transformer_layers - i - 1]]\n",
788
- " )\n",
789
- "\n",
790
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_1')(x)\n",
791
- " x = layers.LayerNormalization(epsilon=1e-6, name='vit_cls_norm')(x)\n",
792
- " cls_token = layers.Lambda(lambda t: t[:, 0, :], name='vit_cls_token')(x)\n",
793
- "\n",
794
- " drop_slots = ['TRAIN_DROPOUT_3', None, 'TRAIN_DROPOUT_5', 'TRAIN_DROPOUT_6']\n",
795
- " head_names = ['vit_head_1', 'vit_head_2', 'vit_head_3', 'vit_head_4']\n",
796
- " y = cls_token\n",
797
- " for units, hname, dname in zip(head_units, head_names, drop_slots):\n",
798
- " y = layers.Dense(units, activation=tf.keras.activations.gelu, name=hname)(y)\n",
799
- " if dname:\n",
800
- " y = layers.Dropout(dropout_rate, name=dname)(y)\n",
801
- "\n",
802
- " outputs = layers.Dense(num_classes, activation='softmax', name='vit_logits')(y)\n",
803
- " return keras.Model(inputs, outputs, name='ViT_UNet_SingleHead')"
804
- ]
805
- },
806
- {
807
- "cell_type": "markdown",
808
- "id": "1cdf93bb",
809
- "metadata": {
810
- "id": "1cdf93bb"
811
- },
812
- "source": [
813
- "# 5.0 \u2014 Training Helpers\n",
814
- "\n",
815
- "Utility functions for calibration metrics, optimiser construction, and the formatted Excel report writer."
816
- ]
817
- },
818
- {
819
- "cell_type": "markdown",
820
- "id": "fb0c5167",
821
- "metadata": {
822
- "id": "fb0c5167"
823
- },
824
- "source": [
825
- "## 5.1 \u2014 Calibration Metrics"
826
- ]
827
- },
828
- {
829
- "cell_type": "code",
830
- "execution_count": null,
831
- "id": "59b6cc9e",
832
- "metadata": {
833
- "id": "59b6cc9e",
834
- "executionInfo": {
835
- "status": "ok",
836
- "timestamp": 1780323238963,
837
- "user_tz": -330,
838
- "elapsed": 16,
839
- "user": {
840
- "displayName": "Randy Orton",
841
- "userId": "11725427482974841050"
842
- }
843
- }
844
- },
845
- "outputs": [],
846
- "source": [
847
- "def multiclass_brier_score(y_onehot, y_prob):\n",
848
- " \"\"\"Compute the multiclass Brier score (mean squared probability error).\"\"\"\n",
849
- " return float(np.mean(np.sum((y_prob - y_onehot) ** 2, axis=1)))\n",
850
- "\n",
851
- "\n",
852
- "def expected_calibration_error(y_true, y_prob, n_bins=15):\n",
853
- " \"\"\"Compute Expected Calibration Error (ECE) using equal-width confidence bins.\"\"\"\n",
854
- " confidences = np.max(y_prob, axis=1)\n",
855
- " predictions = np.argmax(y_prob, axis=1)\n",
856
- " correct = (predictions == y_true).astype(np.float32)\n",
857
- " bin_edges = np.linspace(0.0, 1.0, n_bins + 1)\n",
858
- " ece = 0.0\n",
859
- " for i in range(n_bins):\n",
860
- " lo, hi = bin_edges[i], bin_edges[i + 1]\n",
861
- " in_bin = (confidences >= lo) & (\n",
862
- " confidences <= hi if i == n_bins - 1 else confidences < hi\n",
863
- " )\n",
864
- " prop = np.mean(in_bin)\n",
865
- " if prop > 0:\n",
866
- " ece += np.abs(np.mean(correct[in_bin]) - np.mean(confidences[in_bin])) * prop\n",
867
- " return float(ece)"
868
- ]
869
- },
870
- {
871
- "cell_type": "markdown",
872
- "id": "cb7cf04e",
873
- "metadata": {
874
- "id": "cb7cf04e"
875
- },
876
- "source": [
877
- "## 5.2 \u2014 Optimiser & LR Schedule"
878
- ]
879
- },
880
- {
881
- "cell_type": "code",
882
- "execution_count": null,
883
- "id": "77799a41",
884
- "metadata": {
885
- "id": "77799a41",
886
- "executionInfo": {
887
- "status": "ok",
888
- "timestamp": 1780323239013,
889
- "user_tz": -330,
890
- "elapsed": 40,
891
- "user": {
892
- "displayName": "Randy Orton",
893
- "userId": "11725427482974841050"
894
- }
895
- }
896
- },
897
- "outputs": [],
898
- "source": [
899
- "def make_adamw_optimizer(num_train_samples):\n",
900
- " \"\"\"Build a cosine-decay AdamW optimiser (used for GFNet and ViT).\"\"\"\n",
901
- " steps_per_epoch = int(np.ceil(num_train_samples / BATCH_SIZE))\n",
902
- " decay_steps = max(1, steps_per_epoch * EPOCHS)\n",
903
- " lr_schedule = keras.optimizers.schedules.CosineDecay(\n",
904
- " initial_learning_rate=LEARNING_RATE,\n",
905
- " decay_steps=decay_steps,\n",
906
- " alpha=TRAIN_CFG['cosine_alpha'],\n",
907
- " )\n",
908
- " return keras.optimizers.AdamW(\n",
909
- " learning_rate=lr_schedule,\n",
910
- " weight_decay=TRAIN_CFG['weight_decay'],\n",
911
- " clipnorm=TRAIN_CFG['clipnorm'],\n",
912
- " )\n",
913
- "\n",
914
- "\n",
915
- "def _alexnet_legacy_lr(epoch):\n",
916
- " \"\"\"Cosine LR schedule oscillating between ALEXNET_LR_MIN and ALEXNET_LR_MAX.\"\"\"\n",
917
- " if EPOCHS <= 1:\n",
918
- " return ALEXNET_LR_START\n",
919
- " phase = np.pi * epoch / (EPOCHS - 1)\n",
920
- " cosine_decay = 0.5 * (1.0 + np.cos(phase))\n",
921
- " return float((ALEXNET_LR_MAX - ALEXNET_LR_MIN) * cosine_decay + ALEXNET_LR_MIN)"
922
- ]
923
- },
924
- {
925
- "cell_type": "markdown",
926
- "id": "fa69c58f",
927
- "metadata": {
928
- "id": "fa69c58f"
929
- },
930
- "source": [
931
- "## 5.3 \u2014 Excel Report Helpers\n",
932
- "\n",
933
- "Style constants and helper functions for writing DataFrames and plots into a formatted `.xlsx` workbook."
934
- ]
935
- },
936
- {
937
- "cell_type": "code",
938
- "execution_count": null,
939
- "id": "04c2c0ec",
940
- "metadata": {
941
- "id": "04c2c0ec",
942
- "executionInfo": {
943
- "status": "ok",
944
- "timestamp": 1780323239018,
945
- "user_tz": -330,
946
- "elapsed": 3,
947
- "user": {
948
- "displayName": "Randy Orton",
949
- "userId": "11725427482974841050"
950
- }
951
- }
952
- },
953
- "outputs": [],
954
- "source": [
955
- "# \u2500\u2500 Style constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
956
- "_HDR_FILL = PatternFill('solid', start_color='1F4E79')\n",
957
- "_ALT_FILL = PatternFill('solid', start_color='D6E4F0')\n",
958
- "_HDR_FONT = Font(name='Arial', bold=True, color='FFFFFF', size=11)\n",
959
- "_BODY_FONT = Font(name='Arial', size=10)\n",
960
- "_TITLE_FONT = Font(name='Arial', bold=True, size=13)\n",
961
- "_CENTER = Alignment(horizontal='center', vertical='center', wrap_text=True)\n",
962
- "_LEFT = Alignment(horizontal='left', vertical='center')\n",
963
- "_THIN_SIDE = Side(style='thin', color='AAAAAA')\n",
964
- "_THIN_BORDER = Border(\n",
965
- " left=_THIN_SIDE, right=_THIN_SIDE, top=_THIN_SIDE, bottom=_THIN_SIDE\n",
966
- ")\n",
967
- "\n",
968
- "\n",
969
- "def _style_header_row(ws, row, col_start, col_end):\n",
970
- " \"\"\"Apply dark-blue header styling to a worksheet row range.\"\"\"\n",
971
- " for c in range(col_start, col_end + 1):\n",
972
- " cell = ws.cell(row=row, column=c)\n",
973
- " cell.fill = _HDR_FILL\n",
974
- " cell.font = _HDR_FONT\n",
975
- " cell.alignment = _CENTER\n",
976
- " cell.border = _THIN_BORDER\n",
977
- "\n",
978
- "\n",
979
- "def _style_data_rows(ws, row_start, row_end, col_start, col_end):\n",
980
- " \"\"\"Apply alternating-row fill and border to a worksheet data range.\"\"\"\n",
981
- " for r in range(row_start, row_end + 1):\n",
982
- " fill = _ALT_FILL if r % 2 == 0 else PatternFill()\n",
983
- " for c in range(col_start, col_end + 1):\n",
984
- " cell = ws.cell(row=r, column=c)\n",
985
- " cell.font = _BODY_FONT\n",
986
- " cell.fill = fill\n",
987
- " cell.alignment = _LEFT\n",
988
- " cell.border = _THIN_BORDER\n",
989
- "\n",
990
- "\n",
991
- "def _write_df_to_sheet(ws, df, start_row=1, start_col=1, title=None):\n",
992
- " \"\"\"Write a DataFrame into a worksheet with headers, alternating rows, and auto-width columns.\"\"\"\n",
993
- " r = start_row\n",
994
- " if title:\n",
995
- " cell = ws.cell(row=r, column=start_col, value=title)\n",
996
- " cell.font = _TITLE_FONT\n",
997
- " cell.alignment = _LEFT\n",
998
- " ws.merge_cells(\n",
999
- " start_row=r, start_column=start_col,\n",
1000
- " end_row=r, end_column=start_col + len(df.columns) - 1\n",
1001
- " )\n",
1002
- " r += 1\n",
1003
- "\n",
1004
- " # Header row\n",
1005
- " for j, col_name in enumerate(df.columns, start=start_col):\n",
1006
- " ws.cell(row=r, column=j, value=col_name)\n",
1007
- " _style_header_row(ws, r, start_col, start_col + len(df.columns) - 1)\n",
1008
- " r += 1\n",
1009
- "\n",
1010
- " # Data rows\n",
1011
- " data_start = r\n",
1012
- " for _, row_data in df.iterrows():\n",
1013
- " for j, val in enumerate(row_data, start=start_col):\n",
1014
- " ws.cell(row=r, column=j,\n",
1015
- " value=round(float(val), 6)\n",
1016
- " if isinstance(val, (float, np.floating)) else val)\n",
1017
- " r += 1\n",
1018
- " _style_data_rows(ws, data_start, r - 1, start_col, start_col + len(df.columns) - 1)\n",
1019
- "\n",
1020
- " # Auto column width\n",
1021
- " for j, col_name in enumerate(df.columns, start=start_col):\n",
1022
- " max_len = max(\n",
1023
- " len(str(col_name)),\n",
1024
- " max((len(str(ws.cell(row=row, column=j).value or ''))\n",
1025
- " for row in range(data_start, r)), default=0)\n",
1026
- " )\n",
1027
- " ws.column_dimensions[get_column_letter(j)].width = min(max_len + 4, 40)\n",
1028
- "\n",
1029
- " return r # next free row"
1030
- ]
1031
- },
1032
- {
1033
- "cell_type": "markdown",
1034
- "id": "487d2188",
1035
- "metadata": {
1036
- "id": "487d2188"
1037
- },
1038
- "source": [
1039
- "## 5.4 \u2014 Excel Report Builder\n",
1040
- "\n",
1041
- "Assembles the full `CREDIT_Results.xlsx` workbook with four sheets: Training Summary, Evaluation Summary, Confusion Matrices, and Plots."
1042
- ]
1043
- },
1044
- {
1045
- "cell_type": "code",
1046
- "execution_count": null,
1047
- "id": "80e777c5",
1048
- "metadata": {
1049
- "id": "80e777c5",
1050
- "executionInfo": {
1051
- "status": "ok",
1052
- "timestamp": 1780323239031,
1053
- "user_tz": -330,
1054
- "elapsed": 3,
1055
- "user": {
1056
- "displayName": "Randy Orton",
1057
- "userId": "11725427482974841050"
1058
- }
1059
- }
1060
- },
1061
- "outputs": [],
1062
- "source": [
1063
- "def create_credit_excel_report(\n",
1064
- " results_dir,\n",
1065
- " train_summary_df,\n",
1066
- " eval_summary_df,\n",
1067
- " credit_artifacts_dict,\n",
1068
- " plot_paths # list of (label_str, image_path_str)\n",
1069
- "):\n",
1070
- " \"\"\"\n",
1071
- " Create CREDIT_Results.xlsx with four sheets:\n",
1072
- " Sheet 1 \u2014 Training Summary\n",
1073
- " Sheet 2 \u2014 Evaluation Summary\n",
1074
- " Sheet 3 \u2014 Confusion Matrices (per model, side by side)\n",
1075
- " Sheet 4 \u2014 Plots (one image per row)\n",
1076
- " \"\"\"\n",
1077
- " xlsx_path = Path(results_dir) / 'CREDIT_Results.xlsx'\n",
1078
- " wb = Workbook()\n",
1079
- "\n",
1080
- " # \u2500\u2500 Sheet 1: Training Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1081
- " ws1 = wb.active\n",
1082
- " ws1.title = 'Training Summary'\n",
1083
- " ws1.row_dimensions[1].height = 30\n",
1084
- " _write_df_to_sheet(ws1, train_summary_df,\n",
1085
- " start_row=1, title='CREDIT \u2014 Training Uncertainty Summary')\n",
1086
- "\n",
1087
- " # \u2500\u2500 Sheet 2: Evaluation Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1088
- " ws2 = wb.create_sheet('Evaluation Summary')\n",
1089
- " _write_df_to_sheet(ws2, eval_summary_df,\n",
1090
- " start_row=1, title='CREDIT \u2014 Full Evaluation Metrics')\n",
1091
- "\n",
1092
- " # \u2500\u2500 Sheet 3: Confusion Matrices \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1093
- " ws3 = wb.create_sheet('Confusion Matrices')\n",
1094
- " col_cursor = 1\n",
1095
- " for mname, art in credit_artifacts_dict.items():\n",
1096
- " cm = art['confusion_matrix']\n",
1097
- " rep = art['report']\n",
1098
- " n_cls = cm.shape[0]\n",
1099
- "\n",
1100
- " # Title\n",
1101
- " cell = ws3.cell(row=1, column=col_cursor,\n",
1102
- " value=f'{mname} \u2014 Confusion Matrix')\n",
1103
- " cell.font = _TITLE_FONT\n",
1104
- " ws3.merge_cells(start_row=1, start_column=col_cursor,\n",
1105
- " end_row=1, end_column=col_cursor + n_cls)\n",
1106
- "\n",
1107
- " # Column headers (Predicted)\n",
1108
- " ws3.cell(row=2, column=col_cursor,\n",
1109
- " value='True \\\\ Pred').font = Font(bold=True, name='Arial')\n",
1110
- " for j in range(n_cls):\n",
1111
- " c = ws3.cell(row=2, column=col_cursor + 1 + j, value=f'C{j+1}')\n",
1112
- " c.font = Font(bold=True, name='Arial', color='FFFFFF')\n",
1113
- " c.fill = _HDR_FILL\n",
1114
- " c.alignment = _CENTER\n",
1115
- "\n",
1116
- " # Data rows\n",
1117
- " for i in range(n_cls):\n",
1118
- " ws3.cell(row=3 + i, column=col_cursor,\n",
1119
- " value=f'C{i+1}').font = Font(bold=True, name='Arial')\n",
1120
- " for j in range(n_cls):\n",
1121
- " val = int(cm[i, j])\n",
1122
- " cell = ws3.cell(row=3 + i, column=col_cursor + 1 + j, value=val)\n",
1123
- " cell.alignment = _CENTER\n",
1124
- " cell.border = _THIN_BORDER\n",
1125
- " if i == j: # diagonal \u2192 green\n",
1126
- " cell.fill = PatternFill('solid', start_color='C6EFCE')\n",
1127
- " cell.font = Font(name='Arial', bold=True, color='276221')\n",
1128
- " else:\n",
1129
- " cell.font = _BODY_FONT\n",
1130
- "\n",
1131
- " # Per-class F1 from classification report\n",
1132
- " f1_row = 3 + n_cls + 1\n",
1133
- " ws3.cell(row=f1_row, column=col_cursor,\n",
1134
- " value='Per-Class F1').font = Font(bold=True, name='Arial')\n",
1135
- " for j in range(n_cls):\n",
1136
- " key = str(j)\n",
1137
- " f1 = rep.get(key, {}).get('f1-score', '')\n",
1138
- " ws3.cell(row=f1_row, column=col_cursor + 1 + j,\n",
1139
- " value=round(f1, 4) if f1 != '' else '').font = _BODY_FONT\n",
1140
- "\n",
1141
- " col_cursor += n_cls + 3 # gap between model tables\n",
1142
- "\n",
1143
- " # \u2500\u2500 Sheet 4: Plots \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1144
- " ws4 = wb.create_sheet('Plots')\n",
1145
- " title_cell = ws4.cell(row=1, column=1, value='CREDIT \u2014 All Output Plots')\n",
1146
- " title_cell.font = Font(name='Arial', bold=True, size=14)\n",
1147
- "\n",
1148
- " img_row = 3\n",
1149
- " for label, img_path in plot_paths:\n",
1150
- " if not Path(img_path).exists():\n",
1151
- " continue\n",
1152
- " ws4.cell(row=img_row, column=1,\n",
1153
- " value=label).font = Font(name='Arial', bold=True, size=11)\n",
1154
- " img_row += 1\n",
1155
- "\n",
1156
- " xl_img = XLImage(img_path)\n",
1157
- " orig_w, orig_h = xl_img.width, xl_img.height\n",
1158
- " target_w = 900\n",
1159
- " scale = target_w / orig_w if orig_w > 0 else 1\n",
1160
- " xl_img.width = int(orig_w * scale)\n",
1161
- " xl_img.height = int(orig_h * scale)\n",
1162
- "\n",
1163
- " cell_addr = f'A{img_row}'\n",
1164
- " ws4.add_image(xl_img, cell_addr)\n",
1165
- " # openpyxl row height is in points; 1pt \u2248 0.75px\n",
1166
- " ws4.row_dimensions[img_row].height = xl_img.height * 0.75\n",
1167
- " img_row += int(xl_img.height / 15) + 3\n",
1168
- "\n",
1169
- " wb.save(xlsx_path)\n",
1170
- " print(f'\\n\u2705 Excel report saved \u2192 {xlsx_path}')\n",
1171
- " return str(xlsx_path)"
1172
- ]
1173
- },
1174
- {
1175
- "cell_type": "markdown",
1176
- "id": "04e1cb62",
1177
- "metadata": {
1178
- "id": "04e1cb62"
1179
- },
1180
- "source": [
1181
- "# 6.0 \u2014 CREDIT Distillation\n",
1182
- "\n",
1183
- "Generates soft targets from the pre-trained ensemble teachers, builds a dual-head student for each architecture, and trains it via KL divergence (aleatoric head) + MSE (epistemic head)."
1184
- ]
1185
- },
1186
- {
1187
- "cell_type": "markdown",
1188
- "id": "13bc3c0f",
1189
- "metadata": {
1190
- "id": "13bc3c0f"
1191
- },
1192
- "source": [
1193
- "## 6.1 \u2014 Soft Target Generation\n",
1194
- "\n",
1195
- "Loads each ensemble teacher, runs inference over the training data, and derives CREDIT targets: `p_star` (normalised per-class minimum \u2014 aleatoric proxy) and `delta_p` (per-class prediction spread \u2014 epistemic proxy)."
1196
- ]
1197
- },
1198
- {
1199
- "cell_type": "code",
1200
- "execution_count": null,
1201
- "id": "5b2a9527",
1202
- "metadata": {
1203
- "id": "5b2a9527",
1204
- "executionInfo": {
1205
- "status": "ok",
1206
- "timestamp": 1780323240528,
1207
- "user_tz": -330,
1208
- "elapsed": 31,
1209
- "user": {
1210
- "displayName": "Randy Orton",
1211
- "userId": "11725427482974841050"
1212
- }
1213
- }
1214
- },
1215
- "outputs": [],
1216
- "source": [
1217
- "def get_ensemble_paths(model_name):\n",
1218
- " \"\"\"Locate ensemble teacher weights using three fallback search patterns.\"\"\"\n",
1219
- " # Primary: standardised location\n",
1220
- " pattern = str(ENSEMBLE_DIR / f'{model_name}_ens_*_final.keras')\n",
1221
- " paths = glob.glob(pattern)\n",
1222
- "\n",
1223
- " # Fallback 1: sub-folder style\n",
1224
- " if not paths:\n",
1225
- " pattern = str(\n",
1226
- " ENSEMBLE_DIR / f'ensembles_{model_name}' / f'{model_name}_ens_*_final.keras'\n",
1227
- " )\n",
1228
- " paths = glob.glob(pattern)\n",
1229
- "\n",
1230
- " # Fallback 2: old Credit/models location\n",
1231
- " if not paths:\n",
1232
- " pattern = str(MODEL_DIR / f'{model_name}_ens_*_final.keras')\n",
1233
- " paths = glob.glob(pattern)\n",
1234
- "\n",
1235
- " return sorted(paths)\n",
1236
- "\n",
1237
- "\n",
1238
- "def generate_credit_targets(ensemble_paths, x_data, batch_size=128):\n",
1239
- " \"\"\"\n",
1240
- " Run each teacher over x_data and derive CREDIT interval targets.\n",
1241
- "\n",
1242
- " Returns:\n",
1243
- " p_star_true : (N, C) normalised per-class minimum (aleatoric proxy)\n",
1244
- " delta_p_true : (N, C) per-class prediction spread (epistemic proxy)\n",
1245
- " \"\"\"\n",
1246
- " all_preds = []\n",
1247
- " for path in ensemble_paths:\n",
1248
- " print(f' Loading teacher: {path}')\n",
1249
- " model = tf.keras.models.load_model(path, compile=False, safe_mode=False)\n",
1250
- " all_preds.append(model.predict(x_data, batch_size=batch_size, verbose=1))\n",
1251
- " del model\n",
1252
- " tf.keras.backend.clear_session()\n",
1253
- "\n",
1254
- " stacked = tf.stack(all_preds, axis=0) # (M, N, C)\n",
1255
- " p_min = tf.reduce_min(stacked, axis=0) # (N, C)\n",
1256
- " p_max = tf.reduce_max(stacked, axis=0) # (N, C)\n",
1257
- "\n",
1258
- " delta_p_true = p_max - p_min\n",
1259
- " p_star_true = p_min / (tf.reduce_sum(p_min, axis=-1, keepdims=True) + 1e-12)\n",
1260
- " return p_star_true, delta_p_true"
1261
- ]
1262
- },
1263
- {
1264
- "cell_type": "markdown",
1265
- "id": "4cfa106a",
1266
- "metadata": {
1267
- "id": "4cfa106a"
1268
- },
1269
- "source": [
1270
- "## 6.2 \u2014 CREDIT Student (Dual-Head)\n",
1271
- "\n",
1272
- "Wraps any single-head base architecture with two output heads: a softmax head for the aleatoric belief (`p_star`) and a sigmoid head for the epistemic spread (`delta_p`)."
1273
- ]
1274
- },
1275
- {
1276
- "cell_type": "code",
1277
- "execution_count": null,
1278
- "id": "0cd5e8ff",
1279
- "metadata": {
1280
- "id": "0cd5e8ff",
1281
- "executionInfo": {
1282
- "status": "ok",
1283
- "timestamp": 1780323240533,
1284
- "user_tz": -330,
1285
- "elapsed": 2,
1286
- "user": {
1287
- "displayName": "Randy Orton",
1288
- "userId": "11725427482974841050"
1289
- }
1290
- }
1291
- },
1292
- "outputs": [],
1293
- "source": [
1294
- "def build_credit_student(base_builder_func, num_classes):\n",
1295
- " \"\"\"\n",
1296
- " Attach CREDIT dual-output heads to any single-head base architecture.\n",
1297
- "\n",
1298
- " Args:\n",
1299
- " base_builder_func: Zero-argument callable that returns a Keras model.\n",
1300
- " num_classes: Number of target classes.\n",
1301
- "\n",
1302
- " Returns:\n",
1303
- " A Keras model with outputs [p_star (softmax), delta_p (sigmoid)].\n",
1304
- " \"\"\"\n",
1305
- " base_model = base_builder_func()\n",
1306
- " features = base_model.layers[-2].output # penultimate feature tensor\n",
1307
- "\n",
1308
- " p_star = layers.Dense(num_classes, activation='softmax', name='p_star' )(features)\n",
1309
- " delta_p = layers.Dense(num_classes, activation='sigmoid', name='delta_p')(features)\n",
1310
- "\n",
1311
- " return tf.keras.Model(\n",
1312
- " inputs=base_model.input,\n",
1313
- " outputs=[p_star, delta_p],\n",
1314
- " name='CREDIT_Student'\n",
1315
- " )"
1316
- ]
1317
- },
1318
- {
1319
- "cell_type": "markdown",
1320
- "id": "d86f2e38",
1321
- "metadata": {
1322
- "id": "d86f2e38"
1323
- },
1324
- "source": [
1325
- "## 6.3 \u2014 Model Builder Registry\n",
1326
- "\n",
1327
- "Centralised registry mapping model names to zero-argument builder lambdas."
1328
- ]
1329
- },
1330
- {
1331
- "cell_type": "code",
1332
- "execution_count": null,
1333
- "id": "f40ae339",
1334
- "metadata": {
1335
- "id": "f40ae339",
1336
- "executionInfo": {
1337
- "status": "ok",
1338
- "timestamp": 1780323240537,
1339
- "user_tz": -330,
1340
- "elapsed": 2,
1341
- "user": {
1342
- "displayName": "Randy Orton",
1343
- "userId": "11725427482974841050"
1344
- }
1345
- }
1346
- },
1347
- "outputs": [],
1348
- "source": [
1349
- "model_builders = {\n",
1350
- " 'AlexNet_CNN': lambda: build_alexnet(\n",
1351
- " input_shape, num_classes,\n",
1352
- " dropout_rate=DROPOUT_RATE, cfg=ALEXNET_CFG\n",
1353
- " ),\n",
1354
- " 'GFNet': lambda: build_gfnet(\n",
1355
- " input_shape, num_classes,\n",
1356
- " inner_patch=INNER_PATCH,\n",
1357
- " hidden_dim=GFNET_CFG['hidden_dim'],\n",
1358
- " num_blocks=GFNET_CFG['num_blocks'],\n",
1359
- " mlp_ratio=GFNET_CFG['mlp_ratio'],\n",
1360
- " dropout_rate=DROPOUT_RATE\n",
1361
- " ),\n",
1362
- " 'ViT_UNet': lambda: build_vit_unet_singlehead(\n",
1363
- " input_shape, num_classes,\n",
1364
- " inner_patch=INNER_PATCH,\n",
1365
- " projection_dim=VIT_CFG['projection_dim'],\n",
1366
- " num_heads=VIT_CFG['num_heads'],\n",
1367
- " transformer_layers=VIT_CFG['transformer_layers'],\n",
1368
- " mlp_multiplier=VIT_CFG['mlp_multiplier'],\n",
1369
- " dropout_rate=DROPOUT_RATE,\n",
1370
- " head_units=tuple(VIT_CFG['head_units'])\n",
1371
- " ),\n",
1372
- "}"
1373
- ]
1374
- },
1375
- {
1376
- "cell_type": "markdown",
1377
- "id": "9f24ca6b",
1378
- "metadata": {
1379
- "id": "9f24ca6b"
1380
- },
1381
- "source": [
1382
- "## 6.4 \u2014 Training Loop\n",
1383
- "\n",
1384
- "For each architecture: locate ensemble teachers \u2192 generate soft targets \u2192 build `tf.data` pipelines \u2192 build and compile the CREDIT student \u2192 train with `ModelCheckpoint` \u2192 compute a quick post-training uncertainty summary."
1385
- ]
1386
- },
1387
- {
1388
- "cell_type": "code",
1389
- "execution_count": null,
1390
- "id": "8b145879",
1391
- "metadata": {
1392
- "colab": {
1393
- "base_uri": "https://localhost:8080/"
1394
- },
1395
- "id": "8b145879",
1396
- "executionInfo": {
1397
- "status": "ok",
1398
- "timestamp": 1780326555495,
1399
- "user_tz": -330,
1400
- "elapsed": 3314957,
1401
- "user": {
1402
- "displayName": "Randy Orton",
1403
- "userId": "11725427482974841050"
1404
- }
1405
- },
1406
- "outputId": "5153aa53-dd92-4dbd-80fa-8ab153d3b51a"
1407
- },
1408
- "outputs": [],
1409
- "source": [
1410
- "train_results = []\n",
1411
- "\n",
1412
- "for model_name, builder in model_builders.items():\n",
1413
- " print(f\"\\n{'='*25} CREDIT Distillation: {model_name} {'='*25}\")\n",
1414
- "\n",
1415
- " # \u2500\u2500 1. Locate ensemble teachers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1416
- " ensemble_paths = get_ensemble_paths(model_name)\n",
1417
- " if len(ensemble_paths) == 0:\n",
1418
- " print(' No ensemble models found \u2014 skipping.')\n",
1419
- " continue\n",
1420
- " if len(ensemble_paths) < 5:\n",
1421
- " print(f' Warning: only {len(ensemble_paths)} ensemble models found (expected 5).')\n",
1422
- "\n",
1423
- " # \u2500\u2500 2. Select correct data split \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1424
- " x_tr, x_te = (\n",
1425
- " (x_train_alex, x_test_alex) if model_name == 'AlexNet_CNN'\n",
1426
- " else (x_train, x_test)\n",
1427
- " )\n",
1428
- "\n",
1429
- " # \u2500\u2500 3. Generate soft targets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1430
- " print(' Generating training targets...')\n",
1431
- " p_star_tr, delta_p_tr = generate_credit_targets(ensemble_paths, x_tr)\n",
1432
- "\n",
1433
- " print(' Generating test targets...')\n",
1434
- " p_star_te, delta_p_te = generate_credit_targets(ensemble_paths, x_te)\n",
1435
- "\n",
1436
- " # \u2500\u2500 4. Build tf.data pipelines \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1437
- " train_ds = (\n",
1438
- " tf.data.Dataset\n",
1439
- " .from_tensor_slices((x_tr, (p_star_tr, delta_p_tr)))\n",
1440
- " .shuffle(1024).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n",
1441
- " )\n",
1442
- " test_ds = (\n",
1443
- " tf.data.Dataset\n",
1444
- " .from_tensor_slices((x_te, (p_star_te, delta_p_te)))\n",
1445
- " .batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n",
1446
- " )\n",
1447
- "\n",
1448
- " # \u2500\u2500 5. Build & compile student \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1449
- " tf.keras.backend.clear_session()\n",
1450
- " student = build_credit_student(builder, num_classes)\n",
1451
- "\n",
1452
- " optimizer = (\n",
1453
- " keras.optimizers.Adagrad(learning_rate=ALEXNET_LR_START)\n",
1454
- " if model_name == 'AlexNet_CNN'\n",
1455
- " else keras.optimizers.Adam(learning_rate=LEARNING_RATE)\n",
1456
- " )\n",
1457
- "\n",
1458
- " student.compile(\n",
1459
- " optimizer=optimizer,\n",
1460
- " loss={\n",
1461
- " 'p_star': tf.keras.losses.KLDivergence(),\n",
1462
- " 'delta_p': tf.keras.losses.MeanSquaredError(),\n",
1463
- " },\n",
1464
- " loss_weights={'p_star': 1.0, 'delta_p': 0.5}, # lambda = 0.5\n",
1465
- " )\n",
1466
- "\n",
1467
- " # \u2500\u2500 6. Train \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1468
- " best_path = MODEL_DIR / f'{model_name}_CREDIT_best.keras'\n",
1469
- " callbacks = [\n",
1470
- " keras.callbacks.ModelCheckpoint(\n",
1471
- " filepath=str(best_path), monitor='val_loss',\n",
1472
- " mode='min', save_best_only=True, verbose=1\n",
1473
- " )\n",
1474
- " ]\n",
1475
- "\n",
1476
- " t0 = time.perf_counter()\n",
1477
- " student.fit(\n",
1478
- " train_ds, validation_data=test_ds,\n",
1479
- " epochs=EPOCHS, callbacks=callbacks, verbose=1\n",
1480
- " )\n",
1481
- " train_time = time.perf_counter() - t0\n",
1482
- "\n",
1483
- " # \u2500\u2500 7. Post-training uncertainty summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1484
- " student.load_weights(best_path)\n",
1485
- " p_star_pred, delta_p_pred = student.predict(x_te, batch_size=BATCH_SIZE)\n",
1486
- "\n",
1487
- " au = -np.sum(p_star_pred * np.log(p_star_pred + 1e-12), axis=-1)\n",
1488
- " eu = np.mean(delta_p_pred, axis=-1)\n",
1489
- " tu = au + eu\n",
1490
- "\n",
1491
- " train_results.append({\n",
1492
- " 'Model': model_name,\n",
1493
- " 'Mean_AU': np.mean(au),\n",
1494
- " 'Mean_EU': np.mean(eu),\n",
1495
- " 'Mean_TU': np.mean(tu),\n",
1496
- " 'Train_Time_sec': train_time,\n",
1497
- " })\n",
1498
- "\n",
1499
- "# \u2500\u2500 Save training uncertainty summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1500
- "summary_df = pd.DataFrame(train_results)\n",
1501
- "summary_path = RESULTS_DIR / 'credit_train_uncertainty_summary.csv'\n",
1502
- "summary_df.to_csv(summary_path, index=False)\n",
1503
- "print(f'\\nSaved training summary \u2192 {summary_path}')\n",
1504
- "print(summary_df)\n",
1505
- "\n",
1506
- "# Stash for the final Excel report\n",
1507
- "_credit_train_summary_df = summary_df.copy()"
1508
- ]
1509
- },
1510
- {
1511
- "cell_type": "markdown",
1512
- "id": "8cf0a904",
1513
- "metadata": {
1514
- "id": "8cf0a904"
1515
- },
1516
- "source": [
1517
- "# 7.0 \u2014 Evaluation\n",
1518
- "\n",
1519
- "Loads the best saved CREDIT student weights for each architecture and computes a full suite of classification and calibration metrics \u2014 no re-training."
1520
- ]
1521
- },
1522
- {
1523
- "cell_type": "markdown",
1524
- "id": "70b726a0",
1525
- "metadata": {
1526
- "id": "70b726a0"
1527
- },
1528
- "source": [
1529
- "## 7.1 \u2014 Load, Predict & Compute Metrics"
1530
- ]
1531
- },
1532
- {
1533
- "cell_type": "code",
1534
- "execution_count": null,
1535
- "id": "82f55461",
1536
- "metadata": {
1537
- "colab": {
1538
- "base_uri": "https://localhost:8080/"
1539
- },
1540
- "id": "82f55461",
1541
- "executionInfo": {
1542
- "status": "ok",
1543
- "timestamp": 1780326576716,
1544
- "user_tz": -330,
1545
- "elapsed": 21210,
1546
- "user": {
1547
- "displayName": "Randy Orton",
1548
- "userId": "11725427482974841050"
1549
- }
1550
- },
1551
- "outputId": "f025f072-7db8-4da9-ee44-951a1bd1f685"
1552
- },
1553
- "outputs": [],
1554
- "source": [
1555
- "eval_results = []\n",
1556
- "credit_artifacts = {}\n",
1557
- "\n",
1558
- "for model_name, builder in model_builders.items():\n",
1559
- " best_path = MODEL_DIR / f'{model_name}_CREDIT_best.keras'\n",
1560
- "\n",
1561
- " if not best_path.exists():\n",
1562
- " print(f' [{model_name}] No saved weights at {best_path} \u2014 skipping.')\n",
1563
- " continue\n",
1564
- "\n",
1565
- " print(f\"\\n{'='*25} Evaluating: {model_name} {'='*25}\")\n",
1566
- " tf.keras.backend.clear_session()\n",
1567
- "\n",
1568
- " student = build_credit_student(builder, num_classes)\n",
1569
- " student.load_weights(best_path)\n",
1570
- "\n",
1571
- " # Select the correct test split for this architecture\n",
1572
- " if model_name == 'AlexNet_CNN':\n",
1573
- " x_te, y_te_true, y_te_cat = x_test_alex, y_test_alex, y_test_alex_cat\n",
1574
- " else:\n",
1575
- " x_te, y_te_true, y_te_cat = x_test, y_test, y_test_cat\n",
1576
- "\n",
1577
- " p_star_pred, delta_p_pred = student.predict(x_te, batch_size=BATCH_SIZE)\n",
1578
- "\n",
1579
- " au = -np.sum(p_star_pred * np.log(p_star_pred + 1e-12), axis=-1)\n",
1580
- " eu = np.mean(delta_p_pred, axis=-1)\n",
1581
- " tu = au + eu\n",
1582
- "\n",
1583
- " y_pred = np.argmax(p_star_pred, axis=-1)\n",
1584
- "\n",
1585
- " credit_artifacts[model_name] = {\n",
1586
- " 'confusion_matrix': confusion_matrix(y_te_true, y_pred),\n",
1587
- " 'report': classification_report(\n",
1588
- " y_te_true, y_pred, output_dict=True, zero_division=0\n",
1589
- " ),\n",
1590
- " }\n",
1591
- "\n",
1592
- " eval_results.append({\n",
1593
- " 'Model': model_name,\n",
1594
- " 'Test_Accuracy': float(accuracy_score(y_te_true, y_pred)),\n",
1595
- " 'Macro_F1': float(f1_score(y_te_true, y_pred, average='macro')),\n",
1596
- " 'Cohen_Kappa': float(cohen_kappa_score(y_te_true, y_pred)),\n",
1597
- " 'Test_NLL': float(log_loss(y_te_true, p_star_pred,\n",
1598
- " labels=np.arange(num_classes))),\n",
1599
- " 'Test_Brier': float(multiclass_brier_score(y_te_cat, p_star_pred)),\n",
1600
- " 'Test_ECE': float(expected_calibration_error(\n",
1601
- " y_te_true, p_star_pred, n_bins=15)),\n",
1602
- " 'Mean_AU': float(np.mean(au)),\n",
1603
- " 'Mean_EU': float(np.mean(eu)),\n",
1604
- " 'Mean_TU': float(np.mean(tu)),\n",
1605
- " })\n",
1606
- "\n",
1607
- "eval_df = pd.DataFrame(eval_results)\n",
1608
- "eval_df.to_csv(RESULTS_DIR / 'credit_evaluation_summary.csv', index=False)\n",
1609
- "print('\\n--- CREDIT Evaluation Summary ---')\n",
1610
- "print(eval_df.to_string(index=False))\n",
1611
- "\n",
1612
- "# Stash for the final Excel report\n",
1613
- "_credit_eval_df = eval_df.copy()\n",
1614
- "_credit_artifacts_dict = credit_artifacts"
1615
- ]
1616
- },
1617
- {
1618
- "cell_type": "markdown",
1619
- "id": "8f9182c1",
1620
- "metadata": {
1621
- "id": "8f9182c1"
1622
- },
1623
- "source": [
1624
- "## 7.2 \u2014 Confusion Matrices\n",
1625
- "\n",
1626
- "Renders and saves a side-by-side confusion matrix heatmap for every evaluated model."
1627
- ]
1628
- },
1629
- {
1630
- "cell_type": "code",
1631
- "execution_count": null,
1632
- "id": "7632b240",
1633
- "metadata": {
1634
- "colab": {
1635
- "base_uri": "https://localhost:8080/",
1636
- "height": 0
1637
- },
1638
- "id": "7632b240",
1639
- "executionInfo": {
1640
- "status": "ok",
1641
- "timestamp": 1780326578351,
1642
- "user_tz": -330,
1643
- "elapsed": 1545,
1644
- "user": {
1645
- "displayName": "Randy Orton",
1646
- "userId": "11725427482974841050"
1647
- }
1648
- },
1649
- "outputId": "d082ff93-05eb-4067-a5f2-d10127152519"
1650
- },
1651
- "outputs": [],
1652
- "source": [
1653
- "if credit_artifacts:\n",
1654
- " class_ticks = [str(i + 1) for i in range(num_classes)]\n",
1655
- " n_models = len(credit_artifacts)\n",
1656
- " fig, axes = plt.subplots(1, n_models, figsize=(7 * n_models, 5.5))\n",
1657
- " if n_models == 1:\n",
1658
- " axes = [axes]\n",
1659
- "\n",
1660
- " for ax, (mname, art) in zip(axes, credit_artifacts.items()):\n",
1661
- " sns.heatmap(\n",
1662
- " art['confusion_matrix'], annot=True, fmt='d', cmap='Blues',\n",
1663
- " xticklabels=class_ticks, yticklabels=class_ticks, cbar=False, ax=ax\n",
1664
- " )\n",
1665
- " ax.set_title(f'{mname} \u2014 CREDIT\\nConfusion Matrix', fontsize=13)\n",
1666
- " ax.set_xlabel('Predicted Class')\n",
1667
- " ax.set_ylabel('True Class')\n",
1668
- "\n",
1669
- " plt.tight_layout()\n",
1670
- " save_path = RESULTS_DIR / 'credit_confusion_matrices.png'\n",
1671
- " plt.savefig(save_path, dpi=220, bbox_inches='tight')\n",
1672
- " print(f'Saved \u2192 {save_path}')\n",
1673
- " plt.show()"
1674
- ]
1675
- },
1676
- {
1677
- "cell_type": "markdown",
1678
- "id": "cdb67872",
1679
- "metadata": {
1680
- "id": "cdb67872"
1681
- },
1682
- "source": [
1683
- "# 8.0 \u2014 Spatial Uncertainty Mapping\n",
1684
- "\n",
1685
- "Runs full-scene inference for each CREDIT student and produces a 3\u00d74 figure showing: the base prediction map, binary certain/uncertain masks, grey-overlay maps, and per-class pixel-count bar charts \u2014 all for the aleatoric, epistemic, and total uncertainty channels."
1686
- ]
1687
- },
1688
- {
1689
- "cell_type": "markdown",
1690
- "id": "783b6a92",
1691
- "metadata": {
1692
- "id": "783b6a92"
1693
- },
1694
- "source": [
1695
- "## 8.1 \u2014 Extract Full-Scene Patch Array"
1696
- ]
1697
- },
1698
- {
1699
- "cell_type": "code",
1700
- "execution_count": null,
1701
- "id": "105e5c3b",
1702
- "metadata": {
1703
- "colab": {
1704
- "base_uri": "https://localhost:8080/"
1705
- },
1706
- "id": "105e5c3b",
1707
- "executionInfo": {
1708
- "status": "ok",
1709
- "timestamp": 1780326578373,
1710
- "user_tz": -330,
1711
- "elapsed": 18,
1712
- "user": {
1713
- "displayName": "Randy Orton",
1714
- "userId": "11725427482974841050"
1715
- }
1716
- },
1717
- "outputId": "8171ce94-4b55-4df4-942c-b3799d6f89a4"
1718
- },
1719
- "outputs": [],
1720
- "source": [
1721
- "print('Extracting full-scene patch array (all H\u00d7W pixels)...')\n",
1722
- "pad = PATCH_SIZE // 2\n",
1723
- "x_pad = np.pad(x_img, ((pad, pad), (pad, pad), (0, 0)), mode='edge')\n",
1724
- "\n",
1725
- "scene_patches = np.empty((H * W, PATCH_SIZE, PATCH_SIZE, B), dtype=np.float32)\n",
1726
- "for idx, (r, c) in enumerate(\n",
1727
- " [(r, c) for r in range(H) for c in range(W)]\n",
1728
- "):\n",
1729
- " scene_patches[idx] = x_pad[r:r + PATCH_SIZE, c:c + PATCH_SIZE, :]\n",
1730
- "\n",
1731
- "print(f'Scene patch tensor: {scene_patches.shape}')"
1732
- ]
1733
- },
1734
- {
1735
- "cell_type": "markdown",
1736
- "id": "dabe63f1",
1737
- "metadata": {
1738
- "id": "dabe63f1"
1739
- },
1740
- "source": [
1741
- "## 8.2 \u2014 Spatial Map Generator\n",
1742
- "\n",
1743
- "Defines the function that produces and saves the 3\u00d74 uncertainty figure for a given student model."
1744
- ]
1745
- },
1746
- {
1747
- "cell_type": "code",
1748
- "execution_count": null,
1749
- "id": "39c0b971",
1750
- "metadata": {
1751
- "id": "39c0b971",
1752
- "executionInfo": {
1753
- "status": "ok",
1754
- "timestamp": 1780326578552,
1755
- "user_tz": -330,
1756
- "elapsed": 95,
1757
- "user": {
1758
- "displayName": "Randy Orton",
1759
- "userId": "11725427482974841050"
1760
- }
1761
- }
1762
- },
1763
- "outputs": [],
1764
- "source": [
1765
- "# Same bold, saturated base palette used across the other notebooks,\n",
1766
- "# auto-extended with additional bold shades (same style) if a future\n",
1767
- "# dataset has more than 10 classes.\n",
1768
- "import colorsys\n",
1769
- "\n",
1770
- "CLASS_COLOR_BASE = [\n",
1771
- " '#0000FF', '#00FF00', '#FF0000', '#00FFFF',\n",
1772
- " '#FF00FF', '#FFFF00', '#A52A2A', '#FFA500',\n",
1773
- " '#7FFF00', '#8A2BE2',\n",
1774
- "]\n",
1775
- "UNCERTAIN_COLOR = '#808080'\n",
1776
- "\n",
1777
- "def _generate_extra_shades(n_extra, hue_offset=0.05, sat=0.85, val=0.95):\n",
1778
- " \"\"\"Bold, highly-saturated extra hues in the same style as CLASS_COLOR_BASE.\"\"\"\n",
1779
- " shades = []\n",
1780
- " for i in range(n_extra):\n",
1781
- " hue = (hue_offset + i / max(n_extra, 1)) % 1.0\n",
1782
- " r, g, b = colorsys.hsv_to_rgb(hue, sat, val)\n",
1783
- " shades.append('#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)))\n",
1784
- " return shades\n",
1785
- "\n",
1786
- "def get_class_colors(n):\n",
1787
- " \"\"\"Return n distinct hex colours, extending the base 10 with additional bold\n",
1788
- " shades in the same saturated style if needed.\"\"\"\n",
1789
- " if n <= len(CLASS_COLOR_BASE):\n",
1790
- " return CLASS_COLOR_BASE[:n]\n",
1791
- " return CLASS_COLOR_BASE + _generate_extra_shades(n - len(CLASS_COLOR_BASE))\n",
1792
- "\n",
1793
- "def resolve_uncertain_color(class_colors, preferred=UNCERTAIN_COLOR, min_dist=60):\n",
1794
- " \"\"\"Return `preferred` unless it visually clashes with any class colour, in\n",
1795
- " which case fall back to a distinct dark slate so 'Uncertain' never collides\n",
1796
- " with a real class colour.\"\"\"\n",
1797
- " def _hex_to_rgb(h):\n",
1798
- " h = h.lstrip('#')\n",
1799
- " return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))\n",
1800
- " pr, pg, pb = _hex_to_rgb(preferred)\n",
1801
- " for c in class_colors:\n",
1802
- " cr, cg, cb = _hex_to_rgb(c)\n",
1803
- " dist = ((pr - cr) ** 2 + (pg - cg) ** 2 + (pb - cb) ** 2) ** 0.5\n",
1804
- " if dist < min_dist:\n",
1805
- " return '#2F2F2F'\n",
1806
- " return preferred\n",
1807
- "\n",
1808
- "\n",
1809
- "def generate_spatial_credit_maps(\n",
1810
- " model_name, student_model, scene_pixels,\n",
1811
- " H, W, au_thresh=0.5, eu_thresh=0.2, tu_thresh=0.7\n",
1812
- "):\n",
1813
- " \"\"\"\n",
1814
- " Run full-scene inference and save a 3\u00d74 spatial uncertainty figure.\n",
1815
- "\n",
1816
- " Panel layout (rows \u00d7 cols):\n",
1817
- " Row 0: Base prediction | AU binary | EU binary | TU binary\n",
1818
- " Row 1: AU grey overlay | EU grey overlay | TU grey overlay | (blank)\n",
1819
- " Row 2: AU bar chart | EU bar chart | TU bar chart | (blank)\n",
1820
- " \"\"\"\n",
1821
- " print(f' Inferring full scene for {model_name}...')\n",
1822
- " p_star_scene, delta_p_scene = student_model.predict(\n",
1823
- " scene_pixels, batch_size=2048, verbose=1\n",
1824
- " )\n",
1825
- " p_star_scene = np.clip(p_star_scene, 1e-12, 1.0)\n",
1826
- " n_cls = p_star_scene.shape[-1]\n",
1827
- "\n",
1828
- " au_scene = -np.sum(p_star_scene * np.log(p_star_scene), axis=-1)\n",
1829
- " eu_scene = np.mean(delta_p_scene, axis=-1)\n",
1830
- " tu_scene = au_scene + eu_scene\n",
1831
- "\n",
1832
- " pred_map = np.argmax(p_star_scene, axis=-1).reshape(H, W)\n",
1833
- " au_mask = (au_scene.reshape(H, W) > au_thresh).astype(int)\n",
1834
- " eu_mask = (eu_scene.reshape(H, W) > eu_thresh).astype(int)\n",
1835
- " tu_mask = (tu_scene.reshape(H, W) > tu_thresh).astype(int)\n",
1836
- "\n",
1837
- " combined_au = np.where(au_mask == 1, n_cls, pred_map)\n",
1838
- " combined_eu = np.where(eu_mask == 1, n_cls, pred_map)\n",
1839
- " combined_tu = np.where(tu_mask == 1, n_cls, pred_map)\n",
1840
- "\n",
1841
- " class_colors = get_class_colors(n_cls)\n",
1842
- " uncertain_color = resolve_uncertain_color(class_colors)\n",
1843
- " cmap_base = ListedColormap(class_colors)\n",
1844
- " cmap_unc = ListedColormap(class_colors + [uncertain_color])\n",
1845
- " cmap_binary = ListedColormap(['#FFFF00', '#001F3F'])\n",
1846
- "\n",
1847
- " bar_lbls = [f'Class {i}' for i in range(n_cls)] + ['Uncertain']\n",
1848
- " bar_cols = class_colors + [uncertain_color]\n",
1849
- "\n",
1850
- " fig, axes = plt.subplots(3, 4, figsize=(38, 26))\n",
1851
- " fig.suptitle(\n",
1852
- " f'{model_name} \u2014 CREDIT Uncertainty Maps (Absolute Thresholds)',\n",
1853
- " fontsize=24, fontweight='bold', y=0.99\n",
1854
- " )\n",
1855
- "\n",
1856
- " # \u2500\u2500 Row 0: Base prediction + 3 binary maps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1857
- " axes[0, 0].imshow(pred_map, cmap=cmap_base, vmin=0, vmax=n_cls - 1)\n",
1858
- " axes[0, 0].set_title('Base Prediction Map', fontsize=15)\n",
1859
- " axes[0, 0].axis('off')\n",
1860
- "\n",
1861
- " binary_specs = [\n",
1862
- " (axes[0, 1], au_mask, f'Aleatoric (AU > {au_thresh})'),\n",
1863
- " (axes[0, 2], eu_mask, f'Epistemic (EU > {eu_thresh})'),\n",
1864
- " (axes[0, 3], tu_mask, f'Total (TU > {tu_thresh})'),\n",
1865
- " ]\n",
1866
- " for ax, mask, label in binary_specs:\n",
1867
- " ax.imshow(mask, cmap=cmap_binary, vmin=0, vmax=1)\n",
1868
- " ax.set_title(f'Certain vs Uncertain\\n{label}', fontsize=15, pad=10)\n",
1869
- " ax.axis('off')\n",
1870
- " ax.legend(\n",
1871
- " handles=[\n",
1872
- " Patch(facecolor='#FFFF00', label='Certain'),\n",
1873
- " Patch(facecolor='#001F3F', label='Uncertain'),\n",
1874
- " ],\n",
1875
- " loc='upper left',\n",
1876
- " bbox_to_anchor=(0.0, -0.02),\n",
1877
- " borderaxespad=0,\n",
1878
- " fontsize=11,\n",
1879
- " framealpha=0.9,\n",
1880
- " ncol=2\n",
1881
- " )\n",
1882
- "\n",
1883
- " # \u2500\u2500 Row 1: Grey overlay maps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1884
- " overlay_specs = [\n",
1885
- " (axes[1, 0], combined_au, f'Aleatoric (AU > {au_thresh})'),\n",
1886
- " (axes[1, 1], combined_eu, f'Epistemic (EU > {eu_thresh})'),\n",
1887
- " (axes[1, 2], combined_tu, f'Total (TU > {tu_thresh})'),\n",
1888
- " ]\n",
1889
- " for ax, combined, label in overlay_specs:\n",
1890
- " ax.imshow(combined, cmap=cmap_unc, vmin=0, vmax=n_cls)\n",
1891
- " ax.set_title(f'Grey Overlay \u2014 {label}', fontsize=15, pad=10)\n",
1892
- " ax.axis('off')\n",
1893
- " axes[1, 3].axis('off')\n",
1894
- "\n",
1895
- " # \u2500\u2500 Row 2: Pixel-count bar charts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1896
- " bar_specs = [\n",
1897
- " (axes[2, 0], combined_au, f'Aleatoric (AU > {au_thresh})'),\n",
1898
- " (axes[2, 1], combined_eu, f'Epistemic (EU > {eu_thresh})'),\n",
1899
- " (axes[2, 2], combined_tu, f'Total (TU > {tu_thresh})'),\n",
1900
- " ]\n",
1901
- " for ax, combined, label in bar_specs:\n",
1902
- " uniq, cnt = np.unique(combined, return_counts=True)\n",
1903
- " c_dict = {int(k): int(v) for k, v in zip(uniq, cnt)}\n",
1904
- " bar_vals = [c_dict.get(i, 0) for i in range(n_cls + 1)]\n",
1905
- " ax.bar(bar_lbls, bar_vals, color=bar_cols, edgecolor='black')\n",
1906
- " ax.set_title(f'Pixel Counts \u2014 {label}', fontsize=15, pad=10)\n",
1907
- " ax.tick_params(axis='x', rotation=45, labelsize=11)\n",
1908
- " ax.set_ylabel('Pixel Count', fontsize=12)\n",
1909
- " max_val = max(bar_vals, default=1)\n",
1910
- " for i, v in enumerate(bar_vals):\n",
1911
- " ax.text(i, v + max_val * 0.01,\n",
1912
- " f'{v:,}', ha='center', va='bottom',\n",
1913
- " fontweight='bold', fontsize=9)\n",
1914
- " ax.set_ylim(0, max_val * 1.12)\n",
1915
- " axes[2, 3].axis('off')\n",
1916
- "\n",
1917
- " plt.tight_layout(rect=[0, 0.03, 1, 0.98])\n",
1918
- " save_path = RESULTS_DIR / f'{model_name}_CREDIT_spatial_maps.png'\n",
1919
- " plt.savefig(save_path, dpi=300, bbox_inches='tight')\n",
1920
- " print(f' Saved \u2192 {save_path}')\n",
1921
- " plt.show()"
1922
- ]
1923
- },
1924
- {
1925
- "cell_type": "markdown",
1926
- "id": "95c54f00",
1927
- "metadata": {
1928
- "id": "95c54f00"
1929
- },
1930
- "source": [
1931
- "## 8.3 \u2014 Run Spatial Mapping for All Models"
1932
- ]
1933
- },
1934
- {
1935
- "cell_type": "code",
1936
- "execution_count": null,
1937
- "id": "6758d694",
1938
- "metadata": {
1939
- "colab": {
1940
- "base_uri": "https://localhost:8080/",
1941
- "height": 1000,
1942
- "output_embedded_package_id": "1gDB51H9wc43U3d8erY1lxaGDsVsR279b"
1943
- },
1944
- "id": "6758d694",
1945
- "executionInfo": {
1946
- "status": "ok",
1947
- "timestamp": 1780326716495,
1948
- "user_tz": -330,
1949
- "elapsed": 137941,
1950
- "user": {
1951
- "displayName": "Randy Orton",
1952
- "userId": "11725427482974841050"
1953
- }
1954
- },
1955
- "outputId": "b232112c-8bb8-4e90-f288-7baa55c7ffbe"
1956
- },
1957
- "outputs": [],
1958
- "source": [
1959
- "for model_name, builder in model_builders.items():\n",
1960
- " best_path = MODEL_DIR / f'{model_name}_CREDIT_best.keras'\n",
1961
- " if not best_path.exists():\n",
1962
- " print(f'[{model_name}] No weights found \u2014 skipping spatial maps.')\n",
1963
- " continue\n",
1964
- "\n",
1965
- " tf.keras.backend.clear_session()\n",
1966
- " student = build_credit_student(builder, num_classes)\n",
1967
- " student.load_weights(best_path)\n",
1968
- "\n",
1969
- " generate_spatial_credit_maps(\n",
1970
- " model_name, student, scene_patches, H, W,\n",
1971
- " au_thresh=0.5, eu_thresh=0.2, tu_thresh=0.7\n",
1972
- " )"
1973
- ]
1974
- },
1975
- {
1976
- "cell_type": "markdown",
1977
- "id": "6fddf771",
1978
- "metadata": {
1979
- "id": "6fddf771"
1980
- },
1981
- "source": [
1982
- "# 9.0 \u2014 Excel Report Export\n",
1983
- "\n",
1984
- "Collects all saved plot paths (confusion matrices, spatial uncertainty maps, and any training-curve PNGs) and writes the final `CREDIT_Results.xlsx` workbook."
1985
- ]
1986
- },
1987
- {
1988
- "cell_type": "code",
1989
- "execution_count": null,
1990
- "id": "3ceb105a",
1991
- "metadata": {
1992
- "colab": {
1993
- "base_uri": "https://localhost:8080/",
1994
- "height": 70
1995
- },
1996
- "id": "3ceb105a",
1997
- "executionInfo": {
1998
- "status": "ok",
1999
- "timestamp": 1780326716583,
2000
- "user_tz": -330,
2001
- "elapsed": 27,
2002
- "user": {
2003
- "displayName": "Randy Orton",
2004
- "userId": "11725427482974841050"
2005
- }
2006
- },
2007
- "outputId": "3e78adfd-20af-4518-a867-0b2d5fe24826"
2008
- },
2009
- "outputs": [],
2010
- "source": [
2011
- "# \u2500\u2500 Collect all plot paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
2012
- "_plot_entries = []\n",
2013
- "\n",
2014
- "# Confusion matrix image\n",
2015
- "_cm_path = str(RESULTS_DIR / 'credit_confusion_matrices.png')\n",
2016
- "_plot_entries.append(('Confusion Matrices (all models)', _cm_path))\n",
2017
- "\n",
2018
- "# Per-model spatial uncertainty maps\n",
2019
- "for _mname in model_builders:\n",
2020
- " _sp_path = str(RESULTS_DIR / f'{_mname}_CREDIT_spatial_maps.png')\n",
2021
- " _plot_entries.append((f'{_mname} \u2014 Spatial Uncertainty Maps', _sp_path))\n",
2022
- "\n",
2023
- "# Any additional training-curve PNGs saved to PLOT_DIR\n",
2024
- "for _p in sorted(PLOT_DIR.glob('*.png')):\n",
2025
- " _plot_entries.append((_p.stem.replace('_', ' ').title(), str(_p)))\n",
2026
- "\n",
2027
- "# \u2500\u2500 Write the workbook \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
2028
- "create_credit_excel_report(\n",
2029
- " results_dir = RESULTS_DIR,\n",
2030
- " train_summary_df = _credit_train_summary_df,\n",
2031
- " eval_summary_df = _credit_eval_df,\n",
2032
- " credit_artifacts_dict = _credit_artifacts_dict,\n",
2033
- " plot_paths = _plot_entries,\n",
2034
- ")"
2035
- ]
2036
- }
2037
- ],
2038
- "metadata": {
2039
- "colab": {
2040
- "provenance": [],
2041
- "collapsed_sections": [
2042
- "5302be31",
2043
- "90f75990",
2044
- "5897552f",
2045
- "7d481920",
2046
- "1cdf93bb",
2047
- "04e1cb62",
2048
- "8cf0a904",
2049
- "6fddf771"
2050
- ],
2051
- "gpuType": "T4"
2052
- },
2053
- "kernelspec": {
2054
- "display_name": "Python 3",
2055
- "name": "python3"
2056
- },
2057
- "language_info": {
2058
- "name": "python",
2059
- "version": "3.10.0"
2060
- },
2061
- "accelerator": "GPU"
2062
- },
2063
- "nbformat": 4,
2064
- "nbformat_minor": 5
2065
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
credit/results/AlexNet_CNN_CREDIT_spatial_maps.png DELETED

Git LFS Details

  • SHA256: 7566d8f5d68c9874be855fc4e2c74225ede8d52704da9b36794d6fb059e6e02c
  • Pointer size: 132 Bytes
  • Size of remote file: 1.15 MB
credit/results/CREDIT_Results.xlsx DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:137bd093325fd32c4d70f7fd049216d6a2f6a00a7811c84bcf06c8f04feba04b
3
- size 2556098
 
 
 
 
credit/results/GFNet_CREDIT_spatial_maps.png DELETED

Git LFS Details

  • SHA256: db3ce27d36654743387723d869f00709f602ea7e51e6aa4b0cc1172b600c18cb
  • Pointer size: 132 Bytes
  • Size of remote file: 1.22 MB
credit/results/ViT_UNet_CREDIT_spatial_maps.png DELETED

Git LFS Details

  • SHA256: 2e76f3229cb8a85f8a11a3b7901b5087a847c7b280cde36b387532740d507896
  • Pointer size: 132 Bytes
  • Size of remote file: 1.22 MB
credit/results/credit_confusion_matrices.png DELETED

Git LFS Details

  • SHA256: e65a423f3630ed52ecc89a51ce1a35b2ca90b9ac75f4d8a53cbd8893e3d951b9
  • Pointer size: 131 Bytes
  • Size of remote file: 120 kB
credit/results/credit_evaluation_summary.csv DELETED
@@ -1,4 +0,0 @@
1
- Model,Test_Accuracy,Macro_F1,Cohen_Kappa,Test_NLL,Test_Brier,Test_ECE,Mean_AU,Mean_EU,Mean_TU
2
- AlexNet_CNN,0.9994804809262283,0.9989244889559838,0.9993741482276686,0.0015514798324969712,0.0007639509124735289,0.0005383794759277847,0.0031835082918405533,0.00027990108355879784,0.0034634096082299948
3
- GFNet,0.9999257829894612,0.9998136737701507,0.9999105961705329,0.0012463549818028674,0.0002783389623474388,0.0009749657112960736,0.007062746211886406,1.1088519613622339e-06,0.007063854485750198
4
- ViT_UNet,0.9978477066943744,0.9965033072622065,0.9974071374608717,0.04059596322322891,0.004743752003709026,0.03291026674818175,0.21300919353961945,0.00024190374824684113,0.21325111389160156
 
 
 
 
 
credit/results/credit_train_uncertainty_summary.csv DELETED
@@ -1,4 +0,0 @@
1
- Model,Mean_AU,Mean_EU,Mean_TU,Train_Time_sec
2
- AlexNet_CNN,0.0031835083,0.00027990108,0.0034634096,2038.4876195370002
3
- GFNet,0.007062746,1.108852e-06,0.0070638545,2269.2560890150003
4
- ViT_UNet,0.2130092,0.00024190375,0.21325111,3810.0610708649992
 
 
 
 
 
ensemble/Model_training_ensembles.ipynb DELETED
@@ -1,2006 +0,0 @@
1
- {
2
- "nbformat": 4,
3
- "nbformat_minor": 5,
4
- "metadata": {
5
- "kernelspec": {
6
- "display_name": "Python 3",
7
- "name": "python3"
8
- },
9
- "language_info": {
10
- "name": "python",
11
- "version": "3.10.0"
12
- },
13
- "colab": {
14
- "provenance": [],
15
- "collapsed_sections": [
16
- "Wq4Ce3i-jmew",
17
- "_HTyxJcIjme6",
18
- "WlGs-SYMjme_",
19
- "djg40XJDjmfB",
20
- "NKYV1hDsjmfD",
21
- "hPJBPlZTjmfE",
22
- "KG2bOKWtjmfI",
23
- "g1HAWWVBjmfK",
24
- "t_0f2DsWjmfM"
25
- ],
26
- "gpuType": "T4"
27
- },
28
- "accelerator": "GPU"
29
- },
30
- "cells": [
31
- {
32
- "cell_type": "markdown",
33
- "metadata": {
34
- "id": "Wq4Ce3i-jmew"
35
- },
36
- "source": [
37
- "# 1.0 \u2014 Setup & Imports\n",
38
- "\n",
39
- "Mounts Google Drive, imports all required libraries, and seeds all random number generators for reproducibility."
40
- ],
41
- "id": "Wq4Ce3i-jmew"
42
- },
43
- {
44
- "cell_type": "markdown",
45
- "metadata": {
46
- "id": "VIEqxcMqjmez"
47
- },
48
- "source": [
49
- "## 1.1 \u2014 Google Drive & Library Imports"
50
- ],
51
- "id": "VIEqxcMqjmez"
52
- },
53
- {
54
- "cell_type": "code",
55
- "execution_count": null,
56
- "metadata": {
57
- "colab": {
58
- "base_uri": "https://localhost:8080/"
59
- },
60
- "id": "ZtXnIJLNjme1",
61
- "outputId": "c5e39849-8f6d-4945-f54d-a8178aa215f3",
62
- "executionInfo": {
63
- "status": "ok",
64
- "timestamp": 1782312434471,
65
- "user_tz": -330,
66
- "elapsed": 2071,
67
- "user": {
68
- "displayName": "Danesh Selwal",
69
- "userId": "10565017814556927251"
70
- }
71
- }
72
- },
73
- "outputs": [],
74
- "source": [
75
- "from google.colab import drive\n",
76
- "drive.mount('/content/drive')\n",
77
- "\n",
78
- "# Standard library\n",
79
- "import os\n",
80
- "import json\n",
81
- "import time\n",
82
- "import random\n",
83
- "import io\n",
84
- "from pathlib import Path\n",
85
- "\n",
86
- "from scipy.io import loadmat\n",
87
- "\n",
88
- "# Numerical / data\n",
89
- "import numpy as np\n",
90
- "import pandas as pd\n",
91
- "\n",
92
- "# Visualisation\n",
93
- "import seaborn as sns\n",
94
- "import matplotlib.pyplot as plt\n",
95
- "from matplotlib.colors import ListedColormap\n",
96
- "\n",
97
- "# Deep learning\n",
98
- "import tensorflow as tf\n",
99
- "from tensorflow import keras\n",
100
- "from tensorflow.keras import layers, regularizers\n",
101
- "\n",
102
- "# Scikit-learn\n",
103
- "from sklearn.model_selection import train_test_split\n",
104
- "from sklearn.metrics import (\n",
105
- " accuracy_score,\n",
106
- " cohen_kappa_score,\n",
107
- " confusion_matrix,\n",
108
- " classification_report,\n",
109
- " f1_score,\n",
110
- " log_loss,\n",
111
- ")\n",
112
- "\n",
113
- "# Excel export\n",
114
- "from openpyxl import Workbook, load_workbook\n",
115
- "from openpyxl.drawing.image import Image as XLImage\n",
116
- "\n",
117
- "sns.set_style('whitegrid')\n",
118
- "print('TensorFlow:', tf.__version__)"
119
- ],
120
- "id": "ZtXnIJLNjme1"
121
- },
122
- {
123
- "cell_type": "markdown",
124
- "metadata": {
125
- "id": "9RW99Ciojme2"
126
- },
127
- "source": [
128
- "## 1.2 \u2014 Seed Initialisation\n",
129
- "\n",
130
- "Sets the global random seed across Python, NumPy, and TensorFlow to ensure reproducible runs."
131
- ],
132
- "id": "9RW99Ciojme2"
133
- },
134
- {
135
- "cell_type": "code",
136
- "execution_count": null,
137
- "metadata": {
138
- "id": "d1NVUhVRjme4",
139
- "executionInfo": {
140
- "status": "ok",
141
- "timestamp": 1782312434473,
142
- "user_tz": -330,
143
- "elapsed": 5,
144
- "user": {
145
- "displayName": "Danesh Selwal",
146
- "userId": "10565017814556927251"
147
- }
148
- }
149
- },
150
- "outputs": [],
151
- "source": [
152
- "SEED = 42\n",
153
- "random.seed(SEED)\n",
154
- "np.random.seed(SEED)\n",
155
- "tf.random.set_seed(SEED)"
156
- ],
157
- "id": "d1NVUhVRjme4"
158
- },
159
- {
160
- "cell_type": "markdown",
161
- "metadata": {
162
- "id": "_HTyxJcIjme6"
163
- },
164
- "source": [
165
- "# 2.0 \u2014 Configuration\n",
166
- "\n",
167
- "All project paths, dataset geometry, training hyperparameters, and per-architecture configs are defined here. Change values in this section only \u2014 nothing below should need editing for typical experiments."
168
- ],
169
- "id": "_HTyxJcIjme6"
170
- },
171
- {
172
- "cell_type": "markdown",
173
- "metadata": {
174
- "id": "qlYXkH0gjme7"
175
- },
176
- "source": [
177
- "## 2.1 \u2014 Paths & Directories"
178
- ],
179
- "id": "qlYXkH0gjme7"
180
- },
181
- {
182
- "cell_type": "code",
183
- "execution_count": null,
184
- "metadata": {
185
- "colab": {
186
- "base_uri": "https://localhost:8080/"
187
- },
188
- "id": "YGJY_eCAjme7",
189
- "outputId": "75f5d3b8-aae1-4203-ee2c-a4f966a6fa71",
190
- "executionInfo": {
191
- "status": "ok",
192
- "timestamp": 1782312434483,
193
- "user_tz": -330,
194
- "elapsed": 8,
195
- "user": {
196
- "displayName": "Danesh Selwal",
197
- "userId": "10565017814556927251"
198
- }
199
- }
200
- },
201
- "outputs": [],
202
- "source": [
203
- "PROJECT_ROOT = Path('/content/drive/My Drive/Classification/')\n",
204
- "DATA_DIR = PROJECT_ROOT / 'data'\n",
205
- "MODEL_DIR = PROJECT_ROOT / 'ensemble' / 'models'\n",
206
- "MODEL_DIR.mkdir(parents=True, exist_ok=True)\n",
207
- "PLOT_DIR = MODEL_DIR / 'training_plots'\n",
208
- "PLOT_DIR.mkdir(parents=True, exist_ok=True)\n",
209
- "VIS_DIR = MODEL_DIR / 'scene_visualizations'\n",
210
- "VIS_DIR.mkdir(parents=True, exist_ok=True)\n",
211
- "\n",
212
- "DATA_FILE = DATA_DIR / 'DIAS.mat' # hyperspectral cube (H, W, Bands)\n",
213
- "LABEL_FILE = DATA_DIR / 'DIAS_ref.mat' # reference / ground-truth label map\n",
214
- "VIS_EXCEL_PATH = VIS_DIR / 'initial_classification_maps.xlsx'\n",
215
- "\n",
216
- "print('Data file :', DATA_FILE)\n",
217
- "print('Label file :', LABEL_FILE)\n",
218
- "print('Model dir :', MODEL_DIR)\n",
219
- "print('Plot dir :', PLOT_DIR)"
220
- ],
221
- "id": "YGJY_eCAjme7"
222
- },
223
- {
224
- "cell_type": "markdown",
225
- "metadata": {
226
- "id": "jJujDppQjme8"
227
- },
228
- "source": [
229
- "## 2.2 \u2014 Dataset & Training Hyperparameters"
230
- ],
231
- "id": "jJujDppQjme8"
232
- },
233
- {
234
- "cell_type": "code",
235
- "execution_count": null,
236
- "metadata": {
237
- "id": "vbuDs_kJjme9",
238
- "executionInfo": {
239
- "status": "ok",
240
- "timestamp": 1782312434496,
241
- "user_tz": -330,
242
- "elapsed": 14,
243
- "user": {
244
- "displayName": "Danesh Selwal",
245
- "userId": "10565017814556927251"
246
- }
247
- }
248
- },
249
- "outputs": [],
250
- "source": [
251
- "# H, W, B are inferred automatically from the .mat cube shape once loaded in\n",
252
- "# Section 3.2, so this notebook works unchanged for any scene size / band count.\n",
253
- "H, W, B = None, None, None\n",
254
- "PERCENTILE_CLIP = (2.0, 98.0) # robust per-band stretch range (2nd-98th percentile)\n",
255
- "PATCH_SIZE = 9\n",
256
- "INNER_PATCH = 3 # used by GFNet/ViT tokenisation\n",
257
- "\n",
258
- "TRAIN_PERCENT = 0.75\n",
259
- "VAL_SPLIT_FROM_TRAIN = 0.20\n",
260
- "\n",
261
- "BATCH_SIZE = 128\n",
262
- "EPOCHS = 100\n",
263
- "LEARNING_RATE = 3e-4\n",
264
- "DROPOUT_RATE = 0.25\n",
265
- "\n",
266
- "TRAIN_CFG = {\n",
267
- " 'label_smoothing': 0.05,\n",
268
- " 'weight_decay': 1e-4,\n",
269
- " 'clipnorm': 1.0,\n",
270
- " 'cosine_alpha': 0.05,\n",
271
- "}"
272
- ],
273
- "id": "vbuDs_kJjme9"
274
- },
275
- {
276
- "cell_type": "markdown",
277
- "metadata": {
278
- "id": "FQVtnNAZjme9"
279
- },
280
- "source": [
281
- "## 2.3 \u2014 Architecture Configs (Primary & Fallback)"
282
- ],
283
- "id": "FQVtnNAZjme9"
284
- },
285
- {
286
- "cell_type": "code",
287
- "execution_count": null,
288
- "metadata": {
289
- "colab": {
290
- "base_uri": "https://localhost:8080/"
291
- },
292
- "id": "GgqwWdkyjme9",
293
- "outputId": "622f35a4-5e34-4c02-b9b3-baecb25ab54c",
294
- "executionInfo": {
295
- "status": "ok",
296
- "timestamp": 1782312434522,
297
- "user_tz": -330,
298
- "elapsed": 21,
299
- "user": {
300
- "displayName": "Danesh Selwal",
301
- "userId": "10565017814556927251"
302
- }
303
- }
304
- },
305
- "outputs": [],
306
- "source": [
307
- "CAPACITY_PRESET = 'legacy_arch'\n",
308
- "\n",
309
- "ALEXNET_CFG = {\n",
310
- " 'conv_filters': [96, 256, 384, 384, 256],\n",
311
- " 'dense_units': [4096, 1024, 256, 32],\n",
312
- "}\n",
313
- "\n",
314
- "GFNET_CFG = {\n",
315
- " 'hidden_dim': 512,\n",
316
- " 'num_blocks': 5,\n",
317
- " 'mlp_ratio': 4,\n",
318
- "}\n",
319
- "\n",
320
- "VIT_CFG = {\n",
321
- " 'projection_dim': 256,\n",
322
- " 'num_heads': 4,\n",
323
- " 'transformer_layers': 12,\n",
324
- " 'mlp_multiplier': 2,\n",
325
- " 'head_units': [512, 256, 128, 64],\n",
326
- "}\n",
327
- "\n",
328
- "# Deterministic fallback configs if Colab runs out of memory\n",
329
- "GFNET_FALLBACK_CFG = {\n",
330
- " 'hidden_dim': 384,\n",
331
- " 'num_blocks': 4,\n",
332
- " 'mlp_ratio': 4,\n",
333
- "}\n",
334
- "\n",
335
- "VIT_FALLBACK_CFG = {\n",
336
- " 'projection_dim': 192,\n",
337
- " 'num_heads': 4,\n",
338
- " 'transformer_layers': 8,\n",
339
- " 'mlp_multiplier': 2,\n",
340
- " 'head_units': [384, 192, 96, 64],\n",
341
- "}\n",
342
- "\n",
343
- "print('Architecture preset:', CAPACITY_PRESET)"
344
- ],
345
- "id": "GgqwWdkyjme9"
346
- },
347
- {
348
- "cell_type": "markdown",
349
- "metadata": {
350
- "id": "RDmmrIBtjme-"
351
- },
352
- "source": [
353
- "## 2.4 \u2014 AlexNet Legacy Training Config\n",
354
- "\n",
355
- "Separate learning-rate settings for AlexNet, matching the original single-head script for uncertainty recovery."
356
- ],
357
- "id": "RDmmrIBtjme-"
358
- },
359
- {
360
- "cell_type": "code",
361
- "execution_count": null,
362
- "metadata": {
363
- "id": "iaFlb4N0jme-",
364
- "executionInfo": {
365
- "status": "ok",
366
- "timestamp": 1782312434543,
367
- "user_tz": -330,
368
- "elapsed": 19,
369
- "user": {
370
- "displayName": "Danesh Selwal",
371
- "userId": "10565017814556927251"
372
- }
373
- }
374
- },
375
- "outputs": [],
376
- "source": [
377
- "ALEXNET_LEGACY_SPLIT_SEED = 10\n",
378
- "ALEXNET_LEGACY_TRAIN_PERCENT = 0.75\n",
379
- "ALEXNET_LR_START = 0.01\n",
380
- "ALEXNET_LR_MAX = 0.02\n",
381
- "ALEXNET_LR_MIN = 0.005"
382
- ],
383
- "id": "iaFlb4N0jme-"
384
- },
385
- {
386
- "cell_type": "markdown",
387
- "metadata": {
388
- "id": "xW4Xhcfkjme_"
389
- },
390
- "source": [
391
- "## 2.5 \u2014 Visualisation Palette"
392
- ],
393
- "id": "xW4Xhcfkjme_"
394
- },
395
- {
396
- "cell_type": "code",
397
- "execution_count": null,
398
- "metadata": {
399
- "id": "2mwkmnUnjme_",
400
- "executionInfo": {
401
- "status": "ok",
402
- "timestamp": 1782312434545,
403
- "user_tz": -330,
404
- "elapsed": 4,
405
- "user": {
406
- "displayName": "Danesh Selwal",
407
- "userId": "10565017814556927251"
408
- }
409
- }
410
- },
411
- "outputs": [],
412
- "source": [
413
- "CLASS_COLOR_BASE = [\n",
414
- " '#0000FF', '#00FF00', '#FF0000', '#00FFFF',\n",
415
- " '#FF00FF', '#FFFF00', '#A52A2A', '#FFA500',\n",
416
- " '#7FFF00', '#8A2BE2',\n",
417
- "]\n",
418
- "BACKGROUND_COLOR = '#000000'\n",
419
- "\n",
420
- "import colorsys\n",
421
- "\n",
422
- "def _generate_extra_shades(n_extra, hue_offset=0.05, sat=0.85, val=0.95):\n",
423
- " \"\"\"Bold, highly-saturated extra hues in the same style as CLASS_COLOR_BASE,\n",
424
- " for datasets with more than 10 classes.\"\"\"\n",
425
- " shades = []\n",
426
- " for i in range(n_extra):\n",
427
- " hue = (hue_offset + i / max(n_extra, 1)) % 1.0\n",
428
- " r, g, b = colorsys.hsv_to_rgb(hue, sat, val)\n",
429
- " shades.append('#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)))\n",
430
- " return shades\n",
431
- "\n",
432
- "def get_class_colors(n):\n",
433
- " \"\"\"Return n distinct hex colours, extending the base 10 with additional bold\n",
434
- " shades in the same saturated style if needed.\"\"\"\n",
435
- " if n <= len(CLASS_COLOR_BASE):\n",
436
- " return CLASS_COLOR_BASE[:n]\n",
437
- " return CLASS_COLOR_BASE + _generate_extra_shades(n - len(CLASS_COLOR_BASE))"
438
- ],
439
- "id": "2mwkmnUnjme_"
440
- },
441
- {
442
- "cell_type": "markdown",
443
- "metadata": {
444
- "id": "WlGs-SYMjme_"
445
- },
446
- "source": [
447
- "# 3.0 \u2014 Data Loading & Preprocessing\n",
448
- "\n",
449
- "Loads the hyperspectral/multispectral scene and labels from .mat files, robustly normalises per-band via percentile clip, extracts labeled patches, and creates train/val/test splits."
450
- ],
451
- "id": "WlGs-SYMjme_"
452
- },
453
- {
454
- "cell_type": "markdown",
455
- "metadata": {
456
- "id": "lqxHO9jMjme_"
457
- },
458
- "source": [
459
- "## 3.1 \u2014 Data Loading & Patch Extraction Functions"
460
- ],
461
- "id": "lqxHO9jMjme_"
462
- },
463
- {
464
- "cell_type": "code",
465
- "execution_count": null,
466
- "metadata": {
467
- "id": "m-DJahHZjme_",
468
- "executionInfo": {
469
- "status": "ok",
470
- "timestamp": 1782312434563,
471
- "user_tz": -330,
472
- "elapsed": 16,
473
- "user": {
474
- "displayName": "Danesh Selwal",
475
- "userId": "10565017814556927251"
476
- }
477
- }
478
- },
479
- "outputs": [],
480
- "source": [
481
- "def load_scene_from_mat(data_path, label_path, data_key=None, label_key=None):\n",
482
- " \"\"\"Load a hyperspectral/multispectral scene + reference labels from .mat files.\n",
483
- "\n",
484
- " Auto-detects the variable name inside each .mat file if not given (matches\n",
485
- " files such as DIAS.mat / DIAS_ref.mat storing the array under a single\n",
486
- " named variable). Each spectral band is robustly stretched to [0, 1] using\n",
487
- " a percentile clip (PERCENTILE_CLIP) rather than literal min/max \u2014 a handful\n",
488
- " of saturated/no-data pixels (e.g. sensor clipping at int8 range limits\n",
489
- " -128/127) would otherwise dominate the stretch.\n",
490
- " \"\"\"\n",
491
- " def _first_array(mat_dict, key):\n",
492
- " if key is not None:\n",
493
- " return mat_dict[key]\n",
494
- " candidates = [v for k, v in mat_dict.items() if not k.startswith('__')]\n",
495
- " if not candidates:\n",
496
- " raise ValueError(f'No data arrays found in {mat_dict.keys()}')\n",
497
- " return candidates[0]\n",
498
- "\n",
499
- " data_mat = loadmat(data_path)\n",
500
- " label_mat = loadmat(label_path)\n",
501
- "\n",
502
- " x = _first_array(data_mat, data_key).astype(np.float32)\n",
503
- " y = _first_array(label_mat, label_key).astype(np.int32)\n",
504
- "\n",
505
- " if x.ndim != 3:\n",
506
- " raise ValueError(f'Expected a (H, W, Bands) cube, got shape {x.shape}')\n",
507
- " if y.shape != x.shape[:2]:\n",
508
- " raise ValueError(f'Label shape {y.shape} does not match image shape {x.shape[:2]}')\n",
509
- "\n",
510
- " height, width, bands = x.shape\n",
511
- "\n",
512
- " x_norm = np.empty_like(x, dtype=np.float32)\n",
513
- " for b in range(bands):\n",
514
- " band = x[:, :, b]\n",
515
- " lo = np.percentile(band, PERCENTILE_CLIP[0])\n",
516
- " hi = np.percentile(band, PERCENTILE_CLIP[1])\n",
517
- " denom = max(hi - lo, 1e-8)\n",
518
- " band_clipped = np.clip(band, lo, hi)\n",
519
- " x_norm[:, :, b] = (band_clipped - lo) / denom\n",
520
- "\n",
521
- " return x_norm, y, height, width, bands\n",
522
- "\n",
523
- "\n",
524
- "def extract_labeled_patches(x, y, patch_size=9):\n",
525
- " \"\"\"Extract fixed-size patches centred on every labeled (non-zero) pixel.\"\"\"\n",
526
- " pad = patch_size // 2\n",
527
- " x_pad = np.pad(x, ((pad, pad), (pad, pad), (0, 0)), mode='edge')\n",
528
- "\n",
529
- " coords = np.argwhere(y > 0)\n",
530
- " patches = np.empty((coords.shape[0], patch_size, patch_size, x.shape[-1]), dtype=np.float32)\n",
531
- " labels = np.empty((coords.shape[0],), dtype=np.int32)\n",
532
- "\n",
533
- " for i, (r, c) in enumerate(coords):\n",
534
- " patches[i] = x_pad[r:r + patch_size, c:c + patch_size, :]\n",
535
- " labels[i] = int(y[r, c]) - 1 # convert class IDs to 0..C-1\n",
536
- "\n",
537
- " return patches, labels, coords"
538
- ],
539
- "id": "m-DJahHZjme_"
540
- },
541
- {
542
- "cell_type": "markdown",
543
- "metadata": {
544
- "id": "suyQHfy0jmfA"
545
- },
546
- "source": [
547
- "## 3.2 \u2014 Load Dataset"
548
- ],
549
- "id": "suyQHfy0jmfA"
550
- },
551
- {
552
- "cell_type": "code",
553
- "execution_count": null,
554
- "metadata": {
555
- "colab": {
556
- "base_uri": "https://localhost:8080/"
557
- },
558
- "id": "Cm8sPt9NjmfA",
559
- "outputId": "0cab272a-842d-43a7-a1e0-30293d03580c",
560
- "executionInfo": {
561
- "status": "ok",
562
- "timestamp": 1782312434709,
563
- "user_tz": -330,
564
- "elapsed": 148,
565
- "user": {
566
- "displayName": "Danesh Selwal",
567
- "userId": "10565017814556927251"
568
- }
569
- }
570
- },
571
- "outputs": [],
572
- "source": [
573
- "x_img, y_img, H, W, B = load_scene_from_mat(DATA_FILE, LABEL_FILE)\n",
574
- "X, y, coords = extract_labeled_patches(x_img, y_img, PATCH_SIZE)\n",
575
- "\n",
576
- "num_classes = int(np.unique(y).size)\n",
577
- "input_shape = (PATCH_SIZE, PATCH_SIZE, B)\n",
578
- "\n",
579
- "print('Scene geometry: H=%d W=%d Bands=%d' % (H, W, B))\n",
580
- "print('x_img:', x_img.shape, ' y_img:', y_img.shape)\n",
581
- "print('Labeled samples:', X.shape[0])\n",
582
- "print('Patch tensor:', X.shape)\n",
583
- "print('Num classes:', num_classes)"
584
- ],
585
- "id": "Cm8sPt9NjmfA"
586
- },
587
- {
588
- "cell_type": "markdown",
589
- "metadata": {
590
- "id": "ROMwfXI8jmfA"
591
- },
592
- "source": [
593
- "## 3.3 \u2014 Train / Val / Test Splits\n",
594
- "\n",
595
- "Creates the primary stratified splits used by GFNet and ViT, plus the legacy split used by AlexNet to match original script behaviour."
596
- ],
597
- "id": "ROMwfXI8jmfA"
598
- },
599
- {
600
- "cell_type": "code",
601
- "execution_count": null,
602
- "metadata": {
603
- "colab": {
604
- "base_uri": "https://localhost:8080/"
605
- },
606
- "id": "fe2Tg2PJjmfA",
607
- "outputId": "f3a59e38-7c06-45e8-8a70-ef038f5b4adf",
608
- "executionInfo": {
609
- "status": "ok",
610
- "timestamp": 1782312434829,
611
- "user_tz": -330,
612
- "elapsed": 105,
613
- "user": {
614
- "displayName": "Danesh Selwal",
615
- "userId": "10565017814556927251"
616
- }
617
- }
618
- },
619
- "outputs": [],
620
- "source": [
621
- "# Primary split (GFNet / ViT)\n",
622
- "x_train_full, x_test, y_train_full, y_test = train_test_split(\n",
623
- " X, y, train_size=TRAIN_PERCENT, random_state=SEED, stratify=y,\n",
624
- ")\n",
625
- "x_train, x_val, y_train, y_val = train_test_split(\n",
626
- " x_train_full, y_train_full,\n",
627
- " test_size=VAL_SPLIT_FROM_TRAIN, random_state=SEED, stratify=y_train_full,\n",
628
- ")\n",
629
- "\n",
630
- "y_train_cat = keras.utils.to_categorical(y_train, num_classes)\n",
631
- "y_val_cat = keras.utils.to_categorical(y_val, num_classes)\n",
632
- "y_test_cat = keras.utils.to_categorical(y_test, num_classes)\n",
633
- "\n",
634
- "print('Train:', x_train.shape, y_train.shape)\n",
635
- "print('Val :', x_val.shape, y_val.shape)\n",
636
- "print('Test :', x_test.shape, y_test.shape)\n",
637
- "\n",
638
- "# AlexNet legacy split (matches old single-head script behaviour)\n",
639
- "x_train_alex, x_test_alex, y_train_alex, y_test_alex = train_test_split(\n",
640
- " X, y,\n",
641
- " train_size=ALEXNET_LEGACY_TRAIN_PERCENT,\n",
642
- " random_state=ALEXNET_LEGACY_SPLIT_SEED,\n",
643
- " stratify=y,\n",
644
- ")\n",
645
- "y_train_alex_cat = keras.utils.to_categorical(y_train_alex, num_classes)\n",
646
- "y_test_alex_cat = keras.utils.to_categorical(y_test_alex, num_classes)\n",
647
- "\n",
648
- "print('AlexNet Train/Test:', x_train_alex.shape, y_train_alex.shape,\n",
649
- " x_test_alex.shape, y_test_alex.shape)"
650
- ],
651
- "id": "fe2Tg2PJjmfA"
652
- },
653
- {
654
- "cell_type": "markdown",
655
- "metadata": {
656
- "id": "djg40XJDjmfB"
657
- },
658
- "source": [
659
- "# 4.0 \u2014 Model Definitions\n",
660
- "\n",
661
- "Defines three single-head architectures: AlexNet-based CNN, Global Filter Network (GFNet), and Vision Transformer (ViT with U-Net style skip connections). Custom Keras layers are registered for serialisation compatibility."
662
- ],
663
- "id": "djg40XJDjmfB"
664
- },
665
- {
666
- "cell_type": "markdown",
667
- "metadata": {
668
- "id": "StRqys60jmfB"
669
- },
670
- "source": [
671
- "## 4.1 \u2014 Shared Custom Keras Layers\n",
672
- "\n",
673
- "Reusable layers shared across GFNet and ViT: patch extraction, positional encoding, and the frequency-domain global filter."
674
- ],
675
- "id": "StRqys60jmfB"
676
- },
677
- {
678
- "cell_type": "code",
679
- "execution_count": null,
680
- "metadata": {
681
- "id": "-S-TKPEjjmfB",
682
- "executionInfo": {
683
- "status": "ok",
684
- "timestamp": 1782312434841,
685
- "user_tz": -330,
686
- "elapsed": 15,
687
- "user": {
688
- "displayName": "Danesh Selwal",
689
- "userId": "10565017814556927251"
690
- }
691
- }
692
- },
693
- "outputs": [],
694
- "source": [
695
- "@tf.keras.utils.register_keras_serializable()\n",
696
- "class PatchExtractor(layers.Layer):\n",
697
- " \"\"\"Extracts non-overlapping patches from an image tensor.\"\"\"\n",
698
- "\n",
699
- " def __init__(self, patch_size=3, **kwargs):\n",
700
- " super().__init__(**kwargs)\n",
701
- " self.patch_size = patch_size\n",
702
- "\n",
703
- " def call(self, images):\n",
704
- " patches = tf.image.extract_patches(\n",
705
- " images=images,\n",
706
- " sizes=[1, self.patch_size, self.patch_size, 1],\n",
707
- " strides=[1, self.patch_size, self.patch_size, 1],\n",
708
- " rates=[1, 1, 1, 1],\n",
709
- " padding='VALID',\n",
710
- " )\n",
711
- " batch = tf.shape(images)[0]\n",
712
- " num_patches = tf.shape(patches)[1] * tf.shape(patches)[2]\n",
713
- " patch_dim = tf.shape(patches)[-1]\n",
714
- " return tf.reshape(patches, [batch, num_patches, patch_dim])\n",
715
- "\n",
716
- " def get_config(self):\n",
717
- " cfg = super().get_config()\n",
718
- " cfg.update({'patch_size': self.patch_size})\n",
719
- " return cfg\n",
720
- "\n",
721
- "\n",
722
- "@tf.keras.utils.register_keras_serializable()\n",
723
- "class PatchPositionEncoder(layers.Layer):\n",
724
- " \"\"\"Projects patches to a hidden dim and adds learned positional embeddings (no CLS token).\"\"\"\n",
725
- "\n",
726
- " def __init__(self, num_patches, projection_dim, **kwargs):\n",
727
- " super().__init__(**kwargs)\n",
728
- " self.num_patches = num_patches\n",
729
- " self.projection_dim = projection_dim\n",
730
- " self.projection = layers.Dense(projection_dim)\n",
731
- " self.position_embedding = layers.Embedding(input_dim=num_patches, output_dim=projection_dim)\n",
732
- "\n",
733
- " def call(self, patches):\n",
734
- " positions = tf.range(start=0, limit=self.num_patches, delta=1)\n",
735
- " return self.projection(patches) + self.position_embedding(positions)\n",
736
- "\n",
737
- " def get_config(self):\n",
738
- " cfg = super().get_config()\n",
739
- " cfg.update({'num_patches': self.num_patches, 'projection_dim': self.projection_dim})\n",
740
- " return cfg\n",
741
- "\n",
742
- "\n",
743
- "@tf.keras.utils.register_keras_serializable()\n",
744
- "class GlobalFilterLayer(layers.Layer):\n",
745
- " \"\"\"Applies a learnable complex-valued frequency filter via 2-D FFT (GFNet core block).\"\"\"\n",
746
- "\n",
747
- " def __init__(self, token_side, **kwargs):\n",
748
- " super().__init__(**kwargs)\n",
749
- " self.token_side = token_side\n",
750
- "\n",
751
- " def build(self, input_shape):\n",
752
- " channels = int(input_shape[-1])\n",
753
- " self.w_real = self.add_weight(\n",
754
- " name='w_real', shape=(self.token_side, self.token_side, channels),\n",
755
- " initializer='glorot_uniform', trainable=True,\n",
756
- " )\n",
757
- " self.w_imag = self.add_weight(\n",
758
- " name='w_imag', shape=(self.token_side, self.token_side, channels),\n",
759
- " initializer='zeros', trainable=True,\n",
760
- " )\n",
761
- " super().build(input_shape)\n",
762
- "\n",
763
- " def call(self, x):\n",
764
- " batch = tf.shape(x)[0]\n",
765
- " channels = tf.shape(x)[-1]\n",
766
- " x_2d = tf.reshape(x, [batch, self.token_side, self.token_side, channels])\n",
767
- " x_fft = tf.signal.fft2d(tf.cast(x_2d, tf.complex64))\n",
768
- " w_complex = tf.complex(self.w_real, self.w_imag)\n",
769
- " x_filtered = x_fft * w_complex\n",
770
- " x_spatial = tf.math.real(tf.signal.ifft2d(x_filtered))\n",
771
- " return tf.reshape(x_spatial, [batch, self.token_side * self.token_side, channels])\n",
772
- "\n",
773
- " def get_config(self):\n",
774
- " cfg = super().get_config()\n",
775
- " cfg.update({'token_side': self.token_side})\n",
776
- " return cfg\n",
777
- "\n",
778
- "\n",
779
- "@tf.keras.utils.register_keras_serializable()\n",
780
- "class PatchEncoderWithCLS(layers.Layer):\n",
781
- " \"\"\"Projects patches to a hidden dim and prepends a trainable CLS token with positional embeddings.\"\"\"\n",
782
- "\n",
783
- " def __init__(self, num_patches, projection_dim, **kwargs):\n",
784
- " super().__init__(**kwargs)\n",
785
- " self.num_patches = num_patches\n",
786
- " self.projection_dim = projection_dim\n",
787
- " self.projection = layers.Dense(projection_dim)\n",
788
- " self.position_embedding = layers.Embedding(input_dim=num_patches + 1, output_dim=projection_dim)\n",
789
- "\n",
790
- " def build(self, input_shape):\n",
791
- " self.cls_token = self.add_weight(\n",
792
- " name='cls_token', shape=(1, 1, self.projection_dim),\n",
793
- " initializer='zeros', trainable=True,\n",
794
- " )\n",
795
- " super().build(input_shape)\n",
796
- "\n",
797
- " def call(self, patches):\n",
798
- " batch = tf.shape(patches)[0]\n",
799
- " patch_proj = self.projection(patches)\n",
800
- " cls_tokens = tf.repeat(self.cls_token, repeats=batch, axis=0)\n",
801
- " x = tf.concat([cls_tokens, patch_proj], axis=1)\n",
802
- " positions = tf.range(start=0, limit=self.num_patches + 1, delta=1)\n",
803
- " return x + self.position_embedding(positions)\n",
804
- "\n",
805
- " def get_config(self):\n",
806
- " cfg = super().get_config()\n",
807
- " cfg.update({'num_patches': self.num_patches, 'projection_dim': self.projection_dim})\n",
808
- " return cfg"
809
- ],
810
- "id": "-S-TKPEjjmfB"
811
- },
812
- {
813
- "cell_type": "markdown",
814
- "metadata": {
815
- "id": "7bVMnqfkjmfB"
816
- },
817
- "source": [
818
- "## 4.2 \u2014 AlexNet-Based CNN (Single-Head, Legacy Architecture)"
819
- ],
820
- "id": "7bVMnqfkjmfB"
821
- },
822
- {
823
- "cell_type": "code",
824
- "execution_count": null,
825
- "metadata": {
826
- "id": "bh2CxuNEjmfC",
827
- "executionInfo": {
828
- "status": "ok",
829
- "timestamp": 1782312434892,
830
- "user_tz": -330,
831
- "elapsed": 7,
832
- "user": {
833
- "displayName": "Danesh Selwal",
834
- "userId": "10565017814556927251"
835
- }
836
- }
837
- },
838
- "outputs": [],
839
- "source": [
840
- "def build_alexnet(input_shape, num_classes, dropout_rate=0.25, cfg=None):\n",
841
- " \"\"\"Build a legacy AlexNet-style CNN with configurable conv and dense layer sizes.\"\"\"\n",
842
- " cfg = cfg or ALEXNET_CFG\n",
843
- "\n",
844
- " inputs = keras.Input(shape=input_shape)\n",
845
- " x = inputs\n",
846
- "\n",
847
- " for i, filters in enumerate(cfg['conv_filters'], start=1):\n",
848
- " x = layers.Conv2D(filters, (3, 3), activation='relu', padding='same', name=f'alex_conv_{i}')(x)\n",
849
- "\n",
850
- " x = layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2), padding='same', name='alex_pool')(x)\n",
851
- "\n",
852
- " dense_units = cfg['dense_units']\n",
853
- " x = layers.Flatten(name='alex_flatten')(x)\n",
854
- " x = layers.Dense(dense_units[0], activation='relu', name='alex_fc1')(x)\n",
855
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_1')(x)\n",
856
- " x = layers.Dense(dense_units[1], activation='relu', name='alex_fc2')(x)\n",
857
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_2')(x)\n",
858
- " x = layers.Dense(dense_units[2], activation='relu', name='alex_fc3')(x)\n",
859
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_3')(x)\n",
860
- " x = layers.Dense(dense_units[3], activation='relu', name='alex_fc4')(x)\n",
861
- "\n",
862
- " outputs = layers.Dense(num_classes, activation='softmax', name='alex_logits')(x)\n",
863
- " return keras.Model(inputs, outputs, name='AlexNet_SingleHead')"
864
- ],
865
- "id": "bh2CxuNEjmfC"
866
- },
867
- {
868
- "cell_type": "markdown",
869
- "metadata": {
870
- "id": "wHEWTSBgjmfC"
871
- },
872
- "source": [
873
- "## 4.3 \u2014 Global Filter Network (Single-Head, Legacy Architecture)"
874
- ],
875
- "id": "wHEWTSBgjmfC"
876
- },
877
- {
878
- "cell_type": "code",
879
- "execution_count": null,
880
- "metadata": {
881
- "id": "BTSyxIIKjmfC",
882
- "executionInfo": {
883
- "status": "ok",
884
- "timestamp": 1782312434904,
885
- "user_tz": -330,
886
- "elapsed": 16,
887
- "user": {
888
- "displayName": "Danesh Selwal",
889
- "userId": "10565017814556927251"
890
- }
891
- }
892
- },
893
- "outputs": [],
894
- "source": [
895
- "def gf_block(x, token_side, dim, mlp_ratio=4, dropout_rate=0.25, name_prefix='gf'):\n",
896
- " \"\"\"Single GFNet block: global filter in frequency domain followed by a gated MLP, with residual.\"\"\"\n",
897
- " y = layers.LayerNormalization(name=f'{name_prefix}_ln1')(x)\n",
898
- " y = GlobalFilterLayer(token_side, name=f'{name_prefix}_gfilter')(y)\n",
899
- " y = layers.LayerNormalization(name=f'{name_prefix}_ln2')(y)\n",
900
- " y = layers.Dense(dim * mlp_ratio, activation=tf.keras.activations.gelu, name=f'{name_prefix}_mlp1')(y)\n",
901
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop1')(y)\n",
902
- " y = layers.Dense(dim, activation=tf.keras.activations.gelu, name=f'{name_prefix}_mlp2')(y)\n",
903
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop2')(y)\n",
904
- " return layers.Add(name=f'{name_prefix}_add')([x, y])\n",
905
- "\n",
906
- "\n",
907
- "def build_gfnet(input_shape, num_classes, inner_patch=3,\n",
908
- " hidden_dim=512, num_blocks=5, mlp_ratio=4, dropout_rate=0.25):\n",
909
- " \"\"\"Build a Global Filter Network with configurable depth and width.\"\"\"\n",
910
- " num_patches = (input_shape[0] // inner_patch) * (input_shape[1] // inner_patch)\n",
911
- " token_side = int(np.sqrt(num_patches))\n",
912
- "\n",
913
- " inputs = keras.Input(shape=input_shape)\n",
914
- " x = PatchExtractor(inner_patch, name='gf_patch_extractor')(inputs)\n",
915
- " x = PatchPositionEncoder(num_patches, hidden_dim, name='gf_patch_encoder')(x)\n",
916
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_1')(x)\n",
917
- "\n",
918
- " for i in range(num_blocks):\n",
919
- " x = gf_block(x, token_side, hidden_dim, mlp_ratio, dropout_rate,\n",
920
- " name_prefix=f'gf_block_{i+1}')\n",
921
- "\n",
922
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_2')(x)\n",
923
- " x = layers.LayerNormalization(name='gf_final_ln')(x)\n",
924
- " x = layers.GlobalAveragePooling1D(name='gf_gap')(x)\n",
925
- " x = layers.Flatten(name='gf_flatten')(x)\n",
926
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_3')(x)\n",
927
- " outputs = layers.Dense(num_classes, activation='softmax', name='gf_logits')(x)\n",
928
- "\n",
929
- " return keras.Model(inputs, outputs, name='GFNet_SingleHead')"
930
- ],
931
- "id": "BTSyxIIKjmfC"
932
- },
933
- {
934
- "cell_type": "markdown",
935
- "metadata": {
936
- "id": "vakSvbuAjmfC"
937
- },
938
- "source": [
939
- "## 4.4 \u2014 Vision Transformer with U-Net Skip Connections (Single-Head)"
940
- ],
941
- "id": "vakSvbuAjmfC"
942
- },
943
- {
944
- "cell_type": "code",
945
- "execution_count": null,
946
- "metadata": {
947
- "id": "MHgOzlppjmfD",
948
- "executionInfo": {
949
- "status": "ok",
950
- "timestamp": 1782312434906,
951
- "user_tz": -330,
952
- "elapsed": 16,
953
- "user": {
954
- "displayName": "Danesh Selwal",
955
- "userId": "10565017814556927251"
956
- }
957
- }
958
- },
959
- "outputs": [],
960
- "source": [
961
- "def transformer_block(x, num_heads, projection_dim, mlp_dim, dropout_rate, name_prefix):\n",
962
- " \"\"\"Standard transformer block: MHSA + FFN, each with pre-LN and residual connection.\"\"\"\n",
963
- " y = layers.LayerNormalization(epsilon=1e-6, name=f'{name_prefix}_ln1')(x)\n",
964
- " y = layers.MultiHeadAttention(\n",
965
- " num_heads=num_heads, key_dim=projection_dim,\n",
966
- " dropout=dropout_rate, name=f'{name_prefix}_mha',\n",
967
- " )(y, y)\n",
968
- " x = layers.Add(name=f'{name_prefix}_add1')([y, x])\n",
969
- "\n",
970
- " y = layers.LayerNormalization(epsilon=1e-6, name=f'{name_prefix}_ln2')(x)\n",
971
- " y = layers.Dense(mlp_dim, activation=tf.keras.activations.gelu, name=f'{name_prefix}_mlp1')(y)\n",
972
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop1')(y)\n",
973
- " y = layers.Dense(projection_dim, activation=tf.keras.activations.gelu, name=f'{name_prefix}_mlp2')(y)\n",
974
- " y = layers.Dropout(dropout_rate, name=f'{name_prefix}_drop2')(y)\n",
975
- " return layers.Add(name=f'{name_prefix}_add2')([y, x])\n",
976
- "\n",
977
- "\n",
978
- "def build_vit_unet_singlehead(\n",
979
- " input_shape, num_classes, inner_patch=3,\n",
980
- " projection_dim=256, num_heads=4, transformer_layers=12,\n",
981
- " mlp_multiplier=2, dropout_rate=0.25, head_units=(512, 256, 128, 64),\n",
982
- "):\n",
983
- " \"\"\"Build a ViT with U-Net-style symmetric skip connections between encoder and decoder blocks.\"\"\"\n",
984
- " num_patches = (input_shape[0] // inner_patch) * (input_shape[1] // inner_patch)\n",
985
- "\n",
986
- " inputs = keras.Input(shape=input_shape)\n",
987
- " x = PatchExtractor(inner_patch, name='vit_patch_extractor')(inputs)\n",
988
- " x = PatchEncoderWithCLS(num_patches, projection_dim, name='vit_patch_encoder')(x)\n",
989
- "\n",
990
- " block_list = []\n",
991
- " for i in range(transformer_layers):\n",
992
- " x = transformer_block(\n",
993
- " x, num_heads=num_heads, projection_dim=projection_dim,\n",
994
- " mlp_dim=projection_dim * mlp_multiplier, dropout_rate=0.1,\n",
995
- " name_prefix=f'vit_block_{i+1}',\n",
996
- " )\n",
997
- " # Save encoder-side outputs for skip connections\n",
998
- " if i <= transformer_layers // 2:\n",
999
- " block_list.append(x)\n",
1000
- " else:\n",
1001
- " x = layers.Add(name=f'vit_skip_add_{i+1}')([x, block_list[transformer_layers - i - 1]])\n",
1002
- "\n",
1003
- " x = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_1')(x)\n",
1004
- " x = layers.LayerNormalization(epsilon=1e-6, name='vit_cls_norm')(x)\n",
1005
- " cls_token = layers.Lambda(lambda t: t[:, 0, :], name='vit_cls_token')(x)\n",
1006
- "\n",
1007
- " y = layers.Dense(head_units[0], activation=tf.keras.activations.gelu, name='vit_head_1')(cls_token)\n",
1008
- " y = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_3')(y)\n",
1009
- " y = layers.Dense(head_units[1], activation=tf.keras.activations.gelu, name='vit_head_2')(y)\n",
1010
- " y = layers.Dense(head_units[2], activation=tf.keras.activations.gelu, name='vit_head_3')(y)\n",
1011
- " y = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_5')(y)\n",
1012
- " y = layers.Dense(head_units[3], activation=tf.keras.activations.gelu, name='vit_head_4')(y)\n",
1013
- " y = layers.Dropout(dropout_rate, name='TRAIN_DROPOUT_6')(y)\n",
1014
- "\n",
1015
- " outputs = layers.Dense(num_classes, activation='softmax', name='vit_logits')(y)\n",
1016
- " return keras.Model(inputs, outputs, name='ViT_UNet_SingleHead')"
1017
- ],
1018
- "id": "MHgOzlppjmfD"
1019
- },
1020
- {
1021
- "cell_type": "markdown",
1022
- "metadata": {
1023
- "id": "NKYV1hDsjmfD"
1024
- },
1025
- "source": [
1026
- "# 5.0 \u2014 Training & Evaluation Helpers\n",
1027
- "\n",
1028
- "Utility functions for calibration metrics (Brier score, ECE), the shared AdamW/cosine-decay optimiser factory, and the AlexNet legacy cosine LR scheduler."
1029
- ],
1030
- "id": "NKYV1hDsjmfD"
1031
- },
1032
- {
1033
- "cell_type": "code",
1034
- "execution_count": null,
1035
- "metadata": {
1036
- "id": "nITKN2ZUjmfD",
1037
- "executionInfo": {
1038
- "status": "ok",
1039
- "timestamp": 1782312434913,
1040
- "user_tz": -330,
1041
- "elapsed": 5,
1042
- "user": {
1043
- "displayName": "Danesh Selwal",
1044
- "userId": "10565017814556927251"
1045
- }
1046
- }
1047
- },
1048
- "outputs": [],
1049
- "source": [
1050
- "def multiclass_brier_score(y_onehot, y_prob):\n",
1051
- " \"\"\"Compute the multiclass Brier score (mean squared error over probability vectors).\"\"\"\n",
1052
- " return float(np.mean(np.sum((y_prob - y_onehot) ** 2, axis=1)))\n",
1053
- "\n",
1054
- "\n",
1055
- "def expected_calibration_error(y_true, y_prob, n_bins=15):\n",
1056
- " \"\"\"Compute Expected Calibration Error (ECE) using equal-width confidence bins.\"\"\"\n",
1057
- " confidences = np.max(y_prob, axis=1)\n",
1058
- " predictions = np.argmax(y_prob, axis=1)\n",
1059
- " correct = (predictions == y_true).astype(np.float32)\n",
1060
- "\n",
1061
- " bin_edges = np.linspace(0.0, 1.0, n_bins + 1)\n",
1062
- " ece = 0.0\n",
1063
- " for i in range(n_bins):\n",
1064
- " lo, hi = bin_edges[i], bin_edges[i + 1]\n",
1065
- " in_bin = (confidences >= lo) & (confidences <= hi if i == n_bins - 1 else confidences < hi)\n",
1066
- " prop = np.mean(in_bin)\n",
1067
- " if prop > 0:\n",
1068
- " acc_bin = np.mean(correct[in_bin])\n",
1069
- " conf_bin = np.mean(confidences[in_bin])\n",
1070
- " ece += np.abs(acc_bin - conf_bin) * prop\n",
1071
- " return float(ece)\n",
1072
- "\n",
1073
- "\n",
1074
- "def make_optimizer(num_train_samples):\n",
1075
- " \"\"\"Create an AdamW optimiser with cosine-decay learning rate schedule.\"\"\"\n",
1076
- " steps_per_epoch = int(np.ceil(num_train_samples / BATCH_SIZE))\n",
1077
- " decay_steps = max(1, steps_per_epoch * EPOCHS)\n",
1078
- " lr_schedule = keras.optimizers.schedules.CosineDecay(\n",
1079
- " initial_learning_rate=LEARNING_RATE,\n",
1080
- " decay_steps=decay_steps,\n",
1081
- " alpha=TRAIN_CFG['cosine_alpha'],\n",
1082
- " )\n",
1083
- " return keras.optimizers.AdamW(\n",
1084
- " learning_rate=lr_schedule,\n",
1085
- " weight_decay=TRAIN_CFG['weight_decay'],\n",
1086
- " clipnorm=TRAIN_CFG['clipnorm'],\n",
1087
- " )\n",
1088
- "\n",
1089
- "\n",
1090
- "def _alexnet_legacy_lr(epoch):\n",
1091
- " \"\"\"Cosine LR schedule between ALEXNET_LR_MIN and ALEXNET_LR_MAX (AlexNet legacy recipe).\"\"\"\n",
1092
- " if EPOCHS <= 1:\n",
1093
- " return ALEXNET_LR_START\n",
1094
- " phase = np.pi * epoch / (EPOCHS - 1)\n",
1095
- " cosine_decay = 0.5 * (1.0 + np.cos(phase))\n",
1096
- " return float((ALEXNET_LR_MAX - ALEXNET_LR_MIN) * cosine_decay + ALEXNET_LR_MIN)"
1097
- ],
1098
- "id": "nITKN2ZUjmfD"
1099
- },
1100
- {
1101
- "cell_type": "markdown",
1102
- "metadata": {
1103
- "id": "hPJBPlZTjmfE"
1104
- },
1105
- "source": [
1106
- "# 6.0 \u2014 Ensemble Training (M=5)\n",
1107
- "\n",
1108
- "Trains M=5 independently seeded copies of each selected architecture. Models are first saved locally to avoid Drive I/O crashes mid-run, then copied to Google Drive. Evaluation metrics are collected per ensemble member."
1109
- ],
1110
- "id": "hPJBPlZTjmfE"
1111
- },
1112
- {
1113
- "cell_type": "markdown",
1114
- "metadata": {
1115
- "id": "ntscv9v2jmfE"
1116
- },
1117
- "source": [
1118
- "## 6.1 \u2014 Builder Wrappers & Directory Setup"
1119
- ],
1120
- "id": "ntscv9v2jmfE"
1121
- },
1122
- {
1123
- "cell_type": "code",
1124
- "execution_count": null,
1125
- "metadata": {
1126
- "id": "vt8pou-hjmfE",
1127
- "executionInfo": {
1128
- "status": "ok",
1129
- "timestamp": 1782312434956,
1130
- "user_tz": -330,
1131
- "elapsed": 40,
1132
- "user": {
1133
- "displayName": "Danesh Selwal",
1134
- "userId": "10565017814556927251"
1135
- }
1136
- }
1137
- },
1138
- "outputs": [],
1139
- "source": [
1140
- "import shutil\n",
1141
- "\n",
1142
- "def build_gfnet_with_cfg(cfg):\n",
1143
- " \"\"\"Instantiate GFNet from a config dict.\"\"\"\n",
1144
- " return build_gfnet(\n",
1145
- " input_shape, num_classes, inner_patch=INNER_PATCH,\n",
1146
- " hidden_dim=cfg['hidden_dim'], num_blocks=cfg['num_blocks'],\n",
1147
- " mlp_ratio=cfg['mlp_ratio'], dropout_rate=DROPOUT_RATE,\n",
1148
- " )\n",
1149
- "\n",
1150
- "\n",
1151
- "def build_vit_with_cfg(cfg):\n",
1152
- " \"\"\"Instantiate ViT-UNet from a config dict.\"\"\"\n",
1153
- " return build_vit_unet_singlehead(\n",
1154
- " input_shape, num_classes, inner_patch=INNER_PATCH,\n",
1155
- " projection_dim=cfg['projection_dim'], num_heads=cfg['num_heads'],\n",
1156
- " transformer_layers=cfg['transformer_layers'], mlp_multiplier=cfg['mlp_multiplier'],\n",
1157
- " dropout_rate=DROPOUT_RATE, head_units=tuple(cfg['head_units']),\n",
1158
- " )\n",
1159
- "\n",
1160
- "\n",
1161
- "model_builders = {\n",
1162
- " 'AlexNet_CNN': lambda: build_alexnet(input_shape, num_classes,\n",
1163
- " dropout_rate=DROPOUT_RATE, cfg=ALEXNET_CFG),\n",
1164
- " 'GFNet': lambda: build_gfnet_with_cfg(GFNET_CFG),\n",
1165
- " 'ViT_UNet': lambda: build_vit_with_cfg(VIT_CFG),\n",
1166
- "}\n",
1167
- "\n",
1168
- "# Local save directory (prevents Drive crash during training)\n",
1169
- "local_dir = Path('/content/local_models')\n",
1170
- "local_dir.mkdir(parents=True, exist_ok=True)\n",
1171
- "\n",
1172
- "# Google Drive destination for final copies\n",
1173
- "ensemble_dir = MODEL_DIR / 'ensembles'\n",
1174
- "ensemble_dir.mkdir(parents=True, exist_ok=True)"
1175
- ],
1176
- "id": "vt8pou-hjmfE"
1177
- },
1178
- {
1179
- "cell_type": "markdown",
1180
- "metadata": {
1181
- "id": "YTiTctJVjmfF"
1182
- },
1183
- "source": [
1184
- "## 6.2 \u2014 Ensemble Training Loop\n",
1185
- "\n",
1186
- "Iterates over selected architectures, training M=5 members each with a distinct seed. Uses fallback configs when a ResourceExhaustedError is raised. All weights are saved locally first, then synced to Drive."
1187
- ],
1188
- "id": "YTiTctJVjmfF"
1189
- },
1190
- {
1191
- "cell_type": "code",
1192
- "execution_count": null,
1193
- "metadata": {
1194
- "colab": {
1195
- "base_uri": "https://localhost:8080/",
1196
- "height": 0
1197
- },
1198
- "id": "gdbH48OBjmfH",
1199
- "outputId": "1c8e662c-9560-47c4-db23-62cca0edd2f9",
1200
- "executionInfo": {
1201
- "status": "ok",
1202
- "timestamp": 1782320195390,
1203
- "user_tz": -330,
1204
- "elapsed": 7760436,
1205
- "user": {
1206
- "displayName": "Danesh Selwal",
1207
- "userId": "10565017814556927251"
1208
- }
1209
- }
1210
- },
1211
- "outputs": [],
1212
- "source": [
1213
- "M = 5\n",
1214
- "results_rows = []\n",
1215
- "model_artifacts = {}\n",
1216
- "\n",
1217
- "for model_name, builder in model_builders.items():\n",
1218
- " if model_name != \"ViT_UNet\": # \ud83d\udd25 change this to the one you want\n",
1219
- " continue\n",
1220
- " print(f\"\\n{'=' * 25} Training {model_name} Ensembles (M={M}) {'=' * 25}\")\n",
1221
- "\n",
1222
- " for i in range(1, M + 1):\n",
1223
- " print(f\"\\n--- {model_name} Ensemble Model {i}/{M} ---\")\n",
1224
- "\n",
1225
- " # Per-member seed\n",
1226
- " seed_val = 42 + i\n",
1227
- " tf.random.set_seed(seed_val)\n",
1228
- " np.random.seed(seed_val)\n",
1229
- " random.seed(seed_val)\n",
1230
- "\n",
1231
- " tf.keras.backend.clear_session()\n",
1232
- "\n",
1233
- " # Build model \u2014 fall back to smaller config on OOM\n",
1234
- " try:\n",
1235
- " model = builder()\n",
1236
- " capacity_tag = CAPACITY_PRESET\n",
1237
- " except tf.errors.ResourceExhaustedError:\n",
1238
- " if model_name == 'GFNet':\n",
1239
- " model = build_gfnet_with_cfg(GFNET_FALLBACK_CFG)\n",
1240
- " capacity_tag = 'fallback'\n",
1241
- " elif model_name == 'ViT_UNet':\n",
1242
- " model = build_vit_with_cfg(VIT_FALLBACK_CFG)\n",
1243
- " capacity_tag = 'fallback'\n",
1244
- " else:\n",
1245
- " raise\n",
1246
- "\n",
1247
- " # Local checkpoint paths\n",
1248
- " best_path = local_dir / f'{model_name}_ens_{i}_best.weights.h5'\n",
1249
- " final_path = local_dir / f'{model_name}_ens_{i}_final.keras'\n",
1250
- "\n",
1251
- " # Compile and set up training data / callbacks per architecture\n",
1252
- " if model_name == 'AlexNet_CNN':\n",
1253
- " model.compile(\n",
1254
- " optimizer=keras.optimizers.Adagrad(learning_rate=ALEXNET_LR_START),\n",
1255
- " loss='sparse_categorical_crossentropy',\n",
1256
- " metrics=['accuracy'],\n",
1257
- " )\n",
1258
- " callbacks = [\n",
1259
- " keras.callbacks.ModelCheckpoint(\n",
1260
- " filepath=str(best_path), monitor='val_accuracy', mode='max',\n",
1261
- " save_best_only=True, save_weights_only=True, verbose=1,\n",
1262
- " ),\n",
1263
- " keras.callbacks.LearningRateScheduler(_alexnet_legacy_lr, verbose=0),\n",
1264
- " ]\n",
1265
- " x_tr, y_tr = x_train_alex, y_train_alex\n",
1266
- " x_va, y_va = x_test_alex, y_test_alex\n",
1267
- " x_te, y_te, y_te_cat = x_test_alex, y_test_alex, y_test_alex_cat\n",
1268
- " x_eval_for_metrics, y_eval_for_metrics, y_eval_for_metrics_cat = (\n",
1269
- " x_test_alex, y_test_alex, y_test_alex_cat\n",
1270
- " )\n",
1271
- " fit_shuffle = False\n",
1272
- "\n",
1273
- " else:\n",
1274
- " model.compile(\n",
1275
- " optimizer=make_optimizer(len(x_train)),\n",
1276
- " loss=keras.losses.CategoricalCrossentropy(label_smoothing=TRAIN_CFG['label_smoothing']),\n",
1277
- " metrics=['accuracy'],\n",
1278
- " )\n",
1279
- " callbacks = [\n",
1280
- " keras.callbacks.ModelCheckpoint(\n",
1281
- " filepath=str(best_path), monitor='val_loss', mode='min',\n",
1282
- " save_best_only=True, save_weights_only=True, verbose=1,\n",
1283
- " ),\n",
1284
- " ]\n",
1285
- " x_tr, y_tr = x_train, y_train_cat\n",
1286
- " x_va, y_va = x_val, y_val_cat\n",
1287
- " x_te, y_te, y_te_cat = x_test, y_test, y_test_cat\n",
1288
- " x_eval_for_metrics, y_eval_for_metrics, y_eval_for_metrics_cat = (\n",
1289
- " x_val, y_val, y_val_cat\n",
1290
- " )\n",
1291
- " fit_shuffle = True\n",
1292
- "\n",
1293
- " # Train\n",
1294
- " train_start = time.perf_counter()\n",
1295
- " history_obj = model.fit(\n",
1296
- " x_tr, y_tr,\n",
1297
- " validation_data=(x_va, y_va),\n",
1298
- " epochs=EPOCHS, batch_size=BATCH_SIZE,\n",
1299
- " callbacks=callbacks, verbose=1, shuffle=fit_shuffle,\n",
1300
- " )\n",
1301
- " train_time_sec = float(time.perf_counter() - train_start)\n",
1302
- "\n",
1303
- " # Save full model locally, then copy to Drive\n",
1304
- " model.save(final_path)\n",
1305
- " shutil.copy(best_path, ensemble_dir / best_path.name)\n",
1306
- " shutil.copy(final_path, ensemble_dir / final_path.name)\n",
1307
- "\n",
1308
- " # Evaluate\n",
1309
- " y_eval_prob = model.predict(x_eval_for_metrics, batch_size=BATCH_SIZE, verbose=0)\n",
1310
- " y_test_prob = model.predict(x_te, batch_size=BATCH_SIZE, verbose=0)\n",
1311
- " y_eval_pred = np.argmax(y_eval_prob, axis=1)\n",
1312
- " y_test_pred = np.argmax(y_test_prob, axis=1)\n",
1313
- "\n",
1314
- " report = classification_report(y_te, y_test_pred, output_dict=True, zero_division=0)\n",
1315
- " cm = confusion_matrix(y_te, y_test_pred)\n",
1316
- "\n",
1317
- " row = {\n",
1318
- " 'model': f'{model_name}_ens_{i}',\n",
1319
- " 'capacity_tag': capacity_tag,\n",
1320
- " 'test_accuracy': float(accuracy_score(y_te, y_test_pred)),\n",
1321
- " 'kappa': float(cohen_kappa_score(y_te, y_test_pred)),\n",
1322
- " 'macro_f1': float(f1_score(y_te, y_test_pred, average='macro')),\n",
1323
- " 'weighted_f1': float(f1_score(y_te, y_test_pred, average='weighted')),\n",
1324
- " 'val_nll': float(log_loss(y_eval_for_metrics, y_eval_prob, labels=np.arange(num_classes))),\n",
1325
- " 'test_nll': float(log_loss(y_te, y_test_prob, labels=np.arange(num_classes))),\n",
1326
- " 'val_brier': multiclass_brier_score(y_eval_for_metrics_cat, y_eval_prob),\n",
1327
- " 'test_brier': multiclass_brier_score(y_te_cat, y_test_prob),\n",
1328
- " 'test_ece_15bin': expected_calibration_error(y_te, y_test_prob, n_bins=15),\n",
1329
- " 'epochs_configured': int(EPOCHS),\n",
1330
- " 'epochs_ran': int(len(history_obj.history.get('loss', []))),\n",
1331
- " 'train_time_sec': train_time_sec,\n",
1332
- " 'best_model_path': str(ensemble_dir / best_path.name),\n",
1333
- " 'final_model_path': str(ensemble_dir / final_path.name),\n",
1334
- " }\n",
1335
- " results_rows.append(row)\n",
1336
- "\n",
1337
- " # Store last member's artifacts for plotting\n",
1338
- " if i == M:\n",
1339
- " model_artifacts[model_name] = {\n",
1340
- " 'report': report,\n",
1341
- " 'confusion_matrix': cm,\n",
1342
- " 'history': history_obj.history,\n",
1343
- " }\n",
1344
- "\n",
1345
- "summary_df = (pd.DataFrame(results_rows)\n",
1346
- " .sort_values('test_accuracy', ascending=False)\n",
1347
- " .reset_index(drop=True))\n",
1348
- "summary_df"
1349
- ],
1350
- "id": "gdbH48OBjmfH"
1351
- },
1352
- {
1353
- "cell_type": "markdown",
1354
- "metadata": {
1355
- "id": "KG2bOKWtjmfI"
1356
- },
1357
- "source": [
1358
- "# 7.0 \u2014 Results & Analysis\n",
1359
- "\n",
1360
- "Saves the classification summary CSV, plots training curves, cross-model metric comparisons, calibration proxy charts, and per-model confusion matrices."
1361
- ],
1362
- "id": "KG2bOKWtjmfI"
1363
- },
1364
- {
1365
- "cell_type": "markdown",
1366
- "metadata": {
1367
- "id": "-0Pc8L9tjmfI"
1368
- },
1369
- "source": [
1370
- "## 7.1 \u2014 Save Summary CSV & Per-Model Reports"
1371
- ],
1372
- "id": "-0Pc8L9tjmfI"
1373
- },
1374
- {
1375
- "cell_type": "code",
1376
- "execution_count": null,
1377
- "metadata": {
1378
- "id": "r35TTpYcjmfI",
1379
- "colab": {
1380
- "base_uri": "https://localhost:8080/",
1381
- "height": 0
1382
- },
1383
- "executionInfo": {
1384
- "status": "ok",
1385
- "timestamp": 1782320195423,
1386
- "user_tz": -330,
1387
- "elapsed": 39,
1388
- "user": {
1389
- "displayName": "Danesh Selwal",
1390
- "userId": "10565017814556927251"
1391
- }
1392
- },
1393
- "outputId": "ed478e45-b977-41f3-c062-d21333c93139"
1394
- },
1395
- "outputs": [],
1396
- "source": [
1397
- "summary_path = MODEL_DIR / 'classification_summary.csv'\n",
1398
- "summary_df.to_csv(summary_path, index=False)\n",
1399
- "print('Saved summary:', summary_path)\n",
1400
- "\n",
1401
- "for model_name, artifact in model_artifacts.items():\n",
1402
- " report_path = MODEL_DIR / f'{model_name}_classification_report.json'\n",
1403
- " with open(report_path, 'w', encoding='utf-8') as f:\n",
1404
- " json.dump(artifact['report'], f, indent=2)\n",
1405
- "\n",
1406
- "print('Saved per-model classification reports in:', MODEL_DIR)\n",
1407
- "summary_df"
1408
- ],
1409
- "id": "r35TTpYcjmfI"
1410
- },
1411
- {
1412
- "cell_type": "markdown",
1413
- "metadata": {
1414
- "id": "-GFcXWgLjmfI"
1415
- },
1416
- "source": [
1417
- "## 7.2 \u2014 Training Curves (Accuracy & Loss)\n",
1418
- "\n",
1419
- "Plots dual-axis accuracy/loss curves for each trained model and saves them to `PLOT_DIR`."
1420
- ],
1421
- "id": "-GFcXWgLjmfI"
1422
- },
1423
- {
1424
- "cell_type": "code",
1425
- "execution_count": null,
1426
- "metadata": {
1427
- "id": "IfLhfCT5jmfI",
1428
- "colab": {
1429
- "base_uri": "https://localhost:8080/",
1430
- "height": 0
1431
- },
1432
- "executionInfo": {
1433
- "status": "ok",
1434
- "timestamp": 1782320196178,
1435
- "user_tz": -330,
1436
- "elapsed": 753,
1437
- "user": {
1438
- "displayName": "Danesh Selwal",
1439
- "userId": "10565017814556927251"
1440
- }
1441
- },
1442
- "outputId": "48859585-392c-4b10-d968-90688b0659b7"
1443
- },
1444
- "outputs": [],
1445
- "source": [
1446
- "for model_name, artifact in model_artifacts.items():\n",
1447
- " hist = artifact['history']\n",
1448
- " epochs = np.arange(1, len(hist['loss']) + 1)\n",
1449
- "\n",
1450
- " fig, ax1 = plt.subplots(figsize=(12, 5))\n",
1451
- " ax1.plot(epochs, hist['accuracy'], color='#1f77b4', linewidth=2, label='Train Accuracy')\n",
1452
- " ax1.plot(epochs, hist['val_accuracy'], color='#2ca02c', linewidth=2, linestyle='--', label='Val Accuracy')\n",
1453
- " ax1.set_xlabel('Epoch')\n",
1454
- " ax1.set_ylabel('Accuracy')\n",
1455
- " ax1.set_ylim(0, 1.0)\n",
1456
- " ax1.grid(True, alpha=0.25)\n",
1457
- "\n",
1458
- " ax2 = ax1.twinx()\n",
1459
- " ax2.plot(epochs, hist['loss'], color='#111111', linewidth=1.8, label='Train Loss')\n",
1460
- " ax2.plot(epochs, hist['val_loss'], color='#d62728', linewidth=1.8, linestyle='--', label='Val Loss')\n",
1461
- " ax2.set_ylabel('Loss')\n",
1462
- "\n",
1463
- " lines = ax1.get_lines() + ax2.get_lines()\n",
1464
- " labels = [ln.get_label() for ln in lines]\n",
1465
- " ax1.legend(lines, labels, loc='upper right', frameon=True)\n",
1466
- " plt.title(f'{model_name}: Accuracy/Loss vs Epoch')\n",
1467
- " plt.tight_layout()\n",
1468
- "\n",
1469
- " curve_path = PLOT_DIR / f'{model_name}_training_curves.png'\n",
1470
- " plt.savefig(curve_path, dpi=200, bbox_inches='tight')\n",
1471
- " plt.show()"
1472
- ],
1473
- "id": "IfLhfCT5jmfI"
1474
- },
1475
- {
1476
- "cell_type": "markdown",
1477
- "metadata": {
1478
- "id": "yKCm6__LjmfJ"
1479
- },
1480
- "source": [
1481
- "## 7.3 \u2014 Cross-Model Metric Comparison Charts"
1482
- ],
1483
- "id": "yKCm6__LjmfJ"
1484
- },
1485
- {
1486
- "cell_type": "code",
1487
- "execution_count": null,
1488
- "metadata": {
1489
- "id": "v-trp7mFjmfJ",
1490
- "colab": {
1491
- "base_uri": "https://localhost:8080/",
1492
- "height": 0
1493
- },
1494
- "executionInfo": {
1495
- "status": "ok",
1496
- "timestamp": 1782320197433,
1497
- "user_tz": -330,
1498
- "elapsed": 1246,
1499
- "user": {
1500
- "displayName": "Danesh Selwal",
1501
- "userId": "10565017814556927251"
1502
- }
1503
- },
1504
- "outputId": "89265a0d-3b7c-405a-a94e-2c698a78b988"
1505
- },
1506
- "outputs": [],
1507
- "source": [
1508
- "plot_df = summary_df[[\n",
1509
- " 'model', 'test_accuracy', 'macro_f1', 'kappa',\n",
1510
- " 'train_time_sec', 'test_nll', 'test_brier', 'test_ece_15bin',\n",
1511
- "]].copy()\n",
1512
- "\n",
1513
- "fig, axes = plt.subplots(2, 2, figsize=(15, 9))\n",
1514
- "axes = axes.flatten()\n",
1515
- "palette = ['#4c72b0', '#55a868', '#c44e52']\n",
1516
- "\n",
1517
- "metric_specs = [\n",
1518
- " ('test_accuracy', 'Test Accuracy', (0.0, 1.0), '{:.3f}'),\n",
1519
- " ('macro_f1', 'Macro F1', (0.0, 1.0), '{:.3f}'),\n",
1520
- " ('kappa', 'Cohen Kappa', (0.0, 1.0), '{:.3f}'),\n",
1521
- " ('train_time_sec', 'Train Time (sec)', None, '{:.1f}'),\n",
1522
- "]\n",
1523
- "\n",
1524
- "for ax, (col, title, ylim, fmt) in zip(axes, metric_specs):\n",
1525
- " local = plot_df[['model', col]].copy()\n",
1526
- " bars = ax.bar(local['model'], local[col], color=palette[:len(local)])\n",
1527
- " ax.set_title(title)\n",
1528
- " ax.set_xlabel('Model')\n",
1529
- " ax.set_ylabel(title)\n",
1530
- " if ylim is not None:\n",
1531
- " ax.set_ylim(*ylim)\n",
1532
- " ax.grid(axis='y', alpha=0.25)\n",
1533
- " for patch in bars:\n",
1534
- " height = patch.get_height()\n",
1535
- " x = patch.get_x() + patch.get_width() / 2\n",
1536
- " offset = (ylim[1] - ylim[0]) * 0.02 if ylim else max(local[col].max() * 0.02, 0.05)\n",
1537
- " ax.text(x, height + offset, fmt.format(height), ha='center', va='bottom', fontsize=9)\n",
1538
- "\n",
1539
- "plt.suptitle('Single-Head Model Comparison', y=1.02, fontsize=14)\n",
1540
- "plt.tight_layout()\n",
1541
- "comparison_path = PLOT_DIR / 'model_comparison_metrics.png'\n",
1542
- "plt.savefig(comparison_path, dpi=200, bbox_inches='tight')\n",
1543
- "plt.show()"
1544
- ],
1545
- "id": "v-trp7mFjmfJ"
1546
- },
1547
- {
1548
- "cell_type": "markdown",
1549
- "metadata": {
1550
- "id": "NUGaljEajmfJ"
1551
- },
1552
- "source": [
1553
- "## 7.4 \u2014 Calibration Proxy Metrics Chart"
1554
- ],
1555
- "id": "NUGaljEajmfJ"
1556
- },
1557
- {
1558
- "cell_type": "code",
1559
- "execution_count": null,
1560
- "metadata": {
1561
- "id": "c5vVc1AdjmfJ",
1562
- "colab": {
1563
- "base_uri": "https://localhost:8080/",
1564
- "height": 0
1565
- },
1566
- "executionInfo": {
1567
- "status": "ok",
1568
- "timestamp": 1782320197892,
1569
- "user_tz": -330,
1570
- "elapsed": 458,
1571
- "user": {
1572
- "displayName": "Danesh Selwal",
1573
- "userId": "10565017814556927251"
1574
- }
1575
- },
1576
- "outputId": "2cf65229-f99c-45b3-c949-b6fa1424f477"
1577
- },
1578
- "outputs": [],
1579
- "source": [
1580
- "fig, ax = plt.subplots(figsize=(12, 5))\n",
1581
- "calib_cols = ['test_nll', 'test_brier', 'test_ece_15bin']\n",
1582
- "calib_df = plot_df[['model'] + calib_cols].set_index('model')\n",
1583
- "calib_df.plot(kind='bar', ax=ax, rot=0)\n",
1584
- "ax.set_title('Uncertainty Proxy Metrics (Lower is Better)')\n",
1585
- "ax.set_ylabel('Metric Value')\n",
1586
- "ax.grid(axis='y', alpha=0.25)\n",
1587
- "plt.tight_layout()\n",
1588
- "calib_path = PLOT_DIR / 'uncertainty_proxy_metrics.png'\n",
1589
- "plt.savefig(calib_path, dpi=200, bbox_inches='tight')\n",
1590
- "plt.show()\n",
1591
- "print('Saved training comparison plots to:', PLOT_DIR)"
1592
- ],
1593
- "id": "c5vVc1AdjmfJ"
1594
- },
1595
- {
1596
- "cell_type": "markdown",
1597
- "metadata": {
1598
- "id": "Do2l3cYbjmfJ"
1599
- },
1600
- "source": [
1601
- "## 7.5 \u2014 Confusion Matrices"
1602
- ],
1603
- "id": "Do2l3cYbjmfJ"
1604
- },
1605
- {
1606
- "cell_type": "code",
1607
- "execution_count": null,
1608
- "metadata": {
1609
- "id": "pIJ1f7ZMjmfK",
1610
- "colab": {
1611
- "base_uri": "https://localhost:8080/",
1612
- "height": 0
1613
- },
1614
- "executionInfo": {
1615
- "status": "ok",
1616
- "timestamp": 1782320198402,
1617
- "user_tz": -330,
1618
- "elapsed": 505,
1619
- "user": {
1620
- "displayName": "Danesh Selwal",
1621
- "userId": "10565017814556927251"
1622
- }
1623
- },
1624
- "outputId": "68c7e96e-d1b4-4dcd-c23c-ea8b4294b8a0"
1625
- },
1626
- "outputs": [],
1627
- "source": [
1628
- "class_ticks = [str(i + 1) for i in range(num_classes)]\n",
1629
- "fig, axes = plt.subplots(1, len(model_artifacts), figsize=(7 * len(model_artifacts), 5.5))\n",
1630
- "\n",
1631
- "if len(model_artifacts) == 1:\n",
1632
- " axes = [axes]\n",
1633
- "\n",
1634
- "for ax, (model_name, artifact) in zip(axes, model_artifacts.items()):\n",
1635
- " sns.heatmap(\n",
1636
- " artifact['confusion_matrix'], annot=True, fmt='d', cmap='Blues',\n",
1637
- " xticklabels=class_ticks, yticklabels=class_ticks, cbar=False, ax=ax,\n",
1638
- " )\n",
1639
- " ax.set_title(f'{model_name} Confusion Matrix')\n",
1640
- " ax.set_xlabel('Predicted')\n",
1641
- " ax.set_ylabel('True')\n",
1642
- "\n",
1643
- "plt.tight_layout()\n",
1644
- "cm_path = PLOT_DIR / 'confusion_matrices_side_by_side.png'\n",
1645
- "plt.savefig(cm_path, dpi=220, bbox_inches='tight')\n",
1646
- "plt.show()\n",
1647
- "print('Saved confusion matrix figure:', cm_path)"
1648
- ],
1649
- "id": "pIJ1f7ZMjmfK"
1650
- },
1651
- {
1652
- "cell_type": "markdown",
1653
- "metadata": {
1654
- "id": "g1HAWWVBjmfK"
1655
- },
1656
- "source": [
1657
- "# 8.0 \u2014 Scene Visualisation & Classification Maps\n",
1658
- "\n",
1659
- "Loads saved models, runs full-scene patch-by-patch inference, and exports classification maps as individual PNGs, a combined overview PNG, and an Excel workbook."
1660
- ],
1661
- "id": "g1HAWWVBjmfK"
1662
- },
1663
- {
1664
- "cell_type": "markdown",
1665
- "metadata": {
1666
- "id": "kbjnbZWRjmfK"
1667
- },
1668
- "source": [
1669
- "## 8.1 \u2014 Visualisation Helper Functions"
1670
- ],
1671
- "id": "kbjnbZWRjmfK"
1672
- },
1673
- {
1674
- "cell_type": "code",
1675
- "execution_count": null,
1676
- "metadata": {
1677
- "id": "3xH3h143jmfK",
1678
- "executionInfo": {
1679
- "status": "ok",
1680
- "timestamp": 1782320198407,
1681
- "user_tz": -330,
1682
- "elapsed": 9,
1683
- "user": {
1684
- "displayName": "Danesh Selwal",
1685
- "userId": "10565017814556927251"
1686
- }
1687
- }
1688
- },
1689
- "outputs": [],
1690
- "source": [
1691
- "# Custom objects dict required for loading serialised models\n",
1692
- "VIS_CUSTOM_OBJECTS = {\n",
1693
- " 'PatchExtractor': PatchExtractor,\n",
1694
- " 'PatchPositionEncoder': PatchPositionEncoder,\n",
1695
- " 'GlobalFilterLayer': GlobalFilterLayer,\n",
1696
- " 'PatchEncoderWithCLS': PatchEncoderWithCLS,\n",
1697
- "}\n",
1698
- "\n",
1699
- "\n",
1700
- "def get_scene_rgb(x_img, bands):\n",
1701
- " \"\"\"Return an approximate RGB image by selecting three middle bands.\"\"\"\n",
1702
- " if bands >= 6:\n",
1703
- " rgb_idx = [bands // 2 - 1, bands // 2, bands // 2 + 1]\n",
1704
- " elif bands >= 3:\n",
1705
- " rgb_idx = [0, 1, 2]\n",
1706
- " else:\n",
1707
- " raise ValueError(f'Need at least 3 bands to form RGB, got {bands}.')\n",
1708
- " return np.clip(x_img[:, :, rgb_idx], 0.0, 1.0)\n",
1709
- "\n",
1710
- "\n",
1711
- "def get_display_cmap(num_classes):\n",
1712
- " \"\"\"Return a ListedColormap with black background plus one colour per class.\"\"\"\n",
1713
- " return ListedColormap([BACKGROUND_COLOR] + get_class_colors(num_classes))\n",
1714
- "\n",
1715
- "\n",
1716
- "def predict_full_scene_labels(model, x_img, patch_size=9, batch_size=256):\n",
1717
- " \"\"\"Run patch-by-patch inference over the full scene and return a class-label map (1..K).\"\"\"\n",
1718
- " pad = patch_size // 2\n",
1719
- " x_pad = np.pad(x_img, ((pad, pad), (pad, pad), (0, 0)), mode='edge')\n",
1720
- " preds = np.zeros((x_img.shape[0], x_img.shape[1]), dtype=np.int32)\n",
1721
- "\n",
1722
- " for r in range(x_img.shape[0]):\n",
1723
- " row_patches = np.empty(\n",
1724
- " (x_img.shape[1], patch_size, patch_size, x_img.shape[-1]), dtype=np.float32,\n",
1725
- " )\n",
1726
- " for c in range(x_img.shape[1]):\n",
1727
- " row_patches[c] = x_pad[r:r + patch_size, c:c + patch_size, :]\n",
1728
- " row_prob = model.predict(row_patches, batch_size=batch_size, verbose=0)\n",
1729
- " preds[r] = np.argmax(row_prob, axis=1) + 1 # back to 1..K\n",
1730
- "\n",
1731
- " return preds\n",
1732
- "\n",
1733
- "\n",
1734
- "def load_saved_models_for_visualization(model_dir, custom_objects):\n",
1735
- " \"\"\"Attempt to load best-checkpoint .keras files for all three architectures.\"\"\"\n",
1736
- " model_files = {\n",
1737
- " 'AlexNet_CNN': model_dir / 'AlexNet_CNN_best.keras',\n",
1738
- " 'GFNet': model_dir / 'GFNet_best.keras',\n",
1739
- " 'ViT_UNet': model_dir / 'ViT_UNet_best.keras',\n",
1740
- " }\n",
1741
- " loaded, missing = {}, {}\n",
1742
- " for model_key, model_path in model_files.items():\n",
1743
- " if not model_path.exists():\n",
1744
- " missing[model_key] = f'Missing file: {model_path}'\n",
1745
- " continue\n",
1746
- " try:\n",
1747
- " loaded[model_key] = keras.models.load_model(\n",
1748
- " model_path, custom_objects=custom_objects, compile=False, safe_mode=False,\n",
1749
- " )\n",
1750
- " except Exception as exc:\n",
1751
- " missing[model_key] = f'Load failed: {exc}'\n",
1752
- " return loaded, missing\n",
1753
- "\n",
1754
- "\n",
1755
- "def save_single_panel(image, title, save_path, cmap=None, vmin=None, vmax=None):\n",
1756
- " \"\"\"Save a single image panel to PNG.\"\"\"\n",
1757
- " fig, ax = plt.subplots(figsize=(8, 6))\n",
1758
- " if cmap is None:\n",
1759
- " ax.imshow(image)\n",
1760
- " else:\n",
1761
- " ax.imshow(image, cmap=cmap, vmin=vmin, vmax=vmax)\n",
1762
- " ax.set_title(title, fontsize=14)\n",
1763
- " ax.axis('off')\n",
1764
- " fig.tight_layout()\n",
1765
- " fig.savefig(save_path, dpi=220, bbox_inches='tight')\n",
1766
- " plt.show()\n",
1767
- " plt.close(fig)\n",
1768
- " print('Saved:', save_path)\n",
1769
- "\n",
1770
- "\n",
1771
- "def save_combined_overview(rgb_image, y_true, pred_maps, missing_info, num_classes, save_path):\n",
1772
- " \"\"\"Save a single figure with RGB, ground truth, and all three classification maps side-by-side.\"\"\"\n",
1773
- " cmap = get_display_cmap(num_classes)\n",
1774
- "\n",
1775
- " panels = [\n",
1776
- " ('Approximate RGB', rgb_image, 'rgb'),\n",
1777
- " ('True Label Map', y_true, 'label'),\n",
1778
- " ('AlexNet Initial Classification', pred_maps.get('AlexNet_CNN'), 'label'),\n",
1779
- " ('GFNet Initial Classification', pred_maps.get('GFNet'), 'label'),\n",
1780
- " ('ViT Initial Classification', pred_maps.get('ViT_UNet'), 'label'),\n",
1781
- " ]\n",
1782
- "\n",
1783
- " fig, axes = plt.subplots(1, len(panels), figsize=(30, 7), sharex=True, sharey=True)\n",
1784
- " for ax, (title, img, kind) in zip(axes, panels):\n",
1785
- " ax.set_title(title, fontsize=13)\n",
1786
- " if kind == 'rgb':\n",
1787
- " ax.imshow(img)\n",
1788
- " else:\n",
1789
- " if img is None:\n",
1790
- " ax.imshow(np.zeros_like(y_true), cmap=cmap, vmin=0, vmax=num_classes)\n",
1791
- " # Determine which key is missing to show the correct error message\n",
1792
- " key = next((k for k in ['AlexNet_CNN', 'GFNet', 'ViT_UNet'] if k in title), None)\n",
1793
- " ax.text(0.5, 0.5, missing_info.get(key, 'Unavailable'),\n",
1794
- " ha='center', va='center', fontsize=10, wrap=True, transform=ax.transAxes)\n",
1795
- " else:\n",
1796
- " ax.imshow(img, cmap=cmap, vmin=0, vmax=num_classes)\n",
1797
- " ax.axis('off')\n",
1798
- "\n",
1799
- " handles = [plt.Rectangle((0, 0), 1, 1, color=BACKGROUND_COLOR, label='Background')]\n",
1800
- " class_colors = get_class_colors(num_classes)\n",
1801
- " for i in range(num_classes):\n",
1802
- " handles.append(plt.Rectangle((0, 0), 1, 1, color=class_colors[i], label=f'Class {i+1}'))\n",
1803
- " fig.legend(handles=handles, loc='lower center', ncol=min(num_classes + 1, 8),\n",
1804
- " bbox_to_anchor=(0.5, -0.02), frameon=True)\n",
1805
- " fig.tight_layout(rect=[0, 0.05, 1, 1])\n",
1806
- " fig.savefig(save_path, dpi=220, bbox_inches='tight')\n",
1807
- " plt.show()\n",
1808
- " plt.close(fig)\n",
1809
- " print('Saved:', save_path)\n",
1810
- "\n",
1811
- "\n",
1812
- "def save_images_to_excel(excel_path, image_paths, sheet_name='Initial_Classification_Maps'):\n",
1813
- " \"\"\"Embed PNG images into an Excel workbook sheet at predefined anchor cells.\"\"\"\n",
1814
- " if excel_path.exists():\n",
1815
- " wb = load_workbook(excel_path)\n",
1816
- " if sheet_name in wb.sheetnames:\n",
1817
- " del wb[sheet_name]\n",
1818
- " ws = wb.create_sheet(title=sheet_name)\n",
1819
- " else:\n",
1820
- " wb = Workbook()\n",
1821
- " ws = wb.active\n",
1822
- " ws.title = sheet_name\n",
1823
- "\n",
1824
- " anchors = ['A1', 'J1', 'A25', 'J25', 'A49', 'J49']\n",
1825
- " for path, anchor in zip(image_paths, anchors):\n",
1826
- " if path.exists():\n",
1827
- " img = XLImage(str(path))\n",
1828
- " img.anchor = anchor\n",
1829
- " ws.add_image(img)\n",
1830
- "\n",
1831
- " wb.save(excel_path)\n",
1832
- " print('Saved Excel workbook:', excel_path)"
1833
- ],
1834
- "id": "3xH3h143jmfK"
1835
- },
1836
- {
1837
- "cell_type": "markdown",
1838
- "metadata": {
1839
- "id": "KBnpo1UMjmfL"
1840
- },
1841
- "source": [
1842
- "## 8.2 \u2014 Load Models & Generate Classification Maps"
1843
- ],
1844
- "id": "KBnpo1UMjmfL"
1845
- },
1846
- {
1847
- "cell_type": "code",
1848
- "execution_count": null,
1849
- "metadata": {
1850
- "id": "9tRAAuPDjmfL",
1851
- "colab": {
1852
- "base_uri": "https://localhost:8080/"
1853
- },
1854
- "executionInfo": {
1855
- "status": "ok",
1856
- "timestamp": 1782320198451,
1857
- "user_tz": -330,
1858
- "elapsed": 38,
1859
- "user": {
1860
- "displayName": "Danesh Selwal",
1861
- "userId": "10565017814556927251"
1862
- }
1863
- },
1864
- "outputId": "f215e392-495c-4bf5-b8df-98462dc7b1e1"
1865
- },
1866
- "outputs": [],
1867
- "source": [
1868
- "scene_rgb = get_scene_rgb(x_img, B)\n",
1869
- "display_cmap = get_display_cmap(num_classes)\n",
1870
- "\n",
1871
- "loaded_models, missing_models = load_saved_models_for_visualization(MODEL_DIR, VIS_CUSTOM_OBJECTS)\n",
1872
- "prediction_maps = {}\n",
1873
- "\n",
1874
- "for model_key, loaded_model in loaded_models.items():\n",
1875
- " print(f'Generating full-scene classified image for {model_key}...')\n",
1876
- " prediction_maps[model_key] = predict_full_scene_labels(\n",
1877
- " loaded_model, x_img, patch_size=PATCH_SIZE, batch_size=BATCH_SIZE,\n",
1878
- " )\n",
1879
- " print(f'{model_key} map shape:', prediction_maps[model_key].shape)\n",
1880
- "\n",
1881
- "if missing_models:\n",
1882
- " print('Some models could not be loaded:')\n",
1883
- " for model_key, reason in missing_models.items():\n",
1884
- " print(f' - {model_key}: {reason}')"
1885
- ],
1886
- "id": "9tRAAuPDjmfL"
1887
- },
1888
- {
1889
- "cell_type": "markdown",
1890
- "metadata": {
1891
- "id": "_QerZQMWjmfL"
1892
- },
1893
- "source": [
1894
- "## 8.3 \u2014 Save PNGs & Excel Workbook"
1895
- ],
1896
- "id": "_QerZQMWjmfL"
1897
- },
1898
- {
1899
- "cell_type": "code",
1900
- "execution_count": null,
1901
- "metadata": {
1902
- "id": "bCBTA_H4jmfM",
1903
- "colab": {
1904
- "base_uri": "https://localhost:8080/",
1905
- "height": 0
1906
- },
1907
- "executionInfo": {
1908
- "status": "ok",
1909
- "timestamp": 1782320201632,
1910
- "user_tz": -330,
1911
- "elapsed": 3179,
1912
- "user": {
1913
- "displayName": "Danesh Selwal",
1914
- "userId": "10565017814556927251"
1915
- }
1916
- },
1917
- "outputId": "f232098e-7b22-452a-a1a4-f1d55d52eef1"
1918
- },
1919
- "outputs": [],
1920
- "source": [
1921
- "rgb_png = VIS_DIR / 'scene_rgb.png'\n",
1922
- "gt_png = VIS_DIR / 'ground_truth_label_map.png'\n",
1923
- "alex_png = VIS_DIR / 'AlexNet_CNN_initial_classification.png'\n",
1924
- "gfnet_png = VIS_DIR / 'GFNet_initial_classification.png'\n",
1925
- "vit_png = VIS_DIR / 'ViT_UNet_initial_classification.png'\n",
1926
- "overview_png = VIS_DIR / 'combined_initial_classification_overview.png'\n",
1927
- "\n",
1928
- "save_single_panel(scene_rgb, 'Approximate RGB', rgb_png)\n",
1929
- "save_single_panel(y_img, 'Ground Truth Label Map', gt_png,\n",
1930
- " cmap=display_cmap, vmin=0, vmax=num_classes)\n",
1931
- "\n",
1932
- "if prediction_maps.get('AlexNet_CNN') is not None:\n",
1933
- " save_single_panel(prediction_maps['AlexNet_CNN'], 'AlexNet Initial Classification',\n",
1934
- " alex_png, cmap=display_cmap, vmin=0, vmax=num_classes)\n",
1935
- "\n",
1936
- "if prediction_maps.get('GFNet') is not None:\n",
1937
- " save_single_panel(prediction_maps['GFNet'], 'GFNet Initial Classification',\n",
1938
- " gfnet_png, cmap=display_cmap, vmin=0, vmax=num_classes)\n",
1939
- "\n",
1940
- "if prediction_maps.get('ViT_UNet') is not None:\n",
1941
- " save_single_panel(prediction_maps['ViT_UNet'], 'ViT Initial Classification',\n",
1942
- " vit_png, cmap=display_cmap, vmin=0, vmax=num_classes)\n",
1943
- "\n",
1944
- "save_combined_overview(\n",
1945
- " rgb_image=scene_rgb, y_true=y_img,\n",
1946
- " pred_maps=prediction_maps, missing_info=missing_models,\n",
1947
- " num_classes=num_classes, save_path=overview_png,\n",
1948
- ")\n",
1949
- "\n",
1950
- "image_paths_for_excel = [rgb_png, gt_png, alex_png, gfnet_png, vit_png, overview_png]\n",
1951
- "save_images_to_excel(VIS_EXCEL_PATH, image_paths_for_excel,\n",
1952
- " sheet_name='Initial_Classification_Maps')\n",
1953
- "\n",
1954
- "print('All visualisation outputs saved in:', VIS_DIR)"
1955
- ],
1956
- "id": "bCBTA_H4jmfM"
1957
- },
1958
- {
1959
- "cell_type": "markdown",
1960
- "metadata": {
1961
- "id": "t_0f2DsWjmfM"
1962
- },
1963
- "source": [
1964
- "# 9.0 \u2014 Model Reload Utility\n",
1965
- "\n",
1966
- "Convenience cell for loading any saved `.keras` model from Drive for downstream uncertainty analysis without retraining."
1967
- ],
1968
- "id": "t_0f2DsWjmfM"
1969
- },
1970
- {
1971
- "cell_type": "code",
1972
- "execution_count": null,
1973
- "metadata": {
1974
- "id": "cPjwBCk5jmfM",
1975
- "colab": {
1976
- "base_uri": "https://localhost:8080/"
1977
- },
1978
- "executionInfo": {
1979
- "status": "ok",
1980
- "timestamp": 1782320201648,
1981
- "user_tz": -330,
1982
- "elapsed": 25,
1983
- "user": {
1984
- "displayName": "Danesh Selwal",
1985
- "userId": "10565017814556927251"
1986
- }
1987
- },
1988
- "outputId": "3c5ce609-1833-4c1e-bc97-892abc2b2c7b"
1989
- },
1990
- "outputs": [],
1991
- "source": [
1992
- "example_path = MODEL_DIR / 'ViT_UNet_final.keras'\n",
1993
- "if example_path.exists():\n",
1994
- " loaded_model = keras.models.load_model(\n",
1995
- " example_path, custom_objects=VIS_CUSTOM_OBJECTS,\n",
1996
- " compile=False, safe_mode=False,\n",
1997
- " )\n",
1998
- " print('Loaded:', example_path)\n",
1999
- " print('Model name:', loaded_model.name)\n",
2000
- "else:\n",
2001
- " print('Example model not found. Train first, then reload.')"
2002
- ],
2003
- "id": "cPjwBCk5jmfM"
2004
- }
2005
- ]
2006
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ensemble/Model_uncertainty_CreDE.ipynb DELETED
@@ -1,1365 +0,0 @@
1
- {
2
- "nbformat": 4,
3
- "nbformat_minor": 5,
4
- "metadata": {
5
- "kernelspec": {
6
- "display_name": "Python 3",
7
- "name": "python3"
8
- },
9
- "language_info": {
10
- "name": "python",
11
- "version": "3.10.0"
12
- },
13
- "colab": {
14
- "provenance": [],
15
- "gpuType": "T4"
16
- },
17
- "accelerator": "GPU"
18
- },
19
- "cells": [
20
- {
21
- "cell_type": "markdown",
22
- "metadata": {
23
- "id": "63UryAyvkXs3"
24
- },
25
- "source": [
26
- "# 1.0 \u2014 Setup & Imports\n",
27
- "\n",
28
- "Mount Google Drive and import all required standard-library, third-party, and framework packages."
29
- ],
30
- "id": "63UryAyvkXs3"
31
- },
32
- {
33
- "cell_type": "markdown",
34
- "metadata": {
35
- "id": "vfTGGbvBkXs3"
36
- },
37
- "source": [
38
- "## 1.1 \u2014 Drive Mount"
39
- ],
40
- "id": "vfTGGbvBkXs3"
41
- },
42
- {
43
- "cell_type": "code",
44
- "execution_count": null,
45
- "metadata": {
46
- "colab": {
47
- "base_uri": "https://localhost:8080/"
48
- },
49
- "id": "M7Y8Rl0zkXs3",
50
- "executionInfo": {
51
- "status": "ok",
52
- "timestamp": 1780309403777,
53
- "user_tz": -330,
54
- "elapsed": 38883,
55
- "user": {
56
- "displayName": "Randy Orton",
57
- "userId": "11725427482974841050"
58
- }
59
- },
60
- "outputId": "6fcb4ec0-0712-4ac1-883a-59c52b24aa35"
61
- },
62
- "outputs": [],
63
- "source": [
64
- "from google.colab import drive\n",
65
- "drive.mount('/content/drive')"
66
- ],
67
- "id": "M7Y8Rl0zkXs3"
68
- },
69
- {
70
- "cell_type": "markdown",
71
- "metadata": {
72
- "id": "JT-hTYlVkXs3"
73
- },
74
- "source": [
75
- "## 1.2 \u2014 Library Imports\n",
76
- "\n",
77
- "Groups: standard library \u2192 numerical/data \u2192 visualisation \u2192 deep learning."
78
- ],
79
- "id": "JT-hTYlVkXs3"
80
- },
81
- {
82
- "cell_type": "code",
83
- "execution_count": null,
84
- "metadata": {
85
- "colab": {
86
- "base_uri": "https://localhost:8080/"
87
- },
88
- "id": "4znih1uIkXs3",
89
- "executionInfo": {
90
- "status": "ok",
91
- "timestamp": 1780309408442,
92
- "user_tz": -330,
93
- "elapsed": 4668,
94
- "user": {
95
- "displayName": "Randy Orton",
96
- "userId": "11725427482974841050"
97
- }
98
- },
99
- "outputId": "1ba3cd1a-99e8-4744-feb6-dcd959834bad"
100
- },
101
- "outputs": [],
102
- "source": [
103
- "# Standard library\n",
104
- "import os\n",
105
- "import gc\n",
106
- "import glob\n",
107
- "from pathlib import Path\n",
108
- "\n",
109
- "from scipy.io import loadmat\n",
110
- "\n",
111
- "# Numerical & data\n",
112
- "import numpy as np\n",
113
- "import pandas as pd\n",
114
- "import seaborn as sns\n",
115
- "\n",
116
- "# Visualisation\n",
117
- "import matplotlib.pyplot as plt\n",
118
- "from matplotlib.colors import ListedColormap\n",
119
- "from matplotlib.patches import Patch\n",
120
- "\n",
121
- "# Deep learning\n",
122
- "import tensorflow as tf\n",
123
- "from tensorflow.keras import layers\n",
124
- "\n",
125
- "# Metrics & Splitting\n",
126
- "from sklearn.model_selection import train_test_split\n",
127
- "from sklearn.metrics import (\n",
128
- " accuracy_score, cohen_kappa_score, confusion_matrix,\n",
129
- " classification_report, f1_score, log_loss\n",
130
- ")\n",
131
- "\n",
132
- "# \u2500\u2500 Excel export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
133
- "from openpyxl import Workbook\n",
134
- "from openpyxl.styles import Font, PatternFill, Alignment, Border, Side\n",
135
- "from openpyxl.utils import get_column_letter\n",
136
- "from openpyxl.drawing.image import Image as XLImage\n",
137
- "\n",
138
- "sns.set_style(\"whitegrid\")\n",
139
- "print(\"TensorFlow:\", tf.__version__)"
140
- ],
141
- "id": "4znih1uIkXs3"
142
- },
143
- {
144
- "cell_type": "markdown",
145
- "metadata": {
146
- "id": "7KYP-49QkXs4"
147
- },
148
- "source": [
149
- "# 2.0 \u2014 Configuration\n",
150
- "\n",
151
- "All tunable constants and filesystem paths live here. Edit this section before running\n",
152
- "the notebook on a new dataset or experiment."
153
- ],
154
- "id": "7KYP-49QkXs4"
155
- },
156
- {
157
- "cell_type": "markdown",
158
- "metadata": {
159
- "id": "0_MMSyFCkXs4"
160
- },
161
- "source": [
162
- "## 2.1 \u2014 Constants & Paths"
163
- ],
164
- "id": "0_MMSyFCkXs4"
165
- },
166
- {
167
- "cell_type": "code",
168
- "execution_count": null,
169
- "metadata": {
170
- "id": "BMVg7YpUkXs4",
171
- "executionInfo": {
172
- "status": "ok",
173
- "timestamp": 1780309408454,
174
- "user_tz": -330,
175
- "elapsed": 3,
176
- "user": {
177
- "displayName": "Randy Orton",
178
- "userId": "11725427482974841050"
179
- }
180
- }
181
- },
182
- "outputs": [],
183
- "source": [
184
- "# Reproducibility\n",
185
- "SEED = 42\n",
186
- "np.random.seed(SEED)\n",
187
- "tf.random.set_seed(SEED)\n",
188
- "\n",
189
- "# Filesystem\n",
190
- "ROOT_DIR = Path(\"/content/drive/My Drive/Classification\")\n",
191
- "MODEL_DIR = Path(\"/content/drive/My Drive/Classification/ensemble/models/ensembles\")\n",
192
- "DATA_DIR = Path(\"/content/drive/My Drive/Classification/data\")\n",
193
- "DATA_FILE = DATA_DIR / \"DIAS.mat\" # hyperspectral cube (H, W, Bands)\n",
194
- "LABEL_FILE = DATA_DIR / \"DIAS_ref.mat\" # reference / ground-truth label map\n",
195
- "\n",
196
- "# Scene geometry \u2014 H, W, B are inferred automatically from the .mat cube shape\n",
197
- "# once loaded in Section 3.1, so this notebook works unchanged for any scene\n",
198
- "# size / band count.\n",
199
- "H, W, B = None, None, None\n",
200
- "PERCENTILE_CLIP = (2.0, 98.0) # robust per-band stretch range (2nd-98th percentile)\n",
201
- "PATCH_SIZE = 9\n",
202
- "\n",
203
- "# Uncertainty thresholds used for masking\n",
204
- "AU_THRESH = 0.5\n",
205
- "EU_THRESH = 0.2\n",
206
- "TU_THRESH = 0.7"
207
- ],
208
- "id": "BMVg7YpUkXs4"
209
- },
210
- {
211
- "cell_type": "markdown",
212
- "metadata": {
213
- "id": "sYYDGaimkXs4"
214
- },
215
- "source": [
216
- "## 2.2 \u2014 Output Directory Setup\n",
217
- "\n",
218
- "Creates a dedicated subfolder inside `MODEL_DIR` to hold all CreDE outputs (PNGs and CSV)."
219
- ],
220
- "id": "sYYDGaimkXs4"
221
- },
222
- {
223
- "cell_type": "code",
224
- "execution_count": null,
225
- "metadata": {
226
- "colab": {
227
- "base_uri": "https://localhost:8080/"
228
- },
229
- "id": "m2H6dkT8kXs4",
230
- "executionInfo": {
231
- "status": "ok",
232
- "timestamp": 1780309409217,
233
- "user_tz": -330,
234
- "elapsed": 761,
235
- "user": {
236
- "displayName": "Randy Orton",
237
- "userId": "11725427482974841050"
238
- }
239
- },
240
- "outputId": "8f027b2c-6390-4a3c-b1d0-d2555712d8e9"
241
- },
242
- "outputs": [],
243
- "source": [
244
- "CREDE_OUT_DIR = ROOT_DIR/ \"ensemble\" / \"results\"\n",
245
- "CREDE_OUT_DIR.mkdir(parents=True, exist_ok=True)\n",
246
- "print(f\"CreDE output directory: {CREDE_OUT_DIR}\")"
247
- ],
248
- "id": "m2H6dkT8kXs4"
249
- },
250
- {
251
- "cell_type": "markdown",
252
- "metadata": {
253
- "id": "WO9u-5JckXs4"
254
- },
255
- "source": [
256
- "# 3.0 \u2014 Data Loading & Preprocessing\n",
257
- "\n",
258
- "Reads the hyperspectral/multispectral scene and label raster from .mat files, robustly\n",
259
- "normalises each band via a percentile clip to [0, 1], then extracts every spatial patch needed for full-scene inference."
260
- ],
261
- "id": "WO9u-5JckXs4"
262
- },
263
- {
264
- "cell_type": "markdown",
265
- "metadata": {
266
- "id": "4PqA6hOzkXs4"
267
- },
268
- "source": [
269
- "## 3.1 \u2014 Hyperspectral/Multispectral Data Loader"
270
- ],
271
- "id": "4PqA6hOzkXs4"
272
- },
273
- {
274
- "cell_type": "code",
275
- "execution_count": null,
276
- "metadata": {
277
- "colab": {
278
- "base_uri": "https://localhost:8080/"
279
- },
280
- "id": "0DLcWZkLkXs4",
281
- "executionInfo": {
282
- "status": "ok",
283
- "timestamp": 1780309410338,
284
- "user_tz": -330,
285
- "elapsed": 1119,
286
- "user": {
287
- "displayName": "Randy Orton",
288
- "userId": "11725427482974841050"
289
- }
290
- },
291
- "outputId": "7d510f99-c3c5-43ca-b0f7-c347ab0b28c8"
292
- },
293
- "outputs": [],
294
- "source": [
295
- "def load_scene_from_mat(data_path, label_path, data_key=None, label_key=None):\n",
296
- " \"\"\"\n",
297
- " Load a hyperspectral/multispectral scene + reference labels from .mat files.\n",
298
- "\n",
299
- " Auto-detects the variable name inside each .mat file if not given (matches\n",
300
- " files such as DIAS.mat / DIAS_ref.mat storing the array under a single named\n",
301
- " variable). Each spectral band is robustly stretched to [0, 1] using a\n",
302
- " percentile clip (PERCENTILE_CLIP) rather than literal min/max \u2014 a handful of\n",
303
- " saturated/no-data pixels (e.g. sensor clipping at int8 range limits -128/127)\n",
304
- " would otherwise dominate the stretch.\n",
305
- "\n",
306
- " Returns\n",
307
- " -------\n",
308
- " x_norm : np.ndarray Shape (H, W, B), dtype float32, values in [0, 1].\n",
309
- " y : np.ndarray Shape (H, W), dtype int32.\n",
310
- " height, width, bands : int\n",
311
- " \"\"\"\n",
312
- " def _first_array(mat_dict, key):\n",
313
- " if key is not None:\n",
314
- " return mat_dict[key]\n",
315
- " candidates = [v for k, v in mat_dict.items() if not k.startswith(\"__\")]\n",
316
- " if not candidates:\n",
317
- " raise ValueError(f\"No data arrays found in {mat_dict.keys()}\")\n",
318
- " return candidates[0]\n",
319
- "\n",
320
- " data_mat = loadmat(data_path)\n",
321
- " label_mat = loadmat(label_path)\n",
322
- "\n",
323
- " x = _first_array(data_mat, data_key).astype(np.float32)\n",
324
- " y = _first_array(label_mat, label_key).astype(np.int32)\n",
325
- "\n",
326
- " if x.ndim != 3:\n",
327
- " raise ValueError(f\"Expected a (H, W, Bands) cube, got shape {x.shape}\")\n",
328
- " if y.shape != x.shape[:2]:\n",
329
- " raise ValueError(f\"Label shape {y.shape} does not match image shape {x.shape[:2]}\")\n",
330
- "\n",
331
- " height, width, bands = x.shape\n",
332
- "\n",
333
- " x_norm = np.empty_like(x, dtype=np.float32)\n",
334
- " for b in range(bands):\n",
335
- " band = x[:, :, b]\n",
336
- " lo = np.percentile(band, PERCENTILE_CLIP[0])\n",
337
- " hi = np.percentile(band, PERCENTILE_CLIP[1])\n",
338
- " denom = max(hi - lo, 1e-8)\n",
339
- " x_norm[:, :, b] = np.clip((band - lo) / denom, 0.0, 1.0)\n",
340
- "\n",
341
- " return x_norm, y, height, width, bands\n",
342
- "\n",
343
- "\n",
344
- "print(\"Loading hyperspectral/multispectral data...\")\n",
345
- "x_img, y_img, H, W, B = load_scene_from_mat(DATA_FILE, LABEL_FILE)\n",
346
- "num_classes = int(np.unique(y_img[y_img > 0]).size)\n",
347
- "print(f\"Scene shape: {x_img.shape} | H={H} W={W} B={B} | Classes: {num_classes}\")"
348
- ],
349
- "id": "0DLcWZkLkXs4"
350
- },
351
- {
352
- "cell_type": "markdown",
353
- "metadata": {
354
- "id": "8nnfqMLMkXs5"
355
- },
356
- "source": [
357
- "## 3.2 \u2014 Spatial Patch Extraction\n",
358
- "\n",
359
- "Pads the scene and slides a `PATCH_SIZE \u00d7 PATCH_SIZE` window over every pixel,\n",
360
- "producing a flat array of patches ready for batch inference."
361
- ],
362
- "id": "8nnfqMLMkXs5"
363
- },
364
- {
365
- "cell_type": "code",
366
- "execution_count": null,
367
- "metadata": {
368
- "colab": {
369
- "base_uri": "https://localhost:8080/"
370
- },
371
- "id": "fMjSovXKkXs5",
372
- "executionInfo": {
373
- "status": "ok",
374
- "timestamp": 1780309410514,
375
- "user_tz": -330,
376
- "elapsed": 162,
377
- "user": {
378
- "displayName": "Randy Orton",
379
- "userId": "11725427482974841050"
380
- }
381
- },
382
- "outputId": "12fab1e2-e775-4d79-9d56-34966e956c03"
383
- },
384
- "outputs": [],
385
- "source": [
386
- "print(\"Extracting all spatial patches for full-scene inference...\")\n",
387
- "\n",
388
- "pad = PATCH_SIZE // 2\n",
389
- "x_pad = np.pad(x_img, ((pad, pad), (pad, pad), (0, 0)), mode=\"edge\")\n",
390
- "\n",
391
- "scene_pixels_scaled = np.empty((H * W, PATCH_SIZE, PATCH_SIZE, B), dtype=np.float32)\n",
392
- "\n",
393
- "idx = 0\n",
394
- "for r in range(H):\n",
395
- " for c in range(W):\n",
396
- " scene_pixels_scaled[idx] = x_pad[r:r + PATCH_SIZE, c:c + PATCH_SIZE, :]\n",
397
- " idx += 1\n",
398
- "\n",
399
- "print(f\"scene_pixels_scaled shape: {scene_pixels_scaled.shape}\")"
400
- ],
401
- "id": "fMjSovXKkXs5"
402
- },
403
- {
404
- "cell_type": "code",
405
- "source": [
406
- "# -----------------------------\n",
407
- "# 3.3 \u2014 Labeled Patch Extraction & Splitting\n",
408
- "# -----------------------------\n",
409
- "print(\"Extracting labeled patches for metric evaluation...\")\n",
410
- "\n",
411
- "def extract_labeled_patches(x, y, patch_size=9):\n",
412
- " pad = patch_size // 2\n",
413
- " x_pad = np.pad(x, ((pad, pad), (pad, pad), (0, 0)), mode=\"edge\")\n",
414
- " coords = np.argwhere(y > 0)\n",
415
- " patches = np.empty((coords.shape[0], patch_size, patch_size, x.shape[-1]), dtype=np.float32)\n",
416
- " labels = np.empty((coords.shape[0],), dtype=np.int32)\n",
417
- " for i, (r, c) in enumerate(coords):\n",
418
- " patches[i] = x_pad[r:r + patch_size, c:c + patch_size, :]\n",
419
- " labels[i] = int(y[r, c]) - 1\n",
420
- " return patches, labels, coords\n",
421
- "\n",
422
- "X, y_labels, coords = extract_labeled_patches(x_img, y_img, PATCH_SIZE)\n",
423
- "\n",
424
- "# Standard Split\n",
425
- "TRAIN_PERCENT = 0.75\n",
426
- "x_train_full, x_test, y_train_full, y_test = train_test_split(\n",
427
- " X, y_labels, train_size=TRAIN_PERCENT, random_state=SEED, stratify=y_labels\n",
428
- ")\n",
429
- "y_test_cat = tf.keras.utils.to_categorical(y_test, num_classes)\n",
430
- "\n",
431
- "print(f\"Standard Test Set Extracted: {x_test.shape}\")"
432
- ],
433
- "metadata": {
434
- "colab": {
435
- "base_uri": "https://localhost:8080/"
436
- },
437
- "id": "LTpD-oZSex0w",
438
- "executionInfo": {
439
- "status": "ok",
440
- "timestamp": 1780309410612,
441
- "user_tz": -330,
442
- "elapsed": 92,
443
- "user": {
444
- "displayName": "Randy Orton",
445
- "userId": "11725427482974841050"
446
- }
447
- },
448
- "outputId": "18422bc5-e302-4e9b-84f8-3a3424f90dde"
449
- },
450
- "id": "LTpD-oZSex0w",
451
- "execution_count": null,
452
- "outputs": []
453
- },
454
- {
455
- "cell_type": "markdown",
456
- "metadata": {
457
- "id": "l9X69z2skXs5"
458
- },
459
- "source": [
460
- "# 4.0 \u2014 Custom Keras Layers\n",
461
- "\n",
462
- "Registers the four custom `tf.keras` layers required to deserialise the saved `.keras` models.\n",
463
- "All four are decorated with `@register_keras_serializable()` so they survive `load_model`.\n",
464
- "**Do not alter any layer internals** \u2014 these must match the definitions used during training."
465
- ],
466
- "id": "l9X69z2skXs5"
467
- },
468
- {
469
- "cell_type": "markdown",
470
- "metadata": {
471
- "id": "91HKDq_TkXs5"
472
- },
473
- "source": [
474
- "## 4.1 \u2014 PatchExtractor"
475
- ],
476
- "id": "91HKDq_TkXs5"
477
- },
478
- {
479
- "cell_type": "code",
480
- "execution_count": null,
481
- "metadata": {
482
- "id": "Yde0Sg5kkXs5",
483
- "executionInfo": {
484
- "status": "ok",
485
- "timestamp": 1780309410635,
486
- "user_tz": -330,
487
- "elapsed": 16,
488
- "user": {
489
- "displayName": "Randy Orton",
490
- "userId": "11725427482974841050"
491
- }
492
- }
493
- },
494
- "outputs": [],
495
- "source": [
496
- "@tf.keras.utils.register_keras_serializable()\n",
497
- "class PatchExtractor(layers.Layer):\n",
498
- " \"\"\"Extracts non-overlapping image patches and flattens them into a sequence.\"\"\"\n",
499
- "\n",
500
- " def __init__(self, patch_size=3, **kwargs):\n",
501
- " super().__init__(**kwargs)\n",
502
- " self.patch_size = patch_size\n",
503
- "\n",
504
- " def call(self, images):\n",
505
- " patches = tf.image.extract_patches(\n",
506
- " images=images,\n",
507
- " sizes=[1, self.patch_size, self.patch_size, 1],\n",
508
- " strides=[1, self.patch_size, self.patch_size, 1],\n",
509
- " rates=[1, 1, 1, 1],\n",
510
- " padding=\"VALID\"\n",
511
- " )\n",
512
- " batch, patch_dim = tf.shape(images)[0], tf.shape(patches)[-1]\n",
513
- " num_patches = tf.shape(patches)[1] * tf.shape(patches)[2]\n",
514
- " return tf.reshape(patches, [batch, num_patches, patch_dim])\n",
515
- "\n",
516
- " def get_config(self):\n",
517
- " cfg = super().get_config()\n",
518
- " cfg.update({\"patch_size\": self.patch_size})\n",
519
- " return cfg"
520
- ],
521
- "id": "Yde0Sg5kkXs5"
522
- },
523
- {
524
- "cell_type": "markdown",
525
- "metadata": {
526
- "id": "A63Sz62AkXs5"
527
- },
528
- "source": [
529
- "## 4.2 \u2014 PatchPositionEncoder"
530
- ],
531
- "id": "A63Sz62AkXs5"
532
- },
533
- {
534
- "cell_type": "code",
535
- "execution_count": null,
536
- "metadata": {
537
- "id": "4HbRIJRykXs5",
538
- "executionInfo": {
539
- "status": "ok",
540
- "timestamp": 1780309410703,
541
- "user_tz": -330,
542
- "elapsed": 66,
543
- "user": {
544
- "displayName": "Randy Orton",
545
- "userId": "11725427482974841050"
546
- }
547
- }
548
- },
549
- "outputs": [],
550
- "source": [
551
- "@tf.keras.utils.register_keras_serializable()\n",
552
- "class PatchPositionEncoder(layers.Layer):\n",
553
- " \"\"\"Projects patches to `projection_dim` and adds learned positional embeddings.\"\"\"\n",
554
- "\n",
555
- " def __init__(self, num_patches, projection_dim, **kwargs):\n",
556
- " super().__init__(**kwargs)\n",
557
- " self.num_patches = num_patches\n",
558
- " self.projection_dim = projection_dim\n",
559
- " self.projection = layers.Dense(projection_dim)\n",
560
- " self.position_embedding = layers.Embedding(input_dim=num_patches, output_dim=projection_dim)\n",
561
- "\n",
562
- " def call(self, patches):\n",
563
- " positions = tf.range(start=0, limit=self.num_patches, delta=1)\n",
564
- " return self.projection(patches) + self.position_embedding(positions)\n",
565
- "\n",
566
- " def get_config(self):\n",
567
- " cfg = super().get_config()\n",
568
- " cfg.update({\"num_patches\": self.num_patches, \"projection_dim\": self.projection_dim})\n",
569
- " return cfg"
570
- ],
571
- "id": "4HbRIJRykXs5"
572
- },
573
- {
574
- "cell_type": "markdown",
575
- "metadata": {
576
- "id": "7YukC294kXs5"
577
- },
578
- "source": [
579
- "## 4.3 \u2014 GlobalFilterLayer"
580
- ],
581
- "id": "7YukC294kXs5"
582
- },
583
- {
584
- "cell_type": "code",
585
- "execution_count": null,
586
- "metadata": {
587
- "id": "DzxtiRRMkXs5",
588
- "executionInfo": {
589
- "status": "ok",
590
- "timestamp": 1780309410708,
591
- "user_tz": -330,
592
- "elapsed": 2,
593
- "user": {
594
- "displayName": "Randy Orton",
595
- "userId": "11725427482974841050"
596
- }
597
- }
598
- },
599
- "outputs": [],
600
- "source": [
601
- "@tf.keras.utils.register_keras_serializable()\n",
602
- "class GlobalFilterLayer(layers.Layer):\n",
603
- " \"\"\"\n",
604
- " Applies a learnable complex-valued filter in the 2-D frequency domain.\n",
605
- "\n",
606
- " The token sequence is reshaped to a square grid, transformed with FFT2D,\n",
607
- " element-wise multiplied by trainable complex weights, then inverse-transformed\n",
608
- " back to the spatial sequence.\n",
609
- " \"\"\"\n",
610
- "\n",
611
- " def __init__(self, token_side, **kwargs):\n",
612
- " super().__init__(**kwargs)\n",
613
- " self.token_side = token_side\n",
614
- "\n",
615
- " def build(self, input_shape):\n",
616
- " channels = int(input_shape[-1])\n",
617
- " self.w_real = self.add_weight(\n",
618
- " name=\"w_real\", shape=(self.token_side, self.token_side, channels),\n",
619
- " initializer=\"glorot_uniform\", trainable=True\n",
620
- " )\n",
621
- " self.w_imag = self.add_weight(\n",
622
- " name=\"w_imag\", shape=(self.token_side, self.token_side, channels),\n",
623
- " initializer=\"zeros\", trainable=True\n",
624
- " )\n",
625
- " super().build(input_shape)\n",
626
- "\n",
627
- " def call(self, x):\n",
628
- " batch = tf.shape(x)[0]\n",
629
- " channels = tf.shape(x)[-1]\n",
630
- "\n",
631
- " x_2d = tf.reshape(x, [batch, self.token_side, self.token_side, channels])\n",
632
- " x_fft = tf.signal.fft2d(tf.cast(x_2d, tf.complex64))\n",
633
- " w_complex = tf.complex(self.w_real, self.w_imag)\n",
634
- " x_filtered = x_fft * w_complex\n",
635
- " x_spatial = tf.math.real(tf.signal.ifft2d(x_filtered))\n",
636
- "\n",
637
- " return tf.reshape(x_spatial, [batch, self.token_side * self.token_side, channels])\n",
638
- "\n",
639
- " def get_config(self):\n",
640
- " cfg = super().get_config()\n",
641
- " cfg.update({\"token_side\": self.token_side})\n",
642
- " return cfg"
643
- ],
644
- "id": "DzxtiRRMkXs5"
645
- },
646
- {
647
- "cell_type": "markdown",
648
- "metadata": {
649
- "id": "rKGZtJkNkXs5"
650
- },
651
- "source": [
652
- "## 4.4 \u2014 PatchEncoderWithCLS"
653
- ],
654
- "id": "rKGZtJkNkXs5"
655
- },
656
- {
657
- "cell_type": "code",
658
- "execution_count": null,
659
- "metadata": {
660
- "id": "y7JZKKM_kXs5",
661
- "executionInfo": {
662
- "status": "ok",
663
- "timestamp": 1780309410713,
664
- "user_tz": -330,
665
- "elapsed": 2,
666
- "user": {
667
- "displayName": "Randy Orton",
668
- "userId": "11725427482974841050"
669
- }
670
- }
671
- },
672
- "outputs": [],
673
- "source": [
674
- "@tf.keras.utils.register_keras_serializable()\n",
675
- "class PatchEncoderWithCLS(layers.Layer):\n",
676
- " \"\"\"\n",
677
- " Projects patches, prepends a learnable [CLS] token, and adds positional embeddings.\n",
678
- "\n",
679
- " Used by the ViT-UNet architecture; the CLS token aggregates global context\n",
680
- " for the final classification head.\n",
681
- " \"\"\"\n",
682
- "\n",
683
- " def __init__(self, num_patches, projection_dim, **kwargs):\n",
684
- " super().__init__(**kwargs)\n",
685
- " self.num_patches = num_patches\n",
686
- " self.projection_dim = projection_dim\n",
687
- " self.projection = layers.Dense(projection_dim)\n",
688
- " self.position_embedding = layers.Embedding(\n",
689
- " input_dim=num_patches + 1, output_dim=projection_dim\n",
690
- " )\n",
691
- "\n",
692
- " def build(self, input_shape):\n",
693
- " self.cls_token = self.add_weight(\n",
694
- " name=\"cls_token\", shape=(1, 1, self.projection_dim),\n",
695
- " initializer=\"zeros\", trainable=True\n",
696
- " )\n",
697
- " super().build(input_shape)\n",
698
- "\n",
699
- " def call(self, patches):\n",
700
- " batch = tf.shape(patches)[0]\n",
701
- " patch_proj = self.projection(patches)\n",
702
- " cls_tokens = tf.repeat(self.cls_token, repeats=batch, axis=0) # broadcast CLS to batch\n",
703
- " x = tf.concat([cls_tokens, patch_proj], axis=1)\n",
704
- " positions = tf.range(start=0, limit=self.num_patches + 1, delta=1)\n",
705
- " return x + self.position_embedding(positions)\n",
706
- "\n",
707
- " def get_config(self):\n",
708
- " cfg = super().get_config()\n",
709
- " cfg.update({\"num_patches\": self.num_patches, \"projection_dim\": self.projection_dim})\n",
710
- " return cfg"
711
- ],
712
- "id": "y7JZKKM_kXs5"
713
- },
714
- {
715
- "cell_type": "markdown",
716
- "metadata": {
717
- "id": "-69wdDpDkXs5"
718
- },
719
- "source": [
720
- "## 4.5 \u2014 Custom Objects Registry\n",
721
- "\n",
722
- "Collects all custom layer classes into a single dict passed to `tf.keras.models.load_model`."
723
- ],
724
- "id": "-69wdDpDkXs5"
725
- },
726
- {
727
- "cell_type": "code",
728
- "execution_count": null,
729
- "metadata": {
730
- "id": "TqfqdpwrkXs5",
731
- "executionInfo": {
732
- "status": "ok",
733
- "timestamp": 1780309410718,
734
- "user_tz": -330,
735
- "elapsed": 2,
736
- "user": {
737
- "displayName": "Randy Orton",
738
- "userId": "11725427482974841050"
739
- }
740
- }
741
- },
742
- "outputs": [],
743
- "source": [
744
- "CUSTOM_OBJECTS = {\n",
745
- " \"PatchExtractor\": PatchExtractor,\n",
746
- " \"PatchPositionEncoder\": PatchPositionEncoder,\n",
747
- " \"GlobalFilterLayer\": GlobalFilterLayer,\n",
748
- " \"PatchEncoderWithCLS\": PatchEncoderWithCLS,\n",
749
- "}"
750
- ],
751
- "id": "TqfqdpwrkXs5"
752
- },
753
- {
754
- "cell_type": "markdown",
755
- "metadata": {
756
- "id": "6hLXTpdokXs5"
757
- },
758
- "source": [
759
- "# 5.0 \u2014 Core Ensemble Functions\n",
760
- "\n",
761
- "Functions for discovering saved ensemble checkpoints and running the CreDE\n",
762
- "credal-bound computation across all ensemble members."
763
- ],
764
- "id": "6hLXTpdokXs5"
765
- },
766
- {
767
- "cell_type": "markdown",
768
- "metadata": {
769
- "id": "mtRX8AUxkXs5"
770
- },
771
- "source": [
772
- "## 5.1 \u2014 Ensemble Path Discovery"
773
- ],
774
- "id": "mtRX8AUxkXs5"
775
- },
776
- {
777
- "cell_type": "code",
778
- "execution_count": null,
779
- "metadata": {
780
- "id": "UJLC_qOFkXs5",
781
- "executionInfo": {
782
- "status": "ok",
783
- "timestamp": 1780309410818,
784
- "user_tz": -330,
785
- "elapsed": 15,
786
- "user": {
787
- "displayName": "Randy Orton",
788
- "userId": "11725427482974841050"
789
- }
790
- }
791
- },
792
- "outputs": [],
793
- "source": [
794
- "def get_ensemble_paths(model_name):\n",
795
- " \"\"\"\n",
796
- " Locate all saved `.keras` checkpoint files for a given architecture.\n",
797
- "\n",
798
- " Searches `MODEL_DIR/ensembles_<model_name>/` first, then falls back to\n",
799
- " `MODEL_DIR/ensembles_old/` if the primary directory is empty.\n",
800
- "\n",
801
- " Parameters\n",
802
- " ----------\n",
803
- " model_name : str Architecture identifier (e.g. 'AlexNet_CNN').\n",
804
- "\n",
805
- " Returns\n",
806
- " -------\n",
807
- " list[str] Absolute paths to matching checkpoint files.\n",
808
- " \"\"\"\n",
809
- " primary_pattern = str(MODEL_DIR / f\"{model_name}_ens_*_final.keras\")\n",
810
- " paths = glob.glob(primary_pattern)\n",
811
- "\n",
812
- " if not paths: # Fallback to legacy directory\n",
813
- " fallback_pattern = str(MODEL_DIR / \"ensembles_old\" / f\"{model_name}_ens_*_final.keras\")\n",
814
- " paths = glob.glob(fallback_pattern)\n",
815
- "\n",
816
- " return paths"
817
- ],
818
- "id": "UJLC_qOFkXs5"
819
- },
820
- {
821
- "cell_type": "markdown",
822
- "metadata": {
823
- "id": "CzNXiivkkXs5"
824
- },
825
- "source": [
826
- "## 5.2 \u2014 Homogeneous Ensemble Evaluation (CreDE)\n",
827
- "\n",
828
- "Loads each ensemble member sequentially, predicts on the full scene,\n",
829
- "then computes credal bounds (p_min / p_max) to derive aleatoric uncertainty (AU),\n",
830
- "epistemic uncertainty (EU), and total uncertainty (TU).\n",
831
- "Each model is deleted immediately after prediction to minimise peak RAM usage."
832
- ],
833
- "id": "CzNXiivkkXs5"
834
- },
835
- {
836
- "cell_type": "code",
837
- "execution_count": null,
838
- "metadata": {
839
- "id": "DYfcyGmdkXs5",
840
- "executionInfo": {
841
- "status": "ok",
842
- "timestamp": 1780309410822,
843
- "user_tz": -330,
844
- "elapsed": 2,
845
- "user": {
846
- "displayName": "Randy Orton",
847
- "userId": "11725427482974841050"
848
- }
849
- }
850
- },
851
- "outputs": [],
852
- "source": [
853
- "def evaluate_homogeneous_ensemble(model_paths, input_data, batch_size=2048):\n",
854
- " \"\"\"\n",
855
- " Run CreDE inference over a list of ensemble checkpoints.\n",
856
- "\n",
857
- " For each model: predict \u2192 stack \u2192 compute credal bounds \u2192 derive uncertainty measures.\n",
858
- "\n",
859
- " Parameters\n",
860
- " ----------\n",
861
- " model_paths : list[str] Paths to `.keras` checkpoint files.\n",
862
- " input_data : np.ndarray Full-scene patch array, shape (N, P, P, B).\n",
863
- " batch_size : int Inference batch size (default 2048).\n",
864
- "\n",
865
- " Returns\n",
866
- " -------\n",
867
- " pred_class : np.ndarray Shape (N,) \u2014 argmax of credal mean probabilities.\n",
868
- " p_star : np.ndarray Shape (N, C) \u2014 normalised lower credal probabilities.\n",
869
- " au : np.ndarray Shape (N,) \u2014 aleatoric uncertainty (entropy of p_star).\n",
870
- " eu : np.ndarray Shape (N,) \u2014 epistemic uncertainty (mean credal spread).\n",
871
- " tu : np.ndarray Shape (N,) \u2014 total uncertainty (au + eu).\n",
872
- " \"\"\"\n",
873
- " all_preds = []\n",
874
- "\n",
875
- " for path in model_paths:\n",
876
- " print(f\" -> Loading & predicting: {Path(path).name}\")\n",
877
- " model = tf.keras.models.load_model(\n",
878
- " path, compile=False, custom_objects=CUSTOM_OBJECTS, safe_mode=False\n",
879
- " )\n",
880
- " preds = model.predict(input_data, batch_size=batch_size, verbose=1)\n",
881
- " all_preds.append(preds)\n",
882
- "\n",
883
- " # Delete model immediately to free RAM before loading the next member\n",
884
- " del model\n",
885
- " tf.keras.backend.clear_session()\n",
886
- " gc.collect()\n",
887
- "\n",
888
- " print(\" -> Computing credal bounds...\")\n",
889
- " stacked_preds = tf.stack(all_preds, axis=0) # (M, N, C)\n",
890
- " p_min = tf.reduce_min(stacked_preds, axis=0)\n",
891
- " p_max = tf.reduce_max(stacked_preds, axis=0)\n",
892
- "\n",
893
- " delta_p = p_max - p_min\n",
894
- "\n",
895
- " # Normalise lower credal probabilities to form p_star\n",
896
- " p_star = p_min / (tf.reduce_sum(p_min, axis=-1, keepdims=True) + 1e-12)\n",
897
- " p_star = np.clip(p_star, 1e-12, 1.0)\n",
898
- "\n",
899
- " # Uncertainty decomposition\n",
900
- " au = -np.sum(p_star * np.log(p_star), axis=-1) # aleatoric: entropy\n",
901
- " eu = np.mean(delta_p, axis=-1) # epistemic: mean credal spread\n",
902
- " tu = au + eu # total uncertainty\n",
903
- "\n",
904
- " pred_class = np.argmax(p_star, axis=-1)\n",
905
- "\n",
906
- " # Coerce any TF tensors to numpy\n",
907
- " to_np = lambda t: t.numpy() if hasattr(t, 'numpy') else t\n",
908
- " return to_np(pred_class), to_np(p_star), to_np(au), to_np(eu), to_np(tu)"
909
- ],
910
- "id": "DYfcyGmdkXs5"
911
- },
912
- {
913
- "cell_type": "markdown",
914
- "metadata": {
915
- "id": "0_BcMa2UkXs5"
916
- },
917
- "source": [
918
- "# 6.0 \u2014 Visualisation Engine\n",
919
- "\n",
920
- "Generates and saves a standardised 6-panel figure for each architecture:\n",
921
- "base prediction map, binary certain/uncertain mask, pixel-count bar chart,\n",
922
- "and three grey-overlay maps (AU, EU, TU)."
923
- ],
924
- "id": "0_BcMa2UkXs5"
925
- },
926
- {
927
- "cell_type": "markdown",
928
- "metadata": {
929
- "id": "lpwmFGLBkXs5"
930
- },
931
- "source": [
932
- "## 6.1 \u2014 6-Panel Spatial Mapping Function"
933
- ],
934
- "id": "lpwmFGLBkXs5"
935
- },
936
- {
937
- "cell_type": "code",
938
- "execution_count": null,
939
- "metadata": {
940
- "id": "CjzaWpWEkXs6",
941
- "executionInfo": {
942
- "status": "ok",
943
- "timestamp": 1780309410825,
944
- "user_tz": -330,
945
- "elapsed": 1,
946
- "user": {
947
- "displayName": "Randy Orton",
948
- "userId": "11725427482974841050"
949
- }
950
- }
951
- },
952
- "outputs": [],
953
- "source": [
954
- "# Shared colour palette \u2014 same bold, saturated base used across the other\n",
955
- "# notebooks, auto-extended with additional bold shades (same style) if a\n",
956
- "# future dataset has more than 10 classes.\n",
957
- "import colorsys\n",
958
- "\n",
959
- "CLASS_COLOR_BASE = [\n",
960
- " '#0000FF', '#00FF00', '#FF0000', '#00FFFF', '#FF00FF',\n",
961
- " '#FFFF00', '#A52A2A', '#FFA500', '#7FFF00', '#8A2BE2'\n",
962
- "]\n",
963
- "UNCERTAIN_COLOR = '#808080'\n",
964
- "\n",
965
- "def _generate_extra_shades(n_extra, hue_offset=0.05, sat=0.85, val=0.95):\n",
966
- " \"\"\"Bold, highly-saturated extra hues in the same style as CLASS_COLOR_BASE.\"\"\"\n",
967
- " shades = []\n",
968
- " for i in range(n_extra):\n",
969
- " hue = (hue_offset + i / max(n_extra, 1)) % 1.0\n",
970
- " r, g, b = colorsys.hsv_to_rgb(hue, sat, val)\n",
971
- " shades.append('#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)))\n",
972
- " return shades\n",
973
- "\n",
974
- "def get_class_colors(n):\n",
975
- " \"\"\"Return n distinct hex colours, extending the base 10 with additional bold\n",
976
- " shades in the same saturated style if needed.\"\"\"\n",
977
- " if n <= len(CLASS_COLOR_BASE):\n",
978
- " return CLASS_COLOR_BASE[:n]\n",
979
- " return CLASS_COLOR_BASE + _generate_extra_shades(n - len(CLASS_COLOR_BASE))\n",
980
- "\n",
981
- "def resolve_uncertain_color(class_colors, preferred=UNCERTAIN_COLOR, min_dist=60):\n",
982
- " \"\"\"Return `preferred` unless it visually clashes with any class colour, in\n",
983
- " which case fall back to a distinct dark slate so 'Uncertain' never collides\n",
984
- " with a real class colour.\"\"\"\n",
985
- " def _hex_to_rgb(h):\n",
986
- " h = h.lstrip('#')\n",
987
- " return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))\n",
988
- " pr, pg, pb = _hex_to_rgb(preferred)\n",
989
- " for c in class_colors:\n",
990
- " cr, cg, cb = _hex_to_rgb(c)\n",
991
- " dist = ((pr - cr) ** 2 + (pg - cg) ** 2 + (pb - cb) ** 2) ** 0.5\n",
992
- " if dist < min_dist:\n",
993
- " return '#2F2F2F'\n",
994
- " return preferred\n",
995
- "\n",
996
- "def generate_spatial_crede_maps(\n",
997
- " model_name, pred_class_scene, p_star_scene, au_scene, eu_scene, tu_scene,\n",
998
- " H, W, au_thresh=0.5, eu_thresh=0.2, tu_thresh=0.7\n",
999
- "):\n",
1000
- " # H, W are always supplied explicitly by the caller (scene geometry is now\n",
1001
- " # inferred from the .mat cube, so no dataset-specific defaults are baked in here)\n",
1002
- " \"\"\"Produce and save a 3x4 spatial uncertainty figure for CreDE.\"\"\"\n",
1003
- " print(f' -> Generating 3x4 spatial maps for {model_name}...')\n",
1004
- " n_cls = p_star_scene.shape[-1]\n",
1005
- "\n",
1006
- " au_map = au_scene.reshape((H, W))\n",
1007
- " eu_map = eu_scene.reshape((H, W))\n",
1008
- " tu_map = tu_scene.reshape((H, W))\n",
1009
- " pred_map = pred_class_scene.reshape((H, W))\n",
1010
- "\n",
1011
- " au_mask = (au_map > au_thresh).astype(int)\n",
1012
- " eu_mask = (eu_map > eu_thresh).astype(int)\n",
1013
- " tu_mask = (tu_map > tu_thresh).astype(int)\n",
1014
- "\n",
1015
- " combined_au = np.where(au_mask == 1, n_cls, pred_map)\n",
1016
- " combined_eu = np.where(eu_mask == 1, n_cls, pred_map)\n",
1017
- " combined_tu = np.where(tu_mask == 1, n_cls, pred_map)\n",
1018
- "\n",
1019
- " class_colors = get_class_colors(n_cls)\n",
1020
- " uncertain_color = resolve_uncertain_color(class_colors)\n",
1021
- " cmap_base = ListedColormap(class_colors)\n",
1022
- " cmap_unc = ListedColormap(class_colors + [uncertain_color])\n",
1023
- " cmap_binary = ListedColormap(['#FFFF00', '#001F3F'])\n",
1024
- "\n",
1025
- " bar_lbls = [f'Class {i}' for i in range(n_cls)] + ['Uncertain']\n",
1026
- " bar_cols = class_colors + [uncertain_color]\n",
1027
- "\n",
1028
- " fig, axes = plt.subplots(3, 4, figsize=(38, 26))\n",
1029
- " fig.suptitle(f'{model_name} \u2014 CreDE Uncertainty Maps (Absolute Thresholds)',\n",
1030
- " fontsize=24, fontweight='bold', y=0.99)\n",
1031
- "\n",
1032
- " # \u2500\u2500 Row 0: Base prediction + 3 binary maps\n",
1033
- " axes[0, 0].imshow(pred_map, cmap=cmap_base, vmin=0, vmax=n_cls - 1)\n",
1034
- " axes[0, 0].set_title('Base Prediction Map', fontsize=15)\n",
1035
- " axes[0, 0].axis('off')\n",
1036
- "\n",
1037
- " binary_specs = [\n",
1038
- " (axes[0, 1], au_mask, f'Aleatoric (AU > {au_thresh})'),\n",
1039
- " (axes[0, 2], eu_mask, f'Epistemic (EU > {eu_thresh})'),\n",
1040
- " (axes[0, 3], tu_mask, f'Total (TU > {tu_thresh})'),\n",
1041
- " ]\n",
1042
- " for ax, mask, label in binary_specs:\n",
1043
- " ax.imshow(mask, cmap=cmap_binary, vmin=0, vmax=1)\n",
1044
- " ax.set_title(f'Certain vs Uncertain\\n{label}', fontsize=15, pad=10)\n",
1045
- " ax.axis('off')\n",
1046
- " ax.legend(\n",
1047
- " handles=[Patch(facecolor='#FFFF00', label='Certain'),\n",
1048
- " Patch(facecolor='#001F3F', label='Uncertain')],\n",
1049
- " loc='upper left', bbox_to_anchor=(0.0, -0.02), borderaxespad=0,\n",
1050
- " fontsize=11, framealpha=0.9, ncol=2\n",
1051
- " )\n",
1052
- "\n",
1053
- " # \u2500\u2500 Row 1: Grey overlay maps (AU, EU, TU) + blank\n",
1054
- " overlay_specs = [\n",
1055
- " (axes[1, 0], combined_au, f'Aleatoric (AU > {au_thresh})'),\n",
1056
- " (axes[1, 1], combined_eu, f'Epistemic (EU > {eu_thresh})'),\n",
1057
- " (axes[1, 2], combined_tu, f'Total (TU > {tu_thresh})'),\n",
1058
- " ]\n",
1059
- " for ax, combined, label in overlay_specs:\n",
1060
- " ax.imshow(combined, cmap=cmap_unc, vmin=0, vmax=n_cls)\n",
1061
- " ax.set_title(f'Grey Overlay \u2014 {label}', fontsize=15, pad=10)\n",
1062
- " ax.axis('off')\n",
1063
- "\n",
1064
- " axes[1, 3].axis('off')\n",
1065
- "\n",
1066
- " # \u2500\u2500 Row 2: Bar charts (AU, EU, TU) + blank\n",
1067
- " bar_specs = [\n",
1068
- " (axes[2, 0], combined_au, f'Aleatoric (AU > {au_thresh})'),\n",
1069
- " (axes[2, 1], combined_eu, f'Epistemic (EU > {eu_thresh})'),\n",
1070
- " (axes[2, 2], combined_tu, f'Total (TU > {tu_thresh})'),\n",
1071
- " ]\n",
1072
- " for ax, combined, label in bar_specs:\n",
1073
- " uniq, cnt = np.unique(combined, return_counts=True)\n",
1074
- " c_dict = {int(k): int(v) for k, v in zip(uniq, cnt)}\n",
1075
- " bar_vals = [c_dict.get(i, 0) for i in range(n_cls + 1)]\n",
1076
- " ax.bar(bar_lbls, bar_vals, color=bar_cols, edgecolor='black')\n",
1077
- " ax.set_title(f'Pixel Counts \u2014 {label}', fontsize=15, pad=10)\n",
1078
- " ax.tick_params(axis='x', rotation=45, labelsize=11)\n",
1079
- " ax.set_ylabel('Pixel Count', fontsize=12)\n",
1080
- " max_val = max(bar_vals, default=1)\n",
1081
- " for i, v in enumerate(bar_vals):\n",
1082
- " ax.text(i, v + max_val * 0.01,\n",
1083
- " f'{v:,}', ha='center', va='bottom', fontweight='bold', fontsize=9)\n",
1084
- " ax.set_ylim(0, max_val * 1.12)\n",
1085
- "\n",
1086
- " axes[2, 3].axis('off')\n",
1087
- "\n",
1088
- " plt.tight_layout(rect=[0, 0.03, 1, 0.98])\n",
1089
- " save_path = CREDE_OUT_DIR / f'{model_name}_CreDE_spatial_maps.png'\n",
1090
- " plt.savefig(save_path, dpi=300, bbox_inches='tight')\n",
1091
- " plt.show()\n",
1092
- " print(f' -> Saved: {save_path}')\n",
1093
- " return str(save_path)"
1094
- ],
1095
- "id": "CjzaWpWEkXs6"
1096
- },
1097
- {
1098
- "cell_type": "markdown",
1099
- "metadata": {
1100
- "id": "zOIG2k38kXs6"
1101
- },
1102
- "source": [
1103
- "# 7.0 \u2014 Main Execution\n",
1104
- "\n",
1105
- "Iterates over each target architecture, runs CreDE inference, generates spatial maps,\n",
1106
- "and accumulates per-architecture metrics into a master summary CSV.\n",
1107
- "RAM is wiped aggressively after every architecture to prevent OOM errors."
1108
- ],
1109
- "id": "zOIG2k38kXs6"
1110
- },
1111
- {
1112
- "cell_type": "code",
1113
- "source": [
1114
- "# \u2500\u2500 Excel Export Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1115
- "_HDR_FILL = PatternFill('solid', start_color='1F4E79')\n",
1116
- "_ALT_FILL = PatternFill('solid', start_color='D6E4F0')\n",
1117
- "_HDR_FONT = Font(name='Arial', bold=True, color='FFFFFF', size=11)\n",
1118
- "_BODY_FONT = Font(name='Arial', size=10)\n",
1119
- "_TITLE_FONT = Font(name='Arial', bold=True, size=13)\n",
1120
- "_CENTER = Alignment(horizontal='center', vertical='center', wrap_text=True)\n",
1121
- "_LEFT = Alignment(horizontal='left', vertical='center')\n",
1122
- "_THIN_SIDE = Side(style='thin', color='AAAAAA')\n",
1123
- "_THIN_BORDER= Border(left=_THIN_SIDE, right=_THIN_SIDE,\n",
1124
- " top=_THIN_SIDE, bottom=_THIN_SIDE)\n",
1125
- "\n",
1126
- "def _style_header_row(ws, row, col_start, col_end):\n",
1127
- " for c in range(col_start, col_end + 1):\n",
1128
- " cell = ws.cell(row=row, column=c)\n",
1129
- " cell.fill = _HDR_FILL\n",
1130
- " cell.font = _HDR_FONT\n",
1131
- " cell.alignment = _CENTER\n",
1132
- " cell.border = _THIN_BORDER\n",
1133
- "\n",
1134
- "def _style_data_rows(ws, row_start, row_end, col_start, col_end):\n",
1135
- " for r in range(row_start, row_end + 1):\n",
1136
- " fill = _ALT_FILL if r % 2 == 0 else PatternFill()\n",
1137
- " for c in range(col_start, col_end + 1):\n",
1138
- " cell = ws.cell(row=r, column=c)\n",
1139
- " cell.font = _BODY_FONT\n",
1140
- " cell.fill = fill\n",
1141
- " cell.alignment = _LEFT\n",
1142
- " cell.border = _THIN_BORDER\n",
1143
- "\n",
1144
- "def _write_df_to_sheet(ws, df, start_row=1, start_col=1, title=None):\n",
1145
- " r = start_row\n",
1146
- " if title:\n",
1147
- " cell = ws.cell(row=r, column=start_col, value=title)\n",
1148
- " cell.font = _TITLE_FONT\n",
1149
- " cell.alignment = _LEFT\n",
1150
- " ws.merge_cells(start_row=r, start_column=start_col,\n",
1151
- " end_row=r, end_column=start_col + len(df.columns) - 1)\n",
1152
- " r += 1\n",
1153
- "\n",
1154
- " for j, col_name in enumerate(df.columns, start=start_col):\n",
1155
- " ws.cell(row=r, column=j, value=col_name)\n",
1156
- " _style_header_row(ws, r, start_col, start_col + len(df.columns) - 1)\n",
1157
- " r += 1\n",
1158
- "\n",
1159
- " data_start = r\n",
1160
- " for _, row_data in df.iterrows():\n",
1161
- " for j, val in enumerate(row_data, start=start_col):\n",
1162
- " ws.cell(row=r, column=j, value=round(float(val), 6)\n",
1163
- " if isinstance(val, (float, np.floating)) else val)\n",
1164
- " r += 1\n",
1165
- " _style_data_rows(ws, data_start, r - 1, start_col, start_col + len(df.columns) - 1)\n",
1166
- "\n",
1167
- " for j, col_name in enumerate(df.columns, start=start_col):\n",
1168
- " max_len = max(len(str(col_name)),\n",
1169
- " max((len(str(ws.cell(row=row, column=j).value or ''))\n",
1170
- " for row in range(data_start, r)), default=0))\n",
1171
- " ws.column_dimensions[get_column_letter(j)].width = min(max_len + 4, 40)\n",
1172
- " return r\n",
1173
- "\n",
1174
- "def create_crede_excel_report(out_dir, summary_df, plot_paths):\n",
1175
- " \"\"\"Auto-creates CreDE_Results.xlsx with Summary and Plots.\"\"\"\n",
1176
- " xlsx_path = Path(out_dir) / 'CreDE_Results.xlsx'\n",
1177
- " wb = Workbook()\n",
1178
- "\n",
1179
- " # \u2500\u2500 Sheet 1: Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1180
- " ws1 = wb.active\n",
1181
- " ws1.title = 'CreDE Summary'\n",
1182
- " ws1.row_dimensions[1].height = 30\n",
1183
- " _write_df_to_sheet(ws1, summary_df, start_row=1, title='CreDE \u2014 Inference Summary')\n",
1184
- "\n",
1185
- " # \u2500\u2500 Sheet 2: Plots \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n",
1186
- " if plot_paths:\n",
1187
- " ws2 = wb.create_sheet('Plots')\n",
1188
- " title_cell = ws2.cell(row=1, column=1, value='CreDE \u2014 Spatial Uncertainty Maps')\n",
1189
- " title_cell.font = Font(name='Arial', bold=True, size=14)\n",
1190
- "\n",
1191
- " img_row = 3\n",
1192
- " for label, img_path in plot_paths:\n",
1193
- " if not Path(img_path).exists():\n",
1194
- " continue\n",
1195
- " ws2.cell(row=img_row, column=1, value=label).font = Font(name='Arial', bold=True, size=11)\n",
1196
- " img_row += 1\n",
1197
- "\n",
1198
- " xl_img = XLImage(img_path)\n",
1199
- " orig_w, orig_h = xl_img.width, xl_img.height\n",
1200
- " target_w = 900\n",
1201
- " scale = target_w / orig_w if orig_w > 0 else 1\n",
1202
- " xl_img.width = int(orig_w * scale)\n",
1203
- " xl_img.height = int(orig_h * scale)\n",
1204
- "\n",
1205
- " ws2.add_image(xl_img, f'A{img_row}')\n",
1206
- " ws2.row_dimensions[img_row].height = xl_img.height * 0.75\n",
1207
- " img_row += int(xl_img.height / 15) + 3\n",
1208
- "\n",
1209
- " wb.save(xlsx_path)\n",
1210
- " print(f'\\n\u2705 Excel report saved \u2192 {xlsx_path}')\n",
1211
- " return str(xlsx_path)"
1212
- ],
1213
- "metadata": {
1214
- "id": "VCAvzVzai2Cx",
1215
- "executionInfo": {
1216
- "status": "ok",
1217
- "timestamp": 1780309410855,
1218
- "user_tz": -330,
1219
- "elapsed": 28,
1220
- "user": {
1221
- "displayName": "Randy Orton",
1222
- "userId": "11725427482974841050"
1223
- }
1224
- }
1225
- },
1226
- "id": "VCAvzVzai2Cx",
1227
- "execution_count": null,
1228
- "outputs": []
1229
- },
1230
- {
1231
- "cell_type": "code",
1232
- "source": [
1233
- "# -----------------------------\n",
1234
- "# Metric Helpers\n",
1235
- "# -----------------------------\n",
1236
- "def multiclass_brier_score(y_onehot, y_prob):\n",
1237
- " return float(np.mean(np.sum((y_prob - y_onehot) ** 2, axis=1)))\n",
1238
- "\n",
1239
- "def expected_calibration_error(y_true, y_prob, n_bins=15):\n",
1240
- " confidences, predictions = np.max(y_prob, axis=1), np.argmax(y_prob, axis=1)\n",
1241
- " correct = (predictions == y_true).astype(np.float32)\n",
1242
- " bin_edges = np.linspace(0.0, 1.0, n_bins + 1)\n",
1243
- " ece = 0.0\n",
1244
- " for i in range(n_bins):\n",
1245
- " in_bin = (confidences >= bin_edges[i]) & (confidences <= bin_edges[i+1] if i == n_bins - 1 else confidences < bin_edges[i+1])\n",
1246
- " prop = np.mean(in_bin)\n",
1247
- " if prop > 0:\n",
1248
- " ece += np.abs(np.mean(correct[in_bin]) - np.mean(confidences[in_bin])) * prop\n",
1249
- " return float(ece)"
1250
- ],
1251
- "metadata": {
1252
- "id": "WJp12J3ee27I",
1253
- "executionInfo": {
1254
- "status": "ok",
1255
- "timestamp": 1780309410869,
1256
- "user_tz": -330,
1257
- "elapsed": 30,
1258
- "user": {
1259
- "displayName": "Randy Orton",
1260
- "userId": "11725427482974841050"
1261
- }
1262
- }
1263
- },
1264
- "id": "WJp12J3ee27I",
1265
- "execution_count": null,
1266
- "outputs": []
1267
- },
1268
- {
1269
- "cell_type": "markdown",
1270
- "metadata": {
1271
- "id": "Sfygsyj4kXs6"
1272
- },
1273
- "source": [
1274
- "## 7.1 \u2014 Master Evaluation Loop & Summary Export"
1275
- ],
1276
- "id": "Sfygsyj4kXs6"
1277
- },
1278
- {
1279
- "cell_type": "code",
1280
- "execution_count": null,
1281
- "metadata": {
1282
- "colab": {
1283
- "base_uri": "https://localhost:8080/",
1284
- "height": 0,
1285
- "output_embedded_package_id": "1mLGEUJR_9XlYIqk_TDYaleXtM8GDiIxB"
1286
- },
1287
- "id": "GLzhR-fskXs6",
1288
- "executionInfo": {
1289
- "status": "ok",
1290
- "timestamp": 1780309930687,
1291
- "user_tz": -330,
1292
- "elapsed": 519817,
1293
- "user": {
1294
- "displayName": "Randy Orton",
1295
- "userId": "11725427482974841050"
1296
- }
1297
- },
1298
- "outputId": "4b8fbfd4-23d0-4e9d-a6d6-9b6f380d6393"
1299
- },
1300
- "outputs": [],
1301
- "source": [
1302
- "master_results = []\n",
1303
- "plot_entries = []\n",
1304
- "\n",
1305
- "# Assuming you want to loop over these architectures (edit if you have more)\n",
1306
- "architectures = ['AlexNet_CNN', 'GFNet', 'ViT_UNet']\n",
1307
- "\n",
1308
- "for model_name in architectures:\n",
1309
- " print(f\"\\n{'='*60}\\n Evaluating CreDE: {model_name}\\n{'='*60}\")\n",
1310
- "\n",
1311
- " ensemble_paths = get_ensemble_paths(model_name)\n",
1312
- " if not ensemble_paths:\n",
1313
- " print(f\" -> No ensemble models found for {model_name}. Skipping.\")\n",
1314
- " continue\n",
1315
- "\n",
1316
- " # Step 1: Run homogenous ensemble evaluation\n",
1317
- " pred_class, p_star, au, eu, tu = evaluate_homogeneous_ensemble(\n",
1318
- " ensemble_paths, scene_pixels_scaled, batch_size=2048\n",
1319
- " )\n",
1320
- "\n",
1321
- " # Step 2: Generate 3x4 spatial maps\n",
1322
- " saved_plot_path = generate_spatial_crede_maps(\n",
1323
- " model_name, pred_class, p_star, au, eu, tu,\n",
1324
- " H=H, W=W, au_thresh=AU_THRESH, eu_thresh=EU_THRESH, tu_thresh=TU_THRESH\n",
1325
- " )\n",
1326
- " plot_entries.append((f'{model_name} \u2014 Spatial Uncertainty Maps', saved_plot_path))\n",
1327
- "\n",
1328
- " # Step 3: Accumulate summary metrics\n",
1329
- " unique, counts = np.unique(pred_class, return_counts=True)\n",
1330
- " pixel_counts = dict(zip(unique, counts))\n",
1331
- "\n",
1332
- " master_results.append({\n",
1333
- " \"Model\": f\"{model_name}_CreDE\",\n",
1334
- " \"Mean_AU\": float(np.mean(au)),\n",
1335
- " \"Mean_EU\": float(np.mean(eu)),\n",
1336
- " \"Mean_TU\": float(np.mean(tu)),\n",
1337
- " **{f\"Class_{int(k)}_Pixels\": int(v) for k, v in pixel_counts.items()}\n",
1338
- " })\n",
1339
- "\n",
1340
- " # Step 4: Aggressively free RAM before next architecture\n",
1341
- " del pred_class, p_star, au, eu, tu\n",
1342
- " tf.keras.backend.clear_session()\n",
1343
- " gc.collect()\n",
1344
- "\n",
1345
- "# --- Export master summary & Excel Report ---\n",
1346
- "if master_results:\n",
1347
- " df_summary = pd.DataFrame(master_results)\n",
1348
- "\n",
1349
- " # Save CSV\n",
1350
- " csv_path = CREDE_OUT_DIR / \"CreDE_Master_Summary.csv\"\n",
1351
- " df_summary.to_csv(csv_path, index=False)\n",
1352
- " print(f\"\\nSaved CSV summary \u2192 {csv_path}\")\n",
1353
- " print(df_summary.to_string(index=False))\n",
1354
- "\n",
1355
- " # Save Excel Report\n",
1356
- " create_crede_excel_report(\n",
1357
- " out_dir=CREDE_OUT_DIR,\n",
1358
- " summary_df=df_summary,\n",
1359
- " plot_paths=plot_entries\n",
1360
- " )"
1361
- ],
1362
- "id": "GLzhR-fskXs6"
1363
- }
1364
- ]
1365
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ensemble/results/AlexNet_CNN_CreDE_spatial_maps.png DELETED

Git LFS Details

  • SHA256: 1df7636dc05766d5a07c1370537d5516a1ac4c3da54df70911c5a4028178b853
  • Pointer size: 132 Bytes
  • Size of remote file: 1.19 MB
ensemble/results/AlexNet_CNN_classification_report.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "0": {
3
- "precision": 1.0,
4
- "recall": 1.0,
5
- "f1-score": 1.0,
6
- "support": 2432.0
7
- },
8
- "1": {
9
- "precision": 1.0,
10
- "recall": 1.0,
11
- "f1-score": 1.0,
12
- "support": 1408.0
13
- },
14
- "2": {
15
- "precision": 0.9982363315696648,
16
- "recall": 1.0,
17
- "f1-score": 0.999117387466902,
18
- "support": 566.0
19
- },
20
- "3": {
21
- "precision": 1.0,
22
- "recall": 0.9992671308171491,
23
- "f1-score": 0.999633431085044,
24
- "support": 2729.0
25
- },
26
- "4": {
27
- "precision": 0.9981167608286252,
28
- "recall": 1.0,
29
- "f1-score": 0.9990574929311969,
30
- "support": 3180.0
31
- },
32
- "5": {
33
- "precision": 0.9995276334435522,
34
- "recall": 0.9985842378480415,
35
- "f1-score": 0.9990557129367328,
36
- "support": 2119.0
37
- },
38
- "6": {
39
- "precision": 1.0,
40
- "recall": 0.9924812030075187,
41
- "f1-score": 0.9962264150943396,
42
- "support": 399.0
43
- },
44
- "7": {
45
- "precision": 0.9984399375975039,
46
- "recall": 0.9984399375975039,
47
- "f1-score": 0.9984399375975039,
48
- "support": 641.0
49
- },
50
- "accuracy": 0.9993320469051507,
51
- "macro avg": {
52
- "precision": 0.9992900829299183,
53
- "recall": 0.9985965636587766,
54
- "f1-score": 0.9989412971389648,
55
- "support": 13474.0
56
- },
57
- "weighted avg": {
58
- "precision": 0.9993329462943703,
59
- "recall": 0.9993320469051507,
60
- "f1-score": 0.9993317721236556,
61
- "support": 13474.0
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ensemble/results/AlexNet_CNN_training_curves.png DELETED

Git LFS Details

  • SHA256: 50800ed244747e022146de9e702489c3242e4d75493558834f97a506987e98ee
  • Pointer size: 131 Bytes
  • Size of remote file: 155 kB
ensemble/results/CreDE_Master_Summary.csv DELETED
@@ -1,4 +0,0 @@
1
- Model,Mean_AU,Mean_EU,Mean_TU,Class_0_Pixels,Class_1_Pixels,Class_2_Pixels,Class_3_Pixels,Class_4_Pixels,Class_5_Pixels,Class_6_Pixels,Class_7_Pixels
2
- AlexNet_CNN_CreDE,0.06636760383844376,0.10358242690563202,0.16995003819465637,11053,7894,8013,52201,93056,49752,10015,30160
3
- GFNet_CreDE,0.25558435916900635,0.05023321136832237,0.30581754446029663,11466,7519,9322,50468,94312,48723,9697,30637
4
- ViT_UNet_CreDE,0.6706158518791199,0.08109056949615479,0.7517063617706299,11598,7868,10433,47332,94079,48711,13606,28517
 
 
 
 
 
ensemble/results/CreDE_Results.xlsx DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:79b093f471f87dca87033b0b31ee89becfebdc0b39d74efd274c88aa5502aa94
3
- size 2463242
 
 
 
 
ensemble/results/GFNet_CreDE_spatial_maps.png DELETED

Git LFS Details

  • SHA256: c95dd8edac3da3dd60513ab5a77a1ea275a3b15abf8d3c8daaaa6569bc016404
  • Pointer size: 132 Bytes
  • Size of remote file: 1.18 MB
ensemble/results/GFNet_classification_report.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "0": {
3
- "precision": 1.0,
4
- "recall": 1.0,
5
- "f1-score": 1.0,
6
- "support": 2432.0
7
- },
8
- "1": {
9
- "precision": 1.0,
10
- "recall": 1.0,
11
- "f1-score": 1.0,
12
- "support": 1408.0
13
- },
14
- "2": {
15
- "precision": 1.0,
16
- "recall": 1.0,
17
- "f1-score": 1.0,
18
- "support": 566.0
19
- },
20
- "3": {
21
- "precision": 1.0,
22
- "recall": 1.0,
23
- "f1-score": 1.0,
24
- "support": 2729.0
25
- },
26
- "4": {
27
- "precision": 1.0,
28
- "recall": 1.0,
29
- "f1-score": 1.0,
30
- "support": 3180.0
31
- },
32
- "5": {
33
- "precision": 1.0,
34
- "recall": 1.0,
35
- "f1-score": 1.0,
36
- "support": 2119.0
37
- },
38
- "6": {
39
- "precision": 1.0,
40
- "recall": 1.0,
41
- "f1-score": 1.0,
42
- "support": 399.0
43
- },
44
- "7": {
45
- "precision": 1.0,
46
- "recall": 1.0,
47
- "f1-score": 1.0,
48
- "support": 641.0
49
- },
50
- "accuracy": 1.0,
51
- "macro avg": {
52
- "precision": 1.0,
53
- "recall": 1.0,
54
- "f1-score": 1.0,
55
- "support": 13474.0
56
- },
57
- "weighted avg": {
58
- "precision": 1.0,
59
- "recall": 1.0,
60
- "f1-score": 1.0,
61
- "support": 13474.0
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ensemble/results/GFNet_training_curves.png DELETED

Git LFS Details

  • SHA256: e114a58266644c2ab6a5226f48bf97932d2277e7d9928b90ff95580a873caec9
  • Pointer size: 131 Bytes
  • Size of remote file: 135 kB
ensemble/results/ViT_UNet_CreDE_spatial_maps.png DELETED

Git LFS Details

  • SHA256: 155ba4d2367d3c5302d073e7919f8dc1b1b889ebead18285c4ffd7865898476b
  • Pointer size: 132 Bytes
  • Size of remote file: 1.22 MB
ensemble/results/ViT_UNet_classification_report.json DELETED
@@ -1,63 +0,0 @@
1
- {
2
- "0": {
3
- "precision": 1.0,
4
- "recall": 1.0,
5
- "f1-score": 1.0,
6
- "support": 2432.0
7
- },
8
- "1": {
9
- "precision": 1.0,
10
- "recall": 1.0,
11
- "f1-score": 1.0,
12
- "support": 1408.0
13
- },
14
- "2": {
15
- "precision": 0.9982300884955753,
16
- "recall": 0.9964664310954063,
17
- "f1-score": 0.9973474801061007,
18
- "support": 566.0
19
- },
20
- "3": {
21
- "precision": 1.0,
22
- "recall": 0.9996335654085746,
23
- "f1-score": 0.9998167491295583,
24
- "support": 2729.0
25
- },
26
- "4": {
27
- "precision": 0.9996856334486011,
28
- "recall": 1.0,
29
- "f1-score": 0.9998427920138343,
30
- "support": 3180.0
31
- },
32
- "5": {
33
- "precision": 1.0,
34
- "recall": 0.9995280792826805,
35
- "f1-score": 0.9997639839509087,
36
- "support": 2119.0
37
- },
38
- "6": {
39
- "precision": 0.9975,
40
- "recall": 1.0,
41
- "f1-score": 0.9987484355444305,
42
- "support": 399.0
43
- },
44
- "7": {
45
- "precision": 0.9968847352024922,
46
- "recall": 0.9984399375975039,
47
- "f1-score": 0.9976617303195635,
48
- "support": 641.0
49
- },
50
- "accuracy": 0.9996289149473059,
51
- "macro avg": {
52
- "precision": 0.9990375571433336,
53
- "recall": 0.9992585016730207,
54
- "f1-score": 0.9991476463830495,
55
- "support": 13474.0
56
- },
57
- "weighted avg": {
58
- "precision": 0.9996292236692774,
59
- "recall": 0.9996289149473059,
60
- "f1-score": 0.9996289400050211,
61
- "support": 13474.0
62
- }
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ensemble/results/ViT_UNet_training_curves.png DELETED

Git LFS Details

  • SHA256: 9bccf024742107715863a9c0ff69614d4f93fe80fc91b9c5f07089c2177ce267
  • Pointer size: 131 Bytes
  • Size of remote file: 161 kB
ensemble/results/classification_summary.csv DELETED
@@ -1,16 +0,0 @@
1
- model,capacity_tag,test_accuracy,kappa,macro_f1,weighted_f1,val_nll,test_nll,val_brier,test_brier,test_ece_15bin,epochs_configured,epochs_ran,train_time_sec,best_model_path,final_model_path
2
- GFNet_ens_2,legacy_arch,1.0,1.0,1.0,1.0,0.008650941411156436,0.008394689763386073,0.0003470874198973904,0.00016413560970697484,0.008325216172798567,100,100,1091.3922517279998,/kaggle/working/results/models/ensembles/GFNet_ens_2_final.keras,/kaggle/working/results/models/ensembles/GFNet_ens_2_final.keras
3
- GFNet_ens_1,legacy_arch,1.0,1.0,1.0,1.0,0.007782886691788137,0.007580193458155463,0.00022485345195341052,8.675982552089211e-05,0.007542962872771458,100,100,1093.302967228,/kaggle/working/results/models/GFNet_best.keras,/kaggle/working/results/models/ensembles/GFNet_ens_1_final.keras
4
- GFNet_ens_3,legacy_arch,1.0,1.0,1.0,1.0,0.007835681029395622,0.00770890638266266,0.00015427664263396678,9.220673557648059e-05,0.007669471749499234,100,100,1095.5028445569988,/kaggle/working/results/models/ensembles/GFNet_ens_3_final.keras,/kaggle/working/results/models/ensembles/GFNet_ens_3_final.keras
5
- GFNet_ens_4,legacy_arch,1.0,1.0,1.0,1.0,0.008375646065942278,0.008268151222375824,0.00011287869605898921,8.793159663540437e-05,0.008229851138791716,100,100,1098.8232494029999,/kaggle/working/results/models/ensembles/GFNet_ens_4_final.keras,/kaggle/working/results/models/ensembles/GFNet_ens_4_final.keras
6
- GFNet_ens_5,legacy_arch,1.0,1.0,1.0,1.0,0.008144904275139767,0.007577851225909456,0.00037955447040471246,8.569458569951265e-05,0.007541808385567299,100,100,1096.1566321030004,/kaggle/working/results/models/ensembles/GFNet_ens_5_final.keras,/kaggle/working/results/models/ensembles/GFNet_ens_5_final.keras
7
- ViT_UNet_ens_1,legacy_arch,0.9997031319578448,0.9996423935968177,0.9993130507511258,0.9997032255305212,0.04125208399317739,0.04038559127744248,0.0026588554415351317,0.0022089245494705512,0.038699414931083975,100,100,2127.257505900001,/kaggle/working/results/models/ViT_UNet_best.keras,/kaggle/working/results/models/ensembles/ViT_UNet_ens_1_final.keras
8
- ViT_UNet_ens_4,legacy_arch,0.9997031319578448,0.9996424018799352,0.999287299739852,0.9997033045823546,0.040882048437208815,0.03983519771741158,0.002633379651110601,0.002130986443866403,0.0381665819871845,100,100,2151.865034081002,/kaggle/working/results/models/ensembles/ViT_UNet_ens_4_final.keras,/kaggle/working/results/models/ensembles/ViT_UNet_ens_4_final.keras
9
- ViT_UNet_ens_5,legacy_arch,0.9996289149473059,0.9995529894957109,0.9991476463830495,0.9996289400050211,0.042936784080404175,0.0409576964078693,0.0034046686526971017,0.002444263189115926,0.03892784227386362,100,100,2149.094057154998,/kaggle/working/results/models/ensembles/ViT_UNet_ens_5_final.keras,/kaggle/working/results/models/ensembles/ViT_UNet_ens_5_final.keras
10
- ViT_UNet_ens_3,legacy_arch,0.9996289149473059,0.9995529860759044,0.9989782885709497,0.9996290651977676,0.04151233752284096,0.04109393863130706,0.002767361698916364,0.0024408674943176495,0.03868930220727806,100,100,2108.2808553079976,/kaggle/working/results/models/ensembles/ViT_UNet_ens_3_final.keras,/kaggle/working/results/models/ensembles/ViT_UNet_ens_3_final.keras
11
- AlexNet_CNN_ens_3,legacy_arch,0.9994804809262283,0.9993741415954641,0.9988388569382961,0.9994798515712402,0.0025376154609043646,0.0025376154609043646,0.0007780503567997554,0.0007780503567997554,0.00025354179789939654,100,100,1067.531324263,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_3_final.keras,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_3_final.keras
12
- ViT_UNet_ens_2,legacy_arch,0.9994062639156894,0.9992847664645924,0.9985633253467591,0.9994064198749861,0.04356254463611605,0.04152917083272156,0.003853330990829368,0.002736550655265285,0.038360946907812395,100,100,2130.1647957269997,/kaggle/working/results/models/ensembles/ViT_UNet_ens_2_final.keras,/kaggle/working/results/models/ensembles/ViT_UNet_ens_2_final.keras
13
- AlexNet_CNN_ens_5,legacy_arch,0.9993320469051507,0.9991953279840045,0.9989412971389648,0.9993317721236556,0.0022415306676375002,0.0022415306676375002,0.0010475338113041146,0.0010475338113041146,0.00046385921558888605,100,100,1071.6476408859999,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_5_final.keras,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_5_final.keras
14
- AlexNet_CNN_ens_1,legacy_arch,0.9989609618524565,0.9987483380657164,0.9980484258102253,0.9989604947263638,0.004822081824650907,0.004822081824650907,0.0018765840682375234,0.0018765840682375234,0.00038955023456118803,100,100,1085.3566581199998,/kaggle/working/results/models/AlexNet_CNN_best.keras,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_1_final.keras
15
- AlexNet_CNN_ens_4,legacy_arch,0.9968086685468309,0.9961553150730917,0.9947741655752209,0.9968055596174699,0.01159683983002684,0.01159683983002684,0.005574999536239272,0.005574999536239272,0.0012410734126948985,100,100,1071.6318536030003,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_4_final.keras,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_4_final.keras
16
- AlexNet_CNN_ens_2,legacy_arch,0.9960664984414428,0.9952610255039888,0.9907099898333486,0.9960603671533084,0.013130652896216117,0.013130652896216117,0.006438098751476688,0.006438098751476688,0.001688033413140307,100,100,1069.520384081,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_2_final.keras,/kaggle/working/results/models/ensembles/AlexNet_CNN_ens_2_final.keras
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ensemble/results/combined_initial_classification_overview.png DELETED

Git LFS Details

  • SHA256: 808e73f959970a135cf64868e726a9f4f2df720186523ef3d870bda5f800b432
  • Pointer size: 132 Bytes
  • Size of remote file: 3.82 MB