Sync from GitHub
Browse files- .github/workflows/sync-hf.yml +30 -0
- .gitignore +17 -0
- README.md +4 -1
- config/fly_cl_cifar100.yaml +93 -0
- config/fly_cl_cub200.yaml +101 -0
- config/zz_EWCDR/ewcdr-resnet18-cifar100-b10-10-10.yaml +86 -0
- config/zz_EWCDR/ewcdr-resnet18-cifar100-b20-20-5.yaml +86 -0
- config/zz_EWCDR/ewcdr-resnet18-cifar100-b40-3-21.yaml +86 -0
- config/zz_EWCDR/ewcdr-resnet18-cifar100-b5-5-20.yaml +86 -0
- config/zz_EWCDR/ewcdr-resnet18-cifar100-b50-10-6.yaml +86 -0
- config/zz_EWCDR/ewcdr-resnet18-cifar100-b50-5-11.yaml +86 -0
- config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b10-10-20.yaml +27 -0
- config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-10-11.yaml +27 -0
- config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-20-6.yaml +27 -0
- config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-5-21.yaml +27 -0
- config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b20-20-10.yaml +27 -0
- config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b40-40-5.yaml +27 -0
- config/zz_EWCDR/tinyimagenet_common.yaml +42 -0
- config/zz_IDER/er_cifar100_buf2000.yaml +76 -0
- config/zz_IDER/er_cifar100_buf500.yaml +76 -0
- config/zz_IDER/er_cifar10_buf200.yaml +74 -0
- config/zz_IDER/er_cifar10_buf500.yaml +74 -0
- config/zz_IDER/er_tinyimagenet_buf4000.yaml +76 -0
- config/zz_IDER/er_tinyimagenet_buf500.yaml +76 -0
- config/zz_IDER/ider_cifar100_buf2000.yaml +91 -0
- config/zz_IDER/ider_cifar100_buf500.yaml +91 -0
- config/zz_IDER/ider_cifar100_buf500_til.yaml +91 -0
- config/zz_IDER/ider_cifar10_buf200.yaml +89 -0
- config/zz_IDER/ider_cifar10_buf500.yaml +89 -0
- config/zz_IDER/ider_tinyimagenet_buf4000.yaml +91 -0
- config/zz_IDER/ider_tinyimagenet_buf500.yaml +91 -0
- config/zz_IDER/ider_tinyimagenet_buf500_paper.yaml +91 -0
- core/data/dataloader.py +12 -1
- core/data/dataset.py +89 -19
- core/model/__init__.py +5 -1
- core/model/backbone/__init__.py +2 -0
- core/model/backbone/resnet.py +6 -0
- core/model/backbone/resnet_id.py +165 -0
- core/model/backbone/timm_backbone.py +22 -0
- core/model/ewcdr.py +158 -0
- core/model/finetune.py +5 -0
- core/model/fly_cl.py +242 -0
- core/model/ider.py +328 -0
- core/model/paper_er.py +170 -0
- core/trainer.py +65 -21
- core/utils/utils.py +45 -5
- reproduce/ewcdr/README.md +160 -0
- reproduce/fly_cl/run_cifar100_ablations.py +135 -0
- reproduce/fly_cl/run_cub200_ablations.py +380 -0
- reproduce/ider/README.md +64 -0
.github/workflows/sync-hf.yml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
sync:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
|
| 13 |
+
steps:
|
| 14 |
+
|
| 15 |
+
- name: Checkout
|
| 16 |
+
uses: actions/checkout@v4
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
- name: Upload to Hugging Face
|
| 20 |
+
env:
|
| 21 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 22 |
+
run: |
|
| 23 |
+
|
| 24 |
+
pip install huggingface_hub
|
| 25 |
+
|
| 26 |
+
hf upload \
|
| 27 |
+
RL-MIND/LibContinual \
|
| 28 |
+
. \
|
| 29 |
+
--repo-type model \
|
| 30 |
+
--commit-message "Sync from GitHub"
|
.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.log
|
| 2 |
+
/log
|
| 3 |
+
/output/
|
| 4 |
+
.vscode
|
| 5 |
+
__pycache__
|
| 6 |
+
*.py[cod]
|
| 7 |
+
.venv-3.8/
|
| 8 |
+
.history
|
| 9 |
+
/data/
|
| 10 |
+
.DS_Store
|
| 11 |
+
|
| 12 |
+
# Local dependency snapshots kept for environment comparison.
|
| 13 |
+
/old_requirements.txt
|
| 14 |
+
/old_requirements.yaml
|
| 15 |
+
/IDER_EXPERIMENT_REPORT.md
|
| 16 |
+
/run.sh
|
| 17 |
+
/reproduce/ider/scripts/
|
README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
| 6 |
</div>
|
| 7 |
|
| 8 |
## Introduction
|
|
|
|
| 9 |
<div style="text-align: justify">
|
| 10 |
LibContinual is an open-source continual learning toolbox based on PyTorch. The framework currently supports PyTorch 1.13+ (compatibility with earlier versions not fully guaranteed) and provides comprehensive implementations of state-of-the-art continual learning algorithms.
|
| 11 |
</div>
|
|
@@ -27,6 +28,8 @@ LibContinual is an open-source continual learning toolbox based on PyTorch. The
|
|
| 27 |
+ [TRGP (ICLR 2022)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/trgp/README.md): Trust Region Gradient Projection for Continual Learning.
|
| 28 |
+ [API (CVPR 2023)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/api/README.md): Adaptive Plasticity Improvement for Continual Learning.
|
| 29 |
+ [RanPAC (NeurIPS 2023)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/ranpac/README.md): Random Projections and Pre-trained Models for Continual Learning.
|
|
|
|
|
|
|
| 30 |
<!-- + [PRAKA (ICCV 2023)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/praka/README.md): Prototype Reminiscence and Augmented Asymmetric Knowledge Aggregation for Non-Exemplar Class-Incremental Learning. -->
|
| 31 |
<!-- + [DER (CVPR 2021)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/der/README.md): Dynamically Expandable Representation for Class Incremental Learning. -->
|
| 32 |
|
|
@@ -40,7 +43,7 @@ LibContinual is an open-source continual learning toolbox based on PyTorch. The
|
|
| 40 |
+ [SD_LoRA (ICLR 2025)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/sd_lora/README.md): Scalable Decoupled Low-Rank Adaptation for Class Incremental Learning
|
| 41 |
+ [LoRA_Sub_DRS (CVPR 2025)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/lora_sub_drs/README.md): LoRA Subtraction for Drift-Resistant Space in Exemplar-Free Continual Learning
|
| 42 |
+ [CL-LoRA (CVPR 2025)](https://github.com/RL-VIG/LibContinual/tree/master/reproduce/cl_lora/README.md): Continual Low-Rank Adaptation for Rehearsal-Free Class-Incremental Learning
|
| 43 |
-
|
| 44 |
<!-- ## Quick Installation
|
| 45 |
|
| 46 |
Please refer to [`install.md`](https://libcontinual.readthedocs.io/en/latest/docs/install.html) <br>
|
|
|
|
| 6 |
</div>
|
| 7 |
|
| 8 |
## Introduction
|
| 9 |
+
[](https://arxiv.org/abs/2512.22029)
|
| 10 |
<div style="text-align: justify">
|
| 11 |
LibContinual is an open-source continual learning toolbox based on PyTorch. The framework currently supports PyTorch 1.13+ (compatibility with earlier versions not fully guaranteed) and provides comprehensive implementations of state-of-the-art continual learning algorithms.
|
| 12 |
</div>
|
|
|
|
| 28 |
+ [TRGP (ICLR 2022)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/trgp/README.md): Trust Region Gradient Projection for Continual Learning.
|
| 29 |
+ [API (CVPR 2023)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/api/README.md): Adaptive Plasticity Improvement for Continual Learning.
|
| 30 |
+ [RanPAC (NeurIPS 2023)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/ranpac/README.md): Random Projections and Pre-trained Models for Continual Learning.
|
| 31 |
+
+ [EWC-DR (CVPR 2026)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/ewcdr/README.md): Elastic Weight Consolidation Done Right for Continual Learning.
|
| 32 |
+
+ [IDER (ICLR 2026)](reproduce/ider/README.md): IDempotent Experience Replay for Reliable Continual Learning.
|
| 33 |
<!-- + [PRAKA (ICCV 2023)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/praka/README.md): Prototype Reminiscence and Augmented Asymmetric Knowledge Aggregation for Non-Exemplar Class-Incremental Learning. -->
|
| 34 |
<!-- + [DER (CVPR 2021)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/der/README.md): Dynamically Expandable Representation for Class Incremental Learning. -->
|
| 35 |
|
|
|
|
| 43 |
+ [SD_LoRA (ICLR 2025)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/sd_lora/README.md): Scalable Decoupled Low-Rank Adaptation for Class Incremental Learning
|
| 44 |
+ [LoRA_Sub_DRS (CVPR 2025)](https://github.com/RL-VIG/LibContinual/blob/master/reproduce/lora_sub_drs/README.md): LoRA Subtraction for Drift-Resistant Space in Exemplar-Free Continual Learning
|
| 45 |
+ [CL-LoRA (CVPR 2025)](https://github.com/RL-VIG/LibContinual/tree/master/reproduce/cl_lora/README.md): Continual Low-Rank Adaptation for Rehearsal-Free Class-Incremental Learning
|
| 46 |
+
+ [Fly-CL (ICLR 2026)](https://github.com/gfyddha/Fly-CL): A Fly-Inspired Framework for Efficient Decorrelation in Pre-trained Model-based Continual Representation Learning.
|
| 47 |
<!-- ## Quick Installation
|
| 48 |
|
| 49 |
Please refer to [`install.md`](https://libcontinual.readthedocs.io/en/latest/docs/install.html) <br>
|
config/fly_cl_cifar100.yaml
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
dataset: cifar100
|
| 3 |
+
data_root: ./data/cifar100
|
| 4 |
+
image_size: 224
|
| 5 |
+
|
| 6 |
+
init_cls_num: 10
|
| 7 |
+
inc_cls_num: 10
|
| 8 |
+
total_cls_num: 100
|
| 9 |
+
task_num: 10
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
init_epoch: 1
|
| 13 |
+
epoch: 1
|
| 14 |
+
batch_size: 128
|
| 15 |
+
val_per_epoch: 1
|
| 16 |
+
testing_times: 1
|
| 17 |
+
setting: task-agnostic
|
| 18 |
+
testing_per_task: true
|
| 19 |
+
|
| 20 |
+
seed: 1993
|
| 21 |
+
n_gpu: 1
|
| 22 |
+
|
| 23 |
+
class_order: [61, 79, 33, 57, 4, 14, 21, 42, 44, 19,
|
| 24 |
+
51, 73, 45, 89, 35, 85, 39, 56, 0, 24,
|
| 25 |
+
65, 29, 9, 18, 13, 95, 41, 80, 96, 32,
|
| 26 |
+
15, 49, 22, 99, 63, 68, 1, 62, 46, 59,
|
| 27 |
+
23, 60, 7, 86, 3, 27, 67, 69, 50, 92,
|
| 28 |
+
31, 98, 76, 84, 97, 93, 43, 16, 30, 83,
|
| 29 |
+
12, 5, 66, 72, 48, 78, 54, 81, 53, 26,
|
| 30 |
+
20, 94, 74, 47, 88, 38, 90, 10, 36, 11,
|
| 31 |
+
40, 52, 64, 87, 91, 6, 8, 55, 77, 82,
|
| 32 |
+
25, 75, 17, 28, 70, 2, 58, 71, 37, 34]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
train_trfms:
|
| 36 |
+
- Resize:
|
| 37 |
+
size: 224
|
| 38 |
+
interpolation: BICUBIC
|
| 39 |
+
- CenterCrop:
|
| 40 |
+
size: 224
|
| 41 |
+
- ToTensor: {}
|
| 42 |
+
- Normalize:
|
| 43 |
+
mean: [0.5, 0.5, 0.5]
|
| 44 |
+
std: [0.5, 0.5, 0.5]
|
| 45 |
+
|
| 46 |
+
test_trfms:
|
| 47 |
+
- Resize:
|
| 48 |
+
size: 224
|
| 49 |
+
interpolation: BICUBIC
|
| 50 |
+
- CenterCrop:
|
| 51 |
+
size: 224
|
| 52 |
+
- ToTensor: {}
|
| 53 |
+
- Normalize:
|
| 54 |
+
mean: [0.5, 0.5, 0.5]
|
| 55 |
+
std: [0.5, 0.5, 0.5]
|
| 56 |
+
|
| 57 |
+
optimizer:
|
| 58 |
+
name: SGD
|
| 59 |
+
kwargs:
|
| 60 |
+
lr: 0.0
|
| 61 |
+
|
| 62 |
+
lr_scheduler:
|
| 63 |
+
name: Constant
|
| 64 |
+
|
| 65 |
+
backbone:
|
| 66 |
+
name: timm_backbone
|
| 67 |
+
kwargs:
|
| 68 |
+
pretrained: true
|
| 69 |
+
model_name: vit_base_patch16_224
|
| 70 |
+
|
| 71 |
+
buffer:
|
| 72 |
+
name: LinearBuffer
|
| 73 |
+
kwargs:
|
| 74 |
+
buffer_size: 0
|
| 75 |
+
batch_size: 128
|
| 76 |
+
strategy: random
|
| 77 |
+
|
| 78 |
+
classifier:
|
| 79 |
+
name: FlyCL
|
| 80 |
+
kwargs:
|
| 81 |
+
embedding_dim: 768
|
| 82 |
+
expand_dim: 10000
|
| 83 |
+
synaptic_degree: 300
|
| 84 |
+
coding_level: 0.3
|
| 85 |
+
ridge_lower: 6
|
| 86 |
+
ridge_upper: 10
|
| 87 |
+
|
| 88 |
+
gcv_backend: dual_eigh
|
| 89 |
+
disable_cudnn: true
|
| 90 |
+
total_cls_num: 100
|
| 91 |
+
init_cls_num: 10
|
| 92 |
+
inc_cls_num: 10
|
| 93 |
+
mask_unseen_classes: false
|
config/fly_cl_cub200.yaml
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: cub200
|
| 2 |
+
data_root: ./data/cub200
|
| 3 |
+
image_size: 224
|
| 4 |
+
|
| 5 |
+
init_cls_num: 20
|
| 6 |
+
inc_cls_num: 20
|
| 7 |
+
total_cls_num: 200
|
| 8 |
+
task_num: 10
|
| 9 |
+
|
| 10 |
+
init_epoch: 1
|
| 11 |
+
epoch: 1
|
| 12 |
+
batch_size: 128
|
| 13 |
+
val_per_epoch: 1
|
| 14 |
+
testing_times: 1
|
| 15 |
+
setting: task-agnostic
|
| 16 |
+
testing_per_task: true
|
| 17 |
+
|
| 18 |
+
seed: 2023
|
| 19 |
+
n_gpu: 1
|
| 20 |
+
|
| 21 |
+
class_order: [98, 179, 114, 99, 81, 156, 146, 86, 162, 25,
|
| 22 |
+
31, 166, 77, 178, 136, 183, 135, 187, 45, 59,
|
| 23 |
+
181, 163, 4, 53, 92, 17, 159, 32, 9, 51,
|
| 24 |
+
39, 122, 197, 185, 87, 128, 16, 19, 137, 118,
|
| 25 |
+
127, 7, 189, 193, 60, 27, 148, 18, 174, 35,
|
| 26 |
+
196, 168, 52, 46, 188, 54, 74, 69, 30, 14,
|
| 27 |
+
95, 121, 169, 75, 22, 129, 103, 80, 70, 195,
|
| 28 |
+
72, 34, 40, 106, 175, 56, 171, 125, 198, 71,
|
| 29 |
+
104, 29, 152, 65, 44, 120, 82, 15, 2, 150,
|
| 30 |
+
184, 105, 0, 36, 117, 88, 190, 21, 113, 172,
|
| 31 |
+
55, 116, 89, 12, 144, 153, 62, 119, 139, 11,
|
| 32 |
+
83, 173, 42, 140, 8, 93, 61, 84, 123, 134,
|
| 33 |
+
143, 49, 157, 3, 91, 133, 115, 177, 24, 96,
|
| 34 |
+
85, 164, 109, 110, 26, 5, 37, 76, 167, 57,
|
| 35 |
+
23, 79, 1, 73, 63, 102, 124, 186, 165, 43,
|
| 36 |
+
158, 176, 67, 149, 100, 78, 151, 108, 111, 192,
|
| 37 |
+
33, 13, 180, 170, 130, 10, 64, 161, 94, 154,
|
| 38 |
+
132, 191, 48, 145, 101, 50, 66, 126, 141, 155,
|
| 39 |
+
28, 160, 68, 112, 38, 90, 147, 47, 182, 199,
|
| 40 |
+
20, 58, 107, 131, 142, 194, 97, 138, 6, 41]
|
| 41 |
+
|
| 42 |
+
train_trfms:
|
| 43 |
+
- Resize:
|
| 44 |
+
size: 256
|
| 45 |
+
interpolation: BICUBIC
|
| 46 |
+
- CenterCrop:
|
| 47 |
+
size: 224
|
| 48 |
+
- ToTensor: {}
|
| 49 |
+
- Normalize:
|
| 50 |
+
mean: [0.5, 0.5, 0.5]
|
| 51 |
+
std: [0.5, 0.5, 0.5]
|
| 52 |
+
|
| 53 |
+
test_trfms:
|
| 54 |
+
- Resize:
|
| 55 |
+
size: 256
|
| 56 |
+
interpolation: BICUBIC
|
| 57 |
+
- CenterCrop:
|
| 58 |
+
size: 224
|
| 59 |
+
- ToTensor: {}
|
| 60 |
+
- Normalize:
|
| 61 |
+
mean: [0.5, 0.5, 0.5]
|
| 62 |
+
std: [0.5, 0.5, 0.5]
|
| 63 |
+
|
| 64 |
+
optimizer:
|
| 65 |
+
name: SGD
|
| 66 |
+
kwargs:
|
| 67 |
+
lr: 0.0
|
| 68 |
+
|
| 69 |
+
lr_scheduler:
|
| 70 |
+
name: Constant
|
| 71 |
+
|
| 72 |
+
backbone:
|
| 73 |
+
name: timm_backbone
|
| 74 |
+
kwargs:
|
| 75 |
+
pretrained: true
|
| 76 |
+
model_name: vit_base_patch16_224
|
| 77 |
+
|
| 78 |
+
buffer:
|
| 79 |
+
name: LinearBuffer
|
| 80 |
+
kwargs:
|
| 81 |
+
buffer_size: 0
|
| 82 |
+
batch_size: 128
|
| 83 |
+
strategy: random
|
| 84 |
+
|
| 85 |
+
classifier:
|
| 86 |
+
name: FlyCL
|
| 87 |
+
kwargs:
|
| 88 |
+
embedding_dim: 768
|
| 89 |
+
expand_dim: 10000
|
| 90 |
+
synaptic_degree: 300
|
| 91 |
+
coding_level: 0.3
|
| 92 |
+
ridge_lower: 6
|
| 93 |
+
ridge_upper: 10
|
| 94 |
+
gcv_backend: dual_eigh
|
| 95 |
+
disable_cudnn: true
|
| 96 |
+
use_projection: true
|
| 97 |
+
use_ridge: true
|
| 98 |
+
total_cls_num: 200
|
| 99 |
+
init_cls_num: 20
|
| 100 |
+
inc_cls_num: 20
|
| 101 |
+
mask_unseen_classes: false
|
config/zz_EWCDR/ewcdr-resnet18-cifar100-b10-10-10.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./data/binary_cifar100
|
| 3 |
+
shuffle: True
|
| 4 |
+
|
| 5 |
+
total_cls_num: &total_cls_num 100
|
| 6 |
+
init_cls_num: &init_cls_num 10
|
| 7 |
+
inc_cls_num: &inc_cls_num 10
|
| 8 |
+
task_num: &task_num 10
|
| 9 |
+
image_size: &image_size 32
|
| 10 |
+
|
| 11 |
+
init_epoch: 200
|
| 12 |
+
epoch: 180
|
| 13 |
+
val_per_epoch: 5
|
| 14 |
+
batch_size: 128
|
| 15 |
+
num_workers: 4
|
| 16 |
+
testing_times: 1
|
| 17 |
+
seed: 1993
|
| 18 |
+
|
| 19 |
+
train_trfms:
|
| 20 |
+
- RandomCrop:
|
| 21 |
+
size: *image_size
|
| 22 |
+
padding: 4
|
| 23 |
+
- RandomHorizontalFlip: {}
|
| 24 |
+
- ColorJitter:
|
| 25 |
+
brightness: 0.24705882352941178
|
| 26 |
+
- ToTensor: {}
|
| 27 |
+
- Normalize:
|
| 28 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 29 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 30 |
+
|
| 31 |
+
test_trfms:
|
| 32 |
+
- ToTensor: {}
|
| 33 |
+
- Normalize:
|
| 34 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 35 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 36 |
+
|
| 37 |
+
init_optimizer:
|
| 38 |
+
name: SGD
|
| 39 |
+
kwargs:
|
| 40 |
+
lr: 0.1
|
| 41 |
+
momentum: 0.9
|
| 42 |
+
weight_decay: 0.0005
|
| 43 |
+
|
| 44 |
+
optimizer:
|
| 45 |
+
name: SGD
|
| 46 |
+
kwargs:
|
| 47 |
+
lr: 0.1
|
| 48 |
+
momentum: 0.9
|
| 49 |
+
weight_decay: 0.0002
|
| 50 |
+
|
| 51 |
+
init_lr_scheduler:
|
| 52 |
+
name: MultiStepLR
|
| 53 |
+
kwargs:
|
| 54 |
+
gamma: 0.1
|
| 55 |
+
milestones: [60, 120, 170]
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
name: MultiStepLR
|
| 59 |
+
kwargs:
|
| 60 |
+
gamma: 0.1
|
| 61 |
+
milestones: [70, 120, 150]
|
| 62 |
+
|
| 63 |
+
backbone:
|
| 64 |
+
name: resnet18
|
| 65 |
+
kwargs:
|
| 66 |
+
num_classes: *total_cls_num
|
| 67 |
+
args:
|
| 68 |
+
dataset: *dataset
|
| 69 |
+
|
| 70 |
+
buffer:
|
| 71 |
+
name: LinearBuffer
|
| 72 |
+
kwargs:
|
| 73 |
+
buffer_size: 0
|
| 74 |
+
batch_size: 128
|
| 75 |
+
strategy: herding
|
| 76 |
+
|
| 77 |
+
classifier:
|
| 78 |
+
name: EWCDR
|
| 79 |
+
kwargs:
|
| 80 |
+
num_class: *total_cls_num
|
| 81 |
+
feat_dim: 512
|
| 82 |
+
init_cls_num: *init_cls_num
|
| 83 |
+
inc_cls_num: *inc_cls_num
|
| 84 |
+
task_num: *task_num
|
| 85 |
+
lamda: 10000
|
| 86 |
+
omega_max: 0.0001
|
config/zz_EWCDR/ewcdr-resnet18-cifar100-b20-20-5.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./data/binary_cifar100
|
| 3 |
+
shuffle: True
|
| 4 |
+
|
| 5 |
+
total_cls_num: &total_cls_num 100
|
| 6 |
+
init_cls_num: &init_cls_num 20
|
| 7 |
+
inc_cls_num: &inc_cls_num 20
|
| 8 |
+
task_num: &task_num 5
|
| 9 |
+
image_size: &image_size 32
|
| 10 |
+
|
| 11 |
+
init_epoch: 200
|
| 12 |
+
epoch: 180
|
| 13 |
+
val_per_epoch: 5
|
| 14 |
+
batch_size: 128
|
| 15 |
+
num_workers: 4
|
| 16 |
+
testing_times: 1
|
| 17 |
+
seed: 1993
|
| 18 |
+
|
| 19 |
+
train_trfms:
|
| 20 |
+
- RandomCrop:
|
| 21 |
+
size: *image_size
|
| 22 |
+
padding: 4
|
| 23 |
+
- RandomHorizontalFlip: {}
|
| 24 |
+
- ColorJitter:
|
| 25 |
+
brightness: 0.24705882352941178
|
| 26 |
+
- ToTensor: {}
|
| 27 |
+
- Normalize:
|
| 28 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 29 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 30 |
+
|
| 31 |
+
test_trfms:
|
| 32 |
+
- ToTensor: {}
|
| 33 |
+
- Normalize:
|
| 34 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 35 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 36 |
+
|
| 37 |
+
init_optimizer:
|
| 38 |
+
name: SGD
|
| 39 |
+
kwargs:
|
| 40 |
+
lr: 0.1
|
| 41 |
+
momentum: 0.9
|
| 42 |
+
weight_decay: 0.0005
|
| 43 |
+
|
| 44 |
+
optimizer:
|
| 45 |
+
name: SGD
|
| 46 |
+
kwargs:
|
| 47 |
+
lr: 0.1
|
| 48 |
+
momentum: 0.9
|
| 49 |
+
weight_decay: 0.0002
|
| 50 |
+
|
| 51 |
+
init_lr_scheduler:
|
| 52 |
+
name: MultiStepLR
|
| 53 |
+
kwargs:
|
| 54 |
+
gamma: 0.1
|
| 55 |
+
milestones: [60, 120, 170]
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
name: MultiStepLR
|
| 59 |
+
kwargs:
|
| 60 |
+
gamma: 0.1
|
| 61 |
+
milestones: [70, 120, 150]
|
| 62 |
+
|
| 63 |
+
backbone:
|
| 64 |
+
name: resnet18
|
| 65 |
+
kwargs:
|
| 66 |
+
num_classes: *total_cls_num
|
| 67 |
+
args:
|
| 68 |
+
dataset: *dataset
|
| 69 |
+
|
| 70 |
+
buffer:
|
| 71 |
+
name: LinearBuffer
|
| 72 |
+
kwargs:
|
| 73 |
+
buffer_size: 0
|
| 74 |
+
batch_size: 128
|
| 75 |
+
strategy: herding
|
| 76 |
+
|
| 77 |
+
classifier:
|
| 78 |
+
name: EWCDR
|
| 79 |
+
kwargs:
|
| 80 |
+
num_class: *total_cls_num
|
| 81 |
+
feat_dim: 512
|
| 82 |
+
init_cls_num: *init_cls_num
|
| 83 |
+
inc_cls_num: *inc_cls_num
|
| 84 |
+
task_num: *task_num
|
| 85 |
+
lamda: 10000
|
| 86 |
+
omega_max: 0.0001
|
config/zz_EWCDR/ewcdr-resnet18-cifar100-b40-3-21.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./data/binary_cifar100
|
| 3 |
+
shuffle: True
|
| 4 |
+
|
| 5 |
+
total_cls_num: &total_cls_num 100
|
| 6 |
+
init_cls_num: &init_cls_num 40
|
| 7 |
+
inc_cls_num: &inc_cls_num 3
|
| 8 |
+
task_num: &task_num 21
|
| 9 |
+
image_size: &image_size 32
|
| 10 |
+
|
| 11 |
+
init_epoch: 200
|
| 12 |
+
epoch: 180
|
| 13 |
+
val_per_epoch: 5
|
| 14 |
+
batch_size: 128
|
| 15 |
+
num_workers: 4
|
| 16 |
+
testing_times: 1
|
| 17 |
+
seed: 1993
|
| 18 |
+
|
| 19 |
+
train_trfms:
|
| 20 |
+
- RandomCrop:
|
| 21 |
+
size: *image_size
|
| 22 |
+
padding: 4
|
| 23 |
+
- RandomHorizontalFlip: {}
|
| 24 |
+
- ColorJitter:
|
| 25 |
+
brightness: 0.24705882352941178
|
| 26 |
+
- ToTensor: {}
|
| 27 |
+
- Normalize:
|
| 28 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 29 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 30 |
+
|
| 31 |
+
test_trfms:
|
| 32 |
+
- ToTensor: {}
|
| 33 |
+
- Normalize:
|
| 34 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 35 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 36 |
+
|
| 37 |
+
init_optimizer:
|
| 38 |
+
name: SGD
|
| 39 |
+
kwargs:
|
| 40 |
+
lr: 0.1
|
| 41 |
+
momentum: 0.9
|
| 42 |
+
weight_decay: 0.0005
|
| 43 |
+
|
| 44 |
+
optimizer:
|
| 45 |
+
name: SGD
|
| 46 |
+
kwargs:
|
| 47 |
+
lr: 0.1
|
| 48 |
+
momentum: 0.9
|
| 49 |
+
weight_decay: 0.0002
|
| 50 |
+
|
| 51 |
+
init_lr_scheduler:
|
| 52 |
+
name: MultiStepLR
|
| 53 |
+
kwargs:
|
| 54 |
+
gamma: 0.1
|
| 55 |
+
milestones: [60, 120, 170]
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
name: MultiStepLR
|
| 59 |
+
kwargs:
|
| 60 |
+
gamma: 0.1
|
| 61 |
+
milestones: [70, 120, 150]
|
| 62 |
+
|
| 63 |
+
backbone:
|
| 64 |
+
name: resnet18
|
| 65 |
+
kwargs:
|
| 66 |
+
num_classes: *total_cls_num
|
| 67 |
+
args:
|
| 68 |
+
dataset: *dataset
|
| 69 |
+
|
| 70 |
+
buffer:
|
| 71 |
+
name: LinearBuffer
|
| 72 |
+
kwargs:
|
| 73 |
+
buffer_size: 0
|
| 74 |
+
batch_size: 128
|
| 75 |
+
strategy: herding
|
| 76 |
+
|
| 77 |
+
classifier:
|
| 78 |
+
name: EWCDR
|
| 79 |
+
kwargs:
|
| 80 |
+
num_class: *total_cls_num
|
| 81 |
+
feat_dim: 512
|
| 82 |
+
init_cls_num: *init_cls_num
|
| 83 |
+
inc_cls_num: *inc_cls_num
|
| 84 |
+
task_num: *task_num
|
| 85 |
+
lamda: 10000
|
| 86 |
+
omega_max: 0.0001
|
config/zz_EWCDR/ewcdr-resnet18-cifar100-b5-5-20.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./data/binary_cifar100
|
| 3 |
+
shuffle: True
|
| 4 |
+
|
| 5 |
+
total_cls_num: &total_cls_num 100
|
| 6 |
+
init_cls_num: &init_cls_num 5
|
| 7 |
+
inc_cls_num: &inc_cls_num 5
|
| 8 |
+
task_num: &task_num 20
|
| 9 |
+
image_size: &image_size 32
|
| 10 |
+
|
| 11 |
+
init_epoch: 200
|
| 12 |
+
epoch: 180
|
| 13 |
+
val_per_epoch: 5
|
| 14 |
+
batch_size: 128
|
| 15 |
+
num_workers: 4
|
| 16 |
+
testing_times: 1
|
| 17 |
+
seed: 1993
|
| 18 |
+
|
| 19 |
+
train_trfms:
|
| 20 |
+
- RandomCrop:
|
| 21 |
+
size: *image_size
|
| 22 |
+
padding: 4
|
| 23 |
+
- RandomHorizontalFlip: {}
|
| 24 |
+
- ColorJitter:
|
| 25 |
+
brightness: 0.24705882352941178
|
| 26 |
+
- ToTensor: {}
|
| 27 |
+
- Normalize:
|
| 28 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 29 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 30 |
+
|
| 31 |
+
test_trfms:
|
| 32 |
+
- ToTensor: {}
|
| 33 |
+
- Normalize:
|
| 34 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 35 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 36 |
+
|
| 37 |
+
init_optimizer:
|
| 38 |
+
name: SGD
|
| 39 |
+
kwargs:
|
| 40 |
+
lr: 0.1
|
| 41 |
+
momentum: 0.9
|
| 42 |
+
weight_decay: 0.0005
|
| 43 |
+
|
| 44 |
+
optimizer:
|
| 45 |
+
name: SGD
|
| 46 |
+
kwargs:
|
| 47 |
+
lr: 0.1
|
| 48 |
+
momentum: 0.9
|
| 49 |
+
weight_decay: 0.0002
|
| 50 |
+
|
| 51 |
+
init_lr_scheduler:
|
| 52 |
+
name: MultiStepLR
|
| 53 |
+
kwargs:
|
| 54 |
+
gamma: 0.1
|
| 55 |
+
milestones: [60, 120, 170]
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
name: MultiStepLR
|
| 59 |
+
kwargs:
|
| 60 |
+
gamma: 0.1
|
| 61 |
+
milestones: [70, 120, 150]
|
| 62 |
+
|
| 63 |
+
backbone:
|
| 64 |
+
name: resnet18
|
| 65 |
+
kwargs:
|
| 66 |
+
num_classes: *total_cls_num
|
| 67 |
+
args:
|
| 68 |
+
dataset: *dataset
|
| 69 |
+
|
| 70 |
+
buffer:
|
| 71 |
+
name: LinearBuffer
|
| 72 |
+
kwargs:
|
| 73 |
+
buffer_size: 0
|
| 74 |
+
batch_size: 128
|
| 75 |
+
strategy: herding
|
| 76 |
+
|
| 77 |
+
classifier:
|
| 78 |
+
name: EWCDR
|
| 79 |
+
kwargs:
|
| 80 |
+
num_class: *total_cls_num
|
| 81 |
+
feat_dim: 512
|
| 82 |
+
init_cls_num: *init_cls_num
|
| 83 |
+
inc_cls_num: *inc_cls_num
|
| 84 |
+
task_num: *task_num
|
| 85 |
+
lamda: 10000
|
| 86 |
+
omega_max: 0.0001
|
config/zz_EWCDR/ewcdr-resnet18-cifar100-b50-10-6.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./data/binary_cifar100
|
| 3 |
+
shuffle: True
|
| 4 |
+
|
| 5 |
+
total_cls_num: &total_cls_num 100
|
| 6 |
+
init_cls_num: &init_cls_num 50
|
| 7 |
+
inc_cls_num: &inc_cls_num 10
|
| 8 |
+
task_num: &task_num 6
|
| 9 |
+
image_size: &image_size 32
|
| 10 |
+
|
| 11 |
+
init_epoch: 200
|
| 12 |
+
epoch: 180
|
| 13 |
+
val_per_epoch: 5
|
| 14 |
+
batch_size: 128
|
| 15 |
+
num_workers: 4
|
| 16 |
+
testing_times: 1
|
| 17 |
+
seed: 1993
|
| 18 |
+
|
| 19 |
+
train_trfms:
|
| 20 |
+
- RandomCrop:
|
| 21 |
+
size: *image_size
|
| 22 |
+
padding: 4
|
| 23 |
+
- RandomHorizontalFlip: {}
|
| 24 |
+
- ColorJitter:
|
| 25 |
+
brightness: 0.24705882352941178
|
| 26 |
+
- ToTensor: {}
|
| 27 |
+
- Normalize:
|
| 28 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 29 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 30 |
+
|
| 31 |
+
test_trfms:
|
| 32 |
+
- ToTensor: {}
|
| 33 |
+
- Normalize:
|
| 34 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 35 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 36 |
+
|
| 37 |
+
init_optimizer:
|
| 38 |
+
name: SGD
|
| 39 |
+
kwargs:
|
| 40 |
+
lr: 0.1
|
| 41 |
+
momentum: 0.9
|
| 42 |
+
weight_decay: 0.0005
|
| 43 |
+
|
| 44 |
+
optimizer:
|
| 45 |
+
name: SGD
|
| 46 |
+
kwargs:
|
| 47 |
+
lr: 0.1
|
| 48 |
+
momentum: 0.9
|
| 49 |
+
weight_decay: 0.0002
|
| 50 |
+
|
| 51 |
+
init_lr_scheduler:
|
| 52 |
+
name: MultiStepLR
|
| 53 |
+
kwargs:
|
| 54 |
+
gamma: 0.1
|
| 55 |
+
milestones: [60, 120, 170]
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
name: MultiStepLR
|
| 59 |
+
kwargs:
|
| 60 |
+
gamma: 0.1
|
| 61 |
+
milestones: [70, 120, 150]
|
| 62 |
+
|
| 63 |
+
backbone:
|
| 64 |
+
name: resnet18
|
| 65 |
+
kwargs:
|
| 66 |
+
num_classes: *total_cls_num
|
| 67 |
+
args:
|
| 68 |
+
dataset: *dataset
|
| 69 |
+
|
| 70 |
+
buffer:
|
| 71 |
+
name: LinearBuffer
|
| 72 |
+
kwargs:
|
| 73 |
+
buffer_size: 0
|
| 74 |
+
batch_size: 128
|
| 75 |
+
strategy: herding
|
| 76 |
+
|
| 77 |
+
classifier:
|
| 78 |
+
name: EWCDR
|
| 79 |
+
kwargs:
|
| 80 |
+
num_class: *total_cls_num
|
| 81 |
+
feat_dim: 512
|
| 82 |
+
init_cls_num: *init_cls_num
|
| 83 |
+
inc_cls_num: *inc_cls_num
|
| 84 |
+
task_num: *task_num
|
| 85 |
+
lamda: 10000
|
| 86 |
+
omega_max: 0.0001
|
config/zz_EWCDR/ewcdr-resnet18-cifar100-b50-5-11.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./data/binary_cifar100
|
| 3 |
+
shuffle: True
|
| 4 |
+
|
| 5 |
+
total_cls_num: &total_cls_num 100
|
| 6 |
+
init_cls_num: &init_cls_num 50
|
| 7 |
+
inc_cls_num: &inc_cls_num 5
|
| 8 |
+
task_num: &task_num 11
|
| 9 |
+
image_size: &image_size 32
|
| 10 |
+
|
| 11 |
+
init_epoch: 200
|
| 12 |
+
epoch: 180
|
| 13 |
+
val_per_epoch: 5
|
| 14 |
+
batch_size: 128
|
| 15 |
+
num_workers: 4
|
| 16 |
+
testing_times: 1
|
| 17 |
+
seed: 1993
|
| 18 |
+
|
| 19 |
+
train_trfms:
|
| 20 |
+
- RandomCrop:
|
| 21 |
+
size: *image_size
|
| 22 |
+
padding: 4
|
| 23 |
+
- RandomHorizontalFlip: {}
|
| 24 |
+
- ColorJitter:
|
| 25 |
+
brightness: 0.24705882352941178
|
| 26 |
+
- ToTensor: {}
|
| 27 |
+
- Normalize:
|
| 28 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 29 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 30 |
+
|
| 31 |
+
test_trfms:
|
| 32 |
+
- ToTensor: {}
|
| 33 |
+
- Normalize:
|
| 34 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 35 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 36 |
+
|
| 37 |
+
init_optimizer:
|
| 38 |
+
name: SGD
|
| 39 |
+
kwargs:
|
| 40 |
+
lr: 0.1
|
| 41 |
+
momentum: 0.9
|
| 42 |
+
weight_decay: 0.0005
|
| 43 |
+
|
| 44 |
+
optimizer:
|
| 45 |
+
name: SGD
|
| 46 |
+
kwargs:
|
| 47 |
+
lr: 0.1
|
| 48 |
+
momentum: 0.9
|
| 49 |
+
weight_decay: 0.0002
|
| 50 |
+
|
| 51 |
+
init_lr_scheduler:
|
| 52 |
+
name: MultiStepLR
|
| 53 |
+
kwargs:
|
| 54 |
+
gamma: 0.1
|
| 55 |
+
milestones: [60, 120, 170]
|
| 56 |
+
|
| 57 |
+
lr_scheduler:
|
| 58 |
+
name: MultiStepLR
|
| 59 |
+
kwargs:
|
| 60 |
+
gamma: 0.1
|
| 61 |
+
milestones: [70, 120, 150]
|
| 62 |
+
|
| 63 |
+
backbone:
|
| 64 |
+
name: resnet18
|
| 65 |
+
kwargs:
|
| 66 |
+
num_classes: *total_cls_num
|
| 67 |
+
args:
|
| 68 |
+
dataset: *dataset
|
| 69 |
+
|
| 70 |
+
buffer:
|
| 71 |
+
name: LinearBuffer
|
| 72 |
+
kwargs:
|
| 73 |
+
buffer_size: 0
|
| 74 |
+
batch_size: 128
|
| 75 |
+
strategy: herding
|
| 76 |
+
|
| 77 |
+
classifier:
|
| 78 |
+
name: EWCDR
|
| 79 |
+
kwargs:
|
| 80 |
+
num_class: *total_cls_num
|
| 81 |
+
feat_dim: 512
|
| 82 |
+
init_cls_num: *init_cls_num
|
| 83 |
+
inc_cls_num: *inc_cls_num
|
| 84 |
+
task_num: *task_num
|
| 85 |
+
lamda: 10000
|
| 86 |
+
omega_max: 0.0001
|
config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b10-10-20.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
includes:
|
| 2 |
+
- zz_EWCDR/tinyimagenet_common.yaml
|
| 3 |
+
|
| 4 |
+
init_cls_num: 10
|
| 5 |
+
inc_cls_num: 10
|
| 6 |
+
task_num: 20
|
| 7 |
+
|
| 8 |
+
backbone:
|
| 9 |
+
name: resnet18
|
| 10 |
+
kwargs:
|
| 11 |
+
num_classes: 200
|
| 12 |
+
args:
|
| 13 |
+
dataset: tiny-imagenet
|
| 14 |
+
init_cls_num: 10
|
| 15 |
+
inc_cls_num: 10
|
| 16 |
+
|
| 17 |
+
classifier:
|
| 18 |
+
name: EWCDR
|
| 19 |
+
kwargs:
|
| 20 |
+
num_class: 200
|
| 21 |
+
feat_dim: 512
|
| 22 |
+
init_cls_num: 10
|
| 23 |
+
inc_cls_num: 10
|
| 24 |
+
task_num: 20
|
| 25 |
+
lamda: 10000
|
| 26 |
+
omega_max: 0.0001
|
| 27 |
+
|
config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-10-11.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
includes:
|
| 2 |
+
- zz_EWCDR/tinyimagenet_common.yaml
|
| 3 |
+
|
| 4 |
+
init_cls_num: 100
|
| 5 |
+
inc_cls_num: 10
|
| 6 |
+
task_num: 11
|
| 7 |
+
|
| 8 |
+
backbone:
|
| 9 |
+
name: resnet18
|
| 10 |
+
kwargs:
|
| 11 |
+
num_classes: 200
|
| 12 |
+
args:
|
| 13 |
+
dataset: tiny-imagenet
|
| 14 |
+
init_cls_num: 100
|
| 15 |
+
inc_cls_num: 10
|
| 16 |
+
|
| 17 |
+
classifier:
|
| 18 |
+
name: EWCDR
|
| 19 |
+
kwargs:
|
| 20 |
+
num_class: 200
|
| 21 |
+
feat_dim: 512
|
| 22 |
+
init_cls_num: 100
|
| 23 |
+
inc_cls_num: 10
|
| 24 |
+
task_num: 11
|
| 25 |
+
lamda: 10000
|
| 26 |
+
omega_max: 0.0001
|
| 27 |
+
|
config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-20-6.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
includes:
|
| 2 |
+
- zz_EWCDR/tinyimagenet_common.yaml
|
| 3 |
+
|
| 4 |
+
init_cls_num: 100
|
| 5 |
+
inc_cls_num: 20
|
| 6 |
+
task_num: 6
|
| 7 |
+
|
| 8 |
+
backbone:
|
| 9 |
+
name: resnet18
|
| 10 |
+
kwargs:
|
| 11 |
+
num_classes: 200
|
| 12 |
+
args:
|
| 13 |
+
dataset: tiny-imagenet
|
| 14 |
+
init_cls_num: 100
|
| 15 |
+
inc_cls_num: 20
|
| 16 |
+
|
| 17 |
+
classifier:
|
| 18 |
+
name: EWCDR
|
| 19 |
+
kwargs:
|
| 20 |
+
num_class: 200
|
| 21 |
+
feat_dim: 512
|
| 22 |
+
init_cls_num: 100
|
| 23 |
+
inc_cls_num: 20
|
| 24 |
+
task_num: 6
|
| 25 |
+
lamda: 10000
|
| 26 |
+
omega_max: 0.0001
|
| 27 |
+
|
config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-5-21.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
includes:
|
| 2 |
+
- zz_EWCDR/tinyimagenet_common.yaml
|
| 3 |
+
|
| 4 |
+
init_cls_num: 100
|
| 5 |
+
inc_cls_num: 5
|
| 6 |
+
task_num: 21
|
| 7 |
+
|
| 8 |
+
backbone:
|
| 9 |
+
name: resnet18
|
| 10 |
+
kwargs:
|
| 11 |
+
num_classes: 200
|
| 12 |
+
args:
|
| 13 |
+
dataset: tiny-imagenet
|
| 14 |
+
init_cls_num: 100
|
| 15 |
+
inc_cls_num: 5
|
| 16 |
+
|
| 17 |
+
classifier:
|
| 18 |
+
name: EWCDR
|
| 19 |
+
kwargs:
|
| 20 |
+
num_class: 200
|
| 21 |
+
feat_dim: 512
|
| 22 |
+
init_cls_num: 100
|
| 23 |
+
inc_cls_num: 5
|
| 24 |
+
task_num: 21
|
| 25 |
+
lamda: 10000
|
| 26 |
+
omega_max: 0.0001
|
| 27 |
+
|
config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b20-20-10.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
includes:
|
| 2 |
+
- zz_EWCDR/tinyimagenet_common.yaml
|
| 3 |
+
|
| 4 |
+
init_cls_num: 20
|
| 5 |
+
inc_cls_num: 20
|
| 6 |
+
task_num: 10
|
| 7 |
+
|
| 8 |
+
backbone:
|
| 9 |
+
name: resnet18
|
| 10 |
+
kwargs:
|
| 11 |
+
num_classes: 200
|
| 12 |
+
args:
|
| 13 |
+
dataset: tiny-imagenet
|
| 14 |
+
init_cls_num: 20
|
| 15 |
+
inc_cls_num: 20
|
| 16 |
+
|
| 17 |
+
classifier:
|
| 18 |
+
name: EWCDR
|
| 19 |
+
kwargs:
|
| 20 |
+
num_class: 200
|
| 21 |
+
feat_dim: 512
|
| 22 |
+
init_cls_num: 20
|
| 23 |
+
inc_cls_num: 20
|
| 24 |
+
task_num: 10
|
| 25 |
+
lamda: 10000
|
| 26 |
+
omega_max: 0.0001
|
| 27 |
+
|
config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b40-40-5.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
includes:
|
| 2 |
+
- zz_EWCDR/tinyimagenet_common.yaml
|
| 3 |
+
|
| 4 |
+
init_cls_num: 40
|
| 5 |
+
inc_cls_num: 40
|
| 6 |
+
task_num: 5
|
| 7 |
+
|
| 8 |
+
backbone:
|
| 9 |
+
name: resnet18
|
| 10 |
+
kwargs:
|
| 11 |
+
num_classes: 200
|
| 12 |
+
args:
|
| 13 |
+
dataset: tiny-imagenet
|
| 14 |
+
init_cls_num: 40
|
| 15 |
+
inc_cls_num: 40
|
| 16 |
+
|
| 17 |
+
classifier:
|
| 18 |
+
name: EWCDR
|
| 19 |
+
kwargs:
|
| 20 |
+
num_class: 200
|
| 21 |
+
feat_dim: 512
|
| 22 |
+
init_cls_num: 40
|
| 23 |
+
inc_cls_num: 40
|
| 24 |
+
task_num: 5
|
| 25 |
+
lamda: 10000
|
| 26 |
+
omega_max: 0.0001
|
| 27 |
+
|
config/zz_EWCDR/tinyimagenet_common.yaml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: tiny-imagenet
|
| 2 |
+
data_root: ./data
|
| 3 |
+
shuffle: true
|
| 4 |
+
pin_memory: true
|
| 5 |
+
|
| 6 |
+
total_cls_num: 200
|
| 7 |
+
image_size: 64
|
| 8 |
+
init_epoch: 200
|
| 9 |
+
epoch: 180
|
| 10 |
+
val_per_epoch: 5
|
| 11 |
+
batch_size: 128
|
| 12 |
+
num_workers: 8
|
| 13 |
+
testing_times: 1
|
| 14 |
+
seed: 1993
|
| 15 |
+
|
| 16 |
+
train_trfms:
|
| 17 |
+
- RandomResizedCrop: {size: 64}
|
| 18 |
+
- RandomHorizontalFlip: {}
|
| 19 |
+
- ToTensor: {}
|
| 20 |
+
- Normalize: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225]}
|
| 21 |
+
test_trfms:
|
| 22 |
+
- Resize: {size: 64}
|
| 23 |
+
- CenterCrop: {size: 56}
|
| 24 |
+
- ToTensor: {}
|
| 25 |
+
- Normalize: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225]}
|
| 26 |
+
|
| 27 |
+
init_optimizer:
|
| 28 |
+
name: SGD
|
| 29 |
+
kwargs: {lr: 0.1, momentum: 0.9, weight_decay: 0.0005}
|
| 30 |
+
optimizer:
|
| 31 |
+
name: SGD
|
| 32 |
+
kwargs: {lr: 0.1, momentum: 0.9, weight_decay: 0.0002}
|
| 33 |
+
init_lr_scheduler:
|
| 34 |
+
name: MultiStepLR
|
| 35 |
+
kwargs: {gamma: 0.1, milestones: [60, 120, 170]}
|
| 36 |
+
lr_scheduler:
|
| 37 |
+
name: MultiStepLR
|
| 38 |
+
kwargs: {gamma: 0.1, milestones: [70, 120, 150]}
|
| 39 |
+
|
| 40 |
+
buffer:
|
| 41 |
+
name: LinearBuffer
|
| 42 |
+
kwargs: {buffer_size: 0, batch_size: 128, strategy: herding}
|
config/zz_IDER/er_cifar100_buf2000.yaml
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 100
|
| 4 |
+
init_cls_num: &init_cls_num 10
|
| 5 |
+
inc_cls_num: &inc_cls_num 10
|
| 6 |
+
task_num: 10
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 18 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 23 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.1
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
args:
|
| 59 |
+
dataset: *dataset
|
| 60 |
+
|
| 61 |
+
buffer:
|
| 62 |
+
name: LinearBuffer
|
| 63 |
+
kwargs:
|
| 64 |
+
buffer_size: 2000
|
| 65 |
+
batch_size: 32
|
| 66 |
+
strategy: random
|
| 67 |
+
|
| 68 |
+
classifier:
|
| 69 |
+
name: PaperER
|
| 70 |
+
kwargs:
|
| 71 |
+
num_class: *total_cls_num
|
| 72 |
+
feat_dim: 512
|
| 73 |
+
init_cls_num: *init_cls_num
|
| 74 |
+
inc_cls_num: *inc_cls_num
|
| 75 |
+
minibatch_size: 32
|
| 76 |
+
buffer_storage: tensor
|
config/zz_IDER/er_cifar100_buf500.yaml
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 100
|
| 4 |
+
init_cls_num: &init_cls_num 10
|
| 5 |
+
inc_cls_num: &inc_cls_num 10
|
| 6 |
+
task_num: 10
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 18 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 23 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.1
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
args:
|
| 59 |
+
dataset: *dataset
|
| 60 |
+
|
| 61 |
+
buffer:
|
| 62 |
+
name: LinearBuffer
|
| 63 |
+
kwargs:
|
| 64 |
+
buffer_size: 500
|
| 65 |
+
batch_size: 32
|
| 66 |
+
strategy: random
|
| 67 |
+
|
| 68 |
+
classifier:
|
| 69 |
+
name: PaperER
|
| 70 |
+
kwargs:
|
| 71 |
+
num_class: *total_cls_num
|
| 72 |
+
feat_dim: 512
|
| 73 |
+
init_cls_num: *init_cls_num
|
| 74 |
+
inc_cls_num: *inc_cls_num
|
| 75 |
+
minibatch_size: 32
|
| 76 |
+
buffer_storage: tensor
|
config/zz_IDER/er_cifar10_buf200.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar10
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 10
|
| 4 |
+
init_cls_num: &init_cls_num 2
|
| 5 |
+
inc_cls_num: &inc_cls_num 2
|
| 6 |
+
task_num: 5
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 18 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 23 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.1
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: Constant
|
| 50 |
+
kwargs: {}
|
| 51 |
+
|
| 52 |
+
backbone:
|
| 53 |
+
name: resnet18
|
| 54 |
+
kwargs:
|
| 55 |
+
num_classes: *total_cls_num
|
| 56 |
+
args:
|
| 57 |
+
dataset: *dataset
|
| 58 |
+
|
| 59 |
+
buffer:
|
| 60 |
+
name: LinearBuffer
|
| 61 |
+
kwargs:
|
| 62 |
+
buffer_size: 200
|
| 63 |
+
batch_size: 32
|
| 64 |
+
strategy: random
|
| 65 |
+
|
| 66 |
+
classifier:
|
| 67 |
+
name: PaperER
|
| 68 |
+
kwargs:
|
| 69 |
+
num_class: *total_cls_num
|
| 70 |
+
feat_dim: 512
|
| 71 |
+
init_cls_num: *init_cls_num
|
| 72 |
+
inc_cls_num: *inc_cls_num
|
| 73 |
+
minibatch_size: 32
|
| 74 |
+
buffer_storage: tensor
|
config/zz_IDER/er_cifar10_buf500.yaml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar10
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 10
|
| 4 |
+
init_cls_num: &init_cls_num 2
|
| 5 |
+
inc_cls_num: &inc_cls_num 2
|
| 6 |
+
task_num: 5
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 18 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 23 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.1
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: Constant
|
| 50 |
+
kwargs: {}
|
| 51 |
+
|
| 52 |
+
backbone:
|
| 53 |
+
name: resnet18
|
| 54 |
+
kwargs:
|
| 55 |
+
num_classes: *total_cls_num
|
| 56 |
+
args:
|
| 57 |
+
dataset: *dataset
|
| 58 |
+
|
| 59 |
+
buffer:
|
| 60 |
+
name: LinearBuffer
|
| 61 |
+
kwargs:
|
| 62 |
+
buffer_size: 500
|
| 63 |
+
batch_size: 32
|
| 64 |
+
strategy: random
|
| 65 |
+
|
| 66 |
+
classifier:
|
| 67 |
+
name: PaperER
|
| 68 |
+
kwargs:
|
| 69 |
+
num_class: *total_cls_num
|
| 70 |
+
feat_dim: 512
|
| 71 |
+
init_cls_num: *init_cls_num
|
| 72 |
+
inc_cls_num: *inc_cls_num
|
| 73 |
+
minibatch_size: 32
|
| 74 |
+
buffer_storage: tensor
|
config/zz_IDER/er_tinyimagenet_buf4000.yaml
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset processed_tinyimg
|
| 2 |
+
data_root: ./datasets/TINYIMG
|
| 3 |
+
total_cls_num: &total_cls_num 200
|
| 4 |
+
init_cls_num: &init_cls_num 20
|
| 5 |
+
inc_cls_num: &inc_cls_num 20
|
| 6 |
+
task_num: 10
|
| 7 |
+
image_size: 64
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 64
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 18 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 23 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.1
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
args:
|
| 59 |
+
dataset: *dataset
|
| 60 |
+
|
| 61 |
+
buffer:
|
| 62 |
+
name: LinearBuffer
|
| 63 |
+
kwargs:
|
| 64 |
+
buffer_size: 4000
|
| 65 |
+
batch_size: 32
|
| 66 |
+
strategy: random
|
| 67 |
+
|
| 68 |
+
classifier:
|
| 69 |
+
name: PaperER
|
| 70 |
+
kwargs:
|
| 71 |
+
num_class: *total_cls_num
|
| 72 |
+
feat_dim: 512
|
| 73 |
+
init_cls_num: *init_cls_num
|
| 74 |
+
inc_cls_num: *inc_cls_num
|
| 75 |
+
minibatch_size: 32
|
| 76 |
+
buffer_storage: tensor
|
config/zz_IDER/er_tinyimagenet_buf500.yaml
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset processed_tinyimg
|
| 2 |
+
data_root: ./datasets/TINYIMG
|
| 3 |
+
total_cls_num: &total_cls_num 200
|
| 4 |
+
init_cls_num: &init_cls_num 20
|
| 5 |
+
inc_cls_num: &inc_cls_num 20
|
| 6 |
+
task_num: 10
|
| 7 |
+
image_size: 64
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 64
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 18 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 23 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 100
|
| 34 |
+
epoch: 100
|
| 35 |
+
val_per_epoch: 100
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 60, 75]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
args:
|
| 59 |
+
dataset: *dataset
|
| 60 |
+
|
| 61 |
+
buffer:
|
| 62 |
+
name: LinearBuffer
|
| 63 |
+
kwargs:
|
| 64 |
+
buffer_size: 500
|
| 65 |
+
batch_size: 32
|
| 66 |
+
strategy: random
|
| 67 |
+
|
| 68 |
+
classifier:
|
| 69 |
+
name: PaperER
|
| 70 |
+
kwargs:
|
| 71 |
+
num_class: *total_cls_num
|
| 72 |
+
feat_dim: 512
|
| 73 |
+
init_cls_num: *init_cls_num
|
| 74 |
+
inc_cls_num: *inc_cls_num
|
| 75 |
+
minibatch_size: 32
|
| 76 |
+
buffer_storage: tensor
|
config/zz_IDER/ider_cifar100_buf2000.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 100
|
| 4 |
+
init_cls_num: &init_cls_num 10
|
| 5 |
+
inc_cls_num: &inc_cls_num 10
|
| 6 |
+
task_num: &task_num 10
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 18 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 23 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18_id2
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
nf: 64
|
| 59 |
+
use_cos: False
|
| 60 |
+
args:
|
| 61 |
+
dataset: *dataset
|
| 62 |
+
|
| 63 |
+
buffer:
|
| 64 |
+
name: LinearBuffer
|
| 65 |
+
kwargs:
|
| 66 |
+
buffer_size: 2000
|
| 67 |
+
batch_size: 32
|
| 68 |
+
strategy: random
|
| 69 |
+
|
| 70 |
+
classifier:
|
| 71 |
+
name: IDER
|
| 72 |
+
kwargs:
|
| 73 |
+
num_class: *total_cls_num
|
| 74 |
+
feat_dim: 512
|
| 75 |
+
init_cls_num: *init_cls_num
|
| 76 |
+
inc_cls_num: *inc_cls_num
|
| 77 |
+
weighta: 0.5
|
| 78 |
+
weightb: 0.4
|
| 79 |
+
weightc: 0.5
|
| 80 |
+
weightmask: 1.0
|
| 81 |
+
refine_inference: False
|
| 82 |
+
mask_unseen: False
|
| 83 |
+
online_buffer_update: True
|
| 84 |
+
class_balance: True
|
| 85 |
+
print_buffer_stats: True
|
| 86 |
+
buffer_update_after_step: True
|
| 87 |
+
buffer_storage: tensor
|
| 88 |
+
ce_detach_signal: False
|
| 89 |
+
distill_detach_signal: False
|
| 90 |
+
old_model_eval: False
|
| 91 |
+
freeze_old_model: True
|
config/zz_IDER/ider_cifar100_buf500.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 100
|
| 4 |
+
init_cls_num: &init_cls_num 10
|
| 5 |
+
inc_cls_num: &inc_cls_num 10
|
| 6 |
+
task_num: &task_num 10
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 18 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 23 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18_id2
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
nf: 64
|
| 59 |
+
use_cos: False
|
| 60 |
+
args:
|
| 61 |
+
dataset: *dataset
|
| 62 |
+
|
| 63 |
+
buffer:
|
| 64 |
+
name: LinearBuffer
|
| 65 |
+
kwargs:
|
| 66 |
+
buffer_size: 500
|
| 67 |
+
batch_size: 32
|
| 68 |
+
strategy: random
|
| 69 |
+
|
| 70 |
+
classifier:
|
| 71 |
+
name: IDER
|
| 72 |
+
kwargs:
|
| 73 |
+
num_class: *total_cls_num
|
| 74 |
+
feat_dim: 512
|
| 75 |
+
init_cls_num: *init_cls_num
|
| 76 |
+
inc_cls_num: *inc_cls_num
|
| 77 |
+
weighta: 0.5
|
| 78 |
+
weightb: 0.4
|
| 79 |
+
weightc: 0.5
|
| 80 |
+
weightmask: 1.0
|
| 81 |
+
refine_inference: False
|
| 82 |
+
mask_unseen: False
|
| 83 |
+
online_buffer_update: True
|
| 84 |
+
class_balance: True
|
| 85 |
+
print_buffer_stats: True
|
| 86 |
+
buffer_update_after_step: True
|
| 87 |
+
buffer_storage: tensor
|
| 88 |
+
ce_detach_signal: False
|
| 89 |
+
distill_detach_signal: False
|
| 90 |
+
old_model_eval: False
|
| 91 |
+
freeze_old_model: True
|
config/zz_IDER/ider_cifar100_buf500_til.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar100
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 100
|
| 4 |
+
init_cls_num: &init_cls_num 10
|
| 5 |
+
inc_cls_num: &inc_cls_num 10
|
| 6 |
+
task_num: &task_num 10
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 18 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.5071, 0.4867, 0.4408]
|
| 23 |
+
std: [0.2675, 0.2565, 0.2761]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-aware
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18_id2
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
nf: 64
|
| 59 |
+
use_cos: False
|
| 60 |
+
args:
|
| 61 |
+
dataset: *dataset
|
| 62 |
+
|
| 63 |
+
buffer:
|
| 64 |
+
name: LinearBuffer
|
| 65 |
+
kwargs:
|
| 66 |
+
buffer_size: 500
|
| 67 |
+
batch_size: 32
|
| 68 |
+
strategy: random
|
| 69 |
+
|
| 70 |
+
classifier:
|
| 71 |
+
name: IDER
|
| 72 |
+
kwargs:
|
| 73 |
+
num_class: *total_cls_num
|
| 74 |
+
feat_dim: 512
|
| 75 |
+
init_cls_num: *init_cls_num
|
| 76 |
+
inc_cls_num: *inc_cls_num
|
| 77 |
+
weighta: 0.5
|
| 78 |
+
weightb: 0.4
|
| 79 |
+
weightc: 0.5
|
| 80 |
+
weightmask: 1.0
|
| 81 |
+
refine_inference: False
|
| 82 |
+
mask_unseen: False
|
| 83 |
+
online_buffer_update: True
|
| 84 |
+
class_balance: True
|
| 85 |
+
print_buffer_stats: True
|
| 86 |
+
buffer_update_after_step: True
|
| 87 |
+
buffer_storage: tensor
|
| 88 |
+
ce_detach_signal: False
|
| 89 |
+
distill_detach_signal: False
|
| 90 |
+
old_model_eval: False
|
| 91 |
+
freeze_old_model: True
|
config/zz_IDER/ider_cifar10_buf200.yaml
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar10
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 10
|
| 4 |
+
init_cls_num: &init_cls_num 2
|
| 5 |
+
inc_cls_num: &inc_cls_num 2
|
| 6 |
+
task_num: &task_num 5
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 18 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 23 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: Constant
|
| 50 |
+
kwargs: {}
|
| 51 |
+
|
| 52 |
+
backbone:
|
| 53 |
+
name: resnet18_id2
|
| 54 |
+
kwargs:
|
| 55 |
+
num_classes: *total_cls_num
|
| 56 |
+
nf: 64
|
| 57 |
+
use_cos: False
|
| 58 |
+
args:
|
| 59 |
+
dataset: *dataset
|
| 60 |
+
|
| 61 |
+
buffer:
|
| 62 |
+
name: LinearBuffer
|
| 63 |
+
kwargs:
|
| 64 |
+
buffer_size: 200
|
| 65 |
+
batch_size: 32
|
| 66 |
+
strategy: random
|
| 67 |
+
|
| 68 |
+
classifier:
|
| 69 |
+
name: IDER
|
| 70 |
+
kwargs:
|
| 71 |
+
num_class: *total_cls_num
|
| 72 |
+
feat_dim: 512
|
| 73 |
+
init_cls_num: *init_cls_num
|
| 74 |
+
inc_cls_num: *inc_cls_num
|
| 75 |
+
weighta: 0.05
|
| 76 |
+
weightb: 0.05
|
| 77 |
+
weightc: 0.5
|
| 78 |
+
weightmask: 1.0
|
| 79 |
+
refine_inference: False
|
| 80 |
+
mask_unseen: False
|
| 81 |
+
online_buffer_update: True
|
| 82 |
+
class_balance: True
|
| 83 |
+
print_buffer_stats: True
|
| 84 |
+
buffer_update_after_step: True
|
| 85 |
+
buffer_storage: tensor
|
| 86 |
+
ce_detach_signal: False
|
| 87 |
+
distill_detach_signal: False
|
| 88 |
+
old_model_eval: False
|
| 89 |
+
freeze_old_model: True
|
config/zz_IDER/ider_cifar10_buf500.yaml
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset binary_cifar10
|
| 2 |
+
data_root: ./datasets
|
| 3 |
+
total_cls_num: &total_cls_num 10
|
| 4 |
+
init_cls_num: &init_cls_num 2
|
| 5 |
+
inc_cls_num: &inc_cls_num 2
|
| 6 |
+
task_num: &task_num 5
|
| 7 |
+
image_size: 32
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 32
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 18 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4914, 0.4822, 0.4465]
|
| 23 |
+
std: [0.2470, 0.2435, 0.2615]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: Constant
|
| 50 |
+
kwargs: {}
|
| 51 |
+
|
| 52 |
+
backbone:
|
| 53 |
+
name: resnet18_id2
|
| 54 |
+
kwargs:
|
| 55 |
+
num_classes: *total_cls_num
|
| 56 |
+
nf: 64
|
| 57 |
+
use_cos: False
|
| 58 |
+
args:
|
| 59 |
+
dataset: *dataset
|
| 60 |
+
|
| 61 |
+
buffer:
|
| 62 |
+
name: LinearBuffer
|
| 63 |
+
kwargs:
|
| 64 |
+
buffer_size: 500
|
| 65 |
+
batch_size: 32
|
| 66 |
+
strategy: random
|
| 67 |
+
|
| 68 |
+
classifier:
|
| 69 |
+
name: IDER
|
| 70 |
+
kwargs:
|
| 71 |
+
num_class: *total_cls_num
|
| 72 |
+
feat_dim: 512
|
| 73 |
+
init_cls_num: *init_cls_num
|
| 74 |
+
inc_cls_num: *inc_cls_num
|
| 75 |
+
weighta: 0.05
|
| 76 |
+
weightb: 0.05
|
| 77 |
+
weightc: 0.5
|
| 78 |
+
weightmask: 1.0
|
| 79 |
+
refine_inference: False
|
| 80 |
+
mask_unseen: False
|
| 81 |
+
online_buffer_update: True
|
| 82 |
+
class_balance: True
|
| 83 |
+
print_buffer_stats: True
|
| 84 |
+
buffer_update_after_step: True
|
| 85 |
+
buffer_storage: tensor
|
| 86 |
+
ce_detach_signal: False
|
| 87 |
+
distill_detach_signal: False
|
| 88 |
+
old_model_eval: False
|
| 89 |
+
freeze_old_model: True
|
config/zz_IDER/ider_tinyimagenet_buf4000.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset processed_tinyimg
|
| 2 |
+
data_root: ./datasets/TINYIMG
|
| 3 |
+
total_cls_num: &total_cls_num 200
|
| 4 |
+
init_cls_num: &init_cls_num 20
|
| 5 |
+
inc_cls_num: &inc_cls_num 20
|
| 6 |
+
task_num: &task_num 10
|
| 7 |
+
image_size: 64
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 64
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 18 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 23 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18_id2
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
nf: 64
|
| 59 |
+
use_cos: False
|
| 60 |
+
args:
|
| 61 |
+
dataset: *dataset
|
| 62 |
+
|
| 63 |
+
buffer:
|
| 64 |
+
name: LinearBuffer
|
| 65 |
+
kwargs:
|
| 66 |
+
buffer_size: 4000
|
| 67 |
+
batch_size: 32
|
| 68 |
+
strategy: random
|
| 69 |
+
|
| 70 |
+
classifier:
|
| 71 |
+
name: IDER
|
| 72 |
+
kwargs:
|
| 73 |
+
num_class: *total_cls_num
|
| 74 |
+
feat_dim: 512
|
| 75 |
+
init_cls_num: *init_cls_num
|
| 76 |
+
inc_cls_num: *inc_cls_num
|
| 77 |
+
weighta: 0.5
|
| 78 |
+
weightb: 0.4
|
| 79 |
+
weightc: 0.5
|
| 80 |
+
weightmask: 1.0
|
| 81 |
+
refine_inference: False
|
| 82 |
+
mask_unseen: False
|
| 83 |
+
online_buffer_update: True
|
| 84 |
+
class_balance: True
|
| 85 |
+
print_buffer_stats: True
|
| 86 |
+
buffer_update_after_step: True
|
| 87 |
+
buffer_storage: tensor
|
| 88 |
+
ce_detach_signal: False
|
| 89 |
+
distill_detach_signal: False
|
| 90 |
+
old_model_eval: False
|
| 91 |
+
freeze_old_model: True
|
config/zz_IDER/ider_tinyimagenet_buf500.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset processed_tinyimg
|
| 2 |
+
data_root: ./datasets/TINYIMG
|
| 3 |
+
total_cls_num: &total_cls_num 200
|
| 4 |
+
init_cls_num: &init_cls_num 20
|
| 5 |
+
inc_cls_num: &inc_cls_num 20
|
| 6 |
+
task_num: &task_num 10
|
| 7 |
+
image_size: 64
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 64
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 18 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 23 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 50
|
| 34 |
+
epoch: 50
|
| 35 |
+
val_per_epoch: 50
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 45]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18_id2
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
nf: 64
|
| 59 |
+
use_cos: False
|
| 60 |
+
args:
|
| 61 |
+
dataset: *dataset
|
| 62 |
+
|
| 63 |
+
buffer:
|
| 64 |
+
name: LinearBuffer
|
| 65 |
+
kwargs:
|
| 66 |
+
buffer_size: 500
|
| 67 |
+
batch_size: 32
|
| 68 |
+
strategy: random
|
| 69 |
+
|
| 70 |
+
classifier:
|
| 71 |
+
name: IDER
|
| 72 |
+
kwargs:
|
| 73 |
+
num_class: *total_cls_num
|
| 74 |
+
feat_dim: 512
|
| 75 |
+
init_cls_num: *init_cls_num
|
| 76 |
+
inc_cls_num: *inc_cls_num
|
| 77 |
+
weighta: 0.5
|
| 78 |
+
weightb: 0.4
|
| 79 |
+
weightc: 0.5
|
| 80 |
+
weightmask: 1.0
|
| 81 |
+
refine_inference: False
|
| 82 |
+
mask_unseen: False
|
| 83 |
+
online_buffer_update: True
|
| 84 |
+
class_balance: True
|
| 85 |
+
print_buffer_stats: True
|
| 86 |
+
buffer_update_after_step: True
|
| 87 |
+
buffer_storage: tensor
|
| 88 |
+
ce_detach_signal: False
|
| 89 |
+
distill_detach_signal: False
|
| 90 |
+
old_model_eval: False
|
| 91 |
+
freeze_old_model: True
|
config/zz_IDER/ider_tinyimagenet_buf500_paper.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: &dataset processed_tinyimg
|
| 2 |
+
data_root: ./datasets/TINYIMG
|
| 3 |
+
total_cls_num: &total_cls_num 200
|
| 4 |
+
init_cls_num: &init_cls_num 20
|
| 5 |
+
inc_cls_num: &inc_cls_num 20
|
| 6 |
+
task_num: &task_num 10
|
| 7 |
+
image_size: 64
|
| 8 |
+
save_path: ./
|
| 9 |
+
|
| 10 |
+
train_trfms:
|
| 11 |
+
- RandomCrop:
|
| 12 |
+
size: 64
|
| 13 |
+
padding: 4
|
| 14 |
+
- RandomHorizontalFlip: {}
|
| 15 |
+
- ToTensor: {}
|
| 16 |
+
- Normalize:
|
| 17 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 18 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 19 |
+
test_trfms:
|
| 20 |
+
- ToTensor: {}
|
| 21 |
+
- Normalize:
|
| 22 |
+
mean: [0.4802, 0.4480, 0.3975]
|
| 23 |
+
std: [0.2770, 0.2691, 0.2821]
|
| 24 |
+
|
| 25 |
+
seed: 0
|
| 26 |
+
deterministic: True
|
| 27 |
+
device_ids: auto
|
| 28 |
+
n_gpu: 1
|
| 29 |
+
num_workers: 4
|
| 30 |
+
pin_memory: False
|
| 31 |
+
|
| 32 |
+
batch_size: 32
|
| 33 |
+
init_epoch: 100
|
| 34 |
+
epoch: 100
|
| 35 |
+
val_per_epoch: 100
|
| 36 |
+
testing_times: 1
|
| 37 |
+
testing_per_task: True
|
| 38 |
+
setting: task-agnostic
|
| 39 |
+
eval_with_test: True
|
| 40 |
+
|
| 41 |
+
optimizer:
|
| 42 |
+
name: SGD
|
| 43 |
+
kwargs:
|
| 44 |
+
lr: 0.03
|
| 45 |
+
momentum: 0.0
|
| 46 |
+
weight_decay: 0.0
|
| 47 |
+
|
| 48 |
+
lr_scheduler:
|
| 49 |
+
name: MultiStepLR
|
| 50 |
+
kwargs:
|
| 51 |
+
gamma: 0.1
|
| 52 |
+
milestones: [35, 60, 75]
|
| 53 |
+
|
| 54 |
+
backbone:
|
| 55 |
+
name: resnet18_id2
|
| 56 |
+
kwargs:
|
| 57 |
+
num_classes: *total_cls_num
|
| 58 |
+
nf: 64
|
| 59 |
+
use_cos: False
|
| 60 |
+
args:
|
| 61 |
+
dataset: *dataset
|
| 62 |
+
|
| 63 |
+
buffer:
|
| 64 |
+
name: LinearBuffer
|
| 65 |
+
kwargs:
|
| 66 |
+
buffer_size: 500
|
| 67 |
+
batch_size: 32
|
| 68 |
+
strategy: random
|
| 69 |
+
|
| 70 |
+
classifier:
|
| 71 |
+
name: IDER
|
| 72 |
+
kwargs:
|
| 73 |
+
num_class: *total_cls_num
|
| 74 |
+
feat_dim: 512
|
| 75 |
+
init_cls_num: *init_cls_num
|
| 76 |
+
inc_cls_num: *inc_cls_num
|
| 77 |
+
weighta: 0.5
|
| 78 |
+
weightb: 0.4
|
| 79 |
+
weightc: 0.5
|
| 80 |
+
weightmask: 1.0
|
| 81 |
+
refine_inference: False
|
| 82 |
+
mask_unseen: False
|
| 83 |
+
online_buffer_update: True
|
| 84 |
+
class_balance: True
|
| 85 |
+
print_buffer_stats: True
|
| 86 |
+
buffer_update_after_step: True
|
| 87 |
+
buffer_storage: tensor
|
| 88 |
+
ce_detach_signal: False
|
| 89 |
+
distill_detach_signal: False
|
| 90 |
+
old_model_eval: False
|
| 91 |
+
freeze_old_model: True
|
core/data/dataloader.py
CHANGED
|
@@ -108,7 +108,18 @@ def get_dataloader(config, mode, cls_map=None):
|
|
| 108 |
_, cls_code, cls_name = line.strip().split('\t')
|
| 109 |
cls_map[cls_code] = cls_name
|
| 110 |
|
| 111 |
-
elif cls_map is None and dataset
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
# Apply class_order for debugging
|
| 113 |
cls_list = sorted(os.listdir(os.path.join(data_root, mode)))
|
| 114 |
#random.shuffle(cls_list)
|
|
|
|
| 108 |
_, cls_code, cls_name = line.strip().split('\t')
|
| 109 |
cls_map[cls_code] = cls_name
|
| 110 |
|
| 111 |
+
elif cls_map is None and dataset in ['binary_cifar10', 'binary_cifar100']:
|
| 112 |
+
num_classes = 10 if dataset == 'binary_cifar10' else 100
|
| 113 |
+
if 'class_order' in config.keys():
|
| 114 |
+
class_order = config['class_order']
|
| 115 |
+
elif config.get('shuffle', False):
|
| 116 |
+
rng = np.random.RandomState(config['seed'])
|
| 117 |
+
class_order = rng.permutation(num_classes).tolist()
|
| 118 |
+
else:
|
| 119 |
+
class_order = list(range(num_classes))
|
| 120 |
+
cls_map = {label: ori_label for label, ori_label in enumerate(class_order)}
|
| 121 |
+
|
| 122 |
+
elif cls_map is None and dataset not in ['binary_cifar10', 'binary_cifar100', 'processed_tinyimg']:
|
| 123 |
# Apply class_order for debugging
|
| 124 |
cls_list = sorted(os.listdir(os.path.join(data_root, mode)))
|
| 125 |
#random.shuffle(cls_list)
|
core/data/dataset.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import os
|
| 2 |
import torch
|
| 3 |
import pickle
|
| 4 |
-
import random
|
| 5 |
import numpy as np
|
| 6 |
|
| 7 |
from PIL import Image
|
|
@@ -11,6 +10,15 @@ from torch.utils.data.distributed import DistributedSampler
|
|
| 11 |
from continuum.datasets import TinyImageNet200
|
| 12 |
from continuum import ClassIncremental
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
class ContinualDatasets:
|
| 15 |
def __init__(self, dataset, mode, task_num, init_cls_num, inc_cls_num, data_root, cls_map, trfms, batchsize, num_workers, config):
|
| 16 |
self.mode = mode
|
|
@@ -25,8 +33,18 @@ class ContinualDatasets:
|
|
| 25 |
self.config = config
|
| 26 |
self.dataset = dataset
|
| 27 |
|
| 28 |
-
if self.dataset
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
self.create_loaders()
|
| 32 |
|
|
@@ -37,10 +55,11 @@ class ContinualDatasets:
|
|
| 37 |
|
| 38 |
if 'class_order' in self.config:
|
| 39 |
class_order = self.config['class_order']
|
|
|
|
|
|
|
|
|
|
| 40 |
else:
|
| 41 |
class_order = list(range(200))
|
| 42 |
-
random.seed(self.config['seed'])
|
| 43 |
-
random.shuffle(class_order)
|
| 44 |
|
| 45 |
scenario = ClassIncremental(
|
| 46 |
TinyImageNet200(self.data_root, train=self.mode == 'train', download=True),
|
|
@@ -69,7 +88,7 @@ class ContinualDatasets:
|
|
| 69 |
|
| 70 |
self.dataloaders.append(DataLoader(
|
| 71 |
dataset,
|
| 72 |
-
shuffle =
|
| 73 |
batch_size = self.batchsize,
|
| 74 |
drop_last = False,
|
| 75 |
num_workers = self.num_workers,
|
|
@@ -84,11 +103,11 @@ class ContinualDatasets:
|
|
| 84 |
end_idx = start_idx + (self.init_cls_num if i ==0 else self.inc_cls_num)
|
| 85 |
self.dataloaders.append(DataLoader(
|
| 86 |
SingleDataset(self.dataset, self.data_root, self.mode, self.init_cls_num, self.inc_cls_num, self.cls_map, self.trfms, start_idx, end_idx),
|
| 87 |
-
shuffle =
|
| 88 |
batch_size = self.batchsize,
|
| 89 |
drop_last = False,
|
| 90 |
num_workers = self.num_workers,
|
| 91 |
-
pin_memory=
|
| 92 |
))
|
| 93 |
|
| 94 |
def get_loader(self, task_idx):
|
|
@@ -246,7 +265,7 @@ class SingleDataset(Dataset):
|
|
| 246 |
self.images, self.labels, self.labels_name = self._init_datalist()
|
| 247 |
|
| 248 |
def __getitem__(self, idx):
|
| 249 |
-
if self.dataset
|
| 250 |
|
| 251 |
image = self.images[idx]
|
| 252 |
image = Image.fromarray(np.uint8(image))
|
|
@@ -255,6 +274,12 @@ class SingleDataset(Dataset):
|
|
| 255 |
img_path = self.images[idx]
|
| 256 |
image = Image.open(img_path).convert("RGB")
|
| 257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
else:
|
| 259 |
|
| 260 |
img_path = self.images[idx]
|
|
@@ -263,7 +288,7 @@ class SingleDataset(Dataset):
|
|
| 263 |
label = self.labels[idx]
|
| 264 |
image = self.trfms(image)
|
| 265 |
|
| 266 |
-
return {"image": image, "label": label}
|
| 267 |
|
| 268 |
def __len__(self,):
|
| 269 |
return len(self.labels)
|
|
@@ -272,22 +297,67 @@ class SingleDataset(Dataset):
|
|
| 272 |
|
| 273 |
imgs, labels, labels_name = [], [], []
|
| 274 |
|
| 275 |
-
if self.dataset
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
-
for data, label in zip(
|
| 281 |
|
| 282 |
-
if label in
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
r = data[:1024].reshape(32, 32)
|
| 284 |
g = data[1024:2048].reshape(32, 32)
|
| 285 |
b = data[2048:].reshape(32, 32)
|
| 286 |
|
| 287 |
tt_data = np.dstack((r, g, b))
|
| 288 |
-
|
| 289 |
imgs.append(tt_data)
|
| 290 |
-
labels.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
labels_name.append(label)
|
| 292 |
|
| 293 |
else:
|
|
@@ -301,4 +371,4 @@ class SingleDataset(Dataset):
|
|
| 301 |
return imgs, labels, labels_name
|
| 302 |
|
| 303 |
def get_class_names(self):
|
| 304 |
-
return self.labels_name
|
|
|
|
| 1 |
import os
|
| 2 |
import torch
|
| 3 |
import pickle
|
|
|
|
| 4 |
import numpy as np
|
| 5 |
|
| 6 |
from PIL import Image
|
|
|
|
| 10 |
from continuum.datasets import TinyImageNet200
|
| 11 |
from continuum import ClassIncremental
|
| 12 |
|
| 13 |
+
|
| 14 |
+
def _processed_tiny_dir(data_root):
|
| 15 |
+
processed_dir = os.path.join(data_root, "processed")
|
| 16 |
+
if os.path.isdir(processed_dir):
|
| 17 |
+
return processed_dir
|
| 18 |
+
if os.path.basename(os.path.normpath(data_root)) == "processed":
|
| 19 |
+
return data_root
|
| 20 |
+
return processed_dir
|
| 21 |
+
|
| 22 |
class ContinualDatasets:
|
| 23 |
def __init__(self, dataset, mode, task_num, init_cls_num, inc_cls_num, data_root, cls_map, trfms, batchsize, num_workers, config):
|
| 24 |
self.mode = mode
|
|
|
|
| 33 |
self.config = config
|
| 34 |
self.dataset = dataset
|
| 35 |
|
| 36 |
+
if self.dataset in ['binary_cifar10', 'binary_cifar100']:
|
| 37 |
+
num_classes = 10 if self.dataset == 'binary_cifar10' else 100
|
| 38 |
+
if self.cls_map is None:
|
| 39 |
+
class_order = self.config.get('class_order', list(range(num_classes)))
|
| 40 |
+
self.cls_map = {label: ori_label for label, ori_label in enumerate(class_order)}
|
| 41 |
+
if self.dataset == 'binary_cifar10':
|
| 42 |
+
datasets.CIFAR10(self.data_root, download=True)
|
| 43 |
+
else:
|
| 44 |
+
datasets.CIFAR100(self.data_root, download=True)
|
| 45 |
+
elif self.dataset == 'processed_tinyimg':
|
| 46 |
+
class_order = self.config.get('class_order', list(range(200)))
|
| 47 |
+
self.cls_map = {label: ori_label for label, ori_label in enumerate(class_order)}
|
| 48 |
|
| 49 |
self.create_loaders()
|
| 50 |
|
|
|
|
| 55 |
|
| 56 |
if 'class_order' in self.config:
|
| 57 |
class_order = self.config['class_order']
|
| 58 |
+
elif self.config.get('shuffle', False):
|
| 59 |
+
rng = np.random.RandomState(self.config['seed'])
|
| 60 |
+
class_order = rng.permutation(200).tolist()
|
| 61 |
else:
|
| 62 |
class_order = list(range(200))
|
|
|
|
|
|
|
| 63 |
|
| 64 |
scenario = ClassIncremental(
|
| 65 |
TinyImageNet200(self.data_root, train=self.mode == 'train', download=True),
|
|
|
|
| 88 |
|
| 89 |
self.dataloaders.append(DataLoader(
|
| 90 |
dataset,
|
| 91 |
+
shuffle = self.mode == 'train',
|
| 92 |
batch_size = self.batchsize,
|
| 93 |
drop_last = False,
|
| 94 |
num_workers = self.num_workers,
|
|
|
|
| 103 |
end_idx = start_idx + (self.init_cls_num if i ==0 else self.inc_cls_num)
|
| 104 |
self.dataloaders.append(DataLoader(
|
| 105 |
SingleDataset(self.dataset, self.data_root, self.mode, self.init_cls_num, self.inc_cls_num, self.cls_map, self.trfms, start_idx, end_idx),
|
| 106 |
+
shuffle = self.mode == 'train',
|
| 107 |
batch_size = self.batchsize,
|
| 108 |
drop_last = False,
|
| 109 |
num_workers = self.num_workers,
|
| 110 |
+
pin_memory=self.config['pin_memory']
|
| 111 |
))
|
| 112 |
|
| 113 |
def get_loader(self, task_idx):
|
|
|
|
| 265 |
self.images, self.labels, self.labels_name = self._init_datalist()
|
| 266 |
|
| 267 |
def __getitem__(self, idx):
|
| 268 |
+
if self.dataset in ['binary_cifar10', 'binary_cifar100']:
|
| 269 |
|
| 270 |
image = self.images[idx]
|
| 271 |
image = Image.fromarray(np.uint8(image))
|
|
|
|
| 274 |
img_path = self.images[idx]
|
| 275 |
image = Image.open(img_path).convert("RGB")
|
| 276 |
|
| 277 |
+
elif self.dataset == 'processed_tinyimg':
|
| 278 |
+
image = self.images[idx]
|
| 279 |
+
if np.max(image) <= 1.0:
|
| 280 |
+
image = np.uint8(255 * image)
|
| 281 |
+
image = Image.fromarray(np.uint8(image)).convert("RGB")
|
| 282 |
+
|
| 283 |
else:
|
| 284 |
|
| 285 |
img_path = self.images[idx]
|
|
|
|
| 288 |
label = self.labels[idx]
|
| 289 |
image = self.trfms(image)
|
| 290 |
|
| 291 |
+
return {"image": image, "label": label, "index": idx}
|
| 292 |
|
| 293 |
def __len__(self,):
|
| 294 |
return len(self.labels)
|
|
|
|
| 297 |
|
| 298 |
imgs, labels, labels_name = [], [], []
|
| 299 |
|
| 300 |
+
if self.dataset in ['binary_cifar10', 'binary_cifar100']:
|
| 301 |
+
if self.dataset == 'binary_cifar10':
|
| 302 |
+
data_dir = os.path.join(self.data_root, 'cifar-10-batches-py')
|
| 303 |
+
file_names = [f'data_batch_{idx}' for idx in range(1, 6)] if self.mode == 'train' else ['test_batch']
|
| 304 |
+
data_chunks, label_chunks = [], []
|
| 305 |
+
for file_name in file_names:
|
| 306 |
+
with open(os.path.join(data_dir, file_name), 'rb') as f:
|
| 307 |
+
load_data = pickle.load(f, encoding='latin1')
|
| 308 |
+
data_chunks.append(load_data['data'])
|
| 309 |
+
label_chunks.extend(load_data['labels'])
|
| 310 |
+
raw_data = np.concatenate(data_chunks, axis=0)
|
| 311 |
+
raw_labels = label_chunks
|
| 312 |
+
else:
|
| 313 |
+
with open(os.path.join(self.data_root, 'cifar-100-python', self.mode), 'rb') as f:
|
| 314 |
+
load_data = pickle.load(f, encoding='latin1')
|
| 315 |
+
raw_data = load_data['data']
|
| 316 |
+
raw_labels = load_data['fine_labels']
|
| 317 |
+
|
| 318 |
+
num_classes = 10 if self.dataset == 'binary_cifar10' else 100
|
| 319 |
+
cls_map = self.cls_map or {label: label for label in range(num_classes)}
|
| 320 |
+
new_label_by_old = {old_label: new_label for new_label, old_label in cls_map.items()}
|
| 321 |
|
| 322 |
+
for data, label in zip(raw_data, raw_labels):
|
| 323 |
|
| 324 |
+
if label not in new_label_by_old:
|
| 325 |
+
continue
|
| 326 |
+
|
| 327 |
+
new_label = new_label_by_old[label]
|
| 328 |
+
if new_label in range(self.start_idx, self.end_idx):
|
| 329 |
r = data[:1024].reshape(32, 32)
|
| 330 |
g = data[1024:2048].reshape(32, 32)
|
| 331 |
b = data[2048:].reshape(32, 32)
|
| 332 |
|
| 333 |
tt_data = np.dstack((r, g, b))
|
|
|
|
| 334 |
imgs.append(tt_data)
|
| 335 |
+
labels.append(new_label)
|
| 336 |
+
labels_name.append(new_label)
|
| 337 |
+
|
| 338 |
+
elif self.dataset == 'processed_tinyimg':
|
| 339 |
+
split = 'train' if self.mode == 'train' else 'val'
|
| 340 |
+
processed_dir = _processed_tiny_dir(self.data_root)
|
| 341 |
+
label_range = range(self.start_idx, self.end_idx)
|
| 342 |
+
ori_to_label = {self.cls_map[id]: id for id in label_range}
|
| 343 |
+
target_classes = np.array(list(ori_to_label.keys()))
|
| 344 |
+
|
| 345 |
+
for num in range(20):
|
| 346 |
+
data_path = os.path.join(processed_dir, f'x_{split}_{num + 1:02d}.npy')
|
| 347 |
+
label_path = os.path.join(processed_dir, f'y_{split}_{num + 1:02d}.npy')
|
| 348 |
+
raw_labels = np.load(label_path)
|
| 349 |
+
keep_mask = np.isin(raw_labels, target_classes)
|
| 350 |
+
if not np.any(keep_mask):
|
| 351 |
+
continue
|
| 352 |
+
|
| 353 |
+
raw_data = np.load(data_path, mmap_mode='r')
|
| 354 |
+
kept_data = raw_data[keep_mask]
|
| 355 |
+
kept_labels = raw_labels[keep_mask]
|
| 356 |
+
|
| 357 |
+
for data, label in zip(kept_data, kept_labels):
|
| 358 |
+
label = int(label)
|
| 359 |
+
imgs.append(np.asarray(data))
|
| 360 |
+
labels.append(ori_to_label[label])
|
| 361 |
labels_name.append(label)
|
| 362 |
|
| 363 |
else:
|
|
|
|
| 371 |
return imgs, labels, labels_name
|
| 372 |
|
| 373 |
def get_class_names(self):
|
| 374 |
+
return self.labels_name
|
core/model/__init__.py
CHANGED
|
@@ -8,10 +8,13 @@ from .lwf import LWF
|
|
| 8 |
from .wa import WA
|
| 9 |
from .bic import bic
|
| 10 |
from .ewc import EWC
|
|
|
|
| 11 |
from .ocm import OCM
|
| 12 |
from .eraml import ERAML
|
| 13 |
from .erace import ERACE
|
| 14 |
from .der import DER
|
|
|
|
|
|
|
| 15 |
from .dualprompt import DualPrompt
|
| 16 |
from .l2p import L2P
|
| 17 |
from .codaprompt import CodaPrompt
|
|
@@ -31,4 +34,5 @@ from .api import API
|
|
| 31 |
from .dap import DAP
|
| 32 |
from .sd_lora import SD_LoRA
|
| 33 |
from .lora_sub import LoRAsub_DRS
|
| 34 |
-
from .cl_lora import CL_LoRA
|
|
|
|
|
|
| 8 |
from .wa import WA
|
| 9 |
from .bic import bic
|
| 10 |
from .ewc import EWC
|
| 11 |
+
from .ewcdr import EWCDR
|
| 12 |
from .ocm import OCM
|
| 13 |
from .eraml import ERAML
|
| 14 |
from .erace import ERACE
|
| 15 |
from .der import DER
|
| 16 |
+
from .ider import IDER
|
| 17 |
+
from .paper_er import PaperER
|
| 18 |
from .dualprompt import DualPrompt
|
| 19 |
from .l2p import L2P
|
| 20 |
from .codaprompt import CodaPrompt
|
|
|
|
| 34 |
from .dap import DAP
|
| 35 |
from .sd_lora import SD_LoRA
|
| 36 |
from .lora_sub import LoRAsub_DRS
|
| 37 |
+
from .cl_lora import CL_LoRA
|
| 38 |
+
from .fly_cl import FlyCL
|
core/model/backbone/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
from .resnet import *
|
|
|
|
| 2 |
from .vit import vit_pt_imnet
|
| 3 |
from .vit import vit_pt_imnet_in21k_adapter
|
| 4 |
from .vit import vit_cl_lora
|
|
@@ -9,6 +10,7 @@ from .SiNet import SiNet_vit
|
|
| 9 |
from .resnet_cbam import *
|
| 10 |
from .alexnet import AlexNet_TRGP, AlexNet_API
|
| 11 |
from .clip import clip
|
|
|
|
| 12 |
|
| 13 |
def get_backbone(config):
|
| 14 |
"""
|
|
|
|
| 1 |
from .resnet import *
|
| 2 |
+
from .resnet_id import *
|
| 3 |
from .vit import vit_pt_imnet
|
| 4 |
from .vit import vit_pt_imnet_in21k_adapter
|
| 5 |
from .vit import vit_cl_lora
|
|
|
|
| 10 |
from .resnet_cbam import *
|
| 11 |
from .alexnet import AlexNet_TRGP, AlexNet_API
|
| 12 |
from .clip import clip
|
| 13 |
+
from .timm_backbone import timm_backbone
|
| 14 |
|
| 15 |
def get_backbone(config):
|
| 16 |
"""
|
core/model/backbone/resnet.py
CHANGED
|
@@ -133,6 +133,12 @@ class ResNet(nn.Module):
|
|
| 133 |
if 'cifar' in args["dataset"] or '5-datasets' in args["dataset"]:
|
| 134 |
self.conv1 = nn.Sequential(nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False),
|
| 135 |
nn.BatchNorm2d(self.inplanes), nn.ReLU(inplace=True))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
elif 'imagenet' in args["dataset"]:
|
| 137 |
if args["init_cls_num"] == args["inc_cls_num"]:
|
| 138 |
self.conv1 = nn.Sequential(
|
|
|
|
| 133 |
if 'cifar' in args["dataset"] or '5-datasets' in args["dataset"]:
|
| 134 |
self.conv1 = nn.Sequential(nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False),
|
| 135 |
nn.BatchNorm2d(self.inplanes), nn.ReLU(inplace=True))
|
| 136 |
+
elif 'tinyimg' in args["dataset"] or 'tiny-imagenet' in args["dataset"]:
|
| 137 |
+
# The original IDER/Mammoth ResNet18 uses a CIFAR-style 3x3
|
| 138 |
+
# stem for TinyImageNet instead of the torchvision ImageNet
|
| 139 |
+
# 7x7 stride-2 stem.
|
| 140 |
+
self.conv1 = nn.Sequential(nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False),
|
| 141 |
+
nn.BatchNorm2d(self.inplanes), nn.ReLU(inplace=True))
|
| 142 |
elif 'imagenet' in args["dataset"]:
|
| 143 |
if args["init_cls_num"] == args["inc_cls_num"]:
|
| 144 |
self.conv1 = nn.Sequential(
|
core/model/backbone/resnet_id.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
|
| 5 |
+
__all__ = ["resnet18_id2"]
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def conv3x3(in_planes, out_planes, stride=1):
|
| 9 |
+
return nn.Conv2d(
|
| 10 |
+
in_planes,
|
| 11 |
+
out_planes,
|
| 12 |
+
kernel_size=3,
|
| 13 |
+
stride=stride,
|
| 14 |
+
padding=1,
|
| 15 |
+
bias=False,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class BasicBlock(nn.Module):
|
| 20 |
+
expansion = 1
|
| 21 |
+
|
| 22 |
+
def __init__(self, in_planes, planes, stride=1):
|
| 23 |
+
super().__init__()
|
| 24 |
+
self.conv1 = conv3x3(in_planes, planes, stride)
|
| 25 |
+
self.bn1 = nn.BatchNorm2d(planes)
|
| 26 |
+
self.conv2 = conv3x3(planes, planes)
|
| 27 |
+
self.bn2 = nn.BatchNorm2d(planes)
|
| 28 |
+
|
| 29 |
+
self.shortcut = nn.Sequential()
|
| 30 |
+
if stride != 1 or in_planes != self.expansion * planes:
|
| 31 |
+
self.shortcut = nn.Sequential(
|
| 32 |
+
nn.Conv2d(
|
| 33 |
+
in_planes,
|
| 34 |
+
self.expansion * planes,
|
| 35 |
+
kernel_size=1,
|
| 36 |
+
stride=stride,
|
| 37 |
+
bias=False,
|
| 38 |
+
),
|
| 39 |
+
nn.BatchNorm2d(self.expansion * planes),
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def forward(self, x):
|
| 43 |
+
out = F.relu(self.bn1(self.conv1(x)), inplace=True)
|
| 44 |
+
out = self.bn2(self.conv2(out))
|
| 45 |
+
out = out + self.shortcut(x)
|
| 46 |
+
return F.relu(out, inplace=True)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class CosineClassifier(nn.Module):
|
| 50 |
+
def __init__(self, feat_dim, num_classes, temperature=12.0):
|
| 51 |
+
super().__init__()
|
| 52 |
+
fc = nn.Linear(feat_dim, num_classes)
|
| 53 |
+
self.weight = nn.Parameter(fc.weight.t())
|
| 54 |
+
self.bias = nn.Parameter(fc.bias)
|
| 55 |
+
self.temperature = nn.Parameter(torch.tensor([temperature]), requires_grad=False)
|
| 56 |
+
|
| 57 |
+
def forward(self, features):
|
| 58 |
+
features = F.normalize(features, p=2, dim=1, eps=1e-12)
|
| 59 |
+
weight = F.normalize(self.weight, p=2, dim=0, eps=1e-12)
|
| 60 |
+
return self.temperature * torch.mm(features, weight)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class ResNetStage1(nn.Module):
|
| 64 |
+
def __init__(self, block, num_blocks, nf):
|
| 65 |
+
super().__init__()
|
| 66 |
+
self.in_planes = nf
|
| 67 |
+
self.conv1 = conv3x3(3, nf)
|
| 68 |
+
self.bn1 = nn.BatchNorm2d(nf)
|
| 69 |
+
self.layer1 = self._make_layer(block, nf, num_blocks[0], stride=1)
|
| 70 |
+
self.layer2 = self._make_layer(block, nf * 2, num_blocks[1], stride=2)
|
| 71 |
+
|
| 72 |
+
def _make_layer(self, block, planes, num_blocks, stride):
|
| 73 |
+
strides = [stride] + [1] * (num_blocks - 1)
|
| 74 |
+
layers = []
|
| 75 |
+
for stride in strides:
|
| 76 |
+
layers.append(block(self.in_planes, planes, stride))
|
| 77 |
+
self.in_planes = planes * block.expansion
|
| 78 |
+
return nn.Sequential(*layers)
|
| 79 |
+
|
| 80 |
+
def forward(self, x):
|
| 81 |
+
out = F.relu(self.bn1(self.conv1(x)), inplace=True)
|
| 82 |
+
out = self.layer1(out)
|
| 83 |
+
return self.layer2(out)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class ResNetStage2(nn.Module):
|
| 87 |
+
def __init__(self, block, num_blocks, num_classes, nf, use_cos=False):
|
| 88 |
+
super().__init__()
|
| 89 |
+
self.in_planes = nf * 2
|
| 90 |
+
self.num_classes = num_classes
|
| 91 |
+
self.out_dim = nf * 8 * block.expansion
|
| 92 |
+
self.label_fc = nn.Sequential(
|
| 93 |
+
nn.Linear(num_classes, nf * 2),
|
| 94 |
+
nn.LeakyReLU(inplace=True),
|
| 95 |
+
)
|
| 96 |
+
self.layer3 = self._make_layer(block, nf * 4, num_blocks[2], stride=2)
|
| 97 |
+
self.layer4 = self._make_layer(block, nf * 8, num_blocks[3], stride=2)
|
| 98 |
+
if use_cos:
|
| 99 |
+
self.classifier = CosineClassifier(self.out_dim, num_classes)
|
| 100 |
+
else:
|
| 101 |
+
self.classifier = nn.Linear(self.out_dim, num_classes)
|
| 102 |
+
|
| 103 |
+
def _make_layer(self, block, planes, num_blocks, stride):
|
| 104 |
+
strides = [stride] + [1] * (num_blocks - 1)
|
| 105 |
+
layers = []
|
| 106 |
+
for stride in strides:
|
| 107 |
+
layers.append(block(self.in_planes, planes, stride))
|
| 108 |
+
self.in_planes = planes * block.expansion
|
| 109 |
+
return nn.Sequential(*layers)
|
| 110 |
+
|
| 111 |
+
def forward(self, features, label_signal):
|
| 112 |
+
out = features + self.label_fc(label_signal)[..., None, None]
|
| 113 |
+
out = self.layer3(out)
|
| 114 |
+
out = self.layer4(out)
|
| 115 |
+
pooled = F.avg_pool2d(out, out.shape[2])
|
| 116 |
+
flat = torch.flatten(pooled, 1)
|
| 117 |
+
logits = self.classifier(flat)
|
| 118 |
+
return logits[:, : self.num_classes], flat, out
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class IdempotentResNet(nn.Module):
|
| 122 |
+
"""ResNet-18 split for IDER.
|
| 123 |
+
|
| 124 |
+
The image path produces an intermediate feature map. The second input is a
|
| 125 |
+
label/probability signal projected to the same channel dimension and added
|
| 126 |
+
before the later ResNet stages, following the official IDER implementation.
|
| 127 |
+
"""
|
| 128 |
+
|
| 129 |
+
def __init__(self, block, num_blocks, num_classes=100, nf=64, use_cos=False, **kwargs):
|
| 130 |
+
super().__init__()
|
| 131 |
+
self.num_classes = num_classes
|
| 132 |
+
self.out_dim = nf * 8 * block.expansion
|
| 133 |
+
self.f1 = ResNetStage1(block, num_blocks, nf)
|
| 134 |
+
self.f2 = ResNetStage2(block, num_blocks, num_classes, nf, use_cos=use_cos)
|
| 135 |
+
|
| 136 |
+
def empty_signal(self, batch_size, device, upto=None):
|
| 137 |
+
upto = self.num_classes if upto is None else min(upto, self.num_classes)
|
| 138 |
+
signal = torch.zeros(batch_size, self.num_classes, device=device)
|
| 139 |
+
signal[:, :upto] = 1.0 / max(upto, 1)
|
| 140 |
+
return signal
|
| 141 |
+
|
| 142 |
+
def forward(self, x, y=None, upto=None, returnt="all"):
|
| 143 |
+
if y is None:
|
| 144 |
+
y = self.empty_signal(x.size(0), x.device, upto=upto)
|
| 145 |
+
|
| 146 |
+
stage1_features = self.f1(x)
|
| 147 |
+
logits, features, fmap = self.f2(stage1_features, y)
|
| 148 |
+
|
| 149 |
+
if returnt == "logits":
|
| 150 |
+
return logits
|
| 151 |
+
if returnt == "features":
|
| 152 |
+
return {"features": features}
|
| 153 |
+
return {"logits": logits, "features": features, "fmaps": [stage1_features, fmap]}
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def resnet18_id2(pretrained=False, progress=True, num_classes=100, nf=64, use_cos=False, **kwargs):
|
| 157 |
+
if "nclasses" in kwargs:
|
| 158 |
+
num_classes = kwargs["nclasses"]
|
| 159 |
+
return IdempotentResNet(
|
| 160 |
+
BasicBlock,
|
| 161 |
+
[2, 2, 2, 2],
|
| 162 |
+
num_classes=num_classes,
|
| 163 |
+
nf=nf,
|
| 164 |
+
use_cos=use_cos,
|
| 165 |
+
)
|
core/model/backbone/timm_backbone.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import timm
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class TimmBackbone(nn.Module):
|
| 6 |
+
|
| 7 |
+
def __init__(self, model_name="vit_base_patch16_224", pretrained=False, **kwargs):
|
| 8 |
+
super().__init__()
|
| 9 |
+
self.feat = timm.create_model(
|
| 10 |
+
model_name,
|
| 11 |
+
pretrained=pretrained,
|
| 12 |
+
num_classes=0,
|
| 13 |
+
**kwargs,
|
| 14 |
+
)
|
| 15 |
+
self.feat_dim = self.feat.num_features
|
| 16 |
+
|
| 17 |
+
def forward(self, x):
|
| 18 |
+
return self.feat(x)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def timm_backbone(pretrained=False, **kwargs):
|
| 22 |
+
return TimmBackbone(pretrained=pretrained, **kwargs)
|
core/model/ewcdr.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
|
| 5 |
+
from .finetune import Finetune
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class IncrementalLinearNet(nn.Module):
|
| 9 |
+
def __init__(self, backbone, feat_dim, num_class):
|
| 10 |
+
super().__init__()
|
| 11 |
+
self.backbone = backbone
|
| 12 |
+
self.classifier = nn.Linear(feat_dim, num_class)
|
| 13 |
+
|
| 14 |
+
def _features(self, x):
|
| 15 |
+
output = self.backbone(x)
|
| 16 |
+
if isinstance(output, dict):
|
| 17 |
+
return output["features"]
|
| 18 |
+
return output
|
| 19 |
+
|
| 20 |
+
def forward(self, x):
|
| 21 |
+
return self.classifier(self._features(x))
|
| 22 |
+
|
| 23 |
+
def update_classifier(self, num_class):
|
| 24 |
+
old_classifier = self.classifier
|
| 25 |
+
if old_classifier.out_features == num_class:
|
| 26 |
+
return
|
| 27 |
+
|
| 28 |
+
new_classifier = nn.Linear(old_classifier.in_features, num_class)
|
| 29 |
+
new_classifier = new_classifier.to(old_classifier.weight.device)
|
| 30 |
+
with torch.no_grad():
|
| 31 |
+
old_out = old_classifier.out_features
|
| 32 |
+
new_classifier.weight[:old_out].copy_(old_classifier.weight)
|
| 33 |
+
new_classifier.bias[:old_out].copy_(old_classifier.bias)
|
| 34 |
+
self.classifier = new_classifier
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class EWCDR(Finetune):
|
| 38 |
+
"""EWC Done Right with logits reversal during importance estimation."""
|
| 39 |
+
|
| 40 |
+
def __init__(self, backbone, feat_dim, num_class, **kwargs):
|
| 41 |
+
super().__init__(backbone, feat_dim, num_class, **kwargs)
|
| 42 |
+
self.init_cls_num = kwargs["init_cls_num"]
|
| 43 |
+
self.inc_cls_num = kwargs["inc_cls_num"]
|
| 44 |
+
self.lamda = kwargs["lamda"]
|
| 45 |
+
self.omega_max = kwargs.get("omega_max", 1e-4)
|
| 46 |
+
|
| 47 |
+
self.task_idx = 0
|
| 48 |
+
self.known_cls_num = 0
|
| 49 |
+
self.total_cls_num = self.init_cls_num
|
| 50 |
+
self.network = IncrementalLinearNet(backbone, feat_dim, self.init_cls_num)
|
| 51 |
+
self.ref_param = {}
|
| 52 |
+
self.omega = None
|
| 53 |
+
|
| 54 |
+
def before_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 55 |
+
self.task_idx = task_idx
|
| 56 |
+
self.known_cls_num = (
|
| 57 |
+
0 if task_idx == 0 else self.init_cls_num + (task_idx - 1) * self.inc_cls_num
|
| 58 |
+
)
|
| 59 |
+
self.total_cls_num = self.init_cls_num + task_idx * self.inc_cls_num
|
| 60 |
+
self.network.update_classifier(self.total_cls_num)
|
| 61 |
+
self.network.to(self.device)
|
| 62 |
+
|
| 63 |
+
def observe(self, data):
|
| 64 |
+
x = data["image"].to(self.device)
|
| 65 |
+
y = data["label"].to(self.device)
|
| 66 |
+
logits = self.network(x)
|
| 67 |
+
|
| 68 |
+
if self.task_idx == 0:
|
| 69 |
+
loss = F.cross_entropy(logits, y)
|
| 70 |
+
else:
|
| 71 |
+
loss = F.cross_entropy(
|
| 72 |
+
logits[:, self.known_cls_num:], y - self.known_cls_num
|
| 73 |
+
)
|
| 74 |
+
loss = loss + self.lamda * self.compute_ewc()
|
| 75 |
+
|
| 76 |
+
pred = torch.argmax(logits, dim=1)
|
| 77 |
+
acc = torch.sum(pred == y).item()
|
| 78 |
+
return pred, acc / x.size(0), loss
|
| 79 |
+
|
| 80 |
+
def inference(self, data):
|
| 81 |
+
x = data["image"].to(self.device)
|
| 82 |
+
y = data["label"].to(self.device)
|
| 83 |
+
logits = self.network(x)
|
| 84 |
+
pred = torch.argmax(logits, dim=1)
|
| 85 |
+
acc = torch.sum(pred == y).item()
|
| 86 |
+
return pred, acc / x.size(0)
|
| 87 |
+
|
| 88 |
+
def after_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 89 |
+
new_omega = self.get_importance(train_loader)
|
| 90 |
+
if self.omega is not None:
|
| 91 |
+
alpha = self.known_cls_num / self.total_cls_num
|
| 92 |
+
for name, old_omega in self.omega.items():
|
| 93 |
+
if name not in new_omega:
|
| 94 |
+
continue
|
| 95 |
+
old_slice = self._prefix_slice(old_omega)
|
| 96 |
+
new_omega[name][old_slice] = (
|
| 97 |
+
alpha * old_omega + (1 - alpha) * new_omega[name][old_slice]
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
self.omega = new_omega
|
| 101 |
+
self.ref_param = {
|
| 102 |
+
name: param.clone().detach()
|
| 103 |
+
for name, param in self.network.named_parameters()
|
| 104 |
+
if param.requires_grad
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
def get_importance(self, train_loader):
|
| 108 |
+
omega = {
|
| 109 |
+
name: torch.zeros_like(param, device=self.device)
|
| 110 |
+
for name, param in self.network.named_parameters()
|
| 111 |
+
if param.requires_grad
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
was_training = self.network.training
|
| 115 |
+
self.network.train()
|
| 116 |
+
for data in train_loader:
|
| 117 |
+
x = data["image"].to(self.device)
|
| 118 |
+
y = data["label"].to(self.device)
|
| 119 |
+
logits = -self.network(x)
|
| 120 |
+
loss = F.cross_entropy(logits, y)
|
| 121 |
+
|
| 122 |
+
self.network.zero_grad(set_to_none=True)
|
| 123 |
+
loss.backward()
|
| 124 |
+
|
| 125 |
+
for name, param in self.network.named_parameters():
|
| 126 |
+
if param.grad is not None and name in omega:
|
| 127 |
+
omega[name] += param.grad.pow(2).detach()
|
| 128 |
+
|
| 129 |
+
for name, value in omega.items():
|
| 130 |
+
value = value / len(train_loader)
|
| 131 |
+
omega[name] = torch.clamp(value, max=self.omega_max)
|
| 132 |
+
|
| 133 |
+
self.network.zero_grad(set_to_none=True)
|
| 134 |
+
self.network.train(was_training)
|
| 135 |
+
return omega
|
| 136 |
+
|
| 137 |
+
def compute_ewc(self):
|
| 138 |
+
if self.omega is None:
|
| 139 |
+
return torch.zeros((), device=self.device)
|
| 140 |
+
|
| 141 |
+
loss = torch.zeros((), device=self.device)
|
| 142 |
+
for name, param in self.network.named_parameters():
|
| 143 |
+
if name not in self.omega:
|
| 144 |
+
continue
|
| 145 |
+
ref = self.ref_param[name]
|
| 146 |
+
old_slice = self._prefix_slice(ref)
|
| 147 |
+
loss += torch.sum(self.omega[name] * (param[old_slice] - ref).pow(2)) / 2
|
| 148 |
+
return loss
|
| 149 |
+
|
| 150 |
+
@staticmethod
|
| 151 |
+
def _prefix_slice(tensor):
|
| 152 |
+
return (slice(0, tensor.shape[0]),) + (slice(None),) * (tensor.dim() - 1)
|
| 153 |
+
|
| 154 |
+
def forward(self, x):
|
| 155 |
+
return self.network(x)
|
| 156 |
+
|
| 157 |
+
def get_parameters(self, config):
|
| 158 |
+
return [{"params": self.network.parameters()}]
|
core/model/finetune.py
CHANGED
|
@@ -35,6 +35,11 @@ class Finetune(nn.Module):
|
|
| 35 |
acc = torch.sum(pred == y).item()
|
| 36 |
return pred, acc / x.size(0)
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
def forward(self, x):
|
| 39 |
return self.classifier(self.backbone(x)['features'])
|
| 40 |
|
|
|
|
| 35 |
acc = torch.sum(pred == y).item()
|
| 36 |
return pred, acc / x.size(0)
|
| 37 |
|
| 38 |
+
def predict_logits(self, data):
|
| 39 |
+
"""Return logits for calibration metrics during evaluation."""
|
| 40 |
+
x = data['image'].to(self.device)
|
| 41 |
+
return self.classifier(self.backbone(x)['features'])
|
| 42 |
+
|
| 43 |
def forward(self, x):
|
| 44 |
return self.classifier(self.backbone(x)['features'])
|
| 45 |
|
core/model/fly_cl.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class FlyCL(nn.Module):
|
| 11 |
+
def __init__(self, backbone, device, **kwargs):
|
| 12 |
+
super().__init__()
|
| 13 |
+
self.backbone = backbone
|
| 14 |
+
self.device = device
|
| 15 |
+
|
| 16 |
+
self.embedding_dim = int(kwargs.get("embedding_dim", backbone.feat_dim))
|
| 17 |
+
self.expand_dim = int(kwargs.get("expand_dim", 10000))
|
| 18 |
+
self.synaptic_degree = int(kwargs.get("synaptic_degree", 300))
|
| 19 |
+
self.coding_level = float(kwargs.get("coding_level", 0.3))
|
| 20 |
+
self.ridge_lower = float(kwargs.get("ridge_lower", 6))
|
| 21 |
+
self.ridge_upper = float(kwargs.get("ridge_upper", 10))
|
| 22 |
+
self.gcv_backend = kwargs.get("gcv_backend", "dual_eigh")
|
| 23 |
+
self.disable_cudnn = bool(kwargs.get("disable_cudnn", False))
|
| 24 |
+
self.use_projection = bool(kwargs.get("use_projection", True))
|
| 25 |
+
self.use_ridge = bool(kwargs.get("use_ridge", True))
|
| 26 |
+
self.total_cls_num = int(kwargs["total_cls_num"])
|
| 27 |
+
self.init_cls_num = int(kwargs["init_cls_num"])
|
| 28 |
+
self.inc_cls_num = int(kwargs["inc_cls_num"])
|
| 29 |
+
self.mask_unseen_classes = bool(kwargs.get("mask_unseen_classes", False))
|
| 30 |
+
|
| 31 |
+
if self.embedding_dim != int(backbone.feat_dim):
|
| 32 |
+
raise ValueError(
|
| 33 |
+
f"embedding_dim={self.embedding_dim} does not match "
|
| 34 |
+
f"backbone.feat_dim={backbone.feat_dim}"
|
| 35 |
+
)
|
| 36 |
+
if (
|
| 37 |
+
self.use_projection
|
| 38 |
+
and not 1 <= self.synaptic_degree <= self.embedding_dim
|
| 39 |
+
):
|
| 40 |
+
raise ValueError("synaptic_degree must be in [1, embedding_dim]")
|
| 41 |
+
if self.use_projection and not 0.0 < self.coding_level <= 1.0:
|
| 42 |
+
raise ValueError("coding_level must be in (0, 1]")
|
| 43 |
+
if self.ridge_upper <= self.ridge_lower:
|
| 44 |
+
raise ValueError("ridge_upper must be greater than ridge_lower")
|
| 45 |
+
if self.gcv_backend not in {"dual_eigh", "svd"}:
|
| 46 |
+
raise ValueError("gcv_backend must be 'dual_eigh' or 'svd'")
|
| 47 |
+
|
| 48 |
+
for parameter in self.backbone.parameters():
|
| 49 |
+
parameter.requires_grad = False
|
| 50 |
+
self.backbone.eval()
|
| 51 |
+
|
| 52 |
+
if self.disable_cudnn:
|
| 53 |
+
torch.backends.cudnn.deterministic = True
|
| 54 |
+
torch.backends.cudnn.enabled = False
|
| 55 |
+
|
| 56 |
+
self.analytic_dim = (
|
| 57 |
+
self.expand_dim if self.use_projection else self.embedding_dim
|
| 58 |
+
)
|
| 59 |
+
projection = self._build_sparse_projection() if self.use_projection else None
|
| 60 |
+
self.register_buffer("projection", projection)
|
| 61 |
+
self.register_buffer("Q", torch.zeros(self.analytic_dim, self.total_cls_num))
|
| 62 |
+
gram = (
|
| 63 |
+
torch.zeros(self.analytic_dim, self.analytic_dim)
|
| 64 |
+
if self.use_ridge
|
| 65 |
+
else None
|
| 66 |
+
)
|
| 67 |
+
self.register_buffer("G", gram)
|
| 68 |
+
self.register_buffer(
|
| 69 |
+
"classifier_weight", torch.zeros(self.analytic_dim, self.total_cls_num)
|
| 70 |
+
)
|
| 71 |
+
self.register_buffer("class_counts", torch.zeros(self.total_cls_num))
|
| 72 |
+
|
| 73 |
+
self._optimizer_anchor = nn.Parameter(torch.zeros(()))
|
| 74 |
+
|
| 75 |
+
self._classes_seen_so_far = 0
|
| 76 |
+
self._is_fitted = False
|
| 77 |
+
self.last_ridge = None
|
| 78 |
+
self.last_feature_time = 0.0
|
| 79 |
+
self.last_post_time = 0.0
|
| 80 |
+
|
| 81 |
+
def _build_sparse_projection(self):
|
| 82 |
+
"""Create the reference implementation's sparse Gaussian projection."""
|
| 83 |
+
projection = torch.zeros(self.expand_dim, self.embedding_dim)
|
| 84 |
+
for row in range(self.expand_dim):
|
| 85 |
+
selected = torch.randperm(self.embedding_dim)[: self.synaptic_degree]
|
| 86 |
+
projection[row, selected] = torch.randn(self.synaptic_degree)
|
| 87 |
+
return projection.to_sparse_csc()
|
| 88 |
+
|
| 89 |
+
def train(self, mode=True):
|
| 90 |
+
# Trainer calls model.train(); the pre-trained feature extractor must
|
| 91 |
+
# nevertheless stay deterministic and frozen, as in the official code.
|
| 92 |
+
super().train(mode)
|
| 93 |
+
self.backbone.eval()
|
| 94 |
+
return self
|
| 95 |
+
|
| 96 |
+
def before_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 97 |
+
increment = self.init_cls_num if task_idx == 0 else self.inc_cls_num
|
| 98 |
+
self._classes_seen_so_far += increment
|
| 99 |
+
if self._classes_seen_so_far > self.total_cls_num:
|
| 100 |
+
raise ValueError("Seen classes exceed total_cls_num")
|
| 101 |
+
print(
|
| 102 |
+
f"[FlyCL] Task {task_idx}: frozen feature extraction; "
|
| 103 |
+
"analytic classifier will be updated after the task."
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
def observe(self, data):
|
| 107 |
+
loss = self._optimizer_anchor * 0.0
|
| 108 |
+
return None, 0.0, loss
|
| 109 |
+
|
| 110 |
+
@torch.no_grad()
|
| 111 |
+
def _extract_features(self, data_loader):
|
| 112 |
+
features = []
|
| 113 |
+
labels = []
|
| 114 |
+
self.backbone.eval()
|
| 115 |
+
for batch in tqdm(data_loader, desc="Fly-CL feature extraction"):
|
| 116 |
+
images = batch["image"].to(self.device, non_blocking=True)
|
| 117 |
+
feature = self.backbone(images)
|
| 118 |
+
features.append(feature)
|
| 119 |
+
labels.append(batch["label"].to(self.device, non_blocking=True))
|
| 120 |
+
if not features:
|
| 121 |
+
raise ValueError("Cannot fit Fly-CL on an empty dataloader")
|
| 122 |
+
return torch.cat(features, dim=0), torch.cat(labels, dim=0)
|
| 123 |
+
|
| 124 |
+
def _fly_encode(self, features):
|
| 125 |
+
if not self.use_projection:
|
| 126 |
+
return features.T
|
| 127 |
+
expanded = torch.sparse.mm(self.projection, features.T)
|
| 128 |
+
topk = max(1, int(self.expand_dim * self.coding_level))
|
| 129 |
+
values, indices = expanded.topk(topk, dim=0, largest=True)
|
| 130 |
+
encoded = torch.zeros_like(expanded)
|
| 131 |
+
encoded.scatter_(0, indices, values)
|
| 132 |
+
return encoded
|
| 133 |
+
|
| 134 |
+
@torch.no_grad()
|
| 135 |
+
def _select_ridge_parameter(self, features, targets):
|
| 136 |
+
"""Generalized cross-validation used by the official implementation."""
|
| 137 |
+
if self.gcv_backend == "svd":
|
| 138 |
+
u, singular_values, _ = torch.linalg.svd(features, full_matrices=False)
|
| 139 |
+
singular_sq = singular_values.square()
|
| 140 |
+
else:
|
| 141 |
+
sample_gram = features @ features.T
|
| 142 |
+
singular_sq, u = torch.linalg.eigh(sample_gram)
|
| 143 |
+
singular_sq.clamp_(min=0.0)
|
| 144 |
+
uty = u.T @ targets
|
| 145 |
+
exponents = np.arange(self.ridge_lower, self.ridge_upper)
|
| 146 |
+
ridges = torch.as_tensor(
|
| 147 |
+
10.0 ** exponents,
|
| 148 |
+
dtype=features.dtype,
|
| 149 |
+
device=features.device,
|
| 150 |
+
)
|
| 151 |
+
n_samples = features.shape[0]
|
| 152 |
+
scores = []
|
| 153 |
+
for ridge in ridges:
|
| 154 |
+
diagonal = singular_sq / (singular_sq + ridge)
|
| 155 |
+
degrees_of_freedom = diagonal.sum()
|
| 156 |
+
prediction = u @ (diagonal[:, None] * uty)
|
| 157 |
+
residual = torch.linalg.vector_norm(targets - prediction).square()
|
| 158 |
+
denominator = (1.0 - degrees_of_freedom / n_samples).square()
|
| 159 |
+
scores.append((residual / n_samples) / denominator)
|
| 160 |
+
return ridges[torch.argmin(torch.stack(scores))]
|
| 161 |
+
|
| 162 |
+
@torch.no_grad()
|
| 163 |
+
def fit_features(self, features, labels):
|
| 164 |
+
"""Update the analytic head from already-extracted backbone features."""
|
| 165 |
+
encoded = self._fly_encode(features)
|
| 166 |
+
del features
|
| 167 |
+
targets = F.one_hot(labels.long(), self.total_cls_num).to(encoded.dtype)
|
| 168 |
+
self.class_counts.add_(targets.sum(dim=0))
|
| 169 |
+
del labels
|
| 170 |
+
self.Q.add_(encoded @ targets)
|
| 171 |
+
if self.use_ridge:
|
| 172 |
+
self.G.add_(encoded @ encoded.T)
|
| 173 |
+
ridge = self._select_ridge_parameter(encoded.T, targets)
|
| 174 |
+
del encoded, targets
|
| 175 |
+
regularized = self.G.clone()
|
| 176 |
+
regularized.diagonal().add_(ridge)
|
| 177 |
+
cholesky = torch.linalg.cholesky(regularized)
|
| 178 |
+
del regularized
|
| 179 |
+
self.classifier_weight.copy_(torch.cholesky_solve(self.Q, cholesky))
|
| 180 |
+
self.last_ridge = float(ridge.item())
|
| 181 |
+
else:
|
| 182 |
+
del encoded, targets
|
| 183 |
+
counts = self.class_counts.clamp_min(1.0).unsqueeze(0)
|
| 184 |
+
self.classifier_weight.copy_(self.Q / counts)
|
| 185 |
+
self.last_ridge = None
|
| 186 |
+
|
| 187 |
+
self._is_fitted = True
|
| 188 |
+
|
| 189 |
+
@torch.no_grad()
|
| 190 |
+
def after_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 191 |
+
total_start = time.perf_counter()
|
| 192 |
+
feature_start = time.perf_counter()
|
| 193 |
+
features, labels = self._extract_features(train_loader)
|
| 194 |
+
self.last_feature_time = time.perf_counter() - feature_start
|
| 195 |
+
self.fit_features(features, labels)
|
| 196 |
+
self.last_post_time = (
|
| 197 |
+
time.perf_counter() - total_start - self.last_feature_time
|
| 198 |
+
)
|
| 199 |
+
print(
|
| 200 |
+
f"[FlyCL] Task {task_idx} fitted: "
|
| 201 |
+
f"ridge={self.last_ridge if self.last_ridge is not None else 'disabled'}, "
|
| 202 |
+
f"feature_time={self.last_feature_time:.2f}s, "
|
| 203 |
+
f"post_time={self.last_post_time:.2f}s"
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
@torch.no_grad()
|
| 207 |
+
def predict_features(self, features, targets=None):
|
| 208 |
+
"""Predict from cached backbone features using the fitted analytic head."""
|
| 209 |
+
if not self._is_fitted:
|
| 210 |
+
raise RuntimeError("Fly-CL classifier is not fitted; call after_task first")
|
| 211 |
+
encoded = self._fly_encode(features)
|
| 212 |
+
if self.use_ridge:
|
| 213 |
+
if self.use_projection:
|
| 214 |
+
logits = torch.sparse.mm(
|
| 215 |
+
encoded.T.to_sparse_csc(), self.classifier_weight
|
| 216 |
+
)
|
| 217 |
+
else:
|
| 218 |
+
logits = encoded.T @ self.classifier_weight
|
| 219 |
+
else:
|
| 220 |
+
encoded = F.normalize(encoded, dim=0)
|
| 221 |
+
prototypes = F.normalize(self.classifier_weight, dim=0)
|
| 222 |
+
if self.use_projection:
|
| 223 |
+
logits = torch.sparse.mm(encoded.T.to_sparse_csc(), prototypes)
|
| 224 |
+
else:
|
| 225 |
+
logits = encoded.T @ prototypes
|
| 226 |
+
if self.mask_unseen_classes:
|
| 227 |
+
logits = logits[:, : self._classes_seen_so_far]
|
| 228 |
+
|
| 229 |
+
accuracy = None
|
| 230 |
+
if targets is not None:
|
| 231 |
+
predictions = logits.argmax(dim=1).cpu()
|
| 232 |
+
accuracy = predictions.eq(targets.cpu()).float().mean().item()
|
| 233 |
+
return logits, accuracy
|
| 234 |
+
|
| 235 |
+
@torch.no_grad()
|
| 236 |
+
def inference(self, data):
|
| 237 |
+
images = data["image"].to(self.device, non_blocking=True)
|
| 238 |
+
features = self.backbone(images)
|
| 239 |
+
return self.predict_features(features, data["label"])
|
| 240 |
+
|
| 241 |
+
def get_parameters(self, config):
|
| 242 |
+
return [self._optimizer_anchor]
|
core/model/ider.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import os
|
| 3 |
+
from collections import Counter
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
from PIL import Image
|
| 10 |
+
from torchvision import transforms
|
| 11 |
+
|
| 12 |
+
from .finetune import Finetune
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class IDER(Finetune):
|
| 16 |
+
"""Idempotent Experience Replay for LibContinual.
|
| 17 |
+
|
| 18 |
+
This follows the official implementation: the current batch receives the
|
| 19 |
+
standard idempotent CE loss, while replay samples are drawn inside
|
| 20 |
+
``observe`` for supervised ER and old-checkpoint idempotent distillation.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(self, backbone, feat_dim, num_class, **kwargs):
|
| 24 |
+
super().__init__(backbone, feat_dim, num_class, **kwargs)
|
| 25 |
+
self.classifier = nn.Identity()
|
| 26 |
+
self.weighta = kwargs.get("weighta", 0.5)
|
| 27 |
+
self.weightb = kwargs.get("weightb", 0.4)
|
| 28 |
+
self.weightc = kwargs.get("weightc", 0.5)
|
| 29 |
+
self.empty_prob = kwargs.get("weightmask", kwargs.get("empty_prob", 1.0))
|
| 30 |
+
self.refine_inference = kwargs.get("refine_inference", False)
|
| 31 |
+
self.mask_unseen = kwargs.get("mask_unseen", False)
|
| 32 |
+
self.old_backbone = None
|
| 33 |
+
self.old_seen_cls_num = 0
|
| 34 |
+
self.seen_cls_num = num_class
|
| 35 |
+
self.task_idx = 0
|
| 36 |
+
self.buffer = None
|
| 37 |
+
self.buffer_dataset = None
|
| 38 |
+
self.minibatch_size = kwargs.get("minibatch_size", kwargs.get("buffer_batch_size", 32))
|
| 39 |
+
self.online_buffer_update = kwargs.get("online_buffer_update", True)
|
| 40 |
+
self.class_balance = kwargs.get("class_balance", True)
|
| 41 |
+
self.print_buffer_stats = kwargs.get("print_buffer_stats", False)
|
| 42 |
+
self.buffer_update_after_step = kwargs.get("buffer_update_after_step", False)
|
| 43 |
+
self.buffer_storage = kwargs.get("buffer_storage", "raw")
|
| 44 |
+
self.ce_detach_signal = kwargs.get("ce_detach_signal", True)
|
| 45 |
+
self.distill_detach_signal = kwargs.get("distill_detach_signal", True)
|
| 46 |
+
self.old_model_eval = kwargs.get("old_model_eval", True)
|
| 47 |
+
self.freeze_old_model = kwargs.get("freeze_old_model", True)
|
| 48 |
+
self._pending_buffer_data = None
|
| 49 |
+
self._pending_buffer_labels = None
|
| 50 |
+
self._to_tensor = transforms.ToTensor()
|
| 51 |
+
self._to_pil = transforms.ToPILImage()
|
| 52 |
+
|
| 53 |
+
def _empty_signal(self, batch_size, upto=None):
|
| 54 |
+
upto = self.num_class if upto is None else upto
|
| 55 |
+
if hasattr(self.backbone, "empty_signal"):
|
| 56 |
+
return self.backbone.empty_signal(batch_size, self.device, upto=upto)
|
| 57 |
+
signal = torch.zeros(batch_size, self.num_class, device=self.device)
|
| 58 |
+
signal[:, :upto] = 1.0 / max(upto, 1)
|
| 59 |
+
return signal
|
| 60 |
+
|
| 61 |
+
def _one_hot(self, labels):
|
| 62 |
+
return F.one_hot(labels, num_classes=self.num_class).float()
|
| 63 |
+
|
| 64 |
+
def _sample_second_input(self, labels):
|
| 65 |
+
empty = self._empty_signal(labels.size(0))
|
| 66 |
+
one_hot = self._one_hot(labels)
|
| 67 |
+
if self.empty_prob >= 1:
|
| 68 |
+
return empty
|
| 69 |
+
if self.empty_prob <= 0:
|
| 70 |
+
return one_hot
|
| 71 |
+
use_empty = (torch.rand(1, device=self.device) <= self.empty_prob).item()
|
| 72 |
+
return empty if use_empty else one_hot
|
| 73 |
+
|
| 74 |
+
def _mask_logits(self, logits, upto=None):
|
| 75 |
+
if not self.mask_unseen:
|
| 76 |
+
return logits
|
| 77 |
+
upto = self.seen_cls_num if upto is None else upto
|
| 78 |
+
if upto >= logits.size(1):
|
| 79 |
+
return logits
|
| 80 |
+
masked = logits.clone()
|
| 81 |
+
masked[:, upto:] = -1e9
|
| 82 |
+
return masked
|
| 83 |
+
|
| 84 |
+
def _backbone_logits(self, x, y_signal=None, upto=None, backbone=None):
|
| 85 |
+
backbone = self.backbone if backbone is None else backbone
|
| 86 |
+
try:
|
| 87 |
+
output = backbone(x, y_signal, upto=upto)
|
| 88 |
+
except TypeError:
|
| 89 |
+
output = backbone(x, y_signal)
|
| 90 |
+
|
| 91 |
+
if isinstance(output, dict):
|
| 92 |
+
return output["logits"]
|
| 93 |
+
if isinstance(output, tuple):
|
| 94 |
+
return output[0]
|
| 95 |
+
return output
|
| 96 |
+
|
| 97 |
+
def _refined_logits(self, x):
|
| 98 |
+
empty = self._empty_signal(x.size(0))
|
| 99 |
+
logits0 = self._backbone_logits(x, empty)
|
| 100 |
+
logits0 = self._mask_logits(logits0)
|
| 101 |
+
if not self.refine_inference:
|
| 102 |
+
return logits0
|
| 103 |
+
|
| 104 |
+
probs0 = F.softmax(logits0, dim=1).detach()
|
| 105 |
+
logits1 = self._backbone_logits(x, probs0)
|
| 106 |
+
return self._mask_logits(logits1)
|
| 107 |
+
|
| 108 |
+
def before_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 109 |
+
self.task_idx = task_idx
|
| 110 |
+
self.seen_cls_num = self.kwargs["init_cls_num"] + task_idx * self.kwargs["inc_cls_num"]
|
| 111 |
+
self.seen_cls_num = min(self.seen_cls_num, self.num_class)
|
| 112 |
+
self.buffer = buffer
|
| 113 |
+
self.buffer_dataset = train_loader.dataset if train_loader is not None else None
|
| 114 |
+
if buffer is not None and hasattr(buffer, "batch_size"):
|
| 115 |
+
self.minibatch_size = buffer.batch_size
|
| 116 |
+
if self.old_backbone is not None:
|
| 117 |
+
self.old_backbone = self.old_backbone.to(self.device)
|
| 118 |
+
if self.old_model_eval:
|
| 119 |
+
self.old_backbone.eval()
|
| 120 |
+
else:
|
| 121 |
+
self.old_backbone.train()
|
| 122 |
+
|
| 123 |
+
def _load_buffer_image(self, image_ref):
|
| 124 |
+
dataset = self.buffer_dataset
|
| 125 |
+
if dataset is None:
|
| 126 |
+
raise RuntimeError("IDER needs the train dataset to sample replay images.")
|
| 127 |
+
|
| 128 |
+
if torch.is_tensor(image_ref):
|
| 129 |
+
image = self._to_pil(image_ref.cpu())
|
| 130 |
+
elif dataset.dataset in ["binary_cifar10", "binary_cifar100"]:
|
| 131 |
+
image = Image.fromarray(np.uint8(image_ref))
|
| 132 |
+
elif dataset.dataset == "tiny-imagenet":
|
| 133 |
+
image = Image.open(image_ref).convert("RGB")
|
| 134 |
+
elif dataset.dataset == "processed_tinyimg":
|
| 135 |
+
if np.max(image_ref) <= 1.0:
|
| 136 |
+
image_ref = np.uint8(255 * image_ref)
|
| 137 |
+
image = Image.fromarray(np.uint8(image_ref)).convert("RGB")
|
| 138 |
+
else:
|
| 139 |
+
image = Image.open(os.path.join(dataset.data_root, dataset.mode, image_ref)).convert("RGB")
|
| 140 |
+
return dataset.trfms(image)
|
| 141 |
+
|
| 142 |
+
def _make_buffer_image_ref(self, image_ref):
|
| 143 |
+
if self.buffer_storage != "tensor":
|
| 144 |
+
return image_ref
|
| 145 |
+
|
| 146 |
+
if torch.is_tensor(image_ref):
|
| 147 |
+
return image_ref.detach().cpu()
|
| 148 |
+
|
| 149 |
+
dataset = self.buffer_dataset
|
| 150 |
+
if dataset.dataset in ["binary_cifar10", "binary_cifar100"]:
|
| 151 |
+
image = Image.fromarray(np.uint8(image_ref))
|
| 152 |
+
elif dataset.dataset == "tiny-imagenet":
|
| 153 |
+
image = Image.open(image_ref).convert("RGB")
|
| 154 |
+
elif dataset.dataset == "processed_tinyimg":
|
| 155 |
+
if np.max(image_ref) <= 1.0:
|
| 156 |
+
image_ref = np.uint8(255 * image_ref)
|
| 157 |
+
image = Image.fromarray(np.uint8(image_ref)).convert("RGB")
|
| 158 |
+
else:
|
| 159 |
+
image = Image.open(os.path.join(dataset.data_root, dataset.mode, image_ref)).convert("RGB")
|
| 160 |
+
return self._to_tensor(image).cpu()
|
| 161 |
+
|
| 162 |
+
def _sample_buffer_batch(self):
|
| 163 |
+
if self.buffer is None or self.buffer.is_empty():
|
| 164 |
+
return None
|
| 165 |
+
|
| 166 |
+
n_items = len(self.buffer.labels)
|
| 167 |
+
batch_size = min(self.minibatch_size, n_items)
|
| 168 |
+
indices = np.random.choice(n_items, size=batch_size, replace=False)
|
| 169 |
+
images = [self._load_buffer_image(self.buffer.images[idx]) for idx in indices]
|
| 170 |
+
labels = [self.buffer.labels[idx] for idx in indices]
|
| 171 |
+
return torch.stack(images).to(self.device), torch.tensor(labels, dtype=torch.long, device=self.device)
|
| 172 |
+
|
| 173 |
+
def _online_update_buffer(self, data, labels):
|
| 174 |
+
if (
|
| 175 |
+
not self.online_buffer_update
|
| 176 |
+
or self.buffer is None
|
| 177 |
+
or self.buffer.buffer_size <= 0
|
| 178 |
+
or self.buffer_dataset is None
|
| 179 |
+
or "index" not in data
|
| 180 |
+
):
|
| 181 |
+
return
|
| 182 |
+
|
| 183 |
+
if not hasattr(self.buffer, "num_seen_examples"):
|
| 184 |
+
self.buffer.num_seen_examples = len(self.buffer.labels)
|
| 185 |
+
|
| 186 |
+
indices = data["index"].detach().cpu().numpy().tolist()
|
| 187 |
+
labels = labels.detach().cpu().numpy().tolist()
|
| 188 |
+
|
| 189 |
+
for dataset_idx, label in zip(indices, labels):
|
| 190 |
+
image_ref = self._make_buffer_image_ref(self.buffer_dataset.images[int(dataset_idx)])
|
| 191 |
+
seen = self.buffer.num_seen_examples
|
| 192 |
+
if len(self.buffer.labels) < self.buffer.buffer_size:
|
| 193 |
+
self.buffer.images.append(image_ref)
|
| 194 |
+
self.buffer.labels.append(int(label))
|
| 195 |
+
else:
|
| 196 |
+
replace_idx = np.random.randint(0, seen + 1)
|
| 197 |
+
if replace_idx < self.buffer.buffer_size:
|
| 198 |
+
if self.class_balance and len(self.buffer.labels) > 0:
|
| 199 |
+
counts = Counter(self.buffer.labels)
|
| 200 |
+
max_count = max(counts.values())
|
| 201 |
+
majority_classes = {cls for cls, cnt in counts.items() if cnt == max_count}
|
| 202 |
+
majority_indices = [
|
| 203 |
+
idx for idx, old_label in enumerate(self.buffer.labels)
|
| 204 |
+
if old_label in majority_classes
|
| 205 |
+
]
|
| 206 |
+
replace_idx = int(np.random.choice(majority_indices))
|
| 207 |
+
self.buffer.images[replace_idx] = image_ref
|
| 208 |
+
self.buffer.labels[replace_idx] = int(label)
|
| 209 |
+
self.buffer.num_seen_examples += 1
|
| 210 |
+
|
| 211 |
+
def _idempotent_ce_loss(self, x, y):
|
| 212 |
+
y_signal = self._sample_second_input(y)
|
| 213 |
+
logits1 = self._backbone_logits(x, y_signal)
|
| 214 |
+
signal_logits = logits1.detach() if self.ce_detach_signal else logits1
|
| 215 |
+
logits2 = self._backbone_logits(x, F.softmax(signal_logits, dim=1))
|
| 216 |
+
loss1 = self.loss_fn(self._mask_logits(logits1), y)
|
| 217 |
+
loss2 = self.loss_fn(self._mask_logits(logits2), y)
|
| 218 |
+
return 0.5 * (loss1 + loss2), logits1, logits2, y_signal
|
| 219 |
+
|
| 220 |
+
def _distill_loss(self, x):
|
| 221 |
+
uniform = self._empty_signal(x.size(0))
|
| 222 |
+
logits1 = self._backbone_logits(x, uniform)
|
| 223 |
+
signal_logits = logits1.detach() if self.distill_detach_signal else logits1
|
| 224 |
+
if self.old_model_eval:
|
| 225 |
+
self.old_backbone.eval()
|
| 226 |
+
else:
|
| 227 |
+
self.old_backbone.train()
|
| 228 |
+
logits2 = self._backbone_logits(
|
| 229 |
+
x,
|
| 230 |
+
F.softmax(signal_logits, dim=1),
|
| 231 |
+
backbone=self.old_backbone,
|
| 232 |
+
)
|
| 233 |
+
return F.mse_loss(logits1, logits2)
|
| 234 |
+
|
| 235 |
+
def observe(self, data):
|
| 236 |
+
x, y = data["image"], data["label"]
|
| 237 |
+
x = x.to(self.device)
|
| 238 |
+
y = y.to(self.device)
|
| 239 |
+
|
| 240 |
+
loss, logits1, logits2, _ = self._idempotent_ce_loss(x, y)
|
| 241 |
+
|
| 242 |
+
if self.old_backbone is not None and self.old_seen_cls_num > 0 and self.weightb > 0:
|
| 243 |
+
loss = loss + self.weightb * self._distill_loss(x)
|
| 244 |
+
|
| 245 |
+
if self.weightc != 0:
|
| 246 |
+
buffer_batch = self._sample_buffer_batch()
|
| 247 |
+
if buffer_batch is not None:
|
| 248 |
+
buffer_loss, _, _, _ = self._idempotent_ce_loss(*buffer_batch)
|
| 249 |
+
loss = loss + self.weightc * 2.0 * buffer_loss
|
| 250 |
+
|
| 251 |
+
if self.old_backbone is not None and self.old_seen_cls_num > 0 and self.weighta != 0:
|
| 252 |
+
buffer_batch = self._sample_buffer_batch()
|
| 253 |
+
if buffer_batch is not None:
|
| 254 |
+
loss = loss + self.weighta * self._distill_loss(buffer_batch[0])
|
| 255 |
+
|
| 256 |
+
pred = torch.argmax(self._mask_logits(logits2), dim=1)
|
| 257 |
+
acc = torch.sum(pred == y).item()
|
| 258 |
+
if self.buffer_update_after_step:
|
| 259 |
+
self._pending_buffer_data = data
|
| 260 |
+
self._pending_buffer_labels = y.detach()
|
| 261 |
+
else:
|
| 262 |
+
self._online_update_buffer(data, y)
|
| 263 |
+
return pred, acc / x.size(0), loss
|
| 264 |
+
|
| 265 |
+
def after_observe(self):
|
| 266 |
+
if self._pending_buffer_data is None or self._pending_buffer_labels is None:
|
| 267 |
+
return
|
| 268 |
+
self._online_update_buffer(self._pending_buffer_data, self._pending_buffer_labels)
|
| 269 |
+
self._pending_buffer_data = None
|
| 270 |
+
self._pending_buffer_labels = None
|
| 271 |
+
|
| 272 |
+
def _task_class_range(self, task_id):
|
| 273 |
+
if task_id < 0:
|
| 274 |
+
return 0, self.num_class
|
| 275 |
+
if task_id == 0:
|
| 276 |
+
start = 0
|
| 277 |
+
end = self.kwargs["init_cls_num"]
|
| 278 |
+
else:
|
| 279 |
+
start = self.kwargs["init_cls_num"] + (task_id - 1) * self.kwargs["inc_cls_num"]
|
| 280 |
+
end = start + self.kwargs["inc_cls_num"]
|
| 281 |
+
return start, min(end, self.num_class)
|
| 282 |
+
|
| 283 |
+
def inference(self, data, task_id=-1):
|
| 284 |
+
x, y = data["image"], data["label"]
|
| 285 |
+
x = x.to(self.device)
|
| 286 |
+
y = y.to(self.device)
|
| 287 |
+
|
| 288 |
+
logits = self._refined_logits(x)
|
| 289 |
+
if task_id > -1:
|
| 290 |
+
start, end = self._task_class_range(task_id)
|
| 291 |
+
task_logits = torch.full_like(logits, -1e9)
|
| 292 |
+
task_logits[:, start:end] = logits[:, start:end]
|
| 293 |
+
logits = task_logits
|
| 294 |
+
pred = torch.argmax(logits, dim=1)
|
| 295 |
+
acc = torch.sum(pred == y).item()
|
| 296 |
+
return pred, acc / x.size(0)
|
| 297 |
+
|
| 298 |
+
def predict_logits(self, data):
|
| 299 |
+
x = data["image"].to(self.device)
|
| 300 |
+
return self._refined_logits(x)
|
| 301 |
+
|
| 302 |
+
def forward(self, x):
|
| 303 |
+
return self._refined_logits(x)
|
| 304 |
+
|
| 305 |
+
def after_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 306 |
+
if self.print_buffer_stats and buffer is not None and hasattr(buffer, "labels") and len(buffer.labels) > 0:
|
| 307 |
+
counts = Counter(buffer.labels)
|
| 308 |
+
seen_classes = self.kwargs["init_cls_num"] + task_idx * self.kwargs["inc_cls_num"]
|
| 309 |
+
seen_classes = min(seen_classes, self.num_class)
|
| 310 |
+
values = [counts.get(cls, 0) for cls in range(seen_classes)]
|
| 311 |
+
zero_classes = sum(1 for value in values if value == 0)
|
| 312 |
+
print(
|
| 313 |
+
"[IDER] Buffer class counts after task "
|
| 314 |
+
f"{task_idx}: size={len(buffer.labels)}, classes={len(counts)}, "
|
| 315 |
+
f"min={min(values)}, max={max(values)}, zero={zero_classes}, "
|
| 316 |
+
f"head={values[:min(20, len(values))]}"
|
| 317 |
+
)
|
| 318 |
+
self.old_backbone = copy.deepcopy(self.backbone).to(self.device)
|
| 319 |
+
if self.old_model_eval:
|
| 320 |
+
self.old_backbone.eval()
|
| 321 |
+
else:
|
| 322 |
+
self.old_backbone.train()
|
| 323 |
+
for param in self.old_backbone.parameters():
|
| 324 |
+
param.requires_grad = not self.freeze_old_model
|
| 325 |
+
self.old_seen_cls_num = self.seen_cls_num
|
| 326 |
+
|
| 327 |
+
def get_parameters(self, config):
|
| 328 |
+
return [{"params": self.backbone.parameters()}]
|
core/model/paper_er.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
from PIL import Image
|
| 6 |
+
from torchvision import transforms
|
| 7 |
+
|
| 8 |
+
from .finetune import Finetune
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class PaperER(Finetune):
|
| 12 |
+
"""Online ER baseline used by the original IDER code.
|
| 13 |
+
|
| 14 |
+
Each training step optimizes CE on the current mini-batch concatenated with
|
| 15 |
+
a random replay mini-batch. The current mini-batch is inserted into the
|
| 16 |
+
replay memory by reservoir sampling after the optimizer step.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self, backbone, feat_dim, num_class, **kwargs):
|
| 20 |
+
super().__init__(backbone, feat_dim, num_class, **kwargs)
|
| 21 |
+
self.buffer = None
|
| 22 |
+
self.buffer_dataset = None
|
| 23 |
+
self.minibatch_size = kwargs.get("minibatch_size", kwargs.get("buffer_batch_size", 32))
|
| 24 |
+
self.buffer_storage = kwargs.get("buffer_storage", "tensor")
|
| 25 |
+
self._pending_buffer_data = None
|
| 26 |
+
self._pending_buffer_labels = None
|
| 27 |
+
self._to_tensor = transforms.ToTensor()
|
| 28 |
+
self._to_pil = transforms.ToPILImage()
|
| 29 |
+
|
| 30 |
+
def before_task(self, task_idx, buffer, train_loader, test_loaders):
|
| 31 |
+
self.buffer = buffer
|
| 32 |
+
self.buffer_dataset = train_loader.dataset if train_loader is not None else None
|
| 33 |
+
if buffer is not None and hasattr(buffer, "batch_size"):
|
| 34 |
+
self.minibatch_size = buffer.batch_size
|
| 35 |
+
|
| 36 |
+
def _logits(self, x):
|
| 37 |
+
return self.classifier(self.backbone(x)["features"])
|
| 38 |
+
|
| 39 |
+
def _load_buffer_image(self, image_ref):
|
| 40 |
+
dataset = self.buffer_dataset
|
| 41 |
+
if dataset is None:
|
| 42 |
+
raise RuntimeError("PaperER needs the train dataset to sample replay images.")
|
| 43 |
+
|
| 44 |
+
if torch.is_tensor(image_ref):
|
| 45 |
+
image = self._to_pil(image_ref.cpu())
|
| 46 |
+
elif dataset.dataset in ["binary_cifar10", "binary_cifar100"]:
|
| 47 |
+
image = Image.fromarray(np.uint8(image_ref))
|
| 48 |
+
elif dataset.dataset == "tiny-imagenet":
|
| 49 |
+
image = Image.open(image_ref).convert("RGB")
|
| 50 |
+
elif dataset.dataset == "processed_tinyimg":
|
| 51 |
+
if np.max(image_ref) <= 1.0:
|
| 52 |
+
image_ref = np.uint8(255 * image_ref)
|
| 53 |
+
image = Image.fromarray(np.uint8(image_ref)).convert("RGB")
|
| 54 |
+
else:
|
| 55 |
+
image = Image.open(os.path.join(dataset.data_root, dataset.mode, image_ref)).convert("RGB")
|
| 56 |
+
return dataset.trfms(image)
|
| 57 |
+
|
| 58 |
+
def _make_buffer_image_ref(self, image_ref):
|
| 59 |
+
if self.buffer_storage != "tensor":
|
| 60 |
+
return image_ref
|
| 61 |
+
|
| 62 |
+
dataset = self.buffer_dataset
|
| 63 |
+
if torch.is_tensor(image_ref):
|
| 64 |
+
return image_ref.detach().cpu()
|
| 65 |
+
if dataset.dataset in ["binary_cifar10", "binary_cifar100"]:
|
| 66 |
+
image = Image.fromarray(np.uint8(image_ref))
|
| 67 |
+
elif dataset.dataset == "tiny-imagenet":
|
| 68 |
+
image = Image.open(image_ref).convert("RGB")
|
| 69 |
+
elif dataset.dataset == "processed_tinyimg":
|
| 70 |
+
if np.max(image_ref) <= 1.0:
|
| 71 |
+
image_ref = np.uint8(255 * image_ref)
|
| 72 |
+
image = Image.fromarray(np.uint8(image_ref)).convert("RGB")
|
| 73 |
+
else:
|
| 74 |
+
image = Image.open(os.path.join(dataset.data_root, dataset.mode, image_ref)).convert("RGB")
|
| 75 |
+
return self._to_tensor(image).cpu()
|
| 76 |
+
|
| 77 |
+
def _sample_buffer_batch(self):
|
| 78 |
+
if self.buffer is None or self.buffer.is_empty():
|
| 79 |
+
return None
|
| 80 |
+
|
| 81 |
+
batch_size = min(self.minibatch_size, len(self.buffer.labels))
|
| 82 |
+
indices = np.random.choice(len(self.buffer.labels), size=batch_size, replace=False)
|
| 83 |
+
images = [self._load_buffer_image(self.buffer.images[idx]) for idx in indices]
|
| 84 |
+
labels = [self.buffer.labels[idx] for idx in indices]
|
| 85 |
+
return torch.stack(images).to(self.device), torch.tensor(labels, dtype=torch.long, device=self.device)
|
| 86 |
+
|
| 87 |
+
def _online_update_buffer(self, data, labels):
|
| 88 |
+
if (
|
| 89 |
+
self.buffer is None
|
| 90 |
+
or self.buffer.buffer_size <= 0
|
| 91 |
+
or self.buffer_dataset is None
|
| 92 |
+
or "index" not in data
|
| 93 |
+
):
|
| 94 |
+
return
|
| 95 |
+
|
| 96 |
+
if not hasattr(self.buffer, "num_seen_examples"):
|
| 97 |
+
self.buffer.num_seen_examples = len(self.buffer.labels)
|
| 98 |
+
|
| 99 |
+
indices = data["index"].detach().cpu().numpy().tolist()
|
| 100 |
+
labels = labels.detach().cpu().numpy().tolist()
|
| 101 |
+
|
| 102 |
+
for dataset_idx, label in zip(indices, labels):
|
| 103 |
+
image_ref = self._make_buffer_image_ref(self.buffer_dataset.images[int(dataset_idx)])
|
| 104 |
+
seen = self.buffer.num_seen_examples
|
| 105 |
+
if len(self.buffer.labels) < self.buffer.buffer_size:
|
| 106 |
+
self.buffer.images.append(image_ref)
|
| 107 |
+
self.buffer.labels.append(int(label))
|
| 108 |
+
else:
|
| 109 |
+
replace_idx = np.random.randint(0, seen + 1)
|
| 110 |
+
if replace_idx < self.buffer.buffer_size:
|
| 111 |
+
self.buffer.images[replace_idx] = image_ref
|
| 112 |
+
self.buffer.labels[replace_idx] = int(label)
|
| 113 |
+
self.buffer.num_seen_examples += 1
|
| 114 |
+
|
| 115 |
+
def observe(self, data):
|
| 116 |
+
x, y = data["image"].to(self.device), data["label"].to(self.device)
|
| 117 |
+
real_batch_size = x.size(0)
|
| 118 |
+
|
| 119 |
+
replay_batch = self._sample_buffer_batch()
|
| 120 |
+
if replay_batch is not None:
|
| 121 |
+
buf_x, buf_y = replay_batch
|
| 122 |
+
train_x = torch.cat((x, buf_x), dim=0)
|
| 123 |
+
train_y = torch.cat((y, buf_y), dim=0)
|
| 124 |
+
else:
|
| 125 |
+
train_x, train_y = x, y
|
| 126 |
+
|
| 127 |
+
logits = self._logits(train_x)
|
| 128 |
+
loss = self.loss_fn(logits, train_y)
|
| 129 |
+
|
| 130 |
+
current_logits = logits[:real_batch_size]
|
| 131 |
+
pred = torch.argmax(current_logits, dim=1)
|
| 132 |
+
acc = torch.sum(pred == y).item()
|
| 133 |
+
|
| 134 |
+
self._pending_buffer_data = data
|
| 135 |
+
self._pending_buffer_labels = y.detach()
|
| 136 |
+
return pred, acc / real_batch_size, loss
|
| 137 |
+
|
| 138 |
+
def after_observe(self):
|
| 139 |
+
if self._pending_buffer_data is None or self._pending_buffer_labels is None:
|
| 140 |
+
return
|
| 141 |
+
self._online_update_buffer(self._pending_buffer_data, self._pending_buffer_labels)
|
| 142 |
+
self._pending_buffer_data = None
|
| 143 |
+
self._pending_buffer_labels = None
|
| 144 |
+
|
| 145 |
+
def inference(self, data, task_id=-1):
|
| 146 |
+
x, y = data["image"].to(self.device), data["label"].to(self.device)
|
| 147 |
+
logits = self._logits(x)
|
| 148 |
+
if task_id > -1:
|
| 149 |
+
start, end = self._task_class_range(task_id)
|
| 150 |
+
task_logits = torch.full_like(logits, -1e9)
|
| 151 |
+
task_logits[:, start:end] = logits[:, start:end]
|
| 152 |
+
logits = task_logits
|
| 153 |
+
pred = torch.argmax(logits, dim=1)
|
| 154 |
+
acc = torch.sum(pred == y).item()
|
| 155 |
+
return pred, acc / x.size(0)
|
| 156 |
+
|
| 157 |
+
def _task_class_range(self, task_id):
|
| 158 |
+
if task_id < 0:
|
| 159 |
+
return 0, self.num_class
|
| 160 |
+
if task_id == 0:
|
| 161 |
+
start = 0
|
| 162 |
+
end = self.kwargs["init_cls_num"]
|
| 163 |
+
else:
|
| 164 |
+
start = self.kwargs["init_cls_num"] + (task_id - 1) * self.kwargs["inc_cls_num"]
|
| 165 |
+
end = start + self.kwargs["inc_cls_num"]
|
| 166 |
+
return start, min(end, self.num_class)
|
| 167 |
+
|
| 168 |
+
def predict_logits(self, data):
|
| 169 |
+
x = data["image"].to(self.device)
|
| 170 |
+
return self._logits(x)
|
core/trainer.py
CHANGED
|
@@ -165,19 +165,25 @@ class Trainer(object):
|
|
| 165 |
torch.optim, "optimizer", config, params=model.get_parameters(config)
|
| 166 |
)
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
# Check if the learning rate scheduler specified in the configuration is "CosineSchedule"
|
| 169 |
-
if
|
| 170 |
-
scheduler = CosineSchedule(optimizer, K=
|
| 171 |
-
elif
|
| 172 |
-
scheduler = PatienceSchedule(optimizer, patience =
|
| 173 |
-
elif
|
| 174 |
scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda e: 1)
|
| 175 |
-
elif
|
| 176 |
T_max = len(self.train_loader.get_loader(self.task_idx))
|
| 177 |
T_max *= init_epoch if self.task_idx == 0 else config['epoch']
|
| 178 |
-
scheduler = CosineAnnealingWarmUp(optimizer,
|
| 179 |
else:
|
| 180 |
-
scheduler =
|
|
|
|
|
|
|
| 181 |
|
| 182 |
return init_epoch, config['epoch'], optimizer, scheduler
|
| 183 |
|
|
@@ -302,7 +308,7 @@ class Trainer(object):
|
|
| 302 |
|
| 303 |
dataloader, val_bias_dataloader = self.model.spilt_and_update(dataloader, self.buffer, task_idx, self.config)
|
| 304 |
|
| 305 |
-
elif isinstance(self.buffer, (LinearBuffer, LinearHerdingBuffer)) and self.buffer.buffer_size > 0 and task_idx > 0:
|
| 306 |
datasets = dataloader.dataset
|
| 307 |
if isinstance(datasets.images, list):
|
| 308 |
datasets.images.extend(self.buffer.images)
|
|
@@ -371,7 +377,8 @@ class Trainer(object):
|
|
| 371 |
'PRAKA',
|
| 372 |
'TRGP_CLIP',
|
| 373 |
'LoRAsub_DRS',
|
| 374 |
-
'CL_LoRA'
|
|
|
|
| 375 |
]:
|
| 376 |
if self.rank == 0:
|
| 377 |
print(f" * Disabled validation for this method")
|
|
@@ -407,7 +414,7 @@ class Trainer(object):
|
|
| 407 |
model.after_task(task_idx, self.buffer, self.train_loader.get_loader(task_idx), self.test_loader.get_loader(task_idx))
|
| 408 |
|
| 409 |
# Update Buffer
|
| 410 |
-
if method_name not in ['bic', 'ERACE', 'ERAML']:
|
| 411 |
self.buffer.total_classes += self.init_cls_num if task_idx == 0 else self.inc_cls_num
|
| 412 |
if self.buffer.buffer_size > 0:
|
| 413 |
if self.buffer.strategy == 'herding':
|
|
@@ -454,6 +461,7 @@ class Trainer(object):
|
|
| 454 |
|
| 455 |
#bias_scheduler.step()
|
| 456 |
|
|
|
|
| 457 |
for test_idx in range(testing_times):
|
| 458 |
if self.rank == 0:
|
| 459 |
print(f"================Test {test_idx+1}/{testing_times} of Task {task_idx}!================")
|
|
@@ -461,6 +469,9 @@ class Trainer(object):
|
|
| 461 |
test_acc = self._validate(task_idx)
|
| 462 |
|
| 463 |
batch_last_acc, per_task_acc = test_acc['avg_acc'], test_acc['per_task_acc']
|
|
|
|
|
|
|
|
|
|
| 464 |
best_batch_last_acc = max(batch_last_acc, best_batch_last_acc)
|
| 465 |
|
| 466 |
task_last_acc = np.mean(per_task_acc)
|
|
@@ -474,6 +485,8 @@ class Trainer(object):
|
|
| 474 |
print(f" * [Task] Last Average Acc: {task_last_acc:.2f} (Best: {best_task_last_acc:.2f})")
|
| 475 |
print(f" * Forgetting: {frgt:.3f} (Best: {best_frgt:.3f})")
|
| 476 |
print(f" * Backward Transfer: {bwt:.2f} (Best: {best_bwt:.2f})")
|
|
|
|
|
|
|
| 477 |
print(f" * Per-Task Acc: {per_task_acc}")
|
| 478 |
|
| 479 |
batch_last_acc_list[task_idx] += batch_last_acc # avg_acc_list[task_idx] += avg_acc
|
|
@@ -492,6 +505,9 @@ class Trainer(object):
|
|
| 492 |
task_last_acc = task_last_acc_list[task_idx]
|
| 493 |
|
| 494 |
frgt, bwt = compute_frgt(acc_table, acc_table[task_idx], task_idx), compute_bwt(acc_table, acc_table[task_idx], task_idx)
|
|
|
|
|
|
|
|
|
|
| 495 |
best_frgt, best_bwt = min(frgt, best_frgt), max(bwt, best_bwt)
|
| 496 |
if task_idx > 1:
|
| 497 |
frgt_list.append(frgt)
|
|
@@ -503,6 +519,10 @@ class Trainer(object):
|
|
| 503 |
print(f" * [Task] Last Average Acc: {task_last_acc:.2f} (Best: {best_task_last_acc:.2f})")
|
| 504 |
print(f" * Forgetting: {frgt:.3f} (Best: {best_frgt:.3f})")
|
| 505 |
print(f" * Backward Transfer: {bwt:.2f} (Best: {best_bwt:.2f})")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
print(f" * Per-Task Acc: {acc_table[task_idx][:task_idx + 1]}")
|
| 507 |
|
| 508 |
batch_ovr_avg_acc = np.mean(batch_last_acc_list) #batch_ovr_avg_acc = np.mean(avg_acc_list)
|
|
@@ -512,6 +532,8 @@ class Trainer(object):
|
|
| 512 |
|
| 513 |
ovr_bwt = np.mean(bwt_list) if len(bwt_list) > 0 else float('-inf')
|
| 514 |
ovr_frgt = np.mean(frgt_list) if len(frgt_list) > 0 else float('inf')
|
|
|
|
|
|
|
| 515 |
|
| 516 |
if self.rank == 0:
|
| 517 |
print(f"================Overall Result of {self.task_num} Tasks!================")
|
|
@@ -523,6 +545,10 @@ class Trainer(object):
|
|
| 523 |
print(f" * [Task] Overall Avg Acc : {task_ovr_avg_acc:.2f}")
|
| 524 |
print(f" * Overall Frgt : {ovr_frgt:.3f}")
|
| 525 |
print(f" * Overall BwT : {ovr_bwt:.2f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
print(f" * Average Acc Table : \n{acc_table}")
|
| 527 |
|
| 528 |
print(f"================Model Performance Analysis================")
|
|
@@ -605,6 +631,9 @@ class Trainer(object):
|
|
| 605 |
|
| 606 |
self.optimizer.step()
|
| 607 |
|
|
|
|
|
|
|
|
|
|
| 608 |
if self.config["classifier"]["name"] in ['ERACE', 'ERAML']:
|
| 609 |
model.add_reservoir()
|
| 610 |
|
|
@@ -626,6 +655,24 @@ class Trainer(object):
|
|
| 626 |
|
| 627 |
per_task_acc = []
|
| 628 |
count_all, correct_all = 0, 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
|
| 630 |
if self.config['testing_per_task']:
|
| 631 |
|
|
@@ -636,13 +683,9 @@ class Trainer(object):
|
|
| 636 |
correct_task, count_task = 0, 0
|
| 637 |
|
| 638 |
for b, batch in tqdm(enumerate(dataloader), total=len(dataloader), desc = f"Testing on Task {t} data", disable=self.rank != 0): # Disable tqdm for non-master processes
|
|
|
|
| 639 |
|
| 640 |
-
|
| 641 |
-
output, acc = model.inference(batch, task_id=t)
|
| 642 |
-
elif self.config['setting'] == 'task-agnostic':
|
| 643 |
-
output, acc = model.inference(batch)
|
| 644 |
-
|
| 645 |
-
correct_task += int(acc * batch['label'].shape[0])
|
| 646 |
count_task += batch['label'].shape[0]
|
| 647 |
|
| 648 |
correct_all += correct_task
|
|
@@ -693,9 +736,7 @@ class Trainer(object):
|
|
| 693 |
if self.config['setting'] == 'task-aware':
|
| 694 |
print('Mostly methods dont support this, set testing_per_task to False')
|
| 695 |
raise NotImplementedError
|
| 696 |
-
|
| 697 |
-
elif self.config['setting'] == 'task-agnostic':
|
| 698 |
-
output, acc = model.inference(batch)
|
| 699 |
preds = output.cpu().numpy()
|
| 700 |
|
| 701 |
labels = batch['label'].cpu().numpy()
|
|
@@ -714,7 +755,10 @@ class Trainer(object):
|
|
| 714 |
|
| 715 |
avg_acc = round(correct_all * 100 / count_all, 2)
|
| 716 |
|
|
|
|
|
|
|
| 717 |
return {
|
| 718 |
"avg_acc": avg_acc,
|
| 719 |
-
"per_task_acc": per_task_acc
|
|
|
|
| 720 |
}
|
|
|
|
| 165 |
torch.optim, "optimizer", config, params=model.get_parameters(config)
|
| 166 |
)
|
| 167 |
|
| 168 |
+
scheduler_config = config["lr_scheduler"]
|
| 169 |
+
if self.task_idx == 0 and "init_lr_scheduler" in config.keys():
|
| 170 |
+
scheduler_config = config["init_lr_scheduler"]
|
| 171 |
+
|
| 172 |
# Check if the learning rate scheduler specified in the configuration is "CosineSchedule"
|
| 173 |
+
if scheduler_config['name'] == "CosineSchedule":
|
| 174 |
+
scheduler = CosineSchedule(optimizer, K=scheduler_config['kwargs']['K'])
|
| 175 |
+
elif scheduler_config['name'] == "PatienceSchedule":
|
| 176 |
+
scheduler = PatienceSchedule(optimizer, patience = scheduler_config['kwargs']['patience'], factor = scheduler_config['kwargs']['factor'])
|
| 177 |
+
elif scheduler_config['name'] == "Constant":
|
| 178 |
scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda e: 1)
|
| 179 |
+
elif scheduler_config['name'] == "CosineAnnealingWarmUp":
|
| 180 |
T_max = len(self.train_loader.get_loader(self.task_idx))
|
| 181 |
T_max *= init_epoch if self.task_idx == 0 else config['epoch']
|
| 182 |
+
scheduler = CosineAnnealingWarmUp(optimizer, scheduler_config['kwargs']['warmup_length'], T_max)
|
| 183 |
else:
|
| 184 |
+
scheduler = getattr(torch.optim.lr_scheduler, scheduler_config["name"])(
|
| 185 |
+
optimizer=optimizer, **scheduler_config["kwargs"]
|
| 186 |
+
)
|
| 187 |
|
| 188 |
return init_epoch, config['epoch'], optimizer, scheduler
|
| 189 |
|
|
|
|
| 308 |
|
| 309 |
dataloader, val_bias_dataloader = self.model.spilt_and_update(dataloader, self.buffer, task_idx, self.config)
|
| 310 |
|
| 311 |
+
elif method_name not in ["IDER", "PaperER"] and isinstance(self.buffer, (LinearBuffer, LinearHerdingBuffer)) and self.buffer.buffer_size > 0 and task_idx > 0:
|
| 312 |
datasets = dataloader.dataset
|
| 313 |
if isinstance(datasets.images, list):
|
| 314 |
datasets.images.extend(self.buffer.images)
|
|
|
|
| 377 |
'PRAKA',
|
| 378 |
'TRGP_CLIP',
|
| 379 |
'LoRAsub_DRS',
|
| 380 |
+
'CL_LoRA',
|
| 381 |
+
'FlyCL'
|
| 382 |
]:
|
| 383 |
if self.rank == 0:
|
| 384 |
print(f" * Disabled validation for this method")
|
|
|
|
| 414 |
model.after_task(task_idx, self.buffer, self.train_loader.get_loader(task_idx), self.test_loader.get_loader(task_idx))
|
| 415 |
|
| 416 |
# Update Buffer
|
| 417 |
+
if method_name not in ['bic', 'ERACE', 'ERAML', 'IDER', 'PaperER']:
|
| 418 |
self.buffer.total_classes += self.init_cls_num if task_idx == 0 else self.inc_cls_num
|
| 419 |
if self.buffer.buffer_size > 0:
|
| 420 |
if self.buffer.strategy == 'herding':
|
|
|
|
| 461 |
|
| 462 |
#bias_scheduler.step()
|
| 463 |
|
| 464 |
+
paper_ece_sum, paper_ece_count = 0., 0
|
| 465 |
for test_idx in range(testing_times):
|
| 466 |
if self.rank == 0:
|
| 467 |
print(f"================Test {test_idx+1}/{testing_times} of Task {task_idx}!================")
|
|
|
|
| 469 |
test_acc = self._validate(task_idx)
|
| 470 |
|
| 471 |
batch_last_acc, per_task_acc = test_acc['avg_acc'], test_acc['per_task_acc']
|
| 472 |
+
if test_acc.get('paper_ece') is not None:
|
| 473 |
+
paper_ece_sum += test_acc['paper_ece']
|
| 474 |
+
paper_ece_count += 1
|
| 475 |
best_batch_last_acc = max(batch_last_acc, best_batch_last_acc)
|
| 476 |
|
| 477 |
task_last_acc = np.mean(per_task_acc)
|
|
|
|
| 485 |
print(f" * [Task] Last Average Acc: {task_last_acc:.2f} (Best: {best_task_last_acc:.2f})")
|
| 486 |
print(f" * Forgetting: {frgt:.3f} (Best: {best_frgt:.3f})")
|
| 487 |
print(f" * Backward Transfer: {bwt:.2f} (Best: {best_bwt:.2f})")
|
| 488 |
+
if test_acc.get('paper_ece') is not None:
|
| 489 |
+
print(f" * [Paper] ECE: {test_acc['paper_ece']:.3f}")
|
| 490 |
print(f" * Per-Task Acc: {per_task_acc}")
|
| 491 |
|
| 492 |
batch_last_acc_list[task_idx] += batch_last_acc # avg_acc_list[task_idx] += avg_acc
|
|
|
|
| 505 |
task_last_acc = task_last_acc_list[task_idx]
|
| 506 |
|
| 507 |
frgt, bwt = compute_frgt(acc_table, acc_table[task_idx], task_idx), compute_bwt(acc_table, acc_table[task_idx], task_idx)
|
| 508 |
+
paper_faa = compute_paper_faa(acc_table, task_idx)
|
| 509 |
+
paper_ff = compute_paper_ff(acc_table, task_idx)
|
| 510 |
+
paper_ece = paper_ece_sum / paper_ece_count if paper_ece_count > 0 else None
|
| 511 |
best_frgt, best_bwt = min(frgt, best_frgt), max(bwt, best_bwt)
|
| 512 |
if task_idx > 1:
|
| 513 |
frgt_list.append(frgt)
|
|
|
|
| 519 |
print(f" * [Task] Last Average Acc: {task_last_acc:.2f} (Best: {best_task_last_acc:.2f})")
|
| 520 |
print(f" * Forgetting: {frgt:.3f} (Best: {best_frgt:.3f})")
|
| 521 |
print(f" * Backward Transfer: {bwt:.2f} (Best: {best_bwt:.2f})")
|
| 522 |
+
print(f" * [Paper] FAA/CIL: {paper_faa:.2f}")
|
| 523 |
+
print(f" * [Paper] FF: {paper_ff:.3f}")
|
| 524 |
+
if paper_ece is not None:
|
| 525 |
+
print(f" * [Paper] ECE: {paper_ece:.3f}")
|
| 526 |
print(f" * Per-Task Acc: {acc_table[task_idx][:task_idx + 1]}")
|
| 527 |
|
| 528 |
batch_ovr_avg_acc = np.mean(batch_last_acc_list) #batch_ovr_avg_acc = np.mean(avg_acc_list)
|
|
|
|
| 532 |
|
| 533 |
ovr_bwt = np.mean(bwt_list) if len(bwt_list) > 0 else float('-inf')
|
| 534 |
ovr_frgt = np.mean(frgt_list) if len(frgt_list) > 0 else float('inf')
|
| 535 |
+
paper_faa = compute_paper_faa(acc_table, task_idx)
|
| 536 |
+
paper_ff = compute_paper_ff(acc_table, task_idx)
|
| 537 |
|
| 538 |
if self.rank == 0:
|
| 539 |
print(f"================Overall Result of {self.task_num} Tasks!================")
|
|
|
|
| 545 |
print(f" * [Task] Overall Avg Acc : {task_ovr_avg_acc:.2f}")
|
| 546 |
print(f" * Overall Frgt : {ovr_frgt:.3f}")
|
| 547 |
print(f" * Overall BwT : {ovr_bwt:.2f}")
|
| 548 |
+
print(f" * [Paper] Final Average Accuracy (FAA/CIL) : {paper_faa:.2f}")
|
| 549 |
+
print(f" * [Paper] Final Forgetting (FF) : {paper_ff:.3f}")
|
| 550 |
+
if 'paper_ece' in locals() and paper_ece is not None:
|
| 551 |
+
print(f" * [Paper] Expected Calibration Error (ECE) : {paper_ece:.3f}")
|
| 552 |
print(f" * Average Acc Table : \n{acc_table}")
|
| 553 |
|
| 554 |
print(f"================Model Performance Analysis================")
|
|
|
|
| 631 |
|
| 632 |
self.optimizer.step()
|
| 633 |
|
| 634 |
+
if hasattr(model, 'after_observe'):
|
| 635 |
+
model.after_observe()
|
| 636 |
+
|
| 637 |
if self.config["classifier"]["name"] in ['ERACE', 'ERAML']:
|
| 638 |
model.add_reservoir()
|
| 639 |
|
|
|
|
| 655 |
|
| 656 |
per_task_acc = []
|
| 657 |
count_all, correct_all = 0, 0
|
| 658 |
+
ece_confidences, ece_correctness = [], []
|
| 659 |
+
|
| 660 |
+
def _infer_batch(batch, task_id=None):
|
| 661 |
+
if self.config['setting'] == 'task-agnostic' and hasattr(model, 'predict_logits'):
|
| 662 |
+
logits = model.predict_logits(batch)
|
| 663 |
+
probs = torch.softmax(logits, dim=1)
|
| 664 |
+
confidences, preds = torch.max(probs, dim=1)
|
| 665 |
+
labels = batch['label'].to(self.device)
|
| 666 |
+
correct = preds == labels
|
| 667 |
+
ece_confidences.extend(confidences.detach().cpu().numpy().tolist())
|
| 668 |
+
ece_correctness.extend(correct.detach().cpu().numpy().tolist())
|
| 669 |
+
return preds, correct.float().mean().item()
|
| 670 |
+
|
| 671 |
+
if self.config['setting'] == 'task-aware':
|
| 672 |
+
return model.inference(batch, task_id=task_id)
|
| 673 |
+
if self.config['setting'] == 'task-agnostic':
|
| 674 |
+
return model.inference(batch)
|
| 675 |
+
raise ValueError(f"Unsupported setting: {self.config['setting']}")
|
| 676 |
|
| 677 |
if self.config['testing_per_task']:
|
| 678 |
|
|
|
|
| 683 |
correct_task, count_task = 0, 0
|
| 684 |
|
| 685 |
for b, batch in tqdm(enumerate(dataloader), total=len(dataloader), desc = f"Testing on Task {t} data", disable=self.rank != 0): # Disable tqdm for non-master processes
|
| 686 |
+
output, acc = _infer_batch(batch, task_id=t)
|
| 687 |
|
| 688 |
+
correct_task += int(round(acc * batch['label'].shape[0]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
count_task += batch['label'].shape[0]
|
| 690 |
|
| 691 |
correct_all += correct_task
|
|
|
|
| 736 |
if self.config['setting'] == 'task-aware':
|
| 737 |
print('Mostly methods dont support this, set testing_per_task to False')
|
| 738 |
raise NotImplementedError
|
| 739 |
+
output, acc = _infer_batch(batch)
|
|
|
|
|
|
|
| 740 |
preds = output.cpu().numpy()
|
| 741 |
|
| 742 |
labels = batch['label'].cpu().numpy()
|
|
|
|
| 755 |
|
| 756 |
avg_acc = round(correct_all * 100 / count_all, 2)
|
| 757 |
|
| 758 |
+
paper_ece = compute_ece(ece_confidences, ece_correctness)
|
| 759 |
+
|
| 760 |
return {
|
| 761 |
"avg_acc": avg_acc,
|
| 762 |
+
"per_task_acc": per_task_acc,
|
| 763 |
+
"paper_ece": paper_ece,
|
| 764 |
}
|
core/utils/utils.py
CHANGED
|
@@ -22,8 +22,7 @@ class AverageMeter(object):
|
|
| 22 |
self.reset()
|
| 23 |
|
| 24 |
def reset(self):
|
| 25 |
-
|
| 26 |
-
self._data[col].values[:] = 0
|
| 27 |
|
| 28 |
def update(self, key, value, n=1):
|
| 29 |
if self.writer is not None:
|
|
@@ -200,7 +199,7 @@ def fmt_date_str(date=None, fmt="%y-%m-%d-%H-%M-%S"):
|
|
| 200 |
return date.strftime(fmt)
|
| 201 |
|
| 202 |
def compute_bwt(acc_table, curr_acc, task_idx):
|
| 203 |
-
'''
|
| 204 |
After training T tasks, $BWT = \frac{\sum_{i=3}^T\sum_{j=1}^{i-2}R_{i,j}-R{j,j}}{T(T-1)/2}$
|
| 205 |
Equivalent to Positive BwT of Continuum
|
| 206 |
https://arxiv.org/pdf/1810.13166
|
|
@@ -222,7 +221,7 @@ def compute_bwt(acc_table, curr_acc, task_idx):
|
|
| 222 |
|
| 223 |
|
| 224 |
def compute_frgt(acc_table, curr_acc, task_idx):
|
| 225 |
-
'''
|
| 226 |
After training T tasks, $Frgt = \frac{\sum_{j=1}^{T-2}R_{T-1,j}-R_{j,j}}{T-1}$
|
| 227 |
Equivalent to Forgetting of Continuum
|
| 228 |
'''
|
|
@@ -232,6 +231,47 @@ def compute_frgt(acc_table, curr_acc, task_idx):
|
|
| 232 |
return 0.
|
| 233 |
|
| 234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
def compute_fps(model, config):
|
| 236 |
model.eval()
|
| 237 |
|
|
@@ -254,4 +294,4 @@ def compute_fps(model, config):
|
|
| 254 |
t_all.append(t2 - t1)
|
| 255 |
|
| 256 |
return {'avg_fps' : 1 / np.mean(t_all),
|
| 257 |
-
'best_fps' : 1 / min(t_all)}
|
|
|
|
| 22 |
self.reset()
|
| 23 |
|
| 24 |
def reset(self):
|
| 25 |
+
self._data.loc[:, :] = 0
|
|
|
|
| 26 |
|
| 27 |
def update(self, key, value, n=1):
|
| 28 |
if self.writer is not None:
|
|
|
|
| 199 |
return date.strftime(fmt)
|
| 200 |
|
| 201 |
def compute_bwt(acc_table, curr_acc, task_idx):
|
| 202 |
+
r'''
|
| 203 |
After training T tasks, $BWT = \frac{\sum_{i=3}^T\sum_{j=1}^{i-2}R_{i,j}-R{j,j}}{T(T-1)/2}$
|
| 204 |
Equivalent to Positive BwT of Continuum
|
| 205 |
https://arxiv.org/pdf/1810.13166
|
|
|
|
| 221 |
|
| 222 |
|
| 223 |
def compute_frgt(acc_table, curr_acc, task_idx):
|
| 224 |
+
r'''
|
| 225 |
After training T tasks, $Frgt = \frac{\sum_{j=1}^{T-2}R_{T-1,j}-R_{j,j}}{T-1}$
|
| 226 |
Equivalent to Forgetting of Continuum
|
| 227 |
'''
|
|
|
|
| 231 |
return 0.
|
| 232 |
|
| 233 |
|
| 234 |
+
def compute_paper_faa(acc_table, task_idx):
|
| 235 |
+
"""
|
| 236 |
+
Final Average Accuracy (FAA) used by IDER: average accuracy over all
|
| 237 |
+
tasks observed so far after finishing the current task.
|
| 238 |
+
"""
|
| 239 |
+
return float(np.mean(acc_table[task_idx, :task_idx + 1]))
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def compute_paper_ff(acc_table, task_idx):
|
| 243 |
+
"""
|
| 244 |
+
Final Forgetting (FF) used by IDER:
|
| 245 |
+
mean over old tasks of historical best accuracy minus final accuracy.
|
| 246 |
+
"""
|
| 247 |
+
if task_idx <= 0:
|
| 248 |
+
return 0.
|
| 249 |
+
|
| 250 |
+
old_task_acc = acc_table[:task_idx + 1, :task_idx]
|
| 251 |
+
best_old_acc = np.max(old_task_acc, axis=0)
|
| 252 |
+
final_old_acc = acc_table[task_idx, :task_idx]
|
| 253 |
+
return float(np.mean(best_old_acc - final_old_acc))
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def compute_ece(confidences, correctness, n_bins=15):
|
| 257 |
+
"""Expected Calibration Error in the IDER paper."""
|
| 258 |
+
if len(confidences) == 0:
|
| 259 |
+
return None
|
| 260 |
+
|
| 261 |
+
confidences = np.asarray(confidences)
|
| 262 |
+
correctness = np.asarray(correctness).astype(float)
|
| 263 |
+
ece = 0.
|
| 264 |
+
bin_boundaries = np.linspace(0., 1., n_bins + 1)
|
| 265 |
+
for bin_lower, bin_upper in zip(bin_boundaries[:-1], bin_boundaries[1:]):
|
| 266 |
+
in_bin = (confidences > bin_lower) & (confidences <= bin_upper)
|
| 267 |
+
prop_in_bin = np.mean(in_bin)
|
| 268 |
+
if prop_in_bin > 0:
|
| 269 |
+
acc_in_bin = np.mean(correctness[in_bin])
|
| 270 |
+
conf_in_bin = np.mean(confidences[in_bin])
|
| 271 |
+
ece += prop_in_bin * abs(conf_in_bin - acc_in_bin)
|
| 272 |
+
return float(ece * 100)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
def compute_fps(model, config):
|
| 276 |
model.eval()
|
| 277 |
|
|
|
|
| 294 |
t_all.append(t2 - t1)
|
| 295 |
|
| 296 |
return {'avg_fps' : 1 / np.mean(t_all),
|
| 297 |
+
'best_fps' : 1 / min(t_all)}
|
reproduce/ewcdr/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EWC-DR: Elastic Weight Consolidation Done Right for Continual Learning [(CVPR'2026)](https://arxiv.org/abs/2603.18596)
|
| 2 |
+
|
| 3 |
+
## Abstract
|
| 4 |
+
|
| 5 |
+
Weight-regularization methods alleviate catastrophic forgetting by estimating
|
| 6 |
+
which parameters are important for previous tasks and penalizing their changes.
|
| 7 |
+
Elastic Weight Consolidation (EWC) is a classic method in this family, but its
|
| 8 |
+
Fisher-based importance estimation can suffer from vanishing gradients when the
|
| 9 |
+
model is already confident on old-task samples. EWC-DR addresses this issue with
|
| 10 |
+
a simple Logits Reversal operation during importance estimation:
|
| 11 |
+
|
| 12 |
+
```python
|
| 13 |
+
loss = cross_entropy(-logits, labels)
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
This migrated implementation follows the official EWC-DR logic in the
|
| 17 |
+
LibContinual training interface. It keeps the expanding classifier, new-class
|
| 18 |
+
cross entropy for incremental tasks, clipped importance weights, and
|
| 19 |
+
exemplar-free class-incremental learning setting.
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
## Citation
|
| 23 |
+
|
| 24 |
+
```bibtex
|
| 25 |
+
@inproceedings{liu2026elastic,
|
| 26 |
+
title={Elastic Weight Consolidation Done Right for Continual Learning},
|
| 27 |
+
author={Liu, Xuan and Chang, Xiaobin},
|
| 28 |
+
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
|
| 29 |
+
year={2026}
|
| 30 |
+
}
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## How to Reproduce
|
| 34 |
+
|
| 35 |
+
- **Step 1: Install dependencies**
|
| 36 |
+
|
| 37 |
+
Use the repository dependency file:
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
conda create -n libcontinual-ewcdr python=3.8 -y
|
| 41 |
+
conda activate libcontinual-ewcdr
|
| 42 |
+
pip install -r requirements.txt
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
- **Step 2: Prepare CIFAR-100**
|
| 46 |
+
|
| 47 |
+
The provided configs use `binary_cifar100`. CIFAR-100 will be downloaded to:
|
| 48 |
+
|
| 49 |
+
```text
|
| 50 |
+
./data/binary_cifar100/cifar-100-python
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
- **Step 3: Run one setting**
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
python run_trainer.py --config ewcdr-resnet18-cifar100-b10-10-10.yaml --device 0
|
| 57 |
+
python run_trainer.py --config ewcdr-resnet18-cifar100-b50-5-11.yaml --device 0
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
- **Step 4: Run all CIFAR-100 Table 1 EWC-DR settings**
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
python reproduce/ewcdr/run_cifar100_table1.py --device 0
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
To reproduce the paper-style mean over three independent trials:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
python reproduce/ewcdr/run_cifar100_table1.py --device 0 --seeds 1993 1994 1995
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
- **Step 5: Run ImageNet-Subset Table 2 or Tiny-ImageNet Table 3**
|
| 73 |
+
|
| 74 |
+
Prepare ImageNet-Subset in ImageFolder layout under
|
| 75 |
+
`./data/ImageNet-100/imagenet-100/{train,val}/<class>/`. Tiny-ImageNet is
|
| 76 |
+
downloaded by the existing `tiny-imagenet` data backend under
|
| 77 |
+
`./data/tiny-imagenet-200` (`data_root` itself is `./data`, because the
|
| 78 |
+
backend appends the dataset directory name).
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
python reproduce/ewcdr/run_imagenet_subset_table2.py --device 0
|
| 82 |
+
python reproduce/ewcdr/run_tiny_imagenet_table3.py --device 0
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
Use `--seeds 1993 1994 1995` for the paper-style three-run mean, or
|
| 86 |
+
`--dry-run` to inspect all generated commands. The runners execute two jobs
|
| 87 |
+
concurrently per GPU by default. Pass multiple IDs, for example
|
| 88 |
+
`--device 0 1 2 --max-parallel 2`, to run at most two jobs on each GPU
|
| 89 |
+
(six total). They archive stable logs plus `summary.md` under
|
| 90 |
+
`reproduce/ewcdr/logs/<dataset_table>/`.
|
| 91 |
+
|
| 92 |
+
## Settings
|
| 93 |
+
|
| 94 |
+
| Paper Setting | LibContinual Config | Class Split |
|
| 95 |
+
| :---: | :--- | :--- |
|
| 96 |
+
| Big T=5 | `ewcdr-resnet18-cifar100-b50-10-6.yaml` | 50 initial + 5 increments x 10 classes |
|
| 97 |
+
| Big T=10 | `ewcdr-resnet18-cifar100-b50-5-11.yaml` | 50 initial + 10 increments x 5 classes |
|
| 98 |
+
| Big T=20 | `ewcdr-resnet18-cifar100-b40-3-21.yaml` | 40 initial + 20 increments x 3 classes |
|
| 99 |
+
| Eq T=5 | `ewcdr-resnet18-cifar100-b20-20-5.yaml` | 5 tasks x 20 classes |
|
| 100 |
+
| Eq T=10 | `ewcdr-resnet18-cifar100-b10-10-10.yaml` | 10 tasks x 10 classes |
|
| 101 |
+
| Eq T=20 | `ewcdr-resnet18-cifar100-b5-5-20.yaml` | 20 tasks x 5 classes |
|
| 102 |
+
|
| 103 |
+
ImageNet-Subset uses the same six 100-class splits as CIFAR-100. Its configs
|
| 104 |
+
are named `ewcdr-resnet18-imagenet100-*.yaml`. Tiny-ImageNet uses:
|
| 105 |
+
|
| 106 |
+
| Paper Setting | LibContinual Config | Class Split |
|
| 107 |
+
| :---: | :--- | :--- |
|
| 108 |
+
| Big T=5 | `ewcdr-resnet18-tinyimagenet-b100-20-6.yaml` | 100 initial + 5 increments x 20 classes |
|
| 109 |
+
| Big T=10 | `ewcdr-resnet18-tinyimagenet-b100-10-11.yaml` | 100 initial + 10 increments x 10 classes |
|
| 110 |
+
| Big T=20 | `ewcdr-resnet18-tinyimagenet-b100-5-21.yaml` | 100 initial + 20 increments x 5 classes |
|
| 111 |
+
| Eq T=5 | `ewcdr-resnet18-tinyimagenet-b40-40-5.yaml` | 5 tasks x 40 classes |
|
| 112 |
+
| Eq T=10 | `ewcdr-resnet18-tinyimagenet-b20-20-10.yaml` | 10 tasks x 20 classes |
|
| 113 |
+
| Eq T=20 | `ewcdr-resnet18-tinyimagenet-b10-10-20.yaml` | 20 tasks x 10 classes |
|
| 114 |
+
|
| 115 |
+
## Results
|
| 116 |
+
|
| 117 |
+
The reproduced results below are single-run results with `seed=1993`. The paper
|
| 118 |
+
reports means over three independent trials.
|
| 119 |
+
|
| 120 |
+
### CIFAR-100
|
| 121 |
+
|
| 122 |
+
| Setting | Seed | LibContinual A_last | Paper A_last | Diff | LibContinual A_avg | Paper A_avg | Diff |
|
| 123 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 124 |
+
| Big start / T=5 | 1993 | 50.87 | 50.23 | **+0.64** | 64.48 | 63.75 | **+0.73** |
|
| 125 |
+
| Big start / T=10 | 1993 | 46.91 | 44.88 | **+2.03** | 61.09 | 60.94 | **+0.15** |
|
| 126 |
+
| Big start / T=20 | 1993 | 36.02 | 35.86 | **+0.16** | 52.80 | 53.45 | **-0.65** |
|
| 127 |
+
| Equally split / T=5 | 1993 | 46.45 | 46.89 | **-0.44** | 61.42 | 61.47 | **-0.05** |
|
| 128 |
+
| Equally split / T=10 | 1993 | 30.34 | 29.41 | **+0.93** | 47.45 | 46.01 | **+1.44** |
|
| 129 |
+
| Equally split / T=20 | 1993 | 19.15 | 18.00 | **+1.15** | 34.44 | 33.52 | **+0.92** |
|
| 130 |
+
|
| 131 |
+
### Tiny-ImageNet
|
| 132 |
+
|
| 133 |
+
| Setting | Seed | LibContinual A_last | Paper A_last | Diff | LibContinual A_avg | Paper A_avg | Diff |
|
| 134 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 135 |
+
| Big start / T=5 | 1993 | 39.59 | 38.24 | **+1.35** | 47.47 | 47.00 | **+0.47** |
|
| 136 |
+
| Big start / T=10 | 1993 | 35.48 | 31.43 | **+4.05** | 45.13 | 42.88 | **+2.25** |
|
| 137 |
+
| Big start / T=20 | 1993 | 30.69 | 23.64 | **+7.05** | 41.32 | 37.56 | **+3.76** |
|
| 138 |
+
| Equally split / T=5 | 1993 | 26.46 | 28.67 | **-2.21** | 38.34 | 39.52 | **-1.18** |
|
| 139 |
+
| Equally split / T=10 | 1993 | 21.46 | 21.46 | **+0.00** | 34.30 | 32.79 | **+1.51** |
|
| 140 |
+
| Equally split / T=20 | 1993 | 13.60 | 12.09 | **+1.51** | 24.50 | 22.62 | **+1.88** |
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
The LibContinual metrics correspond to the final log fields:
|
| 144 |
+
|
| 145 |
+
| Paper Metric | LibContinual Log Field |
|
| 146 |
+
| :---: | :--- |
|
| 147 |
+
| `A_last` | `[Batch] Last Average Acc` |
|
| 148 |
+
| `A_avg` | `[Batch] Overall Avg Acc` |
|
| 149 |
+
|
| 150 |
+
## Notes
|
| 151 |
+
|
| 152 |
+
- The official big-start `T` counts incremental phases, while LibContinual
|
| 153 |
+
`task_num` includes the initial phase. For example, Big T=10 is configured as
|
| 154 |
+
`task_num=11`.
|
| 155 |
+
- `binary_cifar100` must follow the same shuffled class order and label remap as
|
| 156 |
+
the official implementation; this is handled in the migrated data pipeline.
|
| 157 |
+
- Initial and incremental stages use different schedulers, matching the official
|
| 158 |
+
setup.
|
| 159 |
+
- The Table 1 runner can launch multiple jobs on the same GPU for convenience.
|
| 160 |
+
For strict reporting, prefer three seeds and avoid overloading one GPU.
|
reproduce/fly_cl/run_cifar100_ablations.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run the five Fly-CL component ablations on CIFAR-100.
|
| 2 |
+
|
| 3 |
+
The script caches the frozen ViT features once for each input-normalization
|
| 4 |
+
setting and delegates every analytic update and prediction to ``FlyCL``.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import json
|
| 9 |
+
import math
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
ROOT = Path(__file__).resolve().parents[2]
|
| 17 |
+
if str(ROOT) not in sys.path:
|
| 18 |
+
sys.path.insert(0, str(ROOT))
|
| 19 |
+
|
| 20 |
+
from core.config import Config
|
| 21 |
+
from reproduce.fly_cl.run_cub200_ablations import (
|
| 22 |
+
COMPONENT_EXPERIMENTS,
|
| 23 |
+
load_or_build_cache,
|
| 24 |
+
run_experiment,
|
| 25 |
+
save_results,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
EXPECTED_EXPERIMENTS = {spec["name"] for spec in COMPONENT_EXPERIMENTS}
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def validate_complete_results(results):
|
| 33 |
+
by_name = {item["name"]: item for item in results}
|
| 34 |
+
if set(by_name) != EXPECTED_EXPERIMENTS:
|
| 35 |
+
missing = EXPECTED_EXPERIMENTS - set(by_name)
|
| 36 |
+
extra = set(by_name) - EXPECTED_EXPERIMENTS
|
| 37 |
+
raise ValueError(
|
| 38 |
+
f"Incomplete CIFAR sweep; missing={sorted(missing)}, "
|
| 39 |
+
f"extra={sorted(extra)}"
|
| 40 |
+
)
|
| 41 |
+
if len(by_name) != len(results):
|
| 42 |
+
raise ValueError("Duplicate CIFAR experiment names")
|
| 43 |
+
for name, item in by_name.items():
|
| 44 |
+
if len(item["stage_average_accuracy"]) != 10:
|
| 45 |
+
raise ValueError(f"{name}: expected 10 stage accuracies")
|
| 46 |
+
if [len(row) for row in item["accuracy_matrix"]] != list(range(1, 11)):
|
| 47 |
+
raise ValueError(f"{name}: malformed triangular accuracy matrix")
|
| 48 |
+
metrics = (
|
| 49 |
+
item["overall_accuracy"],
|
| 50 |
+
item["last_stage_accuracy"],
|
| 51 |
+
item["bwt"],
|
| 52 |
+
item["wall_seconds"],
|
| 53 |
+
)
|
| 54 |
+
if not all(math.isfinite(value) for value in metrics):
|
| 55 |
+
raise ValueError(f"{name}: non-finite metric")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def main():
|
| 59 |
+
parser = argparse.ArgumentParser()
|
| 60 |
+
parser.add_argument("--config", default="config/fly_cl_cifar100.yaml")
|
| 61 |
+
parser.add_argument("--device", type=int, default=0)
|
| 62 |
+
parser.add_argument("--rebuild-cache", action="store_true")
|
| 63 |
+
parser.add_argument(
|
| 64 |
+
"--output", default="outputs/fly_cl_cifar100_ablations.json"
|
| 65 |
+
)
|
| 66 |
+
parser.add_argument("--only", nargs="*")
|
| 67 |
+
parser.add_argument("--rerun", nargs="*", default=())
|
| 68 |
+
args = parser.parse_args()
|
| 69 |
+
|
| 70 |
+
os.chdir(ROOT)
|
| 71 |
+
config = Config(args.config).get_config_dict()
|
| 72 |
+
device = torch.device(f"cuda:{args.device}")
|
| 73 |
+
cache_dir = ROOT / "outputs" / "fly_cl_cifar100_feature_cache"
|
| 74 |
+
normalized_cache = load_or_build_cache(
|
| 75 |
+
config,
|
| 76 |
+
True,
|
| 77 |
+
cache_dir / f"normalized_seed{config['seed']}.pt",
|
| 78 |
+
device,
|
| 79 |
+
args.rebuild_cache,
|
| 80 |
+
)
|
| 81 |
+
no_norm_cache = load_or_build_cache(
|
| 82 |
+
config,
|
| 83 |
+
False,
|
| 84 |
+
cache_dir / f"no_normalization_seed{config['seed']}.pt",
|
| 85 |
+
device,
|
| 86 |
+
args.rebuild_cache,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
specs = list(COMPONENT_EXPERIMENTS)
|
| 90 |
+
if args.only:
|
| 91 |
+
requested = set(args.only)
|
| 92 |
+
unknown = requested - EXPECTED_EXPERIMENTS
|
| 93 |
+
if unknown:
|
| 94 |
+
raise ValueError(f"Unknown CIFAR experiments: {sorted(unknown)}")
|
| 95 |
+
specs = [spec for spec in specs if spec["name"] in requested]
|
| 96 |
+
rerun = set(args.rerun)
|
| 97 |
+
unknown_reruns = rerun - {spec["name"] for spec in specs}
|
| 98 |
+
if unknown_reruns:
|
| 99 |
+
raise ValueError(f"Unknown CIFAR reruns: {sorted(unknown_reruns)}")
|
| 100 |
+
|
| 101 |
+
output = ROOT / args.output
|
| 102 |
+
previous = {}
|
| 103 |
+
if output.exists():
|
| 104 |
+
with output.open("r", encoding="utf-8") as handle:
|
| 105 |
+
previous = {
|
| 106 |
+
item["name"]: item for item in json.load(handle)["experiments"]
|
| 107 |
+
}
|
| 108 |
+
completed = dict(previous)
|
| 109 |
+
metadata = {
|
| 110 |
+
"dataset": config["dataset"],
|
| 111 |
+
"seed": config["seed"],
|
| 112 |
+
"train_images": sum(len(x["labels"]) for x in normalized_cache["train"]),
|
| 113 |
+
"test_images": sum(len(x["labels"]) for x in normalized_cache["test"]),
|
| 114 |
+
"normalized_feature_seconds": normalized_cache["feature_seconds"],
|
| 115 |
+
"no_normalization_feature_seconds": no_norm_cache["feature_seconds"],
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
for spec in specs:
|
| 119 |
+
if spec["name"] in previous and spec["name"] not in rerun:
|
| 120 |
+
print(f"Skipping completed experiment: {spec['name']}")
|
| 121 |
+
continue
|
| 122 |
+
cache = normalized_cache if spec.get("normalization", True) else no_norm_cache
|
| 123 |
+
print(f"Running CIFAR-100 experiment: {spec['name']}")
|
| 124 |
+
completed[spec["name"]] = run_experiment(config, spec, cache, device)
|
| 125 |
+
save_results(output, metadata, list(completed.values()))
|
| 126 |
+
|
| 127 |
+
results = list(completed.values())
|
| 128 |
+
if set(completed) == EXPECTED_EXPERIMENTS:
|
| 129 |
+
validate_complete_results(results)
|
| 130 |
+
save_results(output, metadata, results)
|
| 131 |
+
print(f"Saved {len(results)} CIFAR-100 experiments to {output}")
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
if __name__ == "__main__":
|
| 135 |
+
main()
|
reproduce/fly_cl/run_cub200_ablations.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run the Fly-CL CUB-200 component ablations and sensitivity sweeps.
|
| 2 |
+
|
| 3 |
+
The frozen ViT features are cached once per input-normalization setting. Every
|
| 4 |
+
analytic experiment still uses ``core.model.fly_cl.FlyCL`` for projection,
|
| 5 |
+
streaming statistics, GCV, Cholesky solving, prototype construction, and
|
| 6 |
+
inference. This removes repeated backbone inference without changing the
|
| 7 |
+
algorithm under study.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
import copy
|
| 12 |
+
import gc
|
| 13 |
+
import json
|
| 14 |
+
import math
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
import time
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
import torch.nn as nn
|
| 22 |
+
|
| 23 |
+
ROOT = Path(__file__).resolve().parents[2]
|
| 24 |
+
if str(ROOT) not in sys.path:
|
| 25 |
+
sys.path.insert(0, str(ROOT))
|
| 26 |
+
|
| 27 |
+
from core.config import Config
|
| 28 |
+
from core.data import get_dataloader
|
| 29 |
+
from core.model.backbone.timm_backbone import timm_backbone
|
| 30 |
+
from core.model.fly_cl import FlyCL
|
| 31 |
+
from core.utils import init_seed
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
COMPONENT_EXPERIMENTS = [
|
| 35 |
+
{"name": "full", "normalization": True},
|
| 36 |
+
{
|
| 37 |
+
"name": "wo_projection",
|
| 38 |
+
"normalization": True,
|
| 39 |
+
"use_projection": False,
|
| 40 |
+
# Removing the expansion changes the feature scale by orders of
|
| 41 |
+
# magnitude. A broad GCV grid is required for a meaningful ablation.
|
| 42 |
+
"ridge_lower": 0,
|
| 43 |
+
"ridge_upper": 10,
|
| 44 |
+
},
|
| 45 |
+
{"name": "wo_ridge", "normalization": True, "use_ridge": False},
|
| 46 |
+
{"name": "wo_normalization", "normalization": False},
|
| 47 |
+
{
|
| 48 |
+
"name": "wo_all",
|
| 49 |
+
"normalization": False,
|
| 50 |
+
"use_projection": False,
|
| 51 |
+
"use_ridge": False,
|
| 52 |
+
},
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
SENSITIVITY_EXPERIMENTS = (
|
| 56 |
+
[
|
| 57 |
+
{"name": f"m_{m}", "group": "expand_dim", "expand_dim": m}
|
| 58 |
+
for m in (500, 1000, 2000, 5000, 10000, 20000)
|
| 59 |
+
]
|
| 60 |
+
+ [
|
| 61 |
+
{"name": f"p_{p}", "group": "synaptic_degree", "synaptic_degree": p}
|
| 62 |
+
for p in (50, 100, 200, 300, 500, 700, 768)
|
| 63 |
+
]
|
| 64 |
+
+ [
|
| 65 |
+
{
|
| 66 |
+
"name": f"k_{k}",
|
| 67 |
+
"group": "activation_sparsity",
|
| 68 |
+
"coding_level": k / 10000,
|
| 69 |
+
"coding_k": k,
|
| 70 |
+
}
|
| 71 |
+
for k in (500, 1000, 2000, 3000, 4000, 5000, 7000, 8000, 9000, 10000)
|
| 72 |
+
]
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
EXPECTED_EXPERIMENTS = {
|
| 76 |
+
spec["name"] for spec in COMPONENT_EXPERIMENTS + SENSITIVITY_EXPERIMENTS
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class IdentityBackbone(nn.Module):
|
| 81 |
+
def __init__(self, feat_dim):
|
| 82 |
+
super().__init__()
|
| 83 |
+
self.feat_dim = feat_dim
|
| 84 |
+
|
| 85 |
+
def forward(self, features):
|
| 86 |
+
return features
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _without_normalization(config):
|
| 90 |
+
config = copy.deepcopy(config)
|
| 91 |
+
for key in ("train_trfms", "test_trfms"):
|
| 92 |
+
config[key] = [item for item in config[key] if "Normalize" not in item]
|
| 93 |
+
return config
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
@torch.no_grad()
|
| 97 |
+
def _extract_split(backbone, loaders, device):
|
| 98 |
+
tasks = []
|
| 99 |
+
for task_idx, loader in enumerate(loaders.dataloaders):
|
| 100 |
+
features, labels = [], []
|
| 101 |
+
for batch in loader:
|
| 102 |
+
images = batch["image"].to(device, non_blocking=True)
|
| 103 |
+
features.append(backbone(images).cpu())
|
| 104 |
+
labels.append(batch["label"].cpu())
|
| 105 |
+
tasks.append(
|
| 106 |
+
{
|
| 107 |
+
"features": torch.cat(features),
|
| 108 |
+
"labels": torch.cat(labels),
|
| 109 |
+
"task": task_idx,
|
| 110 |
+
}
|
| 111 |
+
)
|
| 112 |
+
return tasks
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def build_feature_cache(config, normalized, cache_path, device):
|
| 116 |
+
variant = config if normalized else _without_normalization(config)
|
| 117 |
+
init_seed(variant["seed"], variant["deterministic"])
|
| 118 |
+
backbone = timm_backbone(**variant["backbone"]["kwargs"]).to(device).eval()
|
| 119 |
+
|
| 120 |
+
# FlyCL constructs its random projection immediately after the backbone.
|
| 121 |
+
# Saving this state lets cached-feature experiments reproduce that ordering.
|
| 122 |
+
projection_rng_state = torch.get_rng_state().clone()
|
| 123 |
+
train_loaders = get_dataloader(variant, "train")
|
| 124 |
+
test_loaders = get_dataloader(
|
| 125 |
+
variant, "test", cls_map=train_loaders.cls_map
|
| 126 |
+
)
|
| 127 |
+
started = time.perf_counter()
|
| 128 |
+
train = _extract_split(backbone, train_loaders, device)
|
| 129 |
+
test = _extract_split(backbone, test_loaders, device)
|
| 130 |
+
cache = {
|
| 131 |
+
"normalized": normalized,
|
| 132 |
+
"seed": variant["seed"],
|
| 133 |
+
"class_map": train_loaders.cls_map,
|
| 134 |
+
"projection_rng_state": projection_rng_state,
|
| 135 |
+
"train": train,
|
| 136 |
+
"test": test,
|
| 137 |
+
"feature_seconds": time.perf_counter() - started,
|
| 138 |
+
}
|
| 139 |
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
| 140 |
+
torch.save(cache, cache_path)
|
| 141 |
+
del backbone, train_loaders, test_loaders
|
| 142 |
+
torch.cuda.empty_cache()
|
| 143 |
+
return cache
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def load_or_build_cache(config, normalized, cache_path, device, rebuild=False):
|
| 147 |
+
if cache_path.exists() and not rebuild:
|
| 148 |
+
cache = torch.load(cache_path, map_location="cpu")
|
| 149 |
+
if cache.get("seed") != config["seed"]:
|
| 150 |
+
raise ValueError(f"Stale cache seed in {cache_path}")
|
| 151 |
+
return cache
|
| 152 |
+
return build_feature_cache(config, normalized, cache_path, device)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@torch.no_grad()
|
| 156 |
+
def evaluate_task(model, task, device, batch_size=512):
|
| 157 |
+
correct = 0
|
| 158 |
+
total = len(task["labels"])
|
| 159 |
+
for start in range(0, total, batch_size):
|
| 160 |
+
stop = min(start + batch_size, total)
|
| 161 |
+
features = task["features"][start:stop].to(device)
|
| 162 |
+
labels = task["labels"][start:stop]
|
| 163 |
+
logits, _ = model.predict_features(features)
|
| 164 |
+
correct += logits.argmax(dim=1).cpu().eq(labels).sum().item()
|
| 165 |
+
return 100.0 * correct / total
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def run_experiment(base_config, spec, cache, device):
|
| 169 |
+
kwargs = copy.deepcopy(base_config["classifier"]["kwargs"])
|
| 170 |
+
kwargs.update(
|
| 171 |
+
{
|
| 172 |
+
key: value
|
| 173 |
+
for key, value in spec.items()
|
| 174 |
+
if key
|
| 175 |
+
in {
|
| 176 |
+
"expand_dim",
|
| 177 |
+
"synaptic_degree",
|
| 178 |
+
"coding_level",
|
| 179 |
+
"ridge_lower",
|
| 180 |
+
"ridge_upper",
|
| 181 |
+
"use_projection",
|
| 182 |
+
"use_ridge",
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
)
|
| 186 |
+
torch.set_rng_state(cache["projection_rng_state"].clone())
|
| 187 |
+
model = FlyCL(
|
| 188 |
+
IdentityBackbone(kwargs["embedding_dim"]), device, **kwargs
|
| 189 |
+
).to(device)
|
| 190 |
+
|
| 191 |
+
accuracy_matrix = []
|
| 192 |
+
stage_average = []
|
| 193 |
+
ridge_values = []
|
| 194 |
+
post_seconds = []
|
| 195 |
+
started = time.perf_counter()
|
| 196 |
+
for task_idx, train_task in enumerate(cache["train"]):
|
| 197 |
+
model.before_task(task_idx, None, None, None)
|
| 198 |
+
task_started = time.perf_counter()
|
| 199 |
+
model.fit_features(
|
| 200 |
+
train_task["features"].to(device), train_task["labels"].to(device)
|
| 201 |
+
)
|
| 202 |
+
post_seconds.append(time.perf_counter() - task_started)
|
| 203 |
+
ridge_values.append(model.last_ridge)
|
| 204 |
+
row = [
|
| 205 |
+
evaluate_task(model, cache["test"][test_idx], device)
|
| 206 |
+
for test_idx in range(task_idx + 1)
|
| 207 |
+
]
|
| 208 |
+
accuracy_matrix.append(row)
|
| 209 |
+
stage_average.append(sum(row) / len(row))
|
| 210 |
+
print(
|
| 211 |
+
f"[{spec['name']}] task={task_idx + 1}/10 "
|
| 212 |
+
f"A_t={stage_average[-1]:.4f}"
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
final_row = accuracy_matrix[-1]
|
| 216 |
+
bwt = sum(
|
| 217 |
+
final_row[idx] - accuracy_matrix[idx][idx]
|
| 218 |
+
for idx in range(len(final_row) - 1)
|
| 219 |
+
) / (len(final_row) - 1)
|
| 220 |
+
result = {
|
| 221 |
+
**spec,
|
| 222 |
+
"stage_average_accuracy": stage_average,
|
| 223 |
+
"accuracy_matrix": accuracy_matrix,
|
| 224 |
+
"overall_accuracy": sum(stage_average) / len(stage_average),
|
| 225 |
+
"last_stage_accuracy": stage_average[-1],
|
| 226 |
+
"bwt": bwt,
|
| 227 |
+
"ridge_values": ridge_values,
|
| 228 |
+
"post_seconds_per_task": post_seconds,
|
| 229 |
+
"wall_seconds": time.perf_counter() - started,
|
| 230 |
+
}
|
| 231 |
+
del model
|
| 232 |
+
gc.collect()
|
| 233 |
+
torch.cuda.empty_cache()
|
| 234 |
+
return result
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def save_results(path, metadata, results):
|
| 238 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 239 |
+
payload = {"metadata": metadata, "experiments": results}
|
| 240 |
+
with path.open("w", encoding="utf-8") as handle:
|
| 241 |
+
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def validate_complete_results(results):
|
| 245 |
+
"""Fail loudly if a supposedly complete sweep is missing or malformed."""
|
| 246 |
+
by_name = {item["name"]: item for item in results}
|
| 247 |
+
if set(by_name) != EXPECTED_EXPERIMENTS:
|
| 248 |
+
missing = EXPECTED_EXPERIMENTS - set(by_name)
|
| 249 |
+
extra = set(by_name) - EXPECTED_EXPERIMENTS
|
| 250 |
+
raise ValueError(f"Incomplete sweep; missing={sorted(missing)}, extra={sorted(extra)}")
|
| 251 |
+
if len(by_name) != len(results):
|
| 252 |
+
raise ValueError("Duplicate experiment names in result file")
|
| 253 |
+
for name, item in by_name.items():
|
| 254 |
+
if len(item["stage_average_accuracy"]) != 10:
|
| 255 |
+
raise ValueError(f"{name}: expected 10 stage accuracies")
|
| 256 |
+
if [len(row) for row in item["accuracy_matrix"]] != list(range(1, 11)):
|
| 257 |
+
raise ValueError(f"{name}: malformed triangular accuracy matrix")
|
| 258 |
+
numeric = [
|
| 259 |
+
item["overall_accuracy"],
|
| 260 |
+
item["last_stage_accuracy"],
|
| 261 |
+
item["bwt"],
|
| 262 |
+
item["wall_seconds"],
|
| 263 |
+
]
|
| 264 |
+
if not all(math.isfinite(value) for value in numeric):
|
| 265 |
+
raise ValueError(f"{name}: non-finite metric")
|
| 266 |
+
|
| 267 |
+
reference = by_name["full"]["accuracy_matrix"]
|
| 268 |
+
for duplicate in ("m_10000", "p_300", "k_3000"):
|
| 269 |
+
if by_name[duplicate]["accuracy_matrix"] != reference:
|
| 270 |
+
raise ValueError(f"{duplicate} does not reproduce the default full model")
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def main():
|
| 274 |
+
parser = argparse.ArgumentParser()
|
| 275 |
+
parser.add_argument("--config", default="config/fly_cl_cub200.yaml")
|
| 276 |
+
parser.add_argument("--device", type=int, default=0)
|
| 277 |
+
parser.add_argument("--rebuild-cache", action="store_true")
|
| 278 |
+
parser.add_argument(
|
| 279 |
+
"--scope", choices=("all", "components", "sensitivity"), default="all"
|
| 280 |
+
)
|
| 281 |
+
parser.add_argument(
|
| 282 |
+
"--output", default="outputs/fly_cl_cub200_ablations.json"
|
| 283 |
+
)
|
| 284 |
+
parser.add_argument(
|
| 285 |
+
"--only",
|
| 286 |
+
nargs="*",
|
| 287 |
+
help="Optional experiment names, for example: --only full wo_ridge m_500",
|
| 288 |
+
)
|
| 289 |
+
parser.add_argument(
|
| 290 |
+
"--rerun",
|
| 291 |
+
nargs="*",
|
| 292 |
+
default=(),
|
| 293 |
+
help="Recompute named experiments even when they exist in the output JSON",
|
| 294 |
+
)
|
| 295 |
+
args = parser.parse_args()
|
| 296 |
+
|
| 297 |
+
root = ROOT
|
| 298 |
+
os.chdir(root)
|
| 299 |
+
config = Config(args.config).get_config_dict()
|
| 300 |
+
device = torch.device(f"cuda:{args.device}")
|
| 301 |
+
cache_dir = root / "outputs" / "fly_cl_cub200_feature_cache"
|
| 302 |
+
normalized_cache = load_or_build_cache(
|
| 303 |
+
config,
|
| 304 |
+
True,
|
| 305 |
+
cache_dir / f"normalized_seed{config['seed']}.pt",
|
| 306 |
+
device,
|
| 307 |
+
args.rebuild_cache,
|
| 308 |
+
)
|
| 309 |
+
no_norm_cache = None
|
| 310 |
+
if args.scope in ("all", "components"):
|
| 311 |
+
no_norm_cache = load_or_build_cache(
|
| 312 |
+
config,
|
| 313 |
+
False,
|
| 314 |
+
cache_dir / f"no_normalization_seed{config['seed']}.pt",
|
| 315 |
+
device,
|
| 316 |
+
args.rebuild_cache,
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
specs = []
|
| 320 |
+
if args.scope in ("all", "components"):
|
| 321 |
+
specs.extend(COMPONENT_EXPERIMENTS)
|
| 322 |
+
if args.scope in ("all", "sensitivity"):
|
| 323 |
+
specs.extend(SENSITIVITY_EXPERIMENTS)
|
| 324 |
+
if args.only:
|
| 325 |
+
requested = set(args.only)
|
| 326 |
+
known = {spec["name"] for spec in specs}
|
| 327 |
+
unknown = requested - known
|
| 328 |
+
if unknown:
|
| 329 |
+
raise ValueError(f"Unknown experiments: {sorted(unknown)}")
|
| 330 |
+
specs = [spec for spec in specs if spec["name"] in requested]
|
| 331 |
+
rerun = set(args.rerun)
|
| 332 |
+
unknown_reruns = rerun - {spec["name"] for spec in specs}
|
| 333 |
+
if unknown_reruns:
|
| 334 |
+
raise ValueError(f"Unknown rerun experiments: {sorted(unknown_reruns)}")
|
| 335 |
+
|
| 336 |
+
output = root / args.output
|
| 337 |
+
previous = {}
|
| 338 |
+
previous_metadata = {}
|
| 339 |
+
if output.exists():
|
| 340 |
+
with output.open("r", encoding="utf-8") as handle:
|
| 341 |
+
payload = json.load(handle)
|
| 342 |
+
previous = {
|
| 343 |
+
item["name"]: item for item in payload["experiments"]
|
| 344 |
+
}
|
| 345 |
+
previous_metadata = payload.get("metadata", {})
|
| 346 |
+
completed = dict(previous)
|
| 347 |
+
metadata = {
|
| 348 |
+
"dataset": config["dataset"],
|
| 349 |
+
"seed": config["seed"],
|
| 350 |
+
"train_images": sum(len(x["labels"]) for x in normalized_cache["train"]),
|
| 351 |
+
"test_images": sum(len(x["labels"]) for x in normalized_cache["test"]),
|
| 352 |
+
"normalized_feature_seconds": normalized_cache["feature_seconds"],
|
| 353 |
+
"no_normalization_feature_seconds": (
|
| 354 |
+
no_norm_cache["feature_seconds"]
|
| 355 |
+
if no_norm_cache
|
| 356 |
+
else previous_metadata.get("no_normalization_feature_seconds")
|
| 357 |
+
),
|
| 358 |
+
}
|
| 359 |
+
for spec in specs:
|
| 360 |
+
if spec["name"] in previous and spec["name"] not in rerun:
|
| 361 |
+
print(f"Skipping completed experiment: {spec['name']}")
|
| 362 |
+
continue
|
| 363 |
+
cache = (
|
| 364 |
+
normalized_cache
|
| 365 |
+
if spec.get("normalization", True)
|
| 366 |
+
else no_norm_cache
|
| 367 |
+
)
|
| 368 |
+
print(f"Running experiment: {spec['name']}")
|
| 369 |
+
completed[spec["name"]] = run_experiment(config, spec, cache, device)
|
| 370 |
+
save_results(output, metadata, list(completed.values()))
|
| 371 |
+
|
| 372 |
+
complete_results = list(completed.values())
|
| 373 |
+
if set(completed) == EXPECTED_EXPERIMENTS:
|
| 374 |
+
validate_complete_results(complete_results)
|
| 375 |
+
save_results(output, metadata, complete_results)
|
| 376 |
+
print(f"Saved {len(completed)} experiments to {output}")
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
if __name__ == "__main__":
|
| 380 |
+
main()
|
reproduce/ider/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IDER: Idempotent Experience Replay for Reliable Continual Learning [(ICLR 2026)](https://arxiv.org/abs/2603.00624)
|
| 2 |
+
|
| 3 |
+
## Abstract
|
| 4 |
+
|
| 5 |
+
Catastrophic forgetting is a central challenge in continual learning: when a neural network learns new tasks, it often loses performance on previously learned tasks. The IDER paper further points out that continual learning models should not only maintain accuracy, but also produce reliable confidence estimates, especially in mission-critical scenarios.
|
| 6 |
+
|
| 7 |
+
IDER proposes **Idempotent Experience Replay**, a replay-based continual learning method built on the idempotent property. The method encourages repeated prediction with the model's own output signal to remain stable, and introduces an idempotence distillation objective between the current model and the old checkpoint. As a result, IDER can be integrated with mainstream replay methods and improves prediction reliability while also improving accuracy and reducing forgetting.
|
| 8 |
+
|
| 9 |
+
In this LibContinual reproduction, ordinary Experience Replay is reported as `baseline`, and IDER corresponds to the paper's `ER+ID`.
|
| 10 |
+
|
| 11 |
+

|
| 12 |
+
|
| 13 |
+
## Citation
|
| 14 |
+
|
| 15 |
+
```bibtex
|
| 16 |
+
@article{liu2026ider,
|
| 17 |
+
title={IDER: IDempotent Experience Replay for Reliable Continual Learning},
|
| 18 |
+
author={Liu, Zhanwang and Li, Yuting and Gao, Haoyuan and Li, Yexin and Kong, Linghe and Sun, Lichao and Huang, Weiran},
|
| 19 |
+
journal={arXiv preprint arXiv:2603.00624},
|
| 20 |
+
year={2026}
|
| 21 |
+
}
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## How to Reproduce
|
| 25 |
+
|
| 26 |
+
The IDER and ER baseline configs are in [config/zz_IDER](../../config/zz_IDER/). Run an experiment by passing the corresponding config name to `run_trainer.py`:
|
| 27 |
+
|
| 28 |
+
```shell
|
| 29 |
+
python run_trainer.py --config ider_cifar100_buf500 --device 0 --seed 0
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Choose another IDER or ER config from `config/zz_IDER/` as needed. To reproduce multi-seed results, repeat the command with seeds `0`, `1`, `2`, `3`, and `4`.
|
| 33 |
+
|
| 34 |
+
## Results
|
| 35 |
+
|
| 36 |
+
The following table compares our LibContinual reproduction with the original paper. `FAA/CIL` is the final class-incremental average accuracy, and `FF` is final forgetting. Higher FAA/CIL is better, lower FF is better.
|
| 37 |
+
|
| 38 |
+
| Dataset | Buffer | Method | Paper FAA/CIL | Ours FAA/CIL | Paper FF | Ours FF |
|
| 39 |
+
|---|---:|---|---:|---:|---:|---:|
|
| 40 |
+
| CIFAR-10 | 200 | ER / baseline | 44.46 +/- 2.87 | 48.55 +/- 0.78 | 71.35 +/- 7.77 | 59.84 +/- 1.11 |
|
| 41 |
+
| CIFAR-10 | 200 | ER+ID / IDER | 71.02 +/- 1.98 | 70.57 +/- 0.60 | 15.28 +/- 2.41 | 17.06 +/- 1.73 |
|
| 42 |
+
| CIFAR-10 | 500 | ER / baseline | 58.84 +/- 3.85 | 61.82 +/- 1.60 | 52.12 +/- 7.56 | 42.84 +/- 2.02 |
|
| 43 |
+
| CIFAR-10 | 500 | ER+ID / IDER | 74.74 +/- 0.42 | 75.14 +/- 1.06 | 11.93 +/- 0.49 | 12.28 +/- 0.90 |
|
| 44 |
+
| CIFAR-100 | 500 | ER / baseline | 23.41 +/- 1.15 | 20.37 +/- 0.58 | 71.92 +/- 0.74 | 74.23 +/- 0.66 |
|
| 45 |
+
| CIFAR-100 | 500 | ER+ID / IDER | 44.82 +/- 0.85 | 44.09 +/- 0.80 | 29.98 +/- 2.52 | 34.86 +/- 1.34 |
|
| 46 |
+
| CIFAR-100 | 2000 | ER / baseline | 40.47 +/- 0.95 | 36.53 +/- 0.37 | 51.82 +/- 0.75 | 55.33 +/- 0.47 |
|
| 47 |
+
| CIFAR-100 | 2000 | ER+ID / IDER | 56.59 +/- 0.35 | 55.99 +/- 0.39 | 17.46 +/- 1.04 | 20.03 +/- 0.54 |
|
| 48 |
+
| TinyImageNet | 500 | ER / baseline | 10.13 +/- 0.39 | 9.252 +/- 0.130 | 74.79 +/- 0.67 | 71.567 +/- 0.140 |
|
| 49 |
+
| TinyImageNet | 500 | ER+ID / IDER | 29.88 +/- 1.15 | 30.734 +/- 1.756 | 36.63 +/- 3.37 | 36.682 +/- 4.177 |
|
| 50 |
+
|
| 51 |
+
For CIFAR experiments, the paper also reports Expected Calibration Error (ECE). Lower ECE is better.
|
| 52 |
+
|
| 53 |
+
| Dataset | Buffer | Method | Paper ECE | Ours ECE |
|
| 54 |
+
|---|---:|---|---:|---:|
|
| 55 |
+
| CIFAR-10 | 200 | ER / baseline | 45.53 | 45.57 +/- 0.78 |
|
| 56 |
+
| CIFAR-10 | 200 | ER+ID / IDER | 12.36 | 12.96 +/- 0.82 |
|
| 57 |
+
| CIFAR-10 | 500 | ER / baseline | 32.69 | 32.61 +/- 1.57 |
|
| 58 |
+
| CIFAR-10 | 500 | ER+ID / IDER | 11.73 | 12.33 +/- 0.84 |
|
| 59 |
+
| CIFAR-100 | 500 | ER / baseline | 64.59 | 65.53 +/- 0.58 |
|
| 60 |
+
| CIFAR-100 | 500 | ER+ID / IDER | 13.65 | 10.94 +/- 1.03 |
|
| 61 |
+
| CIFAR-100 | 2000 | ER / baseline | 45.64 | 48.58 +/- 0.30 |
|
| 62 |
+
| CIFAR-100 | 2000 | ER+ID / IDER | 12.87 | 10.61 +/- 0.63 |
|
| 63 |
+
|
| 64 |
+
Overall, the reproduced IDER results are close to the original paper across CIFAR-10, CIFAR-100, and TinyImageNet. The reproduced experiments preserve the main conclusions of the paper: IDER improves final class-incremental accuracy, reduces forgetting, and substantially improves calibration on CIFAR settings.
|