Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- README.md +33 -7
- app.py +205 -0
- ensemble_config.json +106 -0
- hf_bundle_manifest.json +14 -0
- proposed_lcvc_ensemble_summary.json +34 -0
- requirements.txt +6 -0
- selected_lcvc_ensemble_members.csv +6 -0
README.md
CHANGED
|
@@ -1,13 +1,39 @@
|
|
| 1 |
---
|
| 2 |
-
title: LCVC
|
| 3 |
-
emoji: 🐠
|
| 4 |
-
colorFrom: yellow
|
| 5 |
-
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.14.0
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: LCVC-Ensemble Brain Tumor MRI Classifier
|
|
|
|
|
|
|
|
|
|
| 3 |
sdk: gradio
|
|
|
|
|
|
|
| 4 |
app_file: app.py
|
| 5 |
pinned: false
|
| 6 |
+
license: mit
|
| 7 |
---
|
| 8 |
|
| 9 |
+
# LCVC-Ensemble Brain Tumor MRI Classifier
|
| 10 |
+
|
| 11 |
+
This Space contains a research demonstration of **LCVC-Ensemble**
|
| 12 |
+
(**Leakage-Controlled Validation-Calibrated Multi-Backbone Ensemble**) for four-class brain tumor MRI classification.
|
| 13 |
+
|
| 14 |
+
## Classes
|
| 15 |
+
|
| 16 |
+
glioma, meningioma, notumor, pituitary
|
| 17 |
+
|
| 18 |
+
## Test metrics from the leakage-aware evaluation protocol
|
| 19 |
+
|
| 20 |
+
- Accuracy: 0.9887640449438202
|
| 21 |
+
- Macro-F1: 0.9887621278421996
|
| 22 |
+
- Balanced accuracy: 0.9887638076313365
|
| 23 |
+
- Macro-AUC OVR: 0.9997131229291261
|
| 24 |
+
|
| 25 |
+
## Ensemble
|
| 26 |
+
|
| 27 |
+
The app loads `5` selected checkpoints and performs weighted averaging of temperature-scaled softmax probabilities.
|
| 28 |
+
|
| 29 |
+
## Intended use
|
| 30 |
+
|
| 31 |
+
Research and educational demonstration only.
|
| 32 |
+
|
| 33 |
+
## Medical disclaimer
|
| 34 |
+
|
| 35 |
+
This is **not a medical device**. It must not be used for diagnosis, treatment, triage, or clinical decision-making.
|
| 36 |
+
|
| 37 |
+
## Reproducibility note
|
| 38 |
+
|
| 39 |
+
The ensemble was selected using validation Macro-F1 only. Test labels were used only for final reporting after model selection.
|
app.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from torchvision import models, transforms
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
ROOT = Path(__file__).resolve().parent
|
| 15 |
+
CONFIG_PATH = ROOT / "ensemble_config.json"
|
| 16 |
+
|
| 17 |
+
with open(CONFIG_PATH, "r") as f:
|
| 18 |
+
CFG = json.load(f)
|
| 19 |
+
|
| 20 |
+
CLASS_NAMES = CFG["classes"]
|
| 21 |
+
NUM_CLASSES = int(CFG["num_classes"])
|
| 22 |
+
IMAGE_SIZE = int(CFG["image_size"])
|
| 23 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def replace_classifier(model, model_name, num_classes):
|
| 27 |
+
if model_name == "vgg16_bn":
|
| 28 |
+
in_features = model.classifier[-1].in_features
|
| 29 |
+
model.classifier[-1] = nn.Linear(in_features, num_classes)
|
| 30 |
+
|
| 31 |
+
elif model_name == "densenet121":
|
| 32 |
+
in_features = model.classifier.in_features
|
| 33 |
+
model.classifier = nn.Linear(in_features, num_classes)
|
| 34 |
+
|
| 35 |
+
elif model_name == "efficientnet_b0":
|
| 36 |
+
in_features = model.classifier[-1].in_features
|
| 37 |
+
model.classifier[-1] = nn.Linear(in_features, num_classes)
|
| 38 |
+
|
| 39 |
+
elif model_name == "mobilenet_v3_small":
|
| 40 |
+
in_features = model.classifier[-1].in_features
|
| 41 |
+
model.classifier[-1] = nn.Linear(in_features, num_classes)
|
| 42 |
+
|
| 43 |
+
elif model_name == "convnext_tiny":
|
| 44 |
+
in_features = model.classifier[-1].in_features
|
| 45 |
+
model.classifier[-1] = nn.Linear(in_features, num_classes)
|
| 46 |
+
|
| 47 |
+
else:
|
| 48 |
+
raise ValueError(f"Unknown model name: {model_name}")
|
| 49 |
+
|
| 50 |
+
return model
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def build_model(model_name, num_classes):
|
| 54 |
+
if model_name == "vgg16_bn":
|
| 55 |
+
model = models.vgg16_bn(weights=None)
|
| 56 |
+
|
| 57 |
+
elif model_name == "densenet121":
|
| 58 |
+
model = models.densenet121(weights=None)
|
| 59 |
+
|
| 60 |
+
elif model_name == "efficientnet_b0":
|
| 61 |
+
model = models.efficientnet_b0(weights=None)
|
| 62 |
+
|
| 63 |
+
elif model_name == "mobilenet_v3_small":
|
| 64 |
+
model = models.mobilenet_v3_small(weights=None)
|
| 65 |
+
|
| 66 |
+
elif model_name == "convnext_tiny":
|
| 67 |
+
model = models.convnext_tiny(weights=None)
|
| 68 |
+
|
| 69 |
+
else:
|
| 70 |
+
raise ValueError(f"Unknown model name: {model_name}")
|
| 71 |
+
|
| 72 |
+
return replace_classifier(model, model_name, num_classes)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def load_state_dict_safely(path):
|
| 76 |
+
try:
|
| 77 |
+
ckpt = torch.load(path, map_location="cpu", weights_only=True)
|
| 78 |
+
except TypeError:
|
| 79 |
+
ckpt = torch.load(path, map_location="cpu")
|
| 80 |
+
|
| 81 |
+
if isinstance(ckpt, dict):
|
| 82 |
+
for key in ["model_state_dict", "state_dict", "model"]:
|
| 83 |
+
if key in ckpt and isinstance(ckpt[key], dict):
|
| 84 |
+
ckpt = ckpt[key]
|
| 85 |
+
break
|
| 86 |
+
|
| 87 |
+
cleaned = {}
|
| 88 |
+
|
| 89 |
+
for k, v in ckpt.items():
|
| 90 |
+
nk = k[7:] if str(k).startswith("module.") else k
|
| 91 |
+
cleaned[nk] = v
|
| 92 |
+
|
| 93 |
+
return cleaned
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def load_ensemble():
|
| 97 |
+
loaded = []
|
| 98 |
+
|
| 99 |
+
for member in CFG["members"]:
|
| 100 |
+
model_name = member["model"]
|
| 101 |
+
ckpt_path = ROOT / member["checkpoint_file"]
|
| 102 |
+
|
| 103 |
+
model = build_model(model_name, NUM_CLASSES)
|
| 104 |
+
state = load_state_dict_safely(ckpt_path)
|
| 105 |
+
model.load_state_dict(state, strict=True)
|
| 106 |
+
model.to(DEVICE)
|
| 107 |
+
model.eval()
|
| 108 |
+
|
| 109 |
+
loaded.append({
|
| 110 |
+
"model": model,
|
| 111 |
+
"display_name": member.get("display_name", model_name),
|
| 112 |
+
"seed": member.get("seed"),
|
| 113 |
+
"weight": float(member["weight"]),
|
| 114 |
+
"temperature": max(float(member["temperature"]), 1e-8),
|
| 115 |
+
})
|
| 116 |
+
|
| 117 |
+
weight_sum = sum(m["weight"] for m in loaded)
|
| 118 |
+
|
| 119 |
+
if weight_sum <= 0:
|
| 120 |
+
for m in loaded:
|
| 121 |
+
m["weight"] = 1.0 / len(loaded)
|
| 122 |
+
else:
|
| 123 |
+
for m in loaded:
|
| 124 |
+
m["weight"] /= weight_sum
|
| 125 |
+
|
| 126 |
+
return loaded
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
ENSEMBLE = load_ensemble()
|
| 130 |
+
|
| 131 |
+
PREPROCESS = transforms.Compose([
|
| 132 |
+
transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),
|
| 133 |
+
transforms.ToTensor(),
|
| 134 |
+
transforms.Normalize(
|
| 135 |
+
mean=CFG["preprocessing"]["normalization_mean"],
|
| 136 |
+
std=CFG["preprocessing"]["normalization_std"],
|
| 137 |
+
),
|
| 138 |
+
])
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
@torch.no_grad()
|
| 142 |
+
def predict(image):
|
| 143 |
+
if image is None:
|
| 144 |
+
return None, "Please upload an MRI image."
|
| 145 |
+
|
| 146 |
+
if not isinstance(image, Image.Image):
|
| 147 |
+
image = Image.fromarray(image)
|
| 148 |
+
|
| 149 |
+
image = image.convert("RGB")
|
| 150 |
+
x = PREPROCESS(image).unsqueeze(0).to(DEVICE)
|
| 151 |
+
|
| 152 |
+
final_probs = torch.zeros((1, NUM_CLASSES), dtype=torch.float32, device=DEVICE)
|
| 153 |
+
member_lines = []
|
| 154 |
+
|
| 155 |
+
for member in ENSEMBLE:
|
| 156 |
+
logits = member["model"](x)
|
| 157 |
+
probs = F.softmax(logits / member["temperature"], dim=1)
|
| 158 |
+
final_probs += member["weight"] * probs
|
| 159 |
+
|
| 160 |
+
top_prob, top_idx = torch.max(probs, dim=1)
|
| 161 |
+
|
| 162 |
+
member_lines.append(
|
| 163 |
+
f"{member['display_name']} seed {member['seed']}: "
|
| 164 |
+
f"{CLASS_NAMES[int(top_idx.item())]} ({float(top_prob.item()):.4f})"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
final_probs_np = final_probs.squeeze(0).detach().cpu().numpy()
|
| 168 |
+
pred_idx = int(np.argmax(final_probs_np))
|
| 169 |
+
pred_class = CLASS_NAMES[pred_idx]
|
| 170 |
+
pred_conf = float(final_probs_np[pred_idx])
|
| 171 |
+
|
| 172 |
+
label_scores = {
|
| 173 |
+
CLASS_NAMES[i]: float(final_probs_np[i])
|
| 174 |
+
for i in range(NUM_CLASSES)
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
details = (
|
| 178 |
+
f"Predicted class: {pred_class}\n"
|
| 179 |
+
f"Calibrated ensemble confidence: {pred_conf:.4f}\n\n"
|
| 180 |
+
"Member predictions:\n"
|
| 181 |
+
+ "\n".join(member_lines)
|
| 182 |
+
+ "\n\nDisclaimer: This tool is for research and educational demonstration only. "
|
| 183 |
+
"It is not a medical device and must not be used for diagnosis or treatment."
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
return label_scores, details
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
demo = gr.Interface(
|
| 190 |
+
fn=predict,
|
| 191 |
+
inputs=gr.Image(type="pil", label="Upload MRI image"),
|
| 192 |
+
outputs=[
|
| 193 |
+
gr.Label(num_top_classes=NUM_CLASSES, label="Calibrated ensemble probabilities"),
|
| 194 |
+
gr.Textbox(label="Prediction details"),
|
| 195 |
+
],
|
| 196 |
+
title="LCVC-Ensemble Brain Tumor MRI Classifier",
|
| 197 |
+
description=(
|
| 198 |
+
"Leakage-Controlled Validation-Calibrated Multi-Backbone Ensemble. "
|
| 199 |
+
"Research demonstration only; not for clinical use."
|
| 200 |
+
),
|
| 201 |
+
flagging_mode="never",
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
if __name__ == "__main__":
|
| 205 |
+
demo.launch()
|
ensemble_config.json
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"proposed_model": "LCVC-Ensemble",
|
| 3 |
+
"full_name": "Leakage-Controlled Validation-Calibrated Multi-Backbone Ensemble",
|
| 4 |
+
"task": "Brain tumor MRI four-class classification",
|
| 5 |
+
"classes": [
|
| 6 |
+
"glioma",
|
| 7 |
+
"meningioma",
|
| 8 |
+
"notumor",
|
| 9 |
+
"pituitary"
|
| 10 |
+
],
|
| 11 |
+
"num_classes": 4,
|
| 12 |
+
"image_size": 224,
|
| 13 |
+
"preprocessing": {
|
| 14 |
+
"input_mode": "RGB",
|
| 15 |
+
"resize": [
|
| 16 |
+
224,
|
| 17 |
+
224
|
| 18 |
+
],
|
| 19 |
+
"normalization_mean": [
|
| 20 |
+
0.485,
|
| 21 |
+
0.456,
|
| 22 |
+
0.406
|
| 23 |
+
],
|
| 24 |
+
"normalization_std": [
|
| 25 |
+
0.229,
|
| 26 |
+
0.224,
|
| 27 |
+
0.225
|
| 28 |
+
]
|
| 29 |
+
},
|
| 30 |
+
"ensemble_rule": "weighted_average_of_temperature_scaled_softmax_probabilities",
|
| 31 |
+
"selection_rule": "highest validation Macro-F1 among predeclared ensembles; no test labels used for selection",
|
| 32 |
+
"strategy": "top5_by_checkpoint_val_macro_f1",
|
| 33 |
+
"calibrated_probs": true,
|
| 34 |
+
"weight_mode": "uniform",
|
| 35 |
+
"num_members": 5,
|
| 36 |
+
"members": [
|
| 37 |
+
{
|
| 38 |
+
"member_order": 0,
|
| 39 |
+
"model": "convnext_tiny",
|
| 40 |
+
"display_name": "ConvNeXt-Tiny",
|
| 41 |
+
"seed": 123,
|
| 42 |
+
"weight": 0.2,
|
| 43 |
+
"temperature": 1.3677136307807327,
|
| 44 |
+
"checkpoint_file": "checkpoints/best_convnext_tiny_seed123.pt",
|
| 45 |
+
"checkpoint_val_macro_f1": 0.9870486143666763,
|
| 46 |
+
"test_cal_macro_f1": 0.9747367912288595
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"member_order": 1,
|
| 50 |
+
"model": "efficientnet_b0",
|
| 51 |
+
"display_name": "EfficientNet-B0",
|
| 52 |
+
"seed": 123,
|
| 53 |
+
"weight": 0.2,
|
| 54 |
+
"temperature": 1.3258544405019956,
|
| 55 |
+
"checkpoint_file": "checkpoints/best_efficientnet_b0_seed123.pt",
|
| 56 |
+
"checkpoint_val_macro_f1": 0.9842404596100688,
|
| 57 |
+
"test_cal_macro_f1": 0.984111907683999
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"member_order": 2,
|
| 61 |
+
"model": "efficientnet_b0",
|
| 62 |
+
"display_name": "EfficientNet-B0",
|
| 63 |
+
"seed": 2026,
|
| 64 |
+
"weight": 0.2,
|
| 65 |
+
"temperature": 1.2086706764615134,
|
| 66 |
+
"checkpoint_file": "checkpoints/best_efficientnet_b0_seed2026.pt",
|
| 67 |
+
"checkpoint_val_macro_f1": 0.9842174297345077,
|
| 68 |
+
"test_cal_macro_f1": 0.9802870622455948
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"member_order": 3,
|
| 72 |
+
"model": "efficientnet_b0",
|
| 73 |
+
"display_name": "EfficientNet-B0",
|
| 74 |
+
"seed": 42,
|
| 75 |
+
"weight": 0.2,
|
| 76 |
+
"temperature": 1.4470591581130952,
|
| 77 |
+
"checkpoint_file": "checkpoints/best_efficientnet_b0_seed42.pt",
|
| 78 |
+
"checkpoint_val_macro_f1": 0.9835034785528444,
|
| 79 |
+
"test_cal_macro_f1": 0.9859513817770005
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"member_order": 4,
|
| 83 |
+
"model": "convnext_tiny",
|
| 84 |
+
"display_name": "ConvNeXt-Tiny",
|
| 85 |
+
"seed": 42,
|
| 86 |
+
"weight": 0.2,
|
| 87 |
+
"temperature": 1.0611162444290951,
|
| 88 |
+
"checkpoint_file": "checkpoints/best_convnext_tiny_seed42.pt",
|
| 89 |
+
"checkpoint_val_macro_f1": 0.9824939017733962,
|
| 90 |
+
"test_cal_macro_f1": 0.9831484695235262
|
| 91 |
+
}
|
| 92 |
+
],
|
| 93 |
+
"validation_metrics": {
|
| 94 |
+
"accuracy": 0.9916201117318436,
|
| 95 |
+
"macro_f1": 0.9917075605938603,
|
| 96 |
+
"balanced_accuracy": 0.9916862142328199,
|
| 97 |
+
"macro_auc_ovr": 0.9997306138031021
|
| 98 |
+
},
|
| 99 |
+
"test_metrics": {
|
| 100 |
+
"accuracy": 0.9887640449438202,
|
| 101 |
+
"macro_f1": 0.9887621278421996,
|
| 102 |
+
"balanced_accuracy": 0.9887638076313365,
|
| 103 |
+
"macro_auc_ovr": 0.9997131229291261
|
| 104 |
+
},
|
| 105 |
+
"medical_disclaimer": "This app is for research and educational demonstration only. It is not a medical device and must not be used for diagnosis, treatment, or clinical decision-making."
|
| 106 |
+
}
|
hf_bundle_manifest.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bundle_type": "huggingface_space_upload",
|
| 3 |
+
"contains_checkpoints": true,
|
| 4 |
+
"num_checkpoints": 5,
|
| 5 |
+
"files": [
|
| 6 |
+
"app.py",
|
| 7 |
+
"requirements.txt",
|
| 8 |
+
"README.md",
|
| 9 |
+
"ensemble_config.json",
|
| 10 |
+
"selected_lcvc_ensemble_members.csv",
|
| 11 |
+
"proposed_lcvc_ensemble_summary.json",
|
| 12 |
+
"checkpoints/*.pt"
|
| 13 |
+
]
|
| 14 |
+
}
|
proposed_lcvc_ensemble_summary.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"proposed_model": "LCVC_Ensemble",
|
| 3 |
+
"selection_rule": "highest validation Macro-F1 among predeclared ensembles; no test labels used for selection",
|
| 4 |
+
"strategy": "top5_by_checkpoint_val_macro_f1",
|
| 5 |
+
"calibrated_probs": true,
|
| 6 |
+
"weight_mode": "uniform",
|
| 7 |
+
"num_members": 5,
|
| 8 |
+
"class_names": [
|
| 9 |
+
"glioma",
|
| 10 |
+
"meningioma",
|
| 11 |
+
"notumor",
|
| 12 |
+
"pituitary"
|
| 13 |
+
],
|
| 14 |
+
"validation_metrics": {
|
| 15 |
+
"accuracy": 0.9916201117318436,
|
| 16 |
+
"macro_f1": 0.9917075605938603,
|
| 17 |
+
"balanced_accuracy": 0.9916862142328199,
|
| 18 |
+
"macro_auc_ovr": 0.9997306138031021
|
| 19 |
+
},
|
| 20 |
+
"test_metrics": {
|
| 21 |
+
"accuracy": 0.9887640449438202,
|
| 22 |
+
"macro_f1": 0.9887621278421996,
|
| 23 |
+
"balanced_accuracy": 0.9887638076313365,
|
| 24 |
+
"macro_auc_ovr": 0.9997131229291261
|
| 25 |
+
},
|
| 26 |
+
"cm_csv_path": "/kaggle/working/lcvc_ensemble_outputs/cm_LCVC_Ensemble.csv",
|
| 27 |
+
"report_path": "/kaggle/working/lcvc_ensemble_outputs/classification_report_LCVC_Ensemble.json",
|
| 28 |
+
"selected_members_path": "/kaggle/working/lcvc_ensemble_outputs/selected_lcvc_ensemble_members.csv",
|
| 29 |
+
"auc_fix": {
|
| 30 |
+
"fixed": true,
|
| 31 |
+
"method": "manual one-vs-rest macro-AUC from saved probabilities",
|
| 32 |
+
"note": "Model selection remains validation-only. Test labels are used only for final reporting."
|
| 33 |
+
}
|
| 34 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
numpy
|
| 4 |
+
pandas
|
| 5 |
+
Pillow
|
| 6 |
+
gradio>=5.0
|
selected_lcvc_ensemble_members.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
member_order,model,display_name,seed,weight,temperature,checkpoint_path,checkpoint_val_macro_f1,test_cal_macro_f1
|
| 2 |
+
0,convnext_tiny,ConvNeXt-Tiny,123,0.2,1.3677136307807327,/kaggle/input/datasets/sayemahmedshayeed/baseline-cnn-result/mri_backbone_baselines_outputs/best_convnext_tiny_seed123.pt,0.9870486143666763,0.9747367912288595
|
| 3 |
+
1,efficientnet_b0,EfficientNet-B0,123,0.2,1.3258544405019956,/kaggle/input/datasets/sayemahmedshayeed/baseline-cnn-result/mri_backbone_baselines_outputs/best_efficientnet_b0_seed123.pt,0.9842404596100688,0.984111907683999
|
| 4 |
+
2,efficientnet_b0,EfficientNet-B0,2026,0.2,1.2086706764615134,/kaggle/input/datasets/sayemahmedshayeed/baseline-cnn-result/mri_backbone_baselines_outputs/best_efficientnet_b0_seed2026.pt,0.9842174297345077,0.9802870622455948
|
| 5 |
+
3,efficientnet_b0,EfficientNet-B0,42,0.2,1.4470591581130952,/kaggle/input/datasets/sayemahmedshayeed/baseline-cnn-result/mri_backbone_baselines_outputs/best_efficientnet_b0_seed42.pt,0.9835034785528444,0.9859513817770005
|
| 6 |
+
4,convnext_tiny,ConvNeXt-Tiny,42,0.2,1.0611162444290951,/kaggle/input/datasets/sayemahmedshayeed/baseline-cnn-result/mri_backbone_baselines_outputs/best_convnext_tiny_seed42.pt,0.9824939017733962,0.9831484695235262
|