DFA-MoE / continual_clip /datasets.py
boringKey's picture
Upload 126 files
3ea5987 verified
Raw
History Blame Contribute Delete
18.2 kB
import os
import math
import errno
from typing import List, Dict, Optional
import torch.nn as nn
import numpy as np
from PIL import Image
import h5py
from continuum import ClassIncremental, InstanceIncremental
from continuum.datasets import (
CIFAR100, ImageNet100, TinyImageNet200, ImageFolderDataset, Core50
)
from .utils import get_dataset_class_names
from MTIL_datasets.stanford_cars import StanfordCars as MTILStanfordCars
from MTIL_datasets.fgvc_aircraft import FGVCAircraft as MTILFGVCAircraft
from MTIL_datasets.caltech101 import Caltech101 as MTILCaltech101
from MTIL_datasets.dtd import DescribableTextures as MTILDTD
from MTIL_datasets.eurosat import EuroSAT as MTILEuroSAT
from MTIL_datasets.oxford_flowers import OxfordFlowers as MTILOxfordFlowers
from MTIL_datasets.food101 import Food101 as MTILFood101
from MTIL_datasets.mnist import MNIST as MTILMNIST
from MTIL_datasets.oxford_pets import OxfordPets as MTILOxfordPets
from MTIL_datasets.sun397 import SUN397 as MTILSUN397
from MTIL_datasets.country211 import Country211 as MTILCountry211
from MTIL_datasets.sst2 import SST2 as MTILSST2
from MTIL_datasets.hatefulmemes import HatefulMemes as MTILHatefulMemes
from MTIL_datasets.gtsrb import GTSRB as MTILGTSRB
from MTIL_datasets.resisc import RESISC45 as MTILRESISC45
from MTIL_datasets.fer2013 import FER2013 as MTILFER2013
from MTIL_datasets.ucf101 import UCF101 as MTILUCF101
from MTIL_datasets.cifar10 import CIFAR10 as MTILCIFAR10
from MTIL_datasets.stl10 import STL10 as MTILSTL10
from MTIL_datasets.voc2007 import VOC2007 as MTILVOC2007
from MTIL_datasets.imagenet_r import ImageNetR as MTILImageNetR
from MTIL_datasets.kitti_distance import KittiDistance as MTILKittiDistance
from MTIL_datasets.pcam import PCam as MTILPCam
from MTIL_datasets.clevr_count import CLEVRCount as MTILCLEVRCount
class ImageNet1000(ImageFolderDataset):
"""Continuum dataset for datasets with tree-like structure.
:param train_folder: The folder of the train data.
:param test_folder: The folder of the test data.
:param download: Dummy parameter.
"""
def __init__(
self,
data_path: str,
train: bool = True,
download: bool = False,
):
super().__init__(data_path=data_path, train=train, download=download)
def get_data(self):
if self.train:
self.data_path = os.path.join(self.data_path, "train")
else:
self.data_path = os.path.join(self.data_path, "val")
return super().get_data()
class MTILImageFolderCL(ImageFolderDataset):
"""Generic Continuum-compatible adapter for MTIL datasets.
Builds a class-folder cache using the MTIL wrapper's file list so that
Continuum's ImageFolderDataset can load it.
"""
def __init__(self, data_root: str, mtil_cls, train: bool = True, download: bool = False, mtil_kwargs: Optional[Dict] = None):
mtil_kwargs = mtil_kwargs or {}
_inst = None
try:
_inst = mtil_cls(root=data_root, num_shots=0, seed=1, **mtil_kwargs)
except TypeError:
try:
_inst = mtil_cls(root=data_root, seed=1, **mtil_kwargs)
except TypeError:
_inst = mtil_cls(root=data_root, **mtil_kwargs)
self._mtil = _inst
self.dataset_dir = self._mtil.dataset_dir # absolute path inside data_root
self.cache_split = 'train' if train else 'test'
self.cache_dir = os.path.join(self.dataset_dir, 'cl_cache', self.cache_split)
self._ensure_cache(train)
super().__init__(data_path=self.cache_dir, train=train, download=download)
def _safe_symlink(self, src: str, dst: str):
try:
if not os.path.exists(dst):
os.symlink(src, dst)
except OSError as e:
if e.errno in (errno.EPERM, errno.EACCES, errno.ENOTSUP):
if not os.path.exists(dst):
import shutil
shutil.copy2(src, dst)
else:
raise
def _ensure_cache(self, train: bool):
if os.path.isdir(self.cache_dir) and any(os.scandir(self.cache_dir)):
return
os.makedirs(self.cache_dir, exist_ok=True)
items = getattr(self._mtil, 'train_x') if train else getattr(self._mtil, 'test')
# per-class counters to generate unique filenames when saving from arrays/PIL
per_class_count: Dict[str, int] = {}
for item in items:
class_dir = os.path.join(self.cache_dir, item.classname)
os.makedirs(class_dir, exist_ok=True)
impath = item.impath
# Direct string path case
if isinstance(impath, str):
dst = os.path.join(class_dir, os.path.basename(impath))
self._safe_symlink(impath, dst)
continue
# H5 tuple path case: ('h5', abs_path, key, index)
if isinstance(impath, tuple) and len(impath) == 4 and impath[0] == 'h5':
_, fpath, key, index = impath
try:
with h5py.File(fpath, 'r') as f:
arr = f[key][int(index)]
arr = np.asarray(arr)
# CHW -> HWC if needed
if arr.ndim == 3 and arr.shape[0] in (1, 3) and arr.shape[-1] not in (1, 3):
arr = np.transpose(arr, (1, 2, 0))
if arr.dtype != np.uint8:
arr = arr.astype(np.uint8)
if arr.ndim == 2:
img = Image.fromarray(arr, mode='L').convert('RGB')
else:
if arr.shape[-1] == 1:
img = Image.fromarray(arr.squeeze(-1), mode='L').convert('RGB')
else:
img = Image.fromarray(arr, mode='RGB')
except Exception:
continue
c = per_class_count.get(item.classname, 0)
per_class_count[item.classname] = c + 1
dst = os.path.join(class_dir, f"{c:08d}.png")
try:
img.save(dst)
except Exception:
pass
continue
# PIL Image case
if isinstance(impath, Image.Image):
img = impath
c = per_class_count.get(item.classname, 0)
per_class_count[item.classname] = c + 1
dst = os.path.join(class_dir, f"{c:08d}.png")
try:
img.convert('RGB').save(dst)
except Exception:
pass
continue
# Unsupported type: skip
def get_data(self):
return super().get_data()
class StanfordCarsCL(ImageFolderDataset):
"""Continuum-compatible StanfordCars built on a symlinked cache.
We reuse the MTIL StanfordCars parser to read splits and then create a
class-folder tree with symlinks so that ImageFolderDataset can load it.
"""
def __init__(self, data_root: str, train: bool = True, download: bool = False):
# data_root is the global dataset_root, which contains 'stanford_cars/'
self.dataset_dir = os.path.join(data_root, 'stanford_cars')
self.cache_split = 'train' if train else 'test'
self.cache_dir = os.path.join(self.dataset_dir, 'cl_cache', self.cache_split)
self._ensure_cache(data_root, train)
super().__init__(data_path=self.cache_dir, train=train, download=download)
def _safe_symlink(self, src: str, dst: str):
try:
if not os.path.exists(dst):
os.symlink(src, dst)
except OSError as e:
# Fallback: if symlink not permitted (e.g., Windows), copy the file
if e.errno in (errno.EPERM, errno.EACCES, errno.ENOTSUP):
if not os.path.exists(dst):
# lazy import to avoid overhead
import shutil
shutil.copy2(src, dst)
else:
raise
def _ensure_cache(self, data_root: str, train: bool):
if os.path.isdir(self.cache_dir) and any(os.scandir(self.cache_dir)):
return # cache exists
os.makedirs(self.cache_dir, exist_ok=True)
mtil = MTILStanfordCars(root=data_root, num_shots=0, seed=1)
items = mtil.train_x if train else mtil.test
# Build class folders and link images
for item in items:
class_dir = os.path.join(self.cache_dir, item.classname)
os.makedirs(class_dir, exist_ok=True)
dst = os.path.join(class_dir, os.path.basename(item.impath))
self._safe_symlink(item.impath, dst)
def get_data(self):
# ImageFolderDataset expects data_path to be set; already set to cache_dir
return super().get_data()
def get_dataset(cfg, is_train, transforms=None):
if cfg.dataset == "cifar100":
data_path = os.path.join(cfg.dataset_root, cfg.dataset)
dataset = CIFAR100(
data_path=data_path,
download=True,
train=is_train,
# transforms=transforms
)
classes_names = dataset.dataset.classes
elif cfg.dataset == "tinyimagenet":
data_path = os.path.join(cfg.dataset_root, cfg.dataset)
dataset = TinyImageNet200(
data_path,
train=is_train,
download=True
)
classes_names = get_dataset_class_names(cfg.workdir, cfg.dataset)
elif cfg.dataset == "imagenet100":
data_path = os.path.join(cfg.dataset_root, "ImageNet")
dataset = ImageNet100(
data_path,
train=is_train,
data_subset=os.path.join('/home/dhw/yjz_workspace/project1_y/CIL_ours_compare_v3_lr_5e_3_1router_l2/Continual-CLIP/dataset_reqs/imagenet100_splits', "train_100.txt" if is_train else "val_100.txt")
)
classes_names = get_dataset_class_names(cfg.workdir, cfg.dataset)
elif cfg.dataset == "imagenet1000":
data_path = os.path.join(cfg.dataset_root, cfg.dataset)
dataset = ImageNet1000(
data_path,
train=is_train
)
classes_names = get_dataset_class_names(cfg.workdir, cfg.dataset)
elif cfg.dataset == "core50":
data_path = os.path.join(cfg.dataset_root, cfg.dataset)
dataset = dataset = Core50(
data_path,
scenario="domains",
classification="category",
train=is_train
)
classes_names = [
"plug adapters", "mobile phones", "scissors", "light bulbs", "cans",
"glasses", "balls", "markers", "cups", "remote controls"
]
elif cfg.dataset in ("stanfordcars", "stanford_cars", "StanfordCars"):
# Use global dataset_root; MTIL wrapper expects root that contains 'stanford_cars/'
dataset = StanfordCarsCL(
data_root=cfg.dataset_root,
train=is_train,
download=False,
)
# Classes inferred from folder names in cache
# Continuum internally wraps a torchvision ImageFolder as .dataset
try:
classes_names = dataset.dataset.classes # type: ignore[attr-defined]
except Exception:
# Fallback: discover from cache directory
classes_names = sorted([d.name for d in os.scandir(dataset.data_path) if d.is_dir()])
elif cfg.dataset in ("aircraft", "caltech101", "dtd", "eurosat", "oxford_flowers", "food101", "mnist", "oxford_pets", "sun397",
"country211", "sst2", "hatefulmemes", "gtsrb", "resisc45", "fer2013", "ucf101", "cifar10", "stl10",
"voc2007", "imagenet_r", "kitti_distance", "pcam", "clevr_count"):
key2cls = {
"aircraft": MTILFGVCAircraft,
"caltech101": MTILCaltech101,
"dtd": MTILDTD,
"eurosat": MTILEuroSAT,
"oxford_flowers": MTILOxfordFlowers,
"food101": MTILFood101,
"mnist": MTILMNIST,
"oxford_pets": MTILOxfordPets,
"sun397": MTILSUN397,
"country211": MTILCountry211,
"sst2": MTILSST2,
"hatefulmemes": MTILHatefulMemes,
"gtsrb": MTILGTSRB,
"resisc45": MTILRESISC45,
"fer2013": MTILFER2013,
"ucf101": MTILUCF101,
"cifar10": MTILCIFAR10,
"stl10": MTILSTL10,
"voc2007": MTILVOC2007,
"imagenet_r": MTILImageNetR,
"kitti_distance": MTILKittiDistance,
"pcam": MTILPCam,
"clevr_count": MTILCLEVRCount,
}
# Special kwargs per dataset (e.g., VOC2007 single-label mode)
special_kwargs: Dict[str, Dict] = {
"voc2007": {"single_label": True},
}
dataset = MTILImageFolderCL(
data_root=cfg.dataset_root,
mtil_cls=key2cls[cfg.dataset],
train=is_train,
download=False,
mtil_kwargs=special_kwargs.get(cfg.dataset, {}),
)
try:
classes_names = dataset.dataset.classes # type: ignore[attr-defined]
except Exception:
classes_names = sorted([d.name for d in os.scandir(dataset.data_path) if d.is_dir()])
else:
ValueError(f"'{cfg.dataset}' is a invalid dataset.")
return dataset, classes_names
def build_cl_scenarios(cfg, is_train, transforms) -> nn.Module:
dataset, classes_names = get_dataset(cfg, is_train)
if cfg.scenario == "class":
# Build balanced increments if cil_splits is specified: first r tasks get base+1, rest base
num_classes = len(classes_names)
# Determine target number of tasks K:
# Prefer scalar cfg.cil_splits; if it's list/ListConfig (multi-dataset), derive K from cfg.increment.
raw_cil = getattr(cfg, 'cil_splits', 0)
cil_splits_scalar = None
# scalar forms
try:
if isinstance(raw_cil, (int, str)):
cil_splits_scalar = int(raw_cil)
except Exception:
cil_splits_scalar = None
# list/ListConfig -> ambiguous in multi-dataset; ignore and use increment
try:
from omegaconf import ListConfig # type: ignore
if isinstance(raw_cil, (list, tuple, ListConfig)):
cil_splits_scalar = None
except Exception:
pass
scenario = None
# Determine class order: default to contiguous [0..num_classes-1] if not provided
default_order = list(range(num_classes))
class_order_to_use = getattr(cfg, 'class_order', None)
# Coerce Hydra ListConfig to list if needed
try:
from omegaconf import ListConfig # type: ignore
if isinstance(class_order_to_use, ListConfig):
class_order_to_use = list(class_order_to_use)
except Exception:
pass
if class_order_to_use in (None, []):
class_order_to_use = default_order
# decide K
k = None
if isinstance(cil_splits_scalar, int) and cil_splits_scalar > 0:
k = min(cil_splits_scalar, max(1, num_classes))
else:
# derive from cfg.increment (robust to ListConfig/list/str)
inc_val = getattr(cfg, 'increment', None)
inc_int = None
try:
from omegaconf import ListConfig # type: ignore
except Exception:
ListConfig = tuple() # type: ignore
try:
if isinstance(inc_val, (list, tuple)):
inc_int = int(inc_val[0]) if len(inc_val) > 0 else None
elif isinstance(inc_val, ListConfig): # type: ignore
inc_int = int(inc_val[0]) if len(inc_val) > 0 else None
elif inc_val is not None:
inc_int = int(inc_val)
except Exception:
inc_int = None
if inc_int and inc_int > 0:
k = max(1, math.ceil(num_classes / inc_int))
else:
# try from initial_increment
init_val = getattr(cfg, 'initial_increment', None)
try:
if isinstance(init_val, (list, tuple)):
init_int = int(init_val[0]) if len(init_val) > 0 else None
elif isinstance(init_val, ListConfig): # type: ignore
init_int = int(init_val[0]) if len(init_val) > 0 else None
elif init_val is not None:
init_int = int(init_val)
else:
init_int = None
except Exception:
init_int = None
if init_int and init_int > 0:
k = max(1, math.ceil(num_classes / init_int))
# Always construct full increments list; avoid int-only path to prevent sum mismatch on last task
if not k or k <= 0:
k = 1
k = min(k, max(1, num_classes))
base = num_classes // k
r = num_classes % k
increments = [base + 1] * r + [base] * (k - r)
# Construct scenario with full list increments; omit initial_increment to avoid sum mismatch
scenario = ClassIncremental(
dataset,
increment=increments,
transformations=transforms.transforms, # Convert Compose into list
class_order=class_order_to_use,
)
elif cfg.scenario == "domain":
scenario = InstanceIncremental(
dataset,
transformations=transforms.transforms,
)
elif cfg.scenario == "task-agnostic":
NotImplementedError("Method has not been implemented. Soon be added.")
else:
ValueError(f"You have entered `{cfg.scenario}` which is not a defined scenario, "
"please choose from {{'class', 'domain', 'task-agnostic'}}.")
return scenario, classes_names