diff --git a/configs/Base-AGW.yml b/configs/Base-AGW.yml
index 7d5dcf56d..ded88a762 100644
--- a/configs/Base-AGW.yml
+++ b/configs/Base-AGW.yml
@@ -1,11 +1,11 @@
-_BASE_: "Base-bagtricks.yml"
+_BASE_: Base-bagtricks.yml
MODEL:
BACKBONE:
WITH_NL: True
HEADS:
- POOL_LAYER: "gempool"
+ POOL_LAYER: gempool
LOSSES:
NAME: ("CrossEntropyLoss", "TripletLoss")
diff --git a/configs/Base-MGN.yml b/configs/Base-MGN.yml
index ef9cbe8e0..c6bc93c3c 100644
--- a/configs/Base-MGN.yml
+++ b/configs/Base-MGN.yml
@@ -1,25 +1,12 @@
-_BASE_: "Base-SBS.yml"
+_BASE_: Base-SBS.yml
MODEL:
- META_ARCHITECTURE: 'MGN'
+ META_ARCHITECTURE: MGN
- FREEZE_LAYERS: ["backbone", "b1", "b2", "b3",]
+ FREEZE_LAYERS: [backbone, b1, b2, b3,]
BACKBONE:
WITH_NL: False
HEADS:
EMBEDDING_DIM: 256
-
- LOSSES:
- NAME: ("CrossEntropyLoss", "TripletLoss",)
- CE:
- EPSILON: 0.1
- SCALE: 1.0
-
- TRI:
- MARGIN: 0.0
- HARD_MINING: True
- NORM_FEAT: False
- SCALE: 1.0
-
diff --git a/configs/Base-SBS.yml b/configs/Base-SBS.yml
index c112c5b3b..04d1f9b58 100644
--- a/configs/Base-SBS.yml
+++ b/configs/Base-SBS.yml
@@ -1,15 +1,15 @@
-_BASE_: "Base-bagtricks.yml"
+_BASE_: Base-bagtricks.yml
MODEL:
- FREEZE_LAYERS: ["backbone"]
+ FREEZE_LAYERS: [ backbone ]
BACKBONE:
WITH_NL: True
HEADS:
- NECK_FEAT: "after"
- POOL_LAYER: "gempoolP"
- CLS_LAYER: "circleSoftmax"
+ NECK_FEAT: after
+ POOL_LAYER: gempoolP
+ CLS_LAYER: circleSoftmax
SCALE: 64
MARGIN: 0.35
@@ -26,8 +26,8 @@ MODEL:
SCALE: 1.0
INPUT:
- SIZE_TRAIN: [384, 128]
- SIZE_TEST: [384, 128]
+ SIZE_TRAIN: [ 384, 128 ]
+ SIZE_TEST: [ 384, 128 ]
DO_AUTOAUG: True
AUTOAUG_PROB: 0.1
@@ -36,7 +36,8 @@ DATALOADER:
NUM_INSTANCE: 16
SOLVER:
- OPT: "Adam"
+ FP16_ENABLED: False
+ OPT: Adam
MAX_EPOCH: 60
BASE_LR: 0.00035
BIAS_LR_FACTOR: 1.
@@ -44,19 +45,19 @@ SOLVER:
WEIGHT_DECAY_BIAS: 0.0005
IMS_PER_BATCH: 64
- SCHED: "CosineAnnealingLR"
+ SCHED: CosineAnnealingLR
DELAY_EPOCHS: 30
- ETA_MIN_LR: 0.00000077
+ ETA_MIN_LR: 0.0000007
WARMUP_FACTOR: 0.1
- WARMUP_ITERS: 2000
+ WARMUP_EPOCHS: 10
- FREEZE_ITERS: 2000
+ FREEZE_ITERS: 1000
CHECKPOINT_PERIOD: 20
TEST:
- EVAL_PERIOD: 20
+ EVAL_PERIOD: 10
IMS_PER_BATCH: 128
CUDNN_BENCHMARK: True
diff --git a/configs/Base-bagtricks.yml b/configs/Base-bagtricks.yml
index 39414de9e..a3da8468b 100644
--- a/configs/Base-bagtricks.yml
+++ b/configs/Base-bagtricks.yml
@@ -1,23 +1,22 @@
MODEL:
- META_ARCHITECTURE: "Baseline"
+ META_ARCHITECTURE: Baseline
BACKBONE:
- NAME: "build_resnet_backbone"
- NORM: "BN"
- DEPTH: "50x"
+ NAME: build_resnet_backbone
+ NORM: BN
+ DEPTH: 50x
LAST_STRIDE: 1
FEAT_DIM: 2048
WITH_IBN: False
PRETRAIN: True
- PRETRAIN_PATH: "/export/home/lxy/.cache/torch/checkpoints/resnet50-19c8e357.pth"
HEADS:
- NAME: "EmbeddingHead"
- NORM: "BN"
+ NAME: EmbeddingHead
+ NORM: BN
WITH_BNNECK: True
- POOL_LAYER: "avgpool"
- NECK_FEAT: "before"
- CLS_LAYER: "linear"
+ POOL_LAYER: avgpool
+ NECK_FEAT: before
+ CLS_LAYER: linear
LOSSES:
NAME: ("CrossEntropyLoss", "TripletLoss",)
@@ -33,8 +32,8 @@ MODEL:
SCALE: 1.
INPUT:
- SIZE_TRAIN: [256, 128]
- SIZE_TEST: [256, 128]
+ SIZE_TRAIN: [ 256, 128 ]
+ SIZE_TEST: [ 256, 128 ]
REA:
ENABLED: True
PROB: 0.5
@@ -48,7 +47,7 @@ DATALOADER:
SOLVER:
FP16_ENABLED: True
- OPT: "Adam"
+ OPT: Adam
MAX_EPOCH: 120
BASE_LR: 0.00035
BIAS_LR_FACTOR: 2.
@@ -56,12 +55,12 @@ SOLVER:
WEIGHT_DECAY_BIAS: 0.0005
IMS_PER_BATCH: 64
- SCHED: "MultiStepLR"
- STEPS: [40, 90]
+ SCHED: MultiStepLR
+ STEPS: [ 40, 90 ]
GAMMA: 0.1
WARMUP_FACTOR: 0.1
- WARMUP_ITERS: 2000
+ WARMUP_EPOCHS: 10
CHECKPOINT_PERIOD: 30
diff --git a/configs/DukeMTMC/AGW_R101-ibn.yml b/configs/DukeMTMC/AGW_R101-ibn.yml
index 592ba0fe8..6b8e58b2a 100644
--- a/configs/DukeMTMC/AGW_R101-ibn.yml
+++ b/configs/DukeMTMC/AGW_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/agw_R101-ibn"
+OUTPUT_DIR: logs/dukemtmc/agw_R101-ibn
diff --git a/configs/DukeMTMC/AGW_R50-ibn.yml b/configs/DukeMTMC/AGW_R50-ibn.yml
index 648660fbf..f6e4c6bac 100644
--- a/configs/DukeMTMC/AGW_R50-ibn.yml
+++ b/configs/DukeMTMC/AGW_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/agw_R50-ibn"
+OUTPUT_DIR: logs/dukemtmc/agw_R50-ibn
diff --git a/configs/DukeMTMC/AGW_R50.yml b/configs/DukeMTMC/AGW_R50.yml
index c2cceb8d7..240de2641 100644
--- a/configs/DukeMTMC/AGW_R50.yml
+++ b/configs/DukeMTMC/AGW_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/agw_R50"
+OUTPUT_DIR: logs/dukemtmc/agw_R50
diff --git a/configs/DukeMTMC/AGW_S50.yml b/configs/DukeMTMC/AGW_S50.yml
index f166d6e8a..4f307f6f7 100644
--- a/configs/DukeMTMC/AGW_S50.yml
+++ b/configs/DukeMTMC/AGW_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/agw_S50"
+OUTPUT_DIR: logs/dukemtmc/agw_S50
diff --git a/configs/DukeMTMC/bagtricks_R101-ibn.yml b/configs/DukeMTMC/bagtricks_R101-ibn.yml
index 0865131aa..b5b21edea 100644
--- a/configs/DukeMTMC/bagtricks_R101-ibn.yml
+++ b/configs/DukeMTMC/bagtricks_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/bagtricks_R101-ibn"
+OUTPUT_DIR: logs/dukemtmc/bagtricks_R101-ibn
diff --git a/configs/DukeMTMC/bagtricks_R50-ibn.yml b/configs/DukeMTMC/bagtricks_R50-ibn.yml
index cb469295a..e3bbbb4cd 100644
--- a/configs/DukeMTMC/bagtricks_R50-ibn.yml
+++ b/configs/DukeMTMC/bagtricks_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/bagtricks_R50-ibn"
+OUTPUT_DIR: logs/dukemtmc/bagtricks_R50-ibn
diff --git a/configs/DukeMTMC/bagtricks_R50.yml b/configs/DukeMTMC/bagtricks_R50.yml
index c564e99e3..3be4fe8f4 100644
--- a/configs/DukeMTMC/bagtricks_R50.yml
+++ b/configs/DukeMTMC/bagtricks_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/bagtricks_R50"
+OUTPUT_DIR: logs/dukemtmc/bagtricks_R50
diff --git a/configs/DukeMTMC/bagtricks_S50.yml b/configs/DukeMTMC/bagtricks_S50.yml
index 03735e8fd..846eaf214 100644
--- a/configs/DukeMTMC/bagtricks_S50.yml
+++ b/configs/DukeMTMC/bagtricks_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/bagtricks_S50"
+OUTPUT_DIR: logs/dukemtmc/bagtricks_S50
diff --git a/configs/DukeMTMC/mgn_R50-ibn.yml b/configs/DukeMTMC/mgn_R50-ibn.yml
index ab6bf7cdf..45bf1c343 100644
--- a/configs/DukeMTMC/mgn_R50-ibn.yml
+++ b/configs/DukeMTMC/mgn_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-MGN.yml"
+_BASE_: ../Base-MGN.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/mgn_R50-ibn"
+OUTPUT_DIR: logs/dukemtmc/mgn_R50-ibn
diff --git a/configs/DukeMTMC/sbs_R101-ibn.yml b/configs/DukeMTMC/sbs_R101-ibn.yml
index 01f698460..f6b011f22 100644
--- a/configs/DukeMTMC/sbs_R101-ibn.yml
+++ b/configs/DukeMTMC/sbs_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/sbs_R101-ibn"
+OUTPUT_DIR: logs/dukemtmc/sbs_R101-ibn
diff --git a/configs/DukeMTMC/sbs_R50-ibn.yml b/configs/DukeMTMC/sbs_R50-ibn.yml
index c7aaa2ed4..d3ee4142a 100644
--- a/configs/DukeMTMC/sbs_R50-ibn.yml
+++ b/configs/DukeMTMC/sbs_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/sbs_R50-ibn"
+OUTPUT_DIR: logs/dukemtmc/sbs_R50-ibn
diff --git a/configs/DukeMTMC/sbs_R50.yml b/configs/DukeMTMC/sbs_R50.yml
index fb9e98304..e03193d98 100644
--- a/configs/DukeMTMC/sbs_R50.yml
+++ b/configs/DukeMTMC/sbs_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/sbs_R50"
+OUTPUT_DIR: logs/dukemtmc/sbs_R50
diff --git a/configs/DukeMTMC/sbs_S50.yml b/configs/DukeMTMC/sbs_S50.yml
index 82940cc7f..59a022d77 100644
--- a/configs/DukeMTMC/sbs_S50.yml
+++ b/configs/DukeMTMC/sbs_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("DukeMTMC",)
TESTS: ("DukeMTMC",)
-OUTPUT_DIR: "logs/dukemtmc/sbs_S50"
+OUTPUT_DIR: logs/dukemtmc/sbs_S50
diff --git a/configs/MSMT17/AGW_R101-ibn.yml b/configs/MSMT17/AGW_R101-ibn.yml
index a122f6ca2..0d9dc7062 100644
--- a/configs/MSMT17/AGW_R101-ibn.yml
+++ b/configs/MSMT17/AGW_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/agw_R101-ibn"
+OUTPUT_DIR: logs/msmt17/agw_R101-ibn
diff --git a/configs/MSMT17/AGW_R50-ibn.yml b/configs/MSMT17/AGW_R50-ibn.yml
index 6104ed674..403114f9f 100644
--- a/configs/MSMT17/AGW_R50-ibn.yml
+++ b/configs/MSMT17/AGW_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/agw_R50-ibn"
+OUTPUT_DIR: logs/msmt17/agw_R50-ibn
diff --git a/configs/MSMT17/AGW_R50.yml b/configs/MSMT17/AGW_R50.yml
index a43e32f06..9b8202369 100644
--- a/configs/MSMT17/AGW_R50.yml
+++ b/configs/MSMT17/AGW_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/agw_R50"
+OUTPUT_DIR: logs/msmt17/agw_R50
diff --git a/configs/MSMT17/AGW_S50.yml b/configs/MSMT17/AGW_S50.yml
index 8ec8ccbe8..8310b5c2e 100644
--- a/configs/MSMT17/AGW_S50.yml
+++ b/configs/MSMT17/AGW_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/agw_S50"
+OUTPUT_DIR: logs/msmt17/agw_S50
diff --git a/configs/MSMT17/bagtricks_R101-ibn.yml b/configs/MSMT17/bagtricks_R101-ibn.yml
index 693269f05..d1fea74b4 100644
--- a/configs/MSMT17/bagtricks_R101-ibn.yml
+++ b/configs/MSMT17/bagtricks_R101-ibn.yml
@@ -1,13 +1,13 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/bagtricks_R101-ibn"
+OUTPUT_DIR: logs/msmt17/bagtricks_R101-ibn
diff --git a/configs/MSMT17/bagtricks_R50-ibn.yml b/configs/MSMT17/bagtricks_R50-ibn.yml
index fac921e92..e3ae254ec 100644
--- a/configs/MSMT17/bagtricks_R50-ibn.yml
+++ b/configs/MSMT17/bagtricks_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
@@ -8,5 +8,5 @@ DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/bagtricks_R50-ibn"
+OUTPUT_DIR: logs/msmt17/bagtricks_R50-ibn
diff --git a/configs/MSMT17/bagtricks_R50.yml b/configs/MSMT17/bagtricks_R50.yml
index 313e93e28..8c4649d1b 100644
--- a/configs/MSMT17/bagtricks_R50.yml
+++ b/configs/MSMT17/bagtricks_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/bagtricks_R50"
+OUTPUT_DIR: logs/msmt17/bagtricks_R50
diff --git a/configs/MSMT17/bagtricks_S50.yml b/configs/MSMT17/bagtricks_S50.yml
index b855bfd04..093d8b0c4 100644
--- a/configs/MSMT17/bagtricks_S50.yml
+++ b/configs/MSMT17/bagtricks_S50.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/bagtricks_S50"
+OUTPUT_DIR: logs/msmt17/bagtricks_S50
diff --git a/configs/MSMT17/mgn_R50-ibn.yml b/configs/MSMT17/mgn_R50-ibn.yml
index 07f18ddf6..f81c8748c 100644
--- a/configs/MSMT17/mgn_R50-ibn.yml
+++ b/configs/MSMT17/mgn_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-MGN.yml"
+_BASE_: ../Base-MGN.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/mgn_R50-ibn"
+OUTPUT_DIR: logs/msmt17/mgn_R50-ibn
diff --git a/configs/MSMT17/sbs_R101-ibn.yml b/configs/MSMT17/sbs_R101-ibn.yml
index cd430dde2..2b2cbcacf 100644
--- a/configs/MSMT17/sbs_R101-ibn.yml
+++ b/configs/MSMT17/sbs_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/sbs_R101-ibn"
+OUTPUT_DIR: logs/msmt17/sbs_R101-ibn
diff --git a/configs/MSMT17/sbs_R50-ibn.yml b/configs/MSMT17/sbs_R50-ibn.yml
index 41eeb0efa..93b4c23f0 100644
--- a/configs/MSMT17/sbs_R50-ibn.yml
+++ b/configs/MSMT17/sbs_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/sbs_R50-ibn"
+OUTPUT_DIR: logs/msmt17/sbs_R50-ibn
diff --git a/configs/MSMT17/sbs_R50.yml b/configs/MSMT17/sbs_R50.yml
index 7177c101a..553c5e1fb 100644
--- a/configs/MSMT17/sbs_R50.yml
+++ b/configs/MSMT17/sbs_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/sbs_R50"
+OUTPUT_DIR: logs/msmt17/sbs_R50
diff --git a/configs/MSMT17/sbs_S50.yml b/configs/MSMT17/sbs_S50.yml
index ee18958e7..2afebdcd9 100644
--- a/configs/MSMT17/sbs_S50.yml
+++ b/configs/MSMT17/sbs_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("MSMT17",)
TESTS: ("MSMT17",)
-OUTPUT_DIR: "logs/msmt17/sbs_S50"
+OUTPUT_DIR: logs/msmt17/sbs_S50
diff --git a/configs/Market1501/AGW_R101-ibn.yml b/configs/Market1501/AGW_R101-ibn.yml
index 94d3d85b2..22c048e54 100644
--- a/configs/Market1501/AGW_R101-ibn.yml
+++ b/configs/Market1501/AGW_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/agw_R101-ibn"
+OUTPUT_DIR: logs/market1501/agw_R101-ibn
diff --git a/configs/Market1501/AGW_R50-ibn.yml b/configs/Market1501/AGW_R50-ibn.yml
index 4ec8154e3..60711b38d 100644
--- a/configs/Market1501/AGW_R50-ibn.yml
+++ b/configs/Market1501/AGW_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/agw_R50-ibn"
+OUTPUT_DIR: logs/market1501/agw_R50-ibn
diff --git a/configs/Market1501/AGW_R50.yml b/configs/Market1501/AGW_R50.yml
index 99af75769..236803dd5 100644
--- a/configs/Market1501/AGW_R50.yml
+++ b/configs/Market1501/AGW_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/agw_R50"
+OUTPUT_DIR: logs/market1501/agw_R50
diff --git a/configs/Market1501/AGW_S50.yml b/configs/Market1501/AGW_S50.yml
index ff870bdcf..a8c37394c 100644
--- a/configs/Market1501/AGW_S50.yml
+++ b/configs/Market1501/AGW_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-AGW.yml"
+_BASE_: ../Base-AGW.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/agw_S50"
+OUTPUT_DIR: logs/market1501/agw_S50
diff --git a/configs/Market1501/bagtricks_R101-ibn.yml b/configs/Market1501/bagtricks_R101-ibn.yml
index de11e3f0d..d18bd6c16 100644
--- a/configs/Market1501/bagtricks_R101-ibn.yml
+++ b/configs/Market1501/bagtricks_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/bagtricks_R101-ibn"
+OUTPUT_DIR: logs/market1501/bagtricks_R101-ibn
diff --git a/configs/Market1501/bagtricks_R50-ibn.yml b/configs/Market1501/bagtricks_R50-ibn.yml
index 0a5a032b2..6358655c6 100644
--- a/configs/Market1501/bagtricks_R50-ibn.yml
+++ b/configs/Market1501/bagtricks_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/bagtricks_R50-ibn"
+OUTPUT_DIR: logs/market1501/bagtricks_R50-ibn
diff --git a/configs/Market1501/bagtricks_R50.yml b/configs/Market1501/bagtricks_R50.yml
index d814d8c5e..4c822ddbf 100644
--- a/configs/Market1501/bagtricks_R50.yml
+++ b/configs/Market1501/bagtricks_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/bagtricks_R50"
+OUTPUT_DIR: logs/market1501/bagtricks_R50
diff --git a/configs/Market1501/bagtricks_S50.yml b/configs/Market1501/bagtricks_S50.yml
index 69f51c7a7..c9598a4ac 100644
--- a/configs/Market1501/bagtricks_S50.yml
+++ b/configs/Market1501/bagtricks_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/bagtricks_S50"
+OUTPUT_DIR: logs/market1501/bagtricks_S50
diff --git a/configs/Market1501/mgn_R50-ibn.yml b/configs/Market1501/mgn_R50-ibn.yml
index 2444c4447..99a2751ce 100644
--- a/configs/Market1501/mgn_R50-ibn.yml
+++ b/configs/Market1501/mgn_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-MGN.yml"
+_BASE_: ../Base-MGN.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/mgn_R50-ibn"
+OUTPUT_DIR: logs/market1501/mgn_R50-ibn
diff --git a/configs/Market1501/sbs_R101-ibn.yml b/configs/Market1501/sbs_R101-ibn.yml
index 64d1bb63a..7f2b8f015 100644
--- a/configs/Market1501/sbs_R101-ibn.yml
+++ b/configs/Market1501/sbs_R101-ibn.yml
@@ -1,12 +1,12 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
- DEPTH: "101x"
+ DEPTH: 101x
WITH_IBN: True
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/sbs_R101-ibn"
+OUTPUT_DIR: logs/market1501/sbs_R101-ibn
diff --git a/configs/Market1501/sbs_R50-ibn.yml b/configs/Market1501/sbs_R50-ibn.yml
index 97566d3c5..d803b80ee 100644
--- a/configs/Market1501/sbs_R50-ibn.yml
+++ b/configs/Market1501/sbs_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
@@ -8,4 +8,4 @@ DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/sbs_R50-ibn"
+OUTPUT_DIR: logs/market1501/sbs_R50-ibn
diff --git a/configs/Market1501/sbs_R50.yml b/configs/Market1501/sbs_R50.yml
index 1627117db..727024712 100644
--- a/configs/Market1501/sbs_R50.yml
+++ b/configs/Market1501/sbs_R50.yml
@@ -1,7 +1,7 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/sbs_R50"
+OUTPUT_DIR: logs/market1501/sbs_R50
diff --git a/configs/Market1501/sbs_S50.yml b/configs/Market1501/sbs_S50.yml
index 556b53fd4..aa9fdf801 100644
--- a/configs/Market1501/sbs_S50.yml
+++ b/configs/Market1501/sbs_S50.yml
@@ -1,11 +1,11 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
MODEL:
BACKBONE:
- NAME: "build_resnest_backbone"
+ NAME: build_resnest_backbone
DATASETS:
NAMES: ("Market1501",)
TESTS: ("Market1501",)
-OUTPUT_DIR: "logs/market1501/sbs_S50"
+OUTPUT_DIR: logs/market1501/sbs_S50
diff --git a/configs/VERIWild/bagtricks_R50-ibn.yml b/configs/VERIWild/bagtricks_R50-ibn.yml
index 20fc87782..77a52c2df 100644
--- a/configs/VERIWild/bagtricks_R50-ibn.yml
+++ b/configs/VERIWild/bagtricks_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
INPUT:
SIZE_TRAIN: [256, 256]
@@ -22,7 +22,7 @@ SOLVER:
IMS_PER_BATCH: 128
MAX_ITER: 60
STEPS: [30, 50]
- WARMUP_ITERS: 10
+ WARMUP_EPOCHS: 10
CHECKPOINT_PERIOD: 20
@@ -30,4 +30,4 @@ TEST:
EVAL_PERIOD: 20
IMS_PER_BATCH: 128
-OUTPUT_DIR: "logs/veriwild/bagtricks_R50-ibn_4gpu"
+OUTPUT_DIR: logs/veriwild/bagtricks_R50-ibn_4gpu
diff --git a/configs/VeRi/sbs_R50-ibn.yml b/configs/VeRi/sbs_R50-ibn.yml
index 8c2c1adc7..724d46069 100644
--- a/configs/VeRi/sbs_R50-ibn.yml
+++ b/configs/VeRi/sbs_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-SBS.yml"
+_BASE_: ../Base-SBS.yml
INPUT:
SIZE_TRAIN: [256, 256]
@@ -9,14 +9,14 @@ MODEL:
WITH_IBN: True
SOLVER:
- OPT: "SGD"
+ OPT: SGD
BASE_LR: 0.01
ETA_MIN_LR: 7.7e-5
IMS_PER_BATCH: 64
MAX_ITER: 60
DELAY_ITERS: 30
- WARMUP_ITERS: 10
+ WARMUP_EPOCHS: 10
FREEZE_ITERS: 10
CHECKPOINT_PERIOD: 20
@@ -29,4 +29,4 @@ TEST:
EVAL_PERIOD: 20
IMS_PER_BATCH: 128
-OUTPUT_DIR: "logs/veri/sbs_R50-ibn"
+OUTPUT_DIR: logs/veri/sbs_R50-ibn
diff --git a/configs/VehicleID/bagtricks_R50-ibn.yml b/configs/VehicleID/bagtricks_R50-ibn.yml
index 0020c4f5b..9ce845620 100644
--- a/configs/VehicleID/bagtricks_R50-ibn.yml
+++ b/configs/VehicleID/bagtricks_R50-ibn.yml
@@ -1,4 +1,4 @@
-_BASE_: "../Base-bagtricks.yml"
+_BASE_: ../Base-bagtricks.yml
INPUT:
SIZE_TRAIN: [256, 256]
@@ -24,7 +24,7 @@ SOLVER:
IMS_PER_BATCH: 512
MAX_ITER: 60
STEPS: [30, 50]
- WARMUP_ITERS: 10
+ WARMUP_EPOCHS: 10
CHECKPOINT_PERIOD: 20
@@ -32,4 +32,4 @@ TEST:
EVAL_PERIOD: 20
IMS_PER_BATCH: 128
-OUTPUT_DIR: "logs/vehicleid/bagtricks_R50-ibn_4gpu"
+OUTPUT_DIR: logs/vehicleid/bagtricks_R50-ibn_4gpu
diff --git a/fastreid/config/defaults.py b/fastreid/config/defaults.py
index 8a3f9ecc9..3ac825195 100644
--- a/fastreid/config/defaults.py
+++ b/fastreid/config/defaults.py
@@ -25,6 +25,9 @@
_C.MODEL.FREEZE_LAYERS = ['']
+# MoCo memory size
+_C.MODEL.QUEUE_SIZE = 8192
+
# ---------------------------------------------------------------------------- #
# Backbone options
# ---------------------------------------------------------------------------- #
@@ -120,6 +123,13 @@
# Values to be used for image normalization
_C.MODEL.PIXEL_STD = [0.229*255, 0.224*255, 0.225*255]
+# -----------------------------------------------------------------------------
+# KNOWLEDGE DISTILLATION
+# -----------------------------------------------------------------------------
+
+_C.KD = CN()
+_C.KD.MODEL_CONFIG = ""
+_C.KD.MODEL_WEIGHTS = ""
# -----------------------------------------------------------------------------
# INPUT
@@ -148,6 +158,9 @@
_C.INPUT.CJ.SATURATION = 0.1
_C.INPUT.CJ.HUE = 0.1
+# Random Affine
+_C.INPUT.DO_AFFINE = False
+
# Auto augmentation
_C.INPUT.DO_AUTOAUG = False
_C.INPUT.AUTOAUG_PROB = 0.0
@@ -160,7 +173,7 @@
_C.INPUT.REA = CN()
_C.INPUT.REA.ENABLED = False
_C.INPUT.REA.PROB = 0.5
-_C.INPUT.REA.VALUE = [0.596*255, 0.558*255, 0.497*255]
+_C.INPUT.REA.VALUE = [0.485*255, 0.456*255, 0.406*255]
# Random Patch
_C.INPUT.RPT = CN()
_C.INPUT.RPT.ENABLED = False
@@ -207,6 +220,7 @@
_C.SOLVER.HEADS_LR_FACTOR = 1.
_C.SOLVER.MOMENTUM = 0.9
+_C.SOLVER.NESTEROV = True
_C.SOLVER.WEIGHT_DECAY = 0.0005
_C.SOLVER.WEIGHT_DECAY_BIAS = 0.
@@ -224,7 +238,7 @@
# Warmup options
_C.SOLVER.WARMUP_FACTOR = 0.1
-_C.SOLVER.WARMUP_ITERS = 10
+_C.SOLVER.WARMUP_EPOCHS = 10
_C.SOLVER.WARMUP_METHOD = "linear"
# Backbone freeze iters
diff --git a/fastreid/data/build.py b/fastreid/data/build.py
index a4c629615..8d6bdc5bd 100644
--- a/fastreid/data/build.py
+++ b/fastreid/data/build.py
@@ -59,7 +59,7 @@ def build_reid_train_loader(cfg, mapper=None, **kwargs):
return train_loader
-def build_reid_test_loader(cfg, dataset_name, **kwargs):
+def build_reid_test_loader(cfg, dataset_name, mapper=None, **kwargs):
cfg = cfg.clone()
dataset = DATASET_REGISTRY.get(dataset_name)(root=_root, **kwargs)
@@ -67,8 +67,12 @@ def build_reid_test_loader(cfg, dataset_name, **kwargs):
dataset.show_test()
test_items = dataset.query + dataset.gallery
- test_transforms = build_transforms(cfg, is_train=False)
- test_set = CommDataset(test_items, test_transforms, relabel=False)
+ if mapper is not None:
+ transforms = mapper
+ else:
+ transforms = build_transforms(cfg, is_train=False)
+
+ test_set = CommDataset(test_items, transforms, relabel=False)
mini_batch_size = cfg.TEST.IMS_PER_BATCH // comm.get_world_size()
data_sampler = samplers.InferenceSampler(len(test_set))
diff --git a/fastreid/data/transforms/__init__.py b/fastreid/data/transforms/__init__.py
index a4bea9f3e..7131a3b9a 100644
--- a/fastreid/data/transforms/__init__.py
+++ b/fastreid/data/transforms/__init__.py
@@ -4,7 +4,6 @@
@contact: sherlockliao01@gmail.com
"""
-
+from .autoaugment import *
from .build import build_transforms
from .transforms import *
-from .autoaugment import *
diff --git a/fastreid/data/transforms/build.py b/fastreid/data/transforms/build.py
index cc5acf55a..dbc48da65 100644
--- a/fastreid/data/transforms/build.py
+++ b/fastreid/data/transforms/build.py
@@ -41,6 +41,9 @@ def build_transforms(cfg, is_train=True):
cj_saturation = cfg.INPUT.CJ.SATURATION
cj_hue = cfg.INPUT.CJ.HUE
+ # random affine
+ do_affine = cfg.INPUT.DO_AFFINE
+
# random erasing
do_rea = cfg.INPUT.REA.ENABLED
rea_prob = cfg.INPUT.REA.PROB
@@ -60,9 +63,11 @@ def build_transforms(cfg, is_train=True):
res.extend([T.Pad(padding, padding_mode=padding_mode), T.RandomCrop(size_train)])
if do_cj:
res.append(T.RandomApply([T.ColorJitter(cj_brightness, cj_contrast, cj_saturation, cj_hue)], p=cj_prob))
+ if do_affine:
+ res.append(T.RandomAffine(degrees=0, translate=None, scale=[0.9, 1.1], shear=None, resample=False,
+ fillcolor=128))
if do_augmix:
- res.append(T.RandomApply([AugMix()], p=augmix_prob))
-
+ res.append(AugMix(prob=augmix_prob))
res.append(ToTensor())
if do_rea:
res.append(T.RandomErasing(p=rea_prob, value=rea_value))
diff --git a/fastreid/data/transforms/functional.py b/fastreid/data/transforms/functional.py
index 6e96c114a..e0cfa5a17 100644
--- a/fastreid/data/transforms/functional.py
+++ b/fastreid/data/transforms/functional.py
@@ -114,38 +114,38 @@ def solarize(pil_img, level, *args):
return ImageOps.solarize(pil_img, 256 - level)
-def shear_x(pil_img, level, image_size):
+def shear_x(pil_img, level):
level = float_parameter(sample_level(level), 0.3)
if np.random.uniform() > 0.5:
level = -level
- return pil_img.transform(image_size,
+ return pil_img.transform(pil_img.size,
Image.AFFINE, (1, level, 0, 0, 1, 0),
resample=Image.BILINEAR)
-def shear_y(pil_img, level, image_size):
+def shear_y(pil_img, level):
level = float_parameter(sample_level(level), 0.3)
if np.random.uniform() > 0.5:
level = -level
- return pil_img.transform(image_size,
+ return pil_img.transform(pil_img.size,
Image.AFFINE, (1, 0, 0, level, 1, 0),
resample=Image.BILINEAR)
-def translate_x(pil_img, level, image_size):
- level = int_parameter(sample_level(level), image_size[0] / 3)
+def translate_x(pil_img, level):
+ level = int_parameter(sample_level(level), pil_img.size[0] / 3)
if np.random.random() > 0.5:
level = -level
- return pil_img.transform(image_size,
+ return pil_img.transform(pil_img.size,
Image.AFFINE, (1, 0, level, 0, 1, 0),
resample=Image.BILINEAR)
-def translate_y(pil_img, level, image_size):
- level = int_parameter(sample_level(level), image_size[1] / 3)
+def translate_y(pil_img, level):
+ level = int_parameter(sample_level(level), pil_img.size[1] / 3)
if np.random.random() > 0.5:
level = -level
- return pil_img.transform(image_size,
+ return pil_img.transform(pil_img.size,
Image.AFFINE, (1, 0, 0, 0, 1, level),
resample=Image.BILINEAR)
@@ -174,17 +174,7 @@ def sharpness(pil_img, level, *args):
return ImageEnhance.Sharpness(pil_img).enhance(level)
-augmentations_reid = [
- autocontrast, equalize, posterize, shear_x, shear_y,
- color, contrast, brightness, sharpness
-]
-
augmentations = [
autocontrast, equalize, posterize, rotate, solarize, shear_x, shear_y,
translate_x, translate_y
]
-
-augmentations_all = [
- autocontrast, equalize, posterize, rotate, solarize, shear_x, shear_y,
- translate_x, translate_y, color, contrast, brightness, sharpness
-]
diff --git a/fastreid/data/transforms/transforms.py b/fastreid/data/transforms/transforms.py
index 83adc7745..14ab4f7d7 100644
--- a/fastreid/data/transforms/transforms.py
+++ b/fastreid/data/transforms/transforms.py
@@ -13,7 +13,7 @@
import numpy as np
from PIL import Image
-from .functional import to_tensor, augmentations_reid
+from .functional import to_tensor, augmentations
class ToTensor(object):
@@ -122,38 +122,45 @@ def __call__(self, img):
class AugMix(object):
""" Perform AugMix augmentation and compute mixture.
Args:
+ prob: Probability of taking augmix
aug_prob_coeff: Probability distribution coefficients.
mixture_width: Number of augmentation chains to mix per augmented example.
mixture_depth: Depth of augmentation chains. -1 denotes stochastic depth in [1, 3]'
- severity: Severity of underlying augmentation operators (between 1 to 10).
+ aug_severity: Severity of underlying augmentation operators (between 1 to 10).
"""
- def __init__(self, aug_prob_coeff=1, mixture_width=3, mixture_depth=-1, severity=1):
+ def __init__(self, prob=0.5, aug_prob_coeff=0.1, mixture_width=3, mixture_depth=1, aug_severity=1):
+ self.prob = prob
self.aug_prob_coeff = aug_prob_coeff
self.mixture_width = mixture_width
self.mixture_depth = mixture_depth
- self.severity = severity
- self.aug_list = augmentations_reid
+ self.aug_severity = aug_severity
+ self.augmentations = augmentations
def __call__(self, image):
"""Perform AugMix augmentations and compute mixture.
Returns:
mixed: Augmented and mixed image.
"""
+ if random.random() > self.prob:
+ return np.asarray(image)
+
ws = np.float32(
np.random.dirichlet([self.aug_prob_coeff] * self.mixture_width))
m = np.float32(np.random.beta(self.aug_prob_coeff, self.aug_prob_coeff))
- image = np.asarray(image, dtype=np.float32).copy()
- mix = np.zeros_like(image)
- h, w = image.shape[0], image.shape[1]
+ # image = np.asarray(image, dtype=np.float32).copy()
+ # mix = np.zeros_like(image)
+ mix = np.zeros([image.size[1], image.size[0], 3])
+ # h, w = image.shape[0], image.shape[1]
for i in range(self.mixture_width):
- image_aug = Image.fromarray(image.copy().astype(np.uint8))
+ image_aug = image.copy()
+ # image_aug = Image.fromarray(image.copy().astype(np.uint8))
depth = self.mixture_depth if self.mixture_depth > 0 else np.random.randint(1, 4)
for _ in range(depth):
- op = np.random.choice(self.aug_list)
- image_aug = op(image_aug, self.severity, (w, h))
- mix += ws[i] * np.asarray(image_aug, dtype=np.float32)
+ op = np.random.choice(self.augmentations)
+ image_aug = op(image_aug, self.aug_severity)
+ mix += ws[i] * np.asarray(image_aug)
mixed = (1 - m) * image + m * mix
- return mixed
+ return mixed.astype(np.uint8)
diff --git a/fastreid/engine/defaults.py b/fastreid/engine/defaults.py
index 95f9d0dbc..3d52bfaaa 100644
--- a/fastreid/engine/defaults.py
+++ b/fastreid/engine/defaults.py
@@ -233,8 +233,7 @@ def __init__(self, cfg):
model, data_loader, optimizer
)
- self.iters_per_epoch = len(data_loader.dataset) // cfg.SOLVER.IMS_PER_BATCH
- self.scheduler = self.build_lr_scheduler(cfg, optimizer, self.iters_per_epoch)
+ self.scheduler = self.build_lr_scheduler(cfg, optimizer)
# Assume no other objects need to be checkpointed.
# We can later make it checkpoint the stateful hooks
@@ -246,16 +245,13 @@ def __init__(self, cfg):
**optimizer_ckpt,
**self.scheduler,
)
- self.start_epoch = 0
- # if cfg.SOLVER.SWA.ENABLED:
- # self.max_iter = cfg.SOLVER.MAX_ITER + cfg.SOLVER.SWA.ITER
- # else:
- # self.max_iter = cfg.SOLVER.MAX_ITER
+ self.iters_per_epoch = len(data_loader.dataset) // cfg.SOLVER.IMS_PER_BATCH
+ self.start_epoch = 0
self.max_epoch = cfg.SOLVER.MAX_EPOCH
self.max_iter = self.max_epoch * self.iters_per_epoch
- self.warmup_iters = cfg.SOLVER.WARMUP_ITERS
+ self.warmup_epochs = cfg.SOLVER.WARMUP_EPOCHS
self.delay_epochs = cfg.SOLVER.DELAY_EPOCHS
self.cfg = cfg
@@ -413,15 +409,11 @@ def build_optimizer(cls, cfg, model):
return build_optimizer(cfg, model)
@classmethod
- def build_lr_scheduler(cls, cfg, optimizer, iters_per_epoch):
+ def build_lr_scheduler(cls, cfg, optimizer):
"""
It now calls :func:`fastreid.solver.build_lr_scheduler`.
Overwrite it if you'd like a different scheduler.
"""
- cfg = cfg.clone()
- cfg.defrost()
- cfg.SOLVER.MAX_EPOCH = cfg.SOLVER.MAX_EPOCH - max(
- math.ceil(cfg.SOLVER.WARMUP_ITERS / iters_per_epoch), cfg.SOLVER.DELAY_EPOCHS)
return build_lr_scheduler(cfg, optimizer)
@classmethod
@@ -429,7 +421,7 @@ def build_train_loader(cls, cfg):
"""
Returns:
iterable
- It now calls :func:`fastreid.data.build_detection_train_loader`.
+ It now calls :func:`fastreid.data.build_reid_train_loader`.
Overwrite it if you'd like a different data loader.
"""
logger = logging.getLogger(__name__)
@@ -441,7 +433,7 @@ def build_test_loader(cls, cfg, dataset_name):
"""
Returns:
iterable
- It now calls :func:`fastreid.data.build_detection_test_loader`.
+ It now calls :func:`fastreid.data.build_reid_test_loader`.
Overwrite it if you'd like a different data loader.
"""
return build_reid_test_loader(cfg, dataset_name)
diff --git a/fastreid/engine/hooks.py b/fastreid/engine/hooks.py
index 8fc81e0ac..91fbaf907 100644
--- a/fastreid/engine/hooks.py
+++ b/fastreid/engine/hooks.py
@@ -250,14 +250,11 @@ def after_step(self):
lr = self._optimizer.param_groups[self._best_param_group_id]["lr"]
self.trainer.storage.put_scalar("lr", lr, smoothing_hint=False)
- next_iter = self.trainer.iter + 1
- if next_iter < self.trainer.warmup_iters:
- self._scheduler["warmup_sched"].step()
-
def after_epoch(self):
- next_iter = self.trainer.iter
next_epoch = self.trainer.epoch + 1
- if next_iter >= self.trainer.warmup_iters and next_epoch >= self.trainer.delay_epochs:
+ if next_epoch <= self.trainer.warmup_epochs:
+ self._scheduler["warmup_sched"].step()
+ elif next_epoch >= self.trainer.delay_epochs:
self._scheduler["lr_sched"].step()
@@ -459,7 +456,6 @@ def __init__(self, model, freeze_layers, freeze_iters, fc_freeze_iters):
self.fc_freeze_iters = fc_freeze_iters
self.is_frozen = False
-
self.fc_frozen = False
def before_step(self):
diff --git a/fastreid/engine/train_loop.py b/fastreid/engine/train_loop.py
index d9a6a681d..c1ec35c01 100644
--- a/fastreid/engine/train_loop.py
+++ b/fastreid/engine/train_loop.py
@@ -236,14 +236,7 @@ def run_step(self):
If your want to do something with the heads, you can wrap the model.
"""
- outs = self.model(data)
-
- # Compute loss
- if isinstance(self.model, DistributedDataParallel):
- loss_dict = self.model.module.losses(outs)
- else:
- loss_dict = self.model.losses(outs)
-
+ loss_dict = self.model(data)
losses = sum(loss_dict.values())
"""
@@ -251,6 +244,7 @@ def run_step(self):
wrap the optimizer with your custom `zero_grad()` method.
"""
self.optimizer.zero_grad()
+
losses.backward()
self._write_metrics(loss_dict, data_time)
@@ -308,6 +302,7 @@ class AMPTrainer(SimpleTrainer):
Like :class:`SimpleTrainer`, but uses apex automatic mixed precision
in the training loop.
"""
+
def run_step(self):
"""
Implement the AMP training logic.
@@ -319,14 +314,7 @@ def run_step(self):
data = next(self._data_loader_iter)
data_time = time.perf_counter() - start
- outs = self.model(data)
-
- # Compute loss
- if isinstance(self.model, DistributedDataParallel):
- loss_dict = self.model.module.losses(outs)
- else:
- loss_dict = self.model.losses(outs)
-
+ loss_dict = self.model(data)
losses = sum(loss_dict.values())
self.optimizer.zero_grad()
diff --git a/fastreid/evaluation/reid_evaluation.py b/fastreid/evaluation/reid_evaluation.py
index b2fd94e19..fa2b4d934 100644
--- a/fastreid/evaluation/reid_evaluation.py
+++ b/fastreid/evaluation/reid_evaluation.py
@@ -6,19 +6,18 @@
import copy
import logging
from collections import OrderedDict
-from sklearn import metrics
import numpy as np
import torch
import torch.nn.functional as F
+from sklearn import metrics
+from fastreid.utils import comm
+from fastreid.utils.compute_dist import build_dist
from .evaluator import DatasetEvaluator
from .query_expansion import aqe
from .rank import evaluate_rank
-from .rerank import re_ranking
from .roc import evaluate_roc
-from fastreid.utils import comm
-from fastreid.utils.compute_dist import build_dist
logger = logging.getLogger(__name__)
@@ -103,10 +102,10 @@ def evaluate(self):
mAP = np.mean(all_AP)
mINP = np.mean(all_INP)
for r in [1, 5, 10]:
- self._results['Rank-{}'.format(r)] = cmc[r - 1]
- self._results['mAP'] = mAP
- self._results['mINP'] = mINP
- self._results["metric"] = (mAP + cmc[0]) / 2
+ self._results['Rank-{}'.format(r)] = cmc[r - 1] * 100
+ self._results['mAP'] = mAP * 100
+ self._results['mINP'] = mINP * 100
+ self._results["metric"] = (mAP + cmc[0]) / 2 * 100
if self.cfg.TEST.ROC_ENABLED:
scores, labels = evaluate_roc(dist, query_pids, gallery_pids, query_camids, gallery_camids)
diff --git a/fastreid/evaluation/testing.py b/fastreid/evaluation/testing.py
index fc9019e1b..284d5c860 100644
--- a/fastreid/evaluation/testing.py
+++ b/fastreid/evaluation/testing.py
@@ -30,7 +30,7 @@ def print_csv_format(results):
table = tabulate(
csv_results,
tablefmt="pipe",
- floatfmt=".2%",
+ floatfmt=".2f",
headers=metrics,
numalign="left",
)
diff --git a/fastreid/layers/arc_softmax.py b/fastreid/layers/arc_softmax.py
index f806141a5..4e4ca65c6 100644
--- a/fastreid/layers/arc_softmax.py
+++ b/fastreid/layers/arc_softmax.py
@@ -20,6 +20,8 @@ def __init__(self, cfg, in_feat, num_classes):
self.s = cfg.MODEL.HEADS.SCALE
self.m = cfg.MODEL.HEADS.MARGIN
+ self.easy_margin = False
+
self.cos_m = math.cos(self.m)
self.sin_m = math.sin(self.m)
self.threshold = math.cos(math.pi - self.m)
@@ -30,26 +32,18 @@ def __init__(self, cfg, in_feat, num_classes):
self.register_buffer('t', torch.zeros(1))
def forward(self, features, targets):
- # get cos(theta)
- cos_theta = F.linear(F.normalize(features), F.normalize(self.weight))
- cos_theta = cos_theta.clamp(-1, 1) # for numerical stability
-
- target_logit = cos_theta[torch.arange(0, features.size(0)), targets].view(-1, 1)
-
- sin_theta = torch.sqrt(1.0 - torch.pow(target_logit, 2))
- cos_theta_m = target_logit * self.cos_m - sin_theta * self.sin_m # cos(target+margin)
- mask = cos_theta > cos_theta_m
- final_target_logit = torch.where(target_logit > self.threshold,
- cos_theta_m.to(target_logit),
- target_logit - self.mm)
-
- hard_example = cos_theta[mask]
- with torch.no_grad():
- self.t = target_logit.mean() * 0.01 + (1 - 0.01) * self.t
- cos_theta[mask] = hard_example * (self.t + hard_example).to(hard_example.dtype)
- cos_theta.scatter_(1, targets.view(-1, 1).long(), final_target_logit)
- pred_class_logits = cos_theta * self.s
- return pred_class_logits
+ cosine = F.linear(F.normalize(features), F.normalize(self.weight))
+ sine = torch.sqrt(1.0 - torch.pow(cosine, 2))
+ phi = cosine * self.cos_m - sine * self.sin_m # cos(theta + m)
+ if self.easy_margin:
+ phi = torch.where(cosine > 0, phi, cosine)
+ else:
+ phi = torch.where(cosine > self.threshold, phi, cosine - self.mm)
+ one_hot = torch.zeros(cosine.size(), device=cosine.device)
+ one_hot.scatter_(1, targets.view(-1, 1).long(), 1)
+ output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
+ output *= self.s
+ return output
def extra_repr(self):
return 'in_features={}, num_classes={}, scale={}, margin={}'.format(
diff --git a/fastreid/layers/batch_norm.py b/fastreid/layers/batch_norm.py
index 603dc9307..da8a733c1 100644
--- a/fastreid/layers/batch_norm.py
+++ b/fastreid/layers/batch_norm.py
@@ -80,7 +80,7 @@ def forward(self, input):
self.weight, self.bias, False, self.momentum, self.eps)
-class FrozenBatchNorm(BatchNorm):
+class FrozenBatchNorm(nn.Module):
"""
BatchNorm2d where the batch statistics and the affine parameters are fixed.
It contains non-trainable buffers called
@@ -99,9 +99,13 @@ class FrozenBatchNorm(BatchNorm):
_version = 3
def __init__(self, num_features, eps=1e-5, **kwargs):
- super().__init__(num_features, weight_freeze=True, bias_freeze=True, **kwargs)
+ super().__init__()
self.num_features = num_features
self.eps = eps
+ self.register_buffer("weight", torch.ones(num_features))
+ self.register_buffer("bias", torch.zeros(num_features))
+ self.register_buffer("running_mean", torch.zeros(num_features))
+ self.register_buffer("running_var", torch.ones(num_features) - eps)
def forward(self, x):
if x.requires_grad:
@@ -198,9 +202,9 @@ def get_norm(norm, out_channels, **kwargs):
return None
norm = {
"BN": BatchNorm,
+ "syncBN": SyncBatchNorm,
"GhostBN": GhostBatchNorm,
"FrozenBN": FrozenBatchNorm,
"GN": lambda channels, **args: nn.GroupNorm(32, channels),
- "syncBN": SyncBatchNorm,
}[norm]
return norm(out_channels, **kwargs)
diff --git a/fastreid/modeling/backbones/__init__.py b/fastreid/modeling/backbones/__init__.py
index bf2a35a6e..823c8a71b 100644
--- a/fastreid/modeling/backbones/__init__.py
+++ b/fastreid/modeling/backbones/__init__.py
@@ -11,3 +11,4 @@
from .resnest import build_resnest_backbone
from .resnext import build_resnext_backbone
from .regnet import build_regnet_backbone, build_effnet_backbone
+from .shufflenet import build_shufflenetv2_backbone
diff --git a/fastreid/modeling/backbones/resnet.py b/fastreid/modeling/backbones/resnet.py
index 535ecb54f..89a646409 100644
--- a/fastreid/modeling/backbones/resnet.py
+++ b/fastreid/modeling/backbones/resnet.py
@@ -183,6 +183,7 @@ def forward(self, x):
x = self.relu(x)
x = self.maxpool(x)
+ # layer 1
NL1_counter = 0
if len(self.NL_1_idx) == 0:
self.NL_1_idx = [-1]
@@ -192,7 +193,7 @@ def forward(self, x):
_, C, H, W = x.shape
x = self.NL_1[NL1_counter](x)
NL1_counter += 1
- # Layer 2
+ # layer 2
NL2_counter = 0
if len(self.NL_2_idx) == 0:
self.NL_2_idx = [-1]
@@ -202,7 +203,8 @@ def forward(self, x):
_, C, H, W = x.shape
x = self.NL_2[NL2_counter](x)
NL2_counter += 1
- # Layer 3
+
+ # layer 3
NL3_counter = 0
if len(self.NL_3_idx) == 0:
self.NL_3_idx = [-1]
@@ -212,7 +214,8 @@ def forward(self, x):
_, C, H, W = x.shape
x = self.NL_3[NL3_counter](x)
NL3_counter += 1
- # Layer 4
+
+ # layer 4
NL4_counter = 0
if len(self.NL_4_idx) == 0:
self.NL_4_idx = [-1]
diff --git a/fastreid/modeling/backbones/shufflenet.py b/fastreid/modeling/backbones/shufflenet.py
new file mode 100644
index 000000000..551299eb8
--- /dev/null
+++ b/fastreid/modeling/backbones/shufflenet.py
@@ -0,0 +1,203 @@
+"""
+Author: Guan'an Wang
+Contact: guan.wang0706@gmail.com
+"""
+
+import torch
+from torch import nn
+from collections import OrderedDict
+import logging
+from fastreid.utils.checkpoint import get_missing_parameters_message, get_unexpected_parameters_message
+
+from fastreid.layers import get_norm
+from fastreid.modeling.backbones import BACKBONE_REGISTRY
+
+logger = logging.getLogger(__name__)
+
+
+class ShuffleV2Block(nn.Module):
+ """
+ Reference:
+ https://github.com/megvii-model/ShuffleNet-Series/tree/master/ShuffleNetV2
+ """
+
+ def __init__(self, bn_norm, inp, oup, mid_channels, *, ksize, stride):
+ super(ShuffleV2Block, self).__init__()
+ self.stride = stride
+ assert stride in [1, 2]
+
+ self.mid_channels = mid_channels
+ self.ksize = ksize
+ pad = ksize // 2
+ self.pad = pad
+ self.inp = inp
+
+ outputs = oup - inp
+
+ branch_main = [
+ # pw
+ nn.Conv2d(inp, mid_channels, 1, 1, 0, bias=False),
+ get_norm(bn_norm, mid_channels),
+ nn.ReLU(inplace=True),
+ # dw
+ nn.Conv2d(mid_channels, mid_channels, ksize, stride, pad, groups=mid_channels, bias=False),
+ get_norm(bn_norm, mid_channels),
+ # pw-linear
+ nn.Conv2d(mid_channels, outputs, 1, 1, 0, bias=False),
+ get_norm(bn_norm, outputs),
+ nn.ReLU(inplace=True),
+ ]
+ self.branch_main = nn.Sequential(*branch_main)
+
+ if stride == 2:
+ branch_proj = [
+ # dw
+ nn.Conv2d(inp, inp, ksize, stride, pad, groups=inp, bias=False),
+ get_norm(bn_norm, inp),
+ # pw-linear
+ nn.Conv2d(inp, inp, 1, 1, 0, bias=False),
+ get_norm(bn_norm, inp),
+ nn.ReLU(inplace=True),
+ ]
+ self.branch_proj = nn.Sequential(*branch_proj)
+ else:
+ self.branch_proj = None
+
+ def forward(self, old_x):
+ if self.stride == 1:
+ x_proj, x = self.channel_shuffle(old_x)
+ return torch.cat((x_proj, self.branch_main(x)), 1)
+ elif self.stride == 2:
+ x_proj = old_x
+ x = old_x
+ return torch.cat((self.branch_proj(x_proj), self.branch_main(x)), 1)
+
+ def channel_shuffle(self, x):
+ batchsize, num_channels, height, width = x.data.size()
+ assert (num_channels % 4 == 0)
+ x = x.reshape(batchsize * num_channels // 2, 2, height * width)
+ x = x.permute(1, 0, 2)
+ x = x.reshape(2, -1, num_channels // 2, height, width)
+ return x[0], x[1]
+
+
+class ShuffleNetV2(nn.Module):
+ """
+ Reference:
+ https://github.com/megvii-model/ShuffleNet-Series/tree/master/ShuffleNetV2
+ """
+
+ def __init__(self, bn_norm, model_size='1.5x'):
+ super(ShuffleNetV2, self).__init__()
+
+ self.stage_repeats = [4, 8, 4]
+ self.model_size = model_size
+ if model_size == '0.5x':
+ self.stage_out_channels = [-1, 24, 48, 96, 192, 1024]
+ elif model_size == '1.0x':
+ self.stage_out_channels = [-1, 24, 116, 232, 464, 1024]
+ elif model_size == '1.5x':
+ self.stage_out_channels = [-1, 24, 176, 352, 704, 1024]
+ elif model_size == '2.0x':
+ self.stage_out_channels = [-1, 24, 244, 488, 976, 2048]
+ else:
+ raise NotImplementedError
+
+ # building first layer
+ input_channel = self.stage_out_channels[1]
+ self.first_conv = nn.Sequential(
+ nn.Conv2d(3, input_channel, 3, 2, 1, bias=False),
+ get_norm(bn_norm, input_channel),
+ nn.ReLU(inplace=True),
+ )
+
+ self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+
+ self.features = []
+ for idxstage in range(len(self.stage_repeats)):
+ numrepeat = self.stage_repeats[idxstage]
+ output_channel = self.stage_out_channels[idxstage + 2]
+
+ for i in range(numrepeat):
+ if i == 0:
+ self.features.append(ShuffleV2Block(bn_norm, input_channel, output_channel,
+ mid_channels=output_channel // 2, ksize=3, stride=2))
+ else:
+ self.features.append(ShuffleV2Block(bn_norm, input_channel // 2, output_channel,
+ mid_channels=output_channel // 2, ksize=3, stride=1))
+
+ input_channel = output_channel
+
+ self.features = nn.Sequential(*self.features)
+
+ self.conv_last = nn.Sequential(
+ nn.Conv2d(input_channel, self.stage_out_channels[-1], 1, 1, 0, bias=False),
+ get_norm(bn_norm, self.stage_out_channels[-1]),
+ nn.ReLU(inplace=True)
+ )
+
+ self._initialize_weights()
+
+ def forward(self, x):
+ x = self.first_conv(x)
+ x = self.maxpool(x)
+ x = self.features(x)
+ x = self.conv_last(x)
+
+ return x
+
+ def _initialize_weights(self):
+ for name, m in self.named_modules():
+ if isinstance(m, nn.Conv2d):
+ if 'first' in name:
+ nn.init.normal_(m.weight, 0, 0.01)
+ else:
+ nn.init.normal_(m.weight, 0, 1.0 / m.weight.shape[1])
+ if m.bias is not None:
+ nn.init.constant_(m.bias, 0)
+ elif isinstance(m, nn.BatchNorm2d):
+ nn.init.constant_(m.weight, 1)
+ if m.bias is not None:
+ nn.init.constant_(m.bias, 0.0001)
+ nn.init.constant_(m.running_mean, 0)
+ elif isinstance(m, nn.BatchNorm1d):
+ nn.init.constant_(m.weight, 1)
+ if m.bias is not None:
+ nn.init.constant_(m.bias, 0.0001)
+ nn.init.constant_(m.running_mean, 0)
+ elif isinstance(m, nn.Linear):
+ nn.init.normal_(m.weight, 0, 0.01)
+ if m.bias is not None:
+ nn.init.constant_(m.bias, 0)
+
+
+@BACKBONE_REGISTRY.register()
+def build_shufflenetv2_backbone(cfg):
+ # fmt: off
+ pretrain = cfg.MODEL.BACKBONE.PRETRAIN
+ pretrain_path = cfg.MODEL.BACKBONE.PRETRAIN_PATH
+ bn_norm = cfg.MODEL.BACKBONE.NORM
+ model_size = cfg.MODEL.BACKBONE.DEPTH
+ # fmt: on
+
+ model = ShuffleNetV2(bn_norm, model_size=model_size)
+
+ if pretrain:
+ new_state_dict = OrderedDict()
+ state_dict = torch.load(pretrain_path)["state_dict"]
+ for k, v in state_dict.items():
+ if k[:7] == 'module.':
+ k = k[7:]
+ new_state_dict[k] = v
+
+ incompatible = model.load_state_dict(new_state_dict, strict=False)
+ if incompatible.missing_keys:
+ logger.info(
+ get_missing_parameters_message(incompatible.missing_keys)
+ )
+ if incompatible.unexpected_keys:
+ logger.info(
+ get_unexpected_parameters_message(incompatible.unexpected_keys)
+ )
+
+ return model
diff --git a/fastreid/modeling/heads/__init__.py b/fastreid/modeling/heads/__init__.py
index 7d6233083..adb13db75 100644
--- a/fastreid/modeling/heads/__init__.py
+++ b/fastreid/modeling/heads/__init__.py
@@ -8,4 +8,3 @@
# import all the meta_arch, so they will be registered
from .embedding_head import EmbeddingHead
-from .attr_head import AttrHead
diff --git a/fastreid/modeling/heads/build.py b/fastreid/modeling/heads/build.py
index d57c9a8ce..50fd36717 100644
--- a/fastreid/modeling/heads/build.py
+++ b/fastreid/modeling/heads/build.py
@@ -16,9 +16,9 @@
"""
-def build_heads(cfg):
+def build_heads(cfg, **kwargs):
"""
Build REIDHeads defined by `cfg.MODEL.REID_HEADS.NAME`.
"""
head = cfg.MODEL.HEADS.NAME
- return REID_HEADS_REGISTRY.get(head)(cfg)
+ return REID_HEADS_REGISTRY.get(head)(cfg, **kwargs)
diff --git a/fastreid/modeling/heads/embedding_head.py b/fastreid/modeling/heads/embedding_head.py
index d5f88f020..134ff6786 100644
--- a/fastreid/modeling/heads/embedding_head.py
+++ b/fastreid/modeling/heads/embedding_head.py
@@ -50,7 +50,7 @@ def __init__(self, cfg):
self.bottleneck = nn.Sequential(*bottleneck)
- # identity classification layer
+ # classification layer
# fmt: off
if cls_type == 'linear': self.classifier = nn.Linear(feat_dim, num_classes, bias=False)
elif cls_type == 'arcSoftmax': self.classifier = ArcSoftmax(cfg, feat_dim, num_classes)
diff --git a/fastreid/modeling/meta_arch/__init__.py b/fastreid/modeling/meta_arch/__init__.py
index 3b6b2651c..33a56be12 100644
--- a/fastreid/modeling/meta_arch/__init__.py
+++ b/fastreid/modeling/meta_arch/__init__.py
@@ -10,3 +10,5 @@
# import all the meta_arch, so they will be registered
from .baseline import Baseline
from .mgn import MGN
+from .moco import MoCo
+from .distiller import Distiller
diff --git a/fastreid/modeling/meta_arch/baseline.py b/fastreid/modeling/meta_arch/baseline.py
index da2cc0443..ca6a1cafc 100644
--- a/fastreid/modeling/meta_arch/baseline.py
+++ b/fastreid/modeling/meta_arch/baseline.py
@@ -46,10 +46,8 @@ def forward(self, batched_inputs):
if targets.sum() < 0: targets.zero_()
outputs = self.heads(features, targets)
- return {
- "outputs": outputs,
- "targets": targets,
- }
+ losses = self.losses(outputs, targets)
+ return losses
else:
outputs = self.heads(features)
return outputs
@@ -68,15 +66,13 @@ def preprocess_image(self, batched_inputs):
images.sub_(self.pixel_mean).div_(self.pixel_std)
return images
- def losses(self, outs):
+ def losses(self, outputs, gt_labels):
r"""
Compute loss from modeling's outputs, the loss function input arguments
must be the same as the outputs of the model forwarding.
"""
- # fmt: off
- outputs = outs["outputs"]
- gt_labels = outs["targets"]
# model predictions
+ # fmt: off
pred_class_logits = outputs['pred_class_logits'].detach()
cls_outputs = outputs['cls_outputs']
pred_features = outputs['features']
diff --git a/fastreid/modeling/meta_arch/build.py b/fastreid/modeling/meta_arch/build.py
index 7a4caa192..1d7431414 100644
--- a/fastreid/modeling/meta_arch/build.py
+++ b/fastreid/modeling/meta_arch/build.py
@@ -15,12 +15,12 @@
"""
-def build_model(cfg):
+def build_model(cfg, **kwargs):
"""
Build the whole model architecture, defined by ``cfg.MODEL.META_ARCHITECTURE``.
Note that it does not load any weights from ``cfg``.
"""
meta_arch = cfg.MODEL.META_ARCHITECTURE
- model = META_ARCH_REGISTRY.get(meta_arch)(cfg)
+ model = META_ARCH_REGISTRY.get(meta_arch)(cfg, **kwargs)
model.to(torch.device(cfg.MODEL.DEVICE))
return model
diff --git a/fastreid/modeling/meta_arch/distiller.py b/fastreid/modeling/meta_arch/distiller.py
new file mode 100644
index 000000000..1ee300837
--- /dev/null
+++ b/fastreid/modeling/meta_arch/distiller.py
@@ -0,0 +1,88 @@
+# encoding: utf-8
+"""
+@author: l1aoxingyu
+@contact: sherlockliao01@gmail.com
+"""
+
+import logging
+
+import torch
+import torch.nn.functional as F
+
+from fastreid.config import get_cfg
+from fastreid.modeling.meta_arch import META_ARCH_REGISTRY, build_model, Baseline
+from fastreid.utils.checkpoint import Checkpointer
+
+logger = logging.getLogger(__name__)
+
+
+@META_ARCH_REGISTRY.register()
+class Distiller(Baseline):
+ def __init__(self, cfg):
+ super(Distiller, self).__init__(cfg)
+
+ # Get teacher model config
+ cfg_t = get_cfg()
+ cfg_t.merge_from_file(cfg.KD.MODEL_CONFIG)
+
+ model_t = build_model(cfg_t)
+ logger.info("Teacher model:\n{}".format(model_t))
+
+ # No gradients for teacher model
+ for param in model_t.parameters():
+ param.requires_grad_(False)
+
+ logger.info("Loading teacher model weights ...")
+ Checkpointer(model_t).load(cfg.KD.MODEL_WEIGHTS)
+
+ # Not register teacher model as `nn.Module`, this is
+ # make sure teacher model weights not saved
+ self.model_t = [model_t.backbone, model_t.heads]
+
+ def forward(self, batched_inputs):
+ if self.training:
+ images = self.preprocess_image(batched_inputs)
+ # student model forward
+ s_feat = self.backbone(images)
+ assert "targets" in batched_inputs, "Labels are missing in training!"
+ targets = batched_inputs["targets"].to(self.device)
+
+ if targets.sum() < 0: targets.zero_()
+
+ s_outputs = self.heads(s_feat, targets)
+
+ # teacher model forward
+ with torch.no_grad():
+ t_feat = self.model_t[0](images)
+ t_outputs = self.model_t[1](t_feat, targets)
+
+ losses = self.losses(s_outputs, t_outputs, targets)
+ return losses
+
+ # Eval mode, just conventional reid feature extraction
+ else:
+ return super(Distiller, self).forward(batched_inputs)
+
+ def losses(self, s_outputs, t_outputs, gt_labels):
+ r"""
+ Compute loss from modeling's outputs, the loss function input arguments
+ must be the same as the outputs of the model forwarding.
+ """
+ loss_dict = super(Distiller, self).losses(s_outputs, gt_labels)
+
+ s_logits = s_outputs["pred_class_logits"]
+ t_logits = t_outputs["pred_class_logits"].detach()
+ loss_dict["loss_jsdiv"] = self.jsdiv_loss(s_logits, t_logits)
+
+ return loss_dict
+
+ @staticmethod
+ def _kldiv(y_s, y_t, t):
+ p_s = F.log_softmax(y_s / t, dim=1)
+ p_t = F.softmax(y_t / t, dim=1)
+ loss = F.kl_div(p_s, p_t, reduction="sum") * (t ** 2) / y_s.shape[0]
+ return loss
+
+ def jsdiv_loss(self, y_s, y_t, t=16):
+ loss = (self._kldiv(y_s, y_t, t) + self._kldiv(y_t, y_s, t)) / 2
+ return loss
diff --git a/fastreid/modeling/meta_arch/mgn.py b/fastreid/modeling/meta_arch/mgn.py
index 10f51da55..970e4c6e6 100644
--- a/fastreid/modeling/meta_arch/mgn.py
+++ b/fastreid/modeling/meta_arch/mgn.py
@@ -111,17 +111,11 @@ def forward(self, batched_inputs):
b32_outputs = self.b32_head(b32_feat, targets)
b33_outputs = self.b33_head(b33_feat, targets)
- return {
- "b1_outputs": b1_outputs,
- "b2_outputs": b2_outputs,
- "b21_outputs": b21_outputs,
- "b22_outputs": b22_outputs,
- "b3_outputs": b3_outputs,
- "b31_outputs": b31_outputs,
- "b32_outputs": b32_outputs,
- "b33_outputs": b33_outputs,
- "targets": targets,
- }
+ losses = self.losses(b1_outputs,
+ b2_outputs, b21_outputs, b22_outputs,
+ b3_outputs, b31_outputs, b32_outputs, b33_outputs,
+ targets)
+ return losses
else:
b1_pool_feat = self.b1_head(b1_feat)
b2_pool_feat = self.b2_head(b2_feat)
@@ -150,18 +144,12 @@ def preprocess_image(self, batched_inputs):
images.sub_(self.pixel_mean).div_(self.pixel_std)
return images
- def losses(self, outs):
- # fmt: off
- b1_outputs = outs["b1_outputs"]
- b2_outputs = outs["b2_outputs"]
- b21_outputs = outs["b21_outputs"]
- b22_outputs = outs["b22_outputs"]
- b3_outputs = outs["b3_outputs"]
- b31_outputs = outs["b31_outputs"]
- b32_outputs = outs["b32_outputs"]
- b33_outputs = outs["b33_outputs"]
- gt_labels = outs["targets"]
+ def losses(self,
+ b1_outputs,
+ b2_outputs, b21_outputs, b22_outputs,
+ b3_outputs, b31_outputs, b32_outputs, b33_outputs, gt_labels):
# model predictions
+ # fmt: off
pred_class_logits = b1_outputs['pred_class_logits'].detach()
b1_logits = b1_outputs['cls_outputs']
b2_logits = b2_outputs['cls_outputs']
diff --git a/fastreid/modeling/meta_arch/moco.py b/fastreid/modeling/meta_arch/moco.py
new file mode 100644
index 000000000..3ced541e6
--- /dev/null
+++ b/fastreid/modeling/meta_arch/moco.py
@@ -0,0 +1,126 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import torch
+import torch.nn.functional as F
+from torch import nn
+
+from fastreid.modeling.losses.utils import concat_all_gather
+from fastreid.utils import comm
+from .baseline import Baseline
+from .build import META_ARCH_REGISTRY
+
+
+@META_ARCH_REGISTRY.register()
+class MoCo(Baseline):
+ def __init__(self, cfg):
+ super(MoCo, self).__init__(cfg)
+
+ dim = cfg.MODEL.HEADS.EMBEDDING_DIM if cfg.MODEL.HEADS.EMBEDDING_DIM \
+ else cfg.MODEL.BACKBONE.FEAT_DIM
+ size = cfg.MODEL.QUEUE_SIZE
+ self.memory = Memory(dim, size)
+
+ def losses(self, outputs, gt_labels):
+ """
+ Compute loss from modeling's outputs, the loss function input arguments
+ must be the same as the outputs of the model forwarding.
+ """
+ # reid loss
+ loss_dict = super(MoCo, self).losses(outputs, gt_labels)
+
+ # memory loss
+ pred_features = outputs['features']
+ loss_mb = self.memory(pred_features, gt_labels)
+ loss_dict["loss_mb"] = loss_mb
+ return loss_dict
+
+
+class Memory(nn.Module):
+ """
+ Build a MoCo memory with a queue
+ https://arxiv.org/abs/1911.05722
+ """
+
+ def __init__(self, dim=512, K=65536):
+ """
+ dim: feature dimension (default: 128)
+ K: queue size; number of negative keys (default: 65536)
+ """
+ super().__init__()
+ self.K = K
+
+ self.margin = 0.25
+ self.gamma = 32
+
+ # create the queue
+ self.register_buffer("queue", torch.randn(dim, K))
+ self.queue = F.normalize(self.queue, dim=0)
+
+ self.register_buffer("queue_label", torch.zeros((1, K), dtype=torch.long))
+ self.register_buffer("queue_ptr", torch.zeros(1, dtype=torch.long))
+
+ @torch.no_grad()
+ def _dequeue_and_enqueue(self, keys, targets):
+ # gather keys/targets before updating queue
+ if comm.get_world_size() > 1:
+ keys = concat_all_gather(keys)
+ targets = concat_all_gather(targets)
+ else:
+ keys = keys.detach()
+ targets = targets.detach()
+
+ batch_size = keys.shape[0]
+
+ ptr = int(self.queue_ptr)
+ assert self.K % batch_size == 0 # for simplicity
+
+ # replace the keys at ptr (dequeue and enqueue)
+ self.queue[:, ptr:ptr + batch_size] = keys.T
+ self.queue_label[:, ptr:ptr + batch_size] = targets
+ ptr = (ptr + batch_size) % self.K # move pointer
+
+ self.queue_ptr[0] = ptr
+
+ def forward(self, feat_q, targets):
+ """
+ Memory bank enqueue and compute metric loss
+ Args:
+ feat_q: model features
+ targets: gt labels
+
+ Returns:
+ """
+ # normalize embedding features
+ feat_q = F.normalize(feat_q, p=2, dim=1)
+ # dequeue and enqueue
+ self._dequeue_and_enqueue(feat_q.detach(), targets)
+ # compute loss
+ loss = self._pairwise_cosface(feat_q, targets)
+ return loss
+
+ def _pairwise_cosface(self, feat_q, targets):
+ dist_mat = torch.matmul(feat_q, self.queue)
+
+ N, M = dist_mat.size() # (bsz, memory)
+ is_pos = targets.view(N, 1).expand(N, M).eq(self.queue_label.expand(N, M)).float()
+ is_neg = targets.view(N, 1).expand(N, M).ne(self.queue_label.expand(N, M)).float()
+
+ # Mask scores related to themselves
+ same_indx = torch.eye(N, N, device=is_pos.device)
+ other_indx = torch.zeros(N, M - N, device=is_pos.device)
+ same_indx = torch.cat((same_indx, other_indx), dim=1)
+ is_pos = is_pos - same_indx
+
+ s_p = dist_mat * is_pos
+ s_n = dist_mat * is_neg
+
+ logit_p = -self.gamma * s_p + (-99999999.) * (1 - is_pos)
+ logit_n = self.gamma * (s_n + self.margin) + (-99999999.) * (1 - is_neg)
+
+ loss = F.softplus(torch.logsumexp(logit_p, dim=1) + torch.logsumexp(logit_n, dim=1)).mean()
+
+ return loss
diff --git a/fastreid/solver/build.py b/fastreid/solver/build.py
index 4a3f58912..524a72e46 100644
--- a/fastreid/solver/build.py
+++ b/fastreid/solver/build.py
@@ -23,26 +23,24 @@ def build_optimizer(cfg, model):
params += [{"name": key, "params": [value], "lr": lr, "weight_decay": weight_decay}]
solver_opt = cfg.SOLVER.OPT
- # fmt: off
- if solver_opt == "SGD": opt_fns = getattr(optim, solver_opt)(params, momentum=cfg.SOLVER.MOMENTUM)
- else: opt_fns = getattr(optim, solver_opt)(params)
- # fmt: on
+ if solver_opt == "SGD":
+ opt_fns = getattr(optim, solver_opt)(
+ params,
+ momentum=cfg.SOLVER.MOMENTUM,
+ nesterov=True if cfg.SOLVER.MOMENTUM and cfg.SOLVER.NESTEROV else False
+ )
+ else:
+ opt_fns = getattr(optim, solver_opt)(params)
return opt_fns
def build_lr_scheduler(cfg, optimizer):
- scheduler_dict = {}
+ cfg = cfg.clone()
+ cfg.defrost()
+ cfg.SOLVER.MAX_EPOCH = cfg.SOLVER.MAX_EPOCH - max(
+ cfg.SOLVER.WARMUP_EPOCHS + 1, cfg.SOLVER.DELAY_EPOCHS)
- if cfg.SOLVER.WARMUP_ITERS > 0:
- warmup_args = {
- "optimizer": optimizer,
-
- # warmup options
- "warmup_factor": cfg.SOLVER.WARMUP_FACTOR,
- "warmup_iters": cfg.SOLVER.WARMUP_ITERS,
- "warmup_method": cfg.SOLVER.WARMUP_METHOD,
- }
- scheduler_dict["warmup_sched"] = lr_scheduler.WarmupLR(**warmup_args)
+ scheduler_dict = {}
scheduler_args = {
"MultiStepLR": {
@@ -63,4 +61,15 @@ def build_lr_scheduler(cfg, optimizer):
scheduler_dict["lr_sched"] = getattr(lr_scheduler, cfg.SOLVER.SCHED)(
**scheduler_args[cfg.SOLVER.SCHED])
+ if cfg.SOLVER.WARMUP_EPOCHS > 0:
+ warmup_args = {
+ "optimizer": optimizer,
+
+ # warmup options
+ "warmup_factor": cfg.SOLVER.WARMUP_FACTOR,
+ "warmup_epochs": cfg.SOLVER.WARMUP_EPOCHS,
+ "warmup_method": cfg.SOLVER.WARMUP_METHOD,
+ }
+ scheduler_dict["warmup_sched"] = lr_scheduler.WarmupLR(**warmup_args)
+
return scheduler_dict
diff --git a/fastreid/solver/lr_scheduler.py b/fastreid/solver/lr_scheduler.py
index bd003a802..711c1b53e 100644
--- a/fastreid/solver/lr_scheduler.py
+++ b/fastreid/solver/lr_scheduler.py
@@ -8,26 +8,25 @@
import torch
from torch.optim.lr_scheduler import *
-from torch.optim.lr_scheduler import _LRScheduler
-class WarmupLR(_LRScheduler):
+class WarmupLR(torch.optim.lr_scheduler._LRScheduler):
def __init__(
self,
optimizer: torch.optim.Optimizer,
warmup_factor: float = 0.1,
- warmup_iters: int = 10,
+ warmup_epochs: int = 10,
warmup_method: str = "linear",
last_epoch: int = -1,
):
self.warmup_factor = warmup_factor
- self.warmup_iters = warmup_iters
+ self.warmup_epochs = warmup_epochs
self.warmup_method = warmup_method
super().__init__(optimizer, last_epoch)
def get_lr(self) -> List[float]:
- warmup_factor = _get_warmup_factor_at_iter(
- self.warmup_method, self.last_epoch, self.warmup_iters, self.warmup_factor
+ warmup_factor = _get_warmup_factor_at_epoch(
+ self.warmup_method, self.last_epoch, self.warmup_epochs, self.warmup_factor
)
return [
base_lr * warmup_factor for base_lr in self.base_lrs
@@ -38,30 +37,30 @@ def _compute_values(self) -> List[float]:
return self.get_lr()
-def _get_warmup_factor_at_iter(
- method: str, iter: int, warmup_iters: int, warmup_factor: float
+def _get_warmup_factor_at_epoch(
+ method: str, epoch: int, warmup_epochs: int, warmup_factor: float
) -> float:
"""
Return the learning rate warmup factor at a specific iteration.
See https://arxiv.org/abs/1706.02677 for more details.
Args:
method (str): warmup method; either "constant" or "linear".
- iter (int): iteration at which to calculate the warmup factor.
- warmup_iters (int): the number of warmup iterations.
+ epoch (int): epoch at which to calculate the warmup factor.
+ warmup_epochs (int): the number of warmup epochs.
warmup_factor (float): the base warmup factor (the meaning changes according
to the method used).
Returns:
float: the effective warmup factor at the given iteration.
"""
- if iter >= warmup_iters:
+ if epoch >= warmup_epochs:
return 1.0
if method == "constant":
return warmup_factor
elif method == "linear":
- alpha = (1 - iter / warmup_iters) * (1 - warmup_factor)
- return 1 - alpha
+ alpha = epoch / warmup_epochs
+ return warmup_factor * (1 - alpha) + alpha
elif method == "exp":
- return warmup_factor ** (1 - iter / warmup_iters)
+ return warmup_factor ** (1 - epoch / warmup_epochs)
else:
raise ValueError("Unknown warmup method: {}".format(method))
diff --git a/fastreid/solver/optim/__init__.py b/fastreid/solver/optim/__init__.py
index 40659a10a..4fbcc7819 100644
--- a/fastreid/solver/optim/__init__.py
+++ b/fastreid/solver/optim/__init__.py
@@ -1,3 +1,9 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
from .lamb import Lamb
from .swa import SWA
from torch.optim import *
diff --git a/fastreid/solver/optim/adam.py b/fastreid/solver/optim/adam.py
deleted file mode 100644
index 3b515d9b2..000000000
--- a/fastreid/solver/optim/adam.py
+++ /dev/null
@@ -1,116 +0,0 @@
-import math
-
-import torch
-from torch.optim.optimizer import Optimizer
-
-
-class Adam(Optimizer):
- r"""Implements Adam algorithm.
- It has been proposed in `Adam: A Method for Stochastic Optimization`_.
- The implementation of the L2 penalty follows changes proposed in
- `Decoupled Weight Decay Regularization`_.
- Arguments:
- params (iterable): iterable of parameters to optimize or dicts defining
- parameter groups
- lr (float, optional): learning rate (default: 1e-3)
- betas (Tuple[float, float], optional): coefficients used for computing
- running averages of gradient and its square (default: (0.9, 0.999))
- eps (float, optional): term added to the denominator to improve
- numerical stability (default: 1e-8)
- weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
- amsgrad (boolean, optional): whether to use the AMSGrad variant of this
- algorithm from the paper `On the Convergence of Adam and Beyond`_
- (default: False)
- .. _Adam\: A Method for Stochastic Optimization:
- https://arxiv.org/abs/1412.6980
- .. _Decoupled Weight Decay Regularization:
- https://arxiv.org/abs/1711.05101
- .. _On the Convergence of Adam and Beyond:
- https://openreview.net/forum?id=ryQu7f-RZ
- """
-
- def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
- weight_decay=0, amsgrad=False):
- if not 0.0 <= lr:
- raise ValueError("Invalid learning rate: {}".format(lr))
- if not 0.0 <= eps:
- raise ValueError("Invalid epsilon value: {}".format(eps))
- if not 0.0 <= betas[0] < 1.0:
- raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0]))
- if not 0.0 <= betas[1] < 1.0:
- raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1]))
- if not 0.0 <= weight_decay:
- raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
- defaults = dict(lr=lr, betas=betas, eps=eps,
- weight_decay=weight_decay, amsgrad=amsgrad)
- super(Adam, self).__init__(params, defaults)
-
- def __setstate__(self, state):
- super(Adam, self).__setstate__(state)
- for group in self.param_groups:
- group.setdefault('amsgrad', False)
-
- @torch.no_grad()
- def step(self, closure=None):
- """Performs a single optimization step.
- Arguments:
- closure (callable, optional): A closure that reevaluates the model
- and returns the loss.
- """
- loss = None
- if closure is not None:
- with torch.enable_grad():
- loss = closure()
-
- for group in self.param_groups:
- if group['freeze']: continue
-
- for p in group['params']:
- if p.grad is None:
- continue
- grad = p.grad
- if grad.is_sparse:
- raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead')
- amsgrad = group['amsgrad']
-
- state = self.state[p]
-
- # State initialization
- if len(state) == 0:
- state['step'] = 0
- # Exponential moving average of gradient values
- state['exp_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format)
- # Exponential moving average of squared gradient values
- state['exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
- if amsgrad:
- # Maintains max of all exp. moving avg. of sq. grad. values
- state['max_exp_avg_sq'] = torch.zeros_like(p, memory_format=torch.preserve_format)
-
- exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
- if amsgrad:
- max_exp_avg_sq = state['max_exp_avg_sq']
- beta1, beta2 = group['betas']
-
- state['step'] += 1
- bias_correction1 = 1 - beta1 ** state['step']
- bias_correction2 = 1 - beta2 ** state['step']
-
- if group['weight_decay'] != 0:
- grad = grad.add(p, alpha=group['weight_decay'])
-
- # Decay the first and second moment running average coefficient
- exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
- exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
- if amsgrad:
- # Maintains the maximum of all 2nd moment running avg. till now
- torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
- # Use the max. for normalizing running avg. of gradient
- denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])
- else:
- denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])
-
- step_size = group['lr'] / bias_correction1
-
- p.addcdiv_(exp_avg, denom, value=-step_size)
-
- return loss
diff --git a/fastreid/solver/optim/lamb.py b/fastreid/solver/optim/lamb.py
index 1650b74ed..4b54d74a7 100644
--- a/fastreid/solver/optim/lamb.py
+++ b/fastreid/solver/optim/lamb.py
@@ -68,7 +68,7 @@ def step(self, closure=None):
for group in self.param_groups:
for p in group['params']:
- if p.grad is None or group['freeze']:
+ if p.grad is None:
continue
grad = p.grad.data
if grad.is_sparse:
diff --git a/fastreid/solver/optim/sgd.py b/fastreid/solver/optim/sgd.py
deleted file mode 100644
index 2114856d2..000000000
--- a/fastreid/solver/optim/sgd.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import torch
-from torch.optim.optimizer import Optimizer, required
-
-
-class SGD(Optimizer):
- r"""Implements stochastic gradient descent (optionally with momentum).
- Nesterov momentum is based on the formula from
- `On the importance of initialization and momentum in deep learning`__.
- Args:
- params (iterable): iterable of parameters to optimize or dicts defining
- parameter groups
- lr (float): learning rate
- momentum (float, optional): momentum factor (default: 0)
- weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
- dampening (float, optional): dampening for momentum (default: 0)
- nesterov (bool, optional): enables Nesterov momentum (default: False)
- Example:
- >>> optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
- >>> optimizer.zero_grad()
- >>> loss_fn(model(input), target).backward()
- >>> optimizer.step()
- __ http://www.cs.toronto.edu/%7Ehinton/absps/momentum.pdf
- .. note::
- The implementation of SGD with Momentum/Nesterov subtly differs from
- Sutskever et. al. and implementations in some other frameworks.
- Considering the specific case of Momentum, the update can be written as
- .. math::
- \begin{aligned}
- v_{t+1} & = \mu * v_{t} + g_{t+1}, \\
- p_{t+1} & = p_{t} - \text{lr} * v_{t+1},
- \end{aligned}
- where :math:`p`, :math:`g`, :math:`v` and :math:`\mu` denote the
- parameters, gradient, velocity, and momentum respectively.
- This is in contrast to Sutskever et. al. and
- other frameworks which employ an update of the form
- .. math::
- \begin{aligned}
- v_{t+1} & = \mu * v_{t} + \text{lr} * g_{t+1}, \\
- p_{t+1} & = p_{t} - v_{t+1}.
- \end{aligned}
- The Nesterov version is analogously modified.
- """
-
- def __init__(self, params, lr=required, momentum=0, dampening=0,
- weight_decay=0, nesterov=False):
- if lr is not required and lr < 0.0:
- raise ValueError("Invalid learning rate: {}".format(lr))
- if momentum < 0.0:
- raise ValueError("Invalid momentum value: {}".format(momentum))
- if weight_decay < 0.0:
- raise ValueError("Invalid weight_decay value: {}".format(weight_decay))
-
- defaults = dict(lr=lr, momentum=momentum, dampening=dampening,
- weight_decay=weight_decay, nesterov=nesterov)
- if nesterov and (momentum <= 0 or dampening != 0):
- raise ValueError("Nesterov momentum requires a momentum and zero dampening")
- super(SGD, self).__init__(params, defaults)
-
- def __setstate__(self, state):
- super(SGD, self).__setstate__(state)
- for group in self.param_groups:
- group.setdefault('nesterov', False)
-
- @torch.no_grad()
- def step(self, closure=None):
- """Performs a single optimization step.
- Arguments:
- closure (callable, optional): A closure that reevaluates the model
- and returns the loss.
- """
- loss = None
- if closure is not None:
- with torch.enable_grad():
- loss = closure()
-
- for group in self.param_groups:
- if group['freeze']: continue
-
- weight_decay = group['weight_decay']
- momentum = group['momentum']
- dampening = group['dampening']
- nesterov = group['nesterov']
-
- for p in group['params']:
- if p.grad is None:
- continue
- d_p = p.grad
- if weight_decay != 0:
- d_p = d_p.add(p, alpha=weight_decay)
- if momentum != 0:
- param_state = self.state[p]
- if 'momentum_buffer' not in param_state:
- buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()
- else:
- buf = param_state['momentum_buffer']
- buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
- if nesterov:
- d_p = d_p.add(buf, alpha=momentum)
- else:
- d_p = buf
-
- p.add_(d_p, alpha=-group['lr'])
-
- return loss
diff --git a/fastreid/utils/checkpoint.py b/fastreid/utils/checkpoint.py
index 7c32b1a03..1f142d021 100644
--- a/fastreid/utils/checkpoint.py
+++ b/fastreid/utils/checkpoint.py
@@ -322,20 +322,21 @@ def step(self, epoch: int, **kwargs: Any):
additional_state = {"epoch": epoch}
additional_state.update(kwargs)
if (epoch + 1) % self.period == 0 and epoch < self.max_epoch - 1:
- self.checkpointer.save(
- "model_{:04d}".format(epoch), **additional_state
- )
if additional_state["metric"] > self.best_metric:
self.checkpointer.save(
"model_best", **additional_state
)
self.best_metric = additional_state["metric"]
+ # Put it behind best model save to make last checkpoint valid
+ self.checkpointer.save(
+ "model_{:04d}".format(epoch), **additional_state
+ )
if epoch >= self.max_epoch - 1:
- self.checkpointer.save("model_final", **additional_state)
if additional_state["metric"] > self.best_metric:
self.checkpointer.save(
"model_best", **additional_state
)
+ self.checkpointer.save("model_final", **additional_state)
def save(self, name: str, **kwargs: Any):
"""
diff --git a/fastreid/utils/weight_init.py b/fastreid/utils/weight_init.py
index 86e638635..348719216 100644
--- a/fastreid/utils/weight_init.py
+++ b/fastreid/utils/weight_init.py
@@ -4,7 +4,6 @@
@contact: sherlockliao01@gmail.com
"""
-import math
from torch import nn
__all__ = [
@@ -25,7 +24,6 @@ def weights_init_kaiming(m):
nn.init.constant_(m.bias, 0.0)
elif classname.find('BatchNorm') != -1:
if m.affine:
- # nn.init.normal_(m.weight, 1.0, 0.02)
nn.init.constant_(m.weight, 1.0)
nn.init.constant_(m.bias, 0.0)
diff --git a/projects/DistillReID/README.md b/projects/DistillReID/README.md
deleted file mode 100644
index 77ffb1054..000000000
--- a/projects/DistillReID/README.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# Model Distillation in FastReID
-
-This project provides a training script of small model
- for both fast inference and high accuracy.
-
-
-## Datasets Prepration
-- Market1501
-- DukeMTMC-reID
-- MSMT17
-
-
-## Train and Evaluation
-```shell script
-# a demo on DukeMTMC-reID dataset
-# please see more in ./configs
-# train BagTricksIBN50 as teacher model
-python3 projects/DistillReID/train_net.py --config-file projects/DistillReID/configs/DukeMTMC/bot50ibn.yml
-# train BagTricksIBN18 as student model
-python3 projects/DistillReID/train_net.py --config-file projects/DistillReID/configs/DukeMTMC/KD-bot50ibn-bot18ibn.yml --kd
-```
-
-## Experimental Results and Trained Models
-
-### Settings
-
-All the experiments are conducted with a P40 GPU and
-- CPU: Intel(R) Xeon(R) CPU E5-2683 v4 @ 2.10GHz
-- GPU:Tesla P40 (Memory 22919MB)
-
-### DukeMTMC-reID
-
-
Rank-1 (mAP) / Q.Time/batch(128) | Student (BagTricks) |
---|
IBN-101 | IBN-50 | IBN-34 | IBN-18 |
Teacher (BagTricks) | IBN-101 | 90.8(80.8)/0.3395s | 90.8(81.1)/0.1984s | 89.63(78.9)/0.1760s | 86.96(75.75)/0.0854s |
IBN-50 | - | 89.8(79.8)/0.2264s | 88.82(78.9)/0.1761s | 87.75(76.18)/0.0838s |
IBN-34 | - | - | 88.64(76.4)/0.1766s | 87.43(75.66)/0.0845s |
IBN-18 | - | - | - | 85.50(71.60)/0.9178s |
-
-### Market-1501
-
-Rank-1 (mAP) / Q.Time/batch(128) | Student (BagTricks) |
---|
IBN-101 | IBN-50 | IBN-34 | IBN-18 |
Teacher (BagTricks) | IBN-101 | 95.43(88.95)/0.2698s | 95.19(89.52)/0.1791s | 94.51(87.82)/0.0869s | 93.85(85.77)/0.0612s |
IBN-50 | - | 95.25(88.16)/0.1823s | 95.13(87.28)/0.0863s | 94.18(85.81)/0.0614s |
IBN-34 | | - | 94.63(84.91)/0.0860s | 93.71(85.20)/0.0620s |
IBN-18 | - | - | - | 92.87(81.22)/0.0615s |
Average Q.Time | 0.2698s | 0.1807s | 0.0864s | 0.0616s |
-
-### MSMT17
-
-Rank-1 (mAP) / Q.Time/batch(128) | Student (BagTricks) |
---|
IBN-101 | IBN-50 | IBN-34 | IBN-18 |
Teacher (BagTricks) | IBN-101 | 81.95(60.51)/0.2693s | 82.37(62.08)/0.1792s | 81.07(58.56)/0.0872s | 77.77(52.77)/0.0610s |
IBN-50 | - | 80.18(57.80)/0.1789s | 81.28(58.27)/0.0863s | 78.11(53.10)/0.0623s |
IBN-34 | | - | 78.27(53.41)/0.0873s | 77.65(52.82)/0.0615s |
IBN-18 | - | - | - | 74.11(47.26)/0.0621s |
Average Q.Time | 0.2693s | 0.1801s | 0.0868s | 0.0617s |
-
-
-## Contact
-This project is conducted by [Guan'an Wang](https://wangguanan.github.io/) (guan.wang0706@gmail) and [Xingyu Liao](https://github.com/L1aoXingyu).
-
-
diff --git a/projects/DistillReID/configs/Base-bot-kd.yml b/projects/DistillReID/configs/Base-bot-kd.yml
deleted file mode 100644
index b02dd2ec7..000000000
--- a/projects/DistillReID/configs/Base-bot-kd.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-_BASE_: "../../../configs/Base-bagtricks.yml"
-
-MODEL_TEACHER:
- META_ARCHITECTURE: "Baseline"
-
- BACKBONE:
- NAME: "build_resnet_backbone"
- NORM: "BN"
- DEPTH: "101x"
- FEAT_DIM: 2048
- LAST_STRIDE: 1
- WITH_IBN: True
- PRETRAIN: True
-
- HEADS:
- NAME: "EmbeddingHead"
- NORM: "BN"
- POOL_LAYER: "avgpool"
- NECK_FEAT: "before"
- CLS_LAYER: "linear"
-
-MODEL:
- BACKBONE:
- NAME: "build_resnet_backbone"
- DEPTH: "50x"
- FEAT_DIM: 2048
- WITH_IBN: True
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "logs/dukemtmc/bagtricks_R34-ibn/model_final.pth"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/Base-sbs-kd.yml b/projects/DistillReID/configs/Base-sbs-kd.yml
deleted file mode 100644
index ba4cd4819..000000000
--- a/projects/DistillReID/configs/Base-sbs-kd.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-_BASE_: "../../../configs/Base-Strongerbaseline.yml"
-
-MODEL_TEACHER:
- META_ARCHITECTURE: "Baseline"
-
- BACKBONE:
- NAME: "build_resnet_backbone"
- NORM: "BN"
- DEPTH: "101x"
- FEAT_DIM: 2048
- LAST_STRIDE: 1
- WITH_NL: False
- WITH_IBN: True
- PRETRAIN: True
-
- HEADS:
- NAME: "EmbeddingHead"
- NORM: "BN"
- NECK_FEAT: "after"
- POOL_LAYER: "gempoolP"
- CLS_LAYER: "circleSoftmax"
- SCALE: 64
- MARGIN: 0.35
-
-MODEL:
- BACKBONE:
- NAME: "build_resnet_backbone"
- DEPTH: "50x"
- FEAT_DIM: 2048
- WITH_IBN: True
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "logs/dukemtmc/bagtricks_R34-ibn/model_final.pth"
-
-INPUT:
- SIZE_TRAIN: [ 256, 128 ]
- SIZE_TEST: [ 256, 128 ]
diff --git a/projects/DistillReID/configs/DukeMTMC/KD-bot101ibn-bot18ibn.yml b/projects/DistillReID/configs/DukeMTMC/KD-bot101ibn-bot18ibn.yml
deleted file mode 100644
index 09c471d8f..000000000
--- a/projects/DistillReID/configs/DukeMTMC/KD-bot101ibn-bot18ibn.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-_BASE_: "../Base-bot-kd.yml"
-
-MODEL_TEACHER:
- BACKBONE:
- DEPTH: "101x"
- FEAT_DIM: 2048
-
-MODEL:
- BACKBONE:
- DEPTH: "18x"
- FEAT_DIM: 512
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "projects/DistillReID/logs/dukemtmc/bagtricks_R101-ibn"
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/bot101ibn-kd-bot18ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/KD-bot101ibn-bot50ibn.yml b/projects/DistillReID/configs/DukeMTMC/KD-bot101ibn-bot50ibn.yml
deleted file mode 100644
index 8ccbc4ae5..000000000
--- a/projects/DistillReID/configs/DukeMTMC/KD-bot101ibn-bot50ibn.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-_BASE_: "../Base-bot-kd.yml"
-
-MODEL_TEACHER:
- BACKBONE:
- DEPTH: "101x"
- FEAT_DIM: 2048
-
-MODEL:
- BACKBONE:
- DEPTH: "50x"
- FEAT_DIM: 2048
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "projects/DistillReID/logs/dukemtmc/bagtricks_R101-ibn"
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/dukemtmc/bot101ibn-kd-bot50ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/KD-bot50ibn-bot18ibn.yml b/projects/DistillReID/configs/DukeMTMC/KD-bot50ibn-bot18ibn.yml
deleted file mode 100644
index b7f9e2563..000000000
--- a/projects/DistillReID/configs/DukeMTMC/KD-bot50ibn-bot18ibn.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-_BASE_: "../Base-bot-kd.yml"
-
-MODEL_TEACHER:
- BACKBONE:
- DEPTH: "50x"
- FEAT_DIM: 2048
-
-MODEL:
- BACKBONE:
- DEPTH: "18x"
- FEAT_DIM: 512
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "projects/DistillReID/logs/dukemtmc/bagtricks_R50-ibn/model_final.pth"
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/bot50ibn-kd-bot18ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/KD-sbs101ibn-sbs18ibn.yml b/projects/DistillReID/configs/DukeMTMC/KD-sbs101ibn-sbs18ibn.yml
deleted file mode 100644
index 849b861c2..000000000
--- a/projects/DistillReID/configs/DukeMTMC/KD-sbs101ibn-sbs18ibn.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-_BASE_: "../Base-sbs-kd.yml"
-
-MODEL_TEACHER:
- BACKBONE:
- DEPTH: "101x"
- FEAT_DIM: 2048
-
-MODEL:
- BACKBONE:
- DEPTH: "34x"
- FEAT_DIM: 512
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "projects/DistillReID/logs/dukemtmc/sbs_R101-ibn/model_final.pth"
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/sbs101ibn-kd-sbs18ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/KD-sbs101ibn-sbs50ibn.yml b/projects/DistillReID/configs/DukeMTMC/KD-sbs101ibn-sbs50ibn.yml
deleted file mode 100644
index 01938aa55..000000000
--- a/projects/DistillReID/configs/DukeMTMC/KD-sbs101ibn-sbs50ibn.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-_BASE_: "../Base-sbs-kd.yml"
-
-MODEL_TEACHER:
- BACKBONE:
- DEPTH: "101x"
- FEAT_DIM: 2048
-
-MODEL:
- BACKBONE:
- DEPTH: "50x"
- FEAT_DIM: 2048
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "projects/DistillReID/logs/dukemtmc/sbs_R101-ibn/model_final.pth"
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/sbs101ibn-kd-sbs50ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/KD-sbs50ibn-sbs18ibn.yml b/projects/DistillReID/configs/DukeMTMC/KD-sbs50ibn-sbs18ibn.yml
deleted file mode 100644
index 2c73527ff..000000000
--- a/projects/DistillReID/configs/DukeMTMC/KD-sbs50ibn-sbs18ibn.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-_BASE_: "../Base-sbs-kd.yml"
-
-MODEL_TEACHER:
- BACKBONE:
- DEPTH: "50x"
- FEAT_DIM: 2048
-
-MODEL:
- BACKBONE:
- DEPTH: "18x"
- FEAT_DIM: 512
-
- STUDENT_WEIGHTS: ""
- TEACHER_WEIGHTS: "projects/DistillReID/logs/dukemtmc/sbs_R50-ibn/model_final.pth"
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/sbs50ibn-kd-sbs18ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/bot101ibn.yml b/projects/DistillReID/configs/DukeMTMC/bot101ibn.yml
deleted file mode 100644
index 4fccde20d..000000000
--- a/projects/DistillReID/configs/DukeMTMC/bot101ibn.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-_BASE_: "../../../../configs/Base-bagtricks.yml"
-
-MODEL:
- BACKBONE:
- DEPTH: "101x"
- WITH_IBN: True
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/bagtricks_R101-ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/bot18ibn.yml b/projects/DistillReID/configs/DukeMTMC/bot18ibn.yml
deleted file mode 100644
index 797630455..000000000
--- a/projects/DistillReID/configs/DukeMTMC/bot18ibn.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-_BASE_: "../../../../configs/Base-bagtricks.yml"
-
-MODEL:
- BACKBONE:
- DEPTH: "18x"
- WITH_IBN: True
- FEAT_DIM: 512
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/bagtricks_R18-ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/bot50ibn.yml b/projects/DistillReID/configs/DukeMTMC/bot50ibn.yml
deleted file mode 100644
index dbac55512..000000000
--- a/projects/DistillReID/configs/DukeMTMC/bot50ibn.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-_BASE_: "../../../../configs/Base-bagtricks.yml"
-
-MODEL:
- BACKBONE:
- DEPTH: "50x"
- WITH_IBN: True
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/bagtricks_R50-ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/sbs101ibn.yml b/projects/DistillReID/configs/DukeMTMC/sbs101ibn.yml
deleted file mode 100644
index c06d62c6f..000000000
--- a/projects/DistillReID/configs/DukeMTMC/sbs101ibn.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-_BASE_: "../../../configs/Base-Strongerbaseline.yml"
-
-MODEL:
- BACKBONE:
- DEPTH: "101x"
- WITH_IBN: True
- FEAT_DIM: 2048
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/sbs_R101-ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/sbs18ibn.yml b/projects/DistillReID/configs/DukeMTMC/sbs18ibn.yml
deleted file mode 100644
index 27e8117c4..000000000
--- a/projects/DistillReID/configs/DukeMTMC/sbs18ibn.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-_BASE_: "../../../configs/Base-Strongerbaseline.yml"
-
-MODEL:
- BACKBONE:
- DEPTH: "18x"
- WITH_IBN: True
- FEAT_DIM: 512
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/sbs_R18-ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/configs/DukeMTMC/sbs50ibn.yml b/projects/DistillReID/configs/DukeMTMC/sbs50ibn.yml
deleted file mode 100644
index da4d8d5a8..000000000
--- a/projects/DistillReID/configs/DukeMTMC/sbs50ibn.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-_BASE_: "../../../configs/Base-Strongerbaseline.yml"
-
-MODEL:
- BACKBONE:
- DEPTH: "50x"
- WITH_IBN: True
- FEAT_DIM: 2048
-
-DATASETS:
- NAMES: ("DukeMTMC",)
- TESTS: ("DukeMTMC",)
-
-OUTPUT_DIR: "projects/DistillReID/logs/dukemtmc/sbs_R50-ibn"
\ No newline at end of file
diff --git a/projects/DistillReID/kdreid/__init__.py b/projects/DistillReID/kdreid/__init__.py
deleted file mode 100644
index 53e68678f..000000000
--- a/projects/DistillReID/kdreid/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# encoding: utf-8
-"""
-@author: l1aoxingyu
-@contact: sherlockliao01@gmail.com
-"""
-
-from .config import add_kdreid_config, add_shufflenet_config
-from .kd_trainer import KDTrainer
-from .modeling import build_shufflenetv2_backbone
\ No newline at end of file
diff --git a/projects/DistillReID/kdreid/config.py b/projects/DistillReID/kdreid/config.py
deleted file mode 100644
index 70abc3b41..000000000
--- a/projects/DistillReID/kdreid/config.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# encoding: utf-8
-"""
-@author: l1aoxingyu, guan'an wang
-@contact: sherlockliao01@gmail.com, guan.wang0706@gmail.com
-"""
-
-from fastreid.config import CfgNode as CN
-
-
-def add_shufflenet_config(cfg):
- _C = cfg
- _C.MODEL.BACKBONE.MODEL_SIZE = '1.0x'
-
-
-def add_kdreid_config(cfg):
- _C = cfg
-
- _C.MODEL_TEACHER = CN()
- _C.MODEL_TEACHER.META_ARCHITECTURE = 'Baseline'
-
- # ---------------------------------------------------------------------------- #
- # teacher model Backbone options
- # ---------------------------------------------------------------------------- #
- _C.MODEL_TEACHER.BACKBONE = CN()
-
- _C.MODEL_TEACHER.BACKBONE.NAME = "build_resnet_backbone"
- _C.MODEL_TEACHER.BACKBONE.DEPTH = "50x"
- _C.MODEL_TEACHER.BACKBONE.LAST_STRIDE = 1
- # If use IBN block in backbone
- _C.MODEL_TEACHER.BACKBONE.WITH_IBN = False
- # If use SE block in backbone
- _C.MODEL_TEACHER.BACKBONE.WITH_SE = False
- # If use Non-local block in backbone
- _C.MODEL_TEACHER.BACKBONE.WITH_NL = False
- # Input feature dimension
- _C.MODEL_TEACHER.BACKBONE.FEAT_DIM = 2048
-
- # for shufflenet
- _C.MODEL_TEACHER.BACKBONE.MODEL_SIZE = '1.0x'
-
- #
- _C.MODEL_TEACHER.BACKBONE.NORM = 'BN'
- _C.MODEL_TEACHER.BACKBONE.PRETRAIN = False
-
- # ---------------------------------------------------------------------------- #
- # teacher model HEADS options
- # ---------------------------------------------------------------------------- #
- _C.MODEL_TEACHER.HEADS = CN()
- _C.MODEL_TEACHER.HEADS.NAME = "EmbeddingHead"
-
- # Pooling layer type
- _C.MODEL_TEACHER.HEADS.POOL_LAYER = "avgpool"
- _C.MODEL_TEACHER.HEADS.NECK_FEAT = "before"
- _C.MODEL_TEACHER.HEADS.CLS_LAYER = "linear"
-
- # Pretrained teacher and student model weights
- _C.MODEL.TEACHER_WEIGHTS = ""
- _C.MODEL.STUDENT_WEIGHTS = ""
-
- #
- _C.MODEL_TEACHER.HEADS.NORM = 'BN'
- _C.MODEL_TEACHER.HEADS.SCALE = 64
- _C.MODEL_TEACHER.HEADS.MARGIN = 0.35
-
-
-def update_model_teacher_config(cfg):
- cfg = cfg.clone()
-
- frozen = cfg.is_frozen()
-
- cfg.defrost()
- cfg.MODEL.META_ARCHITECTURE = cfg.MODEL_TEACHER.META_ARCHITECTURE
- # ---------------------------------------------------------------------------- #
- # teacher model Backbone options
- # ---------------------------------------------------------------------------- #
- cfg.MODEL.BACKBONE.NAME = cfg.MODEL_TEACHER.BACKBONE.NAME
- cfg.MODEL.BACKBONE.DEPTH = cfg.MODEL_TEACHER.BACKBONE.DEPTH
- cfg.MODEL.BACKBONE.LAST_STRIDE = cfg.MODEL_TEACHER.BACKBONE.LAST_STRIDE
- # If use IBN block in backbone
- cfg.MODEL.BACKBONE.WITH_IBN = cfg.MODEL_TEACHER.BACKBONE.WITH_IBN
- # If use SE block in backbone
- cfg.MODEL.BACKBONE.WITH_SE = cfg.MODEL_TEACHER.BACKBONE.WITH_SE
- # If use Non-local block in backbone
- cfg.MODEL.BACKBONE.WITH_NL = cfg.MODEL_TEACHER.BACKBONE.WITH_NL
- # Input feature dimension
- cfg.MODEL.BACKBONE.FEAT_DIM = cfg.MODEL_TEACHER.BACKBONE.FEAT_DIM
- cfg.MODEL.BACKBONE.PRETRAIN = False
-
- # for shufflenet
- cfg.MODEL.BACKBONE.MODEL_SIZE = cfg.MODEL_TEACHER.BACKBONE.MODEL_SIZE
-
- # ---------------------------------------------------------------------------- #
- # teacher model HEADS options
- # ---------------------------------------------------------------------------- #
- cfg.MODEL.HEADS.NAME = cfg.MODEL_TEACHER.HEADS.NAME
-
- # Pooling layer type
- cfg.MODEL.HEADS.POOL_LAYER = cfg.MODEL_TEACHER.HEADS.POOL_LAYER
-
- cfg.MODEL.HEADS.SCALE = cfg.MODEL_TEACHER.HEADS.SCALE
- cfg.MODEL.HEADS.MARGIN = cfg.MODEL_TEACHER.HEADS.MARGIN
-
- if frozen: cfg.freeze()
-
- return cfg
\ No newline at end of file
diff --git a/projects/DistillReID/kdreid/kd_trainer.py b/projects/DistillReID/kdreid/kd_trainer.py
deleted file mode 100644
index 125f5418e..000000000
--- a/projects/DistillReID/kdreid/kd_trainer.py
+++ /dev/null
@@ -1,139 +0,0 @@
-# encoding: utf-8
-"""
-@author: l1aoxingyu
-@contact: sherlockliao01@gmail.com
-"""
-
-import logging
-import time
-
-import torch
-import torch.nn.functional as F
-from torch import nn
-from torch.nn.parallel import DistributedDataParallel
-
-from fastreid.engine import DefaultTrainer
-from fastreid.utils.file_io import PathManager
-from fastreid.modeling.meta_arch import build_model
-from fastreid.utils.checkpoint import Checkpointer
-from .config import update_model_teacher_config
-
-
-class KDTrainer(DefaultTrainer):
- """
- A knowledge distillation trainer for person reid of task.
- """
-
- def __init__(self, cfg):
- """
- Args:
- cfg (CfgNode):
- """
- super().__init__(cfg)
-
- model_t = self.build_model_teacher(self.cfg)
- for param in model_t.parameters():
- param.requires_grad = False
-
- logger = logging.getLogger('fastreid.' + __name__)
-
- # Load pre-trained teacher model
- logger.info("Loading teacher model ...")
- Checkpointer(model_t).load(cfg.MODEL.TEACHER_WEIGHTS)
-
- if PathManager.exists(cfg.MODEL.STUDENT_WEIGHTS):
- logger.info("Loading student model ...")
- Checkpointer(self.model).load(cfg.MODEL.STUDENT_WEIGHTS)
- else:
- logger.info("No student model checkpoints")
-
- self.model_t = model_t
-
- def run_step(self):
- """
- Implement the moco training logic described above.
- """
- assert self.model.training, "[KDTrainer] base model was changed to eval mode!"
- start = time.perf_counter()
- """
- If your want to do something with the data, you can wrap the dataloader.
- """
- data = next(self._data_loader_iter)
-
- data_time = time.perf_counter() - start
-
- outs = self.model(data)
-
- # Compute reid loss
- if isinstance(self.model, DistributedDataParallel):
- loss_dict = self.model.module.losses(outs)
- else:
- loss_dict = self.model.losses(outs)
-
- with torch.no_grad():
- outs_t = self.model_t(data)
-
- q_logits = outs["outputs"]["pred_class_logits"]
- t_logits = outs_t["outputs"]["pred_class_logits"].detach()
- loss_dict['loss_kl'] = self.distill_loss(q_logits, t_logits, t=16)
-
- losses = sum(loss_dict.values())
-
- with torch.cuda.stream(torch.cuda.Stream()):
- metrics_dict = loss_dict
- metrics_dict["data_time"] = data_time
- self._write_metrics(metrics_dict)
- self._detect_anomaly(losses, loss_dict)
-
- """
- If you need accumulate gradients or something similar, you can
- wrap the optimizer with your custom `zero_grad()` method.
- """
- self.optimizer.zero_grad()
- losses.backward()
-
- """
- If you need gradient clipping/scaling or other processing, you can
- wrap the optimizer with your custom `step()` method.
- """
- self.optimizer.step()
-
- @classmethod
- def build_model_teacher(cls, cfg) -> nn.Module:
- cfg_t = update_model_teacher_config(cfg)
- model_t = build_model(cfg_t)
- return model_t
-
- @staticmethod
- def pkt_loss(output_net, target_net, eps=0.0000001):
- # Normalize each vector by its norm
- output_net_norm = torch.sqrt(torch.sum(output_net ** 2, dim=1, keepdim=True))
- output_net = output_net / (output_net_norm + eps)
- output_net[output_net != output_net] = 0
-
- target_net_norm = torch.sqrt(torch.sum(target_net ** 2, dim=1, keepdim=True))
- target_net = target_net / (target_net_norm + eps)
- target_net[target_net != target_net] = 0
-
- # Calculate the cosine similarity
- model_similarity = torch.mm(output_net, output_net.transpose(0, 1))
- target_similarity = torch.mm(target_net, target_net.transpose(0, 1))
-
- # Scale cosine similarity to 0..1
- model_similarity = (model_similarity + 1.0) / 2.0
- target_similarity = (target_similarity + 1.0) / 2.0
-
- # Transform them into probabilities
- model_similarity = model_similarity / torch.sum(model_similarity, dim=1, keepdim=True)
- target_similarity = target_similarity / torch.sum(target_similarity, dim=1, keepdim=True)
-
- # Calculate the KL-divergence
- loss = torch.mean(target_similarity * torch.log((target_similarity + eps) / (model_similarity + eps)))
- return loss
-
- @staticmethod
- def distill_loss(y_s, y_t, t=4):
- p_s = F.log_softmax(y_s / t, dim=1)
- p_t = F.softmax(y_t / t, dim=1)
- loss = F.kl_div(p_s, p_t, reduction='sum') * (t ** 2) / y_s.shape[0]
- return loss
diff --git a/projects/DistillReID/kdreid/modeling/__init__.py b/projects/DistillReID/kdreid/modeling/__init__.py
deleted file mode 100644
index 417ac447c..000000000
--- a/projects/DistillReID/kdreid/modeling/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .backbones import build_shufflenetv2_backbone
\ No newline at end of file
diff --git a/projects/DistillReID/kdreid/modeling/backbones/__init__.py b/projects/DistillReID/kdreid/modeling/backbones/__init__.py
deleted file mode 100644
index 42828a282..000000000
--- a/projects/DistillReID/kdreid/modeling/backbones/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from .shufflenetv2 import build_shufflenetv2_backbone
\ No newline at end of file
diff --git a/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/__init__.py b/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/__init__.py
deleted file mode 100644
index 4d302a047..000000000
--- a/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/__init__.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import torch
-import torch.nn as nn
-from collections import OrderedDict
-
-from fastreid.modeling.backbones.build import BACKBONE_REGISTRY
-from .network import ShuffleNetV2
-
-
-__all__ = ['build_shufflenetv2_backbone']
-
-
-@BACKBONE_REGISTRY.register()
-def build_shufflenetv2_backbone(cfg):
-
- pretrain = cfg.MODEL.BACKBONE.PRETRAIN
- pretrain_path = cfg.MODEL.BACKBONE.PRETRAIN_PATH
- model_size = cfg.MODEL.BACKBONE.MODEL_SIZE
-
- return ShuffleNetV2Backbone(model_size=model_size, pretrained=pretrain, pretrain_path=pretrain_path)
-
-
-class ShuffleNetV2Backbone(nn.Module):
-
- def __init__(self, model_size, pretrained=False, pretrain_path=''):
- super(ShuffleNetV2Backbone, self).__init__()
-
- model = ShuffleNetV2(model_size=model_size)
- if pretrained:
- new_state_dict = OrderedDict()
- state_dict = torch.load(pretrain_path)['state_dict']
- for k, v in state_dict.items():
- if k[:7] == 'module.':
- k = k[7:]
- new_state_dict[k] = v
- model.load_state_dict(new_state_dict, strict=True)
-
- self.backbone = nn.Sequential(
- model.first_conv, model.maxpool, model.features, model.conv_last)
-
- def forward(self, x):
- return self.backbone(x)
-
-
diff --git a/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/blocks.py b/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/blocks.py
deleted file mode 100644
index c114428a5..000000000
--- a/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/blocks.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""
-Author: Guan'an Wang
-Contact: guan.wang0706@gmail.com
-"""
-
-import torch
-import torch.nn as nn
-
-class ShuffleV2Block(nn.Module):
- """
- Reference:
- https://github.com/megvii-model/ShuffleNet-Series/tree/master/ShuffleNetV2
- """
- def __init__(self, inp, oup, mid_channels, *, ksize, stride):
- super(ShuffleV2Block, self).__init__()
- self.stride = stride
- assert stride in [1, 2]
-
- self.mid_channels = mid_channels
- self.ksize = ksize
- pad = ksize // 2
- self.pad = pad
- self.inp = inp
-
- outputs = oup - inp
-
- branch_main = [
- # pw
- nn.Conv2d(inp, mid_channels, 1, 1, 0, bias=False),
- nn.BatchNorm2d(mid_channels),
- nn.ReLU(inplace=True),
- # dw
- nn.Conv2d(mid_channels, mid_channels, ksize, stride, pad, groups=mid_channels, bias=False),
- nn.BatchNorm2d(mid_channels),
- # pw-linear
- nn.Conv2d(mid_channels, outputs, 1, 1, 0, bias=False),
- nn.BatchNorm2d(outputs),
- nn.ReLU(inplace=True),
- ]
- self.branch_main = nn.Sequential(*branch_main)
-
- if stride == 2:
- branch_proj = [
- # dw
- nn.Conv2d(inp, inp, ksize, stride, pad, groups=inp, bias=False),
- nn.BatchNorm2d(inp),
- # pw-linear
- nn.Conv2d(inp, inp, 1, 1, 0, bias=False),
- nn.BatchNorm2d(inp),
- nn.ReLU(inplace=True),
- ]
- self.branch_proj = nn.Sequential(*branch_proj)
- else:
- self.branch_proj = None
-
- def forward(self, old_x):
- if self.stride==1:
- x_proj, x = self.channel_shuffle(old_x)
- return torch.cat((x_proj, self.branch_main(x)), 1)
- elif self.stride==2:
- x_proj = old_x
- x = old_x
- return torch.cat((self.branch_proj(x_proj), self.branch_main(x)), 1)
-
- def channel_shuffle(self, x):
- batchsize, num_channels, height, width = x.data.size()
- assert (num_channels % 4 == 0)
- x = x.reshape(batchsize * num_channels // 2, 2, height * width)
- x = x.permute(1, 0, 2)
- x = x.reshape(2, -1, num_channels // 2, height, width)
- return x[0], x[1]
\ No newline at end of file
diff --git a/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/network.py b/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/network.py
deleted file mode 100644
index 54fe0eaaf..000000000
--- a/projects/DistillReID/kdreid/modeling/backbones/shufflenetv2/network.py
+++ /dev/null
@@ -1,116 +0,0 @@
-"""
-Author: Guan'an Wang
-Contact: guan.wang0706@gmail.com
-"""
-
-import torch
-import torch.nn as nn
-from .blocks import ShuffleV2Block
-
-
-class ShuffleNetV2(nn.Module):
- """
- Reference:
- https://github.com/megvii-model/ShuffleNet-Series/tree/master/ShuffleNetV2
- """
-
- def __init__(self, input_size=224, n_class=1000, model_size='1.5x'):
- super(ShuffleNetV2, self).__init__()
- print('model size is ', model_size)
-
- self.stage_repeats = [4, 8, 4]
- self.model_size = model_size
- if model_size == '0.5x':
- self.stage_out_channels = [-1, 24, 48, 96, 192, 1024]
- elif model_size == '1.0x':
- self.stage_out_channels = [-1, 24, 116, 232, 464, 1024]
- elif model_size == '1.5x':
- self.stage_out_channels = [-1, 24, 176, 352, 704, 1024]
- elif model_size == '2.0x':
- self.stage_out_channels = [-1, 24, 244, 488, 976, 2048]
- else:
- raise NotImplementedError
-
- # building first layer
- input_channel = self.stage_out_channels[1]
- self.first_conv = nn.Sequential(
- nn.Conv2d(3, input_channel, 3, 2, 1, bias=False),
- nn.BatchNorm2d(input_channel),
- nn.ReLU(inplace=True),
- )
-
- self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
-
- self.features = []
- for idxstage in range(len(self.stage_repeats)):
- numrepeat = self.stage_repeats[idxstage]
- output_channel = self.stage_out_channels[idxstage + 2]
-
- for i in range(numrepeat):
- if i == 0:
- self.features.append(ShuffleV2Block(input_channel, output_channel,
- mid_channels=output_channel // 2, ksize=3, stride=2))
- else:
- self.features.append(ShuffleV2Block(input_channel // 2, output_channel,
- mid_channels=output_channel // 2, ksize=3, stride=1))
-
- input_channel = output_channel
-
- self.features = nn.Sequential(*self.features)
-
- self.conv_last = nn.Sequential(
- nn.Conv2d(input_channel, self.stage_out_channels[-1], 1, 1, 0, bias=False),
- nn.BatchNorm2d(self.stage_out_channels[-1]),
- nn.ReLU(inplace=True)
- )
- self.globalpool = nn.AvgPool2d(7)
- if self.model_size == '2.0x':
- self.dropout = nn.Dropout(0.2)
- self.classifier = nn.Sequential(nn.Linear(self.stage_out_channels[-1], n_class, bias=False))
- self._initialize_weights()
-
- def forward(self, x):
- x = self.first_conv(x)
- x = self.maxpool(x)
- x = self.features(x)
- x = self.conv_last(x)
-
- x = self.globalpool(x)
- if self.model_size == '2.0x':
- x = self.dropout(x)
- x = x.contiguous().view(-1, self.stage_out_channels[-1])
- x = self.classifier(x)
- return x
-
- def _initialize_weights(self):
- for name, m in self.named_modules():
- if isinstance(m, nn.Conv2d):
- if 'first' in name:
- nn.init.normal_(m.weight, 0, 0.01)
- else:
- nn.init.normal_(m.weight, 0, 1.0 / m.weight.shape[1])
- if m.bias is not None:
- nn.init.constant_(m.bias, 0)
- elif isinstance(m, nn.BatchNorm2d):
- nn.init.constant_(m.weight, 1)
- if m.bias is not None:
- nn.init.constant_(m.bias, 0.0001)
- nn.init.constant_(m.running_mean, 0)
- elif isinstance(m, nn.BatchNorm1d):
- nn.init.constant_(m.weight, 1)
- if m.bias is not None:
- nn.init.constant_(m.bias, 0.0001)
- nn.init.constant_(m.running_mean, 0)
- elif isinstance(m, nn.Linear):
- nn.init.normal_(m.weight, 0, 0.01)
- if m.bias is not None:
- nn.init.constant_(m.bias, 0)
-
-
-if __name__ == "__main__":
- model = ShuffleNetV2()
- # print(model)
-
- test_data = torch.rand(5, 3, 224, 224)
- test_outputs = model(test_data)
- print(test_outputs.size())
diff --git a/projects/FastAttr/README.md b/projects/FastAttr/README.md
new file mode 100644
index 000000000..1a405b1c4
--- /dev/null
+++ b/projects/FastAttr/README.md
@@ -0,0 +1,31 @@
+# FastAttr in FastReID
+
+This project provides a strong baseline for pedestrian attribute recognition.
+
+## Datasets Preparation
+
+We use `PA100k` to evaluate the model's performance.
+You can do download dataset from [HydraPlus-Net](https://github.com/xh-liu/HydraPlus-Net).
+
+## Usage
+
+The training config file can be found in `projects/FastAttr/config`, which you can use to reproduce the results of the repo.
+
+For example
+
+```bash
+python3 projects/FastAttr/train_net.py --config-file projects/FastAttr/configs/pa100.yml --num-gpus 4
+```
+
+## Experiment Results
+
+We refer to [A Strong Baseline of Pedestrian Attribute Recognition](https://github.com/valencebond/Strong_Baseline_of_Pedestrian_Attribute_Recognition/tree/master) as our baseline methods and conduct the experiment
+with 4 GPUs.
+More details can be found in the config file and code.
+
+### PA100k
+
+| Method | Pretrained | mA | Accu | Prec | Recall | F1 |
+| :---: | :---: | :---: |:---: | :---: | :---: | :---: |
+| attribute baseline | ImageNet | 80.50 | 78.84 | 87.24 | 87.12 | 86.78 |
+| FastAttr | ImageNet | 77.57 | 78.03 | 88.39 | 84.98 | 86.65 |
diff --git a/projects/attribute_recognition/configs/Base-attribute.yml b/projects/FastAttr/configs/Base-attribute.yml
similarity index 51%
rename from projects/attribute_recognition/configs/Base-attribute.yml
rename to projects/FastAttr/configs/Base-attribute.yml
index ba6da2792..33eb45246 100644
--- a/projects/attribute_recognition/configs/Base-attribute.yml
+++ b/projects/FastAttr/configs/Base-attribute.yml
@@ -1,22 +1,21 @@
MODEL:
- META_ARCHITECTURE: "AttrBaseline"
+ META_ARCHITECTURE: AttrBaseline
BACKBONE:
- NAME: "build_resnet_backbone"
- NORM: "BN"
- DEPTH: "50x"
+ NAME: build_resnet_backbone
+ NORM: BN
+ DEPTH: 50x
LAST_STRIDE: 2
FEAT_DIM: 2048
WITH_IBN: False
PRETRAIN: True
- PRETRAIN_PATH: "/export/home/lxy/.cache/torch/checkpoints/resnet50-19c8e357.pth"
+ PRETRAIN_PATH: /export/home/lxy/.cache/torch/checkpoints/resnet50-19c8e357.pth
HEADS:
- NAME: "AttrHead"
- NORM: "BN"
+ NAME: AttrHead
WITH_BNNECK: True
- POOL_LAYER: "fastavgpool"
- CLS_LAYER: "linear"
+ POOL_LAYER: fastavgpool
+ CLS_LAYER: linear
NUM_CLASSES: 26
LOSSES:
@@ -27,8 +26,8 @@ MODEL:
SCALE: 1.
INPUT:
- SIZE_TRAIN: [256, 128]
- SIZE_TEST: [256, 128]
+ SIZE_TRAIN: [ 256, 192 ]
+ SIZE_TEST: [ 256, 192 ]
REA:
ENABLED: False
DO_PAD: True
@@ -37,21 +36,21 @@ DATALOADER:
NUM_WORKERS: 8
SOLVER:
- OPT: "SGD"
- MAX_ITER: 30
- BASE_LR: 0.01
+ MAX_EPOCH: 30
+ OPT: SGD
+ BASE_LR: 0.04
BIAS_LR_FACTOR: 2.
HEADS_LR_FACTOR: 10.
WEIGHT_DECAY: 0.0005
WEIGHT_DECAY_BIAS: 0.0005
- IMS_PER_BATCH: 64
+ IMS_PER_BATCH: 256
- SCHED: "WarmupCosineAnnealingLR"
- DELAY_ITERS: 5
- ETA_MIN_LR: 0.00001
+ NESTEROV: False
+ SCHED: MultiStepLR
+ STEPS: [ 15, 20, 25 ]
- WARMUP_FACTOR: 0.01
- WARMUP_ITERS: 5
+ WARMUP_FACTOR: 0.1
+ WARMUP_EPOCHS: 0
CHECKPOINT_PERIOD: 10
diff --git a/projects/FastAttr/configs/pa100.yml b/projects/FastAttr/configs/pa100.yml
new file mode 100644
index 000000000..c2c3eaeaf
--- /dev/null
+++ b/projects/FastAttr/configs/pa100.yml
@@ -0,0 +1,7 @@
+_BASE_: Base-attribute.yml
+
+DATASETS:
+ NAMES: ("PA100K",)
+ TESTS: ("PA100K",)
+
+OUTPUT_DIR: projects/FastAttr/logs/pa100k/strong_baseline
\ No newline at end of file
diff --git a/projects/attribute_recognition/attribute_baseline/__init__.py b/projects/FastAttr/fastattr/__init__.py
similarity index 88%
rename from projects/attribute_recognition/attribute_baseline/__init__.py
rename to projects/FastAttr/fastattr/__init__.py
index a769ede46..93dabc55a 100644
--- a/projects/attribute_recognition/attribute_baseline/__init__.py
+++ b/projects/FastAttr/fastattr/__init__.py
@@ -4,9 +4,9 @@
@contact: sherlockliao01@gmail.com
"""
-from .config import add_attr_config
-from .datasets import *
from .attr_baseline import AttrBaseline
from .attr_evaluation import AttrEvaluator
+from .attr_head import AttrHead
+from .config import add_attr_config
from .data_build import build_attr_train_loader, build_attr_test_loader
-from .attr_trainer import AttrTrainer
+from .datasets import *
diff --git a/projects/attribute_recognition/attribute_baseline/attr_baseline.py b/projects/FastAttr/fastattr/attr_baseline.py
similarity index 64%
rename from projects/attribute_recognition/attribute_baseline/attr_baseline.py
rename to projects/FastAttr/fastattr/attr_baseline.py
index 1961f6800..ae1435655 100644
--- a/projects/attribute_recognition/attribute_baseline/attr_baseline.py
+++ b/projects/FastAttr/fastattr/attr_baseline.py
@@ -11,31 +11,30 @@
@META_ARCH_REGISTRY.register()
class AttrBaseline(Baseline):
-
- def losses(self, outs, sample_weight=None):
+ def __init__(self, cfg, sample_weights):
+ super(AttrBaseline, self).__init__(cfg)
+ bce_weight_enabled = cfg.MODEL.LOSSES.BCE.WEIGHT_ENABLED
+ if bce_weight_enabled:
+ self.register_buffer("sample_weight", sample_weights)
+ else:
+ self.sample_weights = None
+
+ def losses(self, outputs, gt_labels):
r"""
Compute loss from modeling's outputs, the loss function input arguments
must be the same as the outputs of the model forwarding.
"""
- # fmt: off
- outputs = outs["outputs"]
- gt_labels = outs["targets"]
# model predictions
- # pred_class_logits = outputs['pred_class_logits'].detach()
cls_outputs = outputs['cls_outputs']
- # fmt: on
-
- # Log prediction accuracy
- # log_accuracy(pred_class_logits, gt_labels)
loss_dict = {}
loss_names = self._cfg.MODEL.LOSSES.NAME
if "BinaryCrossEntropyLoss" in loss_names:
- loss_dict['loss_bce'] = cross_entropy_sigmoid_loss(
+ loss_dict["loss_bce"] = cross_entropy_sigmoid_loss(
cls_outputs,
gt_labels,
- sample_weight,
+ self.sample_weight,
) * self._cfg.MODEL.LOSSES.BCE.SCALE
return loss_dict
diff --git a/projects/attribute_recognition/attribute_baseline/common_attr.py b/projects/FastAttr/fastattr/attr_dataset.py
similarity index 91%
rename from projects/attribute_recognition/attribute_baseline/common_attr.py
rename to projects/FastAttr/fastattr/attr_dataset.py
index 896ec551c..0d78a46ea 100644
--- a/projects/attribute_recognition/attribute_baseline/common_attr.py
+++ b/projects/FastAttr/fastattr/attr_dataset.py
@@ -26,7 +26,7 @@ def __getitem__(self, index):
img = read_image(img_path)
if self.transform is not None: img = self.transform(img)
- labels = torch.from_numpy(labels)
+ labels = torch.as_tensor(labels)
return {
"images": img,
@@ -40,8 +40,8 @@ def num_classes(self):
@property
def sample_weights(self):
- sample_weights = torch.zeros(self.num_classes, dtype=torch.float)
+ sample_weights = torch.zeros(self.num_classes, dtype=torch.float32)
for _, attr in self.img_items:
- sample_weights += torch.from_numpy(attr)
+ sample_weights += torch.as_tensor(attr)
sample_weights /= len(self)
return sample_weights
diff --git a/projects/attribute_recognition/attribute_baseline/attr_evaluation.py b/projects/FastAttr/fastattr/attr_evaluation.py
similarity index 92%
rename from projects/attribute_recognition/attribute_baseline/attr_evaluation.py
rename to projects/FastAttr/fastattr/attr_evaluation.py
index d10228434..3ab724757 100644
--- a/projects/attribute_recognition/attribute_baseline/attr_evaluation.py
+++ b/projects/FastAttr/fastattr/attr_evaluation.py
@@ -64,11 +64,12 @@ def get_attr_metrics(gt_labels, pred_logits, thres):
label_mA = label_mA_verbose.mean()
results = OrderedDict()
- results["Accu"] = ins_acc
- results["Prec"] = ins_prec
- results["Recall"] = ins_rec
- results["F1"] = ins_f1
- results["mA"] = label_mA
+ results["Accu"] = ins_acc * 100
+ results["Prec"] = ins_prec * 100
+ results["Recall"] = ins_rec * 100
+ results["F1"] = ins_f1 * 100
+ results["mA"] = label_mA * 100
+ results["metric"] = label_mA * 100
return results
def evaluate(self):
diff --git a/fastreid/modeling/heads/attr_head.py b/projects/FastAttr/fastattr/attr_head.py
similarity index 87%
rename from fastreid/modeling/heads/attr_head.py
rename to projects/FastAttr/fastattr/attr_head.py
index 593082845..857fc4ee5 100644
--- a/fastreid/modeling/heads/attr_head.py
+++ b/projects/FastAttr/fastattr/attr_head.py
@@ -8,8 +8,8 @@
from torch import nn
from fastreid.layers import *
+from fastreid.modeling.heads.build import REID_HEADS_REGISTRY
from fastreid.utils.weight_init import weights_init_kaiming, weights_init_classifier
-from .build import REID_HEADS_REGISTRY
@REID_HEADS_REGISTRY.register()
@@ -22,7 +22,6 @@ def __init__(self, cfg):
pool_type = cfg.MODEL.HEADS.POOL_LAYER
cls_type = cfg.MODEL.HEADS.CLS_LAYER
with_bnneck = cfg.MODEL.HEADS.WITH_BNNECK
- norm_type = cfg.MODEL.HEADS.NORM
if pool_type == 'fastavgpool': self.pool_layer = FastGlobalAvgPool2d()
elif pool_type == 'avgpool': self.pool_layer = nn.AdaptiveAvgPool2d(1)
@@ -39,14 +38,13 @@ def __init__(self, cfg):
if cls_type == 'linear': self.classifier = nn.Linear(feat_dim, num_classes, bias=False)
elif cls_type == 'arcSoftmax': self.classifier = ArcSoftmax(cfg, feat_dim, num_classes)
elif cls_type == 'circleSoftmax': self.classifier = CircleSoftmax(cfg, feat_dim, num_classes)
- elif cls_type == 'amSoftmax': self.classifier = CosSoftmax(cfg, feat_dim, num_classes)
+ elif cls_type == 'cosSoftmax': self.classifier = CosSoftmax(cfg, feat_dim, num_classes)
else: raise KeyError(f"{cls_type} is not supported!")
# fmt: on
- # bottleneck = []
- # if with_bnneck:
- # bottleneck.append(get_norm(norm_type, feat_dim, bias_freeze=True))
- bottleneck = [nn.BatchNorm1d(num_classes)]
+ bottleneck = []
+ if with_bnneck:
+ bottleneck.append(nn.BatchNorm1d(num_classes))
self.bottleneck = nn.Sequential(*bottleneck)
@@ -68,10 +66,10 @@ def forward(self, features, targets=None):
cls_outputs = self.bottleneck(cls_outputs)
- if self.training:
+ if not self.training:
+ cls_outputs = torch.sigmoid(cls_outputs)
+ return cls_outputs
+ else:
return {
"cls_outputs": cls_outputs,
}
- else:
- cls_outputs = torch.sigmoid(cls_outputs)
- return cls_outputs
diff --git a/projects/attribute_recognition/attribute_baseline/bce_loss.py b/projects/FastAttr/fastattr/bce_loss.py
similarity index 100%
rename from projects/attribute_recognition/attribute_baseline/bce_loss.py
rename to projects/FastAttr/fastattr/bce_loss.py
diff --git a/projects/attribute_recognition/attribute_baseline/config.py b/projects/FastAttr/fastattr/config.py
similarity index 100%
rename from projects/attribute_recognition/attribute_baseline/config.py
rename to projects/FastAttr/fastattr/config.py
diff --git a/projects/attribute_recognition/attribute_baseline/data_build.py b/projects/FastAttr/fastattr/data_build.py
similarity index 92%
rename from projects/attribute_recognition/attribute_baseline/data_build.py
rename to projects/FastAttr/fastattr/data_build.py
index eb0492244..1b52a44fb 100644
--- a/projects/attribute_recognition/attribute_baseline/data_build.py
+++ b/projects/FastAttr/fastattr/data_build.py
@@ -5,15 +5,16 @@
"""
import os
+
import torch
from torch.utils.data import DataLoader
-from fastreid.utils import comm
-from .common_attr import AttrDataset
from fastreid.data import samplers
from fastreid.data.build import fast_batch_collator
from fastreid.data.datasets import DATASET_REGISTRY
from fastreid.data.transforms import build_transforms
+from fastreid.utils import comm
+from .attr_dataset import AttrDataset
_root = os.getenv("FASTREID_DATASETS", "datasets")
@@ -34,8 +35,6 @@ def build_attr_train_loader(cfg):
attr_dict = dataset.attr_dict
train_items.extend(dataset.train)
- iters_per_epoch = len(train_items) // cfg.SOLVER.IMS_PER_BATCH
- cfg.SOLVER.MAX_ITER *= iters_per_epoch
train_transforms = build_transforms(cfg, is_train=True)
train_set = AttrDataset(train_items, attr_dict, train_transforms)
@@ -73,10 +72,8 @@ def build_attr_test_loader(cfg, dataset_name):
test_loader = DataLoader(
test_set,
batch_sampler=batch_sampler,
- num_workers=0, # save some memory
+ num_workers=4, # save some memory
collate_fn=fast_batch_collator,
pin_memory=True,
)
return test_loader
-
-
diff --git a/projects/attribute_recognition/attribute_baseline/datasets/__init__.py b/projects/FastAttr/fastattr/datasets/__init__.py
similarity index 100%
rename from projects/attribute_recognition/attribute_baseline/datasets/__init__.py
rename to projects/FastAttr/fastattr/datasets/__init__.py
diff --git a/projects/attribute_recognition/attribute_baseline/datasets/bases.py b/projects/FastAttr/fastattr/datasets/bases.py
similarity index 98%
rename from projects/attribute_recognition/attribute_baseline/datasets/bases.py
rename to projects/FastAttr/fastattr/datasets/bases.py
index ee0bbe8fe..49d56bebb 100644
--- a/projects/attribute_recognition/attribute_baseline/datasets/bases.py
+++ b/projects/FastAttr/fastattr/datasets/bases.py
@@ -11,7 +11,7 @@
from tabulate import tabulate
from termcolor import colored
-logger = logging.getLogger("fastreid." + __name__)
+logger = logging.getLogger("fastreid.attr_dataset")
class Dataset(object):
diff --git a/projects/attribute_recognition/attribute_baseline/datasets/pa100k.py b/projects/FastAttr/fastattr/datasets/pa100k.py
similarity index 94%
rename from projects/attribute_recognition/attribute_baseline/datasets/pa100k.py
rename to projects/FastAttr/fastattr/datasets/pa100k.py
index 5d6d15412..df3116c8b 100644
--- a/projects/attribute_recognition/attribute_baseline/datasets/pa100k.py
+++ b/projects/FastAttr/fastattr/datasets/pa100k.py
@@ -28,9 +28,9 @@ class PA100K(Dataset):
def __init__(self, root='', **kwargs):
self.root = root
self.dataset_dir = osp.join(self.root, self.dataset_dir)
- self.data_dir = osp.join(self.dataset_dir, 'data')
+ self.data_dir = osp.join(self.dataset_dir, "data")
self.anno_mat_path = osp.join(
- self.dataset_dir, 'annotation.mat'
+ self.dataset_dir, "annotation.mat"
)
required_files = [self.data_dir, self.anno_mat_path]
diff --git a/projects/FastAttr/train_net.py b/projects/FastAttr/train_net.py
new file mode 100644
index 000000000..5d16370dc
--- /dev/null
+++ b/projects/FastAttr/train_net.py
@@ -0,0 +1,90 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+import logging
+import sys
+
+sys.path.append('.')
+
+from fastreid.config import get_cfg
+from fastreid.engine import DefaultTrainer
+from fastreid.modeling import build_model
+from fastreid.engine import default_argument_parser, default_setup, launch
+from fastreid.utils.checkpoint import Checkpointer
+
+from fastattr import *
+
+
+class Trainer(DefaultTrainer):
+
+ def build_model(self, cfg):
+ """
+ Returns:
+ torch.nn.Module:
+ It now calls :func:`fastreid.modeling.build_model`.
+ Overwrite it if you'd like a different model.
+ """
+ model = build_model(cfg, sample_weights=self.sample_weights)
+ logger = logging.getLogger("fastreid.attr_model")
+ logger.info("Model:\n{}".format(model))
+ return model
+
+ def build_train_loader(self, cfg):
+ data_loader = build_attr_train_loader(cfg)
+ self.sample_weights = data_loader.dataset.sample_weights
+ return data_loader
+
+ @classmethod
+ def build_test_loader(cls, cfg, dataset_name):
+ return build_attr_test_loader(cfg, dataset_name)
+
+ @classmethod
+ def build_evaluator(cls, cfg, dataset_name, output_folder=None):
+ data_loader = cls.build_test_loader(cfg, dataset_name)
+ return data_loader, AttrEvaluator(cfg, output_folder)
+
+
+def setup(args):
+ """
+ Create configs and perform basic setups.
+ """
+ cfg = get_cfg()
+ add_attr_config(cfg)
+ cfg.merge_from_file(args.config_file)
+ cfg.merge_from_list(args.opts)
+ cfg.freeze()
+ default_setup(cfg, args)
+ return cfg
+
+
+def main(args):
+ cfg = setup(args)
+
+ if args.eval_only:
+ cfg.defrost()
+ cfg.MODEL.BACKBONE.PRETRAIN = False
+ model = Trainer.build_model(cfg)
+
+ Checkpointer(model).load(cfg.MODEL.WEIGHTS) # load trained model
+
+ res = Trainer.test(cfg, model)
+ return res
+
+ trainer = Trainer(cfg)
+ trainer.resume_or_load(resume=args.resume)
+ return trainer.train()
+
+
+if __name__ == "__main__":
+ args = default_argument_parser().parse_args()
+ print("Command Line Args:", args)
+ launch(
+ main,
+ args.num_gpus,
+ num_machines=args.num_machines,
+ machine_rank=args.machine_rank,
+ dist_url=args.dist_url,
+ args=(args,),
+ )
diff --git a/projects/FastCls/README.md b/projects/FastCls/README.md
new file mode 100644
index 000000000..8343cfb51
--- /dev/null
+++ b/projects/FastCls/README.md
@@ -0,0 +1,16 @@
+# FastCls in FastReID
+
+This project provides a baseline and example for image classification based on fastreid.
+
+## Datasets Preparation
+
+We refer to [pytorch tutorial](https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html) for dataset
+preparation. This is just an example for building a classification task based on fastreid. You can customize
+your own datasets and model.
+
+## Usage
+
+If you want to train models with 4 gpus, you can run
+```bash
+python3 projects/FastCls/train_net.py --config-file projects/FastCls/config/base-cls.yml --num-gpus 4
+```
diff --git a/projects/FastCls/configs/base-cls.yaml b/projects/FastCls/configs/base-cls.yaml
new file mode 100644
index 000000000..d02747aba
--- /dev/null
+++ b/projects/FastCls/configs/base-cls.yaml
@@ -0,0 +1,75 @@
+MODEL:
+ META_ARCHITECTURE: Baseline
+
+ BACKBONE:
+ NAME: build_resnet_backbone
+ DEPTH: 50x
+ NORM: BN
+ LAST_STRIDE: 2
+ FEAT_DIM: 2048
+ PRETRAIN: True
+
+ HEADS:
+ NAME: ClsHead
+ WITH_BNNECK: False
+ EMBEDDING_DIM: 0
+ POOL_LAYER: fastavgpool
+ CLS_LAYER: linear
+
+ LOSSES:
+ NAME: ("CrossEntropyLoss",)
+
+ CE:
+ EPSILON: 0.1
+ SCALE: 1.
+
+INPUT:
+ SIZE_TEST: [256,]
+ CROP_SIZE: 224
+ DO_FLIP: True
+
+ DO_AUGMIX: False
+ DO_AUTOAUG: False
+ DO_PAD: False
+ CJ:
+ ENABLED: False
+ DO_AFFINE: False
+ REA:
+ ENABLED: False
+
+DATALOADER:
+ PK_SAMPLER: False
+ NUM_WORKERS: 8
+
+SOLVER:
+ MAX_EPOCH: 100
+ FP16_ENABLED: True
+
+ OPT: SGD
+ SCHED: CosineAnnealingLR
+
+ BASE_LR: 0.003
+ MOMENTUM: 0.99
+ NESTEROV: True
+
+ BIAS_LR_FACTOR: 1.
+ WEIGHT_DECAY: 0.0005
+ WEIGHT_DECAY_BIAS: 0.
+ IMS_PER_BATCH: 128
+
+ ETA_MIN_LR: 0.00003
+
+ WARMUP_FACTOR: 0.1
+ WARMUP_EPOCHS: 10
+
+ CHECKPOINT_PERIOD: 10
+
+TEST:
+ EVAL_PERIOD: 10
+ IMS_PER_BATCH: 256
+
+DATASETS:
+ NAMES: ("Hymenoptera",)
+ TESTS: ("Hymenoptera",)
+
+OUTPUT_DIR: projects/FastCls/logs/baseline
\ No newline at end of file
diff --git a/projects/FastCls/fastcls/__init__.py b/projects/FastCls/fastcls/__init__.py
new file mode 100644
index 000000000..1b53bb9b2
--- /dev/null
+++ b/projects/FastCls/fastcls/__init__.py
@@ -0,0 +1,11 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+from .build_data import build_cls_train_loader, build_cls_test_loader
+from .cls_evaluator import ClsEvaluator
+from .cls_head import ClsHead
+from .config import add_cls_config
+from .datasets import *
diff --git a/projects/FastCls/fastcls/build_data.py b/projects/FastCls/fastcls/build_data.py
new file mode 100644
index 000000000..622781e09
--- /dev/null
+++ b/projects/FastCls/fastcls/build_data.py
@@ -0,0 +1,87 @@
+# encoding: utf-8
+"""
+@author: l1aoxingyu
+@contact: sherlockliao01@gmail.com
+"""
+
+import os
+
+import torch
+from torch.utils.data import DataLoader
+
+from fastreid.data import samplers
+from fastreid.data.build import fast_batch_collator
+from fastreid.data.common import CommDataset
+from fastreid.data.datasets import DATASET_REGISTRY
+from fastreid.utils import comm
+from .build_transforms import build_transforms
+
+_root = os.getenv("FASTREID_DATASETS", "datasets")
+
+
+def build_cls_train_loader(cfg, mapper=None, **kwargs):
+ cfg = cfg.clone()
+
+ train_items = list()
+ for d in cfg.DATASETS.NAMES:
+ dataset = DATASET_REGISTRY.get(d)(root=_root, **kwargs)
+ if comm.is_main_process():
+ dataset.show_train()
+ train_items.extend(dataset.train)
+
+ if mapper is not None:
+ transforms = mapper
+ else:
+ transforms = build_transforms(cfg, is_train=True)
+
+ train_set = CommDataset(train_items, transforms, relabel=False)
+
+ num_workers = cfg.DATALOADER.NUM_WORKERS
+ num_instance = cfg.DATALOADER.NUM_INSTANCE
+ mini_batch_size = cfg.SOLVER.IMS_PER_BATCH // comm.get_world_size()
+
+ if cfg.DATALOADER.PK_SAMPLER:
+ if cfg.DATALOADER.NAIVE_WAY:
+ data_sampler = samplers.NaiveIdentitySampler(train_set.img_items, mini_batch_size, num_instance)
+ else:
+ data_sampler = samplers.BalancedIdentitySampler(train_set.img_items, mini_batch_size, num_instance)
+ else:
+ data_sampler = samplers.TrainingSampler(len(train_set))
+ batch_sampler = torch.utils.data.sampler.BatchSampler(data_sampler, mini_batch_size, True)
+
+ train_loader = torch.utils.data.DataLoader(
+ train_set,
+ num_workers=num_workers,
+ batch_sampler=batch_sampler,
+ collate_fn=fast_batch_collator,
+ pin_memory=True,
+ )
+ return train_loader
+
+
+def build_cls_test_loader(cfg, dataset_name, mapper=None, **kwargs):
+ cfg = cfg.clone()
+
+ dataset = DATASET_REGISTRY.get(dataset_name)(root=_root, **kwargs)
+ if comm.is_main_process():
+ dataset.show_test()
+ test_items = dataset.query
+
+ if mapper is not None:
+ transforms = mapper
+ else:
+ transforms = build_transforms(cfg, is_train=False)
+
+ test_set = CommDataset(test_items, transforms, relabel=False)
+
+ mini_batch_size = cfg.TEST.IMS_PER_BATCH // comm.get_world_size()
+ data_sampler = samplers.InferenceSampler(len(test_set))
+ batch_sampler = torch.utils.data.BatchSampler(data_sampler, mini_batch_size, False)
+ test_loader = DataLoader(
+ test_set,
+ batch_sampler=batch_sampler,
+ num_workers=4, # save some memory
+ collate_fn=fast_batch_collator,
+ pin_memory=True,
+ )
+ return test_loader
diff --git a/projects/FastCls/fastcls/build_transforms.py b/projects/FastCls/fastcls/build_transforms.py
new file mode 100644
index 000000000..32182c982
--- /dev/null
+++ b/projects/FastCls/fastcls/build_transforms.py
@@ -0,0 +1,86 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+
+import torchvision.transforms as T
+
+from fastreid.data.transforms import *
+from fastreid.data.transforms.autoaugment import AutoAugment
+
+
+def build_transforms(cfg, is_train=True):
+ res = []
+
+ if is_train:
+ size_train = cfg.INPUT.SIZE_TRAIN
+ crop_size = cfg.INPUT.CROP_SIZE
+
+ # augmix augmentation
+ do_augmix = cfg.INPUT.DO_AUGMIX
+ augmix_prob = cfg.INPUT.AUGMIX_PROB
+
+ # auto augmentation
+ do_autoaug = cfg.INPUT.DO_AUTOAUG
+ autoaug_prob = cfg.INPUT.AUTOAUG_PROB
+
+ # horizontal filp
+ do_flip = cfg.INPUT.DO_FLIP
+ flip_prob = cfg.INPUT.FLIP_PROB
+
+ # padding
+ do_pad = cfg.INPUT.DO_PAD
+ padding = cfg.INPUT.PADDING
+ padding_mode = cfg.INPUT.PADDING_MODE
+
+ # color jitter
+ do_cj = cfg.INPUT.CJ.ENABLED
+ cj_prob = cfg.INPUT.CJ.PROB
+ cj_brightness = cfg.INPUT.CJ.BRIGHTNESS
+ cj_contrast = cfg.INPUT.CJ.CONTRAST
+ cj_saturation = cfg.INPUT.CJ.SATURATION
+ cj_hue = cfg.INPUT.CJ.HUE
+
+ # random affine
+ do_affine = cfg.INPUT.DO_AFFINE
+
+ # random erasing
+ do_rea = cfg.INPUT.REA.ENABLED
+ rea_prob = cfg.INPUT.REA.PROB
+ rea_value = cfg.INPUT.REA.VALUE
+
+ # random patch
+ do_rpt = cfg.INPUT.RPT.ENABLED
+ rpt_prob = cfg.INPUT.RPT.PROB
+
+ if do_autoaug:
+ res.append(T.RandomApply([AutoAugment()], p=autoaug_prob))
+
+ res.append(T.RandomResizedCrop(size=crop_size, interpolation=3))
+ if do_flip:
+ res.append(T.RandomHorizontalFlip(p=flip_prob))
+ if do_pad:
+ res.extend([T.Pad(padding, padding_mode=padding_mode), T.RandomCrop(size_train)])
+ if do_cj:
+ res.append(T.RandomApply([T.ColorJitter(cj_brightness, cj_contrast, cj_saturation, cj_hue)], p=cj_prob))
+ if do_affine:
+ res.append(T.RandomAffine(degrees=0, translate=None, scale=[0.9, 1.1], shear=None, resample=False,
+ fillcolor=128))
+ if do_augmix:
+ res.append(AugMix(prob=augmix_prob))
+ res.append(ToTensor())
+ if do_rea:
+ res.append(T.RandomErasing(p=rea_prob, value=rea_value))
+ if do_rpt:
+ res.append(RandomPatch(prob_happen=rpt_prob))
+ else:
+ size_test = cfg.INPUT.SIZE_TEST
+ crop_size = cfg.INPUT.CROP_SIZE
+
+ res.append(T.Resize(size_test[0] if len(size_test) == 1 else size_test, interpolation=3))
+ res.append(T.CenterCrop(size=crop_size))
+ res.append(ToTensor())
+ return T.Compose(res)
+
diff --git a/projects/FastCls/fastcls/cls_evaluator.py b/projects/FastCls/fastcls/cls_evaluator.py
new file mode 100644
index 000000000..144507dc9
--- /dev/null
+++ b/projects/FastCls/fastcls/cls_evaluator.py
@@ -0,0 +1,79 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import copy
+import logging
+from collections import OrderedDict
+
+import torch
+
+from fastreid.evaluation import DatasetEvaluator
+from fastreid.utils import comm
+
+logger = logging.getLogger("fastreid.cls_evaluator")
+
+
+def accuracy(output, target, topk=(1,)):
+ """Computes the accuracy over the k top predictions for the specified values of k"""
+ with torch.no_grad():
+ maxk = max(topk)
+ batch_size = target.size(0)
+
+ _, pred = output.topk(maxk, 1, True, True)
+ pred = pred.t()
+ correct = pred.eq(target.view(1, -1).expand_as(pred))
+
+ res = []
+ for k in topk:
+ correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True)
+ res.append(correct_k.mul_(100.0 / batch_size))
+ return res
+
+
+class ClsEvaluator(DatasetEvaluator):
+ def __init__(self, cfg, output_dir=None):
+ self.cfg = cfg
+ self._output_dir = output_dir
+
+ self.pred_logits = []
+ self.labels = []
+
+ def reset(self):
+ self.pred_logits = []
+ self.labels = []
+
+ def process(self, inputs, outputs):
+ self.pred_logits.append(outputs.cpu())
+ self.labels.extend(inputs["targets"])
+
+ def evaluate(self):
+ if comm.get_world_size() > 1:
+ comm.synchronize()
+ pred_logits = comm.gather(self.pred_logits)
+ pred_logits = sum(pred_logits, [])
+
+ labels = comm.gather(self.labels)
+ labels = sum(labels, [])
+
+ # fmt: off
+ if not comm.is_main_process(): return {}
+ # fmt: on
+ else:
+ pred_logits = self.pred_logits
+ labels = self.labels
+
+ pred_logits = torch.cat(pred_logits, dim=0)
+ labels = torch.stack(labels)
+
+ # measure accuracy and record loss
+ acc1, = accuracy(pred_logits, labels, topk=(1,))
+
+ self._results = OrderedDict()
+ self._results["Acc@1"] = acc1
+
+ self._results["metric"] = acc1
+
+ return copy.deepcopy(self._results)
diff --git a/projects/FastCls/fastcls/cls_head.py b/projects/FastCls/fastcls/cls_head.py
new file mode 100644
index 000000000..e7aa39e23
--- /dev/null
+++ b/projects/FastCls/fastcls/cls_head.py
@@ -0,0 +1,35 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import torch.nn.functional as F
+
+from fastreid.modeling.heads import REID_HEADS_REGISTRY, EmbeddingHead
+
+
+@REID_HEADS_REGISTRY.register()
+class ClsHead(EmbeddingHead):
+ def forward(self, features, targets=None):
+ """
+ See :class:`ClsHeads.forward`.
+ """
+ global_feat = self.pool_layer(features)
+ bn_feat = self.bottleneck(global_feat)
+ bn_feat = bn_feat[..., 0, 0]
+
+ cls_outputs = self.classifier(bn_feat)
+
+ # Evaluation
+ # fmt: off
+ if not self.training: return cls_outputs
+ # fmt: on
+
+ pred_class_logits = F.linear(bn_feat, self.classifier.weight)
+
+ return {
+ "cls_outputs": cls_outputs,
+ "pred_class_logits": pred_class_logits,
+ "features": bn_feat,
+ }
diff --git a/projects/FastCls/fastcls/config.py b/projects/FastCls/fastcls/config.py
new file mode 100644
index 000000000..28099c6cb
--- /dev/null
+++ b/projects/FastCls/fastcls/config.py
@@ -0,0 +1,11 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+
+def add_cls_config(cfg):
+ _C = cfg
+
+ _C.INPUT.CROP_SIZE = 224
diff --git a/projects/FastCls/fastcls/datasets.py b/projects/FastCls/fastcls/datasets.py
new file mode 100644
index 000000000..cdd443dd4
--- /dev/null
+++ b/projects/FastCls/fastcls/datasets.py
@@ -0,0 +1,69 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import glob
+import os
+
+from fastreid.data.datasets import DATASET_REGISTRY
+from fastreid.data.datasets.bases import ImageDataset
+
+__all__ = ["Hymenoptera"]
+
+
+@DATASET_REGISTRY.register()
+class Hymenoptera(ImageDataset):
+ """This is a demo dataset for smoke test, you can refer to
+ https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html
+ """
+ dataset_dir = 'hymenoptera_data'
+ dataset_name = "hyt"
+
+ def __init__(self, root='datasets', **kwargs):
+ self.root = root
+ self.dataset_dir = os.path.join(self.root, self.dataset_dir)
+ train_dir = os.path.join(self.dataset_dir, "train")
+ val_dir = os.path.join(self.dataset_dir, "val")
+
+ required_files = [
+ self.dataset_dir,
+ train_dir,
+ val_dir,
+ ]
+ self.check_before_run(required_files)
+
+ self.classes, self.class_to_idx = self._find_classes(train_dir)
+
+ train = self.process_dir(train_dir)
+ val = self.process_dir(val_dir)
+
+ super().__init__(train, val, [], **kwargs)
+
+ def process_dir(self, data_dir):
+ data = []
+ all_dirs = [d.name for d in os.scandir(data_dir) if d.is_dir()]
+ for dir_name in all_dirs:
+ all_imgs = glob.glob(os.path.join(data_dir, dir_name, "*.jpg"))
+ for img_name in all_imgs:
+ data.append([img_name, self.class_to_idx[dir_name], '0'])
+ return data
+
+ def _find_classes(self, dir: str):
+ """
+ Finds the class folders in a dataset.
+
+ Args:
+ dir (string): Root directory path.
+
+ Returns:
+ tuple: (classes, class_to_idx) where classes are relative to (dir), and class_to_idx is a dictionary.
+
+ Ensures:
+ No class is a subdirectory of another.
+ """
+ classes = [d.name for d in os.scandir(dir) if d.is_dir()]
+ classes.sort()
+ class_to_idx = {cls_name: i for i, cls_name in enumerate(classes)}
+ return classes, class_to_idx
diff --git a/projects/FastCls/train_net.py b/projects/FastCls/train_net.py
new file mode 100644
index 000000000..56987b9f9
--- /dev/null
+++ b/projects/FastCls/train_net.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# encoding: utf-8
+"""
+@author: sherlock
+@contact: sherlockliao01@gmail.com
+"""
+
+import logging
+import sys
+
+sys.path.append('.')
+
+from fastreid.config import get_cfg
+from fastreid.engine import default_argument_parser, default_setup, launch
+from fastreid.utils.checkpoint import Checkpointer
+from fastreid.engine import DefaultTrainer
+
+from fastcls import *
+
+
+class Trainer(DefaultTrainer):
+
+ @classmethod
+ def build_train_loader(cls, cfg):
+ """
+ Returns:
+ iterable
+ It now calls :func:`fastreid.data.build_reid_train_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ logger = logging.getLogger("fastreid.cls_dataset")
+ logger.info("Prepare training set")
+ return build_cls_train_loader(cfg)
+
+ @classmethod
+ def build_test_loader(cls, cfg, dataset_name):
+ """
+ Returns:
+ iterable
+ It now calls :func:`fastreid.data.build_reid_test_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+
+ return build_cls_test_loader(cfg, dataset_name)
+
+ @classmethod
+ def build_evaluator(cls, cfg, dataset_name, output_dir=None):
+ data_loader = cls.build_test_loader(cfg, dataset_name)
+ return data_loader, ClsEvaluator(cfg, output_dir)
+
+
+def setup(args):
+ """
+ Create configs and perform basic setups.
+ """
+ cfg = get_cfg()
+ add_cls_config(cfg)
+ cfg.merge_from_file(args.config_file)
+ cfg.merge_from_list(args.opts)
+ cfg.freeze()
+ default_setup(cfg, args)
+ return cfg
+
+
+def main(args):
+ cfg = setup(args)
+
+ if args.eval_only:
+ cfg.defrost()
+ cfg.MODEL.BACKBONE.PRETRAIN = False
+ model = Trainer.build_model(cfg)
+
+ Checkpointer(model).load(cfg.MODEL.WEIGHTS) # load trained model
+
+ res = Trainer.test(cfg, model)
+ return res
+
+ trainer = Trainer(cfg)
+
+ trainer.resume_or_load(resume=args.resume)
+ return trainer.train()
+
+
+if __name__ == "__main__":
+ args = default_argument_parser().parse_args()
+ print("Command Line Args:", args)
+ launch(
+ main,
+ args.num_gpus,
+ num_machines=args.num_machines,
+ machine_rank=args.machine_rank,
+ dist_url=args.dist_url,
+ args=(args,),
+ )
diff --git a/projects/FastDistill/README.md b/projects/FastDistill/README.md
new file mode 100644
index 000000000..b889a72b5
--- /dev/null
+++ b/projects/FastDistill/README.md
@@ -0,0 +1,52 @@
+# FastDistill in FastReID
+
+This project provides a strong distillation method for both embedding and classification training.
+The feature distillation comes from [overhaul-distillation](https://github.com/clovaai/overhaul-distillation/tree/master/ImageNet).
+
+
+## Datasets Prepration
+- DukeMTMC-reID
+
+
+## Train and Evaluation
+```shell
+# teacher model training
+python3 projects/FastDistill/train_net.py \
+--config-file projects/FastDistill/configs/sbs_r101ibn.yml \
+--num-gpus 4
+
+# loss distillation
+python3 projects/FastDistill/train_net.py \
+--config-file projects/FastDistill/configs/kd-sbs_r101ibn-sbs_r34.yaml \
+--num-gpus 4 \
+MODEL.META_ARCHITECTURE Distiller
+KD.MODEL_CONFIG projects/FastDistill/logs/dukemtmc/r101_ibn/config.yaml \
+KD.MODEL_WEIGHTS projects/FastDistill/logs/dukemtmc/r101_ibn/model_best.pth
+
+# loss+overhaul distillation
+python3 projects/FastDistill/train_net.py \
+--config-file projects/FastDistill/configs/kd-sbs_r101ibn-sbs_r34.yaml \
+--num-gpus 4 \
+MODEL.META_ARCHITECTURE DistillerOverhaul
+KD.MODEL_CONFIG projects/FastDistill/logs/dukemtmc/r101_ibn/config.yaml \
+KD.MODEL_WEIGHTS projects/FastDistill/logs/dukemtmc/r101_ibn/model_best.pth
+```
+
+## Experimental Results
+
+### Settings
+
+All the experiments are conducted with 4 V100 GPUs.
+
+
+### DukeMTMC-reID
+
+| Model | Rank@1 | mAP |
+| --- | --- | --- |
+| R101_ibn (teacher) | 90.66 | 81.14 |
+| R34 (student) | 86.31 | 73.28 |
+| JS Div | 88.60 | 77.80 |
+| JS Div + Overhaul | 88.73 | 78.25 |
+
+## Contact
+This project is conducted by [Xingyu Liao](https://github.com/L1aoXingyu) and [Guan'an Wang](https://wangguanan.github.io/) (guan.wang0706@gmail).
diff --git a/projects/FastDistill/configs/Base-kd.yml b/projects/FastDistill/configs/Base-kd.yml
new file mode 100644
index 000000000..2fab10f3f
--- /dev/null
+++ b/projects/FastDistill/configs/Base-kd.yml
@@ -0,0 +1,29 @@
+_BASE_: ../../../configs/Base-SBS.yml
+
+MODEL:
+ BACKBONE:
+ NAME: build_resnet_backbone_distill
+ WITH_IBN: False
+ WITH_NL: False
+ PRETRAIN: True
+
+INPUT:
+ SIZE_TRAIN: [ 256, 128 ]
+ SIZE_TEST: [ 256, 128 ]
+
+SOLVER:
+ FP16_ENABLED: True
+ MAX_EPOCH: 60
+ BASE_LR: 0.0007
+ IMS_PER_BATCH: 256
+
+ DELAY_EPOCHS: 30
+ FREEZE_ITERS: 500
+
+ CHECKPOINT_PERIOD: 20
+
+TEST:
+ EVAL_PERIOD: 20
+ IMS_PER_BATCH: 128
+
+CUDNN_BENCHMARK: True
diff --git a/projects/FastDistill/configs/kd-sbs_r101ibn-sbs_r34.yml b/projects/FastDistill/configs/kd-sbs_r101ibn-sbs_r34.yml
new file mode 100644
index 000000000..46f30cf46
--- /dev/null
+++ b/projects/FastDistill/configs/kd-sbs_r101ibn-sbs_r34.yml
@@ -0,0 +1,18 @@
+_BASE_: Base-kd.yml
+
+MODEL:
+ META_ARCHITECTURE: Distiller
+ BACKBONE:
+ DEPTH: 34x
+ FEAT_DIM: 512
+ WITH_IBN: False
+
+KD:
+ MODEL_CONFIG: projects/FastDistill/logs/dukemtmc/r101_ibn/config.yaml
+ MODEL_WEIGHTS: projects/FastDistill/logs/dukemtmc/r101_ibn/model_best.pth
+
+DATASETS:
+ NAMES: ("DukeMTMC",)
+ TESTS: ("DukeMTMC",)
+
+OUTPUT_DIR: projects/FastDistill/logs/dukemtmc/kd-r34-r101_ibn
\ No newline at end of file
diff --git a/projects/FastDistill/configs/sbs_r101ibn.yml b/projects/FastDistill/configs/sbs_r101ibn.yml
new file mode 100644
index 000000000..c1297b855
--- /dev/null
+++ b/projects/FastDistill/configs/sbs_r101ibn.yml
@@ -0,0 +1,12 @@
+_BASE_: Base-kd.yml
+
+MODEL:
+ BACKBONE:
+ WITH_IBN: True
+ DEPTH: 101x
+
+DATASETS:
+ NAMES: ("DukeMTMC",)
+ TESTS: ("DukeMTMC",)
+
+OUTPUT_DIR: projects/FastDistill/logs/dukemtmc/r101_ibn
\ No newline at end of file
diff --git a/projects/FastDistill/configs/sbs_r34.yml b/projects/FastDistill/configs/sbs_r34.yml
new file mode 100644
index 000000000..edb7c1c2a
--- /dev/null
+++ b/projects/FastDistill/configs/sbs_r34.yml
@@ -0,0 +1,13 @@
+_BASE_: Base-kd.yml
+
+MODEL:
+ BACKBONE:
+ DEPTH: 34x
+ FEAT_DIM: 512
+ WITH_IBN: False
+
+DATASETS:
+ NAMES: ("DukeMTMC",)
+ TESTS: ("DukeMTMC",)
+
+OUTPUT_DIR: projects/FastDistill/logs/dukemtmc/r34
\ No newline at end of file
diff --git a/projects/FastDistill/fastdistill/__init__.py b/projects/FastDistill/fastdistill/__init__.py
new file mode 100644
index 000000000..e76fe6d77
--- /dev/null
+++ b/projects/FastDistill/fastdistill/__init__.py
@@ -0,0 +1,8 @@
+# encoding: utf-8
+"""
+@author: l1aoxingyu
+@contact: sherlockliao01@gmail.com
+"""
+
+from .overhaul import DistillerOverhaul
+from .resnet_distill import build_resnet_backbone_distill
diff --git a/projects/FastDistill/fastdistill/overhaul.py b/projects/FastDistill/fastdistill/overhaul.py
new file mode 100644
index 000000000..cf263295c
--- /dev/null
+++ b/projects/FastDistill/fastdistill/overhaul.py
@@ -0,0 +1,116 @@
+# encoding: utf-8
+"""
+@author: l1aoxingyu
+@contact: sherlockliao01@gmail.com
+"""
+
+import logging
+import math
+
+import torch
+import torch.nn.functional as F
+from scipy.stats import norm
+from torch import nn
+
+from fastreid.modeling.meta_arch import META_ARCH_REGISTRY, Distiller
+
+logger = logging.getLogger("fastreid.meta_arch.overhaul_distiller")
+
+
+def distillation_loss(source, target, margin):
+ target = torch.max(target, margin)
+ loss = F.mse_loss(source, target, reduction="none")
+ loss = loss * ((source > target) | (target > 0)).float()
+ return loss.sum()
+
+
+def build_feature_connector(t_channel, s_channel):
+ C = [nn.Conv2d(s_channel, t_channel, kernel_size=1, stride=1, padding=0, bias=False),
+ nn.BatchNorm2d(t_channel)]
+
+ for m in C:
+ if isinstance(m, nn.Conv2d):
+ n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
+ m.weight.data.normal_(0, math.sqrt(2. / n))
+ elif isinstance(m, nn.BatchNorm2d):
+ m.weight.data.fill_(1)
+ m.bias.data.zero_()
+
+ return nn.Sequential(*C)
+
+
+def get_margin_from_BN(bn):
+ margin = []
+ std = bn.weight.data
+ mean = bn.bias.data
+ for (s, m) in zip(std, mean):
+ s = abs(s.item())
+ m = m.item()
+ if norm.cdf(-m / s) > 0.001:
+ margin.append(- s * math.exp(- (m / s) ** 2 / 2) / \
+ math.sqrt(2 * math.pi) / norm.cdf(-m / s) + m)
+ else:
+ margin.append(-3 * s)
+
+ return torch.tensor(margin, dtype=torch.float32, device=mean.device)
+
+
+@META_ARCH_REGISTRY.register()
+class DistillerOverhaul(Distiller):
+ def __init__(self, cfg):
+ super().__init__(cfg)
+
+ s_channels = self.backbone.get_channel_nums()
+ t_channels = self.model_t[0].get_channel_nums()
+
+ self.connectors = nn.ModuleList(
+ [build_feature_connector(t, s) for t, s in zip(t_channels, s_channels)])
+
+ teacher_bns = self.model_t[0].get_bn_before_relu()
+ margins = [get_margin_from_BN(bn) for bn in teacher_bns]
+ for i, margin in enumerate(margins):
+ self.register_buffer("margin%d" % (i + 1),
+ margin.unsqueeze(1).unsqueeze(2).unsqueeze(0).detach())
+
+ def forward(self, batched_inputs):
+ if self.training:
+ images = self.preprocess_image(batched_inputs)
+ # student model forward
+ s_feats, s_feat = self.backbone.extract_feature(images, preReLU=True)
+ assert "targets" in batched_inputs, "Labels are missing in training!"
+ targets = batched_inputs["targets"].to(self.device)
+
+ if targets.sum() < 0: targets.zero_()
+
+ s_outputs = self.heads(s_feat, targets)
+
+ # teacher model forward
+ with torch.no_grad():
+ t_feats, t_feat = self.model_t[0].extract_feature(images, preReLU=True)
+ t_outputs = self.model_t[1](t_feat, targets)
+
+ losses = self.losses(s_outputs, s_feats, t_outputs, t_feats, targets)
+ return losses
+
+ else:
+ outputs = super(DistillerOverhaul, self).forward(batched_inputs)
+ return outputs
+
+ def losses(self, s_outputs, s_feats, t_outputs, t_feats, gt_labels):
+ r"""
+ Compute loss from modeling's outputs, the loss function input arguments
+ must be the same as the outputs of the model forwarding.
+ """
+ loss_dict = super().losses(s_outputs, t_outputs, gt_labels)
+
+ # Overhaul distillation loss
+ feat_num = len(s_feats)
+ loss_distill = 0
+ for i in range(feat_num):
+ s_feats[i] = self.connectors[i](s_feats[i])
+ loss_distill += distillation_loss(s_feats[i], t_feats[i].detach(), getattr(
+ self, "margin%d" % (i + 1)).to(s_feats[i].dtype)) / 2 ** (feat_num - i - 1)
+
+ loss_dict["loss_overhaul"] = loss_distill / len(gt_labels) / 10000
+
+ return loss_dict
diff --git a/projects/FastDistill/fastdistill/resnet_distill.py b/projects/FastDistill/fastdistill/resnet_distill.py
new file mode 100644
index 000000000..567deb50c
--- /dev/null
+++ b/projects/FastDistill/fastdistill/resnet_distill.py
@@ -0,0 +1,347 @@
+# encoding: utf-8
+"""
+@author: liaoxingyu
+@contact: sherlockliao01@gmail.com
+"""
+
+import logging
+import math
+
+import torch
+import torch.nn.functional as F
+from torch import nn
+
+from fastreid.layers import (
+ IBN,
+ SELayer,
+ get_norm,
+)
+from fastreid.modeling.backbones import BACKBONE_REGISTRY
+from fastreid.utils import comm
+from fastreid.utils.checkpoint import get_missing_parameters_message, get_unexpected_parameters_message
+
+logger = logging.getLogger("fastreid.overhaul.backbone")
+model_urls = {
+ '18x': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
+ '34x': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
+ '50x': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
+ '101x': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
+ 'ibn_18x': 'https://github.com/XingangPan/IBN-Net/releases/download/v1.0/resnet18_ibn_a-2f571257.pth',
+ 'ibn_34x': 'https://github.com/XingangPan/IBN-Net/releases/download/v1.0/resnet34_ibn_a-94bc1577.pth',
+ 'ibn_50x': 'https://github.com/XingangPan/IBN-Net/releases/download/v1.0/resnet50_ibn_a-d9d0bb7b.pth',
+ 'ibn_101x': 'https://github.com/XingangPan/IBN-Net/releases/download/v1.0/resnet101_ibn_a-59ea0ac6.pth',
+ 'se_ibn_101x': 'https://github.com/XingangPan/IBN-Net/releases/download/v1.0/se_resnet101_ibn_a-fabed4e2.pth',
+}
+
+
+class BasicBlock(nn.Module):
+ expansion = 1
+
+ def __init__(self, inplanes, planes, bn_norm, with_ibn=False, with_se=False,
+ stride=1, downsample=None, reduction=16):
+ super(BasicBlock, self).__init__()
+ self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
+ if with_ibn:
+ self.bn1 = IBN(planes, bn_norm)
+ else:
+ self.bn1 = get_norm(bn_norm, planes)
+ self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
+ self.bn2 = get_norm(bn_norm, planes)
+ self.relu = nn.ReLU(inplace=True)
+ if with_se:
+ self.se = SELayer(planes, reduction)
+ else:
+ self.se = nn.Identity()
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ x = self.relu(x)
+ identity = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.se(out)
+
+ if self.downsample is not None:
+ identity = self.downsample(x)
+
+ out += identity
+ # out = self.relu(out)
+
+ return out
+
+
+class Bottleneck(nn.Module):
+ expansion = 4
+
+ def __init__(self, inplanes, planes, bn_norm, with_ibn=False, with_se=False,
+ stride=1, downsample=None, reduction=16):
+ super(Bottleneck, self).__init__()
+ self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
+ if with_ibn:
+ self.bn1 = IBN(planes, bn_norm)
+ else:
+ self.bn1 = get_norm(bn_norm, planes)
+ self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
+ padding=1, bias=False)
+ self.bn2 = get_norm(bn_norm, planes)
+ self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False)
+ self.bn3 = get_norm(bn_norm, planes * self.expansion)
+ self.relu = nn.ReLU(inplace=True)
+ if with_se:
+ self.se = SELayer(planes * self.expansion, reduction)
+ else:
+ self.se = nn.Identity()
+ self.downsample = downsample
+ self.stride = stride
+
+ def forward(self, x):
+ x = self.relu(x)
+ residual = x
+
+ out = self.conv1(x)
+ out = self.bn1(out)
+ out = self.relu(out)
+
+ out = self.conv2(out)
+ out = self.bn2(out)
+ out = self.relu(out)
+
+ out = self.conv3(out)
+ out = self.bn3(out)
+ out = self.se(out)
+
+ if self.downsample is not None:
+ residual = self.downsample(x)
+
+ out += residual
+ # out = self.relu(out)
+
+ return out
+
+
+class ResNet(nn.Module):
+ def __init__(self, last_stride, bn_norm, with_ibn, with_se, with_nl, block, layers, non_layers):
+ self.channel_nums = []
+ self.inplanes = 64
+ super().__init__()
+ self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
+ bias=False)
+ self.bn1 = get_norm(bn_norm, 64)
+ self.relu = nn.ReLU(inplace=True)
+ # self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
+ self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True)
+ self.layer1 = self._make_layer(block, 64, layers[0], 1, bn_norm, with_ibn, with_se)
+ self.layer2 = self._make_layer(block, 128, layers[1], 2, bn_norm, with_ibn, with_se)
+ self.layer3 = self._make_layer(block, 256, layers[2], 2, bn_norm, with_ibn, with_se)
+ self.layer4 = self._make_layer(block, 512, layers[3], last_stride, bn_norm, with_se=with_se)
+
+ self.random_init()
+
+ def _make_layer(self, block, planes, blocks, stride=1, bn_norm="BN", with_ibn=False, with_se=False):
+ downsample = None
+ if stride != 1 or self.inplanes != planes * block.expansion:
+ downsample = nn.Sequential(
+ nn.Conv2d(self.inplanes, planes * block.expansion,
+ kernel_size=1, stride=stride, bias=False),
+ get_norm(bn_norm, planes * block.expansion),
+ )
+
+ layers = []
+ layers.append(block(self.inplanes, planes, bn_norm, with_ibn, with_se, stride, downsample))
+ self.inplanes = planes * block.expansion
+ for i in range(1, blocks):
+ layers.append(block(self.inplanes, planes, bn_norm, with_ibn, with_se))
+
+ self.channel_nums.append(self.inplanes)
+ return nn.Sequential(*layers)
+
+ def forward(self, x):
+ x = self.conv1(x)
+ x = self.bn1(x)
+ x = self.relu(x)
+ x = self.maxpool(x)
+
+ x = self.layer1(x)
+ x = self.layer2(x)
+ x = self.layer3(x)
+ x = self.layer4(x)
+ x = F.relu(x, inplace=True)
+
+ return x
+
+ def random_init(self):
+ for m in self.modules():
+ if isinstance(m, nn.Conv2d):
+ n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
+ nn.init.normal_(m.weight, 0, math.sqrt(2. / n))
+ elif isinstance(m, nn.BatchNorm2d):
+ nn.init.constant_(m.weight, 1)
+ nn.init.constant_(m.bias, 0)
+
+ def get_bn_before_relu(self):
+ if isinstance(self.layer1[0], Bottleneck):
+ bn1 = self.layer1[-1].bn3
+ bn2 = self.layer2[-1].bn3
+ bn3 = self.layer3[-1].bn3
+ bn4 = self.layer4[-1].bn3
+ elif isinstance(self.layer1[0], BasicBlock):
+ bn1 = self.layer1[-1].bn2
+ bn2 = self.layer2[-1].bn2
+ bn3 = self.layer3[-1].bn2
+ bn4 = self.layer4[-1].bn2
+ else:
+ logger.info("ResNet unknown block error!")
+ return [bn1, bn2, bn3, bn4]
+
+ def extract_feature(self, x, preReLU=False):
+ x = self.conv1(x)
+ x = self.bn1(x)
+ x = self.relu(x)
+ x = self.maxpool(x)
+
+ feat1 = self.layer1(x)
+ feat2 = self.layer2(feat1)
+ feat3 = self.layer3(feat2)
+ feat4 = self.layer4(feat3)
+
+ if not preReLU:
+ feat1 = F.relu(feat1)
+ feat2 = F.relu(feat2)
+ feat3 = F.relu(feat3)
+ feat4 = F.relu(feat4)
+
+ return [feat1, feat2, feat3, feat4], F.relu(feat4)
+
+ def get_channel_nums(self):
+ return self.channel_nums
+
+
+def init_pretrained_weights(key):
+ """Initializes model with pretrained weights.
+
+ Layers that don't match with pretrained layers in name or size are kept unchanged.
+ """
+ import os
+ import errno
+ import gdown
+
+ def _get_torch_home():
+ ENV_TORCH_HOME = 'TORCH_HOME'
+ ENV_XDG_CACHE_HOME = 'XDG_CACHE_HOME'
+ DEFAULT_CACHE_DIR = '~/.cache'
+ torch_home = os.path.expanduser(
+ os.getenv(
+ ENV_TORCH_HOME,
+ os.path.join(
+ os.getenv(ENV_XDG_CACHE_HOME, DEFAULT_CACHE_DIR), 'torch'
+ )
+ )
+ )
+ return torch_home
+
+ torch_home = _get_torch_home()
+ model_dir = os.path.join(torch_home, 'checkpoints')
+ try:
+ os.makedirs(model_dir)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ # Directory already exists, ignore.
+ pass
+ else:
+ # Unexpected OSError, re-raise.
+ raise
+
+ filename = model_urls[key].split('/')[-1]
+
+ cached_file = os.path.join(model_dir, filename)
+
+ if not os.path.exists(cached_file):
+ if comm.is_main_process():
+ gdown.download(model_urls[key], cached_file, quiet=False)
+
+ comm.synchronize()
+
+ logger.info(f"Loading pretrained model from {cached_file}")
+ state_dict = torch.load(cached_file, map_location=torch.device('cpu'))
+
+ return state_dict
+
+
+@BACKBONE_REGISTRY.register()
+def build_resnet_backbone_distill(cfg):
+ """
+ Create a ResNet instance from config.
+ Returns:
+ ResNet: a :class:`ResNet` instance.
+ """
+
+ # fmt: off
+ pretrain = cfg.MODEL.BACKBONE.PRETRAIN
+ pretrain_path = cfg.MODEL.BACKBONE.PRETRAIN_PATH
+ last_stride = cfg.MODEL.BACKBONE.LAST_STRIDE
+ bn_norm = cfg.MODEL.BACKBONE.NORM
+ with_ibn = cfg.MODEL.BACKBONE.WITH_IBN
+ with_se = cfg.MODEL.BACKBONE.WITH_SE
+ with_nl = cfg.MODEL.BACKBONE.WITH_NL
+ depth = cfg.MODEL.BACKBONE.DEPTH
+ # fmt: on
+
+ num_blocks_per_stage = {
+ '18x': [2, 2, 2, 2],
+ '34x': [3, 4, 6, 3],
+ '50x': [3, 4, 6, 3],
+ '101x': [3, 4, 23, 3],
+ }[depth]
+
+ nl_layers_per_stage = {
+ '18x': [0, 0, 0, 0],
+ '34x': [0, 0, 0, 0],
+ '50x': [0, 2, 3, 0],
+ '101x': [0, 2, 9, 0]
+ }[depth]
+
+ block = {
+ '18x': BasicBlock,
+ '34x': BasicBlock,
+ '50x': Bottleneck,
+ '101x': Bottleneck
+ }[depth]
+
+ model = ResNet(last_stride, bn_norm, with_ibn, with_se, with_nl, block,
+ num_blocks_per_stage, nl_layers_per_stage)
+ if pretrain:
+ # Load pretrain path if specifically
+ if pretrain_path:
+ try:
+ state_dict = torch.load(pretrain_path, map_location=torch.device('cpu'))
+ logger.info(f"Loading pretrained model from {pretrain_path}")
+ except FileNotFoundError as e:
+ logger.info(f'{pretrain_path} is not found! Please check this path.')
+ raise e
+ except KeyError as e:
+ logger.info("State dict keys error! Please check the state dict.")
+ raise e
+ else:
+ key = depth
+ if with_ibn: key = 'ibn_' + key
+ if with_se: key = 'se_' + key
+
+ state_dict = init_pretrained_weights(key)
+
+ incompatible = model.load_state_dict(state_dict, strict=False)
+ if incompatible.missing_keys:
+ logger.info(
+ get_missing_parameters_message(incompatible.missing_keys)
+ )
+ if incompatible.unexpected_keys:
+ logger.info(
+ get_unexpected_parameters_message(incompatible.unexpected_keys)
+ )
+
+ return model
diff --git a/projects/DistillReID/train_net.py b/projects/FastDistill/train_net.py
similarity index 78%
rename from projects/DistillReID/train_net.py
rename to projects/FastDistill/train_net.py
index eaf0f0ed7..fd1a2840a 100644
--- a/projects/DistillReID/train_net.py
+++ b/projects/FastDistill/train_net.py
@@ -1,29 +1,24 @@
#!/usr/bin/env python
# encoding: utf-8
"""
-@author: sherlock, guan'an wang
+@author: L1aoXingyu, guan'an wang
@contact: sherlockliao01@gmail.com, guan.wang0706@gmail.com
"""
import sys
-import torch
-from torch import nn
sys.path.append('.')
from fastreid.config import get_cfg
from fastreid.engine import default_argument_parser, default_setup, DefaultTrainer, launch
from fastreid.utils.checkpoint import Checkpointer
-from kdreid import *
-
+from fastdistill import *
def setup(args):
"""
Create configs and perform basic setups.
"""
cfg = get_cfg()
- add_shufflenet_config(cfg)
- add_kdreid_config(cfg)
cfg.merge_from_file(args.config_file)
cfg.merge_from_list(args.opts)
cfg.freeze()
@@ -40,8 +35,7 @@ def main(args):
res = DefaultTrainer.test(cfg, model)
return res
- if args.kd: trainer = KDTrainer(cfg)
- else: trainer = DefaultTrainer(cfg)
+ trainer = DefaultTrainer(cfg)
trainer.resume_or_load(resume=args.resume)
return trainer.train()
@@ -49,7 +43,6 @@ def main(args):
if __name__ == "__main__":
parser = default_argument_parser()
- parser.add_argument("--kd", action="store_true", help="kd training with teacher model guided")
args = parser.parse_args()
print("Command Line Args:", args)
@@ -60,4 +53,4 @@ def main(args):
machine_rank=args.machine_rank,
dist_url=args.dist_url,
args=(args,),
- )
\ No newline at end of file
+ )
diff --git a/projects/FastFace/README.md b/projects/FastFace/README.md
new file mode 100644
index 000000000..6a8cdfe7e
--- /dev/null
+++ b/projects/FastFace/README.md
@@ -0,0 +1,32 @@
+# FastFace in FastReID
+
+This project provides a baseline for face recognition.
+
+## Datasets Preparation
+
+| Function | Dataset |
+| --- | --- |
+| Train | MS-Celeb-1M |
+| Test-1 | LFW |
+| Test-2 | CPLFW |
+| Test-3 | CALFW |
+| Test-4 | VGG2_FP |
+| Test-5 | AgeDB-30 |
+| Test-6 | CFP_FF |
+| Test-7 | CFP-FP |
+
+We do data wrangling following [InsightFace_Pytorch](https://github.com/TreB1eN/InsightFace_Pytorch) instruction.
+
+## Dependencies
+
+- bcolz
+
+## Experiment Results
+
+We refer to [insightface_pytorch](https://github.com/TreB1eN/InsightFace_Pytorch) as our baseline methods, and on top of it, we use circle loss and cosine lr scheduler.
+
+| Method | LFW(%) | CFP-FF(%) | CFP-FP(%)| AgeDB-30(%) | calfw(%) | cplfw(%) | vgg2_fp(%) |
+| :---: | :---: | :---: |:---: | :---: | :---: | :---: | :---: |
+| [insightface_pytorch](https://github.com/TreB1eN/InsightFace_Pytorch) | 99.52 | 99.62 | 95.04 | 96.22 | 95.57 | 91.07 | 93.86 |
+| ir50_se | 99.70 | 99.60 | 96.43 | 97.87 | 95.95 | 91.10 | 94.32 |
+| ir100_se | 99.65 | 99.69 | 97.10 | 97.98 | 96.00 | 91.53 | 94.62 |
\ No newline at end of file
diff --git a/projects/FastFace/configs/face_base.yml b/projects/FastFace/configs/face_base.yml
new file mode 100644
index 000000000..c6e94d53f
--- /dev/null
+++ b/projects/FastFace/configs/face_base.yml
@@ -0,0 +1,68 @@
+MODEL:
+ META_ARCHITECTURE: Baseline
+
+ PIXEL_MEAN: [127.5, 127.5, 127.5]
+ PIXEL_STD: [127.5, 127.5, 127.5]
+
+ HEADS:
+ NAME: EmbeddingHead
+ NORM: BN
+ NECK_FEAT: after
+ EMBEDDING_DIM: 512
+ POOL_LAYER: flatten
+ CLS_LAYER: circleSoftmax
+ SCALE: 256
+ MARGIN: 0.25
+
+ LOSSES:
+ NAME: ("CrossEntropyLoss",)
+
+ CE:
+ EPSILON: 0.
+ SCALE: 1.
+
+DATASETS:
+ NAMES: ("MS1MV2",)
+ TESTS: ("CPLFW", "VGG2_FP", "CALFW", "CFP_FF", "CFP_FP", "AgeDB_30", "LFW")
+
+INPUT:
+ SIZE_TRAIN: [112, 112]
+ SIZE_TEST: [112, 112]
+ DO_AUGMIX: False
+ DO_AUTOAUG: False
+
+ CJ:
+ ENABLED: False
+
+ DO_FLIP: True
+ FLIP_PROB: 0.5
+ DO_PAD: False
+
+DATALOADER:
+ PK_SAMPLER: False
+ NUM_WORKERS: 8
+
+SOLVER:
+ MAX_EPOCH: 16
+ FP16_ENABLED: False
+
+ OPT: SGD
+ BASE_LR: 0.1
+ MOMENTUM: 0.9
+
+ SCHED: CosineAnnealingLR
+ ETA_MIN_LR: 0.0001
+
+ BIAS_LR_FACTOR: 1.
+ WEIGHT_DECAY: 0.0005
+ WEIGHT_DECAY_BIAS: 0.0005
+ IMS_PER_BATCH: 512
+
+ WARMUP_FACTOR: 0.1
+ WARMUP_EPOCHS: 1
+
+ CHECKPOINT_PERIOD: 2
+
+TEST:
+ EVAL_PERIOD: 2
+ IMS_PER_BATCH: 1024
diff --git a/projects/FastFace/configs/r101_ir.yml b/projects/FastFace/configs/r101_ir.yml
new file mode 100644
index 000000000..a8bc5a31b
--- /dev/null
+++ b/projects/FastFace/configs/r101_ir.yml
@@ -0,0 +1,12 @@
+_BASE_: face_base.yml
+
+MODEL:
+ META_ARCHITECTURE: Baseline
+
+ BACKBONE:
+ NAME: build_resnetIR_backbone
+ DEPTH: 100x
+ FEAT_DIM: 25088 # 512x7x7
+ WITH_SE: True
+
+OUTPUT_DIR: projects/FastFace/logs/ir_se101-ms1mv2-circle
diff --git a/projects/FastFace/configs/r50_ir.yml b/projects/FastFace/configs/r50_ir.yml
new file mode 100644
index 000000000..161d99328
--- /dev/null
+++ b/projects/FastFace/configs/r50_ir.yml
@@ -0,0 +1,12 @@
+_BASE_: face_base.yml
+
+MODEL:
+ META_ARCHITECTURE: Baseline
+
+ BACKBONE:
+ NAME: build_resnetIR_backbone
+ DEPTH: 50x
+ FEAT_DIM: 25088 # 512x7x7
+ WITH_SE: True
+
+OUTPUT_DIR: projects/FastFace/logs/ir_se50-ms1mv2-circle
diff --git a/projects/FastFace/fastface/__init__.py b/projects/FastFace/fastface/__init__.py
new file mode 100644
index 000000000..5895bf0c8
--- /dev/null
+++ b/projects/FastFace/fastface/__init__.py
@@ -0,0 +1,10 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+from .datasets import *
+from .build import build_face_test_loader
+from .resnet_ir import build_resnetIR_backbone
+from .face_evaluator import FaceEvaluator
diff --git a/projects/FastFace/fastface/build.py b/projects/FastFace/fastface/build.py
new file mode 100644
index 000000000..132cde57a
--- /dev/null
+++ b/projects/FastFace/fastface/build.py
@@ -0,0 +1,49 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import torch
+from torch.utils.data import DataLoader
+
+from fastreid.data import samplers
+from fastreid.data.build import fast_batch_collator, _root
+from fastreid.data.common import CommDataset
+from fastreid.data.datasets import DATASET_REGISTRY
+from fastreid.utils import comm
+
+
+class FaceCommDataset(CommDataset):
+ def __init__(self, img_items, labels):
+ self.img_items = img_items
+ self.labels = labels
+
+ def __getitem__(self, index):
+ img = torch.tensor(self.img_items[index]) * 127.5 + 127.5
+ return {
+ "images": img,
+ }
+
+
+def build_face_test_loader(cfg, dataset_name, **kwargs):
+ cfg = cfg.clone()
+
+ dataset = DATASET_REGISTRY.get(dataset_name)(root=_root, **kwargs)
+ if comm.is_main_process():
+ dataset.show_test()
+
+ test_set = FaceCommDataset(dataset.carray, dataset.is_same)
+
+ mini_batch_size = cfg.TEST.IMS_PER_BATCH // comm.get_world_size()
+ data_sampler = samplers.InferenceSampler(len(test_set))
+ batch_sampler = torch.utils.data.BatchSampler(data_sampler, mini_batch_size, False)
+ test_loader = DataLoader(
+ test_set,
+ batch_sampler=batch_sampler,
+ num_workers=4, # save some memory
+ collate_fn=fast_batch_collator,
+ pin_memory=True,
+ )
+ return test_loader, test_set.labels
+
diff --git a/projects/FastFace/fastface/datasets/__init__.py b/projects/FastFace/fastface/datasets/__init__.py
new file mode 100644
index 000000000..099ab8bb8
--- /dev/null
+++ b/projects/FastFace/fastface/datasets/__init__.py
@@ -0,0 +1,8 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+from .ms1mv2 import MS1MV2
+from .test_dataset import *
diff --git a/projects/FastFace/fastface/datasets/ms1mv2.py b/projects/FastFace/fastface/datasets/ms1mv2.py
new file mode 100644
index 000000000..75858bf26
--- /dev/null
+++ b/projects/FastFace/fastface/datasets/ms1mv2.py
@@ -0,0 +1,41 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import glob
+import os
+
+from fastreid.data.datasets import DATASET_REGISTRY
+from fastreid.data.datasets.bases import ImageDataset
+
+
+@DATASET_REGISTRY.register()
+class MS1MV2(ImageDataset):
+ dataset_dir = "MS_Celeb_1M"
+ dataset_name = "ms1mv2"
+
+ def __init__(self, root="datasets", **kwargs):
+ self.root = root
+ self.dataset_dir = os.path.join(self.root, self.dataset_dir)
+
+ required_files = [self.dataset_dir]
+
+ self.check_before_run(required_files)
+
+ train = self.process_dirs()
+
+ super().__init__(train, [], [], **kwargs)
+
+ def process_dirs(self):
+ train_list = []
+
+ fid_list = os.listdir(self.dataset_dir)
+
+ for fid in fid_list:
+ all_imgs = glob.glob(os.path.join(self.dataset_dir, fid, "*.jpg"))
+ for img_path in all_imgs:
+ train_list.append([img_path, self.dataset_name + '_' + fid, '0'])
+
+ return train_list
diff --git a/projects/FastFace/fastface/datasets/test_dataset.py b/projects/FastFace/fastface/datasets/test_dataset.py
new file mode 100644
index 000000000..0932db6b1
--- /dev/null
+++ b/projects/FastFace/fastface/datasets/test_dataset.py
@@ -0,0 +1,67 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import os
+
+import bcolz
+import numpy as np
+
+from fastreid.data.datasets import DATASET_REGISTRY
+from fastreid.data.datasets.bases import ImageDataset
+
+__all__ = ["CPLFW", "VGG2_FP", "AgeDB_30", "CALFW", "CFP_FF", "CFP_FP", "LFW"]
+
+
+@DATASET_REGISTRY.register()
+class CPLFW(ImageDataset):
+ dataset_dir = "faces_emore_val"
+ dataset_name = "cplfw"
+
+ def __init__(self, root='datasets', **kwargs):
+ self.root = root
+ self.dataset_dir = os.path.join(self.root, self.dataset_dir)
+
+ required_files = [self.dataset_dir]
+
+ self.check_before_run(required_files)
+
+ carray = bcolz.carray(rootdir=os.path.join(self.dataset_dir, self.dataset_name), mode='r')
+ is_same = np.load(os.path.join(self.dataset_dir, "{}_list.npy".format(self.dataset_name)))
+
+ self.carray = carray
+ self.is_same = is_same
+
+ super().__init__([], [], [], **kwargs)
+
+
+@DATASET_REGISTRY.register()
+class VGG2_FP(CPLFW):
+ dataset_name = "vgg2_fp"
+
+
+@DATASET_REGISTRY.register()
+class AgeDB_30(CPLFW):
+ dataset_name = "agedb_30"
+
+
+@DATASET_REGISTRY.register()
+class CALFW(CPLFW):
+ dataset_name = "calfw"
+
+
+@DATASET_REGISTRY.register()
+class CFP_FF(CPLFW):
+ dataset_name = "cfp_ff"
+
+
+@DATASET_REGISTRY.register()
+class CFP_FP(CPLFW):
+ dataset_name = "cfp_fp"
+
+
+@DATASET_REGISTRY.register()
+class LFW(CPLFW):
+ dataset_name = "lfw"
diff --git a/projects/FastFace/fastface/face_evaluator.py b/projects/FastFace/fastface/face_evaluator.py
new file mode 100644
index 000000000..1163c0eef
--- /dev/null
+++ b/projects/FastFace/fastface/face_evaluator.py
@@ -0,0 +1,83 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import copy
+import io
+import logging
+import os
+from collections import OrderedDict
+
+import matplotlib.pyplot as plt
+import torch
+import torch.nn.functional as F
+from PIL import Image
+
+from fastreid.evaluation import DatasetEvaluator
+from fastreid.utils import comm
+from fastreid.utils.file_io import PathManager
+from .verification import evaluate
+
+logger = logging.getLogger("fastreid.face_evaluator")
+
+
+def gen_plot(fpr, tpr):
+ """Create a pyplot plot and save to buffer."""
+ plt.figure()
+ plt.xlabel("FPR", fontsize=14)
+ plt.ylabel("TPR", fontsize=14)
+ plt.title("ROC Curve", fontsize=14)
+ plt.plot(fpr, tpr, linewidth=2)
+ buf = io.BytesIO()
+ plt.savefig(buf, format='jpeg')
+ buf.seek(0)
+ plt.close()
+ return buf
+
+
+class FaceEvaluator(DatasetEvaluator):
+ def __init__(self, cfg, labels, dataset_name, output_dir=None):
+ self.cfg = cfg
+ self.labels = labels
+ self.dataset_name = dataset_name
+ self._output_dir = output_dir
+
+ self.features = []
+
+ def reset(self):
+ self.features = []
+
+ def process(self, inputs, outputs):
+ self.features.append(outputs.cpu())
+
+ def evaluate(self):
+ if comm.get_world_size() > 1:
+ comm.synchronize()
+ features = comm.gather(self.features)
+ features = sum(features, [])
+
+ # fmt: off
+ if not comm.is_main_process(): return {}
+ # fmt: on
+ else:
+ features = self.features
+
+ features = torch.cat(features, dim=0)
+ features = F.normalize(features, p=2, dim=1).numpy()
+
+ self._results = OrderedDict()
+ tpr, fpr, accuracy, best_thresholds = evaluate(features, self.labels)
+
+ self._results["Accuracy"] = accuracy.mean() * 100
+ self._results["Threshold"] = best_thresholds.mean()
+ self._results["metric"] = accuracy.mean() * 100
+
+ buf = gen_plot(fpr, tpr)
+ roc_curve = Image.open(buf)
+
+ PathManager.mkdirs(self._output_dir)
+ roc_curve.save(os.path.join(self._output_dir, self.dataset_name + "_roc.png"))
+
+ return copy.deepcopy(self._results)
diff --git a/projects/FastFace/fastface/resnet_ir.py b/projects/FastFace/fastface/resnet_ir.py
new file mode 100644
index 000000000..67632a2ca
--- /dev/null
+++ b/projects/FastFace/fastface/resnet_ir.py
@@ -0,0 +1,122 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+from collections import namedtuple
+
+from torch import nn
+
+from fastreid.layers import get_norm, SELayer
+from fastreid.modeling.backbones import BACKBONE_REGISTRY
+
+
+def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
+ """3x3 convolution with padding"""
+ return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
+ padding=dilation, groups=groups, bias=False, dilation=dilation)
+
+
+class bottleneck_IR(nn.Module):
+ def __init__(self, in_channel, depth, bn_norm, stride, with_se=False):
+ super(bottleneck_IR, self).__init__()
+ if in_channel == depth:
+ self.shortcut_layer = nn.MaxPool2d(1, stride)
+ else:
+ self.shortcut_layer = nn.Sequential(
+ nn.Conv2d(in_channel, depth, (1, 1), stride, bias=False),
+ get_norm(bn_norm, depth))
+ self.res_layer = nn.Sequential(
+ get_norm(bn_norm, in_channel),
+ nn.Conv2d(in_channel, depth, (3, 3), (1, 1), 1, bias=False),
+ nn.PReLU(depth),
+ nn.Conv2d(depth, depth, (3, 3), stride, 1, bias=False),
+ get_norm(bn_norm, depth),
+ SELayer(depth, 16) if with_se else nn.Identity()
+ )
+
+ def forward(self, x):
+ shortcut = self.shortcut_layer(x)
+ res = self.res_layer(x)
+ return res + shortcut
+
+
+class Bottleneck(namedtuple("Block", ["in_channel", "depth", "bn_norm", "stride", "with_se"])):
+ """A named tuple describing a ResNet block."""
+
+
+def get_block(in_channel, depth, bn_norm, num_units, with_se, stride=2):
+ return [Bottleneck(in_channel, depth, bn_norm, stride, with_se)] + \
+ [Bottleneck(depth, depth, bn_norm, 1, with_se) for _ in range(num_units - 1)]
+
+
+def get_blocks(bn_norm, with_se, num_layers):
+ if num_layers == "50x":
+ blocks = [
+ get_block(in_channel=64, depth=64, bn_norm=bn_norm, num_units=3, with_se=with_se),
+ get_block(in_channel=64, depth=128, bn_norm=bn_norm, num_units=4, with_se=with_se),
+ get_block(in_channel=128, depth=256, bn_norm=bn_norm, num_units=14, with_se=with_se),
+ get_block(in_channel=256, depth=512, bn_norm=bn_norm, num_units=3, with_se=with_se)
+ ]
+ elif num_layers == "100x":
+ blocks = [
+ get_block(in_channel=64, depth=64, bn_norm=bn_norm, num_units=3, with_se=with_se),
+ get_block(in_channel=64, depth=128, bn_norm=bn_norm, num_units=13, with_se=with_se),
+ get_block(in_channel=128, depth=256, bn_norm=bn_norm, num_units=30, with_se=with_se),
+ get_block(in_channel=256, depth=512, bn_norm=bn_norm, num_units=3, with_se=with_se)
+ ]
+ elif num_layers == "152x":
+ blocks = [
+ get_block(in_channel=64, depth=64, bn_norm=bn_norm, num_units=3, with_se=with_se),
+ get_block(in_channel=64, depth=128, bn_norm=bn_norm, num_units=8, with_se=with_se),
+ get_block(in_channel=128, depth=256, bn_norm=bn_norm, num_units=36, with_se=with_se),
+ get_block(in_channel=256, depth=512, bn_norm=bn_norm, num_units=3, with_se=with_se)
+ ]
+ return blocks
+
+
+class ResNetIR(nn.Module):
+ def __init__(self, num_layers, bn_norm, drop_ratio, with_se):
+ super(ResNetIR, self).__init__()
+ assert num_layers in ["50x", "100x", "152x"], "num_layers should be 50,100, or 152"
+ blocks = get_blocks(bn_norm, with_se, num_layers)
+ self.input_layer = nn.Sequential(nn.Conv2d(3, 64, (3, 3), 1, 1, bias=False),
+ get_norm(bn_norm, 64),
+ nn.PReLU(64))
+ self.output_layer = nn.Sequential(get_norm(bn_norm, 512),
+ nn.Dropout(drop_ratio))
+ modules = []
+ for block in blocks:
+ for bottleneck in block:
+ modules.append(
+ bottleneck_IR(bottleneck.in_channel,
+ bottleneck.depth,
+ bottleneck.bn_norm,
+ bottleneck.stride,
+ bottleneck.with_se))
+ self.body = nn.Sequential(*modules)
+
+ def forward(self, x):
+ x = self.input_layer(x)
+ x = self.body(x)
+ x = self.output_layer(x)
+ return x
+
+
+@BACKBONE_REGISTRY.register()
+def build_resnetIR_backbone(cfg):
+ """
+ Create a ResNetIR instance from config.
+ Returns:
+ ResNet: a :class:`ResNet` instance.
+ """
+
+ # fmt: off
+ bn_norm = cfg.MODEL.BACKBONE.NORM
+ with_se = cfg.MODEL.BACKBONE.WITH_SE
+ depth = cfg.MODEL.BACKBONE.DEPTH
+ # fmt: on
+
+ model = ResNetIR(depth, bn_norm, 0.5, with_se)
+ return model
diff --git a/projects/FastFace/fastface/verification.py b/projects/FastFace/fastface/verification.py
new file mode 100644
index 000000000..29028e6a5
--- /dev/null
+++ b/projects/FastFace/fastface/verification.py
@@ -0,0 +1,170 @@
+# encoding: utf-8
+
+"""Helper for evaluation on the Labeled Faces in the Wild dataset
+"""
+
+# MIT License
+#
+# Copyright (c) 2016 David Sandberg
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import numpy as np
+import sklearn
+from scipy import interpolate
+from sklearn.decomposition import PCA
+from sklearn.model_selection import KFold
+
+
+def calculate_roc(thresholds, embeddings1, embeddings2, actual_issame, nrof_folds=10, pca=0):
+ assert (embeddings1.shape[0] == embeddings2.shape[0])
+ assert (embeddings1.shape[1] == embeddings2.shape[1])
+ nrof_pairs = min(len(actual_issame), embeddings1.shape[0])
+ nrof_thresholds = len(thresholds)
+ k_fold = KFold(n_splits=nrof_folds, shuffle=False)
+
+ tprs = np.zeros((nrof_folds, nrof_thresholds))
+ fprs = np.zeros((nrof_folds, nrof_thresholds))
+ accuracy = np.zeros((nrof_folds))
+ best_thresholds = np.zeros((nrof_folds))
+ indices = np.arange(nrof_pairs)
+
+ if pca == 0:
+ diff = np.subtract(embeddings1, embeddings2)
+ dist = np.sum(np.square(diff), 1)
+
+ for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)):
+ # print('train_set', train_set)
+ # print('test_set', test_set)
+ if pca > 0:
+ print('doing pca on', fold_idx)
+ embed1_train = embeddings1[train_set]
+ embed2_train = embeddings2[train_set]
+ _embed_train = np.concatenate((embed1_train, embed2_train), axis=0)
+ # print(_embed_train.shape)
+ pca_model = PCA(n_components=pca)
+ pca_model.fit(_embed_train)
+ embed1 = pca_model.transform(embeddings1)
+ embed2 = pca_model.transform(embeddings2)
+ embed1 = sklearn.preprocessing.normalize(embed1)
+ embed2 = sklearn.preprocessing.normalize(embed2)
+ # print(embed1.shape, embed2.shape)
+ diff = np.subtract(embed1, embed2)
+ dist = np.sum(np.square(diff), 1)
+
+ # Find the best threshold for the fold
+ acc_train = np.zeros((nrof_thresholds))
+ for threshold_idx, threshold in enumerate(thresholds):
+ _, _, acc_train[threshold_idx] = calculate_accuracy(threshold, dist[train_set], actual_issame[train_set])
+ best_threshold_index = np.argmax(acc_train)
+ # print('best_threshold_index', best_threshold_index, acc_train[best_threshold_index])
+ best_thresholds[fold_idx] = thresholds[best_threshold_index]
+ for threshold_idx, threshold in enumerate(thresholds):
+ tprs[fold_idx, threshold_idx], fprs[fold_idx, threshold_idx], _ = calculate_accuracy(threshold,
+ dist[test_set],
+ actual_issame[
+ test_set])
+ _, _, accuracy[fold_idx] = calculate_accuracy(thresholds[best_threshold_index], dist[test_set],
+ actual_issame[test_set])
+
+ tpr = np.mean(tprs, 0)
+ fpr = np.mean(fprs, 0)
+ return tpr, fpr, accuracy, best_thresholds
+
+
+def calculate_accuracy(threshold, dist, actual_issame):
+ predict_issame = np.less(dist, threshold)
+ tp = np.sum(np.logical_and(predict_issame, actual_issame))
+ fp = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame)))
+ tn = np.sum(np.logical_and(np.logical_not(predict_issame), np.logical_not(actual_issame)))
+ fn = np.sum(np.logical_and(np.logical_not(predict_issame), actual_issame))
+
+ tpr = 0 if (tp + fn == 0) else float(tp) / float(tp + fn)
+ fpr = 0 if (fp + tn == 0) else float(fp) / float(fp + tn)
+ acc = float(tp + tn) / dist.size
+ return tpr, fpr, acc
+
+
+def calculate_val(thresholds, embeddings1, embeddings2, actual_issame, far_target, nrof_folds=10):
+ '''
+ Copy from [insightface](https://github.com/deepinsight/insightface)
+ :param thresholds:
+ :param embeddings1:
+ :param embeddings2:
+ :param actual_issame:
+ :param far_target:
+ :param nrof_folds:
+ :return:
+ '''
+ assert (embeddings1.shape[0] == embeddings2.shape[0])
+ assert (embeddings1.shape[1] == embeddings2.shape[1])
+ nrof_pairs = min(len(actual_issame), embeddings1.shape[0])
+ nrof_thresholds = len(thresholds)
+ k_fold = KFold(n_splits=nrof_folds, shuffle=False)
+
+ val = np.zeros(nrof_folds)
+ far = np.zeros(nrof_folds)
+
+ diff = np.subtract(embeddings1, embeddings2)
+ dist = np.sum(np.square(diff), 1)
+ indices = np.arange(nrof_pairs)
+
+ for fold_idx, (train_set, test_set) in enumerate(k_fold.split(indices)):
+
+ # Find the threshold that gives FAR = far_target
+ far_train = np.zeros(nrof_thresholds)
+ for threshold_idx, threshold in enumerate(thresholds):
+ _, far_train[threshold_idx] = calculate_val_far(threshold, dist[train_set], actual_issame[train_set])
+ if np.max(far_train) >= far_target:
+ f = interpolate.interp1d(far_train, thresholds, kind='slinear')
+ threshold = f(far_target)
+ else:
+ threshold = 0.0
+
+ val[fold_idx], far[fold_idx] = calculate_val_far(threshold, dist[test_set], actual_issame[test_set])
+
+ val_mean = np.mean(val)
+ far_mean = np.mean(far)
+ val_std = np.std(val)
+ return val_mean, val_std, far_mean
+
+
+def calculate_val_far(threshold, dist, actual_issame):
+ predict_issame = np.less(dist, threshold)
+ true_accept = np.sum(np.logical_and(predict_issame, actual_issame))
+ false_accept = np.sum(np.logical_and(predict_issame, np.logical_not(actual_issame)))
+ n_same = np.sum(actual_issame)
+ n_diff = np.sum(np.logical_not(actual_issame))
+ val = float(true_accept) / float(n_same)
+ far = float(false_accept) / float(n_diff)
+ return val, far
+
+
+def evaluate(embeddings, actual_issame, nrof_folds=10, pca=0):
+ # Calculate evaluation metrics
+ thresholds = np.arange(0, 4, 0.01)
+ embeddings1 = embeddings[0::2]
+ embeddings2 = embeddings[1::2]
+ tpr, fpr, accuracy, best_thresholds = calculate_roc(thresholds, embeddings1, embeddings2,
+ np.asarray(actual_issame), nrof_folds=nrof_folds, pca=pca)
+ # thresholds = np.arange(0, 4, 0.001)
+ # val, val_std, far = calculate_val(thresholds, embeddings1, embeddings2,
+ # np.asarray(actual_issame), 1e-3, nrof_folds=nrof_folds)
+ # return tpr, fpr, accuracy, best_thresholds, val, val_std, far
+ return tpr, fpr, accuracy, best_thresholds
diff --git a/projects/FastFace/train_net.py b/projects/FastFace/train_net.py
new file mode 100644
index 000000000..1cce4075b
--- /dev/null
+++ b/projects/FastFace/train_net.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# encoding: utf-8
+"""
+@author: sherlock
+@contact: sherlockliao01@gmail.com
+"""
+
+import os
+import sys
+
+sys.path.append('.')
+
+from fastreid.config import get_cfg
+from fastreid.engine import DefaultTrainer, default_argument_parser, default_setup, launch
+from fastreid.utils.checkpoint import Checkpointer
+
+from fastface import *
+
+
+class Trainer(DefaultTrainer):
+
+ @classmethod
+ def build_test_loader(cls, cfg, dataset_name):
+ """
+ Returns:
+ iterable
+ It now calls :func:`fastreid.data.build_detection_test_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ return build_face_test_loader(cfg, dataset_name)
+
+ @classmethod
+ def build_evaluator(cls, cfg, dataset_name, output_dir=None):
+ if output_dir is None:
+ output_dir = os.path.join(cfg.OUTPUT_DIR, "visualization")
+ data_loader, labels = cls.build_test_loader(cfg, dataset_name)
+ return data_loader, FaceEvaluator(cfg, labels, dataset_name, output_dir)
+
+
+def setup(args):
+ """
+ Create configs and perform basic setups.
+ """
+ cfg = get_cfg()
+ cfg.merge_from_file(args.config_file)
+ cfg.merge_from_list(args.opts)
+ cfg.freeze()
+ default_setup(cfg, args)
+ return cfg
+
+
+def main(args):
+ cfg = setup(args)
+
+ if args.eval_only:
+ cfg.defrost()
+ cfg.MODEL.BACKBONE.PRETRAIN = False
+ model = Trainer.build_model(cfg)
+
+ Checkpointer(model).load(cfg.MODEL.WEIGHTS) # load trained model
+
+ res = Trainer.test(cfg, model)
+ return res
+
+ trainer = Trainer(cfg)
+
+ trainer.resume_or_load(resume=args.resume)
+ return trainer.train()
+
+
+if __name__ == "__main__":
+ args = default_argument_parser().parse_args()
+ print("Command Line Args:", args)
+ launch(
+ main,
+ args.num_gpus,
+ num_machines=args.num_machines,
+ machine_rank=args.machine_rank,
+ dist_url=args.dist_url,
+ args=(args,),
+ )
diff --git a/projects/FastRetri/README.md b/projects/FastRetri/README.md
new file mode 100644
index 000000000..a1cce97ad
--- /dev/null
+++ b/projects/FastRetri/README.md
@@ -0,0 +1,52 @@
+# FastRetri in FastReID
+
+This project provides a strong baseline for fine-grained image retrieval.
+
+## Datasets Preparation
+
+We use `CUB200`, `CARS-196`, `Standford Online Products` and `In-Shop` to evaluate the model's performance.
+You can do data management following [dml_cross_entropy](https://github.com/jeromerony/dml_cross_entropy) instruction.
+
+## Usage
+
+Each dataset's config file can be found in `projects/FastRetri/config`, which you can use to reproduce the results of the repo.
+
+For example, if you want to train with `CUB200`, you can run an experiment with `cub.yml`
+
+```bash
+python3 projects/FastRetri/train_net.py --config-file projects/FastRetri/config/cub.yml --num-gpus 4
+```
+
+## Experiment Results
+
+We refer to [A unifying mutual information view of metric learning: cross-entropy vs. pairwise losses](arxiv.org/abs/2003.08983) as our baseline methods, and on top of it, we add some tricks, such as gem pooling.
+More details can be found in the config file and code.
+
+### CUB
+
+| Method | Pretrained | Recall@1 | Recall@2 | Recall@4 | Recall@8 | Recall@16 | Recall@32 |
+| :---: | :---: | :---: |:---: | :---: | :---: | :---: | :---: |
+| [dml_cross_entropy](https://github.com/jeromerony/dml_cross_entropy) | ImageNet | 69.2 | 79.2 | 86.9 | 91.6 | 95.0 | 97.3 |
+| Fastretri | ImageNet | 69.46 | 79.57 | 87.53 | 92.61 | 95.75 | 97.35 |
+
+### Cars-196
+
+| Method | Pretrained | Recall@1 | Recall@2 | Recall@4 | Recall@8 | Recall@16 | Recall@32 |
+| :---: | :---: | :---: |:---: | :---: | :---: | :---: | :---: |
+| [dml_cross_entropy](https://github.com/jeromerony/dml_cross_entropy) | ImageNet | 89.3 | 93.9 | 96.6 | 98.4 | 99.3 | 99.7 |
+| Fastretri | ImageNet | 92.31 | 95.99 | 97.60 | 98.63 | 99.24 | 99.62 |
+
+### Standford Online Products
+
+| Method | Pretrained | Recall@1 | Recall@10 | Recall@100 | Recall@1000 |
+| :---: | :---: | :---: |:---: | :---: | :---: |
+| [dml_cross_entropy](https://github.com/jeromerony/dml_cross_entropy) | ImageNet | 81.1 | 91.7 | 96.3 | 98.8 |
+| Fastretri | ImageNet | 82.46 | 92.56 | 96.78 | 98.95 |
+
+### In-Shop
+
+| Method | Pretrained | Recall@1 | Recall@10 | Recall@20 | Recall@30 | Recall@40 | Recall@50 |
+| :---: | :---: | :---: |:---: | :---: | :---: | :---: | :---: |
+| [dml_cross_entropy](https://github.comjeromerony/dml_cross_entropy) | ImageNet | 90.6 | 98.0 | 98.6 | 98.9 | 99.1 | 99.2 |
+| Fastretri | ImageNet | 91.97 | 98.29 | 98.85 | 99.11 | 99.24 | 99.35 |
+
diff --git a/projects/FastRetri/configs/base-image_retri.yml b/projects/FastRetri/configs/base-image_retri.yml
new file mode 100644
index 000000000..85df78ddd
--- /dev/null
+++ b/projects/FastRetri/configs/base-image_retri.yml
@@ -0,0 +1,70 @@
+MODEL:
+ META_ARCHITECTURE: Baseline
+
+ BACKBONE:
+ NAME: build_resnet_backbone
+ DEPTH: 50x
+ NORM: FrozenBN
+ LAST_STRIDE: 1
+ FEAT_DIM: 2048
+ PRETRAIN: True
+
+ HEADS:
+ NAME: EmbeddingHead
+ NORM: syncBN
+ NECK_FEAT: after
+ EMBEDDING_DIM: 0
+ POOL_LAYER: gempool
+ CLS_LAYER: linear
+
+ LOSSES:
+ NAME: ("CrossEntropyLoss",)
+
+ CE:
+ EPSILON: 0.1
+ SCALE: 1.
+
+INPUT:
+ SIZE_TRAIN: [256, 256]
+ SIZE_TEST: [256, 256]
+ CROP_SIZE: 224
+ SCALE: (0.16, 1.)
+ RATIO: (0.75, 1.33333)
+
+ CJ:
+ ENABLED: False
+ BRIGHTNESS: 0.3
+ CONTRAST: 0.3
+ SATURATION: 0.1
+ HUE: 0.1
+
+DATALOADER:
+ PK_SAMPLER: False
+ NUM_WORKERS: 8
+
+SOLVER:
+ MAX_EPOCH: 100
+ FP16_ENABLED: True
+
+ OPT: SGD
+ SCHED: CosineAnnealingLR
+
+ BASE_LR: 0.003
+ MOMENTUM: 0.99
+ NESTEROV: True
+
+ BIAS_LR_FACTOR: 1.
+ WEIGHT_DECAY: 0.0005
+ WEIGHT_DECAY_BIAS: 0.
+ IMS_PER_BATCH: 128
+
+ ETA_MIN_LR: 0.00003
+
+ WARMUP_FACTOR: 0.1
+ WARMUP_EPOCHS: 10
+
+ CHECKPOINT_PERIOD: 10
+
+TEST:
+ EVAL_PERIOD: 10
+ IMS_PER_BATCH: 256
diff --git a/projects/FastRetri/configs/cars.yml b/projects/FastRetri/configs/cars.yml
new file mode 100644
index 000000000..41c4eef50
--- /dev/null
+++ b/projects/FastRetri/configs/cars.yml
@@ -0,0 +1,33 @@
+_BASE_: base-image_retri.yml
+
+MODEL:
+ LOSSES:
+ CE:
+ EPSILON: 0.4
+
+INPUT:
+ CJ:
+ ENABLED: True
+ BRIGHTNESS: 0.3
+ CONTRAST: 0.3
+ SATURATION: 0.3
+ HUE: 0.1
+
+ RATIO: (1., 1.)
+
+SOLVER:
+ MAX_EPOCH: 100
+
+ BASE_LR: 0.05
+ ETA_MIN_LR: 0.0005
+
+ MOMENTUM: 0.
+
+TEST:
+ RECALLS: [ 1, 2, 4, 8, 16, 32 ]
+
+DATASETS:
+ NAMES: ("Cars196",)
+ TESTS: ("Cars196",)
+
+OUTPUT_DIR: projects/FastRetri/logs/r50-base_cars
diff --git a/projects/FastRetri/configs/cub.yml b/projects/FastRetri/configs/cub.yml
new file mode 100644
index 000000000..0e00a40af
--- /dev/null
+++ b/projects/FastRetri/configs/cub.yml
@@ -0,0 +1,34 @@
+_BASE_: base-image_retri.yml
+
+MODEL:
+ LOSSES:
+ CE:
+ EPSILON: 0.4
+
+INPUT:
+ SIZE_TRAIN: [256,]
+ SIZE_TEST: [256,]
+
+ CJ:
+ ENABLED: True
+ BRIGHTNESS: 0.25
+ CONTRAST: 0.25
+ SATURATION: 0.25
+ HUE: 0.0
+
+SOLVER:
+ MAX_EPOCH: 30
+
+ BASE_LR: 0.02
+ ETA_MIN_LR: 0.00002
+
+ MOMENTUM: 0.
+
+TEST:
+ RECALLS: [ 1, 2, 4, 8, 16, 32 ]
+
+DATASETS:
+ NAMES: ("CUB",)
+ TESTS: ("CUB",)
+
+OUTPUT_DIR: projects/FastRetri/logs/r50-base_cub
diff --git a/projects/FastRetri/configs/inshop.yml b/projects/FastRetri/configs/inshop.yml
new file mode 100644
index 000000000..64e9f23be
--- /dev/null
+++ b/projects/FastRetri/configs/inshop.yml
@@ -0,0 +1,23 @@
+_BASE_: base-image_retri.yml
+
+INPUT:
+ SIZE_TRAIN: [0,]
+ SIZE_TEST: [0,]
+
+SOLVER:
+ MAX_EPOCH: 100
+
+ BASE_LR: 0.003
+ ETA_MIN_LR: 0.00003
+
+ MOMENTUM: 0.99
+ NESTEROV: True
+
+TEST:
+ RECALLS: [ 1, 10, 20, 30, 40, 50 ]
+
+DATASETS:
+ NAMES: ("InShop",)
+ TESTS: ("InShop",)
+
+OUTPUT_DIR: projects/FastRetri/logs/r50-base_inshop
\ No newline at end of file
diff --git a/projects/FastRetri/configs/sop.yml b/projects/FastRetri/configs/sop.yml
new file mode 100644
index 000000000..5fbad5c2a
--- /dev/null
+++ b/projects/FastRetri/configs/sop.yml
@@ -0,0 +1,19 @@
+_BASE_: base-image_retri.yml
+
+SOLVER:
+ MAX_EPOCH: 100
+
+ BASE_LR: 0.003
+ ETA_MIN_LR: 0.00003
+
+ MOMENTUM: 0.99
+ NESTEROV: True
+
+TEST:
+ RECALLS: [1, 10, 100, 1000]
+
+DATASETS:
+ NAMES: ("SOP",)
+ TESTS: ("SOP",)
+
+OUTPUT_DIR: projects/FastRetri/logs/r50-base_sop
\ No newline at end of file
diff --git a/projects/FastRetri/fastretri/__init__.py b/projects/FastRetri/fastretri/__init__.py
new file mode 100644
index 000000000..f5a32b811
--- /dev/null
+++ b/projects/FastRetri/fastretri/__init__.py
@@ -0,0 +1,10 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+from .config import add_retri_config
+from .datasets import *
+from .trainer import Trainer
+from .retri_evaluator import RetriEvaluator
diff --git a/projects/FastRetri/fastretri/config.py b/projects/FastRetri/fastretri/config.py
new file mode 100644
index 000000000..b2cd28042
--- /dev/null
+++ b/projects/FastRetri/fastretri/config.py
@@ -0,0 +1,15 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+
+def add_retri_config(cfg):
+ _C = cfg
+
+ _C.INPUT.CROP_SIZE = 224
+ _C.INPUT.SCALE = (0.16, 1)
+ _C.INPUT.RATIO = (3./4., 4./3.)
+
+ _C.TEST.RECALLS = [1, 2, 4, 8, 16, 32]
diff --git a/projects/FastRetri/fastretri/datasets.py b/projects/FastRetri/fastretri/datasets.py
new file mode 100644
index 000000000..abf876d4a
--- /dev/null
+++ b/projects/FastRetri/fastretri/datasets.py
@@ -0,0 +1,88 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import os
+
+from fastreid.data.datasets import DATASET_REGISTRY
+from fastreid.data.datasets.bases import ImageDataset
+
+__all__ = ["Cars196", "CUB", "SOP", "InShop"]
+
+
+@DATASET_REGISTRY.register()
+class Cars196(ImageDataset):
+ dataset_dir = 'Cars_196'
+ dataset_name = "cars"
+
+ def __init__(self, root='datasets', **kwargs):
+ self.root = root
+ self.dataset_dir = os.path.join(self.root, self.dataset_dir)
+ train_file = os.path.join(self.dataset_dir, "train.txt")
+ test_file = os.path.join(self.dataset_dir, "test.txt")
+
+ required_files = [
+ self.dataset_dir,
+ train_file,
+ test_file,
+ ]
+ self.check_before_run(required_files)
+
+ train = self.process_label_file(train_file, is_train=True)
+ query = self.process_label_file(test_file, is_train=False)
+
+ super(Cars196, self).__init__(train, query, [], **kwargs)
+
+ def process_label_file(self, file, is_train):
+ data_list = []
+ with open(file, 'r') as f:
+ lines = f.read().splitlines()
+
+ for line in lines:
+ img_name, label = line.split(',')
+ if is_train:
+ label = self.dataset_name + '_' + str(label)
+
+ data_list.append((os.path.join(self.dataset_dir, img_name), label, '0'))
+
+ return data_list
+
+
+@DATASET_REGISTRY.register()
+class CUB(Cars196):
+ dataset_dir = "CUB_200_2011"
+ dataset_name = "cub"
+
+
+@DATASET_REGISTRY.register()
+class SOP(Cars196):
+ dataset_dir = "Stanford_Online_Products"
+ dataset_name = "sop"
+
+
+@DATASET_REGISTRY.register()
+class InShop(Cars196):
+ dataset_dir = "InShop"
+ dataset_name = "inshop"
+
+ def __init__(self, root="datasets", **kwargs):
+ self.root = root
+ self.dataset_dir = os.path.join(self.root, self.dataset_dir)
+ train_file = os.path.join(self.dataset_dir, "train.txt")
+ query_file = os.path.join(self.dataset_dir, "test_query.txt")
+ gallery_file = os.path.join(self.dataset_dir, "test_gallery.txt")
+
+ required_files = [
+ train_file,
+ query_file,
+ gallery_file,
+ ]
+ self.check_before_run(required_files)
+
+ train = self.process_label_file(train_file, True)
+ query = self.process_label_file(query_file, False)
+ gallery = self.process_label_file(gallery_file, False)
+
+ super(Cars196, self).__init__(train, query, gallery, **kwargs)
diff --git a/projects/FastRetri/fastretri/retri_evaluator.py b/projects/FastRetri/fastretri/retri_evaluator.py
new file mode 100644
index 000000000..ccf1a55d4
--- /dev/null
+++ b/projects/FastRetri/fastretri/retri_evaluator.py
@@ -0,0 +1,140 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import copy
+import logging
+from collections import OrderedDict
+from typing import List, Optional, Dict
+
+import faiss
+import numpy as np
+import torch
+import torch.nn.functional as F
+
+from fastreid.evaluation import DatasetEvaluator
+from fastreid.utils import comm
+
+logger = logging.getLogger("fastreid.retri_evaluator")
+
+
+@torch.no_grad()
+def recall_at_ks(query_features: torch.Tensor,
+ query_labels: np.ndarray,
+ ks: List[int],
+ gallery_features: Optional[torch.Tensor] = None,
+ gallery_labels: Optional[torch.Tensor] = None,
+ cosine: bool = False) -> Dict[int, float]:
+ """
+ Compute the recall between samples at each k. This function uses about 8GB of memory.
+ Parameters
+ ----------
+ query_features : torch.Tensor
+ Features for each query sample. shape: (num_queries, num_features)
+ query_labels : torch.LongTensor
+ Labels corresponding to the query features. shape: (num_queries,)
+ ks : List[int]
+ Values at which to compute the recall.
+ gallery_features : torch.Tensor
+ Features for each gallery sample. shape: (num_queries, num_features)
+ gallery_labels : torch.LongTensor
+ Labels corresponding to the gallery features. shape: (num_queries,)
+ cosine : bool
+ Use cosine distance between samples instead of euclidean distance.
+ Returns
+ -------
+ recalls : Dict[int, float]
+ Values of the recall at each k.
+ """
+ offset = 0
+ if gallery_features is None and gallery_labels is None:
+ offset = 1
+ gallery_features = query_features
+ gallery_labels = query_labels
+ elif gallery_features is None or gallery_labels is None:
+ raise ValueError('gallery_features and gallery_labels needs to be both None or both Tensors.')
+
+ if cosine:
+ query_features = F.normalize(query_features, p=2, dim=1)
+ gallery_features = F.normalize(gallery_features, p=2, dim=1)
+
+ to_cpu_numpy = lambda x: x.cpu().numpy()
+ query_features, gallery_features = map(to_cpu_numpy, [query_features, gallery_features])
+
+ res = faiss.StandardGpuResources()
+ flat_config = faiss.GpuIndexFlatConfig()
+ flat_config.device = 0
+
+ max_k = max(ks)
+ index_function = faiss.GpuIndexFlatIP if cosine else faiss.GpuIndexFlatL2
+ index = index_function(res, gallery_features.shape[1], flat_config)
+ index.add(gallery_features)
+ closest_indices = index.search(query_features, max_k + offset)[1]
+
+ recalls = {}
+ for k in ks:
+ indices = closest_indices[:, offset:k + offset]
+ recalls[k] = (query_labels[:, None] == gallery_labels[indices]).any(1).mean()
+ return {k: round(v * 100, 2) for k, v in recalls.items()}
+
+
+class RetriEvaluator(DatasetEvaluator):
+ def __init__(self, cfg, num_query, output_dir=None):
+ self.cfg = cfg
+ self._num_query = num_query
+ self._output_dir = output_dir
+
+ self.recalls = cfg.TEST.RECALLS
+
+ self.features = []
+ self.labels = []
+
+ def reset(self):
+ self.features = []
+ self.labels = []
+
+ def process(self, inputs, outputs):
+ self.features.append(outputs.cpu())
+ self.labels.extend(inputs["targets"])
+
+ def evaluate(self):
+ if comm.get_world_size() > 1:
+ comm.synchronize()
+ features = comm.gather(self.features)
+ features = sum(features, [])
+
+ labels = comm.gather(self.labels)
+ labels = sum(labels, [])
+
+ # fmt: off
+ if not comm.is_main_process(): return {}
+ # fmt: on
+ else:
+ features = self.features
+ labels = self.labels
+
+ features = torch.cat(features, dim=0)
+ # query feature, person ids and camera ids
+ query_features = features[:self._num_query]
+ query_labels = np.asarray(labels[:self._num_query])
+
+ # gallery features, person ids and camera ids
+ gallery_features = features[self._num_query:]
+ gallery_pids = np.asarray(labels[self._num_query:])
+
+ self._results = OrderedDict()
+
+ if self._num_query == len(features):
+ cmc = recall_at_ks(query_features, query_labels, self.recalls, cosine=True)
+ else:
+ cmc = recall_at_ks(query_features, query_labels, self.recalls,
+ gallery_features, gallery_pids,
+ cosine=True)
+
+ for r in self.recalls:
+ self._results['Recall@{}'.format(r)] = cmc[r]
+ self._results["metric"] = cmc[self.recalls[0]]
+
+ return copy.deepcopy(self._results)
diff --git a/projects/FastRetri/fastretri/trainer.py b/projects/FastRetri/fastretri/trainer.py
new file mode 100644
index 000000000..9911ddb7d
--- /dev/null
+++ b/projects/FastRetri/fastretri/trainer.py
@@ -0,0 +1,82 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import logging
+
+from torchvision import transforms as T
+
+from fastreid.data import build_reid_train_loader, build_reid_test_loader
+from fastreid.data.transforms import ToTensor
+from fastreid.engine import DefaultTrainer
+
+from .retri_evaluator import RetriEvaluator
+
+
+class Trainer(DefaultTrainer):
+
+ @classmethod
+ def build_train_loader(cls, cfg):
+ """
+ Returns:
+ iterable
+ It now calls :func:`fastreid.data.build_reid_train_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+ logger = logging.getLogger("fastreid.dml_dataset")
+ logger.info("Prepare training set")
+
+ mapper = []
+ if cfg.INPUT.SIZE_TRAIN[0] > 0:
+ if len(cfg.INPUT.SIZE_TRAIN) == 1:
+ resize = cfg.INPUT.SIZE_TRAIN[0]
+ else:
+ resize = cfg.INPUT.SIZE_TRAIN
+ mapper.append(T.Resize(resize, interpolation=3))
+
+ if cfg.INPUT.CJ.ENABLED:
+ cj_params = [
+ cfg.INPUT.CJ.BRIGHTNESS,
+ cfg.INPUT.CJ.CONTRAST,
+ cfg.INPUT.CJ.SATURATION,
+ cfg.INPUT.CJ.HUE
+ ]
+ mapper.append(T.ColorJitter(*cj_params))
+
+ mapper.extend([
+ T.RandomResizedCrop(size=cfg.INPUT.CROP_SIZE, scale=cfg.INPUT.SCALE,
+ ratio=cfg.INPUT.RATIO, interpolation=3),
+ T.RandomHorizontalFlip(),
+ ToTensor(),
+ ])
+ return build_reid_train_loader(cfg, mapper=T.Compose(mapper))
+
+ @classmethod
+ def build_test_loader(cls, cfg, dataset_name):
+ """
+ Returns:
+ iterable
+ It now calls :func:`fastreid.data.build_reid_test_loader`.
+ Overwrite it if you'd like a different data loader.
+ """
+
+ mapper = []
+ if cfg.INPUT.SIZE_TEST[0] > 0:
+ if len(cfg.INPUT.SIZE_TEST) == 1:
+ resize = cfg.INPUT.SIZE_TEST[0]
+ else:
+ resize = cfg.INPUT.SIZE_TEST
+ mapper.append(T.Resize(resize, interpolation=3))
+
+ mapper.extend([
+ T.CenterCrop(size=cfg.INPUT.CROP_SIZE),
+ ToTensor(),
+ ])
+ return build_reid_test_loader(cfg, dataset_name, mapper=T.Compose(mapper))
+
+ @classmethod
+ def build_evaluator(cls, cfg, dataset_name, output_dir=None):
+ data_loader, num_query = cls.build_test_loader(cfg, dataset_name)
+ return data_loader, RetriEvaluator(cfg, num_query, output_dir)
diff --git a/projects/attribute_recognition/train_net.py b/projects/FastRetri/train_net.py
similarity index 84%
rename from projects/attribute_recognition/train_net.py
rename to projects/FastRetri/train_net.py
index 1aa97d6a1..56f225043 100644
--- a/projects/attribute_recognition/train_net.py
+++ b/projects/FastRetri/train_net.py
@@ -1,8 +1,10 @@
+#!/usr/bin/env python
# encoding: utf-8
"""
-@author: xingyu liao
+@author: sherlock
@contact: sherlockliao01@gmail.com
"""
+
import sys
sys.path.append('.')
@@ -11,7 +13,7 @@
from fastreid.engine import default_argument_parser, default_setup, launch
from fastreid.utils.checkpoint import Checkpointer
-from attribute_baseline import *
+from fastretri import *
def setup(args):
@@ -19,7 +21,7 @@ def setup(args):
Create configs and perform basic setups.
"""
cfg = get_cfg()
- add_attr_config(cfg)
+ add_retri_config(cfg)
cfg.merge_from_file(args.config_file)
cfg.merge_from_list(args.opts)
cfg.freeze()
@@ -33,14 +35,15 @@ def main(args):
if args.eval_only:
cfg.defrost()
cfg.MODEL.BACKBONE.PRETRAIN = False
- model = AttrTrainer.build_model(cfg)
+ model = Trainer.build_model(cfg)
Checkpointer(model).load(cfg.MODEL.WEIGHTS) # load trained model
- res = AttrTrainer.test(cfg, model)
+ res = Trainer.test(cfg, model)
return res
- trainer = AttrTrainer(cfg)
+ trainer = Trainer(cfg)
+
trainer.resume_or_load(resume=args.resume)
return trainer.train()
diff --git a/projects/HPOReID/README.md b/projects/FastTune/README.md
similarity index 80%
rename from projects/HPOReID/README.md
rename to projects/FastTune/README.md
index 3cb5c9c92..e4269942b 100644
--- a/projects/HPOReID/README.md
+++ b/projects/FastTune/README.md
@@ -16,7 +16,7 @@ This is an example for tuning `batch_size` and `num_instance` automatically.
To train hyperparameter optimization with BOHB(Bayesian Optimization with HyperBand) search algorithm, run
```bash
-python3 projects/HPOReID/train_hpo.py --config-file projects/HPOReID/configs/baseline.yml --srch-algo "bohb"
+python3 projects/FastTune/tune_net.py --config-file projects/FastTune/configs/search_trial.yml --srch-algo "bohb"
```
## Known issues
diff --git a/projects/HPOReID/hporeid/__init__.py b/projects/FastTune/autotuner/__init__.py
similarity index 100%
rename from projects/HPOReID/hporeid/__init__.py
rename to projects/FastTune/autotuner/__init__.py
diff --git a/projects/HPOReID/hporeid/tune_hooks.py b/projects/FastTune/autotuner/tune_hooks.py
similarity index 68%
rename from projects/HPOReID/hporeid/tune_hooks.py
rename to projects/FastTune/autotuner/tune_hooks.py
index a6fc503a9..3f53a2750 100644
--- a/projects/HPOReID/hporeid/tune_hooks.py
+++ b/projects/FastTune/autotuner/tune_hooks.py
@@ -40,18 +40,13 @@ def _do_eval(self):
self.step += 1
# Here we save a checkpoint. It is automatically registered with
- # Ray Tune and will potentially be passed as the `checkpoint_dir`
+ # RayTune and will potentially be passed as the `checkpoint_dir`
# parameter in future iterations.
with tune.checkpoint_dir(step=self.step) as checkpoint_dir:
- additional_state = {"iteration": int(self.trainer.iter)}
- Checkpointer(
- # Assume you want to save checkpoints together with logs/statistics
- self.trainer.model,
- checkpoint_dir,
- save_to_disk=True,
- optimizer=self.trainer.optimizer,
- scheduler=self.trainer.scheduler,
- ).save(name="checkpoint", **additional_state)
-
- metrics = dict(r1=results['Rank-1'], map=results['mAP'], score=(results['Rank-1'] + results['mAP']) / 2)
+ additional_state = {"epoch": int(self.trainer.epoch)}
+ # Change path of save dir where tune can find
+ self.trainer.checkpointer.save_dir = checkpoint_dir
+ self.trainer.checkpointer.save(name="checkpoint", **additional_state)
+
+ metrics = dict(r1=results["Rank-1"], map=results["mAP"], score=(results["Rank-1"] + results["mAP"]) / 2)
tune.report(**metrics)
diff --git a/projects/HPOReID/configs/baseline.yml b/projects/FastTune/configs/search_trial.yml
similarity index 55%
rename from projects/HPOReID/configs/baseline.yml
rename to projects/FastTune/configs/search_trial.yml
index 8e853aca7..1220dfefa 100644
--- a/projects/HPOReID/configs/baseline.yml
+++ b/projects/FastTune/configs/search_trial.yml
@@ -1,26 +1,27 @@
MODEL:
- META_ARCHITECTURE: "Baseline"
+ META_ARCHITECTURE: Baseline
- FREEZE_LAYERS: ["backbone"]
+ FREEZE_LAYERS: [ backbone ]
BACKBONE:
- NAME: "build_resnet_backbone"
- DEPTH: "34x"
+ NAME: build_resnet_backbone
+ DEPTH: 34x
LAST_STRIDE: 1
FEAT_DIM: 512
- NORM: "BN"
+ NORM: BN
WITH_NL: False
WITH_IBN: True
PRETRAIN: True
- PRETRAIN_PATH: "/export/home/lxy/.cache/torch/checkpoints/resnet34_ibn_a-94bc1577.pth"
+ PRETRAIN_PATH: /export/home/lxy/.cache/torch/checkpoints/resnet34_ibn_a-94bc1577.pth
HEADS:
- NAME: "EmbeddingHead"
- NORM: "BN"
- NECK_FEAT: "after"
+ NUM_CLASSES: 702
+ NAME: EmbeddingHead
+ NORM: BN
+ NECK_FEAT: after
EMBEDDING_DIM: 0
- POOL_LAYER: "gempool"
- CLS_LAYER: "circleSoftmax"
+ POOL_LAYER: gempool
+ CLS_LAYER: circleSoftmax
SCALE: 64
MARGIN: 0.35
@@ -37,14 +38,9 @@ MODEL:
NORM_FEAT: False
SCALE: 1.
- CIRCLE:
- MARGIN: 0.25
- ALPHA: 96
- SCALE: 1.0
-
INPUT:
- SIZE_TRAIN: [256, 128]
- SIZE_TEST: [256, 128]
+ SIZE_TRAIN: [ 256, 128 ]
+ SIZE_TEST: [ 256, 128 ]
DO_AUTOAUG: True
REA:
ENABLED: True
@@ -59,23 +55,23 @@ DATALOADER:
NUM_WORKERS: 8
SOLVER:
- AMP_ENABLED: False
- OPT: "Adam"
- SCHED: "WarmupCosineAnnealingLR"
- MAX_ITER: 60
+ FP16_ENABLED: False
+ MAX_EPOCH: 60
+ OPT: Adam
+ SCHED: CosineAnnealingLR
BASE_LR: 0.00035
BIAS_LR_FACTOR: 1.
WEIGHT_DECAY: 0.0005
WEIGHT_DECAY_BIAS: 0.0
IMS_PER_BATCH: 64
- DELAY_ITERS: 30
+ DELAY_EPOCHS: 30
ETA_MIN_LR: 0.00000077
- FREEZE_ITERS: 5
+ FREEZE_ITERS: 500
- WARMUP_FACTOR: 0.01
- WARMUP_ITERS: 5
+ WARMUP_FACTOR: 0.1
+ WARMUP_EPOCHS: 5
CHECKPOINT_PERIOD: 100
@@ -90,4 +86,4 @@ DATASETS:
CUDNN_BENCHMARK: True
-OUTPUT_DIR: "projects/HPOReID/logs/dukemtmc/r34-ibn_bohb_bsz_num-inst"
+OUTPUT_DIR: projects/FastTune/logs/trial
diff --git a/projects/HPOReID/train_hpo.py b/projects/FastTune/tune_net.py
similarity index 74%
rename from projects/HPOReID/train_hpo.py
rename to projects/FastTune/tune_net.py
index 9ab56bf65..2e5839158 100644
--- a/projects/HPOReID/train_hpo.py
+++ b/projects/FastTune/tune_net.py
@@ -27,16 +27,17 @@
from fastreid.modeling import build_model
from fastreid.engine import DefaultTrainer, default_argument_parser, default_setup
from fastreid.utils.events import CommonMetricPrinter
+from fastreid.utils import comm
from fastreid.utils.file_io import PathManager
-from hporeid import *
+from autotuner import *
-logger = logging.getLogger("fastreid.project.tune")
+logger = logging.getLogger("fastreid.auto_tuner")
ray.init(dashboard_host='127.0.0.1')
-class HyperTuneTrainer(DefaultTrainer):
+class AutoTuner(DefaultTrainer):
def build_hooks(self):
r"""
Build a list of default hooks, including timing, evaluation,
@@ -52,15 +53,12 @@ def build_hooks(self):
hooks.LRScheduler(self.optimizer, self.scheduler),
]
- if cfg.MODEL.FREEZE_LAYERS != [''] and cfg.SOLVER.FREEZE_ITERS > 0:
- freeze_layers = ",".join(cfg.MODEL.FREEZE_LAYERS)
- logger.info(f'Freeze layer group "{freeze_layers}" training for {cfg.SOLVER.FREEZE_ITERS:d} iterations')
- ret.append(hooks.FreezeLayer(
- self.model,
- self.optimizer,
- cfg.MODEL.FREEZE_LAYERS,
- cfg.SOLVER.FREEZE_ITERS,
- ))
+ ret.append(hooks.LayerFreeze(
+ self.model,
+ cfg.MODEL.FREEZE_LAYERS,
+ cfg.SOLVER.FREEZE_ITERS,
+ cfg.SOLVER.FREEZE_FC_ITERS,
+ ))
def test_and_save_results():
self._last_eval_results = self.test(self.cfg, self.model)
@@ -70,8 +68,9 @@ def test_and_save_results():
# we can use the saved checkpoint to debug.
ret.append(TuneReportHook(cfg.TEST.EVAL_PERIOD, test_and_save_results))
- # run writers in the end, so that evaluation metrics are written
- ret.append(hooks.PeriodicWriter([CommonMetricPrinter(self.max_iter)], 200))
+ if comm.is_main_process():
+ # run writers in the end, so that evaluation metrics are written
+ ret.append(hooks.PeriodicWriter([CommonMetricPrinter(self.max_iter)], 200))
return ret
@@ -94,42 +93,37 @@ def setup(args):
def update_config(cfg, config):
+ frozen = cfg.is_frozen()
cfg.defrost()
- # lr, weight decay
# cfg.SOLVER.BASE_LR = config["lr"]
- # cfg.SOLVER.ETA_MIN_LR = config["lr"] * 0.0022
- # cfg.SOLVER.DELAY_ITERS = config["delay_iters"]
+ # cfg.SOLVER.ETA_MIN_LR = config["lr"] * 0.0001
+ # cfg.SOLVER.DELAY_EPOCHS = int(config["delay_epochs"])
+ # cfg.MODEL.LOSSES.CE.SCALE = config["ce_scale"]
+ # cfg.MODEL.HEADS.SCALE = config["circle_scale"]
+ # cfg.MODEL.HEADS.MARGIN = config["circle_margin"]
# cfg.SOLVER.WEIGHT_DECAY = config["wd"]
# cfg.SOLVER.WEIGHT_DECAY_BIAS = config["wd_bias"]
-
- # batch size, number of instance
cfg.SOLVER.IMS_PER_BATCH = config["bsz"]
cfg.DATALOADER.NUM_INSTANCE = config["num_inst"]
- # loss related
- # cfg.MODEL.LOSSES.CE.SCALE = config["ce_scale"]
- # cfg.MODEL.HEADS.SCALE = config["circle_scale"]
- # cfg.MODEL.HEADS.MARGIN = config["circle_margin"]
+ if frozen: cfg.freeze()
- # data augmentation
- # cfg.INPUT.DO_AUTOAUG = config["autoaug_enabled"]
- # cfg.INPUT.CJ.ENABLED = config["cj_enabled"]
return cfg
-def train_reid_tune(config, checkpoint_dir=None, cfg=None):
+def train_tuner(config, checkpoint_dir=None, cfg=None):
update_config(cfg, config)
- trainer = HyperTuneTrainer(cfg)
+ tuner = AutoTuner(cfg)
# Load checkpoint if specific
if checkpoint_dir:
path = os.path.join(checkpoint_dir, "checkpoint.pth")
- checkpoint = trainer.checkpointer.resume_or_load(path, resume=False)
- trainer.start_iter = checkpoint.get("iteration", -1) + 1
+ checkpoint = tuner.checkpointer.resume_or_load(path, resume=False)
+ tuner.start_epoch = checkpoint.get("epoch", -1) + 1
# Regular model training
- trainer.train()
+ tuner.train()
def main(args):
@@ -140,8 +134,8 @@ def main(args):
if args.srch_algo == "hyperopt":
# Create a HyperOpt search space
search_space = {
- # "lr": hp.loguniform("lr", 1e-6, 1e-3),
- # "delay_iters": hp.randint("delay_iters", 40) + 10,
+ # "lr": hp.loguniform("lr", np.log(1e-6), np.log(1e-3)),
+ # "delay_epochs": hp.randint("delay_epochs", 20, 60),
# "wd": hp.uniform("wd", 0, 1e-3),
# "wd_bias": hp.uniform("wd_bias", 0, 1e-3),
"bsz": hp.choice("bsz", [64, 96, 128, 160, 224, 256]),
@@ -149,11 +143,17 @@ def main(args):
# "ce_scale": hp.uniform("ce_scale", 0.1, 1.0),
# "circle_scale": hp.choice("circle_scale", [16, 32, 64, 128, 256]),
# "circle_margin": hp.uniform("circle_margin", 0, 1) * 0.4 + 0.1,
- # "autoaug_enabled": hp.choice("autoaug_enabled", [True, False]),
- # "cj_enabled": hp.choice("cj_enabled", [True, False]),
}
- search_algo = HyperOptSearch(search_space, **exp_metrics)
+ current_best_params = [{
+ "bsz": 0, # index of hp.choice list
+ "num_inst": 3,
+ }]
+
+ search_algo = HyperOptSearch(
+ search_space,
+ points_to_evaluate=current_best_params,
+ **exp_metrics)
if args.pbt:
scheduler = PopulationBasedTraining(
@@ -167,19 +167,18 @@ def main(args):
)
else:
scheduler = ASHAScheduler(
- metric="score",
- mode="max",
- max_t=10,
- grace_period=1,
- reduction_factor=2)
+ grace_period=2,
+ reduction_factor=3,
+ max_t=7,
+ **exp_metrics)
elif args.srch_algo == "bohb":
search_space = CS.ConfigurationSpace()
search_space.add_hyperparameters([
- # CS.UniformFloatHyperparameter(name="lr", lower=1e-6, upper=1e-2, log=True),
- # CS.UniformIntegerHyperparameter(name="delay_iters", lower=20, upper=60),
+ # CS.UniformFloatHyperparameter(name="lr", lower=1e-6, upper=1e-3, log=True),
+ # CS.UniformIntegerHyperparameter(name="delay_epochs", lower=20, upper=60),
# CS.UniformFloatHyperparameter(name="ce_scale", lower=0.1, upper=1.0),
- # CS.UniformIntegerHyperparameter(name="circle_scale", lower=8, upper=256),
+ # CS.UniformIntegerHyperparameter(name="circle_scale", lower=8, upper=128),
# CS.UniformFloatHyperparameter(name="circle_margin", lower=0.1, upper=0.5),
# CS.UniformFloatHyperparameter(name="wd", lower=0, upper=1e-3),
# CS.UniformFloatHyperparameter(name="wd_bias", lower=0, upper=1e-3),
@@ -195,7 +194,7 @@ def main(args):
scheduler = HyperBandForBOHB(
time_attr="training_iteration",
reduction_factor=3,
- max_t=9,
+ max_t=7,
**exp_metrics,
)
@@ -208,9 +207,9 @@ def main(args):
analysis = tune.run(
partial(
- train_reid_tune,
+ train_tuner,
cfg=cfg),
- resources_per_trial={"cpu": 12, "gpu": 1},
+ resources_per_trial={"cpu": 4, "gpu": 1},
search_alg=search_algo,
num_samples=args.num_trials,
scheduler=scheduler,
@@ -234,8 +233,8 @@ def main(args):
if __name__ == "__main__":
parser = default_argument_parser()
- parser.add_argument("--num-trials", type=int, default=12, help="number of tune trials")
- parser.add_argument("--srch-algo", type=str, default="bohb",
+ parser.add_argument("--num-trials", type=int, default=8, help="number of tune trials")
+ parser.add_argument("--srch-algo", type=str, default="hyperopt",
help="search algorithms for hyperparameters search space")
parser.add_argument("--pbt", action="store_true", help="use population based training")
args = parser.parse_args()
diff --git a/projects/README.md b/projects/README.md
index 828a74bc6..af77aa0fc 100644
--- a/projects/README.md
+++ b/projects/README.md
@@ -7,9 +7,13 @@ They are examples of how to use fastrei as a library, to make your projects more
Note that these are research projects, and therefore may not have the same level of support or stability of fastreid.
- [Deep Spatial Feature Reconstruction for Partial Person Re-identification](https://github.com/JDAI-CV/fast-reid/tree/master/projects/PartialReID)
-- [Distillation Person Re-identification](https://github.com/JDAI-CV/fast-reid/tree/master/projects/DistillReID)
- [Black Re-ID: A Head-shoulder Descriptor for the Challenging Problem of Person Re-Identification](https://github.com/JDAI-CV/fast-reid/tree/master/projects/HAA)
-- [Person Attribute Recognition](https://github.com/JDAI-CV/fast-reid/tree/master/projects/attribute_recognition)
+- [Image Classification](https://github.com/JDAI-CV/fast-reid/tree/master/projects/FastCls)
+- [Face Recognition](https://github.com/JDAI-CV/fast-reid/tree/master/projects/FastFace)
+- [Image Retrieval](https://github.com/JDAI-CV/fast-reid/tree/master/projects/FastRetri)
+- [Attribute Recognition](https://github.com/JDAI-CV/fast-reid/tree/master/projects/FastAttr)
+- [Hyper-Parameters Optimization](https://github.com/JDAI-CV/fast-reid/tree/master/projects/FastTune)
+- [Overhaul Distillation](https://github.com/JDAI-CV/fast-reid/tree/master/projects/FastDistill)
# External Projects
diff --git a/projects/attribute_recognition/README.md b/projects/attribute_recognition/README.md
deleted file mode 100644
index 6efac098a..000000000
--- a/projects/attribute_recognition/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Person Attribute Recognition in FastReID
-
-## Training and Evaluation
-
-To train a model, run:
-
-```bash
-python3 projects/PartialReID/train_net.py --config-file --num-gpus 1
-```
-
-For example, to train the attribute recognition network with ResNet-50 Backbone in PA100k dataset,
-one should execute:
-
-```bash
-python3 projects/attribute_recognition/train_net.py --config-file projects/attribute_recognition/configs/pa100.yml --num-gpus 4
-```
-
-## Results
-
-### PA100k
-
-| Method | mA | Accu | Prec | Recall | F1 |
-|:--:|:--:|:--:|:--:|:--:|:--:|
-| Strongbaseline | 77.76 | 77.59 | 88.38 | 84.35 | 86.32 |
-
-More datasets and test results are waiting to add, stay tune!
diff --git a/projects/attribute_recognition/attribute_baseline/attr_trainer.py b/projects/attribute_recognition/attribute_baseline/attr_trainer.py
deleted file mode 100644
index d63f12415..000000000
--- a/projects/attribute_recognition/attribute_baseline/attr_trainer.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# encoding: utf-8
-"""
-@author: xingyu liao
-@contact: sherlockliao01@gmail.com
-"""
-
-import time
-import torch
-from torch.nn.parallel import DistributedDataParallel
-from torch.cuda import amp
-from fastreid.engine import DefaultTrainer
-from .data_build import build_attr_train_loader, build_attr_test_loader
-from .attr_evaluation import AttrEvaluator
-
-
-class AttrTrainer(DefaultTrainer):
- def __init__(self, cfg):
- super().__init__(cfg)
-
- # Sample weight for attributed imbalanced classification
- bce_weight_enabled = self.cfg.MODEL.LOSSES.BCE.WEIGHT_ENABLED
- # fmt: off
- if bce_weight_enabled: self.sample_weights = self.data_loader.dataset.sample_weights.to("cuda")
- else: self.sample_weights = None
- # fmt: on
-
- @classmethod
- def build_train_loader(cls, cfg):
- return build_attr_train_loader(cfg)
-
- @classmethod
- def build_test_loader(cls, cfg, dataset_name):
- return build_attr_test_loader(cfg, dataset_name)
-
- @classmethod
- def build_evaluator(cls, cfg, dataset_name, output_folder=None):
- data_loader = cls.build_test_loader(cfg, dataset_name)
- return data_loader, AttrEvaluator(cfg, output_folder)
-
- def run_step(self):
- r"""
- Implement the attribute model training logic.
- """
- assert self.model.training, "[SimpleTrainer] model was changed to eval mode!"
- start = time.perf_counter()
- """
- If your want to do something with the data, you can wrap the dataloader.
- """
- data = next(self._data_loader_iter)
- data_time = time.perf_counter() - start
-
- """
- If your want to do something with the heads, you can wrap the model.
- """
-
- with amp.autocast(enabled=self.amp_enabled):
- outs = self.model(data)
-
- # Compute loss
- if isinstance(self.model, DistributedDataParallel):
- loss_dict = self.model.module.losses(outs, self.sample_weights)
- else:
- loss_dict = self.model.losses(outs, self.sample_weights)
-
- losses = sum(loss_dict.values())
-
- with torch.cuda.stream(torch.cuda.Stream()):
- metrics_dict = loss_dict
- metrics_dict["data_time"] = data_time
- self._write_metrics(metrics_dict)
- self._detect_anomaly(losses, loss_dict)
-
- """
- If you need accumulate gradients or something similar, you can
- wrap the optimizer with your custom `zero_grad()` method.
- """
- self.optimizer.zero_grad()
-
- if self.amp_enabled:
- self.scaler.scale(losses).backward()
- self.scaler.step(self.optimizer)
- self.scaler.update()
- else:
- losses.backward()
- """
- If you need gradient clipping/scaling or other processing, you can
- wrap the optimizer with your custom `step()` method.
- """
- self.optimizer.step()
diff --git a/projects/attribute_recognition/configs/pa100.yml b/projects/attribute_recognition/configs/pa100.yml
deleted file mode 100644
index b7de5ee8a..000000000
--- a/projects/attribute_recognition/configs/pa100.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-_BASE_: "Base-attribute.yml"
-
-DATASETS:
- NAMES: ("PA100K",)
- TESTS: ("PA100K",)
-
-OUTPUT_DIR: "projects/attribute_recognition/logs/pa100k/strong_baseline"
\ No newline at end of file
diff --git a/tools/deploy/README.md b/tools/deploy/README.md
index 81db7807f..2a340b068 100644
--- a/tools/deploy/README.md
+++ b/tools/deploy/README.md
@@ -82,7 +82,7 @@ This is a tiny example for converting fastreid-baseline in `meta_arch` to Caffe
--input test_data/*.jpg --output caffe_output
```
-5. Run `demo/demo.py` to get fastreid model features with the same input images, then verify that Caffe and PyTorch are computing the same value for the network.
+6. Run `demo/demo.py` to get fastreid model features with the same input images, then verify that Caffe and PyTorch are computing the same value for the network.
```python
np.testing.assert_allclose(torch_out, ort_out, rtol=1e-3, atol=1e-6)
@@ -127,9 +127,9 @@ This is a tiny example for converting fastreid-baseline in `meta_arch` to ONNX m
step-to-step pipeline for trt convert
-This is a tiny example for converting fastreid-baseline in `meta_arch` to TRT model. We use [tiny-tensorrt](https://github.com/zerollzeng/tiny-tensorrt), which is a simple and easy-to-use nvidia TensorRT warpper, to get the model converted to tensorRT.
+This is a tiny example for converting fastreid-baseline in `meta_arch` to TRT model. We use [tiny-tensorrt](https://github.com/zerollzeng/tiny-tensorrt) which is a simple and easy-to-use nvidia TensorRt warpper, to get the model converted to tensorRT.
-First you need to convert the pytorch model to ONNX format following [ONNX Convert](https://github.com/JDAI-CV/fast-reid/tree/master/tools/deploy#onnx-convert), and you need to remember your `output` name. Then you can convert ONNX model to TensorRT following instructions below.
+First you need to convert the pytorch model to ONNX format following [ONNX Convert](https://github.com/JDAI-CV/fast-reid#fastreid), and you need to remember your `output` name. Then you can convert ONNX model to TensorRT following instructions below.
1. Run command line below to get the converted TRT model from ONNX model,
diff --git a/tools/deploy/caffe_export.py b/tools/deploy/caffe_export.py
index e6651b75e..8a558e9ef 100644
--- a/tools/deploy/caffe_export.py
+++ b/tools/deploy/caffe_export.py
@@ -5,9 +5,11 @@
"""
import argparse
+import logging
+import sys
import torch
-import sys
+
sys.path.append('../../')
import pytorch_to_caffe
@@ -17,11 +19,15 @@
from fastreid.utils.checkpoint import Checkpointer
from fastreid.utils.logger import setup_logger
-logger = setup_logger(name='caffe_export')
+from projects.bjzProject.projectbaseline import add_moco_config
+
+setup_logger(name='fastreid')
+logger = logging.getLogger("fastreid.caffe_export")
def setup_cfg(args):
cfg = get_cfg()
+ add_moco_config(cfg)
cfg.merge_from_file(args.config_file)
cfg.merge_from_list(args.opts)
cfg.freeze()
diff --git a/tools/deploy/caffe_inference.py b/tools/deploy/caffe_inference.py
index 295681681..ca2a7a667 100644
--- a/tools/deploy/caffe_inference.py
+++ b/tools/deploy/caffe_inference.py
@@ -88,8 +88,8 @@ def normalize(nparray, order=2, axis=-1):
assert args.input, "The input path(s) was not found"
for path in tqdm.tqdm(args.input):
image = preprocess(path, args.height, args.width)
- net.blobs['blob1'].data[...] = image
- feat = net.forward()['output']
+ net.blobs["blob1"].data[...] = image
+ feat = net.forward()["output"]
feat = normalize(feat[..., 0, 0], axis=1)
np.save(os.path.join(args.output, path.replace('.jpg', '.npy').split('/')[-1]), feat)
diff --git a/tools/deploy/run_export.sh b/tools/deploy/run_export.sh
index 48f5c9bf3..bfaca9fa4 100644
--- a/tools/deploy/run_export.sh
+++ b/tools/deploy/run_export.sh
@@ -1,4 +1,4 @@
-python caffe_export.py --config-file /export/home/lxy/cvpalgo-fast-reid/logs/dukemtmc/R34/config.yaml \
---name "baseline_R34" \
---output logs/caffe_R34 \
---opts MODEL.WEIGHTS /export/home/lxy/cvpalgo-fast-reid/logs/dukemtmc/R34/model_final.pth
+python3 caffe_export.py --config-file /export/home/lxy/cvpalgo-fast-reid/projects/bjzProject/configs/r34.yml \
+--name "r34-0603" \
+--output logs/caffe_r34-0603 \
+--opts MODEL.WEIGHTS /export/home/lxy/cvpalgo-fast-reid/logs/bjz/sbs_R34_bjz_0603_8x32/model_final.pth
diff --git a/tools/deploy/trt_export.py b/tools/deploy/trt_export.py
index edc9f1e92..781f46d97 100644
--- a/tools/deploy/trt_export.py
+++ b/tools/deploy/trt_export.py
@@ -8,6 +8,7 @@
import os
import numpy as np
import sys
+import tensorrt as trt
sys.path.append('../../')
sys.path.append("/export/home/lxy/runtimelib-tensorrt-tiny/build")
@@ -53,6 +54,95 @@ def get_parser():
return parser
+def onnx2trt(
+ model,
+ save_path,
+ log_level='ERROR',
+ max_batch_size=1,
+ max_workspace_size=1,
+ fp16_mode=False,
+ strict_type_constraints=False,
+ int8_mode=False,
+ int8_calibrator=None,
+):
+ """build TensorRT model from onnx model.
+ Args:
+ model (string or io object): onnx model name
+ log_level (string, default is ERROR): tensorrt logger level, now
+ INTERNAL_ERROR, ERROR, WARNING, INFO, VERBOSE are support.
+ max_batch_size (int, default=1): The maximum batch size which can be used at execution time, and also the
+ batch size for which the ICudaEngine will be optimized.
+ max_workspace_size (int, default is 1): The maximum GPU temporary memory which the ICudaEngine can use at
+ execution time. default is 1GB.
+ fp16_mode (bool, default is False): Whether or not 16-bit kernels are permitted. During engine build
+ fp16 kernels will also be tried when this mode is enabled.
+ strict_type_constraints (bool, default is False): When strict type constraints is set, TensorRT will choose
+ the type constraints that conforms to type constraints. If the flag is not enabled higher precision
+ implementation may be chosen if it results in higher performance.
+ int8_mode (bool, default is False): Whether Int8 mode is used.
+ int8_calibrator (volksdep.calibrators.base.BaseCalibrator, default is None): calibrator for int8 mode,
+ if None, default calibrator will be used as calibration data.
+ """
+
+ logger = trt.Logger(getattr(trt.Logger, log_level))
+ builder = trt.Builder(logger)
+
+ network = builder.create_network(1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
+ parser = trt.OnnxParser(network, logger)
+ if isinstance(model, str):
+ with open(model, 'rb') as f:
+ flag = parser.parse(f.read())
+ else:
+ flag = parser.parse(model.read())
+ if not flag:
+ for error in range(parser.num_errors):
+ print(parser.get_error(error))
+
+ # re-order output tensor
+ output_tensors = [network.get_output(i) for i in range(network.num_outputs)]
+ [network.unmark_output(tensor) for tensor in output_tensors]
+ for tensor in output_tensors:
+ identity_out_tensor = network.add_identity(tensor).get_output(0)
+ identity_out_tensor.name = 'identity_{}'.format(tensor.name)
+ network.mark_output(tensor=identity_out_tensor)
+
+ builder.max_batch_size = max_batch_size
+
+ config = builder.create_builder_config()
+ config.max_workspace_size = max_workspace_size * (1 << 25)
+ if fp16_mode:
+ config.set_flag(trt.BuilderFlag.FP16)
+ if strict_type_constraints:
+ config.set_flag(trt.BuilderFlag.STRICT_TYPES)
+ # if int8_mode:
+ # config.set_flag(trt.BuilderFlag.INT8)
+ # if int8_calibrator is None:
+ # shapes = [(1,) + network.get_input(i).shape[1:] for i in range(network.num_inputs)]
+ # dummy_data = utils.gen_ones_data(shapes)
+ # int8_calibrator = EntropyCalibrator2(CustomDataset(dummy_data))
+ # config.int8_calibrator = int8_calibrator
+
+ # set dynamic batch size profile
+ profile = builder.create_optimization_profile()
+ for i in range(network.num_inputs):
+ tensor = network.get_input(i)
+ name = tensor.name
+ shape = tensor.shape[1:]
+ min_shape = (1,) + shape
+ opt_shape = ((1 + max_batch_size) // 2,) + shape
+ max_shape = (max_batch_size,) + shape
+ profile.set_shape(name, min_shape, opt_shape, max_shape)
+ config.add_optimization_profile(profile)
+
+ engine = builder.build_engine(network, config)
+
+ with open(save_path, 'wb') as f:
+ f.write(engine.serialize())
+ # trt_model = TRTModel(engine)
+
+ # return trt_model
+
+
def export_trt_model(onnxModel, engineFile, input_numpy_array):
r"""
Export a model to trt format.
@@ -61,9 +151,9 @@ def export_trt_model(onnxModel, engineFile, input_numpy_array):
trt = pytrt.Trt()
customOutput = []
- maxBatchSize = 1
+ maxBatchSize = 8
calibratorData = []
- mode = 2
+ mode = 0
trt.CreateEngine(onnxModel, engineFile, customOutput, maxBatchSize, mode, calibratorData)
trt.DoInference(input_numpy_array) # slightly different from c++
return 0
@@ -72,11 +162,12 @@ def export_trt_model(onnxModel, engineFile, input_numpy_array):
if __name__ == '__main__':
args = get_parser().parse_args()
- inputs = np.zeros(shape=(32, args.height, args.width, 3))
+ inputs = np.zeros(shape=(1, args.height, args.width, 3))
onnxModel = args.onnx_model
engineFile = os.path.join(args.output, args.name+'.engine')
PathManager.mkdirs(args.output)
- export_trt_model(onnxModel, engineFile, inputs)
+ onnx2trt(onnxModel, engineFile)
+ # export_trt_model(onnxModel, engineFile, inputs)
logger.info(f"Export trt model in {args.output} successfully!")
diff --git a/tools/deploy/trt_inference.py b/tools/deploy/trt_inference.py
index 0775364e4..db4fc33be 100644
--- a/tools/deploy/trt_inference.py
+++ b/tools/deploy/trt_inference.py
@@ -10,7 +10,7 @@
import cv2
import numpy as np
-# import tqdm
+import tqdm
sys.path.append("/export/home/lxy/runtimelib-tensorrt-tiny/build")
@@ -91,7 +91,7 @@ def normalize(nparray, order=2, axis=-1):
if os.path.isdir(args.input[0]):
args.input = glob.glob(os.path.expanduser(args.input[0]))
assert args.input, "The input path(s) was not found"
- for path in args.input:
+ for path in tqdm.tqdm(args.input):
input_numpy_array = preprocess(path, args.height, args.width)
trt.DoInference(input_numpy_array)
feat = trt.GetOutput(args.output_name)
diff --git a/tools/plain_train_net.py b/tools/plain_train_net.py
new file mode 100644
index 000000000..2d6236060
--- /dev/null
+++ b/tools/plain_train_net.py
@@ -0,0 +1,212 @@
+# encoding: utf-8
+"""
+@author: xingyu liao
+@contact: sherlockliao01@gmail.com
+"""
+
+import logging
+import os
+import sys
+from collections import OrderedDict
+
+import torch
+
+try:
+ import apex
+ from apex import amp
+ from apex.parallel import DistributedDataParallel
+except ImportError:
+ raise ImportError("Please install apex from https://www.github.com/nvidia/apex to run this example if you want to"
+ "train with DDP")
+
+sys.path.append('.')
+
+from fastreid.config import get_cfg
+from fastreid.data import build_reid_test_loader, build_reid_train_loader
+from fastreid.engine import default_argument_parser, default_setup, launch
+from fastreid.modeling import build_model
+from fastreid.solver import build_lr_scheduler, build_optimizer
+from fastreid.evaluation import inference_on_dataset, print_csv_format, ReidEvaluator
+from fastreid.utils.checkpoint import Checkpointer, PeriodicCheckpointer
+from fastreid.utils import comm
+from fastreid.utils.events import (
+ CommonMetricPrinter,
+ EventStorage,
+ JSONWriter,
+ TensorboardXWriter
+)
+
+logger = logging.getLogger("fastreid")
+
+
+def get_evaluator(cfg, dataset_name, output_dir=None):
+ data_loader, num_query = build_reid_test_loader(cfg, dataset_name)
+ return data_loader, ReidEvaluator(cfg, num_query, output_dir)
+
+
+def do_test(cfg, model):
+ results = OrderedDict()
+ for idx, dataset_name in enumerate(cfg.DATASETS.TESTS):
+ logger.info("Prepare testing set")
+ try:
+ data_loader, evaluator = get_evaluator(cfg, dataset_name)
+ except NotImplementedError:
+ logger.warn(
+ "No evaluator found. implement its `build_evaluator` method."
+ )
+ results[dataset_name] = {}
+ continue
+ results_i = inference_on_dataset(model, data_loader, evaluator, flip_test=cfg.TEST.FLIP_ENABLED)
+ results[dataset_name] = results_i
+
+ if comm.is_main_process():
+ assert isinstance(
+ results, dict
+ ), "Evaluator must return a dict on the main process. Got {} instead.".format(
+ results
+ )
+ print_csv_format(results)
+
+ if len(results) == 1: results = list(results.values())[0]
+
+ return results
+
+
+def do_train(cfg, model, resume=False):
+ data_loader = build_reid_train_loader(cfg)
+
+ model.train()
+ optimizer = build_optimizer(cfg, model)
+
+ optimizer_ckpt = dict(optimizer=optimizer)
+
+ scheduler = build_lr_scheduler(cfg, optimizer)
+
+ checkpointer = Checkpointer(
+ model,
+ cfg.OUTPUT_DIR,
+ save_to_disk=comm.is_main_process(),
+ **optimizer_ckpt,
+ **scheduler
+ )
+
+ iters_per_epoch = len(data_loader.dataset) // cfg.SOLVER.IMS_PER_BATCH
+
+ start_epoch = (
+ checkpointer.resume_or_load(cfg.MODEL.WEIGHTS, resume=resume).get("epoch", -1) + 1
+ )
+ iteration = start_iter = start_epoch * iters_per_epoch
+
+ max_epoch = cfg.SOLVER.MAX_EPOCH
+ max_iter = max_epoch * iters_per_epoch
+ warmup_epochs = cfg.SOLVER.WARMUP_EPOCHS
+ delay_epochs = cfg.SOLVER.DELAY_EPOCHS
+
+ periodic_checkpointer = PeriodicCheckpointer(checkpointer, cfg.SOLVER.CHECKPOINT_PERIOD, max_epoch)
+
+ writers = (
+ [
+ CommonMetricPrinter(max_iter),
+ JSONWriter(os.path.join(cfg.OUTPUT_DIR, "metrics.json")),
+ TensorboardXWriter(cfg.OUTPUT_DIR)
+ ]
+ if comm.is_main_process()
+ else []
+ )
+
+ # compared to "train_net.py", we do not support some hooks, such as
+ # accurate timing, FP16 training and precise BN here,
+ # because they are not trivial to implement in a small training loop
+ logger.info("Start training from epoch {}".format(start_epoch))
+ with EventStorage(start_iter) as storage:
+ for epoch in range(start_epoch, max_epoch):
+ storage.epoch = epoch
+ for data, _ in zip(data_loader, range(iters_per_epoch)):
+ storage.iter = iteration
+
+ loss_dict = model(data)
+ losses = sum(loss_dict.values())
+ assert torch.isfinite(losses).all(), loss_dict
+
+ loss_dict_reduced = {k: v.item() for k, v in comm.reduce_dict(loss_dict).items()}
+ losses_reduced = sum(loss for loss in loss_dict_reduced.values())
+ if comm.is_main_process():
+ storage.put_scalars(total_loss=losses_reduced, **loss_dict_reduced)
+
+ optimizer.zero_grad()
+ losses.backward()
+ optimizer.step()
+ storage.put_scalar("lr", optimizer.param_groups[0]["lr"], smoothing_hint=False)
+
+ if iteration - start_iter > 5 and (
+ (iteration + 1) % 200 == 0 or iteration == max_iter - 1
+ ):
+ for writer in writers:
+ writer.write()
+
+ iteration += 1
+
+ # Write metrics after each epoch
+ for writer in writers:
+ writer.write()
+
+ if (epoch + 1) <= warmup_epochs:
+ scheduler["warmup_sched"].step()
+ elif (epoch + 1) >= delay_epochs:
+ scheduler["lr_sched"].step()
+
+ if (
+ cfg.TEST.EVAL_PERIOD > 0
+ and (epoch + 1) % cfg.TEST.EVAL_PERIOD == 0
+ and epoch != max_iter - 1
+ ):
+ do_test(cfg, model)
+ # Compared to "train_net.py", the test results are not dumped to EventStorage
+
+ periodic_checkpointer.step(epoch)
+
+
+def setup(args):
+ """
+ Create configs and perform basic setups.
+ """
+ cfg = get_cfg()
+ cfg.merge_from_file(args.config_file)
+ cfg.merge_from_list(args.opts)
+ cfg.freeze()
+ default_setup(cfg, args)
+ return cfg
+
+
+def main(args):
+ cfg = setup(args)
+
+ model = build_model(cfg)
+ logger.info("Model:\n{}".format(model))
+ if args.eval_only:
+ cfg.defrost()
+ cfg.MODEL.BACKBONE.PRETRAIN = False
+
+ Checkpointer(model).load(cfg.MODEL.WEIGHTS) # load trained model
+
+ return do_test(cfg, model)
+
+ distributed = comm.get_world_size() > 1
+ if distributed:
+ model = DistributedDataParallel(model, delay_allreduce=True)
+
+ do_train(cfg, model, resume=args.resume)
+ return do_test(cfg, model)
+
+
+if __name__ == "__main__":
+ args = default_argument_parser().parse_args()
+ print("Command Line Args:", args)
+ launch(
+ main,
+ args.num_gpus,
+ num_machines=args.num_machines,
+ machine_rank=args.machine_rank,
+ dist_url=args.dist_url,
+ args=(args,),
+ )