|
|
|
|
| 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 |
| 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_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 |
| |
| if isinstance(impath, str): |
| dst = os.path.join(class_dir, os.path.basename(impath)) |
| self._safe_symlink(impath, dst) |
| continue |
| |
| 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) |
| |
| 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 |
| |
| 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 |
| |
|
|
| 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): |
| |
| 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: |
| |
| 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, data_root: str, 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) |
| mtil = MTILStanfordCars(root=data_root, num_shots=0, seed=1) |
| items = mtil.train_x if train else mtil.test |
|
|
| |
| 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): |
| |
| 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, |
| |
| ) |
| 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"): |
| |
| dataset = StanfordCarsCL( |
| data_root=cfg.dataset_root, |
| train=is_train, |
| download=False, |
| ) |
| |
| |
| try: |
| classes_names = dataset.dataset.classes |
| except Exception: |
| |
| 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: 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 |
| 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": |
| |
| num_classes = len(classes_names) |
| |
| |
| raw_cil = getattr(cfg, 'cil_splits', 0) |
| cil_splits_scalar = None |
| |
| try: |
| if isinstance(raw_cil, (int, str)): |
| cil_splits_scalar = int(raw_cil) |
| except Exception: |
| cil_splits_scalar = None |
| |
| try: |
| from omegaconf import ListConfig |
| if isinstance(raw_cil, (list, tuple, ListConfig)): |
| cil_splits_scalar = None |
| except Exception: |
| pass |
|
|
| scenario = None |
| |
| default_order = list(range(num_classes)) |
| class_order_to_use = getattr(cfg, 'class_order', None) |
| |
| try: |
| from omegaconf import ListConfig |
| 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 |
| |
| k = None |
| if isinstance(cil_splits_scalar, int) and cil_splits_scalar > 0: |
| k = min(cil_splits_scalar, max(1, num_classes)) |
| else: |
| |
| inc_val = getattr(cfg, 'increment', None) |
| inc_int = None |
| try: |
| from omegaconf import ListConfig |
| except Exception: |
| ListConfig = tuple() |
| 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): |
| 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: |
| |
| 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): |
| 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)) |
|
|
| |
| 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) |
| |
| scenario = ClassIncremental( |
| dataset, |
| increment=increments, |
| transformations=transforms.transforms, |
| 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 |