Upload 9 files
Browse filesadd source training code. Weights landing in `weights` are in the root of the folder.
- train/dataset.py +93 -0
- train/evaluate.py +162 -0
- train/model.py +74 -0
- train/pretrained/deeplabv3_weights.tar +3 -0
- train/pretrained/inceptionv3_weights.tar +3 -0
- train/requirements.txt +4 -0
- train/train_clf.py +168 -0
- train/train_seg.py +168 -0
- train/weights/.gitkeep +0 -0
train/dataset.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
dataset.py β PyTorch datasets wrapping the BDAPPV HuggingFace dataset.
|
| 3 |
+
|
| 4 |
+
Two dataset classes:
|
| 5 |
+
- SegmentationDataset : positives only (has_mask=True), returns (image, mask)
|
| 6 |
+
- ClassificationDataset : all examples, returns (image, label)
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils.data import Dataset
|
| 11 |
+
from torchvision import transforms as T
|
| 12 |
+
|
| 13 |
+
IMG_MEAN = [0.485, 0.456, 0.406]
|
| 14 |
+
IMG_STD = [0.229, 0.224, 0.225]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class SegmentationDataset(Dataset):
|
| 18 |
+
"""
|
| 19 |
+
Wraps a HF split pre-filtered to positives (has_mask=True).
|
| 20 |
+
Returns (image_tensor [3, H, W], mask_tensor [1, H, W]) as float32.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(self, hf_split, img_size: int = 400, augment: bool = False):
|
| 24 |
+
self.data = hf_split.filter(lambda x: x["has_mask"])
|
| 25 |
+
self.augment = augment
|
| 26 |
+
self.img_size = img_size
|
| 27 |
+
|
| 28 |
+
self.img_tf = T.Compose([
|
| 29 |
+
T.Resize((img_size, img_size)),
|
| 30 |
+
T.ToTensor(),
|
| 31 |
+
T.Normalize(mean=IMG_MEAN, std=IMG_STD),
|
| 32 |
+
])
|
| 33 |
+
self.mask_tf = T.Compose([
|
| 34 |
+
T.Resize((img_size, img_size), interpolation=T.InterpolationMode.NEAREST),
|
| 35 |
+
T.ToTensor(),
|
| 36 |
+
])
|
| 37 |
+
|
| 38 |
+
def __len__(self):
|
| 39 |
+
return len(self.data)
|
| 40 |
+
|
| 41 |
+
def __getitem__(self, idx):
|
| 42 |
+
ex = self.data[idx]
|
| 43 |
+
image = ex["image"].convert("RGB")
|
| 44 |
+
mask = ex["mask"].convert("L")
|
| 45 |
+
|
| 46 |
+
if self.augment:
|
| 47 |
+
if torch.rand(1) > 0.5:
|
| 48 |
+
image = T.functional.hflip(image)
|
| 49 |
+
mask = T.functional.hflip(mask)
|
| 50 |
+
if torch.rand(1) > 0.5:
|
| 51 |
+
image = T.functional.vflip(image)
|
| 52 |
+
mask = T.functional.vflip(mask)
|
| 53 |
+
|
| 54 |
+
image = self.img_tf(image)
|
| 55 |
+
mask = self.mask_tf(mask)
|
| 56 |
+
mask = (mask > 0.5).float() # binarize β {0, 1}
|
| 57 |
+
|
| 58 |
+
return image, mask
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class ClassificationDataset(Dataset):
|
| 62 |
+
"""
|
| 63 |
+
All examples (positives + negatives).
|
| 64 |
+
Returns (image_tensor [3, H, W], label [float scalar]).
|
| 65 |
+
label = 1.0 if has_mask else 0.0
|
| 66 |
+
"""
|
| 67 |
+
|
| 68 |
+
def __init__(self, hf_split, img_size: int = 299, augment: bool = False):
|
| 69 |
+
self.data = hf_split
|
| 70 |
+
self.augment = augment
|
| 71 |
+
|
| 72 |
+
self.img_tf = T.Compose([
|
| 73 |
+
T.Resize((img_size, img_size)),
|
| 74 |
+
T.ToTensor(),
|
| 75 |
+
T.Normalize(mean=IMG_MEAN, std=IMG_STD),
|
| 76 |
+
])
|
| 77 |
+
|
| 78 |
+
def __len__(self):
|
| 79 |
+
return len(self.data)
|
| 80 |
+
|
| 81 |
+
def __getitem__(self, idx):
|
| 82 |
+
ex = self.data[idx]
|
| 83 |
+
image = ex["image"].convert("RGB")
|
| 84 |
+
label = float(ex["has_mask"])
|
| 85 |
+
|
| 86 |
+
if self.augment:
|
| 87 |
+
if torch.rand(1) > 0.5:
|
| 88 |
+
image = T.functional.hflip(image)
|
| 89 |
+
if torch.rand(1) > 0.5:
|
| 90 |
+
image = T.functional.vflip(image)
|
| 91 |
+
|
| 92 |
+
image = self.img_tf(image)
|
| 93 |
+
return image, torch.tensor(label)
|
train/evaluate.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
evaluate.py β Evaluate trained models on the BDAPPV test split.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
# Segmentation
|
| 6 |
+
python evaluate.py seg --provider google --checkpoint weights/deeplab_google_best.pth
|
| 7 |
+
python evaluate.py seg --provider ign --checkpoint weights/deeplab_ign_best.pth
|
| 8 |
+
|
| 9 |
+
# Classification
|
| 10 |
+
python evaluate.py clf --provider google --checkpoint weights/inception_google_best.pth
|
| 11 |
+
|
| 12 |
+
# Cross-provider (train Google, eval IGN) β the distribution shift benchmark
|
| 13 |
+
python evaluate.py seg --provider ign --checkpoint weights/deeplab_google_best.pth
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import argparse
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn as nn
|
| 20 |
+
from torch.utils.data import DataLoader
|
| 21 |
+
from torchvision.models.segmentation import deeplabv3_resnet101
|
| 22 |
+
from torchvision.models import inception_v3
|
| 23 |
+
|
| 24 |
+
from datasets import load_dataset
|
| 25 |
+
|
| 26 |
+
from dataset import SegmentationDataset, ClassificationDataset
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# ββ Metrics βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 30 |
+
|
| 31 |
+
def compute_seg_metrics(pred_logits, target, threshold=0.5):
|
| 32 |
+
pred = (torch.sigmoid(pred_logits) > threshold).float()
|
| 33 |
+
tp = (pred * target).sum().item()
|
| 34 |
+
fp = (pred * (1 - target)).sum().item()
|
| 35 |
+
fn = ((1 - pred) * target).sum().item()
|
| 36 |
+
iou = tp / max(tp + fp + fn, 1e-6)
|
| 37 |
+
prec = tp / max(tp + fp, 1e-6)
|
| 38 |
+
rec = tp / max(tp + fn, 1e-6)
|
| 39 |
+
f1 = 2 * prec * rec / max(prec + rec, 1e-6)
|
| 40 |
+
return iou, f1
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def compute_clf_metrics(logits, labels, threshold=0.0):
|
| 44 |
+
preds = (logits > threshold).float()
|
| 45 |
+
acc = (preds == labels).float().mean().item()
|
| 46 |
+
tp = (preds * labels).sum().item()
|
| 47 |
+
fp = (preds * (1 - labels)).sum().item()
|
| 48 |
+
fn = ((1 - preds) * labels).sum().item()
|
| 49 |
+
prec = tp / max(tp + fp, 1e-6)
|
| 50 |
+
rec = tp / max(tp + fn, 1e-6)
|
| 51 |
+
f1 = 2 * prec * rec / max(prec + rec, 1e-6)
|
| 52 |
+
return acc, prec, rec, f1
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# ββ Segmentation eval βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 56 |
+
|
| 57 |
+
def eval_seg(args):
|
| 58 |
+
device = (
|
| 59 |
+
torch.device("mps") if torch.backends.mps.is_available() else
|
| 60 |
+
torch.device("cuda") if torch.cuda.is_available() else
|
| 61 |
+
torch.device("cpu")
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
ds = load_dataset("gabrielkasmi/bdappv", args.provider)
|
| 65 |
+
test_ds = SegmentationDataset(ds["test"], img_size=args.img_size, augment=False)
|
| 66 |
+
loader = DataLoader(test_ds, batch_size=args.batch_size, shuffle=False, num_workers=args.num_workers)
|
| 67 |
+
|
| 68 |
+
model = deeplabv3_resnet101(weights=None, aux_loss=False)
|
| 69 |
+
model.classifier[-1] = nn.Conv2d(256, 1, kernel_size=1)
|
| 70 |
+
state = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
|
| 71 |
+
model_dict = model.state_dict()
|
| 72 |
+
compatible = {k: v for k, v in state.items() if k in model_dict and v.shape == model_dict[k].shape}
|
| 73 |
+
model_dict.update(compatible)
|
| 74 |
+
model.load_state_dict(model_dict)
|
| 75 |
+
model = model.to(device)
|
| 76 |
+
model.eval()
|
| 77 |
+
|
| 78 |
+
total_iou, total_f1 = 0.0, 0.0
|
| 79 |
+
with torch.no_grad():
|
| 80 |
+
for images, masks in loader:
|
| 81 |
+
images, masks = images.to(device), masks.to(device)
|
| 82 |
+
logits = model(images)["out"]
|
| 83 |
+
if logits.shape[-2:] != masks.shape[-2:]:
|
| 84 |
+
logits = nn.functional.interpolate(logits, size=masks.shape[-2:], mode="bilinear", align_corners=False)
|
| 85 |
+
iou, f1 = compute_seg_metrics(logits, masks)
|
| 86 |
+
total_iou += iou
|
| 87 |
+
total_f1 += f1
|
| 88 |
+
|
| 89 |
+
n = len(loader)
|
| 90 |
+
print(f"\nββ Segmentation results ββββββββββββββββββββββ")
|
| 91 |
+
print(f" Provider : {args.provider}")
|
| 92 |
+
print(f" Checkpoint : {args.checkpoint}")
|
| 93 |
+
print(f" Test images: {len(test_ds):,}")
|
| 94 |
+
print(f" IoU : {total_iou / n:.4f}")
|
| 95 |
+
print(f" F1 : {total_f1 / n:.4f}")
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# ββ Classification eval βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 99 |
+
|
| 100 |
+
def eval_clf(args):
|
| 101 |
+
device = (
|
| 102 |
+
torch.device("mps") if torch.backends.mps.is_available() else
|
| 103 |
+
torch.device("cuda") if torch.cuda.is_available() else
|
| 104 |
+
torch.device("cpu")
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
ds = load_dataset("gabrielkasmi/bdappv", args.provider)
|
| 108 |
+
test_ds = ClassificationDataset(ds["test"], img_size=299, augment=False)
|
| 109 |
+
loader = DataLoader(test_ds, batch_size=args.batch_size, shuffle=False, num_workers=args.num_workers)
|
| 110 |
+
|
| 111 |
+
model = inception_v3(weights=None, aux_logits=True)
|
| 112 |
+
model.fc = nn.Linear(model.fc.in_features, 1)
|
| 113 |
+
model.AuxLogits.fc = nn.Linear(model.AuxLogits.fc.in_features, 1)
|
| 114 |
+
state = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
|
| 115 |
+
model.load_state_dict(state)
|
| 116 |
+
model.aux_logits = False
|
| 117 |
+
model = model.to(device)
|
| 118 |
+
model.eval()
|
| 119 |
+
|
| 120 |
+
all_logits, all_labels = [], []
|
| 121 |
+
with torch.no_grad():
|
| 122 |
+
for images, labels in loader:
|
| 123 |
+
images = images.to(device)
|
| 124 |
+
logits = model(images)
|
| 125 |
+
if hasattr(logits, "logits"):
|
| 126 |
+
logits = logits.logits
|
| 127 |
+
all_logits.append(logits.cpu())
|
| 128 |
+
all_labels.append(labels.unsqueeze(1))
|
| 129 |
+
|
| 130 |
+
all_logits = torch.cat(all_logits)
|
| 131 |
+
all_labels = torch.cat(all_labels)
|
| 132 |
+
acc, prec, rec, f1 = compute_clf_metrics(all_logits, all_labels)
|
| 133 |
+
|
| 134 |
+
print(f"\nββ Classification results ββββββββββββββββββββ")
|
| 135 |
+
print(f" Provider : {args.provider}")
|
| 136 |
+
print(f" Checkpoint : {args.checkpoint}")
|
| 137 |
+
print(f" Test images: {len(test_ds):,}")
|
| 138 |
+
print(f" Accuracy : {acc:.4f}")
|
| 139 |
+
print(f" Precision : {prec:.4f}")
|
| 140 |
+
print(f" Recall : {rec:.4f}")
|
| 141 |
+
print(f" F1 : {f1:.4f}")
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# ββ Entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 145 |
+
|
| 146 |
+
if __name__ == "__main__":
|
| 147 |
+
parser = argparse.ArgumentParser()
|
| 148 |
+
sub = parser.add_subparsers(dest="task", required=True)
|
| 149 |
+
|
| 150 |
+
for name in ["seg", "clf"]:
|
| 151 |
+
p = sub.add_parser(name)
|
| 152 |
+
p.add_argument("--provider", required=True, choices=["google", "ign"])
|
| 153 |
+
p.add_argument("--checkpoint", required=True)
|
| 154 |
+
p.add_argument("--batch_size", type=int, default=8)
|
| 155 |
+
p.add_argument("--img_size", type=int, default=400)
|
| 156 |
+
p.add_argument("--num_workers", type=int, default=0)
|
| 157 |
+
|
| 158 |
+
args = parser.parse_args()
|
| 159 |
+
if args.task == "seg":
|
| 160 |
+
eval_seg(args)
|
| 161 |
+
else:
|
| 162 |
+
eval_clf(args)
|
train/model.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
model.py β helpers to load BDAPPV baseline models from HuggingFace Hub.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
from huggingface_hub import hf_hub_download
|
| 6 |
+
import importlib.util, sys
|
| 7 |
+
|
| 8 |
+
path = hf_hub_download("gabrielkasmi/bdappv-models", "model.py")
|
| 9 |
+
spec = importlib.util.spec_from_file_location("bdappv_model", path)
|
| 10 |
+
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
|
| 11 |
+
|
| 12 |
+
seg = mod.load_segmentation_model("google") # or "ign"
|
| 13 |
+
clf = mod.load_classification_model("google") # or "ign"
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
from huggingface_hub import hf_hub_download
|
| 19 |
+
from torchvision.models.segmentation import deeplabv3_resnet101
|
| 20 |
+
from torchvision.models import inception_v3
|
| 21 |
+
|
| 22 |
+
REPO = "gabrielkasmi/bdappv-models"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def load_segmentation_model(provider: str = "google", device: str = "cpu"):
|
| 26 |
+
"""
|
| 27 |
+
Load the DeepLabV3-ResNet101 segmentation model.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
provider : "google" or "ign"
|
| 31 |
+
device : "cpu", "cuda", or "mps"
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
model in eval mode
|
| 35 |
+
"""
|
| 36 |
+
assert provider in ("google", "ign"), "provider must be 'google' or 'ign'"
|
| 37 |
+
|
| 38 |
+
path = hf_hub_download(REPO, f"deeplab_{provider}_best.pth")
|
| 39 |
+
model = deeplabv3_resnet101(weights=None, aux_loss=False)
|
| 40 |
+
model.classifier[-1] = nn.Conv2d(256, 1, kernel_size=1)
|
| 41 |
+
|
| 42 |
+
state = torch.load(path, map_location=device, weights_only=False)
|
| 43 |
+
model_dict = model.state_dict()
|
| 44 |
+
compatible = {k: v for k, v in state.items()
|
| 45 |
+
if k in model_dict and v.shape == model_dict[k].shape}
|
| 46 |
+
model_dict.update(compatible)
|
| 47 |
+
model.load_state_dict(model_dict)
|
| 48 |
+
|
| 49 |
+
return model.eval().to(device)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def load_classification_model(provider: str = "google", device: str = "cpu"):
|
| 53 |
+
"""
|
| 54 |
+
Load the InceptionV3 classification model (panel / no panel).
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
provider : "google" or "ign"
|
| 58 |
+
device : "cpu", "cuda", or "mps"
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
model in eval mode
|
| 62 |
+
"""
|
| 63 |
+
assert provider in ("google", "ign"), "provider must be 'google' or 'ign'"
|
| 64 |
+
|
| 65 |
+
path = hf_hub_download(REPO, f"inception_{provider}_best.pth")
|
| 66 |
+
model = inception_v3(weights=None, aux_logits=True)
|
| 67 |
+
model.fc = nn.Linear(model.fc.in_features, 1)
|
| 68 |
+
model.AuxLogits.fc = nn.Linear(model.AuxLogits.fc.in_features, 1)
|
| 69 |
+
|
| 70 |
+
state = torch.load(path, map_location=device, weights_only=False)
|
| 71 |
+
model.load_state_dict(state)
|
| 72 |
+
model.aux_logits = False # dΓ©sactive pour l'infΓ©rence
|
| 73 |
+
|
| 74 |
+
return model.eval().to(device)
|
train/pretrained/deeplabv3_weights.tar
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bbedde9b8e74934fd117c6fc73984a5521ba51863d28d4c980135b162240df8d
|
| 3 |
+
size 244640087
|
train/pretrained/inceptionv3_weights.tar
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d8d8b4a8d5ac646605012f3aaa6ee75420ef9201bb16e853efd7edbbc9ff4bd5
|
| 3 |
+
size 390201979
|
train/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.2.0
|
| 2 |
+
torchvision>=0.17.0
|
| 3 |
+
datasets>=2.18.0
|
| 4 |
+
Pillow>=10.0.0
|
train/train_clf.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
train_clf.py β InceptionV3 binary classification on BDAPPV (panel / no panel).
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
python train_clf.py --provider google
|
| 6 |
+
python train_clf.py --provider ign
|
| 7 |
+
python train_clf.py --provider google --checkpoint weights/inception_google_best.pth
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from torch.utils.data import DataLoader
|
| 17 |
+
from torchvision.models import inception_v3, Inception_V3_Weights
|
| 18 |
+
|
| 19 |
+
from datasets import load_dataset
|
| 20 |
+
|
| 21 |
+
from dataset import ClassificationDataset
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# ββ Metrics βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
|
| 26 |
+
def accuracy_f1(logits, labels, threshold=0.0):
|
| 27 |
+
preds = (logits > threshold).float()
|
| 28 |
+
correct = (preds == labels).float().mean().item()
|
| 29 |
+
tp = (preds * labels).sum().item()
|
| 30 |
+
fp = (preds * (1 - labels)).sum().item()
|
| 31 |
+
fn = ((1 - preds) * labels).sum().item()
|
| 32 |
+
prec = tp / max(tp + fp, 1e-6)
|
| 33 |
+
rec = tp / max(tp + fn, 1e-6)
|
| 34 |
+
f1 = 2 * prec * rec / max(prec + rec, 1e-6)
|
| 35 |
+
return correct, f1
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 39 |
+
|
| 40 |
+
def train(args):
|
| 41 |
+
device = (
|
| 42 |
+
torch.device("mps") if torch.backends.mps.is_available() else
|
| 43 |
+
torch.device("cuda") if torch.cuda.is_available() else
|
| 44 |
+
torch.device("cpu")
|
| 45 |
+
)
|
| 46 |
+
print(f"Device : {device}")
|
| 47 |
+
print(f"Provider: {args.provider} | effective batch: {args.batch_size * args.grad_accum}")
|
| 48 |
+
|
| 49 |
+
# ββ Data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 50 |
+
print("Loading dataset from HuggingFace Hub...")
|
| 51 |
+
ds = load_dataset("gabrielkasmi/bdappv", args.provider)
|
| 52 |
+
|
| 53 |
+
train_ds = ClassificationDataset(ds["train"], img_size=299, augment=True)
|
| 54 |
+
val_ds = ClassificationDataset(ds["validation"], img_size=299, augment=False)
|
| 55 |
+
|
| 56 |
+
print(f" train : {len(train_ds):,} | val : {len(val_ds):,}")
|
| 57 |
+
|
| 58 |
+
train_loader = DataLoader(
|
| 59 |
+
train_ds, batch_size=args.batch_size, shuffle=True,
|
| 60 |
+
num_workers=args.num_workers, pin_memory=(device.type != "mps"),
|
| 61 |
+
)
|
| 62 |
+
val_loader = DataLoader(
|
| 63 |
+
val_ds, batch_size=args.batch_size, shuffle=False,
|
| 64 |
+
num_workers=args.num_workers, pin_memory=(device.type != "mps"),
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# ββ Model βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 68 |
+
model = inception_v3(weights=Inception_V3_Weights.DEFAULT)
|
| 69 |
+
|
| 70 |
+
if args.checkpoint:
|
| 71 |
+
# Poids issus d'un modΓ¨le Γ 2 classes β on charge avec la tΓͺte originale
|
| 72 |
+
# puis on la remplace par une tΓͺte binaire (1 sortie, BCEWithLogitsLoss)
|
| 73 |
+
model.fc = nn.Linear(model.fc.in_features, 2)
|
| 74 |
+
model.AuxLogits.fc = nn.Linear(model.AuxLogits.fc.in_features, 2)
|
| 75 |
+
ckpt = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
|
| 76 |
+
state = ckpt["model_state_dict"] if isinstance(ckpt, dict) else ckpt
|
| 77 |
+
model.load_state_dict(state, strict=True)
|
| 78 |
+
print(f"Loaded checkpoint: {args.checkpoint}")
|
| 79 |
+
|
| 80 |
+
# Remplace la tΓͺte par 1 sortie pour BCEWithLogitsLoss
|
| 81 |
+
model.fc = nn.Linear(model.fc.in_features, 1)
|
| 82 |
+
model.AuxLogits.fc = nn.Linear(model.AuxLogits.fc.in_features, 1)
|
| 83 |
+
|
| 84 |
+
model = model.to(device)
|
| 85 |
+
|
| 86 |
+
# ββ Optimizer + scheduler βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 87 |
+
optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4)
|
| 88 |
+
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs)
|
| 89 |
+
criterion = nn.BCEWithLogitsLoss()
|
| 90 |
+
|
| 91 |
+
# ββ Training loop βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 92 |
+
os.makedirs(args.out_dir, exist_ok=True)
|
| 93 |
+
best_f1 = 0.0
|
| 94 |
+
patience_ctr = 0
|
| 95 |
+
|
| 96 |
+
for epoch in range(1, args.epochs + 1):
|
| 97 |
+
model.train()
|
| 98 |
+
total_loss = 0.0
|
| 99 |
+
optimizer.zero_grad()
|
| 100 |
+
|
| 101 |
+
pbar = tqdm(train_loader, desc=f"Epoch {epoch}/{args.epochs}", leave=False)
|
| 102 |
+
for step, (images, labels) in enumerate(pbar):
|
| 103 |
+
images = images.to(device)
|
| 104 |
+
labels = labels.to(device).unsqueeze(1)
|
| 105 |
+
|
| 106 |
+
out = model(images)
|
| 107 |
+
logits = out.logits if hasattr(out, "logits") else out[0]
|
| 108 |
+
aux = out.aux_logits if hasattr(out, "aux_logits") else out[1]
|
| 109 |
+
|
| 110 |
+
loss = (criterion(logits, labels) + 0.4 * criterion(aux, labels)) / args.grad_accum
|
| 111 |
+
loss.backward()
|
| 112 |
+
total_loss += loss.item() * args.grad_accum
|
| 113 |
+
pbar.set_postfix(loss=f"{loss.item() * args.grad_accum:.4f}")
|
| 114 |
+
|
| 115 |
+
if (step + 1) % args.grad_accum == 0 or (step + 1) == len(train_loader):
|
| 116 |
+
optimizer.step()
|
| 117 |
+
optimizer.zero_grad()
|
| 118 |
+
|
| 119 |
+
# ββ Validation ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 120 |
+
model.eval()
|
| 121 |
+
all_logits, all_labels = [], []
|
| 122 |
+
with torch.no_grad():
|
| 123 |
+
for images, labels in val_loader:
|
| 124 |
+
images = images.to(device)
|
| 125 |
+
logits = model(images)
|
| 126 |
+
if hasattr(logits, "logits"):
|
| 127 |
+
logits = logits.logits
|
| 128 |
+
all_logits.append(logits.cpu())
|
| 129 |
+
all_labels.append(labels.unsqueeze(1))
|
| 130 |
+
|
| 131 |
+
all_logits = torch.cat(all_logits)
|
| 132 |
+
all_labels = torch.cat(all_labels)
|
| 133 |
+
acc, f1 = accuracy_f1(all_logits, all_labels)
|
| 134 |
+
avg_loss = total_loss / len(train_loader)
|
| 135 |
+
|
| 136 |
+
print(f"Epoch {epoch:3d}/{args.epochs} loss={avg_loss:.4f} val_acc={acc:.4f} val_f1={f1:.4f}")
|
| 137 |
+
|
| 138 |
+
scheduler.step()
|
| 139 |
+
|
| 140 |
+
if f1 > best_f1:
|
| 141 |
+
best_f1 = f1
|
| 142 |
+
patience_ctr = 0
|
| 143 |
+
ckpt_path = os.path.join(args.out_dir, f"inception_{args.provider}_best.pth")
|
| 144 |
+
torch.save(model.state_dict(), ckpt_path)
|
| 145 |
+
print(f" β checkpoint saved (F1={best_f1:.4f})")
|
| 146 |
+
else:
|
| 147 |
+
patience_ctr += 1
|
| 148 |
+
if patience_ctr >= args.patience:
|
| 149 |
+
print(f"\nEarly stopping β no improvement for {args.patience} epochs.")
|
| 150 |
+
break
|
| 151 |
+
|
| 152 |
+
print(f"\nDone. Best val F1: {best_f1:.4f}")
|
| 153 |
+
print(f"Weights: {os.path.join(args.out_dir, f'inception_{args.provider}_best.pth')}")
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
if __name__ == "__main__":
|
| 157 |
+
parser = argparse.ArgumentParser()
|
| 158 |
+
parser.add_argument("--provider", default="google", choices=["google", "ign"])
|
| 159 |
+
parser.add_argument("--epochs", type=int, default=50)
|
| 160 |
+
parser.add_argument("--batch_size", type=int, default=8)
|
| 161 |
+
parser.add_argument("--grad_accum", type=int, default=4, help="Gradient accumulation steps")
|
| 162 |
+
parser.add_argument("--lr", type=float, default=1e-4)
|
| 163 |
+
parser.add_argument("--num_workers", type=int, default=2, help="Workers pour le dataloader")
|
| 164 |
+
parser.add_argument("--checkpoint", default=None)
|
| 165 |
+
parser.add_argument("--out_dir", default="weights")
|
| 166 |
+
parser.add_argument("--patience", type=int, default=7, help="Early stopping patience (epochs)")
|
| 167 |
+
args = parser.parse_args()
|
| 168 |
+
train(args)
|
train/train_seg.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
train_seg.py β DeepLabV3-ResNet50 segmentation on BDAPPV.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
python train_seg.py --provider google
|
| 6 |
+
python train_seg.py --provider ign
|
| 7 |
+
python train_seg.py --provider google --checkpoint weights/deeplab_google_best.pth # resume
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from torch.utils.data import DataLoader
|
| 17 |
+
from torchvision.models.segmentation import deeplabv3_resnet101, DeepLabV3_ResNet101_Weights
|
| 18 |
+
|
| 19 |
+
from datasets import load_dataset
|
| 20 |
+
|
| 21 |
+
from dataset import SegmentationDataset
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# ββ Metrics βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
|
| 26 |
+
def iou_score(pred_logits, target, threshold=0.5):
|
| 27 |
+
pred = (torch.sigmoid(pred_logits) > threshold).float()
|
| 28 |
+
inter = (pred * target).sum()
|
| 29 |
+
union = pred.sum() + target.sum() - inter
|
| 30 |
+
return (inter / union.clamp(min=1e-6)).item()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ββ Main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 34 |
+
|
| 35 |
+
def train(args):
|
| 36 |
+
device = (
|
| 37 |
+
torch.device("mps") if torch.backends.mps.is_available() else
|
| 38 |
+
torch.device("cuda") if torch.cuda.is_available() else
|
| 39 |
+
torch.device("cpu")
|
| 40 |
+
)
|
| 41 |
+
print(f"Device : {device}")
|
| 42 |
+
print(f"Provider: {args.provider} | effective batch: {args.batch_size * args.grad_accum}")
|
| 43 |
+
|
| 44 |
+
# ββ Data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 45 |
+
print("Loading dataset from HuggingFace Hub...")
|
| 46 |
+
ds = load_dataset("gabrielkasmi/bdappv", args.provider)
|
| 47 |
+
|
| 48 |
+
train_ds = SegmentationDataset(ds["train"], img_size=args.img_size, augment=True)
|
| 49 |
+
val_ds = SegmentationDataset(ds["validation"], img_size=args.img_size, augment=False)
|
| 50 |
+
|
| 51 |
+
print(f" train positives : {len(train_ds):,}")
|
| 52 |
+
print(f" val positives : {len(val_ds):,}")
|
| 53 |
+
|
| 54 |
+
train_loader = DataLoader(
|
| 55 |
+
train_ds, batch_size=args.batch_size, shuffle=True,
|
| 56 |
+
num_workers=args.num_workers, pin_memory=(device.type != "mps"),
|
| 57 |
+
drop_last=True,
|
| 58 |
+
)
|
| 59 |
+
val_loader = DataLoader(
|
| 60 |
+
val_ds, batch_size=args.batch_size, shuffle=False,
|
| 61 |
+
num_workers=args.num_workers, pin_memory=(device.type != "mps"),
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# ββ Model βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 65 |
+
model = deeplabv3_resnet101(weights=DeepLabV3_ResNet101_Weights.DEFAULT)
|
| 66 |
+
model.classifier[-1] = nn.Conv2d(256, 1, kernel_size=1)
|
| 67 |
+
model.aux_classifier[-1] = nn.Conv2d(256, 1, kernel_size=1)
|
| 68 |
+
|
| 69 |
+
if args.checkpoint:
|
| 70 |
+
# Le checkpoint mΓ©lange une tΓͺte principale binaire et une tΓͺte aux Γ 21 classes
|
| 71 |
+
# (poids de base incohΓ©rents) β on ne charge que les tenseurs dont la shape colle.
|
| 72 |
+
ckpt = torch.load(args.checkpoint, map_location="cpu", weights_only=False)
|
| 73 |
+
state = ckpt["model_state_dict"] if isinstance(ckpt, dict) else ckpt
|
| 74 |
+
model_dict = model.state_dict()
|
| 75 |
+
compatible = {k: v for k, v in state.items() if k in model_dict and v.shape == model_dict[k].shape}
|
| 76 |
+
skipped = set(state.keys()) - set(compatible.keys())
|
| 77 |
+
model_dict.update(compatible)
|
| 78 |
+
model.load_state_dict(model_dict)
|
| 79 |
+
print(f"Loaded checkpoint: {args.checkpoint} ({len(compatible)}/{len(state)} tensors matched, {len(skipped)} skipped)")
|
| 80 |
+
|
| 81 |
+
model = model.to(device)
|
| 82 |
+
|
| 83 |
+
# ββ Optimizer + scheduler βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 84 |
+
optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4)
|
| 85 |
+
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs)
|
| 86 |
+
criterion = nn.BCEWithLogitsLoss()
|
| 87 |
+
|
| 88 |
+
# ββ Training loop βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 89 |
+
os.makedirs(args.out_dir, exist_ok=True)
|
| 90 |
+
best_iou = 0.0
|
| 91 |
+
patience_ctr = 0
|
| 92 |
+
|
| 93 |
+
for epoch in range(1, args.epochs + 1):
|
| 94 |
+
model.train()
|
| 95 |
+
total_loss = 0.0
|
| 96 |
+
optimizer.zero_grad()
|
| 97 |
+
|
| 98 |
+
pbar = tqdm(train_loader, desc=f"Epoch {epoch}/{args.epochs}", leave=False)
|
| 99 |
+
for step, (images, masks) in enumerate(pbar):
|
| 100 |
+
if step == 3:
|
| 101 |
+
break
|
| 102 |
+
images, masks = images.to(device), masks.to(device)
|
| 103 |
+
|
| 104 |
+
out = model(images)
|
| 105 |
+
logits = out["out"]
|
| 106 |
+
aux = out["aux"]
|
| 107 |
+
|
| 108 |
+
if logits.shape[-2:] != masks.shape[-2:]:
|
| 109 |
+
logits = nn.functional.interpolate(logits, size=masks.shape[-2:], mode="bilinear", align_corners=False)
|
| 110 |
+
aux = nn.functional.interpolate(aux, size=masks.shape[-2:], mode="bilinear", align_corners=False)
|
| 111 |
+
|
| 112 |
+
loss = (criterion(logits, masks) + 0.4 * criterion(aux, masks)) / args.grad_accum
|
| 113 |
+
loss.backward()
|
| 114 |
+
total_loss += loss.item() * args.grad_accum
|
| 115 |
+
pbar.set_postfix(loss=f"{loss.item() * args.grad_accum:.4f}")
|
| 116 |
+
|
| 117 |
+
if (step + 1) % args.grad_accum == 0 or (step + 1) == len(train_loader):
|
| 118 |
+
optimizer.step()
|
| 119 |
+
optimizer.zero_grad()
|
| 120 |
+
|
| 121 |
+
# ββ Validation ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 122 |
+
model.eval()
|
| 123 |
+
val_iou = 0.0
|
| 124 |
+
with torch.no_grad():
|
| 125 |
+
for images, masks in val_loader:
|
| 126 |
+
images, masks = images.to(device), masks.to(device)
|
| 127 |
+
logits = model(images)["out"]
|
| 128 |
+
if logits.shape[-2:] != masks.shape[-2:]:
|
| 129 |
+
logits = nn.functional.interpolate(logits, size=masks.shape[-2:], mode="bilinear", align_corners=False)
|
| 130 |
+
val_iou += iou_score(logits, masks)
|
| 131 |
+
|
| 132 |
+
val_iou /= len(val_loader)
|
| 133 |
+
avg_loss = total_loss / len(train_loader)
|
| 134 |
+
|
| 135 |
+
print(f"Epoch {epoch:3d}/{args.epochs} loss={avg_loss:.4f} val_iou={val_iou:.4f}")
|
| 136 |
+
|
| 137 |
+
scheduler.step()
|
| 138 |
+
|
| 139 |
+
if val_iou > best_iou:
|
| 140 |
+
best_iou = val_iou
|
| 141 |
+
patience_ctr = 0
|
| 142 |
+
ckpt_path = os.path.join(args.out_dir, f"deeplab_{args.provider}_best.pth")
|
| 143 |
+
torch.save(model.state_dict(), ckpt_path)
|
| 144 |
+
print(f" β checkpoint saved (IoU={best_iou:.4f})")
|
| 145 |
+
else:
|
| 146 |
+
patience_ctr += 1
|
| 147 |
+
if patience_ctr >= args.patience:
|
| 148 |
+
print(f"\nEarly stopping β no improvement for {args.patience} epochs.")
|
| 149 |
+
break
|
| 150 |
+
|
| 151 |
+
print(f"\nDone. Best val IoU: {best_iou:.4f}")
|
| 152 |
+
print(f"Weights: {os.path.join(args.out_dir, f'deeplab_{args.provider}_best.pth')}")
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
parser = argparse.ArgumentParser()
|
| 157 |
+
parser.add_argument("--provider", default="google", choices=["google", "ign"])
|
| 158 |
+
parser.add_argument("--epochs", type=int, default=50)
|
| 159 |
+
parser.add_argument("--batch_size", type=int, default=4)
|
| 160 |
+
parser.add_argument("--grad_accum", type=int, default=8, help="Gradient accumulation steps (eff. batch = batch_size Γ grad_accum)")
|
| 161 |
+
parser.add_argument("--lr", type=float, default=1e-4)
|
| 162 |
+
parser.add_argument("--img_size", type=int, default=400)
|
| 163 |
+
parser.add_argument("--num_workers", type=int, default=2, help="Workers pour le dataloader")
|
| 164 |
+
parser.add_argument("--checkpoint", default=None)
|
| 165 |
+
parser.add_argument("--out_dir", default="weights")
|
| 166 |
+
parser.add_argument("--patience", type=int, default=7, help="Early stopping patience (epochs)")
|
| 167 |
+
args = parser.parse_args()
|
| 168 |
+
train(args)
|
train/weights/.gitkeep
ADDED
|
File without changes
|