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-101IBN-50IBN-34IBN-18
Teacher
(BagTricks)
IBN-10190.8(80.8)/0.3395s90.8(81.1)/0.1984s89.63(78.9)/0.1760s86.96(75.75)/0.0854s
IBN-50-89.8(79.8)/0.2264s88.82(78.9)/0.1761s87.75(76.18)/0.0838s
IBN-34--88.64(76.4)/0.1766s87.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-101IBN-50IBN-34IBN-18
Teacher
(BagTricks)
IBN-10195.43(88.95)/0.2698s95.19(89.52)/0.1791s94.51(87.82)/0.0869s93.85(85.77)/0.0612s
IBN-50-95.25(88.16)/0.1823s95.13(87.28)/0.0863s94.18(85.81)/0.0614s
IBN-34-94.63(84.91)/0.0860s93.71(85.20)/0.0620s
IBN-18---92.87(81.22)/0.0615s
Average Q.Time0.2698s0.1807s0.0864s0.0616s
- -### MSMT17 - -
Rank-1 (mAP) /
Q.Time/batch(128)
Student (BagTricks)
IBN-101IBN-50IBN-34IBN-18
Teacher
(BagTricks)
IBN-10181.95(60.51)/0.2693s82.37(62.08)/0.1792s81.07(58.56)/0.0872s77.77(52.77)/0.0610s
IBN-50-80.18(57.80)/0.1789s81.28(58.27)/0.0863s78.11(53.10)/0.0623s
IBN-34-78.27(53.41)/0.0873s77.65(52.82)/0.0615s
IBN-18---74.11(47.26)/0.0621s
Average Q.Time0.2693s0.1801s0.0868s0.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,), + )