├── backbone ├── utils │ ├── __init__.py │ └── modules.py ├── __pycache__ │ ├── ResNet18.cpython-37.pyc │ ├── MNISTMLP.cpython-36.pyc │ ├── MNISTMLP.cpython-37.pyc │ ├── MNISTMLP.cpython-38.pyc │ ├── MNISTMLP.cpython-39.pyc │ ├── ResNet18.cpython-36.pyc │ ├── ResNet18.cpython-38.pyc │ ├── ResNet18.cpython-39.pyc │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── __init__.cpython-38.pyc │ ├── __init__.cpython-39.pyc │ ├── pc_cnn.cpython-36.pyc │ ├── pc_cnn.cpython-38.pyc │ ├── pc_cnn.cpython-39.pyc │ ├── MNISTMLP_MAML.cpython-36.pyc │ ├── MNISTMLP_MAML.cpython-37.pyc │ ├── MNISTMLP_MAML.cpython-38.pyc │ ├── MNISTMLP_MAML.cpython-39.pyc │ ├── ResNet18_MAML.cpython-36.pyc │ ├── ResNet18_MAML.cpython-37.pyc │ ├── ResNet18_MAML.cpython-38.pyc │ ├── ResNet18_OBC.cpython-36.pyc │ ├── ResNet18_OBC.cpython-38.pyc │ ├── ResNet18_OBC.cpython-39.pyc │ ├── ResNet18_MAML1.cpython-36.pyc │ ├── ResNet18_MAML1.cpython-38.pyc │ ├── ResNet18_MAML1.cpython-39.pyc │ ├── Pretrained_ResNet18.cpython-36.pyc │ ├── Pretrained_ResNet18.cpython-38.pyc │ └── Pretrained_ResNet18.cpython-39.pyc ├── Pretrained_ResNet18.py ├── MNISTMLP.py ├── MNISTMLP_MAML.py ├── MNISTMLP_PNN.py └── __init__.py ├── datasets ├── utils │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── gcl_dataset.cpython-36.pyc │ │ ├── gcl_dataset.cpython-37.pyc │ │ ├── gcl_dataset.cpython-38.pyc │ │ ├── gcl_dataset.cpython-39.pyc │ │ ├── validation.cpython-36.pyc │ │ ├── validation.cpython-37.pyc │ │ ├── validation.cpython-38.pyc │ │ ├── validation.cpython-39.pyc │ │ ├── continual_dataset.cpython-36.pyc │ │ ├── continual_dataset.cpython-37.pyc │ │ ├── continual_dataset.cpython-38.pyc │ │ └── continual_dataset.cpython-39.pyc │ ├── gcl_dataset.py │ ├── validation.py │ └── continual_dataset.py ├── transforms │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── rotation.cpython-36.pyc │ │ ├── rotation.cpython-37.pyc │ │ ├── rotation.cpython-38.pyc │ │ ├── rotation.cpython-39.pyc │ │ ├── permutation.cpython-36.pyc │ │ ├── permutation.cpython-37.pyc │ │ ├── permutation.cpython-38.pyc │ │ ├── permutation.cpython-39.pyc │ │ ├── denormalization.cpython-36.pyc │ │ ├── denormalization.cpython-37.pyc │ │ ├── denormalization.cpython-38.pyc │ │ └── denormalization.cpython-39.pyc │ ├── denormalization.py │ ├── permutation.py │ └── rotation.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── __init__.cpython-38.pyc │ ├── __init__.cpython-39.pyc │ ├── mnist_360.cpython-36.pyc │ ├── mnist_360.cpython-37.pyc │ ├── mnist_360.cpython-38.pyc │ ├── mnist_360.cpython-39.pyc │ ├── perm_mnist.cpython-36.pyc │ ├── perm_mnist.cpython-37.pyc │ ├── perm_mnist.cpython-38.pyc │ ├── perm_mnist.cpython-39.pyc │ ├── rot_mnist.cpython-36.pyc │ ├── rot_mnist.cpython-37.pyc │ ├── rot_mnist.cpython-38.pyc │ ├── rot_mnist.cpython-39.pyc │ ├── seq_mnist.cpython-36.pyc │ ├── seq_mnist.cpython-37.pyc │ ├── seq_mnist.cpython-38.pyc │ ├── seq_mnist.cpython-39.pyc │ ├── seq_cifar10.cpython-36.pyc │ ├── seq_cifar10.cpython-37.pyc │ ├── seq_cifar10.cpython-38.pyc │ ├── seq_cifar10.cpython-39.pyc │ ├── seq_cifar100.cpython-36.pyc │ ├── seq_cifar100.cpython-37.pyc │ ├── seq_cifar100.cpython-38.pyc │ ├── seq_cifar100.cpython-39.pyc │ ├── perm_mnist_maml.cpython-36.pyc │ ├── perm_mnist_maml.cpython-37.pyc │ ├── perm_mnist_maml.cpython-38.pyc │ ├── perm_mnist_maml.cpython-39.pyc │ ├── rot_mnist_maml.cpython-36.pyc │ ├── rot_mnist_maml.cpython-38.pyc │ ├── rot_mnist_maml.cpython-39.pyc │ ├── seq_tinyimagenet.cpython-36.pyc │ ├── seq_tinyimagenet.cpython-37.pyc │ ├── seq_tinyimagenet.cpython-38.pyc │ ├── seq_tinyimagenet.cpython-39.pyc │ ├── imbalanaced_cifar100.cpython-36.pyc │ ├── imbalanaced_cifar100.cpython-38.pyc │ └── imbalanaced_cifar100.cpython-39.pyc ├── rot_mnist.py ├── rot_mnist_maml.py ├── __init__.py ├── seq_mnist.py ├── perm_mnist_maml.py ├── seq_cifar10_pccnn ├── seq_cifar10.py ├── perm_mnist.py └── seq_cifar100.py ├── models ├── utils │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── continual_model.cpython-36.pyc │ │ ├── continual_model.cpython-38.pyc │ │ └── continual_model.cpython-39.pyc │ └── continual_model.py ├── __init__.py ├── sgd.py ├── er.py ├── der.py ├── cbrs.py ├── joint_gcl.py ├── si.py ├── agem_r.py ├── er_ace.py ├── ER_mask ├── gss.py ├── pnn.py ├── agem.py ├── ewc_on.py ├── fdr.py ├── er_obc.py ├── gdumb.py ├── lwf.py ├── derpp.py ├── joint.py ├── mer.py ├── rpc.py └── clser.py ├── utils ├── __init__.py ├── conf.py ├── batch_norm.py ├── metrics.py ├── args.py ├── continual_training.py ├── main.py ├── status.py ├── tb_logger.py ├── simclrloss.py ├── augmentations.py └── loggers.py └── README.md /backbone/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /datasets/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /datasets/transforms/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18.cpython-37.pyc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP.cpython-37.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/pc_cnn.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/pc_cnn.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/pc_cnn.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/pc_cnn.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/pc_cnn.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/pc_cnn.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/mnist_360.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/mnist_360.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/mnist_360.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/mnist_360.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/mnist_360.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/mnist_360.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/mnist_360.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/mnist_360.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_mnist.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_mnist.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_mnist.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_mnist.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_mnist.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_mnist.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_mnist.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_mnist.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP_MAML.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP_MAML.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP_MAML.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP_MAML.cpython-37.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP_MAML.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP_MAML.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/MNISTMLP_MAML.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/MNISTMLP_MAML.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_MAML.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_MAML.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_MAML.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_MAML.cpython-37.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_MAML.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_MAML.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_OBC.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_OBC.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_OBC.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_OBC.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_OBC.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_OBC.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar10.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar10.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar10.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar10.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar10.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar10.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar10.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar10.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar100.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar100.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar100.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar100.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar100.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar100.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_cifar100.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_cifar100.cpython-39.pyc -------------------------------------------------------------------------------- /models/utils/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/models/utils/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /models/utils/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/models/utils/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /models/utils/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/models/utils/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_MAML1.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_MAML1.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_MAML1.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_MAML1.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/ResNet18_MAML1.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/ResNet18_MAML1.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist_maml.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist_maml.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist_maml.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist_maml.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist_maml.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist_maml.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/perm_mnist_maml.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/perm_mnist_maml.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist_maml.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist_maml.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist_maml.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist_maml.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/rot_mnist_maml.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/rot_mnist_maml.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_tinyimagenet.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_tinyimagenet.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_tinyimagenet.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_tinyimagenet.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_tinyimagenet.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_tinyimagenet.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/seq_tinyimagenet.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/seq_tinyimagenet.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/gcl_dataset.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/gcl_dataset.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/gcl_dataset.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/gcl_dataset.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/gcl_dataset.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/gcl_dataset.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/gcl_dataset.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/gcl_dataset.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/validation.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/validation.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/validation.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/validation.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/validation.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/validation.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/validation.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/validation.cpython-39.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/Pretrained_ResNet18.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/Pretrained_ResNet18.cpython-36.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/Pretrained_ResNet18.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/Pretrained_ResNet18.cpython-38.pyc -------------------------------------------------------------------------------- /backbone/__pycache__/Pretrained_ResNet18.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/backbone/__pycache__/Pretrained_ResNet18.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/imbalanaced_cifar100.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/imbalanaced_cifar100.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/imbalanaced_cifar100.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/imbalanaced_cifar100.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/imbalanaced_cifar100.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/__pycache__/imbalanaced_cifar100.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/rotation.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/rotation.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/rotation.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/rotation.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/rotation.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/rotation.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/rotation.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/rotation.cpython-39.pyc -------------------------------------------------------------------------------- /models/utils/__pycache__/continual_model.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/models/utils/__pycache__/continual_model.cpython-36.pyc -------------------------------------------------------------------------------- /models/utils/__pycache__/continual_model.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/models/utils/__pycache__/continual_model.cpython-38.pyc -------------------------------------------------------------------------------- /models/utils/__pycache__/continual_model.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/models/utils/__pycache__/continual_model.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/permutation.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/permutation.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/permutation.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/permutation.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/permutation.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/permutation.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/permutation.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/permutation.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/continual_dataset.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/continual_dataset.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/continual_dataset.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/continual_dataset.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/continual_dataset.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/continual_dataset.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/utils/__pycache__/continual_dataset.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/utils/__pycache__/continual_dataset.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/denormalization.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/denormalization.cpython-36.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/denormalization.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/denormalization.cpython-37.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/denormalization.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/denormalization.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/transforms/__pycache__/denormalization.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024/HEAD/datasets/transforms/__pycache__/denormalization.cpython-39.pyc -------------------------------------------------------------------------------- /datasets/utils/gcl_dataset.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | class GCLDataset: 7 | """ 8 | Continual learning evaluation setting. 9 | """ 10 | NAME = None 11 | SETTING = None 12 | N_CLASSES = None 13 | LENGTH = None 14 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import os 7 | 8 | 9 | def create_if_not_exists(path: str) -> None: 10 | """ 11 | Creates the specified folder if it does not exist. 12 | :param path: the complete path of the folder to be created 13 | """ 14 | if not os.path.exists(path): 15 | os.makedirs(path) 16 | -------------------------------------------------------------------------------- /datasets/transforms/denormalization.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | 7 | class DeNormalize(object): 8 | def __init__(self, mean, std): 9 | self.mean = mean 10 | self.std = std 11 | 12 | def __call__(self, tensor): 13 | """ 14 | Args: 15 | tensor (Tensor): Tensor image of size (C, H, W) to be normalized. 16 | Returns: 17 | Tensor: Normalized image. 18 | """ 19 | for t, m, s in zip(tensor, self.mean, self.std): 20 | t.mul_(s).add_(m) 21 | return tensor 22 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import os 7 | import importlib 8 | 9 | def get_all_models(): 10 | return [model.split('.')[0] for model in os.listdir('../models') 11 | if not model.find('__') > -1 and 'py' in model] 12 | 13 | names = {} 14 | for model in get_all_models(): 15 | mod = importlib.import_module('models.' + model) 16 | class_name = {x.lower():x for x in mod.__dir__()}[model.replace('_', '')] 17 | names[model] = getattr(mod, class_name) 18 | 19 | def get_model(args, backbone, loss, transform): 20 | return names[args.model](backbone, loss, args, transform) 21 | -------------------------------------------------------------------------------- /utils/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import random 7 | import torch 8 | import numpy as np 9 | 10 | def get_device() -> torch.device: 11 | """ 12 | Returns the GPU device if available else CPU. 13 | """ 14 | return torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 15 | 16 | 17 | def base_path() -> str: 18 | """ 19 | Returns the base bath where to log accuracies and tensorboard data. 20 | """ 21 | return './data/' 22 | 23 | 24 | def set_random_seed(seed: int) -> None: 25 | """ 26 | Sets the seeds at a certain value. 27 | :param seed: the value to be set 28 | """ 29 | random.seed(seed) 30 | np.random.seed(seed) 31 | torch.manual_seed(seed) 32 | torch.cuda.manual_seed_all(seed) 33 | -------------------------------------------------------------------------------- /utils/batch_norm.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | 9 | class bn_track_stats: 10 | def __init__(self, module: nn.Module, condition=True): 11 | self.module = module 12 | self.enable = condition 13 | 14 | def __enter__(self): 15 | if not self.enable: 16 | for m in self.module.modules(): 17 | if isinstance(m, (torch.nn.BatchNorm2d, torch.nn.BatchNorm1d)): 18 | m.track_running_stats = False 19 | 20 | def __exit__(self ,type, value, traceback): 21 | if not self.enable: 22 | for m in self.module.modules(): 23 | if isinstance(m, (torch.nn.BatchNorm2d, torch.nn.BatchNorm1d)): 24 | m.track_running_stats = True -------------------------------------------------------------------------------- /utils/metrics.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import numpy as np 7 | 8 | 9 | def backward_transfer(results): 10 | n_tasks = len(results) 11 | li = list() 12 | for i in range(n_tasks - 1): 13 | li.append(results[-1][i] - results[i][i]) 14 | 15 | return np.mean(li) 16 | 17 | 18 | def forward_transfer(results, random_results): 19 | n_tasks = len(results) 20 | li = list() 21 | for i in range(1, n_tasks): 22 | li.append(results[i-1][i] - random_results[i]) 23 | 24 | return np.mean(li) 25 | 26 | 27 | def forgetting(results): 28 | n_tasks = len(results) 29 | li = list() 30 | for i in range(n_tasks - 1): 31 | results[i] += [0.0] * (n_tasks - len(results[i])) 32 | np_res = np.array(results) 33 | maxx = np.max(np_res, axis=0) 34 | for i in range(n_tasks - 1): 35 | li.append(maxx[i] - results[-1][i]) 36 | 37 | return np.mean(li) 38 | -------------------------------------------------------------------------------- /models/sgd.py: -------------------------------------------------------------------------------- 1 | from utils.args import * 2 | from models.utils.continual_model import ContinualModel 3 | import numpy as np 4 | import torch 5 | 6 | def get_parser() -> ArgumentParser: 7 | parser = ArgumentParser(description='Continual Learning via' 8 | ' Progressive Neural Networks.') 9 | add_management_args(parser) 10 | add_experiment_args(parser) 11 | return parser 12 | 13 | 14 | class Sgd(ContinualModel): 15 | NAME = 'sgd' 16 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 17 | 18 | def __init__(self, backbone, loss, args, transform): 19 | super(Sgd, self).__init__(backbone, loss, args, transform) 20 | def begin_task(self, dataset): 21 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 22 | 23 | 24 | def observe(self, inputs, labels, not_aug_inputs,t): 25 | real_batch_size = inputs.shape[0] 26 | perm = torch.randperm(real_batch_size) 27 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 28 | not_aug_inputs = not_aug_inputs[perm] 29 | 30 | 31 | self.opt.zero_grad() 32 | outputs = self.net(inputs) 33 | loss = self.loss(outputs, labels) 34 | loss.backward() 35 | self.opt.step() 36 | 37 | return loss.item() 38 | -------------------------------------------------------------------------------- /backbone/Pretrained_ResNet18.py: -------------------------------------------------------------------------------- 1 | import torchvision.models as models 2 | import torch 3 | 4 | class StandardResNet(torch.nn.Module): 5 | def __init__(self, n_classes, pretrained=False, rn=18, dim_in=512, ckpt_path=None): 6 | super(StandardResNet, self).__init__() 7 | if rn == 18: 8 | classifier = models.resnet18(pretrained=False) 9 | classifier.load_state_dict(torch.load('resnet18-pretrained.pth')) 10 | elif rn == 50: 11 | classifier = models.resnet50(pretrained=pretrained) 12 | 13 | if ckpt_path is not None: 14 | ckpt = torch.load(ckpt_path)['state_dict'] 15 | classifier.load_state_dict(ckpt) 16 | 17 | self.encoder = torch.nn.Sequential(*list(classifier.children())[:-1]) 18 | dim_in = classifier.fc.in_features 19 | self.linear = torch.nn.Linear(dim_in, n_classes) 20 | 21 | def features(self, x): 22 | '''Features before FC layers''' 23 | out = self.encoder(x) 24 | out = out.view(out.size(0), -1) 25 | return out 26 | 27 | def logits(self, x): 28 | '''Apply the last FC linear mapping to get logits''' 29 | x = self.linear(x) 30 | return x 31 | 32 | def forward(self, x): 33 | out = self.features(x) 34 | logits = self.logits(out) 35 | return logits 36 | 37 | def ResNet18(n_classes, pretrained=True, rn=18, dim_in=512, ckpt_path=None): 38 | classifier = StandardResNet(n_classes, pretrained=pretrained, rn=rn, dim_in=dim_in, ckpt_path=ckpt_path) 39 | return classifier -------------------------------------------------------------------------------- /datasets/rot_mnist.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torchvision.transforms as transforms 7 | from datasets.transforms.rotation import Rotation 8 | from torch.utils.data import DataLoader 9 | from backbone.MNISTMLP import MNISTMLP 10 | from backbone.ResNet18 import resnet18 11 | import torch.nn.functional as F 12 | from datasets.perm_mnist import store_mnist_loaders 13 | from datasets.utils.continual_dataset import ContinualDataset 14 | 15 | 16 | 17 | class RotatedMNIST(ContinualDataset): 18 | NAME = 'rot-mnist' 19 | SETTING = 'domain-il' 20 | N_CLASSES_PER_TASK = 10 21 | N_TASKS = 10 22 | 23 | def get_data_loaders(self): 24 | # transform = transforms.Compose((Rotation(), transforms.ToTensor())) 25 | train, test = store_mnist_loaders(self) 26 | return train, test 27 | 28 | @staticmethod 29 | def get_backbone(): 30 | return MNISTMLP(28 * 28, RotatedMNIST.N_CLASSES_PER_TASK) 31 | 32 | @staticmethod 33 | def get_transform(): 34 | return None 35 | 36 | @staticmethod 37 | def get_normalization_transform(): 38 | return None 39 | 40 | @staticmethod 41 | def get_loss(): 42 | return F.cross_entropy 43 | 44 | @staticmethod 45 | def get_denormalization_transform(): 46 | return None 47 | 48 | @staticmethod 49 | def get_scheduler(model, args): 50 | return None -------------------------------------------------------------------------------- /datasets/rot_mnist_maml.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torchvision.transforms as transforms 7 | from datasets.transforms.rotation import Rotation 8 | from torch.utils.data import DataLoader 9 | from backbone.MNISTMLP_MAML import MNISTMLP_MAML 10 | import torch.nn.functional as F 11 | from datasets.perm_mnist import store_mnist_loaders 12 | from datasets.utils.continual_dataset import ContinualDataset 13 | 14 | 15 | class RotatedMNIST(ContinualDataset): 16 | NAME = 'rot-mnist-maml' 17 | SETTING = 'domain-il' 18 | N_CLASSES_PER_TASK = 10 19 | N_TASKS = 20 20 | 21 | def get_data_loaders(self): 22 | transform = transforms.Compose((Rotation(), transforms.ToTensor())) 23 | train, test = store_mnist_loaders(transform, self) 24 | return train, test 25 | 26 | @staticmethod 27 | def get_backbone(): 28 | return MNISTMLP_MAML(28 * 28, RotatedMNIST.N_CLASSES_PER_TASK) 29 | 30 | @staticmethod 31 | def get_transform(): 32 | return None 33 | 34 | @staticmethod 35 | def get_normalization_transform(): 36 | return None 37 | 38 | @staticmethod 39 | def get_loss(): 40 | return F.cross_entropy 41 | 42 | @staticmethod 43 | def get_denormalization_transform(): 44 | return None 45 | 46 | @staticmethod 47 | def get_scheduler(model, args): 48 | return None -------------------------------------------------------------------------------- /datasets/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import os 7 | import inspect 8 | import importlib 9 | from datasets.utils.gcl_dataset import GCLDataset 10 | from datasets.utils.continual_dataset import ContinualDataset 11 | from argparse import Namespace 12 | 13 | def get_all_models(): 14 | return [model.split('.')[0] for model in os.listdir('../datasets') 15 | if not model.find('__') > -1 and 'py' in model] #(not model.find() ) and 'py' in model 16 | 17 | NAMES = {} 18 | for model in get_all_models(): 19 | mod = importlib.import_module('datasets.' + model) 20 | dataset_classes_name = [x for x in mod.__dir__() if 'type' in str(type(getattr(mod, x))) and 'ContinualDataset' in str(inspect.getmro(getattr(mod, x))[1:])] 21 | for d in dataset_classes_name: 22 | c = getattr(mod, d) 23 | NAMES[c.NAME] = c 24 | 25 | gcl_dataset_classes_name = [x for x in mod.__dir__() if 'type' in str(type(getattr(mod, x))) and 'GCLDataset' in str(inspect.getmro(getattr(mod, x))[1:])] 26 | for d in gcl_dataset_classes_name: 27 | c = getattr(mod, d) 28 | NAMES[c.NAME] = c 29 | 30 | def get_dataset(args: Namespace) -> ContinualDataset: 31 | """ 32 | Creates and returns a continual dataset. 33 | :param args: the arguments which contains the hyperparameters 34 | :return: the continual dataset 35 | """ 36 | assert args.dataset in NAMES.keys() 37 | return NAMES[args.dataset](args) 38 | -------------------------------------------------------------------------------- /backbone/utils/modules.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | from torch.nn.parameter import Parameter 9 | 10 | 11 | class AlphaModule(nn.Module): 12 | def __init__(self, shape): 13 | super(AlphaModule, self).__init__() 14 | if not isinstance(shape, tuple): 15 | shape = (shape,) 16 | self.alpha = Parameter(torch.rand(tuple([1] + list(shape))) * 0.1, 17 | requires_grad=True) 18 | 19 | def forward(self, x): 20 | return x * self.alpha 21 | 22 | def parameters(self, recurse: bool = True): 23 | yield self.alpha 24 | 25 | 26 | class ListModule(nn.Module): 27 | def __init__(self, *args): 28 | super(ListModule, self).__init__() 29 | self.idx = 0 30 | for module in args: 31 | self.add_module(str(self.idx), module) 32 | self.idx += 1 33 | 34 | def append(self, module): 35 | self.add_module(str(self.idx), module) 36 | self.idx += 1 37 | 38 | def __getitem__(self, idx): 39 | if idx < 0: 40 | idx += self.idx 41 | if idx >= len(self._modules): 42 | raise IndexError('index {} is out of range'.format(idx)) 43 | it = iter(self._modules.values()) 44 | for i in range(idx): 45 | next(it) 46 | return next(it) 47 | 48 | def __iter__(self): 49 | return iter(self._modules.values()) 50 | 51 | def __len__(self): 52 | return len(self._modules) 53 | -------------------------------------------------------------------------------- /models/utils/continual_model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch.nn as nn 7 | from torch.optim import SGD 8 | import torch 9 | import torchvision 10 | from argparse import Namespace 11 | from utils.conf import get_device 12 | 13 | 14 | class ContinualModel(nn.Module): 15 | """ 16 | Continual learning model. 17 | """ 18 | NAME = None 19 | COMPATIBILITY = [] 20 | 21 | def __init__(self, backbone: nn.Module, loss: nn.Module, 22 | args: Namespace, transform: torchvision.transforms) -> None: 23 | super(ContinualModel, self).__init__() 24 | 25 | self.net = backbone 26 | # self.net = self.net #.main 27 | self.loss = loss 28 | self.args = args 29 | self.transform = transform 30 | self.opt = SGD(self.net.parameters(), lr=self.args.lr) 31 | self.device = get_device() 32 | 33 | def forward(self, x: torch.Tensor) -> torch.Tensor: 34 | """ 35 | Computes a forward pass. 36 | :param x: batch of inputs 37 | :param task_label: some models require the task label 38 | :return: the result of the computation 39 | """ 40 | return self.net(x) 41 | 42 | def observe(self, inputs: torch.Tensor, labels: torch.Tensor, 43 | not_aug_inputs: torch.Tensor) -> float: 44 | """ 45 | Compute a training step over a given batch of examples. 46 | :param inputs: batch of examples 47 | :param labels: ground-truth labels 48 | :param kwargs: some methods could require additional parameters 49 | :return: the value of the loss function 50 | """ 51 | pass 52 | -------------------------------------------------------------------------------- /datasets/transforms/permutation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import numpy as np 7 | 8 | 9 | class Permutation(object): 10 | """ 11 | Defines a fixed permutation for a numpy array. 12 | """ 13 | def __init__(self) -> None: 14 | """ 15 | Initializes the permutation. 16 | """ 17 | self.perm = None 18 | 19 | def __call__(self, sample: np.ndarray) -> np.ndarray: 20 | """ 21 | Randomly defines the permutation and applies the transformation. 22 | :param sample: image to be permuted 23 | :return: permuted image 24 | """ 25 | old_shape = sample.shape 26 | if self.perm is None: 27 | self.perm = np.random.permutation(len(sample.flatten())) 28 | 29 | return sample.flatten()[self.perm].reshape(old_shape) 30 | 31 | 32 | class FixedPermutation(object): 33 | """ 34 | Defines a fixed permutation (given the seed) for a numpy array. 35 | """ 36 | def __init__(self, seed: int) -> None: 37 | """ 38 | Defines the seed. 39 | :param seed: seed of the permutation 40 | """ 41 | self.perm = None 42 | self.seed = seed 43 | 44 | def __call__(self, sample: np.ndarray) -> np.ndarray: 45 | """ 46 | Defines the permutation and applies the transformation. 47 | :param sample: image to be permuted 48 | :return: permuted image 49 | """ 50 | old_shape = sample.shape 51 | if self.perm is None: 52 | np.random.seed(self.seed) 53 | self.perm = np.random.permutation(len(sample.flatten())) 54 | 55 | return sample.flatten()[self.perm].reshape(old_shape) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # [Meta Continual Learning Revisited: Implicitly Enhancing Online Hessian Approximation via Variance Reduction](https://github.com/WuYichen-97/Meta-Continual-Learning-Revisited-ICLR2024) 3 | ICLR'24 (Oral): Meta Continual Learning Revisited: Implicitly Enhancing Online Hessian Approximation via Variance Reduction (Official Pytorch implementation). 4 | 5 | 6 | 7 | If you find this code useful in your research then please cite 8 | ```bash 9 | @inproceedings{wu2023meta, 10 | title={Meta Continual Learning Revisited: Implicitly Enhancing Online Hessian Approximation via Variance Reduction}, 11 | author={Wu, Yichen and Huang, Long-Kai and Wang, Renzhen and Meng, Deyu and Wei, Ying}, 12 | booktitle={The Twelfth International Conference on Learning Representations}, 13 | year={2024} 14 | } 15 | ``` 16 | 17 | 18 | ## Setups 19 | The requiring environment is as bellow: 20 | 21 | - Linux 22 | - Python 3+ 23 | - PyTorch/ Torchvision 24 | 25 | 26 | ## Running our method on benchmark datasets (CIFAR-10/100 & TinyImageNet). 27 | Here is an example: 28 | ```bash 29 | cd utils 30 | python3 main.py --model vrmcl --dataset seq-cifar100 --n_epochs 1 --grad_clip_norm 1 --buffer_size 1000 --batch_size 32 --replay_batch_size 32 --lr 0.25 --alpha_init 0.1 --seed 0 --asyn_update --second_order --meta_update_per_batch 1 --inner_batch_size 8 --s_momentum 0.15 --s_lr 0.35 31 | python3 main.py --model vrmcl --dataset seq-tinyimg --n_epochs 1 --grad_clip_norm 1 --buffer_size 1000 --batch_size 32 --replay_batch_size 32 --lr 0.25 --alpha_init 0.1 --seed 0 --asyn_update --second_order --meta_update_per_batch 1 --inner_batch_size 8 --s_momentum 0.15 --s_lr 0.35 32 | python3 main.py --model vrmcl --dataset seq-cifar10 --n_epochs 1 --grad_clip_norm 1 --buffer_size 1000 --batch_size 32 --replay_batch_size 32 --lr 0.25 --alpha_init 0.1 --seed 0 --asyn_update --second_order --meta_update_per_batch 1 --inner_batch_size 8 --s_momentum 0.15 --s_lr 0.35 33 | ``` 34 | 35 | The default network structure is Reduced-ResNet18 36 | 37 | 38 | ## Acknowledgements 39 | We thank the Pytorch Continual Learning framework *Mammoth*(https://github.com/aimagelab/mammoth) 40 | 41 | -------------------------------------------------------------------------------- /models/er.py: -------------------------------------------------------------------------------- 1 | 2 | import torch 3 | from utils.buffer import Buffer 4 | from utils.args import * 5 | from models.utils.continual_model import ContinualModel 6 | import numpy as np 7 | np.random.seed(0) 8 | import os 9 | 10 | def get_parser() -> ArgumentParser: 11 | parser = ArgumentParser(description='Continual learning via' 12 | ' Experience Replay.') 13 | add_management_args(parser) 14 | add_experiment_args(parser) 15 | add_rehearsal_args(parser) 16 | return parser 17 | 18 | 19 | class Er(ContinualModel): 20 | NAME = 'er' 21 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 22 | 23 | def __init__(self, backbone, loss, args, transform): 24 | super(Er, self).__init__(backbone, loss, args, transform) 25 | self.buffer = Buffer(self.args.buffer_size, self.device) 26 | 27 | 28 | def begin_task(self, dataset): 29 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 30 | 31 | 32 | 33 | def end_task(self, dataset): 34 | dataset.task_id = dataset.i//dataset.N_CLASSES_PER_TASK 35 | 36 | 37 | 38 | def observe(self, inputs, labels, not_aug_inputs, task_id): 39 | real_batch_size = inputs.shape[0] 40 | perm = torch.randperm(real_batch_size) 41 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 42 | not_aug_inputs = not_aug_inputs[perm] 43 | 44 | self.opt.zero_grad() 45 | outputs = self.net(inputs) 46 | loss = self.loss(outputs, labels) 47 | if not self.buffer.is_empty(): 48 | buf_inputs, buf_labels, buf_id = self.buffer.get_data( 49 | self.args.minibatch_size, transform=self.transform) 50 | loss += self.loss(self.net(buf_inputs), buf_labels) 51 | 52 | 53 | else: 54 | buf_id = task_id*torch.ones_like(labels) 55 | 56 | 57 | loss.backward() 58 | self.opt.step() 59 | self.opt.zero_grad() 60 | 61 | self.buffer.add_data(examples=not_aug_inputs, 62 | labels=labels, 63 | task_labels=task_id*torch.ones_like(labels)) 64 | 65 | return loss.item() 66 | 67 | 68 | -------------------------------------------------------------------------------- /backbone/MNISTMLP.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | from backbone import MammothBackbone, xavier, num_flat_features 9 | 10 | 11 | class MNISTMLP(MammothBackbone): # __init__.py 12 | """ 13 | Network composed of two hidden layers, each containing 100 ReLU activations. 14 | Designed for the MNIST dataset. 15 | """ 16 | 17 | def __init__(self, input_size: int, output_size: int) -> None: 18 | """ 19 | Instantiates the layers of the network. 20 | :param input_size: the size of the input data 21 | :param output_size: the size of the output 22 | """ 23 | super(MNISTMLP, self).__init__() 24 | 25 | self.input_size = input_size 26 | self.output_size = output_size 27 | 28 | self.fc1 = nn.Linear(self.input_size, 100) 29 | self.fc2 = nn.Linear(100, 100) 30 | 31 | self._features = nn.Sequential( 32 | self.fc1, 33 | nn.ReLU(), 34 | self.fc2, 35 | nn.ReLU(), 36 | ) 37 | self.classifier = nn.Linear(100, self.output_size) 38 | self.net = nn.Sequential(self._features, self.classifier) 39 | self.reset_parameters() 40 | 41 | def reset_parameters(self) -> None: 42 | """ 43 | Calls the Xavier parameter initialization function. 44 | """ 45 | self.net.apply(xavier) 46 | 47 | def forward(self, x: torch.Tensor, returnt='out') -> torch.Tensor: 48 | """ 49 | Compute a forward pass. 50 | :param x: input tensor (batch_size, input_size) 51 | :return: output tensor (output_size) 52 | """ 53 | x = x.view(-1, num_flat_features(x)) 54 | 55 | feats = self._features(x) 56 | 57 | if returnt == 'features': 58 | return feats 59 | 60 | out = self.classifier(feats) 61 | 62 | if returnt == 'out': 63 | return out 64 | elif returnt == 'all': 65 | return (out, feats) 66 | 67 | raise NotImplementedError("Unknown return type") -------------------------------------------------------------------------------- /models/der.py: -------------------------------------------------------------------------------- 1 | from utils.buffer import Buffer 2 | from torch.nn import functional as F 3 | from utils.args import * # introduce the parameters 4 | from models.utils.continual_model import ContinualModel 5 | import numpy as np 6 | import torch 7 | 8 | def get_parser() -> ArgumentParser: 9 | parser = ArgumentParser(description='Continual learning via' 10 | ' Dark Experience Replay.') 11 | # args three types parameters 12 | add_management_args(parser) 13 | add_experiment_args(parser) 14 | add_rehearsal_args(parser) 15 | parser.add_argument('--alpha', type=float, required=True, 16 | help='Penalty weight.') 17 | return parser 18 | 19 | class Der(ContinualModel): 20 | NAME = 'der' 21 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 22 | 23 | def __init__(self, backbone, loss, args, transform): 24 | super(Der, self).__init__(backbone, loss, args, transform) 25 | self.buffer = Buffer(self.args.buffer_size, self.device) 26 | 27 | 28 | def begin_task(self, dataset): 29 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 30 | 31 | def end_task(self, dataset): 32 | dataset.task_id = dataset.i//dataset.N_CLASSES_PER_TASK 33 | task_id = dataset.i//dataset.N_CLASSES_PER_TASK 34 | 35 | 36 | def observe(self, inputs, labels, not_aug_inputs, t): 37 | # M = 10 38 | real_batch_size = inputs.shape[0] 39 | perm = torch.randperm(real_batch_size) 40 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 41 | not_aug_inputs = not_aug_inputs[perm] 42 | 43 | 44 | self.opt.zero_grad() 45 | outputs = self.net(inputs) 46 | loss = self.loss(outputs, labels) 47 | 48 | if not self.buffer.is_empty(): 49 | buf_inputs, buf_logits = self.buffer.get_data( 50 | self.args.minibatch_size, transform=self.transform) 51 | buf_outputs = self.net(buf_inputs) 52 | loss += self.args.alpha * F.mse_loss(buf_outputs, buf_logits) # distillation loss according to the DER paper 53 | 54 | loss.backward() 55 | self.opt.step() 56 | self.buffer.add_data(examples=not_aug_inputs, logits=outputs.data) 57 | 58 | return loss.item() 59 | 60 | -------------------------------------------------------------------------------- /models/cbrs.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from utils.cbrs_buffer import Buffer 3 | from utils.args import * 4 | from models.utils.continual_model import ContinualModel 5 | import numpy as np 6 | import random 7 | np.random.seed(0) 8 | 9 | def get_parser() -> ArgumentParser: 10 | parser = ArgumentParser(description='Continual learning via' 11 | ' Experience Replay.') 12 | add_management_args(parser) 13 | add_experiment_args(parser) 14 | add_rehearsal_args(parser) 15 | return parser 16 | 17 | class cbrs(ContinualModel): 18 | NAME = 'cbrs' 19 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 20 | 21 | def __init__(self, backbone, loss, args, transform): 22 | super(cbrs, self).__init__(backbone, loss, args, transform) 23 | self.buffer = Buffer(self.args.buffer_size, self.device) 24 | self.seen_classes = [] 25 | 26 | def begin_task(self, dataset): 27 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 28 | 29 | def observe(self, inputs, labels, not_aug_inputs, task_id): 30 | unique_label = torch.unique(labels) 31 | for i in unique_label: 32 | if i not in self.seen_classes: 33 | self.seen_classes.append(i) 34 | # number of classes encountered so far 35 | n_c = len(self.seen_classes) 36 | 37 | real_batch_size = inputs.shape[0] 38 | perm = torch.randperm(real_batch_size) 39 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 40 | not_aug_inputs = not_aug_inputs[perm] 41 | self.opt.zero_grad() 42 | outputs = self.net(inputs) 43 | loss = (1/n_c)*self.loss(outputs, labels) 44 | 45 | if not self.buffer.is_empty(): 46 | buf_inputs, buf_labels, buf_id = self.buffer.get_data( 47 | self.args.minibatch_size, transform=self.transform) 48 | outputs = self.net(buf_inputs) 49 | loss += (1-1/n_c)*self.loss(outputs, buf_labels) 50 | else: 51 | buf_id = task_id*torch.ones_like(labels) 52 | loss.backward() 53 | self.opt.step() 54 | self.buffer.add_data(examples=not_aug_inputs, 55 | labels=labels, 56 | task_labels=task_id*torch.ones_like(labels)) 57 | 58 | return loss.item() 59 | -------------------------------------------------------------------------------- /models/joint_gcl.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torch.optim import SGD 7 | 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | from datasets.utils.validation import ValidationDataset 11 | from utils.status import progress_bar 12 | import torch 13 | import numpy as np 14 | import math 15 | from tqdm import tqdm 16 | from torchvision import transforms 17 | 18 | 19 | def get_parser() -> ArgumentParser: 20 | parser = ArgumentParser(description='Joint training: a strong, simple baseline.') 21 | add_management_args(parser) 22 | add_experiment_args(parser) 23 | return parser 24 | 25 | 26 | class JointGCL(ContinualModel): 27 | NAME = 'joint_gcl' 28 | COMPATIBILITY = ['general-continual'] 29 | 30 | def __init__(self, backbone, loss, args, transform): 31 | super(JointGCL, self).__init__(backbone, loss, args, transform) 32 | self.old_data = [] 33 | self.old_labels = [] 34 | self.current_task = 0 35 | 36 | def end_task(self, dataset): 37 | # reinit network 38 | self.net = dataset.get_backbone() 39 | self.net.to(self.device) 40 | self.net.train() 41 | self.opt = SGD(self.net.parameters(), lr=self.args.lr) 42 | 43 | # gather data 44 | all_data = torch.cat(self.old_data) 45 | all_labels = torch.cat(self.old_labels) 46 | 47 | # train 48 | for e in range(1):#range(self.args.n_epochs): 49 | rp = torch.randperm(len(all_data)) 50 | for i in range(math.ceil(len(all_data) / self.args.batch_size)): 51 | inputs = all_data[rp][i * self.args.batch_size:(i+1) * self.args.batch_size] 52 | labels = all_labels[rp][i * self.args.batch_size:(i+1) * self.args.batch_size] 53 | inputs, labels = inputs.to(self.device), labels.to(self.device) 54 | 55 | self.opt.zero_grad() 56 | outputs = self.net(inputs) 57 | loss = self.loss(outputs, labels.long()) 58 | loss.backward() 59 | self.opt.step() 60 | progress_bar(i, math.ceil(len(all_data) / self.args.batch_size), e, 'J', loss.item()) 61 | 62 | def observe(self, inputs, labels, not_aug_inputs): 63 | self.old_data.append(inputs.data) 64 | self.old_labels.append(labels.data) 65 | return 0 66 | -------------------------------------------------------------------------------- /models/si.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | 11 | def get_parser() -> ArgumentParser: 12 | parser = ArgumentParser(description='Continual Learning Through' 13 | ' Synaptic Intelligence.') 14 | add_management_args(parser) 15 | add_experiment_args(parser) 16 | parser.add_argument('--c', type=float, required=True, 17 | help='surrogate loss weight parameter c') 18 | parser.add_argument('--xi', type=float, required=True, 19 | help='xi parameter for EWC online') 20 | 21 | return parser 22 | 23 | 24 | class SI(ContinualModel): 25 | NAME = 'si' 26 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il'] 27 | 28 | def __init__(self, backbone, loss, args, transform): 29 | super(SI, self).__init__(backbone, loss, args, transform) 30 | 31 | self.checkpoint = self.net.get_params().data.clone().to(self.device) 32 | self.big_omega = None 33 | self.small_omega = 0 34 | 35 | def penalty(self): 36 | if self.big_omega is None: 37 | return torch.tensor(0.0).to(self.device) 38 | else: 39 | penalty = (self.big_omega * ((self.net.get_params() - self.checkpoint) ** 2)).sum() 40 | return penalty 41 | 42 | def end_task(self, dataset): 43 | # big omega calculation step 44 | if self.big_omega is None: 45 | self.big_omega = torch.zeros_like(self.net.get_params()).to(self.device) 46 | 47 | self.big_omega += self.small_omega / ((self.net.get_params().data - self.checkpoint) ** 2 + self.args.xi) 48 | 49 | # store parameters checkpoint and reset small_omega 50 | self.checkpoint = self.net.get_params().data.clone().to(self.device) 51 | self.small_omega = 0 52 | 53 | def observe(self, inputs, labels, not_aug_inputs, t): 54 | self.opt.zero_grad() 55 | outputs = self.net(inputs) 56 | penalty = self.penalty() 57 | loss = self.loss(outputs, labels) + self.args.c * penalty 58 | loss.backward() 59 | nn.utils.clip_grad.clip_grad_value_(self.net.parameters(), 1) 60 | self.opt.step() 61 | 62 | self.small_omega += self.args.lr * self.net.get_grads().data ** 2 63 | 64 | return loss.item() 65 | -------------------------------------------------------------------------------- /models/agem_r.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import numpy as np 8 | from utils.buffer import Buffer 9 | from models.gem import overwrite_grad 10 | from models.gem import store_grad 11 | from utils.args import * 12 | from models.agem import project 13 | from models.utils.continual_model import ContinualModel 14 | 15 | def get_parser() -> ArgumentParser: 16 | parser = ArgumentParser(description='Continual learning via A-GEM, ' 17 | 'leveraging a reservoir buffer.') 18 | add_management_args(parser) 19 | add_experiment_args(parser) 20 | add_rehearsal_args(parser) 21 | return parser 22 | 23 | class AGemr(ContinualModel): 24 | NAME = 'agem_r' 25 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 26 | 27 | def __init__(self, backbone, loss, args, transform): 28 | super(AGemr, self).__init__(backbone, loss, args, transform) 29 | 30 | self.buffer = Buffer(self.args.buffer_size, self.device) 31 | self.grad_dims = [] 32 | for param in self.parameters(): 33 | self.grad_dims.append(param.data.numel()) 34 | self.grad_xy = torch.Tensor(np.sum(self.grad_dims)).to(self.device) 35 | self.grad_er = torch.Tensor(np.sum(self.grad_dims)).to(self.device) 36 | self.current_task = 0 37 | 38 | def observe(self, inputs, labels, not_aug_inputs): 39 | self.zero_grad() 40 | p = self.net.forward(inputs) 41 | loss = self.loss(p, labels) 42 | loss.backward() 43 | 44 | if not self.buffer.is_empty(): 45 | store_grad(self.parameters, self.grad_xy, self.grad_dims) 46 | 47 | buf_inputs, buf_labels = self.buffer.get_data(self.args.minibatch_size) 48 | self.net.zero_grad() 49 | buf_outputs = self.net.forward(buf_inputs) 50 | penalty = self.loss(buf_outputs, buf_labels) 51 | penalty.backward() 52 | store_grad(self.parameters, self.grad_er, self.grad_dims) 53 | 54 | dot_prod = torch.dot(self.grad_xy, self.grad_er) 55 | if dot_prod.item() < 0: 56 | g_tilde = project(gxy=self.grad_xy, ger=self.grad_er) 57 | overwrite_grad(self.parameters, g_tilde, self.grad_dims) 58 | else: 59 | overwrite_grad(self.parameters, self.grad_xy, self.grad_dims) 60 | 61 | self.opt.step() 62 | 63 | self.buffer.add_data(examples=not_aug_inputs, labels=labels) 64 | 65 | return loss.item() 66 | -------------------------------------------------------------------------------- /models/er_ace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn.functional as F 8 | from utils.buffer import Buffer 9 | from utils.args import * 10 | from models.utils.continual_model import ContinualModel 11 | from datasets import get_dataset 12 | import os 13 | 14 | def get_parser() -> ArgumentParser: 15 | parser = ArgumentParser(description='Continual learning via' 16 | ' Experience Replay.') 17 | add_management_args(parser) 18 | add_experiment_args(parser) 19 | add_rehearsal_args(parser) 20 | return parser 21 | 22 | 23 | class ErACE(ContinualModel): 24 | NAME = 'er_ace' 25 | COMPATIBILITY = ['class-il', 'task-il'] 26 | 27 | def __init__(self, backbone, loss, args, transform): 28 | super(ErACE, self).__init__(backbone, loss, args, transform) 29 | self.buffer = Buffer(self.args.buffer_size, self.device) 30 | self.seen_so_far = torch.tensor([]).long().to(self.device) 31 | self.num_classes = get_dataset(args).N_TASKS * get_dataset(args).N_CLASSES_PER_TASK 32 | self.task = 0 33 | 34 | def begin_task(self, dataset): 35 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 36 | 37 | 38 | def end_task(self, dataset): 39 | self.task += 1 40 | 41 | def observe(self, inputs, labels, not_aug_inputs,t): 42 | 43 | present = labels.unique() 44 | self.seen_so_far = torch.cat([self.seen_so_far, present]).unique() 45 | 46 | logits = self.net(inputs) 47 | mask = torch.zeros_like(logits) 48 | mask[:, present] = 1 49 | 50 | self.opt.zero_grad() 51 | if self.seen_so_far.max() < (self.num_classes - 1): 52 | mask[:, self.seen_so_far.max():] = 1 53 | 54 | if self.task > 0: 55 | logits = logits.masked_fill(mask == 0, torch.finfo(logits.dtype).min) 56 | 57 | loss = self.loss(logits, labels) 58 | loss_re = torch.tensor(0.) 59 | 60 | if self.task > 0: 61 | # sample from buffer 62 | buf_inputs, buf_labels = self.buffer.get_data( 63 | self.args.minibatch_size, transform=self.transform) 64 | # loss_re = self.loss(self.net(buf_inputs, mode='surrogate'), buf_labels) 65 | loss_re = self.loss(self.net(buf_inputs), buf_labels) 66 | 67 | loss += loss_re 68 | 69 | loss.backward() 70 | self.opt.step() 71 | self.opt.zero_grad() 72 | self.buffer.add_data(examples=not_aug_inputs, 73 | labels=labels) 74 | return loss.item() 75 | -------------------------------------------------------------------------------- /utils/args.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from argparse import ArgumentParser 7 | from datasets import NAMES as DATASET_NAMES 8 | from models import get_all_models 9 | 10 | 11 | def add_experiment_args(parser: ArgumentParser) -> None: 12 | """ 13 | Adds the arguments used by all the models. 14 | :param parser: the parser instance 15 | """ 16 | parser.add_argument('--dataset', type=str, required=True, 17 | choices=DATASET_NAMES, 18 | help='Which dataset to perform experiments on.') 19 | # This part cannot be commented out 20 | parser.add_argument('--model', type=str, required=True, 21 | help='Model name.', choices=get_all_models()) 22 | 23 | parser.add_argument('--lr', type=float, required=True, 24 | help='Learning rate.') 25 | 26 | parser.add_argument('--optim_wd', type=float, default=0., 27 | help='optimizer weight decay.') 28 | parser.add_argument('--optim_mom', type=float, default=0., 29 | help='optimizer momentum.') 30 | parser.add_argument('--optim_nesterov', type=int, default=0, 31 | help='optimizer nesterov momentum.') 32 | 33 | parser.add_argument('--n_epochs', type=int, 34 | help='Batch size.') 35 | parser.add_argument('--batch_size', type=int, 36 | help='Batch size.') 37 | 38 | def add_management_args(parser: ArgumentParser) -> None: 39 | parser.add_argument('--seed', type=int, default=None, 40 | help='The random seed.') 41 | parser.add_argument('--notes', type=str, default=None, 42 | help='Notes for this run.') 43 | 44 | parser.add_argument('--non_verbose', action='store_true') 45 | parser.add_argument('--csv_log', action='store_true', 46 | help='Enable csv logging') 47 | parser.add_argument('--tensorboard', action='store_true', 48 | help='Enable tensorboard logging') 49 | parser.add_argument('--validation', action='store_true', 50 | help='Test on the validation set') 51 | 52 | 53 | def add_rehearsal_args(parser: ArgumentParser) -> None: 54 | """ 55 | Adds the arguments used by all the rehearsal-based methods 56 | :param parser: the parser instance 57 | """ 58 | parser.add_argument('--buffer_size', type=int, required=True, 59 | help='The size of the memory buffer.') 60 | parser.add_argument('--minibatch_size', type=int, 61 | help='The batch size of the memory buffer.') 62 | -------------------------------------------------------------------------------- /utils/continual_training.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | 7 | import torch 8 | from datasets import get_dataset 9 | from models import get_model 10 | from utils.status import progress_bar 11 | from utils.tb_logger import * 12 | from utils.status import create_fake_stash 13 | from models.utils.continual_model import ContinualModel 14 | from argparse import Namespace 15 | 16 | 17 | def evaluate(model: ContinualModel, dataset) -> float: 18 | """ 19 | Evaluates the final accuracy of the model. 20 | :param model: the model to be evaluated 21 | :param dataset: the GCL dataset at hand 22 | :return: a float value that indicates the accuracy 23 | """ 24 | model.net.eval() 25 | correct, total = 0, 0 26 | while not dataset.test_over: 27 | inputs, labels = dataset.get_test_data() 28 | inputs, labels = inputs.to(model.device), labels.to(model.device) 29 | outputs = model(inputs) 30 | _, predicted = torch.max(outputs.data, 1) 31 | correct += torch.sum(predicted == labels).item() 32 | total += labels.shape[0] 33 | 34 | acc = correct / total * 100 35 | return acc 36 | 37 | 38 | def train(args: Namespace): 39 | """ 40 | The training process, including evaluations and loggers. 41 | :param model: the module to be trained 42 | :param dataset: the continual dataset at hand 43 | :param args: the arguments of the current execution 44 | """ 45 | if args.csv_log: 46 | from utils.loggers import CsvLogger 47 | 48 | dataset = get_dataset(args) 49 | backbone = dataset.get_backbone() 50 | loss = dataset.get_loss() 51 | model = get_model(args, backbone, loss, dataset.get_transform()) 52 | model.net.to(model.device) 53 | 54 | model_stash = create_fake_stash(model, args) 55 | 56 | if args.csv_log: 57 | csv_logger = CsvLogger(dataset.SETTING, dataset.NAME, model.NAME) 58 | if args.tensorboard: 59 | tb_logger = TensorboardLogger(args, dataset.SETTING, model_stash) 60 | 61 | model.net.train() 62 | epoch, i = 0, 0 63 | while not dataset.train_over: 64 | inputs, labels, not_aug_inputs = dataset.get_train_data() 65 | inputs, labels = inputs.to(model.device), labels.to(model.device) 66 | not_aug_inputs = not_aug_inputs.to(model.device) 67 | loss = model.observe(inputs, labels, not_aug_inputs) 68 | progress_bar(i, dataset.LENGTH // args.batch_size, epoch, 'C', loss) 69 | if args.tensorboard: 70 | tb_logger.log_loss_gcl(loss, i) 71 | i += 1 72 | 73 | if model.NAME == 'joint_gcl': 74 | model.end_task(dataset) 75 | 76 | acc = evaluate(model, dataset) 77 | print('Accuracy:', acc) 78 | 79 | if args.csv_log: 80 | csv_logger.log(acc) 81 | csv_logger.write(vars(args)) 82 | -------------------------------------------------------------------------------- /models/ER_mask: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | from utils.buffer import Buffer 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | import numpy as np 11 | np.random.seed(0) 12 | 13 | def get_parser() -> ArgumentParser: 14 | parser = ArgumentParser(description='Continual learning via' 15 | ' Experience Replay.') 16 | add_management_args(parser) 17 | add_experiment_args(parser) 18 | add_rehearsal_args(parser) 19 | return parser 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | class er_mask(ContinualModel): 28 | NAME = 'er_mask' 29 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 30 | 31 | def __init__(self, backbone, loss, args, transform, task_id): 32 | super(er_mask, self).__init__(backbone, loss, args, transform) 33 | self.buffer = Buffer(self.args.buffer_size, self.device) 34 | 35 | 36 | def compute_offsets(self, task): 37 | offset1 = task * self.nc_per_task 38 | offset2 = (task + 1) * self.nc_per_task 39 | return int(offset1), int(offset2) 40 | 41 | def take_multitask_loss(self, bt, logits, y): 42 | loss = 0.0 43 | for i, ti in enumerate(bt): 44 | offset1, offset2 = self.compute_offsets(ti) 45 | loss += self.loss(logits[i, offset1:offset2].unsqueeze(0), y[i].unsqueeze(0)-offset1) 46 | return loss/len(bt) 47 | 48 | def observe(self, inputs, labels, not_aug_inputs,t): 49 | M = 10 50 | real_batch_size = inputs.shape[0] 51 | real_batch_size = int(np.random.randint(1, min(M, real_batch_size), size=1)) 52 | perm = torch.randperm(real_batch_size) 53 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 54 | not_aug_inputs = not_aug_inputs[perm] 55 | 56 | 57 | self.opt.zero_grad() 58 | if not self.buffer.is_empty(): 59 | buf_inputs, buf_labels, buf_id = self.buffer.get_data( 60 | self.args.minibatch_size, transform=self.transform) 61 | inputs = torch.cat((inputs, buf_inputs)) 62 | labels = torch.cat((labels, buf_labels)) 63 | buf_id = torch.cat((task_id*torch.ones_like(labels),buf_id)) 64 | 65 | outputs = self.net(inputs) 66 | loss = self.take_multitask_loss(buf_id, outputs, labels) 67 | 68 | loss = self.loss(outputs, labels) 69 | loss.backward() 70 | self.opt.step() 71 | 72 | self.buffer.add_data(examples=not_aug_inputs, 73 | labels=labels[:real_batch_size], 74 | task_labels=task_id*torch.ones_like(labels)) 75 | 76 | return loss.item() 77 | -------------------------------------------------------------------------------- /datasets/utils/validation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | from PIL import Image 8 | import numpy as np 9 | import os 10 | from utils import create_if_not_exists 11 | import torchvision.transforms.transforms as transforms 12 | from torchvision import datasets 13 | 14 | 15 | class ValidationDataset(torch.utils.data.Dataset): 16 | def __init__(self, data: torch.Tensor, targets: np.ndarray, 17 | transform: transforms=None, target_transform: transforms=None) -> None: 18 | self.data = data 19 | self.targets = targets 20 | self.transform = transform 21 | self.target_transform = target_transform 22 | 23 | def __len__(self): 24 | return self.data.shape[0] 25 | 26 | def __getitem__(self, index): 27 | img, target = self.data[index], self.targets[index] 28 | 29 | # doing this so that it is consistent with all other datasets 30 | # to return a PIL Image 31 | if isinstance(img, np.ndarray): 32 | if np.max(img) < 2: 33 | img = Image.fromarray(np.uint8(img * 255)) 34 | else: 35 | img = Image.fromarray(img) 36 | else: 37 | img = Image.fromarray(img.numpy()) 38 | 39 | if self.transform is not None: 40 | img = self.transform(img) 41 | 42 | if self.target_transform is not None: 43 | target = self.target_transform(target) 44 | 45 | return img, target 46 | 47 | def get_train_val(train: datasets, test_transform: transforms, 48 | dataset: str, val_perc: float=0.1): 49 | """ 50 | Extract val_perc% of the training set as the validation set. 51 | :param train: training dataset 52 | :param test_transform: transformation of the test dataset 53 | :param dataset: dataset name 54 | :param val_perc: percentage of the training set to be extracted 55 | :return: the training set and the validation set 56 | """ 57 | dataset_length = train.data.shape[0] 58 | directory = 'datasets/val_permutations/' 59 | create_if_not_exists(directory) 60 | file_name = dataset + '.pt' 61 | if os.path.exists(directory + file_name): 62 | perm = torch.load(directory + file_name) 63 | else: 64 | perm = torch.randperm(dataset_length) 65 | torch.save(perm, directory + file_name) 66 | train.data = train.data[perm] 67 | train.targets = np.array(train.targets)[perm] 68 | test_dataset = ValidationDataset(train.data[:int(val_perc * dataset_length)], 69 | train.targets[:int(val_perc * dataset_length)], 70 | transform=test_transform) 71 | train.data = train.data[int(val_perc * dataset_length):] 72 | train.targets = train.targets[int(val_perc * dataset_length):] 73 | 74 | return train, test_dataset 75 | -------------------------------------------------------------------------------- /models/gss.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | from utils.gss_buffer import Buffer as Buffer 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | 11 | 12 | def get_parser() -> ArgumentParser: 13 | parser = ArgumentParser(description='Gradient based sample selection' 14 | 'for online continual learning') 15 | add_management_args(parser) 16 | add_experiment_args(parser) 17 | add_rehearsal_args(parser) 18 | parser.add_argument('--batch_num', type=int, required=True, 19 | help='Number of batches extracted from the buffer.') 20 | parser.add_argument('--gss_minibatch_size', type=int, default=None, 21 | help='The batch size of the gradient comparison.') 22 | return parser 23 | 24 | 25 | class Gss(ContinualModel): 26 | NAME = 'gss' 27 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 28 | 29 | def __init__(self, backbone, loss, args, transform): 30 | super(Gss, self).__init__(backbone, loss, args, transform) 31 | self.buffer = Buffer(self.args.buffer_size, self.device, 32 | self.args.gss_minibatch_size if 33 | self.args.gss_minibatch_size is not None 34 | else self.args.minibatch_size, self) 35 | self.alj_nepochs = self.args.batch_num 36 | 37 | def get_grads(self, inputs, labels): 38 | self.net.eval() 39 | self.opt.zero_grad() 40 | outputs = self.net(inputs) 41 | loss = self.loss(outputs, labels) 42 | loss.backward() 43 | grads = self.net.get_grads().clone().detach() 44 | self.opt.zero_grad() 45 | self.net.train() 46 | if len(grads.shape) == 1: 47 | grads = grads.unsqueeze(0) 48 | return grads 49 | 50 | def observe(self, inputs, labels, not_aug_inputs, t): 51 | 52 | real_batch_size = inputs.shape[0] 53 | self.buffer.drop_cache() 54 | self.buffer.reset_fathom() 55 | 56 | for _ in range(self.alj_nepochs): 57 | self.opt.zero_grad() 58 | if not self.buffer.is_empty(): 59 | buf_inputs, buf_labels = self.buffer.get_data( 60 | self.args.minibatch_size, transform=self.transform) 61 | tinputs = torch.cat((inputs, buf_inputs)) 62 | tlabels = torch.cat((labels, buf_labels)) 63 | else: 64 | tinputs = inputs 65 | tlabels = labels 66 | 67 | outputs = self.net(tinputs) 68 | loss = self.loss(outputs, tlabels) 69 | loss.backward() 70 | self.opt.step() 71 | 72 | self.buffer.add_data(examples=not_aug_inputs, 73 | labels=labels[:real_batch_size]) 74 | 75 | return loss.item() 76 | -------------------------------------------------------------------------------- /utils/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import numpy # needed (don't change it) 7 | import importlib 8 | import os 9 | import sys 10 | import socket 11 | mammoth_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 12 | sys.path.append(mammoth_path) 13 | sys.path.append(mammoth_path + '/datasets') 14 | sys.path.append(mammoth_path + '/backbone') 15 | sys.path.append(mammoth_path + '/models') 16 | 17 | from datasets import NAMES as DATASET_NAMES 18 | from models import get_all_models 19 | from argparse import ArgumentParser 20 | from utils.args import add_management_args 21 | from datasets import ContinualDataset 22 | from utils.continual_training import train as ctrain 23 | from datasets import get_dataset 24 | from models import get_model 25 | from utils.training import train#, evaluate0 26 | from utils.best_args import best_args 27 | from utils.conf import set_random_seed 28 | import setproctitle 29 | import torch 30 | import uuid 31 | import datetime 32 | import wandb 33 | 34 | import time 35 | def count_parameters(model): 36 | return sum(p.numel() for p in model.parameters() if p.requires_grad) 37 | 38 | def parse_args(): # 39 | parser = ArgumentParser(description='mammoth', allow_abbrev=False) 40 | # choose the model want to run 41 | parser.add_argument('--model', type=str, required=True, 42 | help='Model name.', choices=get_all_models()) # duplicate introduce in add_experiment_args 注释掉了args里面的model 43 | torch.set_num_threads(4) 44 | # add_management_args(parser) 45 | args = parser.parse_known_args()[0] # parser.parse_args()[0] (运行报错) 这样在sh文件里面加入其他命令行的时候, 这里还没添加,会报错. 所以必须用parse_known_args() 46 | mod = importlib.import_module('models.' + args.model) 47 | get_parser = getattr(mod, 'get_parser') 48 | parser = get_parser() 49 | # other configurations are shown in args.py 50 | args = parser.parse_args() 51 | 52 | if args.seed is not None: 53 | set_random_seed(args.seed) 54 | 55 | return args 56 | 57 | def main(args=None): 58 | # lecun_fix() 59 | # print('arg',args.model) 60 | if args is None: 61 | args = parse_args() 62 | dataset = get_dataset(args) 63 | if args.model =='vrmcl': 64 | backbone = dataset.get_backbone(eman1=True) 65 | else: 66 | backbone = dataset.get_backbone() 67 | loss = dataset.get_loss() 68 | model = get_model(args, backbone, loss, dataset.get_transform()) 69 | 70 | # print parameters 71 | print("Total trainable parameters: ", count_parameters(model)) 72 | 73 | # wandb.watch(model,log='all') 74 | if isinstance(dataset, ContinualDataset): 75 | start_time = time.time() 76 | train(model, dataset, args) 77 | end_time = time.time() 78 | total_time = end_time - start_time 79 | print('running time is: {:.2f}s'.format(total_time)) 80 | else: 81 | assert not hasattr(model, 'end_task') or model.NAME == 'joint_gcl' 82 | ctrain(args) 83 | 84 | 85 | if __name__ == '__main__': 86 | main() 87 | -------------------------------------------------------------------------------- /backbone/MNISTMLP_MAML.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | from backbone import MammothBackbone, xavier, num_flat_features 9 | from collections import OrderedDict 10 | import torch.nn.functional as F 11 | 12 | 13 | class MNISTMLP_MAML(MammothBackbone): # __init__.py 14 | """ 15 | Network composed of two hidden layers, each containing 100 ReLU activations. 16 | Designed for the MNIST dataset. 17 | """ 18 | def __init__(self, input_size: int, output_size: int) -> None: 19 | """ 20 | Instantiates the layers of the network. 21 | :param input_size: the size of the input data 22 | :param output_size: the size of the output 23 | """ 24 | super(MNISTMLP_MAML, self).__init__() 25 | 26 | self.input_size = input_size 27 | self.output_size = output_size 28 | 29 | self.fc1 = nn.Linear(self.input_size, 100) 30 | self.fc2 = nn.Linear(100, 100) 31 | 32 | self._features = nn.Sequential( 33 | self.fc1, 34 | nn.ReLU(), 35 | self.fc2, 36 | nn.ReLU(), 37 | ) 38 | self.classifier = nn.Linear(100, self.output_size) 39 | self.net = nn.Sequential(self._features, self.classifier) 40 | self.reset_parameters() 41 | 42 | def reset_parameters(self) -> None: 43 | """ 44 | Calls the Xavier parameter initialization function. 45 | """ 46 | self.net.apply(xavier) 47 | 48 | def forward(self, x: torch.Tensor, returnt='out') -> torch.Tensor: 49 | """ 50 | Compute a forward pass. 51 | :param x: input tensor (batch_size, input_size) 52 | :return: output tensor (output_size) 53 | """ 54 | x = x.view(-1, num_flat_features(x)) 55 | 56 | feats = self._features(x) 57 | 58 | if returnt == 'features': 59 | return feats 60 | 61 | out = self.classifier(feats) 62 | 63 | if returnt == 'out': 64 | return out 65 | elif returnt == 'all': 66 | return (out, feats) 67 | raise NotImplementedError("Unknown return type") 68 | 69 | 70 | def functional_forward(self,x:torch.Tensor,fast_weight:OrderedDict,returnt:str ='out')-> torch.Tensor: 71 | 72 | x = x.view(-1, num_flat_features(x)) 73 | x = F.relu(F.linear(x,fast_weight['fc1.weight'], fast_weight['fc1.bias'])) 74 | feats = F.relu(F.linear(x,fast_weight['fc2.weight'], fast_weight['fc2.bias'])) 75 | if returnt == 'features': 76 | return feats 77 | out = F.linear(feats, fast_weight['classifier.weight'], fast_weight['classifier.bias']) 78 | if returnt == 'out': 79 | return out 80 | elif returnt == 'all': 81 | return (out, feats) 82 | raise NotImplementedError("Unknown return type") 83 | 84 | 85 | def get_fast_weight(self) -> OrderedDict: 86 | return OrderedDict([[p[0], p[1].clone()] for p in self.named_parameters()]) -------------------------------------------------------------------------------- /models/pnn.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch.optim as optim 7 | from torch.optim import SGD 8 | import torch 9 | import torch.nn as nn 10 | from utils.conf import get_device 11 | from utils.args import * 12 | from datasets import get_dataset 13 | 14 | 15 | def get_parser() -> ArgumentParser: 16 | parser = ArgumentParser(description='Continual Learning via' 17 | ' Progressive Neural Networks.') 18 | add_management_args(parser) 19 | add_experiment_args(parser) 20 | return parser 21 | 22 | 23 | def get_backbone(bone, old_cols=None, x_shape=None): 24 | from backbone.MNISTMLP import MNISTMLP 25 | from backbone.MNISTMLP_PNN import MNISTMLP_PNN 26 | from backbone.ResNet18 import ResNet 27 | from backbone.ResNet18_PNN import resnet18_pnn 28 | 29 | if isinstance(bone, MNISTMLP): 30 | return MNISTMLP_PNN(bone.input_size, bone.output_size, old_cols) 31 | elif isinstance(bone, ResNet): 32 | return resnet18_pnn(bone.num_classes, bone.nf, old_cols, x_shape) 33 | else: 34 | raise NotImplementedError('Progressive Neural Networks is not implemented for this backbone') 35 | 36 | 37 | class Pnn(nn.Module): 38 | NAME = 'pnn' 39 | COMPATIBILITY = ['task-il'] 40 | 41 | def __init__(self, backbone, loss, args, transform): 42 | super(Pnn, self).__init__() 43 | self.loss = loss 44 | self.args = args 45 | self.transform = transform 46 | self.device = get_device() 47 | self.x_shape = None 48 | self.nets = [get_backbone(backbone).to(self.device)] 49 | self.net = self.nets[-1] 50 | self.opt = SGD(self.net.parameters(), lr=self.args.lr) 51 | 52 | self.soft = torch.nn.Softmax(dim=0) 53 | self.logsoft = torch.nn.LogSoftmax(dim=0) 54 | self.dataset = get_dataset(args) 55 | self.task_idx = 0 56 | 57 | def forward(self, x, task_label): 58 | if self.x_shape is None: 59 | self.x_shape = x.shape 60 | 61 | if self.task_idx == 0: 62 | out = self.net(x) 63 | else: 64 | self.nets[task_label].to(self.device) 65 | out = self.nets[task_label](x) 66 | if self.task_idx != task_label: 67 | self.nets[task_label].cpu() 68 | return out 69 | 70 | def end_task(self, dataset): 71 | # instantiate new column 72 | if self.task_idx == 4: 73 | return 74 | self.task_idx += 1 75 | self.nets[-1].cpu() 76 | self.nets.append(get_backbone(dataset.get_backbone(), self.nets, self.x_shape).to(self.device)) 77 | self.net = self.nets[-1] 78 | self.opt = optim.SGD(self.net.parameters(), lr=self.args.lr) 79 | 80 | def observe(self, inputs, labels, not_aug_inputs): 81 | if self.x_shape is None: 82 | self.x_shape = inputs.shape 83 | 84 | self.opt.zero_grad() 85 | outputs = self.net(inputs) 86 | loss = self.loss(outputs, labels) 87 | loss.backward() 88 | self.opt.step() 89 | 90 | return loss.item() 91 | -------------------------------------------------------------------------------- /datasets/transforms/rotation.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import numpy as np 7 | import torchvision.transforms.functional as F 8 | 9 | 10 | class Rotation(object): 11 | """ 12 | Defines a fixed rotation for a numpy array. 13 | """ 14 | 15 | def __init__(self, degrees, deg_min: int = 0, deg_max: int = 180, ) -> None: 16 | """ 17 | Initializes the rotation with a random angle. 18 | :param deg_min: lower extreme of the possible random angle 19 | :param deg_max: upper extreme of the possible random angle 20 | """ 21 | self.deg_min = deg_min 22 | self.deg_max = deg_max 23 | # self.degrees = np.random.uniform(self.deg_min, self.deg_max) 24 | self.degrees = degrees 25 | 26 | def __call__(self, x: np.ndarray) -> np.ndarray: 27 | """ 28 | Applies the rotation. 29 | :param x: image to be rotated 30 | :return: rotated image 31 | """ 32 | return F.rotate(x, self.degrees) 33 | # return F.rotate(x, degree) 34 | 35 | 36 | class FixedRotation(object): 37 | """ 38 | Defines a fixed rotation for a numpy array. 39 | """ 40 | 41 | def __init__(self, seed: int, deg_min: int = 0, deg_max: int = 180) -> None: 42 | """ 43 | Initializes the rotation with a random angle. 44 | :param seed: seed of the rotation 45 | :param deg_min: lower extreme of the possible random angle 46 | :param deg_max: upper extreme of the possible random angle 47 | """ 48 | self.seed = seed 49 | self.deg_min = deg_min 50 | self.deg_max = deg_max 51 | 52 | np.random.seed(seed) 53 | self.degrees = np.random.uniform(self.deg_min, self.deg_max) 54 | 55 | def __call__(self, x: np.ndarray, degree) -> np.ndarray: 56 | """ 57 | Applies the rotation. 58 | :param x: image to be rotated 59 | :return: rotated image 60 | """ 61 | # return F.rotate(x, self.degrees) 62 | return F.rotate(x, degree) 63 | 64 | 65 | class IncrementalRotation(object): 66 | """ 67 | Defines an incremental rotation for a numpy array. 68 | """ 69 | 70 | def __init__(self, init_deg: int = 0, increase_per_iteration: float = 0.006) -> None: 71 | """ 72 | Defines the initial angle as well as the increase for each rotation 73 | :param init_deg: 74 | :param increase_per_iteration: 75 | """ 76 | self.increase_per_iteration = increase_per_iteration 77 | self.iteration = 0 78 | self.degrees = init_deg 79 | 80 | def __call__(self, x: np.ndarray) -> np.ndarray: 81 | """ 82 | Applies the rotation. 83 | :param x: image to be rotated 84 | :return: rotated image 85 | """ 86 | degs = (self.iteration * self.increase_per_iteration + self.degrees) % 360 87 | self.iteration += 1 88 | return F.rotate(x, degs) 89 | 90 | def set_iteration(self, x: int) -> None: 91 | """ 92 | Set the iteration to a given integer 93 | :param x: iteration index 94 | """ 95 | self.iteration = x 96 | -------------------------------------------------------------------------------- /models/agem.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import numpy as np 8 | from utils.buffer import Buffer 9 | from models.gem import overwrite_grad 10 | from models.gem import store_grad 11 | from utils.args import * 12 | from models.utils.continual_model import ContinualModel 13 | 14 | def get_parser() -> ArgumentParser: 15 | parser = ArgumentParser(description='Continual learning via A-GEM.') 16 | add_management_args(parser) 17 | add_experiment_args(parser) 18 | add_rehearsal_args(parser) 19 | return parser 20 | 21 | def project(gxy: torch.Tensor, ger: torch.Tensor) -> torch.Tensor: 22 | corr = torch.dot(gxy, ger) / torch.dot(ger, ger) 23 | return gxy - corr * ger 24 | 25 | 26 | class AGem(ContinualModel): 27 | NAME = 'agem' 28 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il'] 29 | 30 | def __init__(self, backbone, loss, args, transform): 31 | super(AGem, self).__init__(backbone, loss, args, transform) 32 | 33 | self.buffer = Buffer(self.args.buffer_size, self.device) 34 | self.grad_dims = [] 35 | for param in self.parameters(): 36 | self.grad_dims.append(param.data.numel()) 37 | self.grad_xy = torch.Tensor(np.sum(self.grad_dims)).to(self.device) 38 | self.grad_er = torch.Tensor(np.sum(self.grad_dims)).to(self.device) 39 | 40 | def begin_task(self, dataset): 41 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 42 | 43 | 44 | def end_task(self, dataset): 45 | dataset.task_id = dataset.i//dataset.N_CLASSES_PER_TASK 46 | # Other classifier Reuse 47 | task_id = dataset.i//dataset.N_CLASSES_PER_TASK 48 | 49 | def observe(self, inputs, labels, not_aug_inputs, t): 50 | real_batch_size = inputs.shape[0] 51 | perm = torch.randperm(real_batch_size) 52 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 53 | not_aug_inputs = not_aug_inputs[perm] 54 | 55 | self.zero_grad() 56 | p = self.net.forward(inputs) 57 | loss = self.loss(p, labels) 58 | 59 | 60 | loss.backward() 61 | 62 | if not self.buffer.is_empty(): 63 | store_grad(self.parameters, self.grad_xy, self.grad_dims) 64 | 65 | buf_inputs, buf_labels = self.buffer.get_data(self.args.minibatch_size, transform=self.transform) 66 | self.net.zero_grad() 67 | buf_outputs = self.net.forward(buf_inputs) 68 | penalty = self.loss(buf_outputs, buf_labels) 69 | penalty.backward() 70 | store_grad(self.parameters, self.grad_er, self.grad_dims) 71 | 72 | dot_prod = torch.dot(self.grad_xy, self.grad_er) 73 | if dot_prod.item() < 0: 74 | g_tilde = project(gxy=self.grad_xy, ger=self.grad_er) 75 | overwrite_grad(self.parameters, g_tilde, self.grad_dims) 76 | else: 77 | overwrite_grad(self.parameters, self.grad_xy, self.grad_dims) 78 | 79 | self.opt.step() 80 | self.buffer.add_data(examples=not_aug_inputs, 81 | labels=labels) 82 | return loss.item() 83 | 84 | -------------------------------------------------------------------------------- /utils/status.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from datetime import datetime 7 | import sys 8 | import os 9 | from utils.conf import base_path 10 | from typing import Any, Dict, Union 11 | from torch import nn 12 | from argparse import Namespace 13 | from datasets.utils.continual_dataset import ContinualDataset 14 | 15 | 16 | def create_stash(model: nn.Module, args: Namespace, 17 | dataset: ContinualDataset) -> Dict[Any, str]: 18 | """ 19 | Creates the dictionary where to save the model status. 20 | :param model: the model 21 | :param args: the current arguments 22 | :param dataset: the dataset at hand 23 | """ 24 | now = datetime.now() 25 | model_stash = {'task_idx': 0, 'epoch_idx': 0, 'batch_idx': 0} 26 | name_parts = [args.dataset, model.NAME] 27 | if 'buffer_size' in vars(args).keys(): 28 | name_parts.append('buf_' + str(args.buffer_size)) 29 | name_parts.append(now.strftime("%Y%m%d_%H%M%S_%f")) 30 | model_stash['model_name'] = '/'.join(name_parts) 31 | model_stash['mean_accs'] = [] 32 | model_stash['args'] = args 33 | model_stash['backup_folder'] = os.path.join(base_path(), 'backups', 34 | dataset.SETTING, 35 | model_stash['model_name']) 36 | return model_stash 37 | 38 | 39 | def create_fake_stash(model: nn.Module, args: Namespace) -> Dict[Any, str]: 40 | """ 41 | Create a fake stash, containing just the model name. 42 | This is used in general continual, as it is useless to backup 43 | a lightweight MNIST-360 training. 44 | :param model: the model 45 | :param args: the arguments of the call 46 | :return: a dict containing a fake stash 47 | """ 48 | now = datetime.now() 49 | model_stash = {'task_idx': 0, 'epoch_idx': 0} 50 | name_parts = [args.dataset, model.NAME] 51 | if 'buffer_size' in vars(args).keys(): 52 | name_parts.append('buf_' + str(args.buffer_size)) 53 | name_parts.append(now.strftime("%Y%m%d_%H%M%S_%f")) 54 | model_stash['model_name'] = '/'.join(name_parts) 55 | 56 | return model_stash 57 | 58 | 59 | def progress_bar(i: int, max_iter: int, epoch: Union[int, str], 60 | task_number: int, loss: float) -> None: 61 | """ 62 | Prints out the progress bar on the stderr file. 63 | :param i: the current iteration 64 | :param max_iter: the maximum number of iteration 65 | :param epoch: the epoch 66 | :param task_number: the task index 67 | :param loss: the current value of the loss function 68 | """ 69 | if not (i + 1) % 10 or (i + 1) == max_iter: 70 | progress = min(float((i + 1) / max_iter), 1) 71 | # print('process', process) 72 | progress_bar = ('#' * int(50 * progress)) + ('┈' * (50 - int(50 * progress))) 73 | # progress_bar = ('█' * int(50 * progress)) + ('┈' * (50 - int(50 * progress))) 74 | print('\r[ {} ] Task {} | epoch {}: |{}| loss: {}'.format( 75 | datetime.now().strftime("%m-%d | %H:%M"), 76 | task_number + 1 if isinstance(task_number, int) else task_number, 77 | epoch, 78 | progress_bar, 79 | round(loss, 8) 80 | ), file=sys.stderr, end='', flush=True) 81 | -------------------------------------------------------------------------------- /utils/tb_logger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | 7 | from utils.conf import base_path 8 | import os 9 | from argparse import Namespace 10 | from typing import Dict, Any 11 | import numpy as np 12 | 13 | 14 | class TensorboardLogger: 15 | def __init__(self, args: Namespace, setting: str) -> None: 16 | from torch.utils.tensorboard import SummaryWriter 17 | 18 | self.settings = [setting] 19 | if setting == 'class-il': 20 | self.settings.append('task-il') 21 | self.loggers = {} 22 | self.name = args.model 23 | for a_setting in self.settings: 24 | self.loggers[a_setting] = SummaryWriter( 25 | os.path.join(base_path(), 'tensorboard_runs', a_setting, self.name)) 26 | config_text = ', '.join( 27 | ["%s=%s" % (name, getattr(args, name)) for name in args.__dir__() 28 | if not name.startswith('_')]) 29 | for a_logger in self.loggers.values(): 30 | a_logger.add_text('config', config_text) 31 | 32 | def get_name(self) -> str: 33 | """ 34 | :return: the name of the model 35 | """ 36 | return self.name 37 | 38 | def log_accuracy(self, all_accs: np.ndarray, all_mean_accs: np.ndarray, 39 | args: Namespace, task_number: int) -> None: 40 | """ 41 | Logs the current accuracy value for each task. 42 | :param all_accs: the accuracies (class-il, task-il) for each task 43 | :param all_mean_accs: the mean accuracies for (class-il, task-il) 44 | :param args: the arguments of the run 45 | :param task_number: the task index 46 | """ 47 | mean_acc_common, mean_acc_task_il = all_mean_accs 48 | for setting, a_logger in self.loggers.items(): 49 | mean_acc = mean_acc_task_il\ 50 | if setting == 'task-il' else mean_acc_common 51 | index = 1 if setting == 'task-il' else 0 52 | accs = [all_accs[index][kk] for kk in range(len(all_accs[0]))] 53 | for kk, acc in enumerate(accs): 54 | a_logger.add_scalar('acc_task%02d' % (kk + 1), acc, 55 | task_number * args.n_epochs) 56 | a_logger.add_scalar('acc_mean', mean_acc, task_number * args.n_epochs) 57 | 58 | def log_loss(self, loss: float, args: Namespace, epoch: int, 59 | task_number: int, iteration: int) -> None: 60 | """ 61 | Logs the loss value at each iteration. 62 | :param loss: the loss value 63 | :param args: the arguments of the run 64 | :param epoch: the epoch index 65 | :param task_number: the task index 66 | :param iteration: the current iteration 67 | """ 68 | for a_logger in self.loggers.values(): 69 | a_logger.add_scalar('loss', loss, task_number * args.n_epochs + epoch) 70 | 71 | def log_loss_gcl(self, loss: float, iteration: int) -> None: 72 | """ 73 | Logs the loss value at each iteration. 74 | :param loss: the loss value 75 | :param iteration: the current iteration 76 | """ 77 | for a_logger in self.loggers.values(): 78 | a_logger.add_scalar('loss', loss, iteration) 79 | 80 | def close(self) -> None: 81 | """ 82 | At the end of the execution, closes the logger. 83 | """ 84 | for a_logger in self.loggers.values(): 85 | a_logger.close() 86 | -------------------------------------------------------------------------------- /models/ewc_on.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.functional as F 9 | from utils.args import * 10 | from models.utils.continual_model import ContinualModel 11 | import numpy as np 12 | from copy import deepcopy 13 | 14 | def get_parser() -> ArgumentParser: 15 | parser = ArgumentParser(description='Continual learning via' 16 | ' online EWC.') 17 | add_management_args(parser) 18 | add_experiment_args(parser) 19 | parser.add_argument('--e_lambda', type=float, required=True, 20 | help='lambda weight for EWC') 21 | parser.add_argument('--gamma', type=float, required=True, 22 | help='gamma parameter for EWC online') 23 | 24 | return parser 25 | 26 | 27 | class EwcOn(ContinualModel): 28 | NAME = 'ewc_on' 29 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il'] 30 | 31 | def __init__(self, backbone, loss, args, transform): 32 | super(EwcOn, self).__init__(backbone, loss, args, transform) 33 | 34 | self.logsoft = nn.LogSoftmax(dim=1) 35 | self.checkpoint = None 36 | self.fish = None 37 | self.grad1, self.var1 = [],[] 38 | self.iteration = 0 39 | self.p,self.p_last = 0,0 40 | self.distance = [] 41 | self.grad=[] 42 | self.t =0 43 | self.task_id = -1 44 | def penalty(self): 45 | if self.checkpoint is None: 46 | return torch.tensor(0.0).to(self.device) 47 | else: 48 | penalty = (self.fish * ((self.net.get_params() - self.checkpoint) ** 2)).sum() 49 | return penalty 50 | 51 | def begin_task(self, dataset): 52 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 53 | 54 | 55 | def end_task(self, dataset): 56 | fish = torch.zeros_like(self.net.get_params()) 57 | for j, data in enumerate(dataset.train_loader): 58 | inputs, labels, _ = data 59 | inputs, labels = inputs.to(self.device), torch.tensor(labels.to(self.device),dtype=torch.long) 60 | for ex, lab in zip(inputs, labels): 61 | self.opt.zero_grad() 62 | output = self.net(ex.unsqueeze(0)) 63 | loss = - F.nll_loss(self.logsoft(output), lab.unsqueeze(0), 64 | reduction='none') 65 | exp_cond_prob = torch.mean(torch.exp(loss.detach().clone())) 66 | loss = torch.mean(loss) 67 | loss.backward() 68 | fish += exp_cond_prob * self.net.get_grads() ** 2 69 | 70 | fish /= (len(dataset.train_loader) * self.args.batch_size) 71 | 72 | if self.fish is None: 73 | self.fish = fish 74 | else: 75 | self.fish *= self.args.gamma 76 | self.fish += fish 77 | 78 | 79 | def observe(self, inputs, labels, not_aug_inputs, t): 80 | real_batch_size = inputs.shape[0] 81 | perm = torch.randperm(real_batch_size) 82 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 83 | not_aug_inputs = not_aug_inputs[perm] 84 | 85 | self.opt.zero_grad() 86 | outputs = self.net(inputs) 87 | penalty = self.penalty() 88 | loss = self.loss(outputs, labels) + self.args.e_lambda * penalty 89 | assert not torch.isnan(loss) 90 | loss.backward() 91 | 92 | return loss.item() 93 | 94 | 95 | -------------------------------------------------------------------------------- /models/fdr.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from utils.buffer import Buffer 7 | from utils.args import * 8 | from models.utils.continual_model import ContinualModel 9 | import torch 10 | 11 | 12 | def get_parser() -> ArgumentParser: 13 | parser = ArgumentParser(description='Continual learning via' 14 | ' Dark Experience Replay.') 15 | add_management_args(parser) 16 | add_experiment_args(parser) 17 | add_rehearsal_args(parser) 18 | parser.add_argument('--alpha', type=float, required=True, 19 | help='Penalty weight.') 20 | return parser 21 | 22 | 23 | class Fdr(ContinualModel): 24 | NAME = 'fdr' 25 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 26 | 27 | def __init__(self, backbone, loss, args, transform): 28 | super(Fdr, self).__init__(backbone, loss, args, transform) 29 | self.buffer = Buffer(self.args.buffer_size, self.device) 30 | self.current_task = 0 31 | self.i = 0 32 | self.soft = torch.nn.Softmax(dim=1) 33 | self.logsoft = torch.nn.LogSoftmax(dim=1) 34 | 35 | def end_task(self, dataset): 36 | self.current_task += 1 37 | examples_per_task = self.args.buffer_size // self.current_task 38 | 39 | if self.current_task > 1: 40 | buf_x, buf_log, buf_tl = self.buffer.get_all_data() 41 | self.buffer.empty() 42 | 43 | for ttl in buf_tl.unique(): 44 | idx = (buf_tl == ttl) 45 | ex, log, tasklab = buf_x[idx], buf_log[idx], buf_tl[idx] 46 | first = min(ex.shape[0], examples_per_task) 47 | self.buffer.add_data( 48 | examples=ex[:first], 49 | logits=log[:first], 50 | task_labels=tasklab[:first] 51 | ) 52 | counter = 0 53 | with torch.no_grad(): 54 | for i, data in enumerate(dataset.train_loader): 55 | inputs, labels, not_aug_inputs = data 56 | inputs = inputs.to(self.device) 57 | not_aug_inputs = not_aug_inputs.to(self.device) 58 | outputs = self.net(inputs) 59 | if examples_per_task - counter < 0: 60 | break 61 | self.buffer.add_data(examples=not_aug_inputs[:(examples_per_task - counter)], 62 | logits=outputs.data[:(examples_per_task - counter)], 63 | task_labels=(torch.ones(self.args.batch_size) * 64 | (self.current_task - 1))[:(examples_per_task - counter)]) 65 | counter += self.args.batch_size 66 | 67 | def observe(self, inputs, labels, not_aug_inputs): 68 | self.i += 1 69 | 70 | self.opt.zero_grad() 71 | outputs = self.net(inputs) 72 | loss = self.loss(outputs, labels) 73 | loss.backward() 74 | self.opt.step() 75 | if not self.buffer.is_empty(): 76 | self.opt.zero_grad() 77 | buf_inputs, buf_logits, _ = self.buffer.get_data(self.args.minibatch_size, transform=self.transform) 78 | buf_outputs = self.net(buf_inputs) 79 | loss = torch.norm(self.soft(buf_outputs) - self.soft(buf_logits), 2, 1).mean() 80 | assert not torch.isnan(loss) 81 | loss.backward() 82 | self.opt.step() 83 | 84 | return loss.item() 85 | -------------------------------------------------------------------------------- /datasets/seq_mnist.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torchvision.datasets import MNIST 7 | import torchvision.transforms as transforms 8 | from torch.utils.data import DataLoader 9 | from backbone.MNISTMLP import MNISTMLP 10 | import torch.nn.functional as F 11 | from utils.conf import base_path 12 | from PIL import Image 13 | import numpy as np 14 | from datasets.utils.validation import get_train_val 15 | from datasets.utils.continual_dataset import ContinualDataset, store_masked_loaders 16 | from typing import Tuple 17 | 18 | 19 | class MyMNIST(MNIST): 20 | """ 21 | Overrides the MNIST dataset to change the getitem function. 22 | """ 23 | def __init__(self, root, train=True, transform=None, 24 | target_transform=None, download=False) -> None: 25 | self.not_aug_transform = transforms.ToTensor() 26 | super(MyMNIST, self).__init__(root, train, 27 | transform, target_transform, download) 28 | 29 | def __getitem__(self, index: int) -> Tuple[type(Image), int, type(Image)]: 30 | """ 31 | Gets the requested element from the dataset. 32 | :param index: index of the element to be returned 33 | :returns: tuple: (image, target) where target is index of the target class. 34 | """ 35 | img, target = self.data[index], self.targets[index] 36 | 37 | # doing this so that it is consistent with all other datasets 38 | # to return a PIL Image 39 | img = Image.fromarray(img.numpy(), mode='L') 40 | original_img = self.not_aug_transform(img.copy()) 41 | 42 | if self.transform is not None: 43 | img = self.transform(img) 44 | 45 | if self.target_transform is not None: 46 | target = self.target_transform(target) 47 | 48 | if hasattr(self, 'logits'): 49 | return img, target, original_img, self.logits[index] 50 | 51 | return img, target, original_img 52 | 53 | 54 | class SequentialMNIST(ContinualDataset): 55 | 56 | NAME = 'seq-mnist' 57 | SETTING = 'class-il' 58 | N_CLASSES_PER_TASK = 2 59 | N_TASKS = 5 60 | TRANSFORM = None 61 | 62 | def get_data_loaders(self): 63 | transform = transforms.ToTensor() 64 | train_dataset = MyMNIST(base_path() + 'MNIST', 65 | train=True, download=True, transform=transform) 66 | if self.args.validation: 67 | train_dataset, test_dataset = get_train_val(train_dataset, 68 | transform, self.NAME) 69 | else: 70 | test_dataset = MNIST(base_path() + 'MNIST', 71 | train=False, download=True, transform=transform) 72 | 73 | train, test = store_masked_loaders(train_dataset, test_dataset, self) 74 | return train, test 75 | 76 | @staticmethod 77 | def get_backbone(): 78 | return MNISTMLP(28 * 28, SequentialMNIST.N_TASKS 79 | * SequentialMNIST.N_CLASSES_PER_TASK) 80 | 81 | @staticmethod 82 | def get_transform(): 83 | return None 84 | 85 | @staticmethod 86 | def get_loss(): 87 | return F.cross_entropy 88 | 89 | @staticmethod 90 | def get_normalization_transform(): 91 | return None 92 | 93 | @staticmethod 94 | def get_denormalization_transform(): 95 | return None 96 | 97 | @staticmethod 98 | def get_scheduler(model, args): 99 | return None -------------------------------------------------------------------------------- /models/er_obc.py: -------------------------------------------------------------------------------- 1 | 2 | from functools import reduce 3 | import torch 4 | from utils.buffer import Buffer 5 | from utils.ring_buffer import RingBuffer 6 | 7 | from utils.args import * 8 | from models.utils.continual_model import ContinualModel 9 | from collections import OrderedDict 10 | import torch.nn.functional as F 11 | import numpy as np 12 | import os 13 | np.random.seed(0) 14 | def get_parser() -> ArgumentParser: 15 | parser = ArgumentParser(description='Continual learning via' 16 | ' Experience Replay.') 17 | add_management_args(parser) 18 | add_experiment_args(parser) 19 | add_rehearsal_args(parser) 20 | parser.add_argument('--grad_clip_norm', type=float, help='learning rate', default=1.0) 21 | return parser 22 | 23 | class ErOBC(ContinualModel): 24 | NAME = 'er_obc' 25 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 26 | 27 | def __init__(self, backbone, loss, args, transform): 28 | super(ErOBC, self).__init__(backbone, loss, args, transform) 29 | self.buffer = Buffer(self.args.buffer_size, self.device) 30 | self.iter = 0 31 | 32 | def begin_task(self, dataset): 33 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 34 | self.N_TASKS = dataset.N_TASKS 35 | self.Total_classes = self.N_CLASSES_PER_TASK*self.N_TASKS 36 | 37 | 38 | def end_task(self, dataset): 39 | dataset.task_id = dataset.i//dataset.N_CLASSES_PER_TASK 40 | 41 | def compute_offsets(self, task): 42 | # mapping from classes [1-100] to their idx within a task 43 | offset1 = task * self.N_CLASSES_PER_TASK 44 | offset2 = (task + 1) * self.N_CLASSES_PER_TASK 45 | # print('offset1', offset1) 46 | return int(offset1), int(offset2) 47 | 48 | def take_multitask_loss(self, bt, logits, y, task_id): 49 | loss = 0.0 50 | for i, ti in enumerate(bt): 51 | if i < 10: 52 | offset1, offset2 = self.compute_offsets(ti) 53 | offset2 = 10 54 | else: 55 | _, offset2 = self.compute_offsets(task_id) 56 | # task simiarity exp2 57 | offset1 = 0 58 | loss += self.loss(logits[i, offset1:offset2].unsqueeze(0), y[i].unsqueeze(0)-offset1) 59 | return loss/len(bt) 60 | 61 | 62 | def observe(self, inputs, labels, not_aug_inputs, task_id): 63 | real_batch_size = inputs.shape[0] 64 | perm = torch.randperm(real_batch_size) 65 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 66 | not_aug_inputs = not_aug_inputs[perm] 67 | 68 | self.opt.zero_grad() 69 | outputs = self.net(inputs, mode = 'surrogage') 70 | loss = self.loss(outputs, labels) 71 | if not self.buffer.is_empty(): 72 | buf_inputs, buf_labels, buf_id = self.buffer.get_data( 73 | self.args.minibatch_size, transform=self.transform) 74 | loss += self.loss(self.net(buf_inputs), buf_labels) 75 | else: 76 | buf_id = task_id*torch.ones_like(labels) 77 | loss.backward() 78 | self.opt.step() 79 | self.opt.zero_grad() 80 | 81 | # update the balance classifier 82 | self.opt1.zero_grad() 83 | if not self.buffer.is_empty(): 84 | buf_inputs, buf_labels, buf_id = self.buffer.get_data( 85 | 50, transform=self.transform) 86 | outputs = self.net(buf_inputs) 87 | loss = self.loss(outputs,buf_labels) 88 | loss.backward() 89 | self.opt1.step() 90 | self.opt1.zero_grad() 91 | 92 | self.buffer.add_data(examples=not_aug_inputs, 93 | labels=labels, 94 | task_labels=task_id*torch.ones_like(labels)) 95 | 96 | return loss.item() 97 | 98 | 99 | -------------------------------------------------------------------------------- /datasets/perm_mnist_maml.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torchvision.datasets import MNIST 7 | import torchvision.transforms as transforms 8 | from datasets.transforms.permutation import Permutation 9 | from torch.utils.data import DataLoader 10 | from backbone.MNISTMLP_MAML import MNISTMLP_MAML 11 | import torch.nn.functional as F 12 | from utils.conf import base_path 13 | from PIL import Image 14 | from datasets.utils.validation import get_train_val 15 | from typing import Tuple 16 | from datasets.utils.continual_dataset import ContinualDataset 17 | 18 | 19 | 20 | def store_mnist_loaders(transform, setting): 21 | train_dataset = MyMNIST(base_path() + 'MNIST', 22 | train=True, download=True, transform=transform) 23 | if setting.args.validation: 24 | train_dataset, test_dataset = get_train_val(train_dataset, 25 | transform, setting.NAME) 26 | else: 27 | test_dataset = MNIST(base_path() + 'MNIST', 28 | train=False, download=True, transform=transform) 29 | 30 | train_loader = DataLoader(train_dataset, 31 | batch_size=setting.args.batch_size, shuffle=True) 32 | test_loader = DataLoader(test_dataset, 33 | batch_size=setting.args.batch_size, shuffle=False) 34 | setting.test_loaders.append(test_loader) 35 | setting.train_loader = train_loader 36 | 37 | return train_loader, test_loader 38 | 39 | 40 | class MyMNIST(MNIST): 41 | """ 42 | Overrides the MNIST dataset to change the getitem function. 43 | """ 44 | 45 | def __init__(self, root, train=True, transform=None, 46 | target_transform=None, download=False) -> None: 47 | super(MyMNIST, self).__init__(root, train, transform, 48 | target_transform, download) 49 | 50 | def __getitem__(self, index: int) -> Tuple[type(Image), int, type(Image)]: 51 | """ 52 | Gets the requested element from the dataset. 53 | :param index: index of the element to be returned 54 | :returns: tuple: (image, target) where target is index of the target class. 55 | """ 56 | img, target = self.data[index], int(self.targets[index]) 57 | 58 | # doing this so that it is consistent with all other datasets 59 | # to return a PIL Image 60 | img = Image.fromarray(img.numpy(), mode='L') 61 | 62 | if self.transform is not None: 63 | img = self.transform(img) 64 | 65 | if self.target_transform is not None: 66 | target = self.target_transform(target) 67 | 68 | return img, target, img 69 | 70 | 71 | class PermutedMNIST_MAML(ContinualDataset): 72 | NAME = 'perm-mnist-maml' 73 | SETTING = 'domain-il' 74 | N_CLASSES_PER_TASK = 10 75 | N_TASKS = 20 76 | 77 | def get_data_loaders(self): 78 | transform = transforms.Compose((transforms.ToTensor(), Permutation())) 79 | train, test = store_mnist_loaders(transform, self) 80 | return train, test 81 | 82 | @staticmethod 83 | def get_backbone(): 84 | return MNISTMLP_MAML(28 * 28, PermutedMNIST_MAML.N_CLASSES_PER_TASK) 85 | 86 | @staticmethod 87 | def get_transform(): 88 | return None 89 | 90 | @staticmethod 91 | def get_normalization_transform(): 92 | return None 93 | 94 | @staticmethod 95 | def get_denormalization_transform(): 96 | return None 97 | 98 | @staticmethod 99 | def get_loss(): 100 | return F.cross_entropy 101 | 102 | @staticmethod 103 | def get_scheduler(model, args): 104 | return None 105 | -------------------------------------------------------------------------------- /models/gdumb.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from utils.args import * 7 | from models.utils.continual_model import ContinualModel 8 | from torch.optim import SGD, lr_scheduler 9 | import math 10 | from utils.buffer import Buffer 11 | import torch 12 | from utils.augmentations import cutmix_data 13 | import numpy as np 14 | from utils.status import progress_bar 15 | 16 | def get_parser() -> ArgumentParser: 17 | parser = ArgumentParser(description='Continual Learning via' 18 | ' Progressive Neural Networks.') 19 | add_management_args(parser) 20 | add_rehearsal_args(parser) 21 | parser.add_argument('--maxlr', type=float, default=5e-2, 22 | help='Penalty weight.') 23 | parser.add_argument('--minlr', type=float, default=5e-4, 24 | help='Penalty weight.') 25 | parser.add_argument('--fitting_epochs', type=int, default=256, 26 | help='Penalty weight.') 27 | parser.add_argument('--cutmix_alpha', type=float, default=None, 28 | help='Penalty weight.') 29 | add_experiment_args(parser) 30 | return parser 31 | 32 | def fit_buffer(self, epochs): 33 | for epoch in range(epochs): 34 | 35 | optimizer = SGD(self.net.parameters(), lr=self.args.maxlr, momentum=self.args.optim_mom, weight_decay=self.args.optim_wd, nesterov=self.args.optim_nesterov) 36 | scheduler = lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=1, T_mult=2, eta_min=self.args.minlr) 37 | 38 | if epoch <= 0: # Warm start of 1 epoch 39 | for param_group in optimizer.param_groups: 40 | param_group['lr'] = self.args.maxlr * 0.1 41 | elif epoch == 1: # Then set to maxlr 42 | for param_group in optimizer.param_groups: 43 | param_group['lr'] = self.args.maxlr 44 | else: 45 | scheduler.step() 46 | 47 | all_inputs, all_labels = self.buffer.get_data( 48 | len(self.buffer.examples), transform=self.transform) 49 | 50 | while len(all_inputs): 51 | optimizer.zero_grad() 52 | buf_inputs, buf_labels = all_inputs[:self.args.batch_size], all_labels[:self.args.batch_size] 53 | all_inputs, all_labels = all_inputs[self.args.batch_size:], all_labels[self.args.batch_size:] 54 | 55 | if self.args.cutmix_alpha is not None: 56 | inputs, labels_a, labels_b, lam = cutmix_data(x=buf_inputs.cpu(), y=buf_labels.cpu(), alpha=self.args.cutmix_alpha) 57 | buf_inputs = inputs.to(self.device) 58 | buf_labels_a = labels_a.to(self.device) 59 | buf_labels_b = labels_b.to(self.device) 60 | buf_outputs = self.net(buf_inputs) 61 | loss = lam * self.loss(buf_outputs, buf_labels_a) + (1 - lam) * self.loss(buf_outputs, buf_labels_b) 62 | else: 63 | buf_outputs = self.net(buf_inputs) 64 | loss = self.loss(buf_outputs, buf_labels) 65 | 66 | loss.backward() 67 | optimizer.step() 68 | progress_bar(epoch, epochs, 1, 'G', loss.item()) 69 | 70 | class GDumb(ContinualModel): 71 | NAME = 'gdumb' 72 | COMPATIBILITY = ['class-il', 'task-il'] 73 | 74 | def __init__(self, backbone, loss, args, transform): 75 | super(GDumb, self).__init__(backbone, loss, args, transform) 76 | self.buffer = Buffer(self.args.buffer_size, self.device) 77 | self.task = 0 78 | 79 | def observe(self, inputs, labels, not_aug_inputs): 80 | self.buffer.add_data(examples=not_aug_inputs, 81 | labels=labels) 82 | return 0 83 | 84 | def end_task(self, dataset): 85 | # new model 86 | self.task += 1 87 | if not (self.task == dataset.N_TASKS): 88 | return 89 | self.net = dataset.get_backbone().to(self.device) 90 | fit_buffer(self, self.args.fitting_epochs) -------------------------------------------------------------------------------- /utils/simclrloss.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Yonglong Tian (yonglong@mit.edu) 3 | Date: May 07, 2020 4 | Source: https://github.com/HobbitLong/SupContrast/blob/master/losses.py 5 | """ 6 | from __future__ import print_function 7 | 8 | import torch 9 | import torch.nn as nn 10 | 11 | 12 | class SupConLoss(nn.Module): 13 | """Supervised Contrastive Learning: https://arxiv.org/pdf/2004.11362.pdf. 14 | It also supports the unsupervised contrastive loss in SimCLR""" 15 | def __init__(self, temperature=0.07, contrast_mode='all', 16 | base_temperature=0.07, reduction='mean'): 17 | super(SupConLoss, self).__init__() 18 | self.temperature = temperature 19 | self.contrast_mode = contrast_mode 20 | self.base_temperature = base_temperature 21 | self.reduction = reduction 22 | 23 | def forward(self, features, labels=None, mask=None): 24 | """Compute loss for model. If both `labels` and `mask` are None, 25 | it degenerates to SimCLR unsupervised loss: 26 | https://arxiv.org/pdf/2002.05709.pdf 27 | Args: 28 | features: hidden vector of shape [bsz, n_views, ...]. 29 | labels: ground truth of shape [bsz]. 30 | mask: contrastive mask of shape [bsz, bsz], mask_{i,j}=1 if sample j 31 | has the same class as sample i. Can be asymmetric. 32 | Returns: 33 | A loss scalar. 34 | """ 35 | device = features.device 36 | 37 | if len(features.shape) < 3: 38 | raise ValueError('`features` needs to be [bsz, n_views, ...],' 39 | 'at least 3 dimensions are required') 40 | if len(features.shape) > 3: 41 | features = features.view(features.shape[0], features.shape[1], -1) 42 | 43 | batch_size = features.shape[0] 44 | if labels is not None and mask is not None: 45 | raise ValueError('Cannot define both `labels` and `mask`') 46 | elif labels is None and mask is None: 47 | mask = torch.eye(batch_size, dtype=torch.float32).to(device) 48 | elif labels is not None: 49 | labels = labels.contiguous().view(-1, 1) 50 | if labels.shape[0] != batch_size: 51 | raise ValueError('Num of labels does not match num of features') 52 | mask = torch.eq(labels, labels.T).float().to(device) 53 | else: 54 | mask = mask.float().to(device) 55 | 56 | contrast_count = features.shape[1] 57 | contrast_feature = torch.cat(torch.unbind(features, dim=1), dim=0) 58 | if self.contrast_mode == 'one': 59 | anchor_feature = features[:, 0] 60 | anchor_count = 1 61 | elif self.contrast_mode == 'all': 62 | anchor_feature = contrast_feature 63 | anchor_count = contrast_count 64 | else: 65 | raise ValueError('Unknown mode: {}'.format(self.contrast_mode)) 66 | 67 | # compute logits 68 | anchor_dot_contrast = torch.div( 69 | torch.matmul(anchor_feature, contrast_feature.T), 70 | self.temperature) 71 | # for numerical stability 72 | logits_max, _ = torch.max(anchor_dot_contrast, dim=1, keepdim=True) 73 | logits = anchor_dot_contrast - logits_max.detach() 74 | 75 | # tile mask 76 | mask = mask.repeat(anchor_count, contrast_count) 77 | # mask-out self-contrast cases 78 | logits_mask = torch.scatter( 79 | torch.ones_like(mask), 80 | 1, 81 | torch.arange(batch_size * anchor_count).view(-1, 1).to(device), 82 | 0 83 | ) 84 | mask = mask * logits_mask 85 | 86 | # compute log_prob 87 | exp_logits = torch.exp(logits) * logits_mask 88 | log_prob = logits - torch.log(exp_logits.sum(1, keepdim=True)) 89 | 90 | # compute mean of log-likelihood over positive 91 | mean_log_prob_pos = (mask * log_prob).sum(1) / mask.sum(1) 92 | 93 | # loss 94 | loss = - (self.temperature / self.base_temperature) * mean_log_prob_pos 95 | loss = loss.view(anchor_count, batch_size).mean(0) 96 | 97 | return loss.mean() if self.reduction == 'mean' else loss.sum() -------------------------------------------------------------------------------- /models/lwf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | from datasets import get_dataset 8 | from torch.optim import SGD 9 | from utils.args import * 10 | from models.utils.continual_model import ContinualModel 11 | 12 | 13 | def get_parser() -> ArgumentParser: 14 | parser = ArgumentParser(description='Continual learning via' 15 | ' Learning without Forgetting.') 16 | add_management_args(parser) 17 | add_experiment_args(parser) 18 | parser.add_argument('--alpha', type=float, default=0.5, 19 | help='Penalty weight.') 20 | parser.add_argument('--softmax_temp', type=float, default=2, 21 | help='Temperature of the softmax function.') 22 | return parser 23 | 24 | 25 | def smooth(logits, temp, dim): 26 | log = logits ** (1 / temp) 27 | return log / torch.sum(log, dim).unsqueeze(1) 28 | 29 | 30 | def modified_kl_div(old, new): 31 | return -torch.mean(torch.sum(old * torch.log(new), 1)) 32 | 33 | 34 | class Lwf(ContinualModel): 35 | NAME = 'lwf' 36 | COMPATIBILITY = ['class-il', 'task-il'] 37 | 38 | def __init__(self, backbone, loss, args, transform): 39 | super(Lwf, self).__init__(backbone, loss, args, transform) 40 | self.old_net = None 41 | self.soft = torch.nn.Softmax(dim=1) 42 | self.logsoft = torch.nn.LogSoftmax(dim=1) 43 | self.dataset = get_dataset(args) 44 | self.current_task = 0 45 | self.cpt = get_dataset(args).N_CLASSES_PER_TASK 46 | nc = get_dataset(args).N_TASKS * self.cpt 47 | self.eye = torch.tril(torch.ones((nc, nc))).bool().to(self.device) 48 | 49 | def begin_task(self, dataset): 50 | self.net.eval() 51 | if self.current_task > 0: 52 | # warm-up 53 | opt = SGD(self.net.classifier.parameters(), lr=self.args.lr) 54 | for epoch in range(self.args.n_epochs): 55 | for i, data in enumerate(dataset.train_loader): 56 | inputs, labels, not_aug_inputs = data 57 | inputs, labels = inputs.to(self.device), labels.to(self.device) 58 | opt.zero_grad() 59 | with torch.no_grad(): 60 | feats = self.net(inputs, returnt='features') 61 | mask = self.eye[(self.current_task + 1) * self.cpt - 1] ^ self.eye[self.current_task * self.cpt - 1] 62 | outputs = self.net.classifier(feats)[:, mask] 63 | loss = self.loss(outputs, labels - self.current_task * self.cpt) 64 | loss.backward() 65 | opt.step() 66 | 67 | logits = [] 68 | with torch.no_grad(): 69 | for i in range(0, dataset.train_loader.dataset.data.shape[0], self.args.batch_size): 70 | inputs = torch.stack([dataset.train_loader.dataset.__getitem__(j)[2] 71 | for j in range(i, min(i + self.args.batch_size, 72 | len(dataset.train_loader.dataset)))]) 73 | log = self.net(inputs.to(self.device)).cpu() 74 | logits.append(log) 75 | setattr(dataset.train_loader.dataset, 'logits', torch.cat(logits)) 76 | self.net.train() 77 | 78 | self.current_task += 1 79 | 80 | def observe(self, inputs, labels, not_aug_inputs, t,logits=None): 81 | self.opt.zero_grad() 82 | outputs = self.net(inputs) 83 | 84 | mask = self.eye[self.current_task * self.cpt - 1] 85 | loss = self.loss(outputs[:, mask], labels) 86 | if logits is not None: 87 | mask = self.eye[(self.current_task - 1) * self.cpt - 1] 88 | loss += self.args.alpha * modified_kl_div(smooth(self.soft(logits[:, mask]).to(self.device), 2, 1), 89 | smooth(self.soft(outputs[:, mask]), 2, 1)) 90 | 91 | loss.backward() 92 | self.opt.step() 93 | 94 | return loss.item() 95 | -------------------------------------------------------------------------------- /utils/augmentations.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn.functional as F 8 | import numpy as np 9 | 10 | def rand_bbox(size, lam): 11 | W = size[2] 12 | H = size[3] 13 | cut_rat = np.sqrt(1. - lam) 14 | cut_w = np.int(W * cut_rat) 15 | cut_h = np.int(H * cut_rat) 16 | 17 | # uniform 18 | cx = np.random.randint(W) 19 | cy = np.random.randint(H) 20 | 21 | bbx1 = np.clip(cx - cut_w // 2, 0, W) 22 | bby1 = np.clip(cy - cut_h // 2, 0, H) 23 | bbx2 = np.clip(cx + cut_w // 2, 0, W) 24 | bby2 = np.clip(cy + cut_h // 2, 0, H) 25 | 26 | return bbx1, bby1, bbx2, bby2 27 | 28 | def cutmix_data(x, y, alpha=1.0, cutmix_prob=0.5): 29 | assert(alpha > 0) 30 | # generate mixed sample 31 | lam = np.random.beta(alpha, alpha) 32 | 33 | batch_size = x.size()[0] 34 | index = torch.randperm(batch_size) 35 | 36 | if torch.cuda.is_available(): 37 | index = index.cuda() 38 | 39 | y_a, y_b = y, y[index] 40 | bbx1, bby1, bbx2, bby2 = rand_bbox(x.size(), lam) 41 | x[:, :, bbx1:bbx2, bby1:bby2] = x[index, :, bbx1:bbx2, bby1:bby2] 42 | 43 | # adjust lambda to exactly match pixel ratio 44 | lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (x.size()[-1] * x.size()[-2])) 45 | return x, y_a, y_b, lam 46 | 47 | def normalize(x, mean, std): 48 | assert len(x.shape) == 4 49 | return (x - torch.tensor(mean).unsqueeze(0).unsqueeze(2).unsqueeze(3).to(x.device)) \ 50 | / torch.tensor(std).unsqueeze(0).unsqueeze(2).unsqueeze(3).to(x.device) 51 | 52 | def random_flip(x): 53 | assert len(x.shape) == 4 54 | mask = torch.rand(x.shape[0]) < 0.5 55 | x[mask] = x[mask].flip(3) 56 | return x 57 | 58 | def random_grayscale(x, prob=0.2): 59 | assert len(x.shape) == 4 60 | mask = torch.rand(x.shape[0]) < prob 61 | x[mask] = (x[mask] * torch.tensor([[0.299,0.587,0.114]]).unsqueeze(2).unsqueeze(2).to(x.device)).sum(1, keepdim=True).repeat_interleave(3, 1) 62 | return x 63 | 64 | def random_crop(x, padding): 65 | assert len(x.shape) == 4 66 | crop_x = torch.randint(-padding, padding, size=(x.shape[0],)) 67 | crop_y = torch.randint(-padding, padding, size=(x.shape[0],)) 68 | 69 | crop_x_start, crop_y_start = crop_x + padding, crop_y + padding 70 | crop_x_end, crop_y_end = crop_x_start + x.shape[-1], crop_y_start + x.shape[-2] 71 | 72 | oboe = F.pad(x, (padding, padding, padding, padding)) 73 | mask_x = torch.arange(x.shape[-1] + padding * 2).repeat(x.shape[0], x.shape[-1] + padding * 2, 1) 74 | mask_y = mask_x.transpose(1,2) 75 | mask_x = ((mask_x >= crop_x_start.unsqueeze(1).unsqueeze(2)) & (mask_x < crop_x_end.unsqueeze(1).unsqueeze(2))) 76 | mask_y = ((mask_y >= crop_y_start.unsqueeze(1).unsqueeze(2)) & (mask_y < crop_y_end.unsqueeze(1).unsqueeze(2))) 77 | return oboe[mask_x.unsqueeze(1).repeat(1,x.shape[1],1,1) * mask_y.unsqueeze(1).repeat(1,x.shape[1],1,1)].reshape(x.shape[0], 3, x.shape[2], x.shape[3]) 78 | 79 | class soft_aug(): 80 | 81 | def __init__(self, mean, std): 82 | self.mean = mean 83 | self.std = std 84 | 85 | def __call__(self, x): 86 | return normalize( 87 | random_flip( 88 | random_crop(x, 4) 89 | ), 90 | self.mean, self.std) 91 | class strong_aug(): 92 | 93 | def __init__(self, size, mean, std): 94 | from torchvision import transforms 95 | self.transform = transforms.Compose([ 96 | transforms.ToPILImage(), 97 | transforms.RandomResizedCrop(size=size, scale=(0.2, 1.)), 98 | transforms.RandomApply([ 99 | transforms.ColorJitter(0.4, 0.4, 0.4, 0.1) 100 | ], p=0.8), 101 | transforms.ToTensor() 102 | ]) 103 | self.mean = mean 104 | self.std = std 105 | 106 | def __call__(self, x): 107 | flip = random_flip(x) 108 | return normalize(random_grayscale( 109 | torch.stack( 110 | [self.transform(a) for a in flip] 111 | )), self.mean, self.std) 112 | -------------------------------------------------------------------------------- /backbone/MNISTMLP_PNN.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.functional as F 9 | from backbone import MammothBackbone, xavier, num_flat_features 10 | from backbone.utils.modules import ListModule, AlphaModule 11 | from typing import List 12 | 13 | 14 | class MNISTMLP_PNN(MammothBackbone): 15 | """ 16 | Network composed of two hidden layers, each containing 100 ReLU activations. 17 | Designed for the MNIST dataset, equipped with lateral connection. 18 | """ 19 | 20 | def __init__(self, input_size: int, output_size: int, 21 | old_cols: List[AlphaModule] = None) -> None: 22 | """ 23 | Instantiates the layers of the network. 24 | :param input_size: the size of the input data 25 | :param output_size: the size of the output 26 | :param old_cols: a list of all the old columns 27 | """ 28 | super(MNISTMLP_PNN, self).__init__() 29 | 30 | if old_cols is None: 31 | old_cols = [] 32 | 33 | self.old_cols = [] 34 | 35 | self.input_size = input_size 36 | self.output_size = output_size 37 | 38 | self.fc1 = nn.Linear(self.input_size, 100) 39 | self.fc2 = nn.Linear(100, 100) 40 | 41 | self.classifier = nn.Linear(100, self.output_size) 42 | if len(old_cols) > 0: 43 | self.old_fc1s = ListModule() 44 | self.old_fc2s = ListModule() 45 | self.base_1 = nn.Sequential( 46 | nn.Linear(100 * len(old_cols), 100), 47 | nn.ReLU(), 48 | nn.Linear(100, 100, bias=False) 49 | ) 50 | self.base_2 = nn.Sequential( 51 | nn.Linear(100 * len(old_cols), 100), 52 | nn.ReLU(), 53 | nn.Linear(100, self.output_size, bias=False) 54 | ) 55 | 56 | self.adaptor1 = nn.Sequential(AlphaModule(100 * len(old_cols)), 57 | self.base_1) 58 | self.adaptor2 = nn.Sequential(AlphaModule(100 * len(old_cols)), 59 | self.base_2) 60 | 61 | for old_col in old_cols: 62 | self.old_fc1s.append( 63 | nn.Sequential(nn.Linear(self.input_size, 100), nn.ReLU())) 64 | self.old_fc2s.append( 65 | nn.Sequential(nn.Linear(100, 100), nn.ReLU())) 66 | self.old_fc1s[-1][0].load_state_dict(old_col.fc1.state_dict()) 67 | self.old_fc2s[-1][0].load_state_dict(old_col.fc2.state_dict()) 68 | 69 | self.reset_parameters() 70 | 71 | def reset_parameters(self) -> None: 72 | """ 73 | Calls the Xavier parameter initialization function. 74 | """ 75 | self.fc1.apply(xavier) 76 | self.fc2.apply(xavier) 77 | self.classifier.apply(xavier) 78 | if len(self.old_cols) > 0: 79 | self.adaptor1.apply(xavier) 80 | self.adaptor2.apply(xavier) 81 | 82 | def forward(self, x: torch.Tensor, returnt='out') -> torch.Tensor: 83 | """ 84 | Compute a forward pass. 85 | :param x: input tensor (batch_size, input_size) 86 | :return: output tensor (output_size) 87 | """ 88 | x = x.view(-1, num_flat_features(x)) 89 | if len(self.old_cols) > 0: 90 | with torch.no_grad(): 91 | fc1_kb = [old(x) for old in self.old_fc1s] 92 | fc2_kb = [old(fc1_kb[i]) for i, old in enumerate(self.old_fc2s)] 93 | x = F.relu(self.fc1(x)) 94 | 95 | y = self.adaptor1(torch.cat(fc1_kb, 1)) 96 | x = F.relu(self.fc2(x) + y) 97 | 98 | y = self.adaptor2(torch.cat(fc2_kb, 1)) 99 | x = self.classifier(x) + y 100 | else: 101 | x = F.relu(self.fc1(x)) 102 | x = F.relu(self.fc2(x)) 103 | x = self.classifier(x) 104 | if returnt == 'out': 105 | return x 106 | 107 | raise NotImplementedError("Unknown return type") -------------------------------------------------------------------------------- /models/derpp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from utils.buffer import Buffer 7 | from torch.nn import functional as F 8 | from models.utils.continual_model import ContinualModel 9 | from utils.args import * 10 | import torch 11 | import numpy as np 12 | np.random.seed(0) 13 | import torch.nn as nn 14 | import os 15 | 16 | 17 | def get_parser() -> ArgumentParser: 18 | parser = ArgumentParser(description='Continual learning via' 19 | ' Dark Experience Replay++.') 20 | add_management_args(parser) 21 | add_experiment_args(parser) 22 | add_rehearsal_args(parser) 23 | parser.add_argument('--alpha', type=float, required=True, 24 | help='Penalty weight.') 25 | parser.add_argument('--beta', type=float, required=True, 26 | help='Penalty weight.') 27 | return parser 28 | 29 | def label_smooth(label, n_class=3, alpha=0.1): 30 | ''' 31 | label: true label 32 | n_class: # of class 33 | alpha: smooth factor 34 | ''' 35 | k = alpha / (n_class - 1) 36 | temp = torch.full((label.shape[0], n_class), k) 37 | temp = temp.scatter_(1, label.unsqueeze(1), (1-alpha)) 38 | return temp 39 | 40 | class LabelSmoothingLoss(nn.Module): 41 | def __init__(self, classes, smoothing=0.0, dim=-1): 42 | super(LabelSmoothingLoss, self).__init__() 43 | self.confidence = 1.0 - smoothing 44 | self.smoothing = smoothing 45 | self.cls = classes 46 | self.dim = dim 47 | self.loss = nn.KLDivLoss() 48 | 49 | def forward(self, pred, target): 50 | pred = pred.log_softmax(dim = self.dim) 51 | with torch.no_grad(): 52 | true_dist = torch.zeros_like(pred) 53 | true_dist.fill_(self.smoothing/(self.cls-1)) 54 | true_dist.scatter_(1, target.data.unsqueeze(1),self.confidence) 55 | loss = torch.mean(torch.sum(-true_dist*pred, dim=self.dim)) 56 | loss = self.loss(pred, true_dist) 57 | return loss 58 | 59 | 60 | class Derpp(ContinualModel): 61 | NAME = 'derpp' 62 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 63 | 64 | def __init__(self, backbone, loss, args, transform): 65 | super(Derpp, self).__init__(backbone, loss, args, transform) 66 | 67 | self.buffer = Buffer(self.args.buffer_size, self.device) 68 | self.label_smooth_loss = LabelSmoothingLoss(classes=10, smoothing=0.05) 69 | 70 | 71 | def begin_task(self, dataset): 72 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 73 | 74 | 75 | def end_task(self, dataset): 76 | dataset.task_id = dataset.i//dataset.N_CLASSES_PER_TASK 77 | 78 | 79 | 80 | 81 | def observe(self, inputs, labels, not_aug_inputs,t, test=False): 82 | # M = 10 83 | real_batch_size = inputs.shape[0] 84 | perm = torch.randperm(real_batch_size) 85 | inputs, labels = inputs[perm], torch.tensor(labels[perm],dtype=torch.long) 86 | not_aug_inputs = not_aug_inputs[perm] 87 | 88 | self.opt.zero_grad() 89 | outputs = self.net(inputs) 90 | loss = self.loss(outputs,labels) 91 | 92 | if not self.buffer.is_empty(): 93 | buf_inputs, buf_labels, buf_logits, buf_task = self.buffer.get_data( 94 | self.args.minibatch_size, transform=self.transform) 95 | buf_outputs = self.net(buf_inputs) 96 | loss += self.args.alpha * F.mse_loss(buf_outputs, buf_logits) 97 | 98 | 99 | '——— ++ part: Add the label supervision to avoid the logits bias when task index suddenly change. (In this situation, ' \ 100 | 'the logits are more incline to the previous class)' 101 | buf_inputs, buf_labels, buf_logits, buf_task = self.buffer.get_data( 102 | self.args.minibatch_size, transform=self.transform) 103 | buf_outputs = self.net(buf_inputs) 104 | loss += self.args.beta * self.loss(buf_outputs, buf_labels) 105 | 106 | loss.backward() 107 | self.opt.step() 108 | 109 | self.buffer.add_data(examples=not_aug_inputs, 110 | labels=labels, 111 | logits=outputs.data, 112 | task_labels=t*torch.ones_like(labels)) 113 | return loss.item() 114 | -------------------------------------------------------------------------------- /models/joint.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torch.optim import SGD 7 | 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | from datasets.utils.validation import ValidationDataset 11 | from utils.status import progress_bar 12 | import torch 13 | import numpy as np 14 | import math 15 | from torchvision import transforms 16 | 17 | 18 | def get_parser() -> ArgumentParser: 19 | parser = ArgumentParser(description='Joint training: a strong, simple baseline.') 20 | add_management_args(parser) 21 | add_experiment_args(parser) 22 | return parser 23 | 24 | 25 | class Joint(ContinualModel): 26 | NAME = 'joint' 27 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il'] 28 | 29 | def __init__(self, backbone, loss, args, transform): 30 | super(Joint, self).__init__(backbone, loss, args, transform) 31 | self.old_data = [] 32 | self.old_labels = [] 33 | self.current_task = 0 34 | 35 | def end_task(self, dataset): 36 | if dataset.SETTING != 'domain-il': 37 | self.old_data.append(dataset.train_loader.dataset.data) 38 | self.old_labels.append(torch.tensor(dataset.train_loader.dataset.targets)) 39 | self.current_task += 1 40 | 41 | # # for non-incremental joint training 42 | if len(dataset.test_loaders) != dataset.N_TASKS: return 43 | 44 | # reinit network 45 | self.net = dataset.get_backbone() 46 | self.net.to(self.device) 47 | self.net.train() 48 | self.opt = SGD(self.net.parameters(), lr=self.args.lr) 49 | 50 | # prepare dataloader 51 | all_data, all_labels = None, None 52 | for i in range(len(self.old_data)): 53 | if all_data is None: 54 | all_data = self.old_data[i] 55 | all_labels = self.old_labels[i] 56 | else: 57 | all_data = np.concatenate([all_data, self.old_data[i]]) 58 | all_labels = np.concatenate([all_labels, self.old_labels[i]]) 59 | 60 | transform = dataset.TRANSFORM if dataset.TRANSFORM is not None else transforms.ToTensor() 61 | temp_dataset = ValidationDataset(all_data, all_labels, transform=transform) 62 | loader = torch.utils.data.DataLoader(temp_dataset, batch_size=self.args.batch_size, shuffle=True) 63 | 64 | # train 65 | for e in range(self.args.n_epochs): 66 | for i, batch in enumerate(loader): 67 | inputs, labels = batch 68 | inputs, labels = inputs.to(self.device), labels.to(self.device) 69 | 70 | self.opt.zero_grad() 71 | outputs = self.net(inputs) 72 | loss = self.loss(outputs, labels.long()) 73 | loss.backward() 74 | self.opt.step() 75 | progress_bar(i, len(loader), e, 'J', loss.item()) 76 | else: 77 | self.old_data.append(dataset.train_loader) 78 | # train 79 | if len(dataset.test_loaders) != dataset.N_TASKS: return 80 | 81 | all_inputs = [] 82 | all_labels = [] 83 | for source in self.old_data: 84 | for x, l, _ in source: 85 | all_inputs.append(x) 86 | all_labels.append(l) 87 | all_inputs = torch.cat(all_inputs) 88 | all_labels = torch.cat(all_labels) 89 | bs = self.args.batch_size 90 | scheduler = dataset.get_scheduler(self, self.args) 91 | 92 | for e in range(self.args.n_epochs): 93 | order = torch.randperm(len(all_inputs)) 94 | for i in range(int(math.ceil(len(all_inputs) / bs))): 95 | inputs = all_inputs[order][i * bs: (i+1) * bs] 96 | labels = all_labels[order][i * bs: (i+1) * bs] 97 | inputs, labels = inputs.to(self.device), labels.to(self.device) 98 | self.opt.zero_grad() 99 | outputs = self.net(inputs) 100 | loss = self.loss(outputs, labels.long()) 101 | loss.backward() 102 | self.opt.step() 103 | progress_bar(i, int(math.ceil(len(all_inputs) / bs)), e, 'J', loss.item()) 104 | 105 | if scheduler is not None: 106 | scheduler.step() 107 | 108 | def observe(self, inputs, labels, not_aug_inputs): 109 | return 0 110 | -------------------------------------------------------------------------------- /models/mer.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | from utils.buffer import Buffer 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | 11 | 12 | def get_parser() -> ArgumentParser: 13 | parser = ArgumentParser(description='Continual Learning via Meta-Experience Replay.') 14 | add_management_args(parser) 15 | add_experiment_args(parser) 16 | add_rehearsal_args(parser) 17 | 18 | # it worth to denote this operation 19 | # remove batch_size from parser 20 | for i in range(len(parser._actions)): 21 | if parser._actions[i].dest == 'batch_size': 22 | del parser._actions[i] 23 | break 24 | 25 | parser.add_argument('--beta', type=float, required=True, 26 | help='Within-batch update beta parameter.') 27 | parser.add_argument('--gamma', type=float, required=True, 28 | help='Across-batch update gamma parameter.') 29 | parser.add_argument('--batch_num', type=int, required=True, 30 | help='Number of batches extracted from the buffer.') 31 | 32 | return parser 33 | 34 | 35 | class Mer(ContinualModel): 36 | NAME = 'mer' 37 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 38 | 39 | def __init__(self, backbone, loss, args, transform): 40 | super(Mer, self).__init__(backbone, loss, args, transform) # continual_model.py 41 | self.buffer = Buffer(self.args.buffer_size, self.device) 42 | assert args.batch_size == 1, 'Mer only works with batch_size=1' 43 | 44 | def begin_task(self, dataset): 45 | self.N_CLASSES_PER_TASK = dataset.N_CLASSES_PER_TASK 46 | 47 | 48 | # Cifar10/100 version 49 | def draw_batches(self, inp, lab ,t): 50 | batches = [] 51 | # for i in range(self.args.batch_num): 52 | if not self.buffer.is_empty(): 53 | buf_inputs, buf_labels, buf_id = self.buffer.get_data(self.args.minibatch_size, transform=self.transform) 54 | inputs = torch.cat((buf_inputs, inp)) 55 | labels = torch.cat((buf_labels, torch.tensor([lab]).to(self.device))) 56 | task_id = torch.cat((buf_id, torch.tensor([t]).to(self.device))) 57 | batches.append((inputs.unsqueeze(0), labels.unsqueeze(0), task_id.unsqueeze(0))) 58 | else: 59 | batches.append((inp.unsqueeze(0), torch.tensor([lab]).unsqueeze(0).to(self.device), torch.tensor([lab]).unsqueeze(0).to(self.device))) 60 | return batches 61 | 62 | 63 | def compute_offsets(self, task): 64 | # mapping from classes [1-100] to their idx within a task 65 | offset1 = task * self.N_CLASSES_PER_TASK 66 | offset2 = (task + 1) * self.N_CLASSES_PER_TASK 67 | return int(offset1), int(offset2) 68 | 69 | def take_loss(self, t, logits, y): 70 | # compute loss on data from a single task 71 | offset1, offset2 = self.compute_offsets(t) 72 | offset1 = 0 73 | loss = self.loss(logits[:, offset1:offset2], y-offset1) 74 | return loss 75 | 76 | def observe(self, inputs, labels, not_aug_inputs, t): 77 | theta_A0 = self.net.get_params().data.clone() 78 | for i in range(self.args.batch_num): 79 | theta_Wi0 = self.net.get_params().data.clone() 80 | batches = self.draw_batches(inputs, labels, t) 81 | batch_inputs, batch_labels, batch_id = batches[i] 82 | batch_inputs = batch_inputs.squeeze(0) 83 | batch_labels = batch_labels.squeeze(0) 84 | batch_id = batch_id.squeeze(0) 85 | loss = 0.0 86 | for idx in range(len(batch_inputs)): 87 | self.opt.zero_grad() 88 | bx = batch_inputs[idx] 89 | if len(bx.shape) == 3: 90 | bx = bx.unsqueeze(0) 91 | by = batch_labels[idx].unsqueeze(0).long() 92 | bt = batch_id[idx] 93 | prediction = self.net(bx) 94 | loss = self.loss(prediction, by) 95 | loss.backward() 96 | self.opt.step() 97 | # within batch reptile meta-update 98 | new_params = theta_Wi0 + self.args.beta * (self.net.get_params() - theta_Wi0) 99 | self.net.set_params(new_params) 100 | # across batch reptile meta-update 101 | new_new_params = theta_A0 + self.args.gamma * (self.net.get_params() - theta_A0) 102 | self.net.set_params(new_new_params) 103 | self.buffer.add_data(examples=not_aug_inputs, labels=labels, task_labels=t*torch.ones_like(labels)) 104 | return loss.item() -------------------------------------------------------------------------------- /datasets/seq_cifar10_pccnn: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torchvision.datasets import CIFAR10 7 | import torchvision.transforms as transforms 8 | from backbone.pc_cnn import PC_CNN 9 | import torch.nn.functional as F 10 | from datasets.seq_tinyimagenet import base_path 11 | from PIL import Image 12 | from datasets.utils.validation import get_train_val 13 | from datasets.utils.continual_dataset import ContinualDataset, store_masked_loaders 14 | from typing import Tuple 15 | from datasets.transforms.denormalization import DeNormalize 16 | import torch 17 | 18 | 19 | torch.manual_seed(0) 20 | torch.cuda.manual_seed_all(0) 21 | class MyCIFAR10(CIFAR10): 22 | """ 23 | Overrides the CIFAR10 dataset to change the getitem function. 24 | """ 25 | def __init__(self, root, train=True, transform=None, 26 | target_transform=None, download=False) -> None: 27 | self.not_aug_transform = transforms.Compose([transforms.ToTensor()]) 28 | super(MyCIFAR10, self).__init__(root, train, transform, target_transform, download) 29 | 30 | def __getitem__(self, index: int) -> Tuple[type(Image), int, type(Image)]: 31 | """ 32 | Gets the requested element from the dataset. 33 | :param index: index of the element to be returned 34 | :returns: tuple: (image, target) where target is index of the target class. 35 | """ 36 | img, target = self.data[index], self.targets[index] 37 | 38 | # to return a PIL Image 39 | img = Image.fromarray(img, mode='RGB') 40 | original_img = img.copy() 41 | 42 | not_aug_img = self.not_aug_transform(original_img) 43 | 44 | if self.transform is not None: 45 | img = self.transform(img) 46 | 47 | if self.target_transform is not None: 48 | target = self.target_transform(target) 49 | 50 | if hasattr(self, 'logits'): 51 | return img, target, not_aug_img, self.logits[index] 52 | 53 | return img, target, not_aug_img 54 | 55 | 56 | class SequentialCIFAR10(ContinualDataset): 57 | # 继承自ContinualDataset, 因此有 self.i=0 58 | NAME = 'seq-cifar10-pccnn' 59 | SETTING = 'class-il' 60 | N_CLASSES_PER_TASK = 2 61 | N_TASKS = 5 62 | TRANSFORM = transforms.Compose( 63 | [transforms.RandomCrop(32, padding=4), 64 | transforms.RandomHorizontalFlip(), 65 | transforms.ToTensor(), 66 | transforms.Normalize((0.4914, 0.4822, 0.4465), 67 | (0.2470, 0.2435, 0.2615))]) 68 | 69 | def get_data_loaders(self): 70 | transform = self.TRANSFORM 71 | 72 | test_transform = transforms.Compose( 73 | [transforms.ToTensor(), self.get_normalization_transform()]) 74 | 75 | train_dataset = MyCIFAR10(base_path() + 'CIFAR10', train=True, 76 | download=True, transform=transform) 77 | if self.args.validation: 78 | train_dataset, test_dataset = get_train_val(train_dataset, 79 | test_transform, self.NAME) 80 | else: 81 | test_dataset = CIFAR10(base_path() + 'CIFAR10',train=False, 82 | download=True, transform=test_transform) 83 | 84 | train, test = store_masked_loaders(train_dataset, test_dataset, self) 85 | return train, test 86 | 87 | @staticmethod 88 | def get_transform(): 89 | transform = transforms.Compose( 90 | [transforms.ToPILImage(), SequentialCIFAR10.TRANSFORM]) 91 | return transform 92 | 93 | @staticmethod 94 | # def get_backbone(): 95 | # return resnet18(SequentialCIFAR10.N_CLASSES_PER_TASK 96 | # * SequentialCIFAR10.N_TASKS) 97 | def get_backbone(): 98 | return PC_CNN(SequentialCIFAR10.N_CLASSES_PER_TASK 99 | * SequentialCIFAR10.N_TASKS) 100 | 101 | @staticmethod 102 | def get_loss(): 103 | return F.cross_entropy 104 | 105 | @staticmethod 106 | def get_normalization_transform(): 107 | transform = transforms.Normalize((0.4914, 0.4822, 0.4465), 108 | (0.2470, 0.2435, 0.2615)) 109 | return transform 110 | 111 | @staticmethod 112 | def get_denormalization_transform(): 113 | transform = DeNormalize((0.4914, 0.4822, 0.4465), 114 | (0.2470, 0.2435, 0.2615)) 115 | return transform 116 | 117 | @staticmethod 118 | def get_scheduler(model, args): 119 | return None 120 | -------------------------------------------------------------------------------- /models/rpc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import torch 7 | from utils.buffer import Buffer 8 | from utils.args import * 9 | from models.utils.continual_model import ContinualModel 10 | from datasets import get_dataset 11 | 12 | def dsimplex(num_classes=10): 13 | def simplex_coordinates2(m): 14 | # add the credit 15 | import numpy as np 16 | 17 | x = np.zeros([m, m + 1]) 18 | for j in range(0, m): 19 | x[j, j] = 1.0 20 | 21 | a = (1.0 - np.sqrt(float(1 + m))) / float(m) 22 | 23 | for i in range(0, m): 24 | x[i, m] = a 25 | 26 | # Adjust coordinates so the centroid is at zero. 27 | c = np.zeros(m) 28 | for i in range(0, m): 29 | s = 0.0 30 | for j in range(0, m + 1): 31 | s = s + x[i, j] 32 | c[i] = s / float(m + 1) 33 | 34 | for j in range(0, m + 1): 35 | for i in range(0, m): 36 | x[i, j] = x[i, j] - c[i] 37 | 38 | # Scale so each column has norm 1. UNIT NORMALIZED 39 | s = 0.0 40 | for i in range(0, m): 41 | s = s + x[i, 0] ** 2 42 | s = np.sqrt(s) 43 | 44 | for j in range(0, m + 1): 45 | for i in range(0, m): 46 | x[i, j] = x[i, j] / s 47 | 48 | return x 49 | 50 | feat_dim = num_classes - 1 51 | ds = simplex_coordinates2(feat_dim) 52 | return ds 53 | 54 | def get_parser() -> ArgumentParser: 55 | parser = ArgumentParser(description='Continual learning via' 56 | ' Experience Replay.') 57 | add_management_args(parser) 58 | add_experiment_args(parser) 59 | add_rehearsal_args(parser) 60 | return parser 61 | 62 | 63 | class RPC(ContinualModel): 64 | NAME = 'rpc' 65 | COMPATIBILITY = ['class-il', 'task-il'] 66 | 67 | def __init__(self, backbone, loss, args, transform): 68 | super(RPC, self).__init__(backbone, loss, args, transform) 69 | self.buffer = Buffer(self.args.buffer_size, self.device) 70 | self.cpt = get_dataset(args).N_CLASSES_PER_TASK 71 | self.tasks = get_dataset(args).N_TASKS 72 | self.task=0 73 | self.rpchead = torch.from_numpy(dsimplex(self.cpt * self.tasks)).float().to(self.device) 74 | 75 | def forward(self, x): 76 | x = self.net(x)[:, :-1] 77 | x = x @ self.rpchead 78 | return x 79 | 80 | def end_task(self, dataset): 81 | # reduce coreset 82 | if self.task > 0: 83 | examples_per_class = self.args.buffer_size // ((self.task + 1) * self.cpt) 84 | buf_x, buf_lab = self.buffer.get_all_data() 85 | self.buffer.empty() 86 | for tl in buf_lab.unique(): 87 | idx = tl == buf_lab 88 | ex, lab = buf_x[idx], buf_lab[idx] 89 | first = min(ex.shape[0], examples_per_class) 90 | self.buffer.add_data( 91 | examples=ex[:first], 92 | labels = lab[:first] 93 | ) 94 | 95 | # add new task 96 | examples_last_task = self.buffer.buffer_size - self.buffer.num_seen_examples 97 | examples_per_class = examples_last_task // self.cpt 98 | ce = torch.tensor([examples_per_class] * self.cpt).int() 99 | ce[torch.randperm(self.cpt)[:examples_last_task - (examples_per_class * self.cpt)]] += 1 100 | 101 | with torch.no_grad(): 102 | for data in dataset.train_loader: 103 | _, labels, not_aug_inputs = data 104 | not_aug_inputs = not_aug_inputs.to(self.device) 105 | if all(ce == 0): 106 | break 107 | 108 | flags = torch.zeros(len(labels)).bool() 109 | for j in range(len(flags)): 110 | if ce[labels[j] % self.cpt] > 0: 111 | flags[j] = True 112 | ce[labels[j] % self.cpt] -= 1 113 | 114 | self.buffer.add_data(examples=not_aug_inputs[flags], 115 | labels=labels[flags]) 116 | self.task += 1 117 | 118 | def observe(self, inputs, labels, not_aug_inputs): 119 | self.opt.zero_grad() 120 | if not self.buffer.is_empty(): 121 | buf_inputs, buf_labels = self.buffer.get_data( 122 | self.args.minibatch_size, transform=self.transform) 123 | inputs = torch.cat((inputs, buf_inputs)) 124 | labels = torch.cat((labels, buf_labels)) 125 | 126 | outputs = self.net(inputs) 127 | losses = self.loss(outputs, labels, reduction='none') 128 | loss = losses.mean() 129 | 130 | loss.backward() 131 | self.opt.step() 132 | 133 | 134 | return loss.item() 135 | -------------------------------------------------------------------------------- /backbone/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import math 7 | import torch 8 | import torch.nn as nn 9 | 10 | 11 | def xavier(m: nn.Module) -> None: 12 | """ 13 | Applies Xavier initialization to linear modules. 14 | 15 | :param m: the module to be initialized 16 | 17 | Example:: 18 | >>> net = nn.Sequential(nn.Linear(10, 10), nn.ReLU()) 19 | >>> net.apply(xavier) 20 | """ 21 | if m.__class__.__name__ == 'Linear': 22 | fan_in = m.weight.data.size(1) 23 | fan_out = m.weight.data.size(0) 24 | std = 1.0 * math.sqrt(2.0 / (fan_in + fan_out)) 25 | a = math.sqrt(3.0) * std 26 | m.weight.data.uniform_(-a, a) 27 | if m.bias is not None: 28 | m.bias.data.fill_(0.0) 29 | 30 | 31 | def num_flat_features(x: torch.Tensor) -> int: 32 | """ 33 | Computes the total number of items except the first dimension. 34 | 35 | :param x: input tensor 36 | :return: number of item from the second dimension onward 37 | """ 38 | size = x.size()[1:] 39 | num_features = 1 40 | for ff in size: 41 | num_features *= ff 42 | return num_features 43 | 44 | class MammothBackbone(nn.Module): 45 | 46 | def __init__(self, **kwargs) -> None: 47 | super(MammothBackbone, self).__init__() 48 | 49 | def forward(self, x: torch.Tensor, returnt='out') -> torch.Tensor: 50 | raise NotImplementedError 51 | 52 | def features(self, x: torch.Tensor) -> torch.Tensor: 53 | return self.forward(x, returnt='features') 54 | 55 | def get_params(self) -> torch.Tensor: 56 | """ 57 | Returns all the parameters concatenated in a single tensor. 58 | :return: parameters tensor (??) 59 | """ 60 | params = [] 61 | for pp in list(self.parameters()): 62 | params.append(pp.view(-1)) 63 | return torch.cat(params) 64 | 65 | def set_params(self, new_params: torch.Tensor) -> None: 66 | """ 67 | Sets the parameters to a given value. 68 | :param new_params: concatenated values to be set (??) 69 | """ 70 | assert new_params.size() == self.get_params().size() 71 | progress = 0 72 | for pp in list(self.parameters()): 73 | cand_params = new_params[progress: progress + 74 | torch.tensor(pp.size()).prod()].view(pp.size()) 75 | progress += torch.tensor(pp.size()).prod() 76 | pp.data = cand_params 77 | 78 | def get_grads(self) -> torch.Tensor: 79 | """ 80 | Returns all the gradients concatenated in a single tensor. 81 | :return: gradients tensor (??) 82 | """ 83 | return torch.cat(self.get_grads_list()) 84 | 85 | def get_grads_list(self): 86 | """ 87 | Returns a list containing the gradients (a tensor for each layer). 88 | :return: gradients list 89 | """ 90 | grads = [] 91 | for pp in list(self.parameters()): 92 | grads.append(pp.grad.view(-1)) 93 | return grads 94 | 95 | 96 | class MammothBackbone_MAML(nn.Module): 97 | 98 | def __init__(self, **kwargs) -> None: 99 | super(MammothBackbone, self).__init__() 100 | 101 | def forward(self, x: torch.Tensor, returnt='out') -> torch.Tensor: 102 | raise NotImplementedError 103 | 104 | def features(self, x: torch.Tensor) -> torch.Tensor: 105 | return self.forward(x, returnt='features') 106 | 107 | def get_params(self) -> torch.Tensor: 108 | """ 109 | Returns all the parameters concatenated in a single tensor. 110 | :return: parameters tensor (??) 111 | """ 112 | params = [] 113 | for pp in list(self.parameters()): 114 | params.append(pp.view(-1)) 115 | return torch.cat(params) 116 | 117 | def set_params(self, new_params: torch.Tensor) -> None: 118 | """ 119 | Sets the parameters to a given value. 120 | :param new_params: concatenated values to be set (??) 121 | """ 122 | assert new_params.size() == self.get_params().size() 123 | progress = 0 124 | for pp in list(self.parameters()): 125 | cand_params = new_params[progress: progress + 126 | torch.tensor(pp.size()).prod()].view(pp.size()) 127 | progress += torch.tensor(pp.size()).prod() 128 | pp.data = cand_params 129 | 130 | def get_grads(self) -> torch.Tensor: 131 | """ 132 | Returns all the gradients concatenated in a single tensor. 133 | :return: gradients tensor (??) 134 | """ 135 | return torch.cat(self.get_grads_list()) 136 | 137 | def get_grads_list(self): 138 | """ 139 | Returns a list containing the gradients (a tensor for each layer). 140 | :return: gradients list 141 | """ 142 | grads = [] 143 | for pp in list(self.parameters()): 144 | grads.append(pp.grad.view(-1)) 145 | return grads -------------------------------------------------------------------------------- /datasets/seq_cifar10.py: -------------------------------------------------------------------------------- 1 | from random import seed 2 | import torchvision 3 | from torchvision.datasets import CIFAR10 4 | import torchvision.transforms as transforms 5 | from backbone.ResNet18_MAML import resnet18_maml 6 | from backbone.ResNet18 import resnet18 7 | from backbone.Pretrained_ResNet18 import ResNet18 8 | import PIL 9 | 10 | from backbone.pc_cnn import PC_CNN 11 | import torch.nn.functional as F 12 | from datasets.seq_tinyimagenet import base_path 13 | from PIL import Image 14 | from datasets.utils.validation import get_train_val 15 | from datasets.utils.continual_dataset import ContinualDataset, store_masked_loaders 16 | from typing import Tuple 17 | from datasets.transforms.denormalization import DeNormalize 18 | import torch 19 | import numpy as np 20 | import os 21 | from torchvision.datasets.utils import download_url, check_integrity 22 | import sys 23 | import pickle 24 | import copy 25 | import torchvision.transforms as transforms 26 | 27 | import torchvision.models as models 28 | import torch.nn as nn 29 | 30 | # torch.manual_seed(0) 31 | # torch.cuda.manual_seed_all(0) 32 | def uniform_mix_C(mixing_ratio, num_classes): 33 | ''' 34 | returns a linear interpolation of a uniform matrix and an identity matrix 35 | ''' 36 | return mixing_ratio * np.full((num_classes, num_classes), 1 / num_classes) + \ 37 | (1 - mixing_ratio) * np.eye(num_classes) 38 | class MyCIFAR10(CIFAR10): 39 | """ 40 | Overrides the CIFAR10 dataset to change the getitem function. 41 | """ 42 | def __init__(self, root, train=True, transform=None, 43 | target_transform=None, download=False) -> None: 44 | self.not_aug_transform = transforms.Compose([transforms.ToTensor()]) 45 | super(MyCIFAR10, self).__init__(root, train, transform, target_transform, download) 46 | 47 | def __getitem__(self, index: int) -> Tuple[type(Image), int, type(Image)]: 48 | """ 49 | Gets the requested element from the dataset. 50 | :param index: index of the element to be returned 51 | :returns: tuple: (image, target) where target is index of the target class. 52 | """ 53 | img, target = self.data[index], self.targets[index] 54 | # to return a PIL Image 55 | img = Image.fromarray(img, mode='RGB') 56 | original_img = img.copy() 57 | not_aug_img = self.not_aug_transform(original_img) 58 | 59 | if self.transform is not None: 60 | img = self.transform(img) 61 | 62 | if self.target_transform is not None: 63 | target = self.target_transform(target) 64 | 65 | if hasattr(self, 'logits'): 66 | return img, target, not_aug_img, self.logits[index] 67 | return img, target, not_aug_img 68 | 69 | class SequentialCIFAR10(ContinualDataset): 70 | # 继承自ContinualDataset, 因此有 self.i = 0 71 | NAME = 'seq-cifar10' 72 | SETTING = 'class-il' 73 | N_CLASSES_PER_TASK = 2 74 | N_TASKS = 5 75 | task_id = 0 76 | TRANSFORM = transforms.Compose( 77 | [transforms.RandomCrop(32, padding=4), 78 | transforms.RandomHorizontalFlip(), 79 | transforms.ToTensor(), 80 | transforms.Normalize((0.4914, 0.4822, 0.4465), 81 | (0.2470, 0.2435, 0.2615))]) 82 | 83 | 84 | def get_data_loaders(self, tag=False): 85 | transform = self.TRANSFORM 86 | test_transform = transforms.Compose( 87 | [transforms.ToTensor(), self.get_normalization_transform()]) 88 | 89 | train_dataset = MyCIFAR10(base_path() + 'CIFAR10', train=True, 90 | download=True, transform=transform) 91 | 92 | 93 | if self.args.validation: 94 | train_dataset, test_dataset = get_train_val(train_dataset, 95 | test_transform, self.NAME) 96 | else: 97 | test_dataset = CIFAR10(base_path() + 'CIFAR10',train=False, 98 | download=True, transform=test_transform) 99 | train, test = store_masked_loaders(train_dataset, test_dataset, self, tag) 100 | return train, test 101 | 102 | @staticmethod 103 | def get_transform(): 104 | transform = transforms.Compose( 105 | [transforms.ToPILImage(), SequentialCIFAR10.TRANSFORM]) 106 | return transform 107 | 108 | @staticmethod 109 | def get_backbone(eman1): 110 | return resnet18_maml(SequentialCIFAR10.N_CLASSES_PER_TASK 111 | * SequentialCIFAR10.N_TASKS, eman=eman1) #PC_CNN 112 | # def get_backbone(): 113 | # return PC_CNN(3*32*32,SequentialCIFAR10.N_CLASSES_PER_TASK * SequentialCIFAR10.N_TASKS) 114 | 115 | @staticmethod 116 | def get_loss(): 117 | return F.cross_entropy 118 | # return nn.CrossEntropyLoss(reduction='none') 119 | 120 | @staticmethod 121 | def get_normalization_transform(): 122 | transform = transforms.Normalize((0.4914, 0.4822, 0.4465), 123 | (0.2470, 0.2435, 0.2615)) 124 | return transform 125 | 126 | @staticmethod 127 | def get_denormalization_transform(): 128 | transform = DeNormalize((0.4914, 0.4822, 0.4465), 129 | (0.2470, 0.2435, 0.2615)) 130 | return transform 131 | 132 | @staticmethod 133 | def get_scheduler(model, args): 134 | return None 135 | -------------------------------------------------------------------------------- /datasets/utils/continual_dataset.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from abc import abstractmethod 7 | from argparse import Namespace 8 | from torch import nn as nn 9 | from torchvision.transforms import transforms 10 | from torch.utils.data import DataLoader 11 | from typing import Tuple 12 | from torchvision import datasets 13 | import numpy as np 14 | import torch.optim 15 | from copy import deepcopy 16 | 17 | class ContinualDataset: 18 | """ 19 | Continual learning evaluation setting. 20 | """ 21 | NAME = None 22 | SETTING = None 23 | N_CLASSES_PER_TASK = None 24 | N_TASKS = None 25 | TRANSFORM = None 26 | 27 | def __init__(self, args: Namespace) -> None: 28 | """ 29 | Initializes the train and test lists of dataloaders. 30 | :param args: the arguments which contains the hyperparameters 31 | """ 32 | self.train_loader = None 33 | self.test_loaders = [] 34 | self.i = 0 35 | self.args = args 36 | 37 | @abstractmethod 38 | def get_data_loaders(self) -> Tuple[DataLoader, DataLoader]: 39 | """ 40 | Creates and returns the training and test loaders for the current task. 41 | The current training loader and all test loaders are stored in self. 42 | :return: the current training and test loaders 43 | """ 44 | pass 45 | 46 | @staticmethod 47 | @abstractmethod 48 | def get_backbone() -> nn.Module: 49 | """ 50 | Returns the backbone to be used for to the current dataset. 51 | """ 52 | pass 53 | 54 | @staticmethod 55 | @abstractmethod 56 | def get_transform() -> transforms: 57 | """ 58 | Returns the transform to be used for to the current dataset. 59 | """ 60 | pass 61 | 62 | @staticmethod 63 | @abstractmethod 64 | def get_loss() -> nn.functional: 65 | """ 66 | Returns the loss to be used for to the current dataset. 67 | """ 68 | pass 69 | 70 | @staticmethod 71 | @abstractmethod 72 | def get_normalization_transform() -> transforms: 73 | """ 74 | Returns the transform used for normalizing the current dataset. 75 | """ 76 | pass 77 | 78 | @staticmethod 79 | @abstractmethod 80 | def get_denormalization_transform() -> transforms: 81 | """ 82 | Returns the transform used for denormalizing the current dataset. 83 | """ 84 | pass 85 | 86 | @staticmethod 87 | @abstractmethod 88 | def get_scheduler(model, args: Namespace) -> torch.optim.lr_scheduler: 89 | """ 90 | Returns the scheduler to be used for to the current dataset. 91 | """ 92 | pass 93 | 94 | @staticmethod 95 | def get_epochs(): 96 | pass 97 | 98 | @staticmethod 99 | def get_batch_size(): 100 | pass 101 | 102 | @staticmethod 103 | def get_minibatch_size(): 104 | pass 105 | 106 | def store_masked_loaders(train_dataset: datasets, test_dataset: datasets, 107 | setting: ContinualDataset, tag=False) -> Tuple[DataLoader, DataLoader]: 108 | """ 109 | Divides the dataset into tasks. 110 | :param train_dataset: train dataset 111 | :param test_dataset: test dataset 112 | :param setting: continual learning setting 113 | :return: train and test loaders 114 | """ 115 | # split the task according to the targets 116 | train_mask = np.logical_and(np.array(train_dataset.targets) >= setting.i, 117 | np.array(train_dataset.targets) < setting.i + setting.N_CLASSES_PER_TASK) 118 | 119 | test_mask = np.logical_and(np.array(test_dataset.targets) >= setting.i, 120 | np.array(test_dataset.targets) < setting.i + setting.N_CLASSES_PER_TASK) 121 | 122 | train_dataset.data = train_dataset.data[train_mask] 123 | test_dataset.data = test_dataset.data[test_mask] 124 | 125 | train_dataset.targets = np.array(train_dataset.targets)[train_mask] 126 | test_dataset.targets = np.array(test_dataset.targets)[test_mask] 127 | 128 | train_loader = DataLoader(train_dataset, 129 | batch_size=setting.args.batch_size, shuffle=True, num_workers=4, drop_last=True) 130 | test_loader = DataLoader(test_dataset, 131 | batch_size=setting.args.batch_size, shuffle=False, num_workers=4) 132 | setting.test_loaders.append(test_loader) 133 | setting.train_loader = train_loader 134 | 135 | setting.i += setting.N_CLASSES_PER_TASK # update the task split information 136 | return train_loader, test_loader 137 | 138 | 139 | def get_previous_train_loader(train_dataset: datasets, batch_size: int, 140 | setting: ContinualDataset) -> DataLoader: 141 | """ 142 | Creates a dataloader for the previous task. 143 | :param train_dataset: the entire training set 144 | :param batch_size: the desired batch size 145 | :param setting: the continual dataset at hand 146 | :return: a dataloader 147 | """ 148 | train_mask = np.logical_and(np.array(train_dataset.targets) >= 149 | setting.i - setting.N_CLASSES_PER_TASK, np.array(train_dataset.targets) 150 | < setting.i - setting.N_CLASSES_PER_TASK + setting.N_CLASSES_PER_TASK) 151 | 152 | train_dataset.data = train_dataset.data[train_mask] 153 | train_dataset.targets = np.array(train_dataset.targets)[train_mask] 154 | 155 | return DataLoader(train_dataset, batch_size=batch_size, shuffle=True) 156 | -------------------------------------------------------------------------------- /models/clser.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from utils.buffer import Buffer 3 | from utils.args import * 4 | from models.utils.continual_model import ContinualModel 5 | from copy import deepcopy 6 | from torch import nn 7 | from torch.nn import functional as F 8 | 9 | 10 | def get_parser() -> ArgumentParser: 11 | parser = ArgumentParser(description='Complementary Learning Systems Based Experience Replay') 12 | add_management_args(parser) 13 | add_experiment_args(parser) 14 | add_rehearsal_args(parser) 15 | # tinyimg 0.1,0.07,0.999,0.08,0.999 16 | # Consistency Regularization Weight 17 | parser.add_argument('--reg_weight', type=float, default= 0.15 ) #1 0.15 18 | 19 | # Stable Model parameters 20 | parser.add_argument('--stable_model_update_freq', type=float, default=0.8) #0.9 0.1 21 | parser.add_argument('--stable_model_alpha', type=float, default=0.99) 22 | 23 | # Plastic Model Parameters 24 | parser.add_argument('--plastic_model_update_freq', type=float, default=0.9) #0.9 0.3 25 | parser.add_argument('--plastic_model_alpha', type=float, default=0.99) 26 | 27 | return parser 28 | 29 | 30 | # ============================================================================= 31 | # Mean-ER 32 | # ============================================================================= 33 | class CLSER(ContinualModel): 34 | NAME = 'clser' 35 | COMPATIBILITY = ['class-il', 'domain-il', 'task-il', 'general-continual'] 36 | 37 | def __init__(self, backbone, loss, args, transform): 38 | super(CLSER, self).__init__(backbone, loss, args, transform) 39 | self.buffer = Buffer(self.args.buffer_size, self.device) 40 | # Initialize plastic and stable model 41 | self.plastic_model = deepcopy(self.net).to(self.device) 42 | self.stable_model = deepcopy(self.net).to(self.device) 43 | # set regularization weight 44 | self.reg_weight = args.reg_weight 45 | # set parameters for plastic model 46 | self.plastic_model_update_freq = args.plastic_model_update_freq 47 | self.plastic_model_alpha = args.plastic_model_alpha 48 | # set parameters for stable model 49 | self.stable_model_update_freq = args.stable_model_update_freq 50 | self.stable_model_alpha = args.stable_model_alpha 51 | 52 | self.consistency_loss = nn.MSELoss(reduction='none') 53 | self.current_task = 0 54 | self.global_step = 0 55 | 56 | def observe(self, inputs, labels, not_aug_inputs, t): 57 | 58 | real_batch_size = inputs.shape[0] 59 | 60 | self.opt.zero_grad() 61 | loss = 0 62 | 63 | if not self.buffer.is_empty(): 64 | 65 | buf_inputs, buf_labels = self.buffer.get_data( 66 | self.args.minibatch_size, transform=self.transform) 67 | 68 | stable_model_logits = self.stable_model(buf_inputs) 69 | plastic_model_logits = self.plastic_model(buf_inputs) 70 | 71 | stable_model_prob = F.softmax(stable_model_logits, 1) 72 | plastic_model_prob = F.softmax(plastic_model_logits, 1) 73 | 74 | label_mask = F.one_hot(buf_labels, num_classes=stable_model_logits.shape[-1]) > 0 75 | sel_idx = stable_model_prob[label_mask] > plastic_model_prob[label_mask] 76 | sel_idx = sel_idx.unsqueeze(1) 77 | 78 | ema_logits = torch.where( 79 | sel_idx, 80 | stable_model_logits, 81 | plastic_model_logits, 82 | ) 83 | 84 | l_cons = torch.mean(self.consistency_loss(self.net(buf_inputs), ema_logits.detach())) 85 | l_reg = self.args.reg_weight * l_cons 86 | loss += l_reg 87 | 88 | if hasattr(self, 'writer'): 89 | self.writer.add_scalar(f'Task {self.current_task}/l_cons', l_cons.item(), self.iteration) 90 | self.writer.add_scalar(f'Task {self.current_task}/l_reg', l_reg.item(), self.iteration) 91 | 92 | inputs = torch.cat((inputs, buf_inputs)) 93 | labels = torch.cat((labels, buf_labels)) 94 | 95 | # Log values 96 | if hasattr(self, 'writer'): 97 | self.writer.add_scalar(f'Task {self.current_task}/l_reg', l_reg.item(), self.iteration) 98 | 99 | outputs = self.net(inputs) 100 | ce_loss = self.loss(outputs, labels) 101 | loss += ce_loss 102 | 103 | # Log values 104 | if hasattr(self, 'writer'): 105 | self.writer.add_scalar(f'Task {self.current_task}/ce_loss', ce_loss.item(), self.iteration) 106 | self.writer.add_scalar(f'Task {self.current_task}/loss', loss.item(), self.iteration) 107 | 108 | loss.backward() 109 | self.opt.step() 110 | 111 | self.buffer.add_data( 112 | examples=not_aug_inputs, 113 | labels=labels[:real_batch_size], 114 | ) 115 | 116 | # Update the ema model 117 | self.global_step += 1 118 | if torch.rand(1) < self.plastic_model_update_freq: 119 | self.update_plastic_model_variables() 120 | 121 | if torch.rand(1) < self.stable_model_update_freq: 122 | self.update_stable_model_variables() 123 | 124 | return loss.item() 125 | 126 | def update_plastic_model_variables(self): 127 | alpha = min(1 - 1 / (self.global_step + 1), self.plastic_model_alpha) 128 | for ema_param, param in zip(self.plastic_model.parameters(), self.net.parameters()): 129 | ema_param.data.mul_(alpha).add_(1 - alpha, param.data) 130 | 131 | def update_stable_model_variables(self): 132 | alpha = min(1 - 1 / (self.global_step + 1), self.stable_model_alpha) 133 | for ema_param, param in zip(self.stable_model.parameters(), self.net.parameters()): 134 | ema_param.data.mul_(alpha).add_(1 - alpha, param.data) 135 | -------------------------------------------------------------------------------- /datasets/perm_mnist.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torchvision.datasets import MNIST 7 | import torchvision.transforms as transforms 8 | from datasets.transforms.permutation import Permutation 9 | from torch.utils.data import DataLoader 10 | from backbone.MNISTMLP import MNISTMLP 11 | import torch.nn.functional as F 12 | from utils.conf import base_path 13 | from PIL import Image 14 | from datasets.utils.validation import get_train_val 15 | from typing import Tuple 16 | from datasets.utils.continual_dataset import ContinualDataset 17 | from datasets.transforms.rotation import Rotation 18 | from datasets.transforms.rotation import FixedRotation 19 | 20 | import numpy as np 21 | ''' 22 | def store_mnist_loaders(transform, setting): 23 | train_dataset = MyMNIST(base_path() + 'MNIST', 24 | train=True, download=True, transform=transform) 25 | if setting.args.validation: 26 | train_dataset, test_dataset = get_train_val(train_dataset, 27 | transform, setting.NAME) 28 | else: 29 | test_dataset = MNIST(base_path() + 'MNIST', 30 | train=False, download=True, transform=transform) 31 | 32 | train_loader = DataLoader(train_dataset, 33 | batch_size=setting.args.batch_size, shuffle=True) 34 | test_loader = DataLoader(test_dataset, 35 | batch_size=setting.args.batch_size, shuffle=False) 36 | setting.test_loaders.append(test_loader) 37 | setting.train_loader = train_loader 38 | 39 | return train_loader, test_loader 40 | ''' 41 | 42 | def store_mnist_loaders(setting): 43 | # rot_degree = np.random.uniform(0, 100) 44 | # Rotation_list=[40,20,90,30,60,10,100,70,80,50] 45 | Rotation_list=[216, 252, 288, 0, 72, 36, 108, 324 , 144, 180] 46 | print('Rotation', Rotation_list[setting.i]) 47 | transform = transforms.Compose((Rotation(Rotation_list[setting.i]), transforms.ToTensor())) 48 | # transform = transforms.Compose((Rotation(10*setting.i), transforms.ToTensor())) 49 | # transform = transforms.Compose((FixedRotation(10*setting.i), transforms.ToTensor())) 50 | # print('setting.i', setting.i) 51 | train_dataset = MyMNIST(base_path() + 'MNIST', 52 | train=True, download=True, transform=transform) 53 | # if setting.args.validation: 54 | # train_dataset, test_dataset = get_train_val(train_dataset, 55 | # transform, setting.NAME) 56 | # else: 57 | # test_dataset = MNIST(base_path() + 'MNIST', 58 | # train=False, download=True, transform=Rotation(10*setting.i)) 59 | 60 | train_loader = DataLoader(train_dataset, 61 | batch_size=setting.args.batch_size, shuffle=True) 62 | setting.train_loader = train_loader 63 | setting.test_loaders = [] 64 | for i in range(setting.i+2): 65 | if i ==10: 66 | break 67 | transform = transforms.Compose((Rotation(Rotation_list[i]), transforms.ToTensor())) 68 | # transform = transforms.Compose((Rotation(10*i), transforms.ToTensor())) 69 | # transform = transforms.Compose((FixedRotation(10*setting.i), transforms.ToTensor())) 70 | test_dataset = MNIST(base_path() + 'MNIST', 71 | train=False, download=True, transform=transform) 72 | test_loader = DataLoader(test_dataset, 73 | batch_size=setting.args.batch_size, shuffle=False) 74 | setting.test_loaders.append(test_loader) 75 | 76 | setting.i +=1 77 | return train_loader, test_loader 78 | 79 | class MyMNIST(MNIST): 80 | """ 81 | Overrides the MNIST dataset to change the getitem function. 82 | """ 83 | def __init__(self, root, train=True, transform=None, 84 | target_transform=None, download=False) -> None: 85 | super(MyMNIST, self).__init__(root, train, transform, 86 | target_transform, download) 87 | 88 | def __getitem__(self, index: int) -> Tuple[type(Image), int, type(Image)]: 89 | """ 90 | Gets the requested element from the dataset. 91 | :param index: index of the element to be returned 92 | :returns: tuple: (image, target) where target is index of the target class. 93 | """ 94 | img, target = self.data[index], int(self.targets[index]) 95 | 96 | # doing this so that it is consistent with all other datasets 97 | # to return a PIL Image 98 | img = Image.fromarray(img.numpy(), mode='L') 99 | 100 | if self.transform is not None: 101 | img = self.transform(img) 102 | 103 | if self.target_transform is not None: 104 | target = self.target_transform(target) 105 | 106 | return img, target, img 107 | 108 | class PermutedMNIST(ContinualDataset): 109 | NAME = 'perm-mnist' 110 | SETTING = 'domain-il' 111 | N_CLASSES_PER_TASK = 10 112 | N_TASKS = 20 113 | def get_data_loaders(self): 114 | transform = transforms.Compose((transforms.ToTensor(), Permutation())) 115 | train, test = store_mnist_loaders(transform, self) 116 | return train, test 117 | 118 | @staticmethod 119 | def get_backbone(): 120 | return MNISTMLP(28 * 28, PermutedMNIST.N_CLASSES_PER_TASK) 121 | 122 | @staticmethod 123 | def get_transform(): 124 | return None 125 | 126 | @staticmethod 127 | def get_normalization_transform(): 128 | return None 129 | 130 | @staticmethod 131 | def get_denormalization_transform(): 132 | return None 133 | 134 | @staticmethod 135 | def get_loss(): 136 | return F.cross_entropy 137 | 138 | @staticmethod 139 | def get_scheduler(model, args): 140 | return None 141 | -------------------------------------------------------------------------------- /utils/loggers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2020-present, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Davide Abati, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | import csv 7 | import os 8 | import sys 9 | from typing import Dict, Any 10 | from utils.metrics import * 11 | from utils import create_if_not_exists 12 | from utils.conf import base_path 13 | import numpy as np 14 | useless_args = ['dataset', 'tensorboard', 'validation', 'model', 15 | 'csv_log', 'notes', 'load_best_args'] 16 | 17 | def print_mean_accuracy(mean_acc: np.ndarray, task_number: int, 18 | setting: str) -> None: 19 | """ 20 | Prints the mean accuracy on stderr. 21 | :param mean_acc: mean accuracy value 22 | :param task_number: task index 23 | :param setting: the setting of the benchmark 24 | """ 25 | if setting == 'domain-il': 26 | mean_acc, _ = mean_acc 27 | print('\nAccuracy for {} task(s): {} %'.format( 28 | task_number, round(mean_acc, 2)), file=sys.stderr) 29 | else: 30 | mean_acc_class_il, mean_acc_task_il = mean_acc 31 | print('\nAccuracy for {} task(s): \t [Class-IL]: {} %' 32 | ' \t [Task-IL]: {} %\n'.format(task_number, round( 33 | mean_acc_class_il, 2), round(mean_acc_task_il, 2)), file=sys.stderr) 34 | 35 | 36 | class CsvLogger: 37 | def __init__(self, setting_str: str, dataset_str: str, 38 | model_str: str) -> None: 39 | self.accs = [] 40 | if setting_str == 'class-il': 41 | self.accs_mask_classes = [] 42 | self.setting = setting_str 43 | self.dataset = dataset_str 44 | self.model = model_str 45 | self.fwt = None 46 | self.fwt_mask_classes = None 47 | self.bwt = None 48 | self.bwt_mask_classes = None 49 | self.forgetting = None 50 | self.forgetting_mask_classes = None 51 | 52 | def add_fwt(self, results, accs, results_mask_classes, accs_mask_classes): 53 | self.fwt = forward_transfer(results, accs) 54 | if self.setting == 'class-il': 55 | self.fwt_mask_classes = forward_transfer(results_mask_classes, accs_mask_classes) 56 | 57 | def add_bwt(self, results, results_mask_classes): 58 | self.bwt = backward_transfer(results) 59 | self.bwt_mask_classes = backward_transfer(results_mask_classes) 60 | 61 | def add_forgetting(self, results, results_mask_classes): 62 | self.forgetting = forgetting(results) 63 | self.forgetting_mask_classes = forgetting(results_mask_classes) 64 | 65 | def log(self, mean_acc: np.ndarray) -> None: 66 | """ 67 | Logs a mean accuracy value. 68 | :param mean_acc: mean accuracy value 69 | """ 70 | if self.setting == 'general-continual': 71 | self.accs.append(mean_acc) 72 | elif self.setting == 'domain-il': 73 | mean_acc, _ = mean_acc 74 | self.accs.append(mean_acc) 75 | else: 76 | mean_acc_class_il, mean_acc_task_il = mean_acc 77 | self.accs.append(mean_acc_class_il) 78 | self.accs_mask_classes.append(mean_acc_task_il) 79 | 80 | def write(self, args: Dict[str, Any]) -> None: 81 | """ 82 | writes out the logged value along with its arguments. 83 | :param args: the namespace of the current experiment 84 | """ 85 | for cc in useless_args: 86 | if cc in args: 87 | del args[cc] 88 | 89 | columns = list(args.keys()) 90 | 91 | new_cols = [] 92 | for i, acc in enumerate(self.accs): 93 | args['task' + str(i + 1)] = acc 94 | new_cols.append('task' + str(i + 1)) 95 | 96 | args['forward_transfer'] = self.fwt 97 | new_cols.append('forward_transfer') 98 | 99 | args['backward_transfer'] = self.bwt 100 | new_cols.append('backward_transfer') 101 | 102 | args['forgetting'] = self.forgetting 103 | new_cols.append('forgetting') 104 | 105 | columns = new_cols + columns 106 | 107 | create_if_not_exists(base_path() + "results/" + self.setting) 108 | create_if_not_exists(base_path() + "results/" + self.setting + 109 | "/" + self.dataset) 110 | create_if_not_exists(base_path() + "results/" + self.setting + 111 | "/" + self.dataset + "/" + self.model) 112 | 113 | write_headers = False 114 | path = base_path() + "results/" + self.setting + "/" + self.dataset\ 115 | + "/" + self.model + "/mean_accs.csv" 116 | if not os.path.exists(path): 117 | write_headers = True 118 | with open(path, 'a') as tmp: 119 | writer = csv.DictWriter(tmp, fieldnames=columns) 120 | if write_headers: 121 | writer.writeheader() 122 | writer.writerow(args) 123 | 124 | if self.setting == 'class-il': 125 | create_if_not_exists(base_path() + "results/task-il/" 126 | + self.dataset) 127 | create_if_not_exists(base_path() + "results/task-il/" 128 | + self.dataset + "/" + self.model) 129 | 130 | for i, acc in enumerate(self.accs_mask_classes): 131 | args['task' + str(i + 1)] = acc 132 | 133 | args['forward_transfer'] = self.fwt_mask_classes 134 | args['backward_transfer'] = self.bwt_mask_classes 135 | args['forgetting'] = self.forgetting_mask_classes 136 | 137 | write_headers = False 138 | path = base_path() + "results/task-il" + "/" + self.dataset + "/"\ 139 | + self.model + "/mean_accs.csv" 140 | if not os.path.exists(path): 141 | write_headers = True 142 | with open(path, 'a') as tmp: 143 | writer = csv.DictWriter(tmp, fieldnames=columns) 144 | if write_headers: 145 | writer.writeheader() 146 | writer.writerow(args) 147 | -------------------------------------------------------------------------------- /datasets/seq_cifar100.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-present, Lorenzo Bonicelli, Pietro Buzzega, Matteo Boschini, Angelo Porrello, Simone Calderara. 2 | # All rights reserved. 3 | # This source code is licensed under the license found in the 4 | # LICENSE file in the root directory of this source tree. 5 | 6 | from torchvision.datasets import CIFAR100 7 | import torchvision.transforms as transforms 8 | from backbone.ResNet18_MAML import resnet18_maml 9 | # from backbone.ResNet18 import resnet18 10 | from backbone.ResNet18_obc import resnet18 11 | # from backbone.Pretrained_ResNet18 import ResNet18 12 | from backbone.pc_cnn import PC_CNN 13 | import torch.nn.functional as F 14 | from utils.conf import base_path 15 | from PIL import Image 16 | from datasets.utils.validation import get_train_val 17 | from datasets.utils.continual_dataset import ContinualDataset, store_masked_loaders 18 | from typing import Tuple 19 | from datasets.transforms.denormalization import DeNormalize 20 | import torch.optim 21 | 22 | import torchvision.models as models 23 | import torch.nn as nn 24 | import PIL 25 | 26 | 27 | class TCIFAR100(CIFAR100): 28 | def __init__(self, root, train=True, transform=None, 29 | target_transform=None, download=False) -> None: 30 | self.root = root 31 | super(TCIFAR100, self).__init__(root, train, transform, target_transform, download=not self._check_integrity()) 32 | 33 | class MyCIFAR100(CIFAR100): 34 | """ 35 | Overrides the CIFAR100 dataset to change the getitem function. 36 | """ 37 | def __init__(self, root, train=True, transform=None, 38 | target_transform=None, download=False) -> None: 39 | self.not_aug_transform = transforms.Compose([transforms.ToTensor()]) 40 | self.root = root 41 | super(MyCIFAR100, self).__init__(root, train, transform, target_transform, not self._check_integrity()) 42 | 43 | def __getitem__(self, index: int) -> Tuple[type(Image), int, type(Image)]: 44 | """ 45 | Gets the requested element from the dataset. 46 | :param index: index of the element to be returned 47 | :returns: tuple: (image, target) where target is index of the target class. 48 | """ 49 | img, target = self.data[index], self.targets[index] 50 | 51 | # to return a PIL Image 52 | img = Image.fromarray(img, mode='RGB') 53 | original_img = img.copy() 54 | 55 | not_aug_img = self.not_aug_transform(original_img) 56 | # print('not_aug', not_aug_img.shape) 57 | if self.transform is not None: 58 | img = self.transform(img) 59 | 60 | if self.target_transform is not None: 61 | target = self.target_transform(target) 62 | 63 | if hasattr(self, 'logits'): 64 | return img, target, not_aug_img, self.logits[index] 65 | # print('img', img) 66 | 67 | return img, target, not_aug_img 68 | 69 | 70 | class SequentialCIFAR100(ContinualDataset): 71 | 72 | NAME = 'seq-cifar100' 73 | SETTING = 'class-il' 74 | N_CLASSES_PER_TASK = 10 75 | N_TASKS = 10 76 | TRANSFORM = transforms.Compose( 77 | [transforms.RandomCrop(32, padding=4), 78 | transforms.RandomHorizontalFlip(), 79 | transforms.ToTensor(), 80 | transforms.Normalize((0.5071, 0.4867, 0.4408), 81 | (0.2675, 0.2565, 0.2761))]) 82 | 83 | 84 | def get_examples_number(self): 85 | train_dataset = MyCIFAR100(base_path() + 'CIFAR10', train=True, 86 | download=True) 87 | return len(train_dataset.data) 88 | 89 | def get_data_loaders(self): 90 | transform = self.TRANSFORM 91 | 92 | test_transform = transforms.Compose( 93 | [transforms.ToTensor(), self.get_normalization_transform()]) 94 | 95 | 96 | train_dataset = MyCIFAR100(base_path() + 'CIFAR100', train=True, 97 | download=True, transform=transform) 98 | # download=True, transform=train_transform) 99 | if self.args.validation: 100 | train_dataset, test_dataset = get_train_val(train_dataset, 101 | test_transform, self.NAME) 102 | else: 103 | test_dataset = TCIFAR100(base_path() + 'CIFAR100',train=False, 104 | download=True, transform=test_transform) 105 | # download=True, transform=transform) 106 | 107 | train, test = store_masked_loaders(train_dataset, test_dataset, self) 108 | 109 | return train, test 110 | 111 | @staticmethod 112 | def get_transform(): 113 | transform = transforms.Compose( 114 | [transforms.ToPILImage(), SequentialCIFAR100.TRANSFORM]) 115 | return transform 116 | 117 | @staticmethod 118 | # def get_backbone(): 119 | # return resnet18(SequentialCIFAR100.N_CLASSES_PER_TASK 120 | # * SequentialCIFAR100.N_TASKS) 121 | def get_backbone(): 122 | return resnet18_maml(SequentialCIFAR100.N_CLASSES_PER_TASK 123 | * SequentialCIFAR100.N_TASKS) 124 | # def get_backbone(): 125 | # return PC_CNN(3*32*32,SequentialCIFAR100.N_CLASSES_PER_TASK * SequentialCIFAR100.N_TASKS) 126 | 127 | @staticmethod 128 | def get_loss(): 129 | return F.cross_entropy 130 | 131 | @staticmethod 132 | def get_normalization_transform(): 133 | transform = transforms.Normalize((0.5071, 0.4867, 0.4408), 134 | (0.2675, 0.2565, 0.2761)) 135 | return transform 136 | 137 | @staticmethod 138 | def get_denormalization_transform(): 139 | transform = DeNormalize((0.5071, 0.4867, 0.4408), 140 | (0.2675, 0.2565, 0.2761)) 141 | return transform 142 | 143 | @staticmethod 144 | def get_epochs(): 145 | return 50 146 | 147 | @staticmethod 148 | def get_batch_size(): 149 | return 32 150 | 151 | @staticmethod 152 | def get_minibatch_size(): 153 | return SequentialCIFAR100.get_batch_size() 154 | 155 | @staticmethod 156 | def get_scheduler(model, args) -> torch.optim.lr_scheduler: 157 | model.opt = torch.optim.SGD(model.net.parameters(), lr=args.lr, weight_decay=args.optim_wd, momentum=args.optim_mom) 158 | scheduler = torch.optim.lr_scheduler.MultiStepLR(model.opt, [35, 45], gamma=0.1)#, verbose=False) 159 | return scheduler 160 | --------------------------------------------------------------------------------