boringKey commited on
Commit
3cdd985
·
verified ·
1 Parent(s): 5fee096

Sync from GitHub

Browse files
Files changed (50) hide show
  1. .github/workflows/sync-hf.yml +30 -0
  2. .gitignore +17 -0
  3. README.md +4 -1
  4. config/fly_cl_cifar100.yaml +93 -0
  5. config/fly_cl_cub200.yaml +101 -0
  6. config/zz_EWCDR/ewcdr-resnet18-cifar100-b10-10-10.yaml +86 -0
  7. config/zz_EWCDR/ewcdr-resnet18-cifar100-b20-20-5.yaml +86 -0
  8. config/zz_EWCDR/ewcdr-resnet18-cifar100-b40-3-21.yaml +86 -0
  9. config/zz_EWCDR/ewcdr-resnet18-cifar100-b5-5-20.yaml +86 -0
  10. config/zz_EWCDR/ewcdr-resnet18-cifar100-b50-10-6.yaml +86 -0
  11. config/zz_EWCDR/ewcdr-resnet18-cifar100-b50-5-11.yaml +86 -0
  12. config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b10-10-20.yaml +27 -0
  13. config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-10-11.yaml +27 -0
  14. config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-20-6.yaml +27 -0
  15. config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b100-5-21.yaml +27 -0
  16. config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b20-20-10.yaml +27 -0
  17. config/zz_EWCDR/ewcdr-resnet18-tinyimagenet-b40-40-5.yaml +27 -0
  18. config/zz_EWCDR/tinyimagenet_common.yaml +42 -0
  19. config/zz_IDER/er_cifar100_buf2000.yaml +76 -0
  20. config/zz_IDER/er_cifar100_buf500.yaml +76 -0
  21. config/zz_IDER/er_cifar10_buf200.yaml +74 -0
  22. config/zz_IDER/er_cifar10_buf500.yaml +74 -0
  23. config/zz_IDER/er_tinyimagenet_buf4000.yaml +76 -0
  24. config/zz_IDER/er_tinyimagenet_buf500.yaml +76 -0
  25. config/zz_IDER/ider_cifar100_buf2000.yaml +91 -0
  26. config/zz_IDER/ider_cifar100_buf500.yaml +91 -0
  27. config/zz_IDER/ider_cifar100_buf500_til.yaml +91 -0
  28. config/zz_IDER/ider_cifar10_buf200.yaml +89 -0
  29. config/zz_IDER/ider_cifar10_buf500.yaml +89 -0
  30. config/zz_IDER/ider_tinyimagenet_buf4000.yaml +91 -0
  31. config/zz_IDER/ider_tinyimagenet_buf500.yaml +91 -0
  32. config/zz_IDER/ider_tinyimagenet_buf500_paper.yaml +91 -0
  33. core/data/dataloader.py +12 -1
  34. core/data/dataset.py +89 -19
  35. core/model/__init__.py +5 -1
  36. core/model/backbone/__init__.py +2 -0
  37. core/model/backbone/resnet.py +6 -0
  38. core/model/backbone/resnet_id.py +165 -0
  39. core/model/backbone/timm_backbone.py +22 -0
  40. core/model/ewcdr.py +158 -0
  41. core/model/finetune.py +5 -0
  42. core/model/fly_cl.py +242 -0
  43. core/model/ider.py +328 -0
  44. core/model/paper_er.py +170 -0
  45. core/trainer.py +65 -21
  46. core/utils/utils.py +45 -5
  47. reproduce/ewcdr/README.md +160 -0
  48. reproduce/fly_cl/run_cifar100_ablations.py +135 -0
  49. reproduce/fly_cl/run_cub200_ablations.py +380 -0
  50. 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
+ [![arXiv](https://img.shields.io/badge/arXiv-2512.22029-b31b1b.svg)](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 != 'binary_cifar100':
 
 
 
 
 
 
 
 
 
 
 
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 == 'binary_cifar100':
29
- datasets.CIFAR100(self.data_root, download = True)
 
 
 
 
 
 
 
 
 
 
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 = True,
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 = True,
88
  batch_size = self.batchsize,
89
  drop_last = False,
90
  num_workers = self.num_workers,
91
- pin_memory=False
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 == 'binary_cifar100':
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 == 'binary_cifar100':
276
-
277
- with open(os.path.join(self.data_root, 'cifar-100-python', self.mode), 'rb') as f:
278
- load_data = pickle.load(f, encoding='latin1')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
- for data, label in zip(load_data['data'], load_data['fine_labels']):
281
 
282
- if label in range(self.start_idx, self.end_idx):
 
 
 
 
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(label)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 config['lr_scheduler']['name'] == "CosineSchedule":
170
- scheduler = CosineSchedule(optimizer, K=config['lr_scheduler']['kwargs']['K'])
171
- elif config['lr_scheduler']['name'] == "PatienceSchedule":
172
- scheduler = PatienceSchedule(optimizer, patience = config['lr_scheduler']['kwargs']['patience'], factor = config['lr_scheduler']['kwargs']['factor'])
173
- elif config['lr_scheduler']['name'] == "Constant":
174
  scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda e: 1)
175
- elif config['lr_scheduler']['name'] == "CosineAnnealingWarmUp":
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, config['lr_scheduler']['kwargs']['warmup_length'], T_max)
179
  else:
180
- scheduler = get_instance(torch.optim.lr_scheduler, "lr_scheduler", config, optimizer=optimizer)
 
 
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
- if self.config['setting'] == 'task-aware':
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
- output, acc = model.inference(batch, task_id=None)
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
- for col in self._data.columns:
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
+ ![IDER](../../resources/imgs/ider_method.jpg)
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.