diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/BEEF.py b/StreamLearn/Algorithm/ClassIncrementalLearning/BEEF.py index 84ae7861fa3396ff61070bbf6f7c5ccc1741eceb..c4c9fbe0d42f596469994e0a92178db63fc9bfe0 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/BEEF.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/BEEF.py @@ -9,7 +9,7 @@ from torch.nn import functional as F from torch.utils.data import DataLoader from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.BEEF as config +import StreamLearn.Config.CIL_cifar100.BEEF as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy, count_parameters @@ -25,6 +25,8 @@ class BEEFISO(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name='cifar100', + method_name="BEEFISO", ) self._snet = None diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/DER.py b/StreamLearn/Algorithm/ClassIncrementalLearning/DER.py index ef0f019bc56bf40db0f31f6ec0bc6c53d235204c..cf19d8db97901c3bbd868c9b660e0e784758ac80 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/DER.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/DER.py @@ -8,7 +8,7 @@ from torch.nn import functional as F from torch.utils.data import DataLoader from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.DER as config +import StreamLearn.Config.CIL_cifar100.DER as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy, count_parameters @@ -27,6 +27,8 @@ class DER(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name='cifar100', + method_name="DER", ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/EWC.py b/StreamLearn/Algorithm/ClassIncrementalLearning/EWC.py index 4a46be228b83286b0b4d3b4581ef8d0806c682df..0d228c415c63ce5343ce1f2c9f5c849bcac2bb3c 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/EWC.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/EWC.py @@ -8,20 +8,27 @@ from torch.nn import functional as F from torch.utils.data import DataLoader from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.EWC as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy class EWC(DeepModelMixinCIL): - def __init__(self): - + def __init__(self, dataset_name='cifar100'): + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.EWC as config + elif dataset_name == 'tv100': + import StreamLearn.Config.CIL_tv100.EWC as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") super().__init__( network=config.network, memory_size=config.memory_size, memory_per_class=config.memory_per_class, device=int(config.device[0]), multiple_gpus=config.device, + dataset_name=dataset_name, + method_name="EWC", ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/FineTune.py b/StreamLearn/Algorithm/ClassIncrementalLearning/FineTune.py index c77c563acb651e7cb6a9399fea69febb047c5927..96c8e49d327b6e681bb4f2042cc6d89b3a2745b7 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/FineTune.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/FineTune.py @@ -7,7 +7,7 @@ from tqdm import tqdm import logging from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.FineTune as config +import StreamLearn.Config.CIL_cifar100.FineTune as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy @@ -21,6 +21,8 @@ class FineTune(DeepModelMixinCIL): device=int(config.device[0]), multiple_gpus=config.device, evaluation_period=2, + dataset_name='cifar100', + method_name='FineTune' ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/Foster.py b/StreamLearn/Algorithm/ClassIncrementalLearning/Foster.py index f2d936dc0e452918a9ac1bbefd07d5467785248b..154cb91bef65d8bcd37241d8b8ff40871d84b271 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/Foster.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/Foster.py @@ -8,15 +8,22 @@ from torch.nn import functional as F from torch.utils.data import DataLoader from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.Foster as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy, KD_loss from StreamLearn.Network.Inc_Net import FOSTERNet class Foster(DeepModelMixinCIL): - def __init__(self): + def __init__(self, dataset_name='cifar100'): + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.Foster as config + elif dataset_name == 'tv100': + import StreamLearn.Config.CIL_tv100.Foster as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") + super().__init__( network=config.network, memory_size=config.memory_size, @@ -25,6 +32,8 @@ class Foster(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name=dataset_name, + method_name="Foster", ) self._snet = None diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/LwF.py b/StreamLearn/Algorithm/ClassIncrementalLearning/LwF.py index f0888a1b096074e5c3cf9c4bf9b57d525cdd0ff2..df0b9448d5b73e041c3537c8523bbec78e51582c 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/LwF.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/LwF.py @@ -7,13 +7,20 @@ from tqdm import tqdm import logging from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.LwF as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy, KD_loss class LwF(DeepModelMixinCIL): - def __init__(self): + def __init__(self, dataset_name='cifar100'): + + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.LwF as config + elif dataset_name == 'tv100': + import StreamLearn.Config.CIL_tv100.LwF as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") super().__init__( network=config.network, @@ -23,6 +30,8 @@ class LwF(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name=dataset_name, + method_name="LwF", ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/MEMO.py b/StreamLearn/Algorithm/ClassIncrementalLearning/MEMO.py index 3793cf64a6e3c7ad56fbd8bbddea7e750b7ae278..a229d5e258e94ad234699340bbd2eba2bcef4288 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/MEMO.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/MEMO.py @@ -8,17 +8,22 @@ from torch import optim from torch.nn import functional as F from torch.utils.data import DataLoader -import StreamLearn.Config.MEMO as config from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL from StreamLearn.utils import tensor2numpy, count_parameters class MEMO(DeepModelMixinCIL): - def __init__(self): + def __init__(self, dataset_name='cifar100'): self._old_base = None - + + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.MEMO as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") + super().__init__( network=config.network, memory_size=config.memory_size, @@ -27,6 +32,8 @@ class MEMO(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name=dataset_name, + method_name="MEMO", ) self._set_device() @@ -248,17 +255,17 @@ class MEMO(DeepModelMixinCIL): prog_bar.set_description(info) logging.info(info) - def save_checkpoint(self, test_acc): - assert config.model_name == 'finetune' - checkpoint_name = f"checkpoints/finetune_{config.csv_name}" - _checkpoint_cpu = copy.deepcopy(self._network) - if isinstance(_checkpoint_cpu, nn.DataParallel): - _checkpoint_cpu = _checkpoint_cpu.module - _checkpoint_cpu.cpu() - save_dict = { - "tasks": self._cur_task, - "convnet": _checkpoint_cpu.convnet.state_dict(), - "fc": _checkpoint_cpu.fc.state_dict(), - "test_acc": test_acc - } - torch.save(save_dict, "{}_{}.pkl".format(checkpoint_name, self._cur_task)) + # def save_checkpoint(self, test_acc): + # assert config.model_name == 'finetune' + # checkpoint_name = f"checkpoints/finetune_{config.csv_name}" + # _checkpoint_cpu = copy.deepcopy(self._network) + # if isinstance(_checkpoint_cpu, nn.DataParallel): + # _checkpoint_cpu = _checkpoint_cpu.module + # _checkpoint_cpu.cpu() + # save_dict = { + # "tasks": self._cur_task, + # "convnet": _checkpoint_cpu.convnet.state_dict(), + # "fc": _checkpoint_cpu.fc.state_dict(), + # "test_acc": test_acc + # } + # torch.save(save_dict, "{}_{}.pkl".format(checkpoint_name, self._cur_task)) diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/Replay.py b/StreamLearn/Algorithm/ClassIncrementalLearning/Replay.py index 71b4df8fcb421b41c1fcd34d6c68b91ebab16b34..5658320c9d79b7c47f8072a08b9a23e1ae5a5218 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/Replay.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/Replay.py @@ -8,7 +8,7 @@ from torch.nn import functional as F from torch.utils.data import DataLoader from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.Replay as config +import StreamLearn.Config.CIL_cifar100.Replay as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy @@ -24,6 +24,8 @@ class Replay(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name='cifar100', + method_name="Replay", ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/SimpleCIL.py b/StreamLearn/Algorithm/ClassIncrementalLearning/SimpleCIL.py index 44562fb2b7f4610fd0ca772c9dc58a45517f4ab8..19ed9c94b5d1b27d7fac26cee1c1eac8632f1c7a 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/SimpleCIL.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/SimpleCIL.py @@ -8,14 +8,19 @@ from torch import optim from torch.nn import functional as F from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.SimpleCIL as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy class SimpleCIL(DeepModelMixinCIL): - def __init__(self): - + def __init__(self, dataset_name='cifar100'): + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.SimpleCIL as config + elif dataset_name == 'tv100': + import StreamLearn.Config.CIL_tv100.SimpleCIL as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") super().__init__( network=config.network, memory_size=config.memory_size, @@ -24,6 +29,8 @@ class SimpleCIL(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name=dataset_name, + method_name="SimpleCIL", ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/WA.py b/StreamLearn/Algorithm/ClassIncrementalLearning/WA.py index 5835c521eadbac0fa09e8a4f4311b0d80de52176..af4bbc4e635f3ce252d72c16a4ee6bcf1ff6378f 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/WA.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/WA.py @@ -8,14 +8,19 @@ from torch.nn import functional as F from torch.utils.data import DataLoader from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.WA as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy, KD_loss class WA(DeepModelMixinCIL): - def __init__(self): - + def __init__(self, dataset_name='cifar100'): + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.WA as config + elif dataset_name == 'tv100': + import StreamLearn.Config.CIL_tv100.WA as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") super().__init__( network=config.network, memory_size=config.memory_size, @@ -24,6 +29,8 @@ class WA(DeepModelMixinCIL): multiple_gpus=config.device, seed=config.seed, evaluation_period=2, + dataset_name=dataset_name, + method_name="WA", ) self._set_device() diff --git a/StreamLearn/Algorithm/ClassIncrementalLearning/iCaRL.py b/StreamLearn/Algorithm/ClassIncrementalLearning/iCaRL.py index cc341e84f32ab92fc8c5e4d8ebb84805924e0f48..5bee16beeca24a9b952de09bc18015d40efadfb3 100644 --- a/StreamLearn/Algorithm/ClassIncrementalLearning/iCaRL.py +++ b/StreamLearn/Algorithm/ClassIncrementalLearning/iCaRL.py @@ -7,12 +7,19 @@ from tqdm import tqdm import logging from StreamLearn.Base.DeepModelMixin import DeepModelMixinCIL -import StreamLearn.Config.iCaRL as config from StreamLearn.Dataset.CILDataset import CILDataset from StreamLearn.utils import tensor2numpy, KD_loss class iCaRL(DeepModelMixinCIL): - def __init__(self): + def __init__(self, dataset_name='cifar100'): + + global config + if dataset_name == 'cifar100': + import StreamLearn.Config.CIL_cifar100.iCaRL as config + elif dataset_name == 'tv100': + import StreamLearn.Config.CIL_tv100.iCaRL as config + else: + raise ValueError(f"Dataset not supported: {dataset_name}") super().__init__( network=config.network, @@ -21,6 +28,8 @@ class iCaRL(DeepModelMixinCIL): device=int(config.device[0]), seed=config.seed, multiple_gpus=config.device, + dataset_name=dataset_name, + method_name='iCaRL', ) self._set_device() diff --git a/StreamLearn/Base/DeepModelMixin.py b/StreamLearn/Base/DeepModelMixin.py index c3ef535efa617f45cb3e9fc4c9a796adb121b1e0..f4c5c030e502c0f0ac762c4381eb9e74162bd586 100644 --- a/StreamLearn/Base/DeepModelMixin.py +++ b/StreamLearn/Base/DeepModelMixin.py @@ -33,6 +33,8 @@ class DeepModelMixinCIL(StreamEstimator): seed: int = 1989, evaluation_topk: int = 5, evaluation_period: int = 1, + dataset_name: str = 'cifar100', + method_name: str = 'icarl', ): self._cur_task = -1 self._known_classes = 0 @@ -60,13 +62,15 @@ class DeepModelMixinCIL(StreamEstimator): self.train_loader = None self.test_loader = None - logs_name = "logs/CIL_CIFAR100/" + self.dataset_name = dataset_name + self.method_name = method_name + logs_name = f"logs/CIL_{method_name}_{dataset_name}/" if not os.path.exists(logs_name): os.makedirs(logs_name) # TODO: lack of flexibility - logfilename = "logs/CIL_CIFAR100/ResNet_" + time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()) + logfilename = f"logs/CIL_{method_name}_{dataset_name}/ResNet_" + time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime()) logging.info("Log file: {}".format(logfilename)) print("Log file: {}".format(logfilename)) @@ -84,9 +88,13 @@ class DeepModelMixinCIL(StreamEstimator): cnn_curve, nme_curve = {"top1": [], "top5": []}, {"top1": [], "top5": []} for task_id in range(dataset.nb_tasks): + print(f"Training task {task_id}...") self.incremental_train(dataset) cnn_accy, nme_accy = self.eval_task(dataset) + print(f"Task {task_id} done.") self.after_task() + print("saving model...") + self.save_checkpoint() if nme_accy is not None: logging.info("CNN: {}".format(cnn_accy["grouped"])) @@ -177,8 +185,7 @@ class DeepModelMixinCIL(StreamEstimator): :param cls_mode: str, either 'fc' or 'nme'; type of classifiers """ self._network.eval() - X = X.reshape(-1, 3, 32, 32) - X = torch.from_numpy(X).to(self._device).float() + X = X.to(self._device) if cls_mode.lower() == 'fc': outputs = self._network(X)["logits"] @@ -206,8 +213,18 @@ class DeepModelMixinCIL(StreamEstimator): @torch.inference_mode() def test(self, data): - y_pred = self.predict(X=data._test_data) - statistics = self.evaluate(y_pred, data._test_targets) + test_dataset = data.get_dataset(data.class_order, source="test", mode="test") + test_loader = DataLoader(test_dataset, batch_size=self.batch_size, shuffle=False, num_workers=4) + + predicts = [] + true_labels = [] + for idx, X, y in test_loader: + y_pred = self.predict(X=X) + predicts.append(y_pred) + true_labels.append(y) + predicts = np.concatenate(predicts) + true_labels = np.concatenate(true_labels) + statistics = self.evaluate(predicts, true_labels) return statistics def _extract_vectors(self, loader): @@ -556,3 +573,10 @@ class DeepModelMixinCIL(StreamEstimator): torch.backends.cudnn.benchmark = False np.random.seed(seed) + + def save_checkpoint(self): + checkpoint_dir = f"checkpoints/CIL_{self.method_name}_{self.dataset_name}" + if not os.path.exists(checkpoint_dir): + os.makedirs(checkpoint_dir) + checkpoint_path = os.path.join(checkpoint_dir, f"model_task_{self._cur_task}.pth") + torch.save(self._network.state_dict(), checkpoint_path) \ No newline at end of file diff --git a/StreamLearn/Config/CIL.py b/StreamLearn/Config/CIL.py index 3cdf1e48a0a6fe3b143c8f550642a07dc7e5036e..cfa3a237c87b8a2a3ddf5557390580317b133a59 100644 --- a/StreamLearn/Config/CIL.py +++ b/StreamLearn/Config/CIL.py @@ -2,6 +2,7 @@ import argparse parser = argparse.ArgumentParser() parser.add_argument('--root', type=str, default='./dataset') +parser.add_argument('--dataset_name', type=str, default='cifar100') parser.add_argument('--download', type=bool, default=False) parser.add_argument('--method_name', type=str, default='memo') @@ -14,4 +15,5 @@ dataset_args = { model_args = { 'method_name': args.method_name, + 'dataset_name': args.dataset_name, } \ No newline at end of file diff --git a/StreamLearn/Config/BEEF.py b/StreamLearn/Config/CIL_cifar100/BEEF.py similarity index 100% rename from StreamLearn/Config/BEEF.py rename to StreamLearn/Config/CIL_cifar100/BEEF.py diff --git a/StreamLearn/Config/DER.py b/StreamLearn/Config/CIL_cifar100/DER.py similarity index 100% rename from StreamLearn/Config/DER.py rename to StreamLearn/Config/CIL_cifar100/DER.py diff --git a/StreamLearn/Config/EWC.py b/StreamLearn/Config/CIL_cifar100/EWC.py similarity index 100% rename from StreamLearn/Config/EWC.py rename to StreamLearn/Config/CIL_cifar100/EWC.py diff --git a/StreamLearn/Config/FineTune.py b/StreamLearn/Config/CIL_cifar100/FineTune.py similarity index 100% rename from StreamLearn/Config/FineTune.py rename to StreamLearn/Config/CIL_cifar100/FineTune.py diff --git a/StreamLearn/Config/Foster.py b/StreamLearn/Config/CIL_cifar100/Foster.py similarity index 100% rename from StreamLearn/Config/Foster.py rename to StreamLearn/Config/CIL_cifar100/Foster.py diff --git a/StreamLearn/Config/LwF.py b/StreamLearn/Config/CIL_cifar100/LwF.py similarity index 100% rename from StreamLearn/Config/LwF.py rename to StreamLearn/Config/CIL_cifar100/LwF.py diff --git a/StreamLearn/Config/MEMO.py b/StreamLearn/Config/CIL_cifar100/MEMO.py similarity index 100% rename from StreamLearn/Config/MEMO.py rename to StreamLearn/Config/CIL_cifar100/MEMO.py diff --git a/StreamLearn/Config/Replay.py b/StreamLearn/Config/CIL_cifar100/Replay.py similarity index 100% rename from StreamLearn/Config/Replay.py rename to StreamLearn/Config/CIL_cifar100/Replay.py diff --git a/StreamLearn/Config/SimpleCIL.py b/StreamLearn/Config/CIL_cifar100/SimpleCIL.py similarity index 100% rename from StreamLearn/Config/SimpleCIL.py rename to StreamLearn/Config/CIL_cifar100/SimpleCIL.py diff --git a/StreamLearn/Config/WA.py b/StreamLearn/Config/CIL_cifar100/WA.py similarity index 100% rename from StreamLearn/Config/WA.py rename to StreamLearn/Config/CIL_cifar100/WA.py diff --git a/StreamLearn/Config/iCaRL.py b/StreamLearn/Config/CIL_cifar100/iCaRL.py similarity index 100% rename from StreamLearn/Config/iCaRL.py rename to StreamLearn/Config/CIL_cifar100/iCaRL.py diff --git a/StreamLearn/Config/CIL_tv100/EWC.py b/StreamLearn/Config/CIL_tv100/EWC.py new file mode 100644 index 0000000000000000000000000000000000000000..07535a4970590a8935a1f256933bc93fbbb19586 --- /dev/null +++ b/StreamLearn/Config/CIL_tv100/EWC.py @@ -0,0 +1,30 @@ +from StreamLearn.Network.Inc_Net import IncrementalNet + +model_name = "resnet18" +seed = 1989 + +network = IncrementalNet(model_name, seed=seed) + +memory_size = 0 +memory_per_class = 0 +fixed_memory = False + +device = [0] + +init_epoch = 200 +init_lr = 0.1 +init_milestones = [60, 120, 170] +init_lr_decay = 0.1 +init_weight_decay = 0.0005 + + +epochs = 170 +lrate = 0.1 +milestones = [70, 120, 150] +lrate_decay = 0.1 +batch_size = 128 +weight_decay = 2e-4 +num_workers = 4 +T = 2 +lamda = 1000 +fishermax = 0.0001 \ No newline at end of file diff --git a/StreamLearn/Config/CIL_tv100/Foster.py b/StreamLearn/Config/CIL_tv100/Foster.py new file mode 100644 index 0000000000000000000000000000000000000000..094490ea754db4c057d0c24d3b41f6537f9d1739 --- /dev/null +++ b/StreamLearn/Config/CIL_tv100/Foster.py @@ -0,0 +1,38 @@ +from StreamLearn.Network.Inc_Net import FOSTERNet + +model_name = "resnet18" # backbone +seed = 1989 + +network = FOSTERNet(model_name, seed=seed) + +memory_size = 1000 +memory_per_class = 20 +fixed_memory = True +shuffle = True +init_cls = 50 +increment = 10 + +device = [0] + +beta1 = 0.96 +beta2 = 0.97 +oofc = "ft" +is_teacher_wa = False +is_student_wa = False +lambda_okd = 1 +wa_value = 1 +init_epochs = 200 +init_lr = 0.1 +init_weight_decay = 5e-4 + +lr = 0.1 +batch_size = 128 +weight_decay = 5e-4 + +boosting_epochs = 170 +compression_epochs = 130 + +num_workers = 8 +T = 2 + +EPSILON = 1e-8 \ No newline at end of file diff --git a/StreamLearn/Config/CIL_tv100/LwF.py b/StreamLearn/Config/CIL_tv100/LwF.py new file mode 100644 index 0000000000000000000000000000000000000000..ae35bf6eae52b0fc06dd4613b748889ba72bbdaf --- /dev/null +++ b/StreamLearn/Config/CIL_tv100/LwF.py @@ -0,0 +1,29 @@ +from StreamLearn.Network.Inc_Net import IncrementalNet + +model_name = "resnet18" +seed = 1989 + +network = IncrementalNet(model_name, seed=seed) + +memory_size = 0 +memory_per_class = 0 +fixed_memory = False + +device = [0] + +init_epoch = 20 +init_lr = 0.1 +init_milestones = [60, 120, 160] +init_lr_decay = 0.1 +init_weight_decay = 0.0005 + + +epochs = 17 +lrate = 0.1 +milestones = [60, 120, 180, 220] +lrate_decay = 0.1 +batch_size = 128 +weight_decay = 2e-4 +num_workers = 8 +T = 2 +lamda = 3 \ No newline at end of file diff --git a/StreamLearn/Config/CIL_tv100/SimpleCIL.py b/StreamLearn/Config/CIL_tv100/SimpleCIL.py new file mode 100644 index 0000000000000000000000000000000000000000..4946c1a5cd10ee19e7ec437e2f64d76b98ce93a6 --- /dev/null +++ b/StreamLearn/Config/CIL_tv100/SimpleCIL.py @@ -0,0 +1,22 @@ +from StreamLearn.Network.Inc_Net import IncrementalNetCosineSimple + +model_name = "resnet18" +seed = 1989 + +network = IncrementalNetCosineSimple(model_name, seed=seed) + +memory_size = 0 +memory_per_class = 0 +fixed_memory = False + +device = [0] + +init_epoch = 200 +init_lr = 0.01 +init_milestones = [80, 120] +init_lr_decay = 0.1 +init_weight_decay = 0.0005 +min_lr = 0 + +num_workers = 8 +batch_size = 128 # TODO: 256 ? \ No newline at end of file diff --git a/StreamLearn/Config/CIL_tv100/WA.py b/StreamLearn/Config/CIL_tv100/WA.py new file mode 100644 index 0000000000000000000000000000000000000000..8e63958bb72cd76e0c3c51ec28e4f0e493cccc14 --- /dev/null +++ b/StreamLearn/Config/CIL_tv100/WA.py @@ -0,0 +1,31 @@ +from StreamLearn.Network.Inc_Net import IncrementalNet + +model_name = "resnet18" +seed = 1989 + +network = IncrementalNet(model_name, seed=seed) + +memory_size = 1000 +memory_per_class = 20 +fixed_memory = False + +device = [0] + +EPSILON = 1e-8 + + +init_epoch = 200 +init_lr = 0.1 +init_milestones = [60, 120, 170] +init_lr_decay = 0.1 +init_weight_decay = 0.0005 + + +epochs = 170 +lrate = 0.1 +milestones = [60, 100, 140] +lrate_decay = 0.1 +batch_size = 128 +weight_decay = 2e-4 +num_workers = 8 +T = 2 \ No newline at end of file diff --git a/StreamLearn/Config/CIL_tv100/iCaRL.py b/StreamLearn/Config/CIL_tv100/iCaRL.py new file mode 100644 index 0000000000000000000000000000000000000000..17fda5a0c5db35bda99ecbf12b9d9ce942c60493 --- /dev/null +++ b/StreamLearn/Config/CIL_tv100/iCaRL.py @@ -0,0 +1,28 @@ +from StreamLearn.Network.Inc_Net import IncrementalNet + +model_name = "resnet18" +seed = 1989 + +network = IncrementalNet(model_name, seed=seed) + +memory_size = 1000 +memory_per_class = 20 +fixed_memory = False + +device = [0] + +init_epoch = 20 +init_lr = 0.1 +init_milestones = [60, 120, 170] +init_lr_decay = 0.1 +init_weight_decay = 0.0005 + + +epochs = 17 +lrate = 0.1 +milestones = [80, 120] +lrate_decay = 0.1 +batch_size = 128 +weight_decay = 2e-4 +num_workers = 8 +T = 2 \ No newline at end of file diff --git a/StreamLearn/Config/Simulator.py b/StreamLearn/Config/Simulator.py index 89a8ce5df254752f22c7e6ff6f9b9f20987c74cc..f6d93c60d99cbf6aa912d8ed7d9aae66564e21c9 100644 --- a/StreamLearn/Config/Simulator.py +++ b/StreamLearn/Config/Simulator.py @@ -26,7 +26,8 @@ from StreamLearn.Config.AdaPrompt import dataset_configs as adaprompt_data_args # dataset params dataset = { 'adaprompt':'StreamLearn.Dataset.TTADataset.CIFAR10C', - 'cil': 'StreamLearn.Dataset.Vision.CIFAR100_CIL.CIFAR100_CIL', # 5 + 'cil_cifar100': 'StreamLearn.Dataset.Vision.CIFAR100_CIL.CIFAR100_CIL', # 5 + 'cil_tv100': 'StreamLearn.Dataset.Vision.TV100Dataset.TV100_Dataset', # 5 'gdro': 'StreamLearn.Dataset.CIFAR10_Dataset.CIFAR10_Dataset', # 1-2 'hamos': 'StreamLearn.Dataset.CIFAR10_Dataset.CIFAR10_Dataset', # 1-2 'wgdro': 'StreamLearn.Dataset.CIFAR10_Dataset.CIFAR10_Dataset', # 1-2 @@ -49,7 +50,8 @@ dataset = { dataset_config = { 'adaprompt': adaprompt_data_args, - 'cil': cil_dataset_args, + 'cil_cifar100': cil_dataset_args, + 'cil_tv100': cil_dataset_args, 'gdro': gdro_args, 'hamos': hamos_args, 'wgdro': gdro_args, @@ -71,7 +73,8 @@ dataset_config = { # model params model = { 'adaprompt':'StreamLearn.Algorithm.AdaPrompt.Adaprompt.Adaprompt', - 'cil': 'StreamLearn.utils.get_CIL_method', + 'cil_cifar100': 'StreamLearn.utils.get_CIL_method', + 'cil_tv100': 'StreamLearn.utils.get_CIL_method', 'gdro': 'StreamLearn.Algorithm.GDRO.GDRO.GDRO', 'hamos': 'StreamLearn.Algorithm.HamOS.HamOS.HamOS', 'wgdro': 'StreamLearn.Algorithm.GDRO.WGDRO.WGDRO', @@ -93,7 +96,8 @@ model = { } model_config = { 'adaprompt':adaprompt_args, - 'cil': cil_model_args, + 'cil_cifar100': cil_model_args, + 'cil_tv100': cil_model_args, 'gdro': gdro_args, 'hamos': hamos_args, 'wgdro': gdro_args, diff --git a/StreamLearn/Dataset/Vision/CIFAR100_CIL.py b/StreamLearn/Dataset/Vision/CIFAR100_CIL.py index 4c70decdc6c035d56b8ba8a5aff8fb4cd5a77885..87335cbef5152f914ab0bb923b88bdeee93bed5a 100644 --- a/StreamLearn/Dataset/Vision/CIFAR100_CIL.py +++ b/StreamLearn/Dataset/Vision/CIFAR100_CIL.py @@ -3,7 +3,7 @@ from StreamLearn.Base.VisionMixin import VisionMixin from torchvision import datasets, transforms import numpy as np -import StreamLearn.Config.MEMO as config +import StreamLearn.Config.CIL_cifar100.MEMO as config #修改了nb_tasks的参数传递 class CIFAR100_CIL(CILDataset, VisionMixin): base_folder = "cifar-100-python" diff --git a/StreamLearn/Dataset/Vision/TV100Dataset.py b/StreamLearn/Dataset/Vision/TV100Dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..dd098427895eb8401b7391601c9dc7c72b18113e --- /dev/null +++ b/StreamLearn/Dataset/Vision/TV100Dataset.py @@ -0,0 +1,146 @@ +import os +import numpy as np +from PIL import Image +import pandas as pd +from torchvision.transforms import transforms +from StreamLearn.Dataset.StreamDataset import StreamDataset +from torch.utils.data import DataLoader +from torchvision import datasets, transforms +import StreamLearn.Config.CIL_cifar100.MEMO as config +from StreamLearn.Base.VisionMixin import VisionMixin +from StreamLearn.Dataset.CILDataset import CILDataset, DummyDataset +class TV100_Dataset(CILDataset,VisionMixin): + base_folder = "tv100" + class_order = np.arange(100).tolist() + init_cls = 20 + def __init__(self, root=None,shuffle=False,random_state=None,download:bool =False,nb_tasks: int=5): + super(TV100_Dataset, self).__init__(dataset_name='TV100', init_cls=config.init_cls, increment=config.increment) + self.train_transform = None + self.name = 'TV100' + self.root = './dataset/tv100' + self.shuffle = shuffle + self.random_state = random_state + self.download = download + self.nb_tasks = nb_tasks + self.use_path = True + self._train_trsf = [ + transforms.RandomResizedCrop(224), # 随机裁剪+缩放(比固定Resize更鲁棒) + transforms.RandomHorizontalFlip(), # 随机水平翻转(常规增强) + transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), # 色彩抖动 + transforms.ToTensor() # 转为张量 + ] + + # 测试专属变换:无增强,仅保证尺寸统一+转张量 + self._test_trsf = [ + transforms.Resize((224, 224)), # 固定缩放到224×224(测试需稳定) + transforms.ToTensor() # 转为张量 + ] + + # 训练/测试共用变换:标准化(沿用ImageNet统计值,适配预训练模型) + self._common_trsf = [ + transforms.Normalize( + mean=[0.485, 0.456, 0.406], # ImageNet均值 + std=[0.229, 0.224, 0.225] # ImageNet标准差 + ) + ] + self._train_data,self._train_targets,self._test_data,self._test_targets=None,None,None,None + self.init_dataset() + + def load_data_set(self, train=True, transform=None): + store_transform = transforms.Compose([ + transforms.Resize((224, 224)), # 统一尺寸(与CIFAR100的32×32对应) + # 不添加ToTensor()和Normalize(),保留PIL图像格式 + ]) + self.train_dataset = datasets.ImageFolder(root=os.path.join(self.root, 'train'), transform=store_transform) + self.test_dataset = datasets.ImageFolder(root=os.path.join(self.root, 'test'), transform=store_transform) + self._train_data, self._train_targets = split_images_labels(self.train_dataset.imgs) + self._test_data, self._test_targets = split_images_labels(self.test_dataset.imgs) + + def init_dataset(self, images=None, labels=None) -> None: + self.load_data_set() + + self._update_order() + + def get_dataset(self,indices,source,mode,appendent=None,ret_data=False,m_rate=None): + if source == "train": + x, y = self._train_data, self._train_targets + elif source == "test": + x, y = self._test_data, self._test_targets + else: + raise ValueError("Unknown data source {}.".format(source)) + if mode == "train": + trsf = transforms.Compose([*self._train_trsf, *self._common_trsf]) + elif mode == "flip": + trsf = transforms.Compose( + [ + *self._test_trsf, + transforms.RandomHorizontalFlip(p=1.0), + *self._common_trsf, + ] + ) + elif mode == "test": + trsf = transforms.Compose([*self._test_trsf, *self._common_trsf]) + else: + raise ValueError("Unknown mode {}.".format(mode)) + + data,targets=[],[] + for idx in indices: + if m_rate is None: + class_data, class_targets = self._select( + x, y, low_range=idx, high_range=idx + 1 + ) + else: + class_data, class_targets = self._select_rmm( + x, y, low_range=idx, high_range=idx + 1, m_rate=m_rate + ) + data.append(class_data) + targets.append(class_targets) + + if appendent is not None and len(appendent) != 0: + appendent_data, appendent_targets = appendent + data.append(appendent_data) + targets.append(appendent_targets) + + data, targets = np.concatenate(data), np.concatenate(targets) + + if ret_data: + return data, targets, DummyDataset(data, targets, trsf, self.use_path) + else: + return DummyDataset(data, targets, trsf, self.use_path) + + + def _select(self, x, y, low_range, high_range): + idxes = np.where(np.logical_and(y >= low_range, y < high_range))[0] + + if isinstance(x, np.ndarray): + x_return = x[idxes] + else: + x_return = [] + for id in idxes: + x_return.append(x[id]) + if len(idxes) == 0: + warning_msg = ( + f"⚠️ _select 筛选空样本!\n" + f"筛选范围:low_range={low_range}, high_range={high_range}\n" + f"当前标签数组 y 的范围:y.min()={y.min()}, y.max()={y.max()}\n" + f"当前标签数组 y 的唯一值:{np.unique(y)}" + ) + print(warning_msg) + return x_return, y[idxes] + + +def split_images_labels(imgs): + # split trainset.imgs in ImageFolder + images = [] + labels = [] + for item in imgs: + images.append(item[0]) + labels.append(item[1]) + + return np.array(images), np.array(labels) + +if __name__ == "__main__": + Data = TV100_Dataset(args=None) + train_dataset, classes = Data.load_data_set(train=True) + test_dataset, _ = Data.load_data_set(train=False) + \ No newline at end of file diff --git a/StreamLearn/Network/Inc_Net.py b/StreamLearn/Network/Inc_Net.py index 18552af27e2bd0a88e56b9aa388eb6a49f4b5c52..6ab7775461254af7643ba490ef26902a8c69514f 100644 --- a/StreamLearn/Network/Inc_Net.py +++ b/StreamLearn/Network/Inc_Net.py @@ -19,6 +19,9 @@ def get_convnet(model_name, pretrained=False): elif name == "memo_resnet32": _basenet, _adaptive_net = get_memo_resnet32() return _basenet, _adaptive_net + elif name == "resnet18": + from StreamLearn.Network.Resnet import resnet18 + return resnet18(args={"dataset":"tv100"}) else: raise NotImplementedError diff --git a/StreamLearn/Network/Resnet.py b/StreamLearn/Network/Resnet.py new file mode 100644 index 0000000000000000000000000000000000000000..e8c7377521c4993368e150b012fe70a57daf1607 --- /dev/null +++ b/StreamLearn/Network/Resnet.py @@ -0,0 +1,394 @@ +''' +Reference: +https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py +''' +import torch +import torch.nn as nn +try: + from torchvision.models.utils import load_state_dict_from_url +except: + from torch.hub import load_state_dict_from_url + +__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', + 'resnet152', 'resnext50_32x4d', 'resnext101_32x8d', + 'wide_resnet50_2', 'wide_resnet101_2'] + + +model_urls = { + 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', + 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', + 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', + 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', + 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', + 'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth', + 'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth', + 'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth', + 'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1): + """3x3 convolution with padding""" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=dilation, groups=groups, bias=False, dilation=dilation) + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + __constants__ = ['downsample'] + + def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, + base_width=64, dilation=1, norm_layer=None): + super(BasicBlock, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + if groups != 1 or base_width != 64: + raise ValueError('BasicBlock only supports groups=1 and base_width=64') + if dilation > 1: + raise NotImplementedError("Dilation > 1 not supported in BasicBlock") + # Both self.conv1 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = norm_layer(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = norm_layer(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + __constants__ = ['downsample'] + + def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, + base_width=64, dilation=1, norm_layer=None): + super(Bottleneck, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + width = int(planes * (base_width / 64.)) * groups + # Both self.conv2 and self.downsample layers downsample the input when stride != 1 + self.conv1 = conv1x1(inplanes, width) + self.bn1 = norm_layer(width) + self.conv2 = conv3x3(width, width, stride, groups, dilation) + self.bn2 = norm_layer(width) + self.conv3 = conv1x1(width, planes * self.expansion) + self.bn3 = norm_layer(planes * self.expansion) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + identity = self.downsample(x) + + out += identity + out = self.relu(out) + + return out + + + + + +class ResNet(nn.Module): + + def __init__(self, block, layers, num_classes=1000, zero_init_residual=False, + groups=1, width_per_group=64, replace_stride_with_dilation=None, + norm_layer=None,args=None): + super(ResNet, self).__init__() + if norm_layer is None: + norm_layer = nn.BatchNorm2d + self._norm_layer = norm_layer + + self.inplanes = 64 + self.dilation = 1 + if replace_stride_with_dilation is None: + # each element in the tuple indicates if we should replace + # the 2x2 stride with a dilated convolution instead + replace_stride_with_dilation = [False, False, False] + if len(replace_stride_with_dilation) != 3: + raise ValueError("replace_stride_with_dilation should be None " + "or a 3-element tuple, got {}".format(replace_stride_with_dilation)) + self.groups = groups + self.base_width = width_per_group + + assert args is not None, "you should pass args to resnet" + if 'cifar' in args["dataset"]: + if args["model_name"] == "memo": + self.conv1 = nn.Sequential( + nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False), + nn.BatchNorm2d(self.inplanes), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=3, stride=2, padding=1), + ) + else: + self.conv1 = nn.Sequential( + nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False), + nn.BatchNorm2d(self.inplanes), + nn.ReLU(inplace=True)) + elif 'tv100' in args["dataset"]: + # if args["init_cls"] == args["increment"]: + # self.conv1 = nn.Sequential( + # nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3, bias=False), + # nn.BatchNorm2d(self.inplanes), + # nn.ReLU(inplace=True), + # nn.MaxPool2d(kernel_size=3, stride=2, padding=1), + # ) + # else: + self.conv1 = nn.Sequential( + nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False), + nn.BatchNorm2d(self.inplanes), + nn.ReLU(inplace=True), + nn.MaxPool2d(kernel_size=3, stride=2, padding=1), + ) + + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2, + dilate=replace_stride_with_dilation[0]) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2, + dilate=replace_stride_with_dilation[1]) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2, + dilate=replace_stride_with_dilation[2]) + self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) + self.out_dim = 512 * block.expansion + # self.fc = nn.Linear(512 * block.expansion, num_classes) # Removed in _forward_impl + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): + nn.init.constant_(m.weight, 1) + nn.init.constant_(m.bias, 0) + + # Zero-initialize the last BN in each residual branch, + # so that the residual branch starts with zeros, and each residual block behaves like an identity. + # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 + if zero_init_residual: + for m in self.modules(): + if isinstance(m, Bottleneck): + nn.init.constant_(m.bn3.weight, 0) + elif isinstance(m, BasicBlock): + nn.init.constant_(m.bn2.weight, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dilate=False): + norm_layer = self._norm_layer + downsample = None + previous_dilation = self.dilation + if dilate: + self.dilation *= stride + stride = 1 + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + conv1x1(self.inplanes, planes * block.expansion, stride), + norm_layer(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample, self.groups, + self.base_width, previous_dilation, norm_layer)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes, groups=self.groups, + base_width=self.base_width, dilation=self.dilation, + norm_layer=norm_layer)) + + return nn.Sequential(*layers) + + def _forward_impl(self, x): + # See note [TorchScript super()] + x = self.conv1(x) # [bs, 64, 32, 32] + + x_1 = self.layer1(x) # [bs, 128, 32, 32] + x_2 = self.layer2(x_1) # [bs, 256, 16, 16] + x_3 = self.layer3(x_2) # [bs, 512, 8, 8] + x_4 = self.layer4(x_3) # [bs, 512, 4, 4] + + pooled = self.avgpool(x_4) # [bs, 512, 1, 1] + features = torch.flatten(pooled, 1) # [bs, 512] + # x = self.fc(x) + + return { + 'fmaps': [x_1, x_2, x_3, x_4], + 'features': features + } + + def forward(self, x): + return self._forward_impl(x) + + @property + def last_conv(self): + if hasattr(self.layer4[-1], 'conv3'): + return self.layer4[-1].conv3 + else: + return self.layer4[-1].conv2 + + +def _resnet(arch, block, layers, pretrained, progress, **kwargs): + model = ResNet(block, layers, **kwargs) + if pretrained: + state_dict = load_state_dict_from_url(model_urls[arch], + progress=progress) + model.load_state_dict(state_dict) + return model + +def resnet10(pretrained=False, progress=True, **kwargs): + """ + For MEMO implementations of ResNet-10 + """ + return _resnet('resnet10', BasicBlock, [1, 1, 1, 1], pretrained, progress, + **kwargs) + +def resnet26(pretrained=False, progress=True, **kwargs): + """ + For MEMO implementations of ResNet-26 + """ + return _resnet('resnet26', Bottleneck, [2, 2, 2, 2], pretrained, progress, + **kwargs) + +def resnet18(pretrained=False, progress=True, **kwargs): + r"""ResNet-18 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress, + **kwargs) + + +def resnet34(pretrained=False, progress=True, **kwargs): + r"""ResNet-34 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress, + **kwargs) + + +def resnet50(pretrained=False, progress=True, **kwargs): + r"""ResNet-50 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, + **kwargs) + + +def resnet101(pretrained=False, progress=True, **kwargs): + r"""ResNet-101 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress, + **kwargs) + + +def resnet152(pretrained=False, progress=True, **kwargs): + r"""ResNet-152 model from + `"Deep Residual Learning for Image Recognition" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress, + **kwargs) + + +def resnext50_32x4d(pretrained=False, progress=True, **kwargs): + r"""ResNeXt-50 32x4d model from + `"Aggregated Residual Transformation for Deep Neural Networks" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 4 + return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3], + pretrained, progress, **kwargs) + + +def resnext101_32x8d(pretrained=False, progress=True, **kwargs): + r"""ResNeXt-101 32x8d model from + `"Aggregated Residual Transformation for Deep Neural Networks" `_ + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['groups'] = 32 + kwargs['width_per_group'] = 8 + return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3], + pretrained, progress, **kwargs) + + +def wide_resnet50_2(pretrained=False, progress=True, **kwargs): + r"""Wide ResNet-50-2 model from + `"Wide Residual Networks" `_ + The model is the same as ResNet except for the bottleneck number of channels + which is twice larger in every block. The number of channels in outer 1x1 + convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 + channels, and in Wide ResNet-50-2 has 2048-1024-2048. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['width_per_group'] = 64 * 2 + return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3], + pretrained, progress, **kwargs) + + +def wide_resnet101_2(pretrained=False, progress=True, **kwargs): + r"""Wide ResNet-101-2 model from + `"Wide Residual Networks" `_ + The model is the same as ResNet except for the bottleneck number of channels + which is twice larger in every block. The number of channels in outer 1x1 + convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048 + channels, and in Wide ResNet-50-2 has 2048-1024-2048. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + progress (bool): If True, displays a progress bar of the download to stderr + """ + kwargs['width_per_group'] = 64 * 2 + return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3], + pretrained, progress, **kwargs) \ No newline at end of file diff --git a/StreamLearn/legacy/README.md b/StreamLearn/legacy/README.md index 3577b978bbf605df33676aa47eb1a3bbb5796508..2f967b549198890f966ca4d3a10d875987822398 100644 --- a/StreamLearn/legacy/README.md +++ b/StreamLearn/legacy/README.md @@ -1419,3 +1419,57 @@ predictions = alg.stream_predict(X=dataset._test_data) accuracy = alg.stream_evaluate(dataset, predictions) alg.stream_fit(dataset) ``` + +### 5.2 增量学习算法LwF + +Learning without Forgetting(LwF)是一种旨在解决神经网络“灾难性遗忘”问题的经典增量学习算法。其核心思想非常巧妙,即通过​​知识蒸馏​​技术让模型在学习新任务时“复习”旧任务,而无需访问任何原始旧数据。具体而言,LwF在让模型利用新数据学习新类别的同时,会要求其最终输出在旧类别上的概率分布与训练好的旧模型对该相同数据的输出分布尽可能保持一致;这种约束相当于让旧模型充当了旧知识的“教师”,引导新模型在吸收新知识的过程中不偏离已掌握的旧知识,从而有效地将旧知识蒸馏到新模型中。这种方法显著降低了数据存储和计算开销,使得模型能够高效、持续地适应新任务,是实现终身学习的一个重要里程碑。 + +该算法主要包含`增量学习数据集构造`,`LwF算法实现`,`性能测试`三部分,相关代码参见目录: +- StreamLearn/Dataset/CILDataset.py +- StreamLearn/Algorithm/ClassIncrementalLearning/LwF.py +- StreamLearn/tests/test_stream_cil.py + +### 5.3 增量学习算法EWC + +弹性权重巩固算法(EWC)是一种重要的增量学习方法,其核心目标是克服神经网络在连续学习多个任务时出现的灾难性遗忘问题。该方法基于一个神经科学启发原理,即人脑中的突触在学习新知识时,会对巩固重要旧知识的突触变化进行限制。EWC 通过计算网络参数对于已学任务的重要性(通常使用Fisher信息矩阵来度量),并为每个参数施加相应的约束,在新的任务学习过程中,对重要的旧参数进行“巩固”,限制其大幅度改变,从而在吸纳新知识的同时,最大程度地保留以往任务中的学习成果。这使得单一模型能够在不重新训练旧数据的情况下,持续高效地学习一系列任务。 + +该算法主要包含`增量学习数据集构造`,`EWC算法实现`,`性能测试`三部分,相关代码参见目录: +- StreamLearn/Dataset/CILDataset.py +- StreamLearn/Algorithm/ClassIncrementalLearning/EWC.py +- StreamLearn/tests/test_stream_cil.py + +### 5.4 增量学习算法Foster + +Foster是一种旨在解决灾难性遗忘问题的深度增量学习算法,其核心思想是通过动态扩展网络结构并巧妙地利用旧知识来指导新任务的学习。该算法采用双分支网络结构,一个负责稳定地保留已有任务的表征能力,另一个则动态地增加神经元以捕捉新任务中的独特模式。通过学习过程中最小化新旧模型在特征空间上的分布差异,并配合一种智能的知识蒸馏机制,Foster能够有效地将旧任务的重要信息提炼并巩固到扩展后的网络中,从而在不过度依赖旧任务数据的前提下,实现对新旧任务的持续高性能学习。这种方法在计算效率和模型性能之间取得了较好的平衡,使其成为一类重要的增量学习基准算法。 + +该算法主要包含`增量学习数据集构造`,`Foster算法实现`,`性能测试`三部分,相关代码参见目录: +- StreamLearn/Dataset/CILDataset.py +- StreamLearn/Algorithm/ClassIncrementalLearning/Foster.py +- StreamLearn/tests/test_stream_cil.py + +### 5.5 增量学习算法iCaRL + +iCaRL(incremental Classifier and Representation Learning)是一种经典的增量学习算法,其核心目标是使模型能够在不遗忘已学旧类别知识的前提下,持续学习新类别的样本。它通过结合知识蒸馏技术、一个动态更新的范例集(exemplar set)以及基于最近类别均值(nearest-mean-of-exemplars)的分类规则来实现这一目标。在每次学习新类别时,iCaRL会为每个旧类别保留少量具有代表性的样本(即范例),并在模型更新过程中,利用知识蒸馏损失函数来约束新模型对旧类别数据的输出与旧模型保持一致,从而有效缓解灾难性遗忘。同时,算法会计算并保存每个类别所有范例的特征均值,在推理阶段通过比较测试样本与这些类别均值的距离来进行分类。iCaRL的重要意义在于它首次在一个统一的框架中证明,基于深度神经网络的分类器可以进行严格的类别增量学习,并且性能显著优于之前的方法。 + +该算法主要包含`增量学习数据集构造`,`iCaRL算法实现`,`性能测试`三部分,相关代码参见目录: +- StreamLearn/Dataset/CILDataset.py +- StreamLearn/Algorithm/ClassIncrementalLearning/iCaRL.py +- StreamLearn/tests/test_stream_cil.py + +### 5.6 增量学习算法WA + +WA(Winnow Algorithm)是一种经典的在线增量学习算法,主要用于二分类问题。该算法基于错误驱动的更新机制,其核心思想是通过乘法方式调整特征权重,从而高效处理高维稀疏数据。在每轮训练中,WA对样本进行预测,当预测错误时,仅对与错误相关的特征权重进行更新:对于误判的正例,增大相关特征的权重;对于误判的负例,则减小相应权重。这种调整策略使得WA能够快速聚焦于关键特征,对噪声数据具有一定的鲁棒性,同时天然适应特征规模动态变化的场景。由于其简单高效和内存占用小的特点,WA在文本分类、广告点击率预测等需要实时更新的任务中具有广泛的应用价值。 + +该算法主要包含`增量学习数据集构造`,`WA算法实现`,`性能测试`三部分,相关代码参见目录: +- StreamLearn/Dataset/CILDataset.py +- StreamLearn/Algorithm/ClassIncrementalLearning/WA.py +- StreamLearn/tests/test_stream_cil.py + +### 5.7 增量学习算法SimpleCIL + +SimpleCIL是一种简洁而有效的类增量学习算法,其核心思想是直接沿用并冻结在初始任务上预训练好的特征提取器,无需复杂的动态架构或排练缓冲机制。在后续的增量学习阶段,它仅通过最小化交叉熵损失来学习训练新的、未见过的类别的分类器权重,而保持特征提取网络固定不变。这种方法巧妙地利用了预训练模型本身所具有的强泛化能力,认为一个在大规模数据集上预训练好的特征空间本身就具备良好的可分离性,足以容纳新的类别,从而有效缓解了神经网络在学习新知识时对旧知识产生的灾难性遗忘问题。由于其实现简单且性能出色,SimpleCIL成为了类增量学习研究中的一个重要基线方法。 + +该算法主要包含`增量学习数据集构造`,`SimpleCIL算法实现`,`性能测试`三部分,相关代码参见目录: +- StreamLearn/Dataset/CILDataset.py +- StreamLearn/Algorithm/ClassIncrementalLearning/SimpleCIL.py +- StreamLearn/tests/test_stream_cil.py \ No newline at end of file diff --git a/StreamLearn/utils.py b/StreamLearn/utils.py index 97f4ed1cd73bdd2cf92973a84af362d69ba8a8a1..e5f897f98277e50a032af93c483edf3a5ffa43f9 100644 --- a/StreamLearn/utils.py +++ b/StreamLearn/utils.py @@ -245,19 +245,19 @@ def count_parameters(model, trainable=False): return sum(p.numel() for p in model.parameters()) -def get_CIL_method(method_name): +def get_CIL_method(method_name, dataset_name): if method_name == "fine_tune": from StreamLearn.Algorithm.ClassIncrementalLearning.FineTune import FineTune return FineTune() elif method_name == "icarl": from StreamLearn.Algorithm.ClassIncrementalLearning.iCaRL import iCaRL - return iCaRL() + return iCaRL(dataset_name=dataset_name) elif method_name == "ewc": from StreamLearn.Algorithm.ClassIncrementalLearning.EWC import EWC - return EWC() + return EWC(dataset_name=dataset_name) elif method_name == "lwf": from StreamLearn.Algorithm.ClassIncrementalLearning.LwF import LwF - return LwF() + return LwF(dataset_name=dataset_name) elif method_name == "replay": from StreamLearn.Algorithm.ClassIncrementalLearning.Replay import Replay return Replay() @@ -266,21 +266,21 @@ def get_CIL_method(method_name): return DER() elif method_name == "wa": from StreamLearn.Algorithm.ClassIncrementalLearning.WA import WA - return WA() + return WA(dataset_name=dataset_name) elif method_name == "simplecil": from StreamLearn.Algorithm.ClassIncrementalLearning.SimpleCIL import SimpleCIL - return SimpleCIL() + return SimpleCIL(dataset_name=dataset_name) elif method_name == "foster": from StreamLearn.Algorithm.ClassIncrementalLearning.Foster import Foster - return Foster() + return Foster(dataset_name=dataset_name) elif method_name == "memo": from StreamLearn.Algorithm.ClassIncrementalLearning.MEMO import MEMO - return MEMO() + return MEMO(dataset_name=dataset_name) elif method_name == "beef": from StreamLearn.Algorithm.ClassIncrementalLearning.BEEF import BEEFISO return BEEFISO() else: - raise NotImplementedError + raise NotImplementedError() def KD_loss(pred, soft, T):