├── utils ├── __init__.py ├── meter.py ├── logger.py ├── iotools.py └── reranking.py ├── SOLIDER ├── dataset │ ├── __init__.py │ ├── pedes_attr │ │ ├── __init__.py │ │ ├── preprocess │ │ │ ├── __init__.py │ │ │ ├── format_rap.py │ │ │ ├── format_pa100k.py │ │ │ ├── format_peta.py │ │ │ └── format_rap2.py │ │ ├── pedes.py │ │ └── annotation.md │ └── augmentation.py ├── losses │ ├── __init__.py │ ├── bceloss.py │ ├── label_smoothing.py │ └── scaledbceloss.py ├── metrics │ ├── __init__.py │ ├── pedestrian_metrics.py │ └── ml_metrics.py ├── optim │ ├── __init__.py │ └── adamw.py ├── tools │ ├── __init__.py │ ├── distributed.py │ ├── vis.py │ └── function.py ├── models │ ├── __init__.py │ ├── backbone │ │ ├── __init__.py │ │ └── checkpoints │ │ │ └── __init__.py │ ├── model_factory.py │ ├── registry.py │ ├── base_block.py │ └── model_ema.py ├── scheduler │ ├── __init__.py │ ├── scheduler_factory.py │ ├── cosine_lr_weihua.py │ └── scheduler.py ├── configs │ ├── __init__.py │ ├── pa100k.yaml │ ├── rap_zs.yaml │ ├── peta_zs.yaml │ └── default.py ├── requirements.txt ├── run.sh ├── README.md ├── demo_PETA_Ce.py ├── demo_PETA_ltcc.py ├── batch_engine.py └── demo_PETA_prcc.py ├── model ├── __init__.py ├── build.py ├── meta_encoder.py └── MHSA.py ├── processor └── __init__.py ├── figs ├── sota.png ├── ablation.png └── framework.png ├── loss ├── __init__.py ├── softmax_loss.py ├── center_loss.py ├── arcface.py ├── make_loss.py └── triplet_loss.py ├── solver ├── __init__.py ├── scheduler_factory.py ├── make_optimizer.py ├── lr_scheduler.py ├── cosine_lr.py └── scheduler.py ├── config ├── __init__.py └── defaults.py ├── configs ├── ltcc │ ├── eva02_l_maskmeta.yml │ └── eva02_l_maskmeta_random.yml ├── prcc │ ├── eva02_l_maskmeta.yml │ └── eva02_l_maskmeta_random.yml ├── last │ └── eva02_l_maskmeta_random.yml └── Celeb_light │ └── eva02_l_maskmeta_random.yml ├── dist_train.sh ├── test.py ├── train.py ├── tools ├── utils.py └── eval_metrics.py ├── data ├── img_transforms.py ├── dataset_loader.py ├── __init__.py └── dataloader.py ├── README.md ├── .gitignore └── requirements.txt /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/dataset/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/losses/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/optim/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/models/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /SOLIDER/scheduler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/models/backbone/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SOLIDER/models/backbone/checkpoints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- 1 | from .build import build_model -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/preprocess/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /processor/__init__.py: -------------------------------------------------------------------------------- 1 | from .processor import do_train, do_inference -------------------------------------------------------------------------------- /figs/sota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moon-wh/MADE/HEAD/figs/sota.png -------------------------------------------------------------------------------- /figs/ablation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moon-wh/MADE/HEAD/figs/ablation.png -------------------------------------------------------------------------------- /loss/__init__.py: -------------------------------------------------------------------------------- 1 | from .make_loss import make_loss 2 | from .arcface import ArcFace -------------------------------------------------------------------------------- /figs/framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moon-wh/MADE/HEAD/figs/framework.png -------------------------------------------------------------------------------- /SOLIDER/configs/__init__.py: -------------------------------------------------------------------------------- 1 | from .default import _C as cfg 2 | from .default import update_config -------------------------------------------------------------------------------- /solver/__init__.py: -------------------------------------------------------------------------------- 1 | from .lr_scheduler import WarmupMultiStepLR 2 | from .make_optimizer import make_optimizer -------------------------------------------------------------------------------- /SOLIDER/requirements.txt: -------------------------------------------------------------------------------- 1 | scipy==1.4.1 2 | torch==1.4.0 3 | torchvision==0.5.0 4 | tqdm==4.43.0 5 | easydict==1.9 6 | numpy==1.18.1 7 | Pillow==7.1.2 8 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | @author: sherlock 4 | @contact: sherlockliao01@gmail.com 5 | """ 6 | 7 | from .defaults import _C as cfg 8 | from .defaults import _C as cfg_test 9 | -------------------------------------------------------------------------------- /SOLIDER/run.sh: -------------------------------------------------------------------------------- 1 | # for single gpu 2 | CUDA_VISIBLE_DEVICES=0 python train.py --cfg ./configs/peta_zs.yaml 3 | 4 | # for multi-gpu 5 | #CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port=1233 train.py --cfg ./configs/pa100k.yaml 6 | -------------------------------------------------------------------------------- /utils/meter.py: -------------------------------------------------------------------------------- 1 | class AverageMeter(object): 2 | """Computes and stores the average and current value""" 3 | 4 | def __init__(self): 5 | self.val = 0 6 | self.avg = 0 7 | self.sum = 0 8 | self.count = 0 9 | 10 | def reset(self): 11 | self.val = 0 12 | self.avg = 0 13 | self.sum = 0 14 | self.count = 0 15 | 16 | def update(self, val, n=1): 17 | self.val = val 18 | self.sum += val * n 19 | self.count += n 20 | self.avg = self.sum / self.count -------------------------------------------------------------------------------- /SOLIDER/models/model_factory.py: -------------------------------------------------------------------------------- 1 | from models.registry import BACKBONE 2 | from models.registry import CLASSIFIER 3 | from models.registry import LOSSES 4 | 5 | 6 | model_dict = { 7 | 'swin_t': 768, 8 | 'swin_s': 768, 9 | 'swin_b': 1024, 10 | 11 | } 12 | def build_backbone(key, multi_scale=False): 13 | 14 | 15 | model = BACKBONE[key]() 16 | output_d = model_dict[key] 17 | 18 | return model, output_d 19 | 20 | 21 | def build_classifier(key): 22 | 23 | return CLASSIFIER[key] 24 | 25 | 26 | def build_loss(key): 27 | 28 | return LOSSES[key] 29 | 30 | -------------------------------------------------------------------------------- /model/build.py: -------------------------------------------------------------------------------- 1 | from .eva_meta import eva02_large_patch14_clip_224_meta,eva02_base_patch16_clip_224_meta 2 | from .eva_original import eva02_large_patch14_clip_224 3 | __factory = { 4 | 'eva02_l':eva02_large_patch14_clip_224, 5 | 'eva02_l_meta': eva02_large_patch14_clip_224_meta, 6 | } 7 | 8 | def build_model(config,num_classes): 9 | model_type = config.MODEL.TYPE 10 | if model_type == 'eva02_meta': 11 | model = __factory[config.MODEL.NAME](pretrained=True, num_classes=num_classes, meta_dims=config.MODEL.META_DIMS) 12 | else: 13 | model = __factory[config.MODEL.NAME](num_classes) 14 | return model 15 | -------------------------------------------------------------------------------- /configs/ltcc/eva02_l_maskmeta.yml: -------------------------------------------------------------------------------- 1 | MODEL: 2 | TYPE: eva02_meta 3 | NAME: eva02_l_meta 4 | META_DIMS: [ 105, ] 5 | METRIC_LOSS_TYPE: 'triplet' 6 | IF_LABELSMOOTH: 'off' 7 | IF_WITH_CENTER: 'no' 8 | NO_MARGIN: True 9 | DEVICE_ID: ('6') 10 | MASK_META: True 11 | ADD_META: True 12 | 13 | DATA: 14 | IMG_HEIGHT: 224 15 | IMG_WIDTH: 224 16 | DATASET: 'ltcc' 17 | 18 | 19 | SOLVER: 20 | OPTIMIZER_NAME: 'SGD' 21 | MAX_EPOCHS: 60 22 | BASE_LR: 2e-5 23 | WARMUP_METHOD: 'linear' 24 | LARGE_FC_LR: False 25 | CHECKPOINT_PERIOD: 60 26 | LOG_PERIOD: 50 27 | EVAL_PERIOD: 1 28 | WEIGHT_DECAY: 0.05 29 | WEIGHT_DECAY_BIAS: 0.05 30 | BIAS_LR_FACTOR: 2 31 | 32 | TEST: 33 | WEIGHT: '' 34 | TYPE: 'image_only' 35 | FEAT_NORM: 'yes' 36 | OUTPUT_DIR: '' 37 | 38 | 39 | -------------------------------------------------------------------------------- /configs/prcc/eva02_l_maskmeta.yml: -------------------------------------------------------------------------------- 1 | MODEL: 2 | TYPE: eva02_meta 3 | NAME: eva02_l_meta 4 | META_DIMS: [ 105, ] 5 | METRIC_LOSS_TYPE: 'triplet' 6 | IF_LABELSMOOTH: 'off' 7 | IF_WITH_CENTER: 'no' 8 | NO_MARGIN: True 9 | DEVICE_ID: ('6') 10 | MASK_META: True 11 | ADD_META: True 12 | 13 | DATA: 14 | IMG_HEIGHT: 224 15 | IMG_WIDTH: 224 16 | DATASET: 'prcc' 17 | 18 | 19 | SOLVER: 20 | OPTIMIZER_NAME: 'SGD' 21 | MAX_EPOCHS: 60 22 | BASE_LR: 2e-5 23 | WARMUP_METHOD: 'linear' 24 | LARGE_FC_LR: False 25 | CHECKPOINT_PERIOD: 60 26 | LOG_PERIOD: 50 27 | EVAL_PERIOD: 1 28 | WEIGHT_DECAY: 0.05 29 | WEIGHT_DECAY_BIAS: 0.05 30 | BIAS_LR_FACTOR: 2 31 | 32 | TEST: 33 | WEIGHT: '' 34 | TYPE: 'image_only' 35 | FEAT_NORM: 'yes' 36 | OUTPUT_DIR: '' 37 | 38 | 39 | -------------------------------------------------------------------------------- /model/meta_encoder.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | class ResNormLayer(nn.Module): 3 | def __init__(self, linear_size,): 4 | super(ResNormLayer, self).__init__() 5 | self.l_size = linear_size 6 | self.nonlin1 = nn.ReLU(inplace=True) 7 | self.nonlin2 = nn.ReLU(inplace=True) 8 | self.norm_fn1 = nn.LayerNorm(self.l_size) 9 | self.norm_fn2 = nn.LayerNorm(self.l_size) 10 | self.w1 = nn.Linear(self.l_size, self.l_size) 11 | self.w2 = nn.Linear(self.l_size, self.l_size) 12 | 13 | def forward(self, x): 14 | y = self.w1(x) 15 | y = self.nonlin1(y) 16 | y = self.norm_fn1(y) 17 | y = self.w2(y) 18 | y = self.nonlin2(y) 19 | y = self.norm_fn2(y) 20 | out = x + y 21 | return out 22 | -------------------------------------------------------------------------------- /configs/ltcc/eva02_l_maskmeta_random.yml: -------------------------------------------------------------------------------- 1 | MODEL: 2 | TYPE: eva02_meta 3 | NAME: eva02_l_meta 4 | META_DIMS: [ 105, ] 5 | METRIC_LOSS_TYPE: 'triplet' 6 | IF_LABELSMOOTH: 'off' 7 | IF_WITH_CENTER: 'no' 8 | NO_MARGIN: True 9 | DEVICE_ID: ('6') 10 | ADD_META: True 11 | MASK_META: True 12 | 13 | DATA: 14 | IMG_HEIGHT: 224 15 | IMG_WIDTH: 224 16 | DATASET: 'ltcc' 17 | RANDOM_NOISE: True 18 | 19 | SOLVER: 20 | OPTIMIZER_NAME: 'SGD' 21 | MAX_EPOCHS: 60 22 | BASE_LR: 2e-5 23 | WARMUP_METHOD: 'linear' 24 | LARGE_FC_LR: False 25 | CHECKPOINT_PERIOD: 60 26 | LOG_PERIOD: 50 27 | EVAL_PERIOD: 1 28 | WEIGHT_DECAY: 0.05 29 | WEIGHT_DECAY_BIAS: 0.05 30 | BIAS_LR_FACTOR: 2 31 | 32 | TEST: 33 | WEIGHT: '' 34 | TYPE: 'image_only' 35 | FEAT_NORM: 'yes' 36 | OUTPUT_DIR: '' 37 | 38 | 39 | -------------------------------------------------------------------------------- /configs/last/eva02_l_maskmeta_random.yml: -------------------------------------------------------------------------------- 1 | MODEL: 2 | TYPE: eva02_meta 3 | NAME: eva02_l_meta 4 | META_DIMS: [ 105, ] 5 | METRIC_LOSS_TYPE: 'triplet' 6 | IF_LABELSMOOTH: 'off' 7 | IF_WITH_CENTER: 'no' 8 | NO_MARGIN: True 9 | DEVICE_ID: ('6') 10 | MASK_META: True 11 | ADD_META: True 12 | 13 | DATA: 14 | IMG_HEIGHT: 224 15 | IMG_WIDTH: 224 16 | DATASET: 'last' 17 | RANDOM_NOISE: True 18 | 19 | SOLVER: 20 | OPTIMIZER_NAME: 'SGD' 21 | MAX_EPOCHS: 60 22 | BASE_LR: 2e-5 23 | WARMUP_METHOD: 'linear' 24 | LARGE_FC_LR: False 25 | CHECKPOINT_PERIOD: 60 26 | LOG_PERIOD: 50 27 | EVAL_PERIOD: 1 28 | WEIGHT_DECAY: 0.05 29 | WEIGHT_DECAY_BIAS: 0.05 30 | BIAS_LR_FACTOR: 2 31 | 32 | TEST: 33 | WEIGHT: '' 34 | FEAT_NORM: 'yes' 35 | TYPE: 'image_only' 36 | OUTPUT_DIR: '../MADE/logs' 37 | 38 | 39 | -------------------------------------------------------------------------------- /configs/prcc/eva02_l_maskmeta_random.yml: -------------------------------------------------------------------------------- 1 | MODEL: 2 | TYPE: eva02_meta 3 | NAME: eva02_l_meta 4 | META_DIMS: [ 105, ] 5 | METRIC_LOSS_TYPE: 'triplet' 6 | IF_LABELSMOOTH: 'off' 7 | IF_WITH_CENTER: 'no' 8 | NO_MARGIN: True 9 | DEVICE_ID: ('6') 10 | ADD_META: True 11 | MASK_META: True 12 | 13 | DATA: 14 | IMG_HEIGHT: 224 15 | IMG_WIDTH: 224 16 | DATASET: 'prcc' 17 | RANDOM_NOISE: True 18 | 19 | SOLVER: 20 | OPTIMIZER_NAME: 'SGD' 21 | MAX_EPOCHS: 60 22 | BASE_LR: 2e-5 23 | WARMUP_METHOD: 'linear' 24 | LARGE_FC_LR: False 25 | CHECKPOINT_PERIOD: 60 26 | LOG_PERIOD: 50 27 | EVAL_PERIOD: 1 28 | WEIGHT_DECAY: 0.05 29 | WEIGHT_DECAY_BIAS: 0.05 30 | BIAS_LR_FACTOR: 2 31 | 32 | TEST: 33 | WEIGHT: '' 34 | TYPE: 'image_only' 35 | FEAT_NORM: 'yes' 36 | OUTPUT_DIR: '../MADE/logs' 37 | 38 | 39 | -------------------------------------------------------------------------------- /configs/Celeb_light/eva02_l_maskmeta_random.yml: -------------------------------------------------------------------------------- 1 | MODEL: 2 | TYPE: eva02_meta 3 | NAME: eva02_l_meta 4 | META_DIMS: [ 105, ] 5 | METRIC_LOSS_TYPE: 'triplet' 6 | IF_LABELSMOOTH: 'off' 7 | IF_WITH_CENTER: 'no' 8 | NO_MARGIN: True 9 | DEVICE_ID: ('6') 10 | MASK_META: True 11 | ADD_META: True 12 | 13 | DATA: 14 | IMG_HEIGHT: 224 15 | IMG_WIDTH: 224 16 | DATASET: 'celeb_light' 17 | RANDOM_NOISE: True 18 | 19 | SOLVER: 20 | OPTIMIZER_NAME: 'SGD' 21 | MAX_EPOCHS: 60 22 | BASE_LR: 2e-5 23 | WARMUP_METHOD: 'linear' 24 | LARGE_FC_LR: False 25 | CHECKPOINT_PERIOD: 60 26 | LOG_PERIOD: 50 27 | EVAL_PERIOD: 1 28 | WEIGHT_DECAY: 0.05 29 | WEIGHT_DECAY_BIAS: 0.05 30 | BIAS_LR_FACTOR: 2 31 | 32 | TEST: 33 | WEIGHT: '' 34 | TYPE: 'image_only' 35 | FEAT_NORM: 'yes' 36 | OUTPUT_DIR: '../MADE/logs' 37 | 38 | 39 | -------------------------------------------------------------------------------- /SOLIDER/configs/pa100k.yaml: -------------------------------------------------------------------------------- 1 | NAME: '.sm08' 2 | 3 | DATASET: 4 | TYPE: 'pedes' 5 | NAME: 'PA100k' 6 | TRAIN_SPLIT: 'trainval' 7 | VAL_SPLIT: 'test' 8 | ZERO_SHOT: False 9 | LABEL: 'eval' 10 | HEIGHT: 256 11 | WIDTH: 128 12 | 13 | RELOAD: 14 | TYPE: False 15 | NAME: 'backbone' 16 | 17 | BACKBONE: 18 | TYPE: 'swin_t' # swin_t/s/b 19 | 20 | CLASSIFIER: 21 | NAME: 'linear' 22 | POOLING: 'avg' 23 | SCALE: 1 24 | BN: False 25 | 26 | LOSS: 27 | TYPE: 'bceloss' 28 | LOSS_WEIGHT: [1] 29 | SAMPLE_WEIGHT: 'weight' 30 | 31 | 32 | TRAIN: 33 | CLIP_GRAD: True 34 | BATCH_SIZE: 64 35 | OPTIMIZER: 36 | TYPE: 'adam' 37 | WEIGHT_DECAY: 0.0 38 | LR_SCHEDULER: 39 | TYPE: 'plateau' 40 | LR_FT: 0.00001 41 | LR_NEW: 0.00001 42 | EMA: 43 | ENABLE: False 44 | 45 | METRIC: 46 | TYPE: 'pedestrian' 47 | 48 | VIS: 49 | CAM: 'valid' 50 | -------------------------------------------------------------------------------- /model/MHSA.py: -------------------------------------------------------------------------------- 1 | import math 2 | import torch 3 | import torch.nn as nn 4 | from torch.nn import functional as F 5 | import numpy as np 6 | from timm.models.layers import DropPath, to_2tuple, trunc_normal_ 7 | 8 | class Mlp(nn.Module): 9 | def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.): 10 | super().__init__() 11 | out_features = out_features or in_features 12 | hidden_features = hidden_features or in_features 13 | self.fc1 = nn.Linear(in_features, hidden_features) 14 | self.act = act_layer() 15 | self.fc2 = nn.Linear(hidden_features, out_features) 16 | self.drop = nn.Dropout(drop) 17 | 18 | def forward(self, x, H=None, W=None): 19 | x = self.fc1(x) 20 | x = self.act(x) 21 | x = self.drop(x) 22 | x = self.fc2(x) 23 | x = self.drop(x) 24 | return x 25 | 26 | 27 | -------------------------------------------------------------------------------- /utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import os.path as osp 5 | def setup_logger(name, save_dir, if_train): 6 | logger = logging.getLogger(name) 7 | logger.setLevel(logging.DEBUG) 8 | 9 | ch = logging.StreamHandler(stream=sys.stdout) 10 | ch.setLevel(logging.DEBUG) 11 | formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s") 12 | ch.setFormatter(formatter) 13 | logger.addHandler(ch) 14 | 15 | if save_dir: 16 | if not osp.exists(save_dir): 17 | os.makedirs(save_dir) 18 | if if_train: 19 | fh = logging.FileHandler(os.path.join(save_dir, "train_log.txt"), mode='w') 20 | else: 21 | fh = logging.FileHandler(os.path.join(save_dir, "test_log.txt"), mode='w') 22 | fh.setLevel(logging.DEBUG) 23 | fh.setFormatter(formatter) 24 | logger.addHandler(fh) 25 | 26 | return logger -------------------------------------------------------------------------------- /SOLIDER/configs/rap_zs.yaml: -------------------------------------------------------------------------------- 1 | NAME: '.sm08' 2 | 3 | DATASET: 4 | TYPE: 'pedes' 5 | NAME: 'RAP2' 6 | TRAIN_SPLIT: 'trainval' 7 | VAL_SPLIT: 'test' 8 | ZERO_SHOT: True 9 | LABEL: 'eval' 10 | HEIGHT: 256 11 | WIDTH: 192 12 | 13 | RELOAD: 14 | TYPE: False 15 | NAME: 'backbone' 16 | 17 | BACKBONE: 18 | TYPE: 'swin_t' # swin_t/s/b 19 | 20 | CLASSIFIER: 21 | NAME: 'linear' 22 | POOLING: 'avg' 23 | SCALE: 1 24 | BN: False 25 | 26 | LOSS: 27 | TYPE: 'bceloss' 28 | LOSS_WEIGHT: [1] 29 | SAMPLE_WEIGHT: 'weight' 30 | 31 | 32 | TRAIN: 33 | CLIP_GRAD: True 34 | MAX_EPOCH: 30 35 | BATCH_SIZE: 64 36 | OPTIMIZER: 37 | TYPE: 'adam' 38 | MOMENTUM: 0.9 39 | WEIGHT_DECAY: 5e-4 40 | LR_SCHEDULER: 41 | TYPE: 'plateau' 42 | LR_STEP: [10, 15] 43 | LR_FT: 0.00001 44 | LR_NEW: 0.00001 45 | EMA: 46 | ENABLE: False 47 | 48 | METRIC: 49 | TYPE: 'pedestrian' 50 | 51 | VIS: 52 | CAM: 'valid' 53 | -------------------------------------------------------------------------------- /SOLIDER/configs/peta_zs.yaml: -------------------------------------------------------------------------------- 1 | NAME: '.sm08' 2 | 3 | DATASET: 4 | TYPE: 'pedes' 5 | NAME: 'PETA' 6 | TRAIN_SPLIT: 'trainval' 7 | VAL_SPLIT: 'test' 8 | ZERO_SHOT: True 9 | LABEL: 'all' 10 | HEIGHT: 256 11 | WIDTH: 192 12 | 13 | RELOAD: 14 | TYPE: False 15 | NAME: 'backbone' 16 | 17 | BACKBONE: 18 | TYPE: 'swin_b' # swin_t/s/b 19 | 20 | CLASSIFIER: 21 | NAME: 'linear' 22 | POOLING: 'avg' 23 | SCALE: 1 24 | BN: False 25 | 26 | LOSS: 27 | TYPE: 'bceloss' 28 | LOSS_WEIGHT: [1] 29 | SAMPLE_WEIGHT: 'weight' 30 | 31 | 32 | TRAIN: 33 | CLIP_GRAD: True 34 | MAX_EPOCH: 30 35 | BATCH_SIZE: 64 36 | OPTIMIZER: 37 | TYPE: 'adam' 38 | MOMENTUM: 0.9 39 | WEIGHT_DECAY: 5e-4 40 | LR_SCHEDULER: 41 | TYPE: 'multistep' 42 | LR_STEP: [10, 15] 43 | LR_FT: 0.00001 44 | LR_NEW: 0.00001 45 | EMA: 46 | ENABLE: False 47 | 48 | METRIC: 49 | TYPE: 'pedestrian' 50 | 51 | VIS: 52 | CAM: 'valid' 53 | -------------------------------------------------------------------------------- /utils/iotools.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | @author: sherlock 4 | @contact: sherlockliao01@gmail.com 5 | """ 6 | 7 | import errno 8 | import json 9 | import os 10 | 11 | import os.path as osp 12 | 13 | 14 | def mkdir_if_missing(directory): 15 | if not osp.exists(directory): 16 | try: 17 | os.makedirs(directory) 18 | except OSError as e: 19 | if e.errno != errno.EEXIST: 20 | raise 21 | 22 | 23 | def check_isfile(path): 24 | isfile = osp.isfile(path) 25 | if not isfile: 26 | print("=> Warning: no file found at '{}' (ignored)".format(path)) 27 | return isfile 28 | 29 | 30 | def read_json(fpath): 31 | with open(fpath, 'r') as f: 32 | obj = json.load(f) 33 | return obj 34 | 35 | 36 | def write_json(obj, fpath): 37 | mkdir_if_missing(osp.dirname(fpath)) 38 | with open(fpath, 'w') as f: 39 | json.dump(obj, f, indent=4, separators=(',', ': ')) 40 | -------------------------------------------------------------------------------- /dist_train.sh: -------------------------------------------------------------------------------- 1 | # train 2 | # prcc 3 | CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/prcc/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 4 | # ltcc 5 | #CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/ltcc/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 6 | # Celeb_reID_light 7 | #CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/Celeb_light/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 8 | # last 9 | #CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/last/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 10 | # test 11 | #CUDA_VISIBLE_DEVICES=1,0 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 test.py --config_file configs/ltcc/eva02_l_maskmeta_random.yml 12 | 13 | 14 | -------------------------------------------------------------------------------- /SOLIDER/scheduler/scheduler_factory.py: -------------------------------------------------------------------------------- 1 | """ Scheduler Factory 2 | Hacked together by / Copyright 2020 Ross Wightman 3 | """ 4 | from .cosine_lr import CosineLRScheduler 5 | 6 | 7 | def create_scheduler(cfg, optimizer): 8 | num_epochs = cfg.SOLVER.MAX_EPOCHS 9 | # type 1 10 | # lr_min = 0.01 * cfg.SOLVER.BASE_LR 11 | # warmup_lr_init = 0.001 * cfg.SOLVER.BASE_LR 12 | # type 2 13 | lr_min = 0.002 * cfg.SOLVER.BASE_LR 14 | warmup_lr_init = 0.01 * cfg.SOLVER.BASE_LR 15 | # type 3 16 | # lr_min = 0.001 * cfg.SOLVER.BASE_LR 17 | # warmup_lr_init = 0.01 * cfg.SOLVER.BASE_LR 18 | 19 | warmup_t = cfg.SOLVER.WARMUP_EPOCHS 20 | noise_range = None 21 | 22 | lr_scheduler = CosineLRScheduler( 23 | optimizer, 24 | t_initial=num_epochs, 25 | lr_min=lr_min, 26 | t_mul= 1., 27 | decay_rate=0.1, 28 | warmup_lr_init=warmup_lr_init, 29 | warmup_t=warmup_t, 30 | cycle_limit=1, 31 | t_in_epochs=True, 32 | noise_range_t=noise_range, 33 | noise_pct= 0.67, 34 | noise_std= 1., 35 | noise_seed=42, 36 | ) 37 | 38 | return lr_scheduler 39 | -------------------------------------------------------------------------------- /SOLIDER/tools/distributed.py: -------------------------------------------------------------------------------- 1 | """ Distributed training/validation utils 2 | 3 | Hacked together by / Copyright 2020 Ross Wightman 4 | """ 5 | import torch 6 | from torch import distributed as dist 7 | 8 | from models.model_ema import ModelEma 9 | 10 | 11 | def unwrap_model(model): 12 | if isinstance(model, ModelEma): 13 | return unwrap_model(model.ema) 14 | else: 15 | return model.module if hasattr(model, 'module') else model 16 | 17 | 18 | def reduce_tensor(tensor, n): 19 | rt = tensor.clone() 20 | dist.all_reduce(rt, op=dist.ReduceOp.SUM) 21 | rt /= n 22 | return rt 23 | 24 | 25 | def distribute_bn(model, world_size, reduce=False): 26 | # ensure every node has the same running bn stats 27 | for bn_name, bn_buf in unwrap_model(model).named_buffers(recurse=True): 28 | if ('running_mean' in bn_name) or ('running_var' in bn_name): 29 | if reduce: 30 | # average bn stats across whole group 31 | torch.distributed.all_reduce(bn_buf, op=dist.ReduceOp.SUM) 32 | bn_buf /= float(world_size) 33 | else: 34 | # broadcast bn stats from rank 0 to whole group 35 | torch.distributed.broadcast(bn_buf, 0) 36 | -------------------------------------------------------------------------------- /solver/scheduler_factory.py: -------------------------------------------------------------------------------- 1 | """ Scheduler Factory 2 | Hacked together by / Copyright 2020 Ross Wightman 3 | """ 4 | from .cosine_lr import CosineLRScheduler 5 | 6 | 7 | def create_scheduler(cfg, optimizer): 8 | num_epochs = cfg.SOLVER.MAX_EPOCHS 9 | # type 1 10 | # warmup_lr_init = 0.001 * cfg.SOLVER.BASE_LR 11 | # type 2 12 | # lr_min = 0.002 * cfg.SOLVER.BASE_LR 13 | lr_min = 0.1 * cfg.SOLVER.BASE_LR 14 | # warmup_lr_init = 0.01 * cfg.SOLVER.BASE_LR 15 | # warmup_lr_init = 7.8125e-07 16 | warmup_lr_init = cfg.SOLVER.WARMUP_LR 17 | # type 3 18 | # lr_min = 0.001 * cfg.SOLVER.BASE_LR 19 | # warmup_lr_init = 0.01 * cfg.SOLVER.BASE_LR 20 | 21 | warmup_t = cfg.SOLVER.WARMUP_EPOCHS 22 | noise_range = None 23 | 24 | lr_scheduler = CosineLRScheduler( 25 | optimizer, 26 | t_initial=num_epochs, 27 | lr_min=lr_min, 28 | t_mul= 1., 29 | decay_rate=0.1, 30 | warmup_lr_init=warmup_lr_init, 31 | warmup_t=warmup_t, 32 | cycle_limit=1, 33 | t_in_epochs=True, 34 | noise_range_t=noise_range, 35 | noise_pct= 0.67, 36 | noise_std= 1., 37 | noise_seed=42, 38 | ) 39 | 40 | return lr_scheduler 41 | -------------------------------------------------------------------------------- /solver/make_optimizer.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | 4 | def make_optimizer(cfg, model, center_criterion): 5 | params = [] 6 | for key, value in model.named_parameters(): 7 | if not value.requires_grad: 8 | continue 9 | lr = cfg.SOLVER.BASE_LR 10 | weight_decay = cfg.SOLVER.WEIGHT_DECAY 11 | if "bias" in key: 12 | lr = cfg.SOLVER.BASE_LR * cfg.SOLVER.BIAS_LR_FACTOR 13 | weight_decay = cfg.SOLVER.WEIGHT_DECAY_BIAS 14 | if cfg.SOLVER.LARGE_FC_LR: 15 | if "classifier" in key or "arcface" in key: 16 | lr = cfg.SOLVER.BASE_LR * 2 17 | print('Using two times learning rate for fc ') 18 | 19 | params += [{"params": [value], "lr": lr, "weight_decay": weight_decay}] 20 | 21 | if cfg.SOLVER.OPTIMIZER_NAME == 'SGD': 22 | optimizer = getattr(torch.optim, cfg.SOLVER.OPTIMIZER_NAME)(params, momentum=cfg.SOLVER.MOMENTUM) 23 | elif cfg.SOLVER.OPTIMIZER_NAME == 'AdamW': 24 | optimizer = torch.optim.AdamW(params, lr=cfg.SOLVER.BASE_LR, weight_decay=cfg.SOLVER.WEIGHT_DECAY) 25 | else: 26 | optimizer = getattr(torch.optim, cfg.SOLVER.OPTIMIZER_NAME)(params) 27 | optimizer_center = torch.optim.SGD(center_criterion.parameters(), lr=cfg.SOLVER.CENTER_LR) 28 | 29 | return optimizer, optimizer_center 30 | -------------------------------------------------------------------------------- /SOLIDER/losses/bceloss.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from models.registry import LOSSES 6 | from tools.function import ratio2weight 7 | 8 | 9 | @LOSSES.register("bceloss") 10 | class BCELoss(nn.Module): 11 | 12 | def __init__(self, sample_weight=None, size_sum=True, scale=None, tb_writer=None): 13 | super(BCELoss, self).__init__() 14 | 15 | self.sample_weight = sample_weight 16 | self.size_sum = size_sum 17 | self.hyper = 0.8 18 | self.smoothing = None 19 | 20 | def forward(self, logits, targets): 21 | logits = logits[0] 22 | 23 | if self.smoothing is not None: 24 | targets = (1 - self.smoothing) * targets + self.smoothing * (1 - targets) 25 | 26 | loss_m = F.binary_cross_entropy_with_logits(logits, targets, reduction='none') 27 | 28 | targets_mask = torch.where(targets.detach().cpu() > 0.5, torch.ones(1), torch.zeros(1)) 29 | if self.sample_weight is not None: 30 | sample_weight = ratio2weight(targets_mask, self.sample_weight) 31 | 32 | loss_m = (loss_m * sample_weight.cuda()) 33 | 34 | # losses = loss_m.sum(1).mean() if self.size_sum else loss_m.mean() 35 | loss = loss_m.sum(1).mean() if self.size_sum else loss_m.sum() 36 | 37 | return [loss], [loss_m] -------------------------------------------------------------------------------- /SOLIDER/losses/label_smoothing.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.nn.functional as F 3 | # https://github.com/pytorch/pytorch/issues/7455 4 | 5 | class LabelSmoothLoss(nn.Module): 6 | 7 | def __init__(self, smoothing=0.0): 8 | super(LabelSmoothLoss, self).__init__() 9 | self.smoothing = smoothing 10 | 11 | def forward(self, input, target): 12 | log_prob = F.log_softmax(input, dim=-1) 13 | weight = input.new_ones(input.size()) * self.smoothing / (input.size(-1) - 1.) 14 | weight.scatter_(-1, target.unsqueeze(-1), (1. - self.smoothing)) 15 | loss = (-weight * log_prob).sum(dim=-1).mean() 16 | return loss 17 | 18 | 19 | class LabelSmoothingLoss(nn.Module): 20 | def __init__(self, classes, smoothing=0.0, dim=-1): 21 | super(LabelSmoothingLoss, self).__init__() 22 | self.confidence = 1.0 - smoothing 23 | self.smoothing = smoothing 24 | self.cls = classes 25 | self.dim = dim 26 | 27 | def forward(self, pred, target): 28 | pred = pred.log_softmax(dim=self.dim) 29 | with torch.no_grad(): 30 | # true_dist = pred.data.clone() 31 | true_dist = torch.zeros_like(pred) 32 | true_dist.fill_(self.smoothing / (self.cls - 1)) 33 | true_dist.scatter_(1, target.unsqueeze(1), self.confidence) 34 | return torch.mean(torch.sum(-true_dist * pred, dim=self.dim)) 35 | -------------------------------------------------------------------------------- /SOLIDER/losses/scaledbceloss.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from models.registry import LOSSES 6 | from tools.function import ratio2weight 7 | 8 | 9 | @LOSSES.register("scaledbceloss") 10 | class ScaledBCELoss(nn.Module): 11 | 12 | def __init__(self, sample_weight=None, size_sum=True, scale=30, tb_writer=None): 13 | super(ScaledBCELoss, self).__init__() 14 | 15 | self.sample_weight = sample_weight 16 | self.size_sum = size_sum 17 | self.hyper = 0.8 18 | self.smoothing = None 19 | self.pos_scale = scale 20 | self.neg_scale = scale 21 | self.tb_writer = tb_writer 22 | 23 | def forward(self, logits, targets): 24 | batch_size = logits.shape[0] 25 | 26 | logits = logits * targets * self.pos_scale + logits * (1 - targets) * self.neg_scale 27 | 28 | if self.smoothing is not None: 29 | targets = (1 - self.smoothing) * targets + self.smoothing * (1 - targets) 30 | 31 | loss_m = F.binary_cross_entropy_with_logits(logits, targets, reduction='none') 32 | 33 | targets_mask = torch.where(targets.detach().cpu() > 0.5, torch.ones(1), torch.zeros(1)) 34 | 35 | if self.sample_weight is not None: 36 | sample_weight = ratio2weight(targets_mask, self.sample_weight) 37 | 38 | loss_m = (loss_m * sample_weight.cuda()) 39 | 40 | loss = loss_m.sum(1).mean() if self.size_sum else loss_m.mean() 41 | 42 | return [loss], [loss_m] -------------------------------------------------------------------------------- /SOLIDER/models/registry.py: -------------------------------------------------------------------------------- 1 | def _register_generic(module_dict, module_name, module): 2 | assert module_name not in module_dict 3 | module_dict[module_name] = module 4 | 5 | 6 | class Registry(dict): 7 | """ 8 | A helper class for managing registering modules, it extends a dictionary 9 | and provides a register functions. 10 | 11 | Eg. creeting a registry: 12 | some_registry = Registry({"default": default_module}) 13 | 14 | There're two ways of registering new modules: 15 | 1): normal way is just calling register function: 16 | def foo(): 17 | ... 18 | some_registry.register("foo_module", foo) 19 | 2): used as decorator when declaring the module: 20 | @some_registry.register("foo_module") 21 | @some_registry.register("foo_modeul_nickname") 22 | def foo(): 23 | ... 24 | 25 | Access of module is just like using a dictionary, eg: 26 | f = some_registry["foo_modeul"] 27 | """ 28 | def __init__(self, *args, **kwargs): 29 | super(Registry, self).__init__(*args, **kwargs) 30 | 31 | def register(self, module_name, module=None): 32 | # used as function call 33 | if module is not None: 34 | _register_generic(self, module_name, module) 35 | return 36 | 37 | # used as decorator 38 | def register_fn(fn): 39 | _register_generic(self, module_name, fn) 40 | return fn 41 | 42 | return register_fn 43 | 44 | 45 | BACKBONE = Registry() 46 | CLASSIFIER = Registry() 47 | LOSSES = Registry() -------------------------------------------------------------------------------- /SOLIDER/README.md: -------------------------------------------------------------------------------- 1 | # SOLIDER on [Person Attribute Recognition] 2 | 3 | This repo provides details about how to use [SOLIDER](https://github.com/tinyvision/SOLIDER) pretrained representation on attribute recognition task. 4 | They modify the code from [Rethinking_of_PAR](https://github.com/valencebond/Rethinking_of_PAR), and you can refer to the original repo for more details. 5 | 6 | ## Dependencies 7 | 8 | - python 3.7 9 | - pytorch 1.7.0 10 | - torchvision 0.8.2 11 | - cuda 10.1 12 | 13 | ## Datasets 14 | 15 | - PETA: Pedestrian Attribute Recognition At Far Distance [[Paper](http://mmlab.ie.cuhk.edu.hk/projects/PETA_files/Pedestrian%20Attribute%20Recognition%20At%20Far%20Distance.pdf)][[Project](http://mmlab.ie.cuhk.edu.hk/projects/PETA.html)] 16 | 17 | ## Prepare Pre-trained Models 18 | Step 1. Download models from [SOLIDER](https://github.com/tinyvision/SOLIDER), or use [SOLIDER](https://github.com/tinyvision/SOLIDER) to train your own models. 19 | 20 | Step 2. Put the pretrained models under the `pretrained` file, and rename their names as `./pretrained/solider_swin_tiny(small/base).pth` 21 | 22 | ## Train Model and Get Attributes of Cloth-Changing Datasets 23 | 24 | 1. Create a directory to dowload above datasets. 25 | ``` 26 | cd SOLIDER 27 | mkdir data 28 | ``` 29 | 2. Prepare datasets to have following structure: 30 | ``` 31 | ${project_dir}/data 32 | PETA 33 | images/ 34 | PETA.mat 35 | dataset_all.pkl 36 | dataset_zs_run0.pkl 37 | ``` 38 | 3. Train baseline. 39 | ``` 40 | sh run.sh 41 | ``` 42 | 43 | 4. Modify the dataset and model path, then run and get attributes file of different cloth-changing datasets. 44 | ``` 45 | python demo_PETA_Ce.py 46 | python demo_PETA_last.py 47 | python demo_PETA_ltcc.py 48 | python demo_PETA_prcc.py 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /solver/lr_scheduler.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | @author: liaoxingyu 4 | @contact: sherlockliao01@gmail.com 5 | """ 6 | from bisect import bisect_right 7 | import torch 8 | 9 | 10 | # FIXME ideally this would be achieved with a CombinedLRScheduler, 11 | # separating MultiStepLR with WarmupLR 12 | # but the current LRScheduler design doesn't allow it 13 | 14 | class WarmupMultiStepLR(torch.optim.lr_scheduler._LRScheduler): 15 | def __init__( 16 | self, 17 | optimizer, 18 | milestones, # steps 19 | gamma=0.1, 20 | warmup_factor=1.0 / 3, 21 | warmup_iters=500, 22 | warmup_method="linear", 23 | last_epoch=-1, 24 | ): 25 | if not list(milestones) == sorted(milestones): 26 | raise ValueError( 27 | "Milestones should be a list of" " increasing integers. Got {}", 28 | milestones, 29 | ) 30 | 31 | if warmup_method not in ("constant", "linear"): 32 | raise ValueError( 33 | "Only 'constant' or 'linear' warmup_method accepted" 34 | "got {}".format(warmup_method) 35 | ) 36 | self.milestones = milestones 37 | self.gamma = gamma 38 | self.warmup_factor = warmup_factor 39 | self.warmup_iters = warmup_iters 40 | self.warmup_method = warmup_method 41 | super(WarmupMultiStepLR, self).__init__(optimizer, last_epoch) 42 | 43 | def _get_lr(self): 44 | warmup_factor = 1 45 | if self.last_epoch < self.warmup_iters: 46 | if self.warmup_method == "constant": 47 | warmup_factor = self.warmup_factor 48 | elif self.warmup_method == "linear": 49 | alpha = self.last_epoch / self.warmup_iters 50 | warmup_factor = self.warmup_factor * (1 - alpha) + alpha 51 | return [ 52 | base_lr 53 | * warmup_factor 54 | * self.gamma ** bisect_right(self.milestones, self.last_epoch) 55 | for base_lr in self.base_lrs 56 | ] 57 | -------------------------------------------------------------------------------- /loss/softmax_loss.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from torch.nn import functional as F 4 | class CrossEntropyLabelSmooth(nn.Module): 5 | """Cross entropy loss with label smoothing regularizer. 6 | 7 | Reference: 8 | Szegedy et al. Rethinking the Inception Architecture for Computer Vision. CVPR 2016. 9 | Equation: y = (1 - epsilon) * y + epsilon / K. 10 | 11 | Args: 12 | num_classes (int): number of classes. 13 | epsilon (float): weight. 14 | """ 15 | 16 | def __init__(self, num_classes, epsilon=0.1, use_gpu=True): 17 | super(CrossEntropyLabelSmooth, self).__init__() 18 | self.num_classes = num_classes 19 | self.epsilon = epsilon 20 | self.use_gpu = use_gpu 21 | self.logsoftmax = nn.LogSoftmax(dim=1) 22 | 23 | def forward(self, inputs, targets): 24 | """ 25 | Args: 26 | inputs: prediction matrix (before softmax) with shape (batch_size, num_classes) 27 | targets: ground truth labels with shape (num_classes) 28 | """ 29 | log_probs = self.logsoftmax(inputs) 30 | targets = torch.zeros(log_probs.size()).scatter_(1, targets.unsqueeze(1).data.cpu(), 1) 31 | if self.use_gpu: targets = targets.cuda() 32 | targets = (1 - self.epsilon) * targets + self.epsilon / self.num_classes 33 | loss = (- targets * log_probs).mean(0).sum() 34 | return loss 35 | 36 | class LabelSmoothingCrossEntropy(nn.Module): 37 | """ 38 | NLL loss with label smoothing. 39 | """ 40 | def __init__(self, smoothing=0.1): 41 | """ 42 | Constructor for the LabelSmoothing module. 43 | :param smoothing: label smoothing factor 44 | """ 45 | super(LabelSmoothingCrossEntropy, self).__init__() 46 | assert smoothing < 1.0 47 | self.smoothing = smoothing 48 | self.confidence = 1. - smoothing 49 | 50 | def forward(self, x, target): 51 | logprobs = F.log_softmax(x, dim=-1) 52 | nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1)) 53 | nll_loss = nll_loss.squeeze(1) 54 | smooth_loss = -logprobs.mean(dim=-1) 55 | loss = self.confidence * nll_loss + self.smoothing * smooth_loss 56 | return loss.mean() -------------------------------------------------------------------------------- /loss/center_loss.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import torch 4 | from torch import nn 5 | 6 | 7 | class CenterLoss(nn.Module): 8 | """Center loss. 9 | 10 | Reference: 11 | Wen et al. A Discriminative Feature Learning Approach for Deep Face Recognition. ECCV 2016. 12 | 13 | Args: 14 | num_classes (int): number of classes. 15 | feat_dim (int): feature dimension. 16 | """ 17 | 18 | def __init__(self, num_classes=751, feat_dim=2048, use_gpu=True): 19 | super(CenterLoss, self).__init__() 20 | self.num_classes = num_classes 21 | self.feat_dim = feat_dim 22 | self.use_gpu = use_gpu 23 | 24 | if self.use_gpu: 25 | self.centers = nn.Parameter(torch.randn(self.num_classes, self.feat_dim).cuda()) 26 | else: 27 | self.centers = nn.Parameter(torch.randn(self.num_classes, self.feat_dim)) 28 | 29 | def forward(self, x, labels): 30 | """ 31 | Args: 32 | x: feature matrix with shape (batch_size, feat_dim). 33 | labels: ground truth labels with shape (num_classes). 34 | """ 35 | assert x.size(0) == labels.size(0), "features.size(0) is not equal to labels.size(0)" 36 | 37 | batch_size = x.size(0) 38 | distmat = torch.pow(x, 2).sum(dim=1, keepdim=True).expand(batch_size, self.num_classes) + \ 39 | torch.pow(self.centers, 2).sum(dim=1, keepdim=True).expand(self.num_classes, batch_size).t() 40 | distmat.addmm_(1, -2, x, self.centers.t()) 41 | 42 | classes = torch.arange(self.num_classes).long() 43 | if self.use_gpu: classes = classes.cuda() 44 | labels = labels.unsqueeze(1).expand(batch_size, self.num_classes) 45 | mask = labels.eq(classes.expand(batch_size, self.num_classes)) 46 | 47 | dist = [] 48 | for i in range(batch_size): 49 | value = distmat[i][mask[i]] 50 | value = value.clamp(min=1e-12, max=1e+12) # for numerical stability 51 | dist.append(value) 52 | dist = torch.cat(dist) 53 | loss = dist.mean() 54 | return loss 55 | 56 | 57 | if __name__ == '__main__': 58 | use_gpu = False 59 | center_loss = CenterLoss(use_gpu=use_gpu) 60 | features = torch.rand(16, 2048) 61 | targets = torch.Tensor([0, 1, 2, 3, 2, 3, 1, 4, 5, 3, 2, 1, 0, 0, 5, 4]).long() 62 | if use_gpu: 63 | features = torch.rand(16, 2048).cuda() 64 | targets = torch.Tensor([0, 1, 2, 3, 2, 3, 1, 4, 5, 3, 2, 1, 0, 0, 5, 4]).cuda() 65 | 66 | loss = center_loss(features, targets) 67 | print(loss) 68 | -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/preprocess/format_rap.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import pickle 5 | 6 | from easydict import EasyDict 7 | from scipy.io import loadmat 8 | 9 | np.random.seed(0) 10 | random.seed(0) 11 | 12 | 13 | group_order = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 14 | 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 1, 2, 3, 0, 4, 5, 6, 7, 8, 43, 44, 15 | 45, 46, 47, 48, 49, 50] 16 | 17 | 18 | def make_dir(path): 19 | if os.path.exists(path): 20 | pass 21 | else: 22 | os.mkdir(path) 23 | 24 | 25 | def generate_data_description(save_dir, reorder): 26 | """ 27 | create a dataset description file, which consists of images, labels 28 | """ 29 | 30 | data = loadmat(os.path.join(save_dir, 'RAP_annotation/RAP_annotation.mat')) 31 | 32 | dataset = EasyDict() 33 | dataset.description = 'rap' 34 | dataset.reorder = 'group_order' 35 | dataset.root = os.path.join(save_dir, 'RAP_dataset') 36 | dataset.image_name = [data['RAP_annotation'][0][0][5][i][0][0] for i in range(41585)] 37 | raw_attr_name = [data['RAP_annotation'][0][0][3][i][0][0] for i in range(92)] 38 | # (41585, 92) 39 | raw_label = data['RAP_annotation'][0][0][1] 40 | dataset.label = raw_label[:, np.array(range(51))] 41 | 42 | dataset.label = raw_label 43 | dataset.attr_name = raw_attr_name 44 | 45 | dataset.label_idx = EasyDict() 46 | dataset.label_idx.eval = list(range(51)) 47 | dataset.label_idx.color = list(range(63, raw_label.shape[1])) # (63, 92) 48 | dataset.label_idx.extra = list(range(51, 63)) 49 | 50 | if reorder: 51 | dataset.label_idx.eval = group_order 52 | 53 | dataset.partition = EasyDict() 54 | dataset.partition.trainval = [] 55 | dataset.partition.test = [] 56 | 57 | dataset.weight_trainval = [] 58 | 59 | for idx in range(5): 60 | trainval = data['RAP_annotation'][0][0][0][idx][0][0][0][0][0, :] - 1 61 | test = data['RAP_annotation'][0][0][0][idx][0][0][0][1][0, :] - 1 62 | 63 | dataset.partition.trainval.append(trainval) 64 | dataset.partition.test.append(test) 65 | 66 | weight_trainval = np.mean(dataset.label[trainval], axis=0).astype(np.float32) 67 | dataset.weight_trainval.append(weight_trainval) 68 | 69 | with open(os.path.join(save_dir, 'dataset_all.pkl'), 'wb+') as f: 70 | pickle.dump(dataset, f) 71 | 72 | 73 | if __name__ == "__main__": 74 | save_dir = '/mnt/data1/jiajian/datasets/attribute/RAP/' 75 | reorder = True 76 | generate_data_description(save_dir, reorder) 77 | -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/preprocess/format_pa100k.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import pickle 5 | 6 | from easydict import EasyDict 7 | from scipy.io import loadmat 8 | 9 | np.random.seed(0) 10 | random.seed(0) 11 | 12 | group_order = [7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 9, 10, 11, 12, 1, 2, 3, 0, 4, 5, 6] 13 | 14 | 15 | def make_dir(path): 16 | if os.path.exists(path): 17 | pass 18 | else: 19 | os.mkdir(path) 20 | 21 | 22 | def generate_data_description(save_dir, reorder): 23 | """ 24 | create a dataset description file, which consists of images, labels 25 | """ 26 | # pa100k_data = loadmat('/mnt/data1/jiajian/dataset/attribute/PA100k/annotation.mat') 27 | pa100k_data = loadmat(os.path.join(save_dir, 'annotation.mat')) 28 | 29 | dataset = EasyDict() 30 | dataset.description = 'pa100k' 31 | dataset.reorder = 'group_order' 32 | dataset.root = os.path.join(save_dir, 'data') 33 | 34 | train_image_name = [pa100k_data['train_images_name'][i][0][0] for i in range(80000)] 35 | val_image_name = [pa100k_data['val_images_name'][i][0][0] for i in range(10000)] 36 | test_image_name = [pa100k_data['test_images_name'][i][0][0] for i in range(10000)] 37 | dataset.image_name = train_image_name + val_image_name + test_image_name 38 | 39 | dataset.label = np.concatenate((pa100k_data['train_label'], pa100k_data['val_label'], pa100k_data['test_label']), axis=0) 40 | dataset.attr_name = [pa100k_data['attributes'][i][0][0] for i in range(26)] 41 | 42 | dataset.label_idx = EasyDict() 43 | dataset.label_idx.eval = list(range(26)) 44 | 45 | if reorder: 46 | dataset.label_idx.eval = group_order 47 | 48 | dataset.partition = EasyDict() 49 | dataset.partition.train = np.arange(0, 80000) # np.array(range(80000)) 50 | dataset.partition.val = np.arange(80000, 90000) # np.array(range(80000, 90000)) 51 | dataset.partition.test = np.arange(90000, 100000) # np.array(range(90000, 100000)) 52 | dataset.partition.trainval = np.arange(0, 90000) # np.array(range(90000)) 53 | 54 | dataset.weight_train = np.mean(dataset.label[dataset.partition.train], axis=0).astype(np.float32) 55 | dataset.weight_trainval = np.mean(dataset.label[dataset.partition.trainval], axis=0).astype(np.float32) 56 | 57 | with open(os.path.join(save_dir, 'dataset_all.pkl'), 'wb+') as f: 58 | pickle.dump(dataset, f) 59 | 60 | 61 | if __name__ == "__main__": 62 | save_dir = '/mnt/data1/jiajian/datasets/attribute/PA100k/' 63 | # save_dir = './data/PA100k/' 64 | reoder = True 65 | generate_data_description(save_dir, reorder=True) 66 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import torch 5 | import random 6 | from config import cfg 7 | import argparse 8 | from data import build_dataloader 9 | from processor import do_inference 10 | from utils.logger import setup_logger 11 | from model import build_model 12 | 13 | def set_seed(seed): 14 | torch.manual_seed(seed) 15 | torch.cuda.manual_seed(seed) 16 | torch.cuda.manual_seed_all(seed) 17 | np.random.seed(seed) 18 | random.seed(seed) 19 | torch.backends.cudnn.deterministic = True 20 | torch.backends.cudnn.benchmark = True 21 | 22 | if __name__ == "__main__": 23 | parser = argparse.ArgumentParser(description="CC_ReID Training") 24 | parser.add_argument( 25 | "--config_file", default="", help="path to config file", type=str 26 | ) 27 | parser.add_argument("opts", help="Modify config options using the command-line", default=None, 28 | nargs=argparse.REMAINDER) 29 | parser.add_argument("--local_rank", default=0, type=int) 30 | args = parser.parse_args() 31 | 32 | 33 | 34 | if args.config_file != "": 35 | cfg.merge_from_file(args.config_file) 36 | cfg.merge_from_list(args.opts) 37 | cfg.freeze() 38 | 39 | output_dir = cfg.OUTPUT_DIR 40 | if output_dir and not os.path.exists(output_dir): 41 | os.makedirs(output_dir) 42 | 43 | logger = setup_logger("EVA-attribure", output_dir, if_train=False) 44 | logger.info(args) 45 | 46 | if args.config_file != "": 47 | logger.info("Loaded configuration file {}".format(args.config_file)) 48 | with open(args.config_file, 'r') as cf: 49 | config_str = "\n" + cf.read() 50 | logger.info(config_str) 51 | logger.info("Running with config:\n{}".format(cfg)) 52 | 53 | # os.environ['CUDA_VISIBLE_DEVICES'] = cfg.MODEL.DEVICE_ID 54 | torch.cuda.set_device(args.local_rank) 55 | set_seed(cfg.SOLVER.SEED) 56 | 57 | torch.distributed.init_process_group(backend='nccl', init_method='env://') 58 | 59 | if cfg.DATA.DATASET == 'prcc': 60 | trainloader, queryloader_same, queryloader_diff, galleryloader, dataset, train_sampler,val_loader,val_loader_same= build_dataloader( 61 | cfg) 62 | else: 63 | trainloader, queryloader, galleryloader, dataset, train_sampler ,val_loader= build_dataloader(cfg) 64 | 65 | model = build_model(cfg,dataset.num_train_pids) 66 | model.load_param(cfg.TEST.WEIGHT) 67 | 68 | if cfg.DATA.DATASET == 'prcc': 69 | do_inference(cfg, 70 | model, 71 | galleryloader, 72 | dataset, 73 | val_loader=val_loader, 74 | val_loader_same=val_loader_same 75 | ) 76 | else: 77 | do_inference(cfg, 78 | model, 79 | galleryloader, 80 | dataset, 81 | queryloader, 82 | val_loader=val_loader 83 | ) 84 | 85 | -------------------------------------------------------------------------------- /SOLIDER/tools/vis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def tb_visualizer_pedes(tb_writer, lr, epoch, train_loss, valid_loss, train_result, valid_result, 5 | train_gt, valid_gt, train_loss_mtr, valid_loss_mtr, model, attr_name): 6 | # tb_writer.add_scalars('lr', {'lr': lr}, epoch) 7 | # tb_writer.add_scalars('losses', {'train': train_loss, 8 | # 'test': valid_loss}, epoch) 9 | # 10 | # tb_writer.add_scalars('train_perf/ma', {'ma': train_result.ma, 11 | # 'pos_recall': np.mean(train_result.label_pos_recall), 12 | # 'neg_recall': np.mean(train_result.label_neg_recall), 13 | # 'Acc': train_result.instance_acc, 14 | # 'Prec': train_result.instance_prec, 15 | # 'Rec': train_result.instance_recall, 16 | # 'F1': train_result.instance_f1}, epoch) 17 | # 18 | # tb_writer.add_scalars('test_perf/', {'ma': valid_result.ma, 19 | # 'pos_recall': np.mean(valid_result.label_pos_recall), 20 | # 'neg_recall': np.mean(valid_result.label_neg_recall), 21 | # 'Acc': valid_result.instance_acc, 22 | # 'Prec': valid_result.instance_prec, 23 | # 'Rec': valid_result.instance_recall, 24 | # 'F1': valid_result.instance_f1}, epoch) 25 | 26 | tb_writer.add_scalar('lr/epoch', lr, epoch) 27 | tb_writer.add_scalar('losses/train', train_loss, epoch) 28 | tb_writer.add_scalar('losses/test', valid_loss, epoch) 29 | 30 | tb_writer.add_scalar('train_perf/ma', train_result.ma, epoch) 31 | tb_writer.add_scalar('train_perf/pos_recall', np.mean(train_result.label_pos_recall), epoch) 32 | tb_writer.add_scalar('train_perf/neg_recall', np.mean(train_result.label_neg_recall), epoch) 33 | tb_writer.add_scalar('train_perf/Acc', train_result.instance_acc, epoch) 34 | tb_writer.add_scalar('train_perf/Prec', train_result.instance_prec, epoch) 35 | tb_writer.add_scalar('train_perf/Rec', train_result.instance_recall, epoch) 36 | tb_writer.add_scalar('train_perf/F1', train_result.instance_f1, epoch) 37 | 38 | tb_writer.add_scalar('test_perf/ma', valid_result.ma, epoch) 39 | tb_writer.add_scalar('test_perf/pos_recall', np.mean(valid_result.label_pos_recall), epoch) 40 | tb_writer.add_scalar('test_perf/neg_recall', np.mean(valid_result.label_neg_recall), epoch) 41 | tb_writer.add_scalar('test_perf/Acc', valid_result.instance_acc, epoch) 42 | tb_writer.add_scalar('test_perf/Prec', valid_result.instance_prec, epoch) 43 | tb_writer.add_scalar('test_perf/Rec', valid_result.instance_recall, epoch) 44 | tb_writer.add_scalar('test_perf/F1', valid_result.instance_f1, epoch) 45 | 46 | 47 | -------------------------------------------------------------------------------- /SOLIDER/metrics/pedestrian_metrics.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import numpy as np 4 | from easydict import EasyDict 5 | import torch 6 | 7 | 8 | def get_pedestrian_metrics(gt_label, preds_probs, threshold=0.5, index=None, cfg=None): 9 | """ 10 | index: evaluated label index 11 | """ 12 | pred_label = preds_probs > threshold 13 | 14 | eps = 1e-20 15 | result = EasyDict() 16 | 17 | if index is not None: 18 | pred_label = pred_label[:, index] 19 | gt_label = gt_label[:, index] 20 | 21 | ############################### 22 | # label metrics 23 | # TP + FN 24 | gt_pos = np.sum((gt_label == 1), axis=0).astype(float) 25 | # TN + FP 26 | gt_neg = np.sum((gt_label == 0), axis=0).astype(float) 27 | # TP 28 | true_pos = np.sum((gt_label == 1) * (pred_label == 1), axis=0).astype(float) 29 | # TN 30 | true_neg = np.sum((gt_label == 0) * (pred_label == 0), axis=0).astype(float) 31 | # FP 32 | false_pos = np.sum(((gt_label == 0) * (pred_label == 1)), axis=0).astype(float) 33 | # FN 34 | false_neg = np.sum(((gt_label == 1) * (pred_label == 0)), axis=0).astype(float) 35 | 36 | label_pos_recall = 1.0 * true_pos / (gt_pos + eps) # true positive 37 | label_neg_recall = 1.0 * true_neg / (gt_neg + eps) # true negative 38 | # mean accuracy 39 | label_ma = (label_pos_recall + label_neg_recall) / 2 40 | 41 | result.label_pos_recall = label_pos_recall 42 | result.label_neg_recall = label_neg_recall 43 | result.label_prec = true_pos / (true_pos + false_pos + eps) 44 | result.label_acc = true_pos / (true_pos + false_pos + false_neg + eps) 45 | result.label_f1 = 2 * result.label_prec * result.label_pos_recall / ( 46 | result.label_prec + result.label_pos_recall + eps) 47 | 48 | result.label_ma = label_ma 49 | result.ma = np.mean(label_ma) 50 | 51 | ################ 52 | # instance metrics 53 | gt_pos = np.sum((gt_label == 1), axis=1).astype(float) 54 | true_pos = np.sum((pred_label == 1), axis=1).astype(float) 55 | # true positive 56 | intersect_pos = np.sum((gt_label == 1) * (pred_label == 1), axis=1).astype(float) 57 | # IOU 58 | union_pos = np.sum(((gt_label == 1) + (pred_label == 1)), axis=1).astype(float) 59 | 60 | instance_acc = intersect_pos / (union_pos + eps) 61 | instance_prec = intersect_pos / (true_pos + eps) 62 | instance_recall = intersect_pos / (gt_pos + eps) 63 | instance_f1 = 2 * instance_prec * instance_recall / (instance_prec + instance_recall + eps) 64 | 65 | instance_acc = np.mean(instance_acc) 66 | instance_prec = np.mean(instance_prec) 67 | instance_recall = np.mean(instance_recall) 68 | # instance_f1 = np.mean(instance_f1) 69 | instance_f1 = 2 * instance_prec * instance_recall / (instance_prec + instance_recall + eps) 70 | 71 | result.instance_acc = instance_acc 72 | result.instance_prec = instance_prec 73 | result.instance_recall = instance_recall 74 | result.instance_f1 = instance_f1 75 | 76 | result.error_num, result.fn_num, result.fp_num = false_pos + false_neg, false_neg, false_pos 77 | 78 | return result 79 | -------------------------------------------------------------------------------- /loss/arcface.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | from torch.nn import Parameter 5 | import math 6 | 7 | 8 | class ArcFace(nn.Module): 9 | def __init__(self, in_features, out_features, s=30.0, m=0.50, bias=False): 10 | super(ArcFace, self).__init__() 11 | self.in_features = in_features 12 | self.out_features = out_features 13 | self.s = s 14 | self.m = m 15 | self.cos_m = math.cos(m) 16 | self.sin_m = math.sin(m) 17 | 18 | self.th = math.cos(math.pi - m) 19 | self.mm = math.sin(math.pi - m) * m 20 | 21 | self.weight = Parameter(torch.Tensor(out_features, in_features)) 22 | if bias: 23 | self.bias = Parameter(torch.Tensor(out_features)) 24 | else: 25 | self.register_parameter('bias', None) 26 | self.reset_parameters() 27 | 28 | def reset_parameters(self): 29 | nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5)) 30 | if self.bias is not None: 31 | fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight) 32 | bound = 1 / math.sqrt(fan_in) 33 | nn.init.uniform_(self.bias, -bound, bound) 34 | 35 | def forward(self, input, label): 36 | cosine = F.linear(F.normalize(input), F.normalize(self.weight)) 37 | sine = torch.sqrt((1.0 - torch.pow(cosine, 2)).clamp(0, 1)) 38 | phi = cosine * self.cos_m - sine * self.sin_m 39 | phi = torch.where(cosine > self.th, phi, cosine - self.mm) 40 | # --------------------------- convert label to one-hot --------------------------- 41 | # one_hot = torch.zeros(cosine.size(), requires_grad=True, device='cuda') 42 | one_hot = torch.zeros(cosine.size(), device='cuda') 43 | one_hot.scatter_(1, label.view(-1, 1).long(), 1) 44 | # -------------torch.where(out_i = {x_i if condition_i else y_i) ------------- 45 | output = (one_hot * phi) + ( 46 | (1.0 - one_hot) * cosine) # you can use torch.where if your torch.__version__ is 0.4 47 | output *= self.s 48 | # print(output) 49 | 50 | return output 51 | 52 | class CircleLoss(nn.Module): 53 | def __init__(self, in_features, num_classes, s=256, m=0.25): 54 | super(CircleLoss, self).__init__() 55 | self.weight = Parameter(torch.Tensor(num_classes, in_features)) 56 | self.s = s 57 | self.m = m 58 | self._num_classes = num_classes 59 | self.reset_parameters() 60 | 61 | 62 | def reset_parameters(self): 63 | nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5)) 64 | 65 | def __call__(self, bn_feat, targets): 66 | 67 | sim_mat = F.linear(F.normalize(bn_feat), F.normalize(self.weight)) 68 | alpha_p = torch.clamp_min(-sim_mat.detach() + 1 + self.m, min=0.) 69 | alpha_n = torch.clamp_min(sim_mat.detach() + self.m, min=0.) 70 | delta_p = 1 - self.m 71 | delta_n = self.m 72 | 73 | s_p = self.s * alpha_p * (sim_mat - delta_p) 74 | s_n = self.s * alpha_n * (sim_mat - delta_n) 75 | 76 | targets = F.one_hot(targets, num_classes=self._num_classes) 77 | 78 | pred_class_logits = targets * s_p + (1.0 - targets) * s_n 79 | 80 | return pred_class_logits -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/pedes.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import pickle 4 | 5 | import numpy as np 6 | import torch.utils.data as data 7 | from PIL import Image 8 | 9 | from tools.function import get_pkl_rootpath 10 | 11 | 12 | class PedesAttr(data.Dataset): 13 | 14 | def __init__(self, cfg, split, transform=None, target_transform=None, idx=None): 15 | 16 | assert cfg.DATASET.NAME in ['PETA', 'PA100k', 'RAP', 'RAP2'], \ 17 | f'dataset name {cfg.DATASET.NAME} is not exist' 18 | 19 | data_path = get_pkl_rootpath(cfg.DATASET.NAME, cfg.DATASET.ZERO_SHOT) 20 | 21 | print("which pickle", data_path) 22 | 23 | dataset_info = pickle.load(open(data_path, 'rb+')) 24 | 25 | img_id = dataset_info.image_name 26 | 27 | attr_label = dataset_info.label 28 | attr_label[attr_label == 2] = 0 29 | self.attr_id = dataset_info.attr_name 30 | self.attr_num = len(self.attr_id) 31 | 32 | if 'label_idx' not in dataset_info.keys(): 33 | print(' this is for zero shot split') 34 | assert cfg.DATASET.ZERO_SHOT 35 | self.eval_attr_num = self.attr_num 36 | else: 37 | self.eval_attr_idx = dataset_info.label_idx.eval 38 | self.eval_attr_num = len(self.eval_attr_idx) 39 | 40 | assert cfg.DATASET.LABEL in ['all', 'eval', 'color'], f'key word {cfg.DATASET.LABEL} error' 41 | if cfg.DATASET.LABEL == 'eval': 42 | attr_label = attr_label[:, self.eval_attr_idx] 43 | self.attr_id = [self.attr_id[i] for i in self.eval_attr_idx] 44 | self.attr_num = len(self.attr_id) 45 | elif cfg.DATASET.LABEL == 'color': 46 | attr_label = attr_label[:, self.eval_attr_idx + dataset_info.label_idx.color] 47 | self.attr_id = [self.attr_id[i] for i in self.eval_attr_idx + dataset_info.label_idx.color] 48 | self.attr_num = len(self.attr_id) 49 | 50 | assert split in dataset_info.partition.keys(), f'split {split} is not exist' 51 | 52 | self.dataset = cfg.DATASET.NAME 53 | self.transform = transform 54 | self.target_transform = target_transform 55 | 56 | self.root_path = f"./data/{dataset_info.root[38:]}" # dataset_info.root 57 | 58 | if self.target_transform: 59 | self.attr_num = len(self.target_transform) 60 | print(f'{split} target_label: {self.target_transform}') 61 | else: 62 | self.attr_num = len(self.attr_id) 63 | print(f'{split} target_label: all') 64 | 65 | self.img_idx = dataset_info.partition[split] 66 | 67 | if isinstance(self.img_idx, list): 68 | self.img_idx = self.img_idx[0] # default partition 0 69 | 70 | if idx is not None: 71 | self.img_idx = idx 72 | 73 | self.img_num = self.img_idx.shape[0] 74 | self.img_id = [img_id[i] for i in self.img_idx] 75 | self.label = attr_label[self.img_idx] # [:, [0, 12]] 76 | 77 | def __getitem__(self, index): 78 | 79 | imgname, gt_label, imgidx = self.img_id[index], self.label[index], self.img_idx[index] 80 | 81 | imgpath = os.path.join(self.root_path, imgname) 82 | img = Image.open(imgpath) 83 | 84 | if self.transform is not None: 85 | img = self.transform(img) 86 | 87 | gt_label = gt_label.astype(np.float32) 88 | 89 | if self.target_transform: 90 | gt_label = gt_label[self.target_transform] 91 | 92 | return img, gt_label, imgname, # noisy_weight 93 | 94 | def __len__(self): 95 | return len(self.img_id) 96 | 97 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | from utils.logger import setup_logger 2 | from data import build_dataloader 3 | from solver import make_optimizer 4 | from solver.scheduler_factory import create_scheduler 5 | from loss import make_loss 6 | from processor import do_train 7 | import random 8 | import torch 9 | import numpy as np 10 | import os 11 | import argparse 12 | from config import cfg 13 | from model import build_model 14 | 15 | 16 | def set_seed(seed): 17 | torch.manual_seed(seed) 18 | torch.cuda.manual_seed(seed) 19 | torch.cuda.manual_seed_all(seed) 20 | np.random.seed(seed) 21 | random.seed(seed) 22 | torch.backends.cudnn.deterministic = True 23 | torch.backends.cudnn.benchmark = True 24 | 25 | if __name__ == '__main__': 26 | 27 | parser = argparse.ArgumentParser(description="CC_ReID Training") 28 | parser.add_argument( 29 | "--config_file", default="", help="path to config file", type=str 30 | ) 31 | 32 | parser.add_argument("opts", help="Modify config options using the command-line", default=None, 33 | nargs=argparse.REMAINDER) 34 | parser.add_argument("--local_rank", default=0, type=int) 35 | args = parser.parse_args() 36 | 37 | if args.config_file != "": 38 | cfg.merge_from_file(args.config_file) 39 | cfg.merge_from_list(args.opts) 40 | cfg.freeze() 41 | 42 | set_seed(cfg.SOLVER.SEED) 43 | 44 | if cfg.MODEL.DIST_TRAIN: 45 | torch.cuda.set_device(args.local_rank) 46 | 47 | output_dir = cfg.OUTPUT_DIR 48 | if output_dir and not os.path.exists(output_dir): 49 | os.makedirs(output_dir, exist_ok=True) 50 | 51 | logger = setup_logger("EVA-attribure", output_dir, if_train=True) 52 | logger.info("Saving model in the path :{}".format(cfg.OUTPUT_DIR)) 53 | logger.info(args) 54 | 55 | if args.config_file != "": 56 | logger.info("Loaded configuration file {}".format(args.config_file)) 57 | with open(args.config_file, 'r') as cf: 58 | config_str = "\n" + cf.read() 59 | logger.info(config_str) 60 | logger.info("Running with config:\n{}".format(cfg)) 61 | 62 | if cfg.MODEL.DIST_TRAIN: 63 | torch.distributed.init_process_group(backend='nccl', init_method='env://') 64 | 65 | os.environ['CUDA_VISIBLE_DEVICES'] = cfg.MODEL.DEVICE_ID 66 | if cfg.DATA.DATASET == 'prcc': 67 | trainloader, queryloader_same, queryloader_diff, galleryloader, dataset, train_sampler,val_loader,val_loader_same= build_dataloader( 68 | cfg) # prcc_test 69 | else: 70 | trainloader, queryloader, galleryloader, dataset, train_sampler,val_loader = build_dataloader(cfg) 71 | 72 | model = build_model(cfg,dataset.num_train_pids) 73 | 74 | loss_func, center_criterion = make_loss(cfg, num_classes=dataset.num_train_pids) 75 | 76 | optimizer, optimizer_center = make_optimizer(cfg, model, center_criterion) 77 | 78 | scheduler = create_scheduler(cfg, optimizer) 79 | 80 | if cfg.DATA.DATASET == 'prcc': 81 | do_train( 82 | cfg, 83 | model, 84 | center_criterion, 85 | trainloader, 86 | optimizer, 87 | optimizer_center, 88 | scheduler, 89 | loss_func, 90 | args.local_rank, 91 | dataset, 92 | val_loader=val_loader, 93 | val_loader_same=val_loader_same 94 | ) 95 | else: 96 | do_train( 97 | cfg, 98 | model, 99 | center_criterion, 100 | trainloader, 101 | optimizer, 102 | optimizer_center, 103 | scheduler, 104 | loss_func, 105 | args.local_rank, 106 | dataset, 107 | val_loader=val_loader 108 | ) 109 | -------------------------------------------------------------------------------- /loss/make_loss.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | @author: liaoxingyu 4 | @contact: sherlockliao01@gmail.com 5 | """ 6 | 7 | import torch.nn.functional as F 8 | from .softmax_loss import CrossEntropyLabelSmooth, LabelSmoothingCrossEntropy 9 | from .triplet_loss import TripletLoss 10 | from .center_loss import CenterLoss 11 | 12 | 13 | def make_loss(cfg, num_classes): # modified by gu 14 | sampler = cfg.DATA.SAMPLER 15 | feat_dim = 1024 16 | center_criterion = CenterLoss(num_classes=num_classes, feat_dim=feat_dim, use_gpu=True) # center loss 17 | if 'triplet' in cfg.MODEL.METRIC_LOSS_TYPE: 18 | if cfg.MODEL.NO_MARGIN: 19 | triplet = TripletLoss() 20 | print("using soft triplet loss for training") 21 | else: 22 | triplet = TripletLoss(cfg.SOLVER.MARGIN) # triplet loss 23 | print("using triplet loss with margin:{}".format(cfg.SOLVER.MARGIN)) 24 | else: 25 | print('expected METRIC_LOSS_TYPE should be triplet' 26 | 'but got {}'.format(cfg.MODEL.METRIC_LOSS_TYPE)) 27 | 28 | if cfg.MODEL.IF_LABELSMOOTH == 'on': 29 | xent = CrossEntropyLabelSmooth(num_classes=num_classes) 30 | print("label smooth on, numclasses:", num_classes) 31 | 32 | if sampler == 'softmax': 33 | def loss_func(score, feat, target): 34 | return F.cross_entropy(score, target) 35 | 36 | elif cfg.DATA.SAMPLER == 'softmax_triplet': 37 | def loss_func(score, feat, target, target_cam): 38 | if cfg.MODEL.METRIC_LOSS_TYPE == 'triplet': 39 | if cfg.MODEL.IF_LABELSMOOTH == 'on': 40 | if isinstance(score, list): 41 | ID_LOSS = [xent(scor, target) for scor in score[1:]] 42 | ID_LOSS = sum(ID_LOSS) / len(ID_LOSS) 43 | ID_LOSS = 0.5 * ID_LOSS + 0.5 * xent(score[0], target) 44 | else: 45 | ID_LOSS = xent(score, target) 46 | 47 | if isinstance(feat, list): 48 | TRI_LOSS = [triplet(feats, target)[0] for feats in feat[1:]] 49 | TRI_LOSS = sum(TRI_LOSS) / len(TRI_LOSS) 50 | TRI_LOSS = 0.5 * TRI_LOSS + 0.5 * triplet(feat[0], target)[0] 51 | else: 52 | TRI_LOSS = triplet(feat, target)[0] 53 | 54 | return cfg.MODEL.ID_LOSS_WEIGHT * ID_LOSS + \ 55 | cfg.MODEL.TRIPLET_LOSS_WEIGHT * TRI_LOSS 56 | else: 57 | if isinstance(score, list): 58 | ID_LOSS = [F.cross_entropy(scor, target) for scor in score[1:]] 59 | ID_LOSS = sum(ID_LOSS) / len(ID_LOSS) 60 | ID_LOSS = 0.5 * ID_LOSS + 0.5 * F.cross_entropy(score[0], target) 61 | else: 62 | ID_LOSS = F.cross_entropy(score, target) 63 | 64 | if isinstance(feat, list): 65 | TRI_LOSS = [triplet(feats, target)[0] for feats in feat[1:]] 66 | TRI_LOSS = sum(TRI_LOSS) / len(TRI_LOSS) 67 | TRI_LOSS = 0.5 * TRI_LOSS + 0.5 * triplet(feat[0], target)[0] 68 | else: 69 | TRI_LOSS = triplet(feat, target)[0] 70 | 71 | return cfg.MODEL.ID_LOSS_WEIGHT * ID_LOSS + \ 72 | cfg.MODEL.TRIPLET_LOSS_WEIGHT * TRI_LOSS 73 | else: 74 | print('expected METRIC_LOSS_TYPE should be triplet' 75 | 'but got {}'.format(cfg.MODEL.METRIC_LOSS_TYPE)) 76 | 77 | else: 78 | print('expected sampler should be softmax, triplet, softmax_triplet or softmax_triplet_center' 79 | 'but got {}'.format(cfg.DATALOADER.SAMPLER)) 80 | return loss_func, center_criterion 81 | 82 | 83 | -------------------------------------------------------------------------------- /SOLIDER/models/base_block.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.init as init 6 | 7 | import torch.nn.functional as F 8 | from torch.nn.modules.batchnorm import _BatchNorm 9 | 10 | from models.registry import CLASSIFIER 11 | 12 | 13 | class BaseClassifier(nn.Module): 14 | 15 | def fresh_params(self, bn_wd): 16 | if bn_wd: 17 | return self.parameters() 18 | else: 19 | return self.named_parameters() 20 | 21 | @CLASSIFIER.register("linear") 22 | class LinearClassifier(BaseClassifier): 23 | def __init__(self, nattr, c_in, bn=False, pool='avg', scale=1): 24 | super().__init__() 25 | 26 | self.pool = pool 27 | if pool == 'avg': 28 | self.pool = nn.AdaptiveAvgPool2d(1) 29 | elif pool == 'max': 30 | self.pool = nn.AdaptiveMaxPool2d(1) 31 | 32 | self.logits = nn.Sequential( 33 | nn.Linear(c_in, nattr), 34 | nn.BatchNorm1d(nattr) if bn else nn.Identity() 35 | ) 36 | 37 | 38 | def forward(self, feature, label=None): 39 | 40 | if len(feature.shape) == 3: # for vit (bt, nattr, c) 41 | 42 | bt, hw, c = feature.shape 43 | # NOTE ONLY USED FOR INPUT SIZE (256, 192) 44 | h = 16 45 | w = 12 46 | feature = feature.reshape(bt, h, w, c).permute(0, 3, 1, 2) 47 | 48 | feat = self.pool(feature).view(feature.size(0), -1) 49 | x = self.logits(feat) 50 | 51 | 52 | classifier_n = F.normalize(self.logits[0].weight, dim=1) 53 | feat_n = F.normalize(feat, dim=1) 54 | 55 | cosine = feat_n @ classifier_n.t() 56 | 57 | return [x], cosine 58 | 59 | 60 | 61 | @CLASSIFIER.register("cosine") 62 | class NormClassifier(BaseClassifier): 63 | def __init__(self, nattr, c_in, bn=False, pool='avg', scale=30): 64 | super().__init__() 65 | 66 | self.logits = nn.Parameter(torch.FloatTensor(nattr, c_in)) 67 | 68 | stdv = 1. / math.sqrt(self.logits.data.size(1)) 69 | self.logits.data.uniform_(-stdv, stdv) 70 | 71 | self.pool = pool 72 | if pool == 'avg': 73 | self.pool = nn.AdaptiveAvgPool2d(1) 74 | elif pool == 'max': 75 | self.pool = nn.AdaptiveMaxPool2d(1) 76 | 77 | def forward(self, feature, label=None): 78 | feat = self.pool(feature).view(feature.size(0), -1) 79 | feat_n = F.normalize(feat, dim=1) 80 | weight_n = F.normalize(self.logits, dim=1) 81 | x = torch.matmul(feat_n, weight_n.t()) 82 | return [x], feat_n 83 | 84 | 85 | def initialize_weights(module): 86 | for m in module.children(): 87 | if isinstance(m, nn.Conv2d): 88 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 89 | m.weight.data.normal_(0, math.sqrt(2. / n)) 90 | elif isinstance(m, _BatchNorm): 91 | m.weight.data.fill_(1) 92 | if m.bias is not None: 93 | m.bias.data.zero_() 94 | elif isinstance(m, nn.Linear): 95 | stdv = 1. / math.sqrt(m.weight.size(1)) 96 | m.weight.data.uniform_(-stdv, stdv) 97 | 98 | 99 | class FeatClassifier(nn.Module): 100 | 101 | def __init__(self, backbone, classifier, bn_wd=True): 102 | super(FeatClassifier, self).__init__() 103 | 104 | self.backbone = backbone 105 | self.classifier = classifier 106 | self.bn_wd = bn_wd 107 | 108 | def fresh_params(self): 109 | return self.classifier.fresh_params(self.bn_wd) 110 | 111 | def finetune_params(self): 112 | 113 | if self.bn_wd: 114 | return self.backbone.parameters() 115 | else: 116 | return self.backbone.named_parameters() 117 | 118 | def forward(self, x, label=None): 119 | feat_map = self.backbone(x) 120 | logits, feat = self.classifier(feat_map, label) 121 | return logits, feat 122 | 123 | -------------------------------------------------------------------------------- /tools/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | import errno 5 | import json 6 | import os.path as osp 7 | import torch 8 | import random 9 | import logging 10 | import numpy as np 11 | 12 | 13 | def set_seed(seed=None): 14 | if seed is None: 15 | return 16 | random.seed(seed) 17 | os.environ['PYTHONHASHSEED'] = ("%s" % seed) 18 | np.random.seed(seed) 19 | torch.manual_seed(seed) 20 | torch.cuda.manual_seed(seed) 21 | torch.cuda.manual_seed_all(seed) 22 | torch.backends.cudnn.benchmark = False 23 | torch.backends.cudnn.deterministic = True 24 | 25 | 26 | def mkdir_if_missing(directory): 27 | if not osp.exists(directory): 28 | try: 29 | os.makedirs(directory) 30 | except OSError as e: 31 | if e.errno != errno.EEXIST: 32 | raise 33 | 34 | 35 | def read_json(fpath): 36 | with open(fpath, 'r') as f: 37 | obj = json.load(f) 38 | return obj 39 | 40 | 41 | def write_json(obj, fpath): 42 | mkdir_if_missing(osp.dirname(fpath)) 43 | with open(fpath, 'w') as f: 44 | json.dump(obj, f, indent=4, separators=(',', ': ')) 45 | 46 | 47 | class AverageMeter(object): 48 | """Computes and stores the average and current value. 49 | 50 | Code imported from https://github.com/pytorch/examples/blob/master/imagenet/main.py#L247-L262 51 | """ 52 | def __init__(self): 53 | self.reset() 54 | 55 | def reset(self): 56 | self.val = 0 57 | self.avg = 0 58 | self.sum = 0 59 | self.count = 0 60 | 61 | def update(self, val, n=1): 62 | self.val = val 63 | self.sum += val * n 64 | self.count += n 65 | self.avg = self.sum / self.count 66 | 67 | 68 | def save_checkpoint(state, is_best, fpath='checkpoint.pth.tar'): 69 | mkdir_if_missing(osp.dirname(fpath)) 70 | torch.save(state, fpath) 71 | if is_best: 72 | shutil.copy(fpath, osp.join(osp.dirname(fpath), 'best_model.pth.tar')) 73 | 74 | ''' 75 | class Logger(object): 76 | """ 77 | Write console output to external text file. 78 | Code imported from https://github.com/Cysu/open-reid/blob/master/reid/utils/logging.py. 79 | """ 80 | def __init__(self, fpath=None): 81 | self.console = sys.stdout 82 | self.file = None 83 | if fpath is not None: 84 | mkdir_if_missing(os.path.dirname(fpath)) 85 | self.file = open(fpath, 'w') 86 | 87 | def __del__(self): 88 | self.close() 89 | 90 | def __enter__(self): 91 | pass 92 | 93 | def __exit__(self, *args): 94 | self.close() 95 | 96 | def write(self, msg): 97 | self.console.write(msg) 98 | if self.file is not None: 99 | self.file.write(msg) 100 | 101 | def flush(self): 102 | self.console.flush() 103 | if self.file is not None: 104 | self.file.flush() 105 | os.fsync(self.file.fileno()) 106 | 107 | def close(self): 108 | self.console.close() 109 | if self.file is not None: 110 | self.file.close() 111 | ''' 112 | 113 | 114 | def get_logger(fpath, local_rank=0, name=''): 115 | # Creat logger 116 | logger = logging.getLogger(name) 117 | level = logging.INFO if local_rank in [-1, 0] else logging.WARN 118 | logger.setLevel(level=level) 119 | 120 | # Output to console 121 | console_handler = logging.StreamHandler(sys.stdout) 122 | console_handler.setLevel(level=level) 123 | console_handler.setFormatter(logging.Formatter('%(message)s')) 124 | logger.addHandler(console_handler) 125 | 126 | # Output to file 127 | if fpath is not None: 128 | mkdir_if_missing(os.path.dirname(fpath)) 129 | file_handler = logging.FileHandler(fpath, mode='w') 130 | file_handler.setLevel(level=level) 131 | file_handler.setFormatter(logging.Formatter('%(message)s')) 132 | logger.addHandler(file_handler) 133 | 134 | return logger -------------------------------------------------------------------------------- /SOLIDER/metrics/ml_metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from easydict import EasyDict 3 | from sklearn.metrics import average_precision_score 4 | 5 | 6 | def calc_average_precision(gt_label, probs): 7 | ndata, nattr = gt_label.shape 8 | 9 | ap_list = [] 10 | for i in range(nattr): 11 | y_true = gt_label[:, i] 12 | y_score = probs[:, i] 13 | 14 | ap_list.append(average_precision_score(y_true, y_score)) 15 | ap = np.array(ap_list) 16 | mAp = ap.mean() 17 | return mAp, ap 18 | 19 | def get_map_metrics(gt_label, probs): 20 | mAP, ap = calc_average_precision(gt_label, probs) 21 | 22 | return mAP, ap 23 | 24 | # same as calc_average_precision 25 | def get_mAp(gt_label: np.ndarray, probs: np.ndarray): 26 | ndata, nattr = gt_label.shape 27 | rg = np.arange(1, ndata + 1).astype(float) 28 | ap_list = [] 29 | for k in range(nattr): 30 | # sort scores 31 | scores = probs[:, k] 32 | targets = gt_label[:, k] 33 | sorted_idx = np.argsort(scores)[::-1] # Descending 34 | truth = targets[sorted_idx] 35 | 36 | tp = np.cumsum(truth).astype(float) 37 | # compute precision curve 38 | precision = tp / rg 39 | 40 | # compute average precision 41 | ap_list.append(precision[truth == 1].sum() / max(truth.sum(), 1)) 42 | 43 | ap = np.array(ap_list) 44 | mAp = ap.mean() 45 | return mAp, ap 46 | 47 | 48 | 49 | 50 | 51 | def prob2metric(gt_label: np.ndarray, probs: np.ndarray, th): 52 | eps = 1e-6 53 | ndata, nattr = gt_label.shape 54 | 55 | # ------------------ macro, micro --------------- 56 | # gt_label[gt_label == -1] = 0 57 | pred_label = probs > th 58 | gt_pos = gt_label.sum(0) 59 | pred_pos = pred_label.sum(0) 60 | tp = (gt_label * pred_label).sum(0) 61 | 62 | OP = tp.sum() / pred_pos.sum() 63 | OR = tp.sum() / gt_pos.sum() 64 | OF1 = (2 * OP * OR) / (OP + OR) 65 | 66 | pred_pos[pred_pos == 0] = 1 67 | 68 | CP_all = tp / pred_pos 69 | CR_all = tp / gt_pos 70 | 71 | CP_all_t = tp / pred_pos 72 | CP_all_t[CP_all_t == 0] = 1 73 | CR_all_t = tp / gt_pos 74 | CR_all_t[CR_all_t == 0] = 1 75 | CF1_all = (2 * CP_all * CR_all) / (CP_all_t + CR_all_t) 76 | 77 | CF1_mean = CF1_all.mean() 78 | 79 | CP = np.mean(tp / pred_pos) 80 | CR = np.mean(tp / gt_pos) 81 | CF1 = (2 * CP * CR) / (CP + CR) 82 | 83 | gt_neg = ndata - gt_pos 84 | tn = ((1 - gt_label) * (1 - pred_label)).sum(0) 85 | 86 | label_pos_recall = 1.0 * tp / (gt_pos + eps) # true positive 87 | label_neg_recall = 1.0 * tn / (gt_neg + eps) # true negative 88 | # mean accuracy 89 | label_ma = (label_pos_recall + label_neg_recall) / 2 90 | 91 | ma = label_ma.mean() 92 | 93 | return OP, OR, OF1, CP, CR, CF1, ma, CP_all, CR_all, CF1_all, CF1_mean 94 | 95 | 96 | def get_multilabel_metrics(gt_label, prob_pred, th=0.5): 97 | 98 | result = EasyDict() 99 | 100 | 101 | mAP, ap = calc_average_precision(gt_label, prob_pred) 102 | op, orecall, of1, cp, cr, cf1, ma, cp_all, cr_all, cf1_all, CF1_mean = prob2metric(gt_label, prob_pred, th) 103 | result.map = mAP * 100. 104 | 105 | # to json serializable 106 | result.CP_all = list(cp_all.astype(np.float64)) 107 | result.CR_all = list(cr_all.astype(np.float64)) 108 | result.CF1_all = list(cf1_all.astype(np.float64)) 109 | result.CF1_mean = CF1_mean 110 | 111 | # simplified way 112 | # mAP, ap = calc_average_precision(gt_label, probs) 113 | # pred_label = probs > 0.5 114 | # CP, CR, _, _ = precision_recall_fscore_support(gt_label, pred_label, average='macro') 115 | # CF1 = 2 * CP * CR / (CP + CR) 116 | # OP, OR, OF1, _ = precision_recall_fscore_support(gt_label, pred_label, average='micro') 117 | 118 | result.OP = op * 100. 119 | result.OR = orecall * 100. 120 | result.OF1 = of1 * 100. 121 | result.CP = cp * 100. 122 | result.CR = cr * 100. 123 | result.CF1 = cf1 * 100. 124 | 125 | return result 126 | 127 | -------------------------------------------------------------------------------- /SOLIDER/tools/function.py: -------------------------------------------------------------------------------- 1 | import os 2 | from collections import OrderedDict 3 | 4 | import numpy as np 5 | import torch 6 | 7 | from tools.utils import may_mkdirs 8 | 9 | 10 | def seperate_weight_decay(named_params, lr, weight_decay=1e-5, skip_list=()): 11 | decay = [] 12 | no_decay = [] 13 | for name, param in named_params: 14 | if not param.requires_grad: 15 | continue 16 | if len(param.shape) == 1 or name in skip_list: 17 | no_decay.append(param) 18 | # if 'bias' in name: 19 | # no_decay.append(param) 20 | else: 21 | decay.append(param) 22 | return [{'params': no_decay, 'lr': lr, 'weight_decay': 0.}, 23 | {'params': decay, 'lr': lr, 'weight_decay': weight_decay}] 24 | 25 | 26 | def ratio2weight(targets, ratio): 27 | ratio = torch.from_numpy(ratio).type_as(targets) 28 | 29 | # --------------------- dangwei li TIP20 --------------------- 30 | pos_weights = targets * (1 - ratio) 31 | neg_weights = (1 - targets) * ratio 32 | weights = torch.exp(neg_weights + pos_weights) 33 | 34 | 35 | # --------------------- AAAI --------------------- 36 | # pos_weights = torch.sqrt(1 / (2 * ratio.sqrt())) * targets 37 | # neg_weights = torch.sqrt(1 / (2 * (1 - ratio.sqrt()))) * (1 - targets) 38 | # weights = pos_weights + neg_weights 39 | 40 | # for RAP dataloader, targets element may be 2, with or without smooth, some element must great than 1 41 | weights[targets > 1] = 0.0 42 | 43 | return weights 44 | 45 | 46 | def get_model_log_path(root_path, model_name): 47 | multi_attr_model_dir = os.path.join(root_path, model_name, 'img_model') 48 | may_mkdirs(multi_attr_model_dir) 49 | 50 | multi_attr_log_dir = os.path.join(root_path, model_name, 'log') 51 | may_mkdirs(multi_attr_log_dir) 52 | 53 | return multi_attr_model_dir, multi_attr_log_dir 54 | 55 | 56 | class LogVisual: 57 | 58 | def __init__(self, args): 59 | self.args = vars(args) 60 | self.train_loss = [] 61 | self.val_loss = [] 62 | 63 | self.ap = [] 64 | self.map = [] 65 | self.acc = [] 66 | self.prec = [] 67 | self.recall = [] 68 | self.f1 = [] 69 | 70 | self.error_num = [] 71 | self.fn_num = [] 72 | self.fp_num = [] 73 | 74 | self.save = False 75 | 76 | def append(self, **kwargs): 77 | self.save = False 78 | 79 | if 'result' in kwargs: 80 | self.ap.append(kwargs['result']['label_acc']) 81 | self.map.append(np.mean(kwargs['result']['label_acc'])) 82 | self.acc.append(np.mean(kwargs['result']['instance_acc'])) 83 | self.prec.append(np.mean(kwargs['result']['instance_precision'])) 84 | self.recall.append(np.mean(kwargs['result']['instance_recall'])) 85 | self.f1.append(np.mean(kwargs['result']['floatance_F1'])) 86 | 87 | self.error_num.append(kwargs['result']['error_num']) 88 | self.fn_num.append(kwargs['result']['fn_num']) 89 | self.fp_num.append(kwargs['result']['fp_num']) 90 | 91 | if 'train_loss' in kwargs: 92 | self.train_loss.append(kwargs['train_loss']) 93 | if 'val_loss' in kwargs: 94 | self.val_loss.append(kwargs['val_loss']) 95 | 96 | 97 | def get_pkl_rootpath(dataset, zero_shot): 98 | root = os.path.join("./data", f"{dataset}") 99 | if zero_shot: 100 | data_path = os.path.join(root, 'dataset_zs_run0.pkl') 101 | else: 102 | data_path = os.path.join(root, 'dataset_all.pkl') # 103 | 104 | return data_path 105 | 106 | 107 | def get_reload_weight(model_path, model, pth='ckpt_max.pth'): 108 | model_path = os.path.join(model_path, pth) 109 | load_dict = torch.load(model_path, map_location=lambda storage, loc: storage) 110 | 111 | if isinstance(load_dict, OrderedDict): 112 | pretrain_dict = load_dict 113 | else: 114 | pretrain_dict = load_dict['state_dicts'] 115 | print(f"best performance {load_dict['metric']} in epoch : {load_dict['epoch']}") 116 | 117 | model.load_state_dict(pretrain_dict, strict=True) 118 | 119 | return model 120 | -------------------------------------------------------------------------------- /SOLIDER/configs/default.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | 5 | from yacs.config import CfgNode as CN 6 | 7 | _C = CN() 8 | 9 | 10 | # ----- BASIC SETTINGS ----- 11 | _C.NAME = "default" 12 | _C.REDIRECTOR = True 13 | 14 | _C.RELOAD = CN() 15 | _C.RELOAD.TYPE = False 16 | _C.RELOAD.NAME = 'backbone' 17 | _C.RELOAD.PTH = '' 18 | 19 | 20 | _C.DISTRIBUTTED = False 21 | 22 | # ----- DATASET BUILDER ----- 23 | _C.DATASET = CN() 24 | _C.DATASET.TYPE = "pedes" 25 | _C.DATASET.NAME = "PA100k" 26 | _C.DATASET.TARGETTRANSFORM = [] 27 | _C.DATASET.ZERO_SHOT = False 28 | _C.DATASET.LABEL = 'eval' # train on all labels, test on part labels (35 for peta, 51 for rap) 29 | _C.DATASET.TRAIN_SPLIT = 'trainval' 30 | _C.DATASET.VAL_SPLIT = 'val' 31 | _C.DATASET.TEST_SPLIT = 'test' 32 | _C.DATASET.HEIGHT = 256 33 | _C.DATASET.WIDTH = 192 34 | 35 | # ----- BACKBONE BUILDER ----- 36 | _C.BACKBONE = CN() 37 | _C.BACKBONE.TYPE = "resnet50" 38 | _C.BACKBONE.MULTISCALE = False 39 | 40 | # ----- MODULE BUILDER ----- 41 | # _C.MODULE = CN() 42 | # _C.MODULE.TYPE = "GAP" 43 | 44 | # ----- CLASSIFIER BUILDER ----- 45 | _C.CLASSIFIER = CN() 46 | _C.CLASSIFIER.TYPE = "base" 47 | _C.CLASSIFIER.NAME = "" 48 | _C.CLASSIFIER.POOLING = "avg" 49 | _C.CLASSIFIER.BN = False 50 | _C.CLASSIFIER.SCALE = 1 51 | 52 | # ----- TRAIN BUILDER ----- 53 | _C.TRAIN = CN() 54 | _C.TRAIN.BATCH_SIZE = 64 55 | _C.TRAIN.MAX_EPOCH = 30 56 | _C.TRAIN.SHUFFLE = True 57 | _C.TRAIN.NUM_WORKERS = 4 58 | _C.TRAIN.CLIP_GRAD = False 59 | _C.TRAIN.BN_WD = True 60 | 61 | _C.TRAIN.DATAAUG = CN() 62 | _C.TRAIN.DATAAUG.TYPE = 'base' 63 | _C.TRAIN.DATAAUG.AUTOAUG_PROB = 0.5 64 | 65 | _C.TRAIN.EMA = CN() 66 | _C.TRAIN.EMA.ENABLE = False 67 | _C.TRAIN.EMA.DECAY = 0.9998 68 | _C.TRAIN.EMA.FORCE_CPU = False 69 | 70 | _C.TRAIN.OPTIMIZER = CN() 71 | _C.TRAIN.OPTIMIZER.TYPE = "SGD" 72 | _C.TRAIN.OPTIMIZER.MOMENTUM = 0.9 73 | _C.TRAIN.OPTIMIZER.WEIGHT_DECAY = 1e-4 74 | 75 | _C.TRAIN.LR_SCHEDULER = CN() 76 | _C.TRAIN.LR_SCHEDULER.TYPE = "plateau" 77 | _C.TRAIN.LR_SCHEDULER.LR_STEP = [0,] 78 | _C.TRAIN.LR_SCHEDULER.LR_FT = 1e-2 79 | _C.TRAIN.LR_SCHEDULER.LR_NEW = 1e-2 80 | _C.TRAIN.LR_SCHEDULER.WMUP_COEF = 0.01 81 | _C.TRAIN.LR_SCHEDULER.WMUP_LR_INIT = 1e-6 82 | 83 | 84 | _C.TRAIN.AUX_LOSS_START = -1 85 | 86 | # ----- INFER BUILDER ----- 87 | 88 | _C.INFER = CN() 89 | _C.INFER.SAMPLING = False 90 | 91 | # ----- LOSS BUILDER ----- 92 | _C.LOSS = CN() 93 | _C.LOSS.TYPE = "bce" 94 | _C.LOSS.SAMPLE_WEIGHT = "" # None 95 | _C.LOSS.LOSS_WEIGHT = [1, ] 96 | _C.LOSS.SIZESUM = True # for a sample, BCE losses is the summation of all label instead of the average. 97 | 98 | _C.METRIC = CN() 99 | _C.METRIC.TYPE = 'pedestrian' 100 | 101 | # ------ visualization --------- 102 | _C.VIS = CN() 103 | _C.VIS.CAM = 'valid' 104 | _C.VIS.TENSORBOARD = CN() 105 | _C.VIS.TENSORBOARD.ENABLE = True 106 | 107 | _C.VIS.VISDOM = False 108 | 109 | 110 | # ----------- Transformer ------------- 111 | _C.TRANS = CN() 112 | _C.TRANS.DIM_HIDDEN = 256 113 | _C.TRANS.DROPOUT = 0.1 114 | _C.TRANS.NHEADS = 8 115 | _C.TRANS.DIM_FFD = 2048 116 | _C.TRANS.ENC_LAYERS = 6 117 | _C.TRANS.DEC_LAYERS = 6 118 | _C.TRANS.PRE_NORM = False 119 | _C.TRANS.EOS_COEF = 0.1 120 | _C.TRANS.NUM_QUERIES = 100 121 | 122 | 123 | # testing 124 | # _C.TEST = CN() 125 | # _C.TEST.BATCH_SIZE = 32 126 | # _C.TEST.NUM_WORKERS = 8 127 | # _C.TEST.MODEL_FILE = "" 128 | # 129 | # _C.TRANSFORMS = CN() 130 | # _C.TRANSFORMS.TRAIN_TRANSFORMS = ("random_resized_crop", "random_horizontal_flip") 131 | # _C.TRANSFORMS.TEST_TRANSFORMS = ("shorter_resize_for_crop", "center_crop") 132 | # 133 | # _C.TRANSFORMS.PROCESS_DETAIL = CN() 134 | # _C.TRANSFORMS.PROCESS_DETAIL.RANDOM_CROP = CN() 135 | # _C.TRANSFORMS.PROCESS_DETAIL.RANDOM_CROP.PADDING = 4 136 | # _C.TRANSFORMS.PROCESS_DETAIL.RANDOM_RESIZED_CROP = CN() 137 | # _C.TRANSFORMS.PROCESS_DETAIL.RANDOM_RESIZED_CROP.SCALE = (0.08, 1.0) 138 | # _C.TRANSFORMS.PROCESS_DETAIL.RANDOM_RESIZED_CROP.RATIO = (0.75, 1.333333333) 139 | 140 | 141 | def update_config(cfg, args): 142 | cfg.defrost() 143 | 144 | cfg.merge_from_file(args.cfg) # update cfg 145 | # cfg.merge_from_list(args.opts) 146 | 147 | cfg.freeze() 148 | -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/preprocess/format_peta.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import pickle 5 | 6 | from easydict import EasyDict 7 | from scipy.io import loadmat 8 | 9 | np.random.seed(0) 10 | random.seed(0) 11 | 12 | # note: ref by annotation.md 13 | 14 | group_order = [10, 18, 19, 30, 15, 7, 9, 11, 14, 21, 26, 29, 32, 33, 34, 6, 8, 12, 25, 27, 31, 13, 23, 24, 28, 4, 5, 15 | 17, 20, 22, 0, 1, 2, 3, 16] 16 | 17 | 18 | def make_dir(path): 19 | if os.path.exists(path): 20 | pass 21 | else: 22 | os.mkdir(path) 23 | 24 | 25 | def generate_data_description(save_dir, reorder, new_split_path): 26 | """ 27 | create a dataset description file, which consists of images, labels 28 | """ 29 | peta_data = loadmat(os.path.join(save_dir, 'PETA.mat')) 30 | dataset = EasyDict() 31 | dataset.description = 'peta' 32 | dataset.reorder = 'group_order' 33 | dataset.root = os.path.join(save_dir, 'images') 34 | dataset.image_name = [f'{i + 1:05}.png' for i in range(19000)] 35 | 36 | raw_attr_name = [i[0][0] for i in peta_data['peta'][0][0][1]] 37 | # (19000, 105) 38 | raw_label = peta_data['peta'][0][0][0][:, 4:] 39 | 40 | # (19000, 35) 41 | 42 | dataset.label = raw_label 43 | dataset.attr_name = raw_attr_name 44 | 45 | dataset.label_idx = EasyDict() 46 | dataset.label_idx.eval = list(range(35)) 47 | dataset.label_idx.color = list(range(35, 79)) 48 | dataset.label_idx.extra = range(79, raw_label.shape[1]) # (79, 105) 49 | 50 | if reorder: 51 | dataset.label_idx.eval = group_order 52 | 53 | dataset.partition = EasyDict() 54 | dataset.partition.train = [] 55 | dataset.partition.val = [] 56 | dataset.partition.trainval = [] 57 | dataset.partition.test = [] 58 | 59 | dataset.weight_train = [] 60 | dataset.weight_trainval = [] 61 | 62 | if new_split_path: 63 | 64 | with open(new_split_path, 'rb+') as f: 65 | new_split = pickle.load(f) 66 | 67 | train = np.array(new_split.train_idx) 68 | val = np.array(new_split.val_idx) 69 | test = np.array(new_split.test_idx) 70 | trainval = np.concatenate((train, val), axis=0) 71 | 72 | dataset.partition.train = train 73 | dataset.partition.val = val 74 | dataset.partition.trainval = trainval 75 | dataset.partition.test = test 76 | 77 | weight_train = np.mean(dataset.label[train], axis=0).astype(np.float32) 78 | weight_trainval = np.mean(dataset.label[trainval], axis=0).astype(np.float32) 79 | 80 | dataset.weight_train.append(weight_train) 81 | dataset.weight_trainval.append(weight_trainval) 82 | with open(os.path.join(save_dir, 'dataset_zs_run4.pkl'), 'wb+') as f: 83 | pickle.dump(dataset, f) 84 | 85 | else: 86 | 87 | for idx in range(5): 88 | train = peta_data['peta'][0][0][3][idx][0][0][0][0][:, 0] - 1 89 | val = peta_data['peta'][0][0][3][idx][0][0][0][1][:, 0] - 1 90 | test = peta_data['peta'][0][0][3][idx][0][0][0][2][:, 0] - 1 91 | trainval = np.concatenate((train, val), axis=0) 92 | 93 | dataset.partition.train.append(train) 94 | dataset.partition.val.append(val) 95 | dataset.partition.trainval.append(trainval) 96 | dataset.partition.test.append(test) 97 | 98 | weight_train = np.mean(dataset.label[train], axis=0) 99 | weight_trainval = np.mean(dataset.label[trainval], axis=0) 100 | 101 | dataset.weight_train.append(weight_train) 102 | dataset.weight_trainval.append(weight_trainval) 103 | 104 | """ 105 | dataset.pkl 只包含评价属性的文件 35 label 106 | dataset_all.pkl 包含所有属性的文件 105 label 107 | """ 108 | with open(os.path.join(save_dir, 'dataset_all.pkl'), 'wb+') as f: 109 | pickle.dump(dataset, f) 110 | 111 | 112 | if __name__ == "__main__": 113 | save_dir = '/mnt/data1/jiajian/datasets/attribute/PETA/' 114 | new_split_path = '/mnt/data1/jiajian/code/Rethinking_of_PAR/datasets/jian_split/index_peta_split_id50_img300_ratio0.03_4.pkl' 115 | generate_data_description(save_dir, True, new_split_path) 116 | -------------------------------------------------------------------------------- /solver/cosine_lr.py: -------------------------------------------------------------------------------- 1 | """ Cosine Scheduler 2 | 3 | Cosine LR schedule with warmup, cycle/restarts, noise. 4 | 5 | Hacked together by / Copyright 2020 Ross Wightman 6 | """ 7 | import logging 8 | import math 9 | import torch 10 | 11 | from .scheduler import Scheduler 12 | 13 | 14 | _logger = logging.getLogger(__name__) 15 | 16 | 17 | class CosineLRScheduler(Scheduler): 18 | """ 19 | Cosine decay with restarts. 20 | This is described in the paper https://arxiv.org/abs/1608.03983. 21 | 22 | Inspiration from 23 | https://github.com/allenai/allennlp/blob/master/allennlp/training/learning_rate_schedulers/cosine.py 24 | """ 25 | 26 | def __init__(self, 27 | optimizer: torch.optim.Optimizer, 28 | t_initial: int, 29 | t_mul: float = 1., 30 | lr_min: float = 0., 31 | decay_rate: float = 1., 32 | warmup_t=0, 33 | warmup_lr_init=0, 34 | warmup_prefix=False, 35 | cycle_limit=0, 36 | t_in_epochs=True, 37 | noise_range_t=None, 38 | noise_pct=0.67, 39 | noise_std=1.0, 40 | noise_seed=42, 41 | initialize=True) -> None: 42 | super().__init__( 43 | optimizer, param_group_field="lr", 44 | noise_range_t=noise_range_t, noise_pct=noise_pct, noise_std=noise_std, noise_seed=noise_seed, 45 | initialize=initialize) 46 | 47 | assert t_initial > 0 48 | assert lr_min >= 0 49 | if t_initial == 1 and t_mul == 1 and decay_rate == 1: 50 | _logger.warning("Cosine annealing scheduler will have no effect on the learning " 51 | "rate since t_initial = t_mul = eta_mul = 1.") 52 | self.t_initial = t_initial 53 | self.t_mul = t_mul 54 | self.lr_min = lr_min 55 | self.decay_rate = decay_rate 56 | self.cycle_limit = cycle_limit 57 | self.warmup_t = warmup_t 58 | self.warmup_lr_init = warmup_lr_init 59 | self.warmup_prefix = warmup_prefix 60 | self.t_in_epochs = t_in_epochs 61 | if self.warmup_t: 62 | self.warmup_steps = [(v - warmup_lr_init) / self.warmup_t for v in self.base_values] 63 | super().update_groups(self.warmup_lr_init) 64 | else: 65 | self.warmup_steps = [1 for _ in self.base_values] 66 | 67 | def _get_lr(self, t): 68 | if t < self.warmup_t: 69 | lrs = [self.warmup_lr_init + t * s for s in self.warmup_steps] 70 | else: 71 | if self.warmup_prefix: 72 | t = t - self.warmup_t 73 | 74 | if self.t_mul != 1: 75 | i = math.floor(math.log(1 - t / self.t_initial * (1 - self.t_mul), self.t_mul)) 76 | t_i = self.t_mul ** i * self.t_initial 77 | t_curr = t - (1 - self.t_mul ** i) / (1 - self.t_mul) * self.t_initial 78 | else: 79 | i = t // self.t_initial 80 | t_i = self.t_initial 81 | t_curr = t - (self.t_initial * i) 82 | 83 | gamma = self.decay_rate ** i 84 | lr_min = self.lr_min * gamma 85 | lr_max_values = [v * gamma for v in self.base_values] 86 | 87 | if self.cycle_limit == 0 or (self.cycle_limit > 0 and i < self.cycle_limit): 88 | lrs = [ 89 | lr_min + 0.5 * (lr_max - lr_min) * (1 + math.cos(math.pi * t_curr / t_i)) for lr_max in lr_max_values 90 | ] 91 | else: 92 | lrs = [self.lr_min for _ in self.base_values] 93 | 94 | return lrs 95 | 96 | def get_epoch_values(self, epoch: int): 97 | if self.t_in_epochs: 98 | return self._get_lr(epoch) 99 | else: 100 | return None 101 | 102 | def get_update_values(self, num_updates: int): 103 | if not self.t_in_epochs: 104 | return self._get_lr(num_updates) 105 | else: 106 | return None 107 | 108 | def get_cycle_length(self, cycles=0): 109 | if not cycles: 110 | cycles = self.cycle_limit 111 | cycles = max(1, cycles) 112 | if self.t_mul == 1.0: 113 | return self.t_initial * cycles 114 | else: 115 | return int(math.floor(-self.t_initial * (self.t_mul ** cycles - 1) / (1 - self.t_mul))) 116 | -------------------------------------------------------------------------------- /SOLIDER/scheduler/cosine_lr_weihua.py: -------------------------------------------------------------------------------- 1 | """ Cosine Scheduler 2 | 3 | Cosine LR schedule with warmup, cycle/restarts, noise. 4 | 5 | Hacked together by / Copyright 2020 Ross Wightman 6 | """ 7 | import logging 8 | import math 9 | import torch 10 | 11 | from .scheduler import Scheduler 12 | 13 | 14 | _logger = logging.getLogger(__name__) 15 | 16 | 17 | class CosineLRScheduler(Scheduler): 18 | """ 19 | Cosine decay with restarts. 20 | This is described in the paper https://arxiv.org/abs/1608.03983. 21 | 22 | Inspiration from 23 | https://github.com/allenai/allennlp/blob/master/allennlp/training/learning_rate_schedulers/cosine.py 24 | """ 25 | 26 | def __init__(self, 27 | optimizer: torch.optim.Optimizer, 28 | t_initial: int, 29 | t_mul: float = 1., 30 | lr_min: float = 0., 31 | decay_rate: float = 1., 32 | warmup_t=0, 33 | warmup_lr_init=0, 34 | warmup_prefix=False, 35 | cycle_limit=0, 36 | t_in_epochs=True, 37 | noise_range_t=None, 38 | noise_pct=0.67, 39 | noise_std=1.0, 40 | noise_seed=42, 41 | initialize=True) -> None: 42 | super().__init__( 43 | optimizer, param_group_field="lr", 44 | noise_range_t=noise_range_t, noise_pct=noise_pct, noise_std=noise_std, noise_seed=noise_seed, 45 | initialize=initialize) 46 | 47 | assert t_initial > 0 48 | assert lr_min >= 0 49 | if t_initial == 1 and t_mul == 1 and decay_rate == 1: 50 | _logger.warning("Cosine annealing scheduler will have no effect on the learning " 51 | "rate since t_initial = t_mul = eta_mul = 1.") 52 | self.t_initial = t_initial 53 | self.t_mul = t_mul 54 | self.lr_min = lr_min 55 | self.decay_rate = decay_rate 56 | self.cycle_limit = cycle_limit 57 | self.warmup_t = warmup_t 58 | self.warmup_lr_init = warmup_lr_init 59 | self.warmup_prefix = warmup_prefix 60 | self.t_in_epochs = t_in_epochs 61 | if self.warmup_t: 62 | self.warmup_steps = [(v - warmup_lr_init) / self.warmup_t for v in self.base_values] 63 | super().update_groups(self.warmup_lr_init) 64 | else: 65 | self.warmup_steps = [1 for _ in self.base_values] 66 | 67 | def _get_lr(self, t): 68 | if t < self.warmup_t: 69 | lrs = [self.warmup_lr_init + t * s for s in self.warmup_steps] 70 | else: 71 | if self.warmup_prefix: 72 | t = t - self.warmup_t 73 | 74 | if self.t_mul != 1: 75 | i = math.floor(math.log(1 - t / self.t_initial * (1 - self.t_mul), self.t_mul)) 76 | t_i = self.t_mul ** i * self.t_initial 77 | t_curr = t - (1 - self.t_mul ** i) / (1 - self.t_mul) * self.t_initial 78 | else: 79 | i = t // self.t_initial 80 | t_i = self.t_initial 81 | t_curr = t - (self.t_initial * i) 82 | 83 | gamma = self.decay_rate ** i 84 | lr_min = self.lr_min * gamma 85 | lr_max_values = [v * gamma for v in self.base_values] 86 | 87 | if self.cycle_limit == 0 or (self.cycle_limit > 0 and i < self.cycle_limit): 88 | lrs = [ 89 | lr_min + 0.5 * (lr_max - lr_min) * (1 + math.cos(math.pi * t_curr / t_i)) for lr_max in lr_max_values 90 | ] 91 | else: 92 | lrs = [self.lr_min for _ in self.base_values] 93 | 94 | return lrs 95 | 96 | def get_epoch_values(self, epoch: int): 97 | if self.t_in_epochs: 98 | return self._get_lr(epoch) 99 | else: 100 | return None 101 | 102 | def get_update_values(self, num_updates: int): 103 | if not self.t_in_epochs: 104 | return self._get_lr(num_updates) 105 | else: 106 | return None 107 | 108 | def get_cycle_length(self, cycles=0): 109 | if not cycles: 110 | cycles = self.cycle_limit 111 | cycles = max(1, cycles) 112 | if self.t_mul == 1.0: 113 | return self.t_initial * cycles 114 | else: 115 | return int(math.floor(-self.t_initial * (self.t_mul ** cycles - 1) / (1 - self.t_mul))) 116 | -------------------------------------------------------------------------------- /data/img_transforms.py: -------------------------------------------------------------------------------- 1 | from torchvision.transforms import * 2 | from PIL import Image 3 | import random 4 | import math 5 | 6 | 7 | class ResizeWithEqualScale(object): 8 | """ 9 | Resize an image with equal scale as the original image. 10 | 11 | Args: 12 | height (int): resized height. 13 | width (int): resized width. 14 | interpolation: interpolation manner. 15 | fill_color (tuple): color for padding. 16 | """ 17 | def __init__(self, height, width, interpolation=Image.BILINEAR, fill_color=(0,0,0)): 18 | self.height = height 19 | self.width = width 20 | self.interpolation = interpolation 21 | self.fill_color = fill_color 22 | 23 | def __call__(self, img): 24 | width, height = img.size 25 | if self.height / self.width >= height / width: 26 | height = int(self.width * (height / width)) 27 | width = self.width 28 | else: 29 | width = int(self.height * (width / height)) 30 | height = self.height 31 | 32 | resized_img = img.resize((width, height), self.interpolation) 33 | new_img = Image.new('RGB', (self.width, self.height), self.fill_color) 34 | new_img.paste(resized_img, (int((self.width - width) / 2), int((self.height - height) / 2))) 35 | 36 | return new_img 37 | 38 | 39 | class RandomCroping(object): 40 | """ 41 | With a probability, first increase image size to (1 + 1/8), and then perform random crop. 42 | 43 | Args: 44 | p (float): probability of performing this transformation. Default: 0.5. 45 | """ 46 | def __init__(self, p=0.5, interpolation=Image.BILINEAR): 47 | self.p = p 48 | self.interpolation = interpolation 49 | 50 | def __call__(self, img): 51 | """ 52 | Args: 53 | img (PIL Image): Image to be cropped. 54 | 55 | Returns: 56 | PIL Image: Cropped image. 57 | """ 58 | width, height = img.size 59 | if random.uniform(0, 1) >= self.p: 60 | return img 61 | 62 | new_width, new_height = int(round(width * 1.125)), int(round(height * 1.125)) 63 | resized_img = img.resize((new_width, new_height), self.interpolation) 64 | x_maxrange = new_width - width 65 | y_maxrange = new_height - height 66 | x1 = int(round(random.uniform(0, x_maxrange))) 67 | y1 = int(round(random.uniform(0, y_maxrange))) 68 | croped_img = resized_img.crop((x1, y1, x1 + width, y1 + height)) 69 | 70 | return croped_img 71 | 72 | 73 | class RandomErasing(object): 74 | """ 75 | Randomly selects a rectangle region in an image and erases its pixels. 76 | 77 | Reference: 78 | Zhong et al. Random Erasing Data Augmentation. arxiv: 1708.04896, 2017. 79 | 80 | Args: 81 | probability: The probability that the Random Erasing operation will be performed. 82 | sl: Minimum proportion of erased area against input image. 83 | sh: Maximum proportion of erased area against input image. 84 | r1: Minimum aspect ratio of erased area. 85 | mean: Erasing value. 86 | """ 87 | 88 | def __init__(self, probability = 0.5, sl = 0.02, sh = 0.4, r1 = 0.3, mean=[0.4914, 0.4822, 0.4465]): 89 | self.probability = probability 90 | self.mean = mean 91 | self.sl = sl 92 | self.sh = sh 93 | self.r1 = r1 94 | 95 | def __call__(self, img): 96 | 97 | if random.uniform(0, 1) >= self.probability: 98 | return img 99 | 100 | for attempt in range(100): 101 | area = img.size()[1] * img.size()[2] 102 | 103 | target_area = random.uniform(self.sl, self.sh) * area 104 | aspect_ratio = random.uniform(self.r1, 1/self.r1) 105 | 106 | h = int(round(math.sqrt(target_area * aspect_ratio))) 107 | w = int(round(math.sqrt(target_area / aspect_ratio))) 108 | 109 | if w < img.size()[2] and h < img.size()[1]: 110 | x1 = random.randint(0, img.size()[1] - h) 111 | y1 = random.randint(0, img.size()[2] - w) 112 | if img.size()[0] == 3: 113 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 114 | img[1, x1:x1+h, y1:y1+w] = self.mean[1] 115 | img[2, x1:x1+h, y1:y1+w] = self.mean[2] 116 | else: 117 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 118 | return img 119 | 120 | return img -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Python =3.7](https://img.shields.io/badge/Python-=3.7-yellow.svg) 2 | ![PyTorch =1.13.1](https://img.shields.io/badge/PyTorch-=1.13.1-blue.svg) 3 | 4 | # Masked Attribute Description Embedding for Cloth-Changing Person Re-identification. 5 | The *official* repository for [Masked Attribute Description Embedding for Cloth-Changing Person Re-identification](http://arxiv.org/abs/2401.05646). 6 | 7 | ## Pipeline 8 | 9 | ![framework](figs/framework.png) 10 | 11 | ## Ablation Study of MADE 12 | 13 | ![framework](figs/ablation.png) 14 | 15 | 16 | 17 | #### Requirements 18 | - Python 3.7 19 | - Pytorch 1.13.1 20 | - yacs 21 | - timm 22 | - spacy 23 | 24 | ### Prepare Datasets 25 | 26 | 27 | Download the cloth-changing person Re-ID datasets, [PRCC](http://www.isee-ai.cn/%7Eyangqize/clothing.html), [LTCC](https://naiq.github.io/LTCC_Perosn_ReID.html), [Celeb-reID-light](https://github.com/Huang-3/Celeb-reID) and [LaST](https://github.com/shuxjweb/last.git). Then add attributes file(Get from [SOLIDER](/data/PycharmProjects/MADE/SOLIDER/README.md)) in each dataset file. Or download attributes files directly(remember to modify the path in file), [PRCC](https://drive.google.com/file/d/1qvQXd9r-IbYivczoR3vzHEcqM2AxVPLr/view?usp=sharing), [LTCC](https://drive.google.com/file/d/1uY0zUlpk7dAGZcblCUIyorRYY2c-ZPF1/view?usp=sharing), [Celeb-reID-light](https://drive.google.com/file/d/1dyn2OyOkkNCkA8VnIUVV4E4d3BhfemXq/view?usp=sharing), [LaST](https://drive.google.com/file/d/1TEqb5SdwQ0AVh13bNeJY1_RgZ7FYpCz4/view?usp=sharing). 28 | 29 | 30 | ``` 31 | Data 32 | ├── PRCC 33 | │   └── rgb .. 34 | │   └── sketch .. 35 | │   └── PAR_PETA_105.txt 36 | ├── LTCC 37 | │   └── train .. 38 | │   └── query .. 39 | │   └── test .. 40 | │   └── PAR_PETA_105.txt 41 | ├── Celeb-reID-light 42 | │   └── train .. 43 | │   └── query .. 44 | │   └── gallery .. 45 | │   └── PAR_PETA_105.txt 46 | ├── LaST 47 | │   └── train .. 48 | │   └── val .. 49 | │   └── test .. 50 | │   └── PAR_PETA_105.txt 51 | ``` 52 | 53 | ## Training 54 | 55 | We utilize 2 GPUs for training. Replace `_C.DATA.ROOT` in `config/defaults.py`with your own `data path`. 56 | 57 | ```bash 58 | CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file ${1} DATA.ADD_META ${2} DATA.MASK_META ${3} MODEL.DIST_TRAIN True 59 | ``` 60 | 61 | #### Arguments 62 | 63 | - `${1}`: config file path. 64 | - `${2}`: whether adding attributes. 65 | - `${3}`: whether masking clothing-relevant attribute. 66 | 67 | **or you can directly train with following yml and commands:** 68 | 69 | ```bash 70 | # prcc 71 | CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/prcc/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 72 | # ltcc 73 | CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/ltcc/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 74 | # Celeb_reID_light 75 | CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/Celeb_light/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 76 | # last 77 | CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 train.py --config_file configs/last/eva02_l_maskmeta_random.yml MODEL.DIST_TRAIN True 78 | ``` 79 | 80 | 81 | ## Evaluation 82 | 83 | ```bash 84 | CUDA_VISIBLE_DEVICES=1,0 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 test.py --config_file 'choose which config to test' TEST.WEIGHT "('your path of trained checkpoints')" 85 | ``` 86 | 87 | **example:** 88 | 89 | ```bash 90 | # LTCC 91 | CUDA_VISIBLE_DEVICES=1,0 python -m torch.distributed.launch --nproc_per_node=2 --master_port 6666 test.py --config_file configs/ltcc/eva02_l_maskmeta_random.yml TEST.WEIGHT '../logs/ltcc/eva02_l_meta_best.pth' 92 | ``` 93 | 94 | 95 | ## Acknowledgement 96 | 97 | Codebase from [TransReid](https://github.com/damo-cv/transreid) , [CAL](https://github.com/guxinqian/Simple-CCReID) 98 | 99 | ## Citation 100 | 101 | If you find this code useful for your research, please cite our paper: 102 | 103 | ``` 104 | @misc{peng2024masked, 105 | title={Masked Attribute Description Embedding for Cloth-Changing Person Re-identification}, 106 | author={Chunlei Peng and Boyu Wang and Decheng Liu and Nannan Wang and Ruimin Hu and Xinbo Gao}, 107 | year={2024}, 108 | eprint={2401.05646}, 109 | archivePrefix={arXiv}, 110 | primaryClass={cs.CV} 111 | } 112 | ``` 113 | 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # temporary files which can be created if a process still has a handle open of a deleted file 2 | .fuse_hidden* 3 | 4 | # KDE directory preferences 5 | .directory 6 | 7 | # Linux trash folder which might appear on any partition or disk 8 | .Trash-* 9 | 10 | # .nfs files are created when an open file is removed but is still being accessed 11 | .nfs* 12 | 13 | 14 | *.DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | 18 | # Icon must end with two \r 19 | Icon 20 | 21 | 22 | # Thumbnails 23 | ._* 24 | 25 | # Files that might appear in the root of a volume 26 | .DocumentRevisions-V100 27 | .fseventsd 28 | .Spotlight-V100 29 | .TemporaryItems 30 | .Trashes 31 | .VolumeIcon.icns 32 | .com.apple.timemachine.donotpresent 33 | 34 | # Directories potentially created on remote AFP share 35 | .AppleDB 36 | .AppleDesktop 37 | Network Trash Folder 38 | Temporary Items 39 | .apdisk 40 | 41 | 42 | # swap 43 | [._]*.s[a-v][a-z] 44 | [._]*.sw[a-p] 45 | [._]s[a-v][a-z] 46 | [._]sw[a-p] 47 | # session 48 | Session.vim 49 | # temporary 50 | .netrwhist 51 | *~ 52 | # auto-generated tag files 53 | tags 54 | 55 | 56 | # cache files for sublime text 57 | *.tmlanguage.cache 58 | *.tmPreferences.cache 59 | *.stTheme.cache 60 | 61 | # workspace files are user-specific 62 | *.sublime-workspace 63 | 64 | # project files should be checked into the repository, unless a significant 65 | # proportion of contributors will probably not be using SublimeText 66 | # *.sublime-project 67 | 68 | # sftp configuration file 69 | sftp-config.json 70 | 71 | # Package control specific files 72 | Package Control.last-run 73 | Package Control.ca-list 74 | Package Control.ca-bundle 75 | Package Control.system-ca-bundle 76 | Package Control.cache/ 77 | Package Control.ca-certs/ 78 | Package Control.merged-ca-bundle 79 | Package Control.user-ca-bundle 80 | oscrypto-ca-bundle.crt 81 | bh_unicode_properties.cache 82 | 83 | # Sublime-github package stores a github token in this file 84 | # https://packagecontrol.io/packages/sublime-github 85 | GitHub.sublime-settings 86 | 87 | 88 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 89 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 90 | 91 | # User-specific stuff: 92 | .idea 93 | .idea/**/workspace.xml 94 | .idea/**/tasks.xml 95 | 96 | # Sensitive or high-churn files: 97 | .idea/**/dataSources/ 98 | .idea/**/dataSources.ids 99 | .idea/**/dataSources.xml 100 | .idea/**/dataSources.local.xml 101 | .idea/**/sqlDataSources.xml 102 | .idea/**/dynamic.xml 103 | .idea/**/uiDesigner.xml 104 | 105 | # Gradle: 106 | .idea/**/gradle.xml 107 | .idea/**/libraries 108 | 109 | # Mongo Explorer plugin: 110 | .idea/**/mongoSettings.xml 111 | 112 | ## File-based project format: 113 | *.iws 114 | 115 | ## Plugin-specific files: 116 | 117 | # IntelliJ 118 | /out/ 119 | 120 | # mpeltonen/sbt-idea plugin 121 | .idea_modules/ 122 | 123 | # JIRA plugin 124 | atlassian-ide-plugin.xml 125 | 126 | # Crashlytics plugin (for Android Studio and IntelliJ) 127 | com_crashlytics_export_strings.xml 128 | crashlytics.properties 129 | crashlytics-build.properties 130 | fabric.properties 131 | 132 | 133 | # Byte-compiled / optimized / DLL files 134 | __pycache__/ 135 | *.py[cod] 136 | *$py.class 137 | 138 | # C extensions 139 | *.so 140 | 141 | # Distribution / packaging 142 | .Python 143 | env/ 144 | build/ 145 | develop-eggs/ 146 | dist/ 147 | downloads/ 148 | eggs/ 149 | .eggs/ 150 | lib/ 151 | lib64/ 152 | parts/ 153 | sdist/ 154 | var/ 155 | *.egg-info/ 156 | .installed.cfg 157 | *.egg 158 | 159 | # PyInstaller 160 | # Usually these files are written by a python script from a template 161 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 162 | *.manifest 163 | *.spec 164 | 165 | # Installer logs 166 | pip-log.txt 167 | pip-delete-this-directory.txt 168 | 169 | # Unit test / coverage reports 170 | htmlcov/ 171 | .tox/ 172 | .coverage 173 | .coverage.* 174 | .cache 175 | nosetests.xml 176 | coverage.xml 177 | *,cover 178 | .hypothesis/ 179 | 180 | # Translations 181 | *.mo 182 | *.pot 183 | 184 | # Django stuff: 185 | *.log 186 | local_settings.py 187 | 188 | # Flask stuff: 189 | instance/ 190 | .webassets-cache 191 | 192 | # Scrapy stuff: 193 | .scrapy 194 | 195 | # Sphinx documentation 196 | docs/_build/ 197 | 198 | # PyBuilder 199 | target/ 200 | 201 | # IPython Notebook 202 | .ipynb_checkpoints 203 | 204 | # pyenv 205 | .python-version 206 | 207 | # celery beat schedule file 208 | celerybeat-schedule 209 | 210 | # dotenv 211 | .env 212 | 213 | # virtualenv 214 | venv/ 215 | ENV/ 216 | 217 | # Spyder project settings 218 | .spyderproject 219 | 220 | # Rope project settings 221 | .ropeproject 222 | 223 | -------------------------------------------------------------------------------- /utils/reranking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Fri, 25 May 2018 20:29:09 5 | 6 | 7 | """ 8 | 9 | """ 10 | CVPR2017 paper:Zhong Z, Zheng L, Cao D, et al. Re-ranking Person Re-identification with k-reciprocal Encoding[J]. 2017. 11 | url:http://openaccess.thecvf.com/content_cvpr_2017/papers/Zhong_Re-Ranking_Person_Re-Identification_CVPR_2017_paper.pdf 12 | Matlab version: https://github.com/zhunzhong07/person-re-ranking 13 | """ 14 | 15 | """ 16 | API 17 | 18 | probFea: all feature vectors of the query set (torch tensor) 19 | probFea: all feature vectors of the gallery set (torch tensor) 20 | k1,k2,lambda: parameters, the original paper is (k1=20,k2=6,lambda=0.3) 21 | MemorySave: set to 'True' when using MemorySave mode 22 | Minibatch: avaliable when 'MemorySave' is 'True' 23 | """ 24 | 25 | import numpy as np 26 | import torch 27 | 28 | 29 | def re_ranking(probFea, galFea, k1, k2, lambda_value, local_distmat=None, only_local=False): 30 | # if feature vector is numpy, you should use 'torch.tensor' transform it to tensor 31 | query_num = probFea.size(0) 32 | all_num = query_num + galFea.size(0) 33 | if only_local: 34 | original_dist = local_distmat 35 | else: 36 | feat = torch.cat([probFea, galFea]) 37 | # print('using GPU to compute original distance') 38 | distmat = torch.pow(feat, 2).sum(dim=1, keepdim=True).expand(all_num, all_num) + \ 39 | torch.pow(feat, 2).sum(dim=1, keepdim=True).expand(all_num, all_num).t() 40 | distmat.addmm_(1, -2, feat, feat.t()) 41 | original_dist = distmat.cpu().numpy() 42 | del feat 43 | if not local_distmat is None: 44 | original_dist = original_dist + local_distmat 45 | gallery_num = original_dist.shape[0] 46 | original_dist = np.transpose(original_dist / np.max(original_dist, axis=0)) 47 | V = np.zeros_like(original_dist).astype(np.float16) 48 | initial_rank = np.argsort(original_dist).astype(np.int32) 49 | 50 | # print('starting re_ranking') 51 | for i in range(all_num): 52 | # k-reciprocal neighbors 53 | forward_k_neigh_index = initial_rank[i, :k1 + 1] 54 | backward_k_neigh_index = initial_rank[forward_k_neigh_index, :k1 + 1] 55 | fi = np.where(backward_k_neigh_index == i)[0] 56 | k_reciprocal_index = forward_k_neigh_index[fi] 57 | k_reciprocal_expansion_index = k_reciprocal_index 58 | for j in range(len(k_reciprocal_index)): 59 | candidate = k_reciprocal_index[j] 60 | candidate_forward_k_neigh_index = initial_rank[candidate, :int(np.around(k1 / 2)) + 1] 61 | candidate_backward_k_neigh_index = initial_rank[candidate_forward_k_neigh_index, 62 | :int(np.around(k1 / 2)) + 1] 63 | fi_candidate = np.where(candidate_backward_k_neigh_index == candidate)[0] 64 | candidate_k_reciprocal_index = candidate_forward_k_neigh_index[fi_candidate] 65 | if len(np.intersect1d(candidate_k_reciprocal_index, k_reciprocal_index)) > 2 / 3 * len( 66 | candidate_k_reciprocal_index): 67 | k_reciprocal_expansion_index = np.append(k_reciprocal_expansion_index, candidate_k_reciprocal_index) 68 | 69 | k_reciprocal_expansion_index = np.unique(k_reciprocal_expansion_index) 70 | weight = np.exp(-original_dist[i, k_reciprocal_expansion_index]) 71 | V[i, k_reciprocal_expansion_index] = weight / np.sum(weight) 72 | original_dist = original_dist[:query_num, ] 73 | if k2 != 1: 74 | V_qe = np.zeros_like(V, dtype=np.float16) 75 | for i in range(all_num): 76 | V_qe[i, :] = np.mean(V[initial_rank[i, :k2], :], axis=0) 77 | V = V_qe 78 | del V_qe 79 | del initial_rank 80 | invIndex = [] 81 | for i in range(gallery_num): 82 | invIndex.append(np.where(V[:, i] != 0)[0]) 83 | 84 | jaccard_dist = np.zeros_like(original_dist, dtype=np.float16) 85 | 86 | for i in range(query_num): 87 | temp_min = np.zeros(shape=[1, gallery_num], dtype=np.float16) 88 | indNonZero = np.where(V[i, :] != 0)[0] 89 | indImages = [invIndex[ind] for ind in indNonZero] 90 | for j in range(len(indNonZero)): 91 | temp_min[0, indImages[j]] = temp_min[0, indImages[j]] + np.minimum(V[i, indNonZero[j]], 92 | V[indImages[j], indNonZero[j]]) 93 | jaccard_dist[i] = 1 - temp_min / (2 - temp_min) 94 | 95 | final_dist = jaccard_dist * (1 - lambda_value) + original_dist * lambda_value 96 | del original_dist 97 | del V 98 | del jaccard_dist 99 | final_dist = final_dist[:query_num, query_num:] 100 | return final_dist 101 | 102 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==1.4.0 2 | aliyun-python-sdk-core==2.14.0 3 | aliyun-python-sdk-kms==2.16.2 4 | altair==5.0.1 5 | antlr4-python3-runtime==4.9.3 6 | appdirs==1.4.4 7 | attrs==23.1.0 8 | backcall==0.2.0 9 | backports.zoneinfo==0.2.1 10 | bleach==6.0.0 11 | blinker==1.6.3 12 | blis==0.7.11 13 | braceexpand==0.1.7 14 | brotlipy==0.7.0 15 | cachetools==5.3.1 16 | catalogue==2.0.10 17 | certifi==2023.5.7 18 | cffi @ file:///croot/cffi_1670423208954/work 19 | cfgv==3.3.1 20 | chardet==5.1.0 21 | charset-normalizer==3.2.0 22 | click==8.1.7 23 | cloudpathlib==0.16.0 24 | colorlog==6.7.0 25 | confection==0.1.3 26 | contexttimer==0.3.3 27 | crcmod==1.7 28 | cryptography @ file:///home/conda/feedstock_root/build_artifacts/cryptography_1666563371538/work 29 | cycler==0.11.0 30 | cymem==2.0.8 31 | decorator==5.1.1 32 | decord==0.6.0 33 | diffusers==0.16.0 34 | distlib==0.3.7 35 | docker-pycreds==0.4.0 36 | efficientnet-pytorch==0.7.1 37 | einops==0.4.1 38 | fairscale==0.4.4 39 | filelock==3.12.2 40 | fonttools==4.38.0 41 | fsspec==2023.1.0 42 | ftfy==6.1.1 43 | functorch==1.13.1 44 | gitdb==4.0.10 45 | GitPython==3.1.36 46 | google-auth==2.22.0 47 | google-auth-oauthlib==0.4.6 48 | grpcio==1.56.2 49 | h5py==3.8.0 50 | huggingface-hub==0.16.4 51 | identify==2.5.24 52 | idna @ file:///croot/idna_1666125576474/work 53 | imageio==2.31.2 54 | importlib-metadata==6.7.0 55 | importlib-resources==5.12.0 56 | iopath==0.1.10 57 | ipython==7.34.0 58 | jedi==0.19.1 59 | Jinja2==3.1.2 60 | jmespath==0.10.0 61 | joblib==1.3.1 62 | jsonschema==4.17.3 63 | kaggle==1.5.16 64 | kiwisolver==1.4.4 65 | langcodes==3.3.0 66 | Markdown==3.4.3 67 | markdown-it-py==2.2.0 68 | MarkupSafe==2.1.3 69 | matplotlib==3.5.3 70 | matplotlib-inline==0.1.6 71 | mdurl==0.1.2 72 | mkl-fft==1.3.1 73 | mkl-random @ file:///tmp/build/80754af9/mkl_random_1626179032232/work 74 | mkl-service==2.4.0 75 | more-itertools==9.1.0 76 | munch==4.0.0 77 | murmurhash==1.0.10 78 | networkx==2.6.3 79 | nltk==3.8.1 80 | nodeenv==1.8.0 81 | numpy @ file:///opt/conda/conda-bld/numpy_and_numpy_base_1653915516269/work 82 | nvidia-cublas-cu11==11.10.3.66 83 | nvidia-cuda-nvrtc-cu11==11.7.99 84 | nvidia-cuda-runtime-cu11==11.7.99 85 | nvidia-cudnn-cu11==8.5.0.96 86 | oauthlib==3.2.2 87 | omegaconf==2.3.0 88 | opencv-python-headless==4.5.5.64 89 | opendatasets==0.1.22 90 | oss2==2.18.3 91 | packaging==23.1 92 | pandas==1.3.5 93 | parso==0.8.3 94 | pathtools==0.1.2 95 | pexpect==4.8.0 96 | pickleshare==0.7.5 97 | Pillow==9.5.0 98 | pkgutil_resolve_name==1.3.10 99 | platformdirs==3.11.0 100 | plotly==5.18.0 101 | portalocker==2.7.0 102 | pre-commit==2.21.0 103 | preshed==3.0.9 104 | pretrainedmodels==0.7.4 105 | prompt-toolkit==3.0.39 106 | protobuf==3.20.3 107 | psutil==5.9.5 108 | ptyprocess==0.7.0 109 | pyarrow==12.0.1 110 | pyasn1==0.5.0 111 | pyasn1-modules==0.3.0 112 | pycocoevalcap==1.2 113 | pycocotools==2.0.7 114 | pycparser @ file:///tmp/build/80754af9/pycparser_1636541352034/work 115 | pycryptodome==3.19.0 116 | pydantic==1.10.13 117 | pydeck==0.8.1b0 118 | Pygments==2.16.1 119 | Pympler==1.0.1 120 | pyOpenSSL @ file:///croot/pyopenssl_1677607685877/work 121 | pyparsing==3.1.0 122 | pyrsistent==0.19.3 123 | PySocks @ file:///tmp/build/80754af9/pysocks_1594394576006/work 124 | python-dateutil==2.8.2 125 | python-magic==0.4.27 126 | python-slugify==8.0.1 127 | pytorch-metric-learning==2.2.0 128 | pytz==2023.3 129 | pytz-deprecation-shim==0.1.0.post0 130 | PyWavelets==1.3.0 131 | PyYAML==6.0.1 132 | regex==2023.10.3 133 | requests==2.31.0 134 | requests-oauthlib==1.3.1 135 | rich==13.6.0 136 | rsa==4.9 137 | ruamel.yaml==0.15.46 138 | ruamel.yaml.clib==0.2.8 139 | safetensors==0.4.0 140 | scikit-image==0.19.3 141 | scikit-learn==1.0.2 142 | scipy==1.7.3 143 | sentencepiece==0.1.99 144 | sentry-sdk==1.31.0 145 | setproctitle==1.3.2 146 | six @ file:///tmp/build/80754af9/six_1644875935023/work 147 | smart-open==6.4.0 148 | smmap==5.0.1 149 | spacy==3.7.2 150 | spacy-legacy==3.0.12 151 | spacy-loggers==1.0.5 152 | srsly==2.4.8 153 | streamlit==1.23.1 154 | tenacity==8.2.3 155 | tensorboard==2.11.2 156 | tensorboard-data-server==0.6.1 157 | tensorboard-plugin-wit==1.8.1 158 | termcolor==2.3.0 159 | terminaltables==3.1.10 160 | text-unidecode==1.3 161 | thinc==8.2.1 162 | threadpoolctl==3.1.0 163 | tifffile==2021.11.2 164 | timm==0.9.10 165 | tokenizers==0.13.3 166 | toml==0.10.2 167 | toolz==0.12.0 168 | torch==1.13.1 169 | torchaudio==0.11.0 170 | torchinfo==1.8.0 171 | torchvision==0.12.0 172 | tornado==6.2 173 | tqdm==4.65.0 174 | traitlets==5.9.0 175 | transformers==4.28.0 176 | typer==0.9.0 177 | typing_extensions==4.4.0 178 | tzdata==2023.3 179 | tzlocal==4.3.1 180 | urllib3==1.26.16 181 | validators==0.20.0 182 | virtualenv==20.24.6 183 | wasabi==1.1.2 184 | watchdog==3.0.0 185 | wcwidth==0.2.9 186 | weasel==0.3.3 187 | webdataset==0.2.73 188 | webencodings==0.5.1 189 | Werkzeug==2.2.3 190 | yacs==0.1.8 191 | zipp==3.15.0 192 | -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/preprocess/format_rap2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import pickle 5 | from scipy.io import loadmat 6 | from easydict import EasyDict 7 | 8 | np.random.seed(0) 9 | random.seed(0) 10 | 11 | group_order = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 12 | 36, 37, 38, 39, 40, 41, 42, 43, 44, 1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 45, 46, 47, 48, 49, 50, 51, 52, 53] 13 | 14 | 15 | def make_dir(path): 16 | if os.path.exists(path): 17 | pass 18 | else: 19 | os.mkdir(path) 20 | 21 | 22 | def generate_data_description(save_dir, reorder, new_split_path, version): 23 | data = loadmat(os.path.join(save_dir, 'RAP_annotation/RAP_annotation.mat')) 24 | data = data['RAP_annotation'] 25 | dataset = EasyDict() 26 | dataset.description = 'rap2' 27 | dataset.reorder = 'group_order' 28 | dataset.root = os.path.join(save_dir, 'RAP_dataset') 29 | dataset.image_name = [data['name'][0][0][i][0][0] for i in range(84928)] 30 | raw_attr_name = [data['attribute'][0][0][i][0][0] for i in range(152)] 31 | raw_label = data['data'][0][0] 32 | selected_attr_idx = (data['selected_attribute'][0][0][0] - 1)[group_order].tolist() # 54 33 | 34 | color_attr_idx = list(range(31, 45)) + list(range(53, 67)) + list(range(74, 88)) # 42 35 | extra_attr_idx = np.setdiff1d(range(152), color_attr_idx + selected_attr_idx).tolist()[:24] 36 | extra_attr_idx = extra_attr_idx[:15] + extra_attr_idx[16:] 37 | 38 | dataset.label = raw_label[:, selected_attr_idx + color_attr_idx + extra_attr_idx] # (n, 119) 39 | dataset.attr_name = [raw_attr_name[i] for i in selected_attr_idx + color_attr_idx + extra_attr_idx] 40 | 41 | dataset.label_idx = EasyDict() 42 | dataset.label_idx.eval = list(range(54)) # 54 43 | dataset.label_idx.color = list(range(54, 96)) # not aligned with color label index in label 44 | dataset.label_idx.extra = list(range(96, 119)) # not aligned with extra label index in label 45 | 46 | if reorder: 47 | dataset.label_idx.eval = list(range(54)) 48 | 49 | dataset.partition = EasyDict() 50 | dataset.partition.train = [] 51 | dataset.partition.val = [] 52 | dataset.partition.test = [] 53 | dataset.partition.trainval = [] 54 | 55 | dataset.weight_train = [] 56 | dataset.weight_trainval = [] 57 | 58 | if new_split_path: 59 | 60 | # remove Age46-60 61 | dataset.label_idx.eval.remove(38) # 54 62 | 63 | with open(new_split_path, 'rb+') as f: 64 | new_split = pickle.load(f) 65 | 66 | train = np.array(new_split.train_idx) 67 | val = np.array(new_split.val_idx) 68 | test = np.array(new_split.test_idx) 69 | trainval = np.concatenate((train, val), axis=0) 70 | 71 | print(np.concatenate([trainval, test]).shape) 72 | 73 | dataset.partition.train = train 74 | dataset.partition.val = val 75 | dataset.partition.trainval = trainval 76 | dataset.partition.test = test 77 | 78 | weight_train = np.mean(dataset.label[train], axis=0).astype(np.float32) 79 | weight_trainval = np.mean(dataset.label[trainval], axis=0).astype(np.float32) 80 | 81 | print(weight_trainval[38]) 82 | 83 | dataset.weight_train.append(weight_train) 84 | dataset.weight_trainval.append(weight_trainval) 85 | with open(os.path.join(save_dir, f'dataset_zs_run{version}.pkl'), 'wb+') as f: 86 | pickle.dump(dataset, f) 87 | 88 | else: 89 | for idx in range(5): 90 | train = data['partition_attribute'][0][0][0][idx]['train_index'][0][0][0] - 1 91 | val = data['partition_attribute'][0][0][0][idx]['val_index'][0][0][0] - 1 92 | test = data['partition_attribute'][0][0][0][idx]['test_index'][0][0][0] - 1 93 | trainval = np.concatenate([train, val]) 94 | dataset.partition.train.append(train) 95 | dataset.partition.val.append(val) 96 | dataset.partition.test.append(test) 97 | dataset.partition.trainval.append(trainval) 98 | # cls_weight 99 | weight_train = np.mean(dataset.label[train], axis=0) 100 | weight_trainval = np.mean(dataset.label[trainval], axis=0) 101 | dataset.weight_train.append(weight_train) 102 | dataset.weight_trainval.append(weight_trainval) 103 | with open(os.path.join(save_dir, 'dataset_all.pkl'), 'wb+') as f: 104 | pickle.dump(dataset, f) 105 | 106 | 107 | if __name__ == "__main__": 108 | save_dir = '/mnt/data1/jiajian/datasets/attribute/RAP2/' 109 | reorder = True 110 | 111 | for i in range(5): 112 | new_split_path = f'/mnt/data1/jiajian/code/Rethinking_of_PAR/datasets/jian_split/index_rap2_split_id50_img300_ratio0.03_{i}.pkl' 113 | generate_data_description(save_dir, reorder, new_split_path, i) 114 | -------------------------------------------------------------------------------- /SOLIDER/scheduler/scheduler.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | import torch 4 | 5 | 6 | class Scheduler: 7 | """ Parameter Scheduler Base Class 8 | A scheduler base class that can be used to schedule any optimizer parameter groups. 9 | Unlike the builtin PyTorch schedulers, this is intended to be consistently called 10 | * At the END of each epoch, before incrementing the epoch count, to calculate next epoch's value 11 | * At the END of each optimizer update, after incrementing the update count, to calculate next update's value 12 | The schedulers built on this should try to remain as stateless as possible (for simplicity). 13 | This family of schedulers is attempting to avoid the confusion of the meaning of 'last_epoch' 14 | and -1 values for special behaviour. All epoch and update counts must be tracked in the training 15 | code and explicitly passed in to the schedulers on the corresponding step or step_update call. 16 | Based on ideas from: 17 | * https://github.com/pytorch/fairseq/tree/master/fairseq/optim/lr_scheduler 18 | * https://github.com/allenai/allennlp/tree/master/allennlp/training/learning_rate_schedulers 19 | """ 20 | 21 | def __init__(self, 22 | optimizer: torch.optim.Optimizer, 23 | param_group_field: str, 24 | noise_range_t=None, 25 | noise_type='normal', 26 | noise_pct=0.67, 27 | noise_std=1.0, 28 | noise_seed=None, 29 | initialize: bool = True) -> None: 30 | self.optimizer = optimizer 31 | self.param_group_field = param_group_field 32 | self._initial_param_group_field = f"initial_{param_group_field}" 33 | if initialize: 34 | for i, group in enumerate(self.optimizer.param_groups): 35 | if param_group_field not in group: 36 | raise KeyError(f"{param_group_field} missing from param_groups[{i}]") 37 | group.setdefault(self._initial_param_group_field, group[param_group_field]) 38 | else: 39 | for i, group in enumerate(self.optimizer.param_groups): 40 | if self._initial_param_group_field not in group: 41 | raise KeyError(f"{self._initial_param_group_field} missing from param_groups[{i}]") 42 | self.base_values = [group[self._initial_param_group_field] for group in self.optimizer.param_groups] 43 | self.metric = None # any point to having this for all? 44 | self.noise_range_t = noise_range_t 45 | self.noise_pct = noise_pct 46 | self.noise_type = noise_type 47 | self.noise_std = noise_std 48 | self.noise_seed = noise_seed if noise_seed is not None else 42 49 | self.update_groups(self.base_values) 50 | 51 | def state_dict(self) -> Dict[str, Any]: 52 | return {key: value for key, value in self.__dict__.items() if key != 'optimizer'} 53 | 54 | def load_state_dict(self, state_dict: Dict[str, Any]) -> None: 55 | self.__dict__.update(state_dict) 56 | 57 | def get_epoch_values(self, epoch: int): 58 | return None 59 | 60 | def get_update_values(self, num_updates: int): 61 | return None 62 | 63 | def step(self, epoch: int, metric: float = None) -> None: 64 | self.metric = metric 65 | values = self.get_epoch_values(epoch) 66 | if values is not None: 67 | values = self._add_noise(values, epoch) 68 | self.update_groups(values) 69 | 70 | def step_update(self, num_updates: int, metric: float = None): 71 | self.metric = metric 72 | values = self.get_update_values(num_updates) 73 | if values is not None: 74 | values = self._add_noise(values, num_updates) 75 | self.update_groups(values) 76 | 77 | def update_groups(self, values): 78 | if not isinstance(values, (list, tuple)): 79 | values = [values] * len(self.optimizer.param_groups) 80 | for param_group, value in zip(self.optimizer.param_groups, values): 81 | param_group[self.param_group_field] = value 82 | 83 | def _add_noise(self, lrs, t): 84 | if self.noise_range_t is not None: 85 | if isinstance(self.noise_range_t, (list, tuple)): 86 | apply_noise = self.noise_range_t[0] <= t < self.noise_range_t[1] 87 | else: 88 | apply_noise = t >= self.noise_range_t 89 | if apply_noise: 90 | g = torch.Generator() 91 | g.manual_seed(self.noise_seed + t) 92 | if self.noise_type == 'normal': 93 | while True: 94 | # resample if noise out of percent limit, brute force but shouldn't spin much 95 | noise = torch.randn(1, generator=g).item() 96 | if abs(noise) < self.noise_pct: 97 | break 98 | else: 99 | noise = 2 * (torch.rand(1, generator=g).item() - 0.5) * self.noise_pct 100 | lrs = [v + v * noise for v in lrs] 101 | return lrs 102 | -------------------------------------------------------------------------------- /solver/scheduler.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | import torch 4 | 5 | 6 | class Scheduler: 7 | """ Parameter Scheduler Base Class 8 | A scheduler base class that can be used to schedule any optimizer parameter groups. 9 | 10 | Unlike the builtin PyTorch schedulers, this is intended to be consistently called 11 | * At the END of each epoch, before incrementing the epoch count, to calculate next epoch's value 12 | * At the END of each optimizer update, after incrementing the update count, to calculate next update's value 13 | 14 | The schedulers built on this should try to remain as stateless as possible (for simplicity). 15 | 16 | This family of schedulers is attempting to avoid the confusion of the meaning of 'last_epoch' 17 | and -1 values for special behaviour. All epoch and update counts must be tracked in the training 18 | code and explicitly passed in to the schedulers on the corresponding step or step_update call. 19 | 20 | Based on ideas from: 21 | * https://github.com/pytorch/fairseq/tree/master/fairseq/optim/lr_scheduler 22 | * https://github.com/allenai/allennlp/tree/master/allennlp/training/learning_rate_schedulers 23 | """ 24 | 25 | def __init__(self, 26 | optimizer: torch.optim.Optimizer, 27 | param_group_field: str, 28 | noise_range_t=None, 29 | noise_type='normal', 30 | noise_pct=0.67, 31 | noise_std=1.0, 32 | noise_seed=None, 33 | initialize: bool = True) -> None: 34 | self.optimizer = optimizer 35 | self.param_group_field = param_group_field 36 | self._initial_param_group_field = f"initial_{param_group_field}" 37 | if initialize: 38 | for i, group in enumerate(self.optimizer.param_groups): 39 | if param_group_field not in group: 40 | raise KeyError(f"{param_group_field} missing from param_groups[{i}]") 41 | group.setdefault(self._initial_param_group_field, group[param_group_field]) 42 | else: 43 | for i, group in enumerate(self.optimizer.param_groups): 44 | if self._initial_param_group_field not in group: 45 | raise KeyError(f"{self._initial_param_group_field} missing from param_groups[{i}]") 46 | self.base_values = [group[self._initial_param_group_field] for group in self.optimizer.param_groups] 47 | self.metric = None # any point to having this for all? 48 | self.noise_range_t = noise_range_t 49 | self.noise_pct = noise_pct 50 | self.noise_type = noise_type 51 | self.noise_std = noise_std 52 | self.noise_seed = noise_seed if noise_seed is not None else 42 53 | self.update_groups(self.base_values) 54 | 55 | def state_dict(self) -> Dict[str, Any]: 56 | return {key: value for key, value in self.__dict__.items() if key != 'optimizer'} 57 | 58 | def load_state_dict(self, state_dict: Dict[str, Any]) -> None: 59 | self.__dict__.update(state_dict) 60 | 61 | def get_epoch_values(self, epoch: int): 62 | return None 63 | 64 | def get_update_values(self, num_updates: int): 65 | return None 66 | 67 | def step(self, epoch: int, metric: float = None) -> None: 68 | self.metric = metric 69 | values = self.get_epoch_values(epoch) 70 | if values is not None: 71 | values = self._add_noise(values, epoch) 72 | self.update_groups(values) 73 | 74 | def step_update(self, num_updates: int, metric: float = None): 75 | self.metric = metric 76 | values = self.get_update_values(num_updates) 77 | if values is not None: 78 | values = self._add_noise(values, num_updates) 79 | self.update_groups(values) 80 | 81 | def update_groups(self, values): 82 | if not isinstance(values, (list, tuple)): 83 | values = [values] * len(self.optimizer.param_groups) 84 | for param_group, value in zip(self.optimizer.param_groups, values): 85 | param_group[self.param_group_field] = value 86 | 87 | def _add_noise(self, lrs, t): 88 | if self.noise_range_t is not None: 89 | if isinstance(self.noise_range_t, (list, tuple)): 90 | apply_noise = self.noise_range_t[0] <= t < self.noise_range_t[1] 91 | else: 92 | apply_noise = t >= self.noise_range_t 93 | if apply_noise: 94 | g = torch.Generator() 95 | g.manual_seed(self.noise_seed + t) 96 | if self.noise_type == 'normal': 97 | while True: 98 | # resample if noise out of percent limit, brute force but shouldn't spin much 99 | noise = torch.randn(1, generator=g).item() 100 | if abs(noise) < self.noise_pct: 101 | break 102 | else: 103 | noise = 2 * (torch.rand(1, generator=g).item() - 0.5) * self.noise_pct 104 | lrs = [v + v * noise for v in lrs] 105 | return lrs 106 | -------------------------------------------------------------------------------- /loss/triplet_loss.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch import nn 3 | 4 | 5 | def normalize(x, axis=-1): 6 | """Normalizing to unit length along the specified dimension. 7 | Args: 8 | x: pytorch Variable 9 | Returns: 10 | x: pytorch Variable, same shape as input 11 | """ 12 | x = 1. * x / (torch.norm(x, 2, axis, keepdim=True).expand_as(x) + 1e-12) 13 | return x 14 | 15 | 16 | def euclidean_dist(x, y): 17 | """ 18 | Args: 19 | x: pytorch Variable, with shape [m, d] 20 | y: pytorch Variable, with shape [n, d] 21 | Returns: 22 | dist: pytorch Variable, with shape [m, n] 23 | """ 24 | m, n = x.size(0), y.size(0) 25 | xx = torch.pow(x, 2).sum(1, keepdim=True).expand(m, n) 26 | yy = torch.pow(y, 2).sum(1, keepdim=True).expand(n, m).t() 27 | dist = xx + yy 28 | dist = dist - 2 * torch.matmul(x, y.t()) 29 | # dist.addmm_(1, -2, x, y.t()) 30 | dist = dist.clamp(min=1e-12).sqrt() # for numerical stability 31 | return dist 32 | 33 | 34 | def cosine_dist(x, y): 35 | """ 36 | Args: 37 | x: pytorch Variable, with shape [m, d] 38 | y: pytorch Variable, with shape [n, d] 39 | Returns: 40 | dist: pytorch Variable, with shape [m, n] 41 | """ 42 | m, n = x.size(0), y.size(0) 43 | x_norm = torch.pow(x, 2).sum(1, keepdim=True).sqrt().expand(m, n) 44 | y_norm = torch.pow(y, 2).sum(1, keepdim=True).sqrt().expand(n, m).t() 45 | xy_intersection = torch.mm(x, y.t()) 46 | dist = xy_intersection/(x_norm * y_norm) 47 | dist = (1. - dist) / 2 48 | return dist 49 | 50 | 51 | def hard_example_mining(dist_mat, labels, return_inds=False): 52 | """For each anchor, find the hardest positive and negative sample. 53 | Args: 54 | dist_mat: pytorch Variable, pair wise distance between samples, shape [N, N] 55 | labels: pytorch LongTensor, with shape [N] 56 | return_inds: whether to return the indices. Save time if `False`(?) 57 | Returns: 58 | dist_ap: pytorch Variable, distance(anchor, positive); shape [N] 59 | dist_an: pytorch Variable, distance(anchor, negative); shape [N] 60 | p_inds: pytorch LongTensor, with shape [N]; 61 | indices of selected hard positive samples; 0 <= p_inds[i] <= N - 1 62 | n_inds: pytorch LongTensor, with shape [N]; 63 | indices of selected hard negative samples; 0 <= n_inds[i] <= N - 1 64 | NOTE: Only consider the case in which all labels have same num of samples, 65 | thus we can cope with all anchors in parallel. 66 | """ 67 | 68 | assert len(dist_mat.size()) == 2 69 | assert dist_mat.size(0) == dist_mat.size(1) 70 | N = dist_mat.size(0) 71 | 72 | # shape [N, N] 73 | is_pos = labels.expand(N, N).eq(labels.expand(N, N).t()) 74 | is_neg = labels.expand(N, N).ne(labels.expand(N, N).t()) 75 | 76 | # `dist_ap` means distance(anchor, positive) 77 | # both `dist_ap` and `relative_p_inds` with shape [N, 1] 78 | dist_ap, relative_p_inds = torch.max( 79 | dist_mat[is_pos].contiguous().view(N, -1), 1, keepdim=True) 80 | # print(dist_mat[is_pos].shape) 81 | # `dist_an` means distance(anchor, negative) 82 | # both `dist_an` and `relative_n_inds` with shape [N, 1] 83 | dist_an, relative_n_inds = torch.min( 84 | dist_mat[is_neg].contiguous().view(N, -1), 1, keepdim=True) 85 | # shape [N] 86 | dist_ap = dist_ap.squeeze(1) 87 | dist_an = dist_an.squeeze(1) 88 | 89 | if return_inds: 90 | # shape [N, N] 91 | ind = (labels.new().resize_as_(labels) 92 | .copy_(torch.arange(0, N).long()) 93 | .unsqueeze(0).expand(N, N)) 94 | # shape [N, 1] 95 | p_inds = torch.gather( 96 | ind[is_pos].contiguous().view(N, -1), 1, relative_p_inds.data) 97 | n_inds = torch.gather( 98 | ind[is_neg].contiguous().view(N, -1), 1, relative_n_inds.data) 99 | # shape [N] 100 | p_inds = p_inds.squeeze(1) 101 | n_inds = n_inds.squeeze(1) 102 | return dist_ap, dist_an, p_inds, n_inds 103 | 104 | return dist_ap, dist_an 105 | 106 | 107 | class TripletLoss(object): 108 | """ 109 | Triplet loss using HARDER example mining, 110 | modified based on original triplet loss using hard example mining 111 | """ 112 | 113 | def __init__(self, margin=None, hard_factor=0.0): 114 | self.margin = margin 115 | self.hard_factor = hard_factor 116 | if margin is not None: 117 | self.ranking_loss = nn.MarginRankingLoss(margin=margin) 118 | else: 119 | self.ranking_loss = nn.SoftMarginLoss() 120 | 121 | def __call__(self, global_feat, labels, normalize_feature=False): 122 | if normalize_feature: 123 | global_feat = normalize(global_feat, axis=-1) 124 | dist_mat = euclidean_dist(global_feat, global_feat) 125 | dist_ap, dist_an = hard_example_mining(dist_mat, labels) 126 | 127 | dist_ap *= (1.0 + self.hard_factor) 128 | dist_an *= (1.0 - self.hard_factor) 129 | 130 | y = dist_an.new().resize_as_(dist_an).fill_(1) 131 | if self.margin is not None: 132 | loss = self.ranking_loss(dist_an, dist_ap, y) 133 | else: 134 | loss = self.ranking_loss(dist_an - dist_ap, y) 135 | return loss, dist_ap, dist_an 136 | 137 | 138 | -------------------------------------------------------------------------------- /SOLIDER/dataset/augmentation.py: -------------------------------------------------------------------------------- 1 | import random 2 | import torch 3 | import numpy as np 4 | import torchvision.transforms as T 5 | from PIL import Image 6 | 7 | from dataset.autoaug import AutoAugment 8 | 9 | 10 | class MultiScaleCrop(object): 11 | 12 | def __init__(self, input_size, scales=None, max_distort=1, fix_crop=True, more_fix_crop=True): 13 | self.scales = scales if scales is not None else [1, 875, .75, .66] 14 | self.max_distort = max_distort 15 | self.fix_crop = fix_crop 16 | self.more_fix_crop = more_fix_crop 17 | self.input_size = input_size if not isinstance(input_size, int) else [input_size, input_size] 18 | self.interpolation = Image.BILINEAR 19 | 20 | def __call__(self, img): 21 | im_size = img.size 22 | crop_w, crop_h, offset_w, offset_h = self._sample_crop_size(im_size) 23 | crop_img_group = img.crop((offset_w, offset_h, offset_w + crop_w, offset_h + crop_h)) 24 | ret_img_group = crop_img_group.resize((self.input_size[0], self.input_size[1]), self.interpolation) 25 | return ret_img_group 26 | 27 | def _sample_crop_size(self, im_size): 28 | image_w, image_h = im_size[0], im_size[1] 29 | 30 | # find a crop size 31 | base_size = min(image_w, image_h) 32 | crop_sizes = [int(base_size * x) for x in self.scales] 33 | crop_h = [self.input_size[1] if abs(x - self.input_size[1]) < 3 else x for x in crop_sizes] 34 | crop_w = [self.input_size[0] if abs(x - self.input_size[0]) < 3 else x for x in crop_sizes] 35 | 36 | pairs = [] 37 | for i, h in enumerate(crop_h): 38 | for j, w in enumerate(crop_w): 39 | if abs(i - j) <= self.max_distort: 40 | pairs.append((w, h)) 41 | 42 | crop_pair = random.choice(pairs) 43 | if not self.fix_crop: 44 | w_offset = random.randint(0, image_w - crop_pair[0]) 45 | h_offset = random.randint(0, image_h - crop_pair[1]) 46 | else: 47 | w_offset, h_offset = self._sample_fix_offset(image_w, image_h, crop_pair[0], crop_pair[1]) 48 | 49 | return crop_pair[0], crop_pair[1], w_offset, h_offset 50 | 51 | def _sample_fix_offset(self, image_w, image_h, crop_w, crop_h): 52 | offsets = self.fill_fix_offset(self.more_fix_crop, image_w, image_h, crop_w, crop_h) 53 | return random.choice(offsets) 54 | 55 | @staticmethod 56 | def fill_fix_offset(more_fix_crop, image_w, image_h, crop_w, crop_h): 57 | w_step = (image_w - crop_w) // 4 58 | h_step = (image_h - crop_h) // 4 59 | 60 | ret = list() 61 | ret.append((0, 0)) # upper left 62 | ret.append((4 * w_step, 0)) # upper right 63 | ret.append((0, 4 * h_step)) # lower left 64 | ret.append((4 * w_step, 4 * h_step)) # lower right 65 | ret.append((2 * w_step, 2 * h_step)) # center 66 | 67 | if more_fix_crop: 68 | ret.append((0, 2 * h_step)) # center left 69 | ret.append((4 * w_step, 2 * h_step)) # center right 70 | ret.append((2 * w_step, 4 * h_step)) # lower center 71 | ret.append((2 * w_step, 0 * h_step)) # upper center 72 | 73 | ret.append((1 * w_step, 1 * h_step)) # upper left quarter 74 | ret.append((3 * w_step, 1 * h_step)) # upper right quarter 75 | ret.append((1 * w_step, 3 * h_step)) # lower left quarter 76 | ret.append((3 * w_step, 3 * h_step)) # lower righ quarter 77 | 78 | return ret 79 | 80 | def __str__(self): 81 | return self.__class__.__name__ 82 | 83 | 84 | 85 | def get_transform(cfg): 86 | height = cfg.DATASET.HEIGHT 87 | width = cfg.DATASET.WIDTH 88 | normalize = T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 89 | 90 | if cfg.DATASET.TYPE == 'pedes': 91 | 92 | train_transform = T.Compose([ 93 | T.Resize((height, width)), 94 | T.Pad(10), 95 | T.RandomCrop((height, width)), 96 | T.RandomHorizontalFlip(), 97 | T.ToTensor(), 98 | normalize, 99 | ]) 100 | 101 | valid_transform = T.Compose([ 102 | T.Resize((height, width)), 103 | T.ToTensor(), 104 | normalize 105 | ]) 106 | 107 | elif cfg.DATASET.TYPE == 'multi_label': 108 | 109 | valid_transform = T.Compose([ 110 | T.Resize([height, width]), 111 | T.ToTensor(), 112 | normalize, 113 | ]) 114 | 115 | if cfg.TRAIN.DATAAUG.TYPE == 'autoaug': 116 | train_transform = T.Compose([ 117 | T.RandomApply([AutoAugment()], p=cfg.TRAIN.DATAAUG.AUTOAUG_PROB), 118 | T.Resize((height, width), interpolation=3), 119 | T.RandomHorizontalFlip(), 120 | T.ToTensor(), 121 | ]) 122 | 123 | else: 124 | train_transform = T.Compose([ 125 | T.Resize((height + 64, width + 64)), 126 | MultiScaleCrop(height, scales=(1.0, 0.875, 0.75, 0.66, 0.5), max_distort=2), 127 | T.RandomHorizontalFlip(), 128 | T.ToTensor(), 129 | normalize 130 | ]) 131 | else: 132 | 133 | assert False, 'xxxxxx' 134 | 135 | return train_transform, valid_transform 136 | -------------------------------------------------------------------------------- /tools/eval_metrics.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import numpy as np 3 | 4 | 5 | def compute_ap_cmc(index, good_index, junk_index): 6 | """ Compute AP and CMC for each sample 7 | """ 8 | ap = 0 9 | cmc = np.zeros(len(index)) 10 | 11 | # remove junk_index 12 | mask = np.in1d(index, junk_index, invert=True) 13 | index = index[mask] 14 | 15 | # find good_index index 16 | ngood = len(good_index) 17 | mask = np.in1d(index, good_index) 18 | rows_good = np.argwhere(mask==True) 19 | rows_good = rows_good.flatten() 20 | 21 | cmc[rows_good[0]:] = 1.0 22 | for i in range(ngood): 23 | d_recall = 1.0/ngood 24 | precision = (i+1)*1.0/(rows_good[i]+1) 25 | ap = ap + d_recall*precision 26 | 27 | return ap, cmc 28 | 29 | 30 | def evaluate(distmat, q_pids, g_pids, q_camids, g_camids): 31 | """ Compute CMC and mAP 32 | 33 | Args: 34 | distmat (numpy ndarray): distance matrix with shape (num_query, num_gallery). 35 | q_pids (numpy array): person IDs for query samples. 36 | g_pids (numpy array): person IDs for gallery samples. 37 | q_camids (numpy array): camera IDs for query samples. 38 | g_camids (numpy array): camera IDs for gallery samples. 39 | """ 40 | num_q, num_g = distmat.shape 41 | index = np.argsort(distmat, axis=1) # from small to large 42 | 43 | num_no_gt = 0 # num of query imgs without groundtruth 44 | num_r1 = 0 45 | CMC = np.zeros(len(g_pids)) 46 | AP = 0 47 | 48 | for i in range(num_q): 49 | # groundtruth index 50 | query_index = np.argwhere(g_pids==q_pids[i]) 51 | camera_index = np.argwhere(g_camids==q_camids[i]) 52 | good_index = np.setdiff1d(query_index, camera_index, assume_unique=True) 53 | if good_index.size == 0: 54 | num_no_gt += 1 55 | continue 56 | # remove gallery samples that have the same pid and camid with query 57 | junk_index = np.intersect1d(query_index, camera_index) 58 | 59 | ap_tmp, CMC_tmp = compute_ap_cmc(index[i], good_index, junk_index) 60 | if CMC_tmp[0]==1: 61 | num_r1 += 1 62 | CMC = CMC + CMC_tmp 63 | AP += ap_tmp 64 | 65 | if num_no_gt > 0: 66 | logger = logging.getLogger('reid.evaluate') 67 | logger.info("{} query samples do not have groundtruth.".format(num_no_gt)) 68 | 69 | CMC = CMC / (num_q - num_no_gt) 70 | mAP = AP / (num_q - num_no_gt) 71 | 72 | return CMC, mAP 73 | 74 | 75 | def evaluate_with_clothes(distmat, q_pids, g_pids, q_camids, g_camids, q_clothids, g_clothids, mode='CC'): 76 | """ Compute CMC and mAP with clothes 77 | 78 | Args: 79 | distmat (numpy ndarray): distance matrix with shape (num_query, num_gallery). 80 | q_pids (numpy array): person IDs for query samples. 81 | g_pids (numpy array): person IDs for gallery samples. 82 | q_camids (numpy array): camera IDs for query samples. 83 | g_camids (numpy array): camera IDs for gallery samples. 84 | q_clothids (numpy array): clothes IDs for query samples. 85 | g_clothids (numpy array): clothes IDs for gallery samples. 86 | mode: 'CC' for clothes-changing; 'SC' for the same clothes. 87 | """ 88 | assert mode in ['CC', 'SC'] 89 | 90 | num_q, num_g = distmat.shape 91 | index = np.argsort(distmat, axis=1) # from small to large 92 | 93 | num_no_gt = 0 # num of query imgs without groundtruth 94 | num_r1 = 0 95 | CMC = np.zeros(len(g_pids)) 96 | AP = 0 97 | 98 | for i in range(num_q): 99 | # groundtruth index 100 | query_index = np.argwhere(g_pids==q_pids[i]) 101 | camera_index = np.argwhere(g_camids==q_camids[i]) 102 | cloth_index = np.argwhere(g_clothids==q_clothids[i]) 103 | good_index = np.setdiff1d(query_index, camera_index, assume_unique=True) 104 | if mode == 'CC': 105 | good_index = np.setdiff1d(good_index, cloth_index, assume_unique=True) 106 | # remove gallery samples that have the same (pid, camid) or (pid, clothid) with query 107 | junk_index1 = np.intersect1d(query_index, camera_index) 108 | junk_index2 = np.intersect1d(query_index, cloth_index) 109 | junk_index = np.union1d(junk_index1, junk_index2) 110 | else: 111 | good_index = np.intersect1d(good_index, cloth_index) 112 | # remove gallery samples that have the same (pid, camid) or 113 | # (the same pid and different clothid) with query 114 | junk_index1 = np.intersect1d(query_index, camera_index) 115 | junk_index2 = np.setdiff1d(query_index, cloth_index) 116 | junk_index = np.union1d(junk_index1, junk_index2) 117 | 118 | if good_index.size == 0: 119 | num_no_gt += 1 120 | continue 121 | 122 | ap_tmp, CMC_tmp = compute_ap_cmc(index[i], good_index, junk_index) 123 | if CMC_tmp[0]==1: 124 | num_r1 += 1 125 | CMC = CMC + CMC_tmp 126 | AP += ap_tmp 127 | 128 | if num_no_gt > 0: 129 | logger = logging.getLogger('reid.evaluate') 130 | logger.info("{} query samples do not have groundtruth.".format(num_no_gt)) 131 | 132 | if (num_q - num_no_gt) != 0: 133 | CMC = CMC / (num_q - num_no_gt) 134 | mAP = AP / (num_q - num_no_gt) 135 | else: 136 | mAP = 0 137 | 138 | return CMC, mAP -------------------------------------------------------------------------------- /SOLIDER/optim/adamw.py: -------------------------------------------------------------------------------- 1 | """ AdamW Optimizer 2 | Impl copied from PyTorch master 3 | NOTE: Builtin optim.AdamW is used by the factory, this impl only serves as a Python based reference, will be removed 4 | someday 5 | """ 6 | import math 7 | import torch 8 | from torch.optim.optimizer import Optimizer 9 | 10 | 11 | class AdamW(Optimizer): 12 | r"""Implements AdamW algorithm. 13 | The original Adam algorithm was proposed in `Adam: A Method for Stochastic Optimization`_. 14 | The AdamW variant was proposed in `Decoupled Weight Decay Regularization`_. 15 | Arguments: 16 | params (iterable): iterable of parameters to optimize or dicts defining 17 | parameter groups 18 | lr (float, optional): learning rate (default: 1e-3) 19 | betas (Tuple[float, float], optional): coefficients used for computing 20 | running averages of gradient and its square (default: (0.9, 0.999)) 21 | eps (float, optional): term added to the denominator to improve 22 | numerical stability (default: 1e-8) 23 | weight_decay (float, optional): weight decay coefficient (default: 1e-2) 24 | amsgrad (boolean, optional): whether to use the AMSGrad variant of this 25 | algorithm from the paper `On the Convergence of Adam and Beyond`_ 26 | (default: False) 27 | .. _Adam\: A Method for Stochastic Optimization: 28 | https://arxiv.org/abs/1412.6980 29 | .. _Decoupled Weight Decay Regularization: 30 | https://arxiv.org/abs/1711.05101 31 | .. _On the Convergence of Adam and Beyond: 32 | https://openreview.net/forum?id=ryQu7f-RZ 33 | """ 34 | 35 | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, 36 | weight_decay=1e-2, amsgrad=False): 37 | if not 0.0 <= lr: 38 | raise ValueError("Invalid learning rate: {}".format(lr)) 39 | if not 0.0 <= eps: 40 | raise ValueError("Invalid epsilon value: {}".format(eps)) 41 | if not 0.0 <= betas[0] < 1.0: 42 | raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0])) 43 | if not 0.0 <= betas[1] < 1.0: 44 | raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1])) 45 | defaults = dict(lr=lr, betas=betas, eps=eps, 46 | weight_decay=weight_decay, amsgrad=amsgrad) 47 | super(AdamW, self).__init__(params, defaults) 48 | 49 | def __setstate__(self, state): 50 | super(AdamW, self).__setstate__(state) 51 | for group in self.param_groups: 52 | group.setdefault('amsgrad', False) 53 | 54 | @torch.no_grad() 55 | def step(self, closure=None): 56 | """Performs a single optimization step. 57 | Arguments: 58 | closure (callable, optional): A closure that reevaluates the model 59 | and returns the losses. 60 | """ 61 | loss = None 62 | if closure is not None: 63 | with torch.enable_grad(): 64 | loss = closure() 65 | 66 | for group in self.param_groups: 67 | for p in group['params']: 68 | if p.grad is None: 69 | continue 70 | 71 | # Perform stepweight decay 72 | p.data.mul_(1 - group['lr'] * group['weight_decay']) 73 | 74 | # Perform optimization step 75 | grad = p.grad 76 | if grad.is_sparse: 77 | raise RuntimeError('Adam does not support sparse gradients, please consider SparseAdam instead') 78 | amsgrad = group['amsgrad'] 79 | 80 | state = self.state[p] 81 | 82 | # State initialization 83 | if len(state) == 0: 84 | state['step'] = 0 85 | # Exponential moving average of gradient values 86 | state['exp_avg'] = torch.zeros_like(p) 87 | # Exponential moving average of squared gradient values 88 | state['exp_avg_sq'] = torch.zeros_like(p) 89 | if amsgrad: 90 | # Maintains max of all exp. moving avg. of sq. grad. values 91 | state['max_exp_avg_sq'] = torch.zeros_like(p) 92 | 93 | exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq'] 94 | if amsgrad: 95 | max_exp_avg_sq = state['max_exp_avg_sq'] 96 | beta1, beta2 = group['betas'] 97 | 98 | state['step'] += 1 99 | bias_correction1 = 1 - beta1 ** state['step'] 100 | bias_correction2 = 1 - beta2 ** state['step'] 101 | 102 | # Decay the first and second moment running average coefficient 103 | exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1) 104 | exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2) 105 | if amsgrad: 106 | # Maintains the maximum of all 2nd moment running avg. till now 107 | torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq) 108 | # Use the max. for normalizing running avg. of gradient 109 | denom = (max_exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps']) 110 | else: 111 | denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps']) 112 | 113 | step_size = group['lr'] / bias_correction1 114 | 115 | p.addcdiv_(exp_avg, denom, value=-step_size) 116 | 117 | return loss -------------------------------------------------------------------------------- /SOLIDER/demo_PETA_Ce.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import json 4 | import os 5 | os.environ['CUDA_VISIBLE_DEVICES'] = '0' 6 | 7 | from dataset.augmentation import get_transform 8 | from models.model_factory import build_backbone, build_classifier 9 | 10 | import torch 11 | import os.path as osp 12 | from PIL import Image 13 | from configs import cfg, update_config 14 | 15 | from models.base_block import FeatClassifier 16 | from tools.function import get_model_log_path, get_reload_weight 17 | from tools.utils import set_seed, str2bool 18 | 19 | 20 | clas_name = ['accessoryHat','accessoryMuffler','accessoryNothing','accessorySunglasses','hairLong' , 21 | 'upperBodyCasual', 'upperBodyFormal', 'upperBodyJacket', 'upperBodyLogo', 'upperBodyPlaid', 'upperBodyShortSleeve', 22 | 'upperBodyThinStripes', 'upperBodyTshirt','upperBodyOther','upperBodyVNeck', 23 | 'lowerBodyCasual', 'lowerBodyFormal', 'lowerBodyJeans', 'lowerBodyShorts', 'lowerBodyShortSkirt','lowerBodyTrousers', 24 | 'footwearLeatherShoes', 'footwearSandals', 'footwearShoes', 'footwearSneaker' , 25 | 'carryingBackpack', 'carryingOther', 'carryingMessengerBag', 'carryingNothing', 'carryingPlasticBags' , 26 | 'personalLess30','personalLess45','personalLess60','personalLarger60','personalMale', 27 | 'upperBodyBlack', 'upperBodyBlue', 'upperBodyBrown', 'upperBodyGreen', 'upperBodyGrey', 'upperBodyOrange', 28 | 'upperBodyPink', 'upperBodyPurple', 'upperBodyRed', 'upperBodyWhite', 'upperBodyYellow', 'lowerBodyBlack', 29 | 'lowerBodyBlue', 'lowerBodyBrown', 'lowerBodyGreen', 'lowerBodyGrey', 'lowerBodyOrange', 'lowerBodyPink', 30 | 'lowerBodyPurple', 'lowerBodyRed', 'lowerBodyWhite', 'lowerBodyYellow', 'hairBlack', 'hairBlue', 'hairBrown', 31 | 'hairGreen', 'hairGrey', 'hairOrange', 'hairPink', 'hairPurple', 'hairRed', 'hairWhite', 'hairYellow', 'footwearBlack', 32 | 'footwearBlue', 'footwearBrown', 'footwearGreen', 'footwearGrey', 'footwearOrange', 'footwearPink', 'footwearPurple', 33 | 'footwearRed', 'footwearWhite', 'footwearYellow','accessoryHeadphone', 'personalLess15', 'carryingBabyBuggy', 'hairBald', 34 | 'footwearBoots', 'lowerBodyCapri', 'carryingShoppingTro', 'carryingUmbrella', 'personalFemale', 'carryingFolder', 35 | 'accessoryHairBand', 'lowerBodyHotPants', 'accessoryKerchief', 'lowerBodyLongSkirt', 'upperBodyLongSleeve', 36 | 'lowerBodyPlaid', 'lowerBodyThinStripes', 'carryingLuggageCase', 'upperBodyNoSleeve', 'hairShort', 'footwearStocking', 37 | 'upperBodySuit', 'carryingSuitcase', 'lowerBodySuits', 'upperBodySweater', 'upperBodyThickStripes'] 38 | 39 | 40 | def main(cfg, args): 41 | exp_dir = os.path.join('exp_result', cfg.DATASET.NAME) 42 | model_dir, log_dir = get_model_log_path(exp_dir, cfg.NAME) 43 | 44 | train_tsfm, valid_tsfm = get_transform(cfg) 45 | print(valid_tsfm) 46 | 47 | backbone, c_output = build_backbone(cfg.BACKBONE.TYPE, cfg.BACKBONE.MULTISCALE) 48 | 49 | 50 | classifier = build_classifier(cfg.CLASSIFIER.NAME)( 51 | # nattr=79, 52 | nattr=105, 53 | c_in=c_output, 54 | bn=cfg.CLASSIFIER.BN, 55 | pool=cfg.CLASSIFIER.POOLING, 56 | scale =cfg.CLASSIFIER.SCALE 57 | ) 58 | 59 | model = FeatClassifier(backbone, classifier) 60 | 61 | if torch.cuda.is_available(): 62 | model = torch.nn.DataParallel(model).cuda() 63 | 64 | model = get_reload_weight(model_dir, model, pth='') # Change weights path 65 | model.eval() 66 | with torch.no_grad(): 67 | for file_name in os.listdir(args.test_img): 68 | if not os.path.isdir(file_name): 69 | continue 70 | img_paths = glob.glob(osp.join(args.test_img+'/'+file_name, '*.jpg')) 71 | for img_path in img_paths: 72 | img = Image.open(img_path) 73 | img = img.convert("RGB") 74 | img = valid_tsfm(img).cuda() 75 | img = img.view(1, *img.size()) 76 | valid_logits, attns = model(img) 77 | valid_probs = torch.sigmoid(valid_logits[0]).cpu().numpy() 78 | valid_probs = valid_probs[0]>0.5 79 | for i, val in enumerate(valid_probs): 80 | if val: 81 | with open(args.test_img+ '/' + 'PAR_PETA_105.txt', 'a', 82 | encoding='utf-8') as f: 83 | f.write(img_path + ' ' + str(i) + ' ' + str(1) + '\n') 84 | else: 85 | with open(args.test_img + '/' + 'PAR_PETA_105.txt', 'a', 86 | encoding='utf-8') as f: 87 | f.write(img_path + ' ' + str(i) + ' ' + str(0) + '\n') 88 | 89 | def argument_parser(): 90 | parser = argparse.ArgumentParser(description="attribute recognition", 91 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 92 | parser.add_argument( 93 | "--test_img", help="test images", type=str, 94 | default="../Celeb-reID-light", 95 | ) 96 | parser.add_argument( 97 | "--cfg", help="decide which cfg to use", type=str,default='../MADE/SOLIDER/configs/peta_zs.yaml' 98 | ) 99 | parser.add_argument("--debug", type=str2bool, default="true") 100 | 101 | args = parser.parse_args() 102 | 103 | return args 104 | 105 | if __name__ == '__main__': 106 | args = argument_parser() 107 | update_config(cfg, args) 108 | 109 | main(cfg, args) -------------------------------------------------------------------------------- /SOLIDER/demo_PETA_ltcc.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import os 4 | os.environ['CUDA_VISIBLE_DEVICES'] = '0' 5 | 6 | from dataset.augmentation import get_transform 7 | from models.model_factory import build_backbone, build_classifier 8 | 9 | import numpy as np 10 | import torch 11 | import os.path as osp 12 | from PIL import Image 13 | from configs import cfg, update_config 14 | 15 | from models.base_block import FeatClassifier 16 | from tools.function import get_model_log_path, get_reload_weight 17 | from tools.utils import set_seed, str2bool 18 | set_seed(605) 19 | 20 | clas_name = ['accessoryHat','accessoryMuffler','accessoryNothing','accessorySunglasses','hairLong' , 21 | 'upperBodyCasual', 'upperBodyFormal', 'upperBodyJacket', 'upperBodyLogo', 'upperBodyPlaid', 'upperBodyShortSleeve', 22 | 'upperBodyThinStripes', 'upperBodyTshirt','upperBodyOther','upperBodyVNeck', 23 | 'lowerBodyCasual', 'lowerBodyFormal', 'lowerBodyJeans', 'lowerBodyShorts', 'lowerBodyShortSkirt','lowerBodyTrousers', 24 | 'footwearLeatherShoes', 'footwearSandals', 'footwearShoes', 'footwearSneaker' , 25 | 'carryingBackpack', 'carryingOther', 'carryingMessengerBag', 'carryingNothing', 'carryingPlasticBags' , 26 | 'personalLess30','personalLess45','personalLess60','personalLarger60','personalMale', 27 | 'upperBodyBlack', 'upperBodyBlue', 'upperBodyBrown', 'upperBodyGreen', 'upperBodyGrey', 'upperBodyOrange', 28 | 'upperBodyPink', 'upperBodyPurple', 'upperBodyRed', 'upperBodyWhite', 'upperBodyYellow', 'lowerBodyBlack', 29 | 'lowerBodyBlue', 'lowerBodyBrown', 'lowerBodyGreen', 'lowerBodyGrey', 'lowerBodyOrange', 'lowerBodyPink', 30 | 'lowerBodyPurple', 'lowerBodyRed', 'lowerBodyWhite', 'lowerBodyYellow', 'hairBlack', 'hairBlue', 'hairBrown', 31 | 'hairGreen', 'hairGrey', 'hairOrange', 'hairPink', 'hairPurple', 'hairRed', 'hairWhite', 'hairYellow', 'footwearBlack', 32 | 'footwearBlue', 'footwearBrown', 'footwearGreen', 'footwearGrey', 'footwearOrange', 'footwearPink', 'footwearPurple', 33 | 'footwearRed', 'footwearWhite', 'footwearYellow','accessoryHeadphone', 'personalLess15', 'carryingBabyBuggy', 'hairBald', 34 | 'footwearBoots', 'lowerBodyCapri', 'carryingShoppingTro', 'carryingUmbrella', 'personalFemale', 'carryingFolder', 35 | 'accessoryHairBand', 'lowerBodyHotPants', 'accessoryKerchief', 'lowerBodyLongSkirt', 'upperBodyLongSleeve', 36 | 'lowerBodyPlaid', 'lowerBodyThinStripes', 'carryingLuggageCase', 'upperBodyNoSleeve', 'hairShort', 'footwearStocking', 37 | 'upperBodySuit', 'carryingSuitcase', 'lowerBodySuits', 'upperBodySweater', 'upperBodyThickStripes'] 38 | 39 | 40 | def main(cfg, args): 41 | exp_dir = os.path.join('exp_result', cfg.DATASET.NAME) 42 | model_dir, log_dir = get_model_log_path(exp_dir, cfg.NAME) 43 | 44 | train_tsfm, valid_tsfm = get_transform(cfg) 45 | print(valid_tsfm) 46 | 47 | backbone, c_output = build_backbone(cfg.BACKBONE.TYPE, cfg.BACKBONE.MULTISCALE) 48 | 49 | 50 | classifier = build_classifier(cfg.CLASSIFIER.NAME)( 51 | # nattr=79, 52 | nattr=105, 53 | c_in=c_output, 54 | bn=cfg.CLASSIFIER.BN, 55 | pool=cfg.CLASSIFIER.POOLING, 56 | scale =cfg.CLASSIFIER.SCALE 57 | ) 58 | 59 | model = FeatClassifier(backbone, classifier) 60 | 61 | if torch.cuda.is_available(): 62 | model = torch.nn.DataParallel(model).cuda() 63 | 64 | model = get_reload_weight(model_dir, model, pth='') # Change weights path 65 | model.eval() 66 | 67 | with torch.no_grad(): 68 | for file_name in os.listdir(args.test_img): 69 | if file_name != 'info': 70 | img_paths = glob.glob(osp.join(args.test_img+'/'+file_name, '*.png')) 71 | for img_path in img_paths: 72 | img = Image.open(img_path) 73 | img = img.convert("RGB") 74 | img = valid_tsfm(img).cuda() 75 | img = img.view(1, *img.size()) 76 | valid_logits, attns = model(img) 77 | valid_probs = torch.sigmoid(valid_logits[0]).cpu().numpy() 78 | valid_probs = valid_probs[0]>0.5 79 | for i, val in enumerate(valid_probs): 80 | if val: 81 | with open('/data/Data/ReIDData/LTCC' + '/' + 'PAR_PETA_105.txt', 'a', 82 | encoding='utf-8') as f: 83 | f.write(img_path + ' ' + str(i) + ' ' + str(1) + '\n') 84 | else: 85 | with open('/data/Data/ReIDData/LTCC' + '/' + 'PAR_PETA_105.txt', 'a', 86 | encoding='utf-8') as f: 87 | f.write(img_path + ' ' + str(i) + ' ' + str(0) + '\n') 88 | 89 | def argument_parser(): 90 | parser = argparse.ArgumentParser(description="attribute recognition", 91 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 92 | parser.add_argument( 93 | "--test_img", help="test images", type=str, 94 | default="/data/Data/ReIDData/LTCC", 95 | ) 96 | parser.add_argument( 97 | "--cfg", help="decide which cfg to use", type=str,default='../MADE/SOLIDER/configs/peta_zs.yaml' 98 | ) 99 | parser.add_argument("--debug", type=str2bool, default="true") 100 | parser.add_argument("--name_pattern",type=str, default="Celeb-reID") 101 | 102 | args = parser.parse_args() 103 | 104 | return args 105 | 106 | if __name__ == '__main__': 107 | args = argument_parser() 108 | update_config(cfg, args) 109 | 110 | main(cfg, args) -------------------------------------------------------------------------------- /data/dataset_loader.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import functools 3 | import os.path as osp 4 | from PIL import Image 5 | from torch.utils.data import Dataset 6 | import numpy as np 7 | 8 | def read_image(img_path): 9 | """Keep reading image until succeed. 10 | This can avoid IOError incurred by heavy IO process.""" 11 | got_img = False 12 | if not osp.exists(img_path): 13 | raise IOError("{} does not exist".format(img_path)) 14 | while not got_img: 15 | try: 16 | img = Image.open(img_path).convert('RGB') 17 | got_img = True 18 | except IOError: 19 | print("IOError incurred when reading '{}'. Will redo. Don't worry. Just chill.".format(img_path)) 20 | pass 21 | return img 22 | 23 | 24 | class ImageDataset(Dataset): 25 | """Image Person ReID Dataset""" 26 | def __init__(self, dataset, aux_info=False,transform=None): 27 | self.dataset = dataset 28 | self.transform = transform 29 | self.aux_info = aux_info 30 | 31 | def __len__(self): 32 | return len(self.dataset) 33 | 34 | def __getitem__(self, index): 35 | if self.aux_info: 36 | img_path, pid, camid, clothes_id,aux_info = self.dataset[index] 37 | img = read_image(img_path) 38 | cloth_id_batch = torch.tensor(clothes_id, dtype=torch.int64) 39 | if self.transform is not None: 40 | img = self.transform(img) 41 | if type(aux_info) is np.ndarray: 42 | select_index = np.random.randint(aux_info.shape[0]) 43 | return img, pid,camid, clothes_id,cloth_id_batch, aux_info[select_index,:] 44 | else: 45 | return img, pid,camid, clothes_id,cloth_id_batch, np.asarray(aux_info).astype(np.float64) 46 | else: 47 | img_path, pid, camid, clothes_id = self.dataset[index] 48 | img = read_image(img_path) 49 | cloth_id_batch = torch.tensor(clothes_id, dtype=torch.int64) 50 | if self.transform is not None: 51 | img = self.transform(img) 52 | return img, pid, camid, clothes_id,cloth_id_batch 53 | 54 | 55 | def pil_loader(path): 56 | # open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835) 57 | with open(path, 'rb') as f: 58 | with Image.open(f) as img: 59 | return img.convert('RGB') 60 | 61 | 62 | def accimage_loader(path): 63 | try: 64 | import accimage 65 | return accimage.Image(path) 66 | except IOError: 67 | # Potentially a decoding problem, fall back to PIL.Image 68 | return pil_loader(path) 69 | 70 | 71 | def get_default_image_loader(): 72 | from torchvision import get_image_backend 73 | if get_image_backend() == 'accimage': 74 | return accimage_loader 75 | else: 76 | return pil_loader 77 | 78 | 79 | def image_loader(path): 80 | from torchvision import get_image_backend 81 | if get_image_backend() == 'accimage': 82 | return accimage_loader(path) 83 | else: 84 | return pil_loader(path) 85 | 86 | 87 | def video_loader(img_paths, image_loader): 88 | video = [] 89 | for image_path in img_paths: 90 | if osp.exists(image_path): 91 | video.append(image_loader(image_path)) 92 | else: 93 | return video 94 | 95 | return video 96 | 97 | 98 | def get_default_video_loader(): 99 | image_loader = get_default_image_loader() 100 | return functools.partial(video_loader, image_loader=image_loader) 101 | 102 | 103 | class VideoDataset(Dataset): 104 | """Video Person ReID Dataset. 105 | Note: 106 | Batch data has shape N x C x T x H x W 107 | Args: 108 | dataset (list): List with items (img_paths, pid, camid) 109 | temporal_transform (callable, optional): A function/transform that takes in a list of frame indices 110 | and returns a transformed version 111 | target_transform (callable, optional): A function/transform that takes in the 112 | target and transforms it. 113 | loader (callable, optional): A function to load an video given its path and frame indices. 114 | """ 115 | 116 | def __init__(self, 117 | dataset, 118 | spatial_transform=None, 119 | temporal_transform=None, 120 | get_loader=get_default_video_loader, 121 | cloth_changing=True): 122 | self.dataset = dataset 123 | self.spatial_transform = spatial_transform 124 | self.temporal_transform = temporal_transform 125 | self.loader = get_loader() 126 | self.cloth_changing = cloth_changing 127 | 128 | def __len__(self): 129 | return len(self.dataset) 130 | 131 | def __getitem__(self, index): 132 | """ 133 | Args: 134 | index (int): Index 135 | 136 | Returns: 137 | tuple: (clip, pid, camid) where pid is identity of the clip. 138 | """ 139 | if self.cloth_changing: 140 | img_paths, pid, camid, clothes_id = self.dataset[index] 141 | else: 142 | img_paths, pid, camid = self.dataset[index] 143 | 144 | if self.temporal_transform is not None: 145 | img_paths = self.temporal_transform(img_paths) 146 | 147 | clip = self.loader(img_paths) 148 | 149 | if self.spatial_transform is not None: 150 | self.spatial_transform.randomize_parameters() 151 | clip = [self.spatial_transform(img) for img in clip] 152 | 153 | # trans T x C x H x W to C x T x H x W 154 | clip = torch.stack(clip, 0).permute(1, 0, 2, 3) 155 | 156 | if self.cloth_changing: 157 | return clip, pid, camid, clothes_id 158 | else: 159 | return clip, pid, camid -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- 1 | import data.img_transforms as T 2 | from data.dataloader import DataLoaderX 3 | from data.dataset_loader import ImageDataset, VideoDataset 4 | from data.samplers import DistributedRandomIdentitySampler, DistributedInferenceSampler 5 | from data.datasets.ltcc import LTCC 6 | from data.datasets.prcc import PRCC 7 | from data.datasets.Celeb_light import Celeb_light 8 | from data.datasets.last import LaST 9 | from torch.utils.data import ConcatDataset, DataLoader 10 | 11 | 12 | __factory = { 13 | 'ltcc': LTCC, 14 | 'prcc': PRCC, 15 | 'celeb_light': Celeb_light, 16 | 'last': LaST, 17 | } 18 | 19 | 20 | 21 | def get_names(): 22 | return list(__factory.keys()) 23 | 24 | 25 | def build_dataset(config): 26 | if config.DATA.DATASET not in __factory.keys(): 27 | raise KeyError("Invalid dataset, got '{}', but expected to be one of {}".format(name, __factory.keys())) 28 | dataset = __factory[config.DATA.DATASET](root=config.DATA.ROOT,aux_info=config.DATA.AUX_INFO,meta_dir=config.DATA.META_DIR,meta_dims=config.MODEL.META_DIMS[0]) 29 | 30 | return dataset 31 | 32 | 33 | def build_img_transforms(config): 34 | transform_train = T.Compose([ 35 | T.Resize((config.DATA.IMG_HEIGHT , config.DATA.IMG_WIDTH)), 36 | T.RandomCroping(p=config.AUG.RC_PROB), 37 | T.RandomHorizontalFlip(p=config.AUG.RF_PROB), 38 | T.ToTensor(), 39 | T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), 40 | T.RandomErasing(probability=config.AUG.RE_PROB) 41 | ]) 42 | transform_test = T.Compose([ 43 | T.Resize((config.DATA.IMG_HEIGHT , config.DATA.IMG_WIDTH)), 44 | T.ToTensor(), 45 | T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), 46 | ]) 47 | 48 | return transform_train, transform_test 49 | 50 | 51 | 52 | def build_dataloader(config): 53 | dataset = build_dataset(config) 54 | # image dataset 55 | transform_train, transform_test = build_img_transforms(config) 56 | # transform_train = build_transform(config,is_train=True) 57 | # transform_test = build_transform(config,is_train=False) 58 | train_sampler = DistributedRandomIdentitySampler(dataset.train, 59 | num_instances=config.DATA.NUM_INSTANCES, 60 | seed=config.SOLVER.SEED) 61 | trainloader = DataLoaderX(dataset=ImageDataset(dataset.train, transform=transform_train,aux_info=config.DATA.AUX_INFO), 62 | sampler=train_sampler, 63 | batch_size=config.DATA.BATCH_SIZE, num_workers=config.DATA.NUM_WORKERS, 64 | pin_memory=config.DATA.PIN_MEMORY, drop_last=True) 65 | 66 | galleryloader = DataLoaderX(dataset=ImageDataset(dataset.gallery, transform=transform_test,aux_info=config.DATA.AUX_INFO), 67 | sampler=DistributedInferenceSampler(dataset.gallery), 68 | batch_size=config.DATA.TEST_BATCH, num_workers=config.DATA.NUM_WORKERS, 69 | pin_memory=config.DATA.PIN_MEMORY, drop_last=False, shuffle=False) 70 | if config.DATA.DATASET == 'prcc': 71 | queryloader_same = DataLoaderX(dataset=ImageDataset(dataset.query_same, transform=transform_test,aux_info=config.DATA.AUX_INFO), 72 | sampler=DistributedInferenceSampler(dataset.query_same), 73 | batch_size=config.DATA.TEST_BATCH, num_workers=config.DATA.NUM_WORKERS, 74 | pin_memory=config.DATA.PIN_MEMORY,drop_last=False, shuffle=False) 75 | queryloader_diff = DataLoaderX(dataset=ImageDataset(dataset.query_diff, transform=transform_test,aux_info=config.DATA.AUX_INFO), 76 | sampler=DistributedInferenceSampler(dataset.query_diff), 77 | batch_size=config.DATA.TEST_BATCH, num_workers=config.DATA.NUM_WORKERS, 78 | pin_memory=True, drop_last=False, shuffle=False) 79 | 80 | 81 | combined_dataset = ConcatDataset([queryloader_diff.dataset, galleryloader.dataset]) 82 | 83 | val_loader = DataLoader( 84 | dataset=combined_dataset, 85 | batch_size=config.DATA.TEST_BATCH, 86 | num_workers=config.DATA.NUM_WORKERS, 87 | pin_memory=config.DATA.PIN_MEMORY, 88 | drop_last=False, 89 | shuffle=False 90 | ) 91 | 92 | combined_dataset_same = ConcatDataset([queryloader_same.dataset, galleryloader.dataset]) 93 | 94 | val_loader_same = DataLoader( 95 | dataset=combined_dataset_same, 96 | batch_size=config.DATA.TEST_BATCH, 97 | num_workers=config.DATA.NUM_WORKERS, 98 | pin_memory=config.DATA.PIN_MEMORY, 99 | drop_last=False, 100 | shuffle=False 101 | ) 102 | 103 | return trainloader, queryloader_same, queryloader_diff, galleryloader, dataset, train_sampler,val_loader,val_loader_same 104 | else: 105 | queryloader = DataLoaderX(dataset=ImageDataset(dataset.query, transform=transform_test,aux_info=config.DATA.AUX_INFO), 106 | sampler=DistributedInferenceSampler(dataset.query), 107 | batch_size=config.DATA.TEST_BATCH, num_workers=config.DATA.NUM_WORKERS, 108 | pin_memory=True, drop_last=False, shuffle=False) 109 | 110 | combined_dataset = ConcatDataset([queryloader.dataset, galleryloader.dataset]) 111 | 112 | val_loader = DataLoader( 113 | dataset=combined_dataset, 114 | batch_size=config.DATA.TEST_BATCH, 115 | num_workers=config.DATA.NUM_WORKERS, 116 | pin_memory=config.DATA.PIN_MEMORY, 117 | drop_last=False, 118 | shuffle=False 119 | ) 120 | 121 | 122 | 123 | return trainloader, queryloader, galleryloader, dataset, train_sampler,val_loader 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /SOLIDER/models/model_ema.py: -------------------------------------------------------------------------------- 1 | """ Exponential Moving Average (EMA) of model updates 2 | 3 | Hacked together by / Copyright 2020 Ross Wightman 4 | """ 5 | import logging 6 | from collections import OrderedDict 7 | from copy import deepcopy 8 | 9 | import torch 10 | import torch.nn as nn 11 | 12 | _logger = logging.getLogger(__name__) 13 | 14 | 15 | class ModelEma: 16 | """ Model Exponential Moving Average (DEPRECATED) 17 | 18 | Keep a moving average of everything in the model state_dict (parameters and buffers). 19 | This version is deprecated, it does not work with scripted models. Will be removed eventually. 20 | 21 | This is intended to allow functionality like 22 | https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage 23 | 24 | A smoothed version of the weights is necessary for some training schemes to perform well. 25 | E.g. Google's hyper-params for training MNASNet, MobileNet-V3, EfficientNet, etc that use 26 | RMSprop with a short 2.4-3 epoch decay period and slow LR decay rate of .96-.99 requires EMA 27 | smoothing of weights to match results. Pay attention to the decay constant you are using 28 | relative to your update count per epoch. 29 | 30 | To keep EMA from using GPU resources, set device='cpu'. This will save a bit of memory but 31 | disable validation of the EMA weights. Validation will have to be done manually in a separate 32 | process, or after the training stops converging. 33 | 34 | This class is sensitive where it is initialized in the sequence of model init, 35 | GPU assignment and distributed training wrappers. 36 | """ 37 | def __init__(self, model, decay=0.9999, device='', resume=''): 38 | # make a copy of the model for accumulating moving average of weights 39 | self.ema = deepcopy(model) 40 | self.ema.eval() 41 | self.decay = decay 42 | self.device = device # perform ema on different device from model if set 43 | if device: 44 | self.ema.to(device=device) 45 | self.ema_has_module = hasattr(self.ema, 'module') 46 | if resume: 47 | self._load_checkpoint(resume) 48 | for p in self.ema.parameters(): 49 | p.requires_grad_(False) 50 | 51 | def _load_checkpoint(self, checkpoint_path): 52 | checkpoint = torch.load(checkpoint_path, map_location='cpu') 53 | assert isinstance(checkpoint, dict) 54 | if 'state_dict_ema' in checkpoint: 55 | new_state_dict = OrderedDict() 56 | for k, v in checkpoint['state_dict_ema'].items(): 57 | # ema model may have been wrapped by DataParallel, and need module prefix 58 | if self.ema_has_module: 59 | name = 'module.' + k if not k.startswith('module') else k 60 | else: 61 | name = k 62 | new_state_dict[name] = v 63 | self.ema.load_state_dict(new_state_dict) 64 | _logger.info("Loaded state_dict_ema") 65 | else: 66 | _logger.warning("Failed to find state_dict_ema, starting from loaded model weights") 67 | 68 | def update(self, model): 69 | # correct a mismatch in state dict keys 70 | needs_module = hasattr(model, 'module') and not self.ema_has_module 71 | with torch.no_grad(): 72 | msd = model.state_dict() 73 | for k, ema_v in self.ema.state_dict().items(): 74 | if needs_module: 75 | k = 'module.' + k 76 | model_v = msd[k].detach() 77 | if self.device: 78 | model_v = model_v.to(device=self.device) 79 | ema_v.copy_(ema_v * self.decay + (1. - self.decay) * model_v) 80 | 81 | 82 | class ModelEmaV2(nn.Module): 83 | """ Model Exponential Moving Average V2 84 | 85 | Keep a moving average of everything in the model state_dict (parameters and buffers). 86 | V2 of this module is simpler, it does not match params/buffers based on name but simply 87 | iterates in order. It works with torchscript (JIT of full model). 88 | 89 | This is intended to allow functionality like 90 | https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage 91 | 92 | A smoothed version of the weights is necessary for some training schemes to perform well. 93 | E.g. Google's hyper-params for training MNASNet, MobileNet-V3, EfficientNet, etc that use 94 | RMSprop with a short 2.4-3 epoch decay period and slow LR decay rate of .96-.99 requires EMA 95 | smoothing of weights to match results. Pay attention to the decay constant you are using 96 | relative to your update count per epoch. 97 | 98 | To keep EMA from using GPU resources, set device='cpu'. This will save a bit of memory but 99 | disable validation of the EMA weights. Validation will have to be done manually in a separate 100 | process, or after the training stops converging. 101 | 102 | This class is sensitive where it is initialized in the sequence of model init, 103 | GPU assignment and distributed training wrappers. 104 | """ 105 | def __init__(self, model, decay=0.9999, device=None): 106 | super(ModelEmaV2, self).__init__() 107 | # make a copy of the model for accumulating moving average of weights 108 | self.module = deepcopy(model) 109 | self.module.eval() 110 | self.decay = decay 111 | self.device = device # perform ema on different device from model if set 112 | if self.device is not None: 113 | self.module.to(device=device) 114 | 115 | def _update(self, model, update_fn): 116 | with torch.no_grad(): 117 | for ema_v, model_v in zip(self.module.state_dict().values(), model.state_dict().values()): 118 | if self.device is not None: 119 | model_v = model_v.to(device=self.device) 120 | ema_v.copy_(update_fn(ema_v, model_v)) 121 | 122 | def update(self, model): 123 | self._update(model, update_fn=lambda e, m: self.decay * e + (1. - self.decay) * m) 124 | 125 | def set(self, model): 126 | self._update(model, update_fn=lambda e, m: m) 127 | -------------------------------------------------------------------------------- /data/dataloader.py: -------------------------------------------------------------------------------- 1 | # refer to: https://github.com/JDAI-CV/fast-reid/blob/master/fastreid/data/data_utils.py 2 | 3 | import torch 4 | import threading 5 | import queue 6 | from torch.utils.data import DataLoader 7 | from torch import distributed as dist 8 | 9 | 10 | """ 11 | #based on http://stackoverflow.com/questions/7323664/python-generator-pre-fetch 12 | This is a single-function package that transforms arbitrary generator into a background-thead generator that 13 | prefetches several batches of data in a parallel background thead. 14 | 15 | This is useful if you have a computationally heavy process (CPU or GPU) that 16 | iteratively processes minibatches from the generator while the generator 17 | consumes some other resource (disk IO / loading from database / more CPU if you have unused cores). 18 | 19 | By default these two processes will constantly wait for one another to finish. If you make generator work in 20 | prefetch mode (see examples below), they will work in parallel, potentially saving you your GPU time. 21 | We personally use the prefetch generator when iterating minibatches of data for deep learning with PyTorch etc. 22 | 23 | Quick usage example (ipython notebook) - https://github.com/justheuristic/prefetch_generator/blob/master/example.ipynb 24 | This package contains this object 25 | - BackgroundGenerator(any_other_generator[,max_prefetch = something]) 26 | """ 27 | 28 | 29 | class BackgroundGenerator(threading.Thread): 30 | """ 31 | the usage is below 32 | >> for batch in BackgroundGenerator(my_minibatch_iterator): 33 | >> doit() 34 | More details are written in the BackgroundGenerator doc 35 | >> help(BackgroundGenerator) 36 | """ 37 | 38 | def __init__(self, generator, local_rank, max_prefetch=10): 39 | """ 40 | This function transforms generator into a background-thead generator. 41 | :param generator: generator or genexp or any 42 | It can be used with any minibatch generator. 43 | 44 | It is quite lightweight, but not entirely weightless. 45 | Using global variables inside generator is not recommended (may raise GIL and zero-out the 46 | benefit of having a background thread.) 47 | The ideal use case is when everything it requires is store inside it and everything it 48 | outputs is passed through queue. 49 | 50 | There's no restriction on doing weird stuff, reading/writing files, retrieving 51 | URLs [or whatever] wlilst iterating. 52 | 53 | :param max_prefetch: defines, how many iterations (at most) can background generator keep 54 | stored at any moment of time. 55 | Whenever there's already max_prefetch batches stored in queue, the background process will halt until 56 | one of these batches is dequeued. 57 | 58 | !Default max_prefetch=1 is okay unless you deal with some weird file IO in your generator! 59 | 60 | Setting max_prefetch to -1 lets it store as many batches as it can, which will work 61 | slightly (if any) faster, but will require storing 62 | all batches in memory. If you use infinite generator with max_prefetch=-1, it will exceed the RAM size 63 | unless dequeued quickly enough. 64 | """ 65 | super().__init__() 66 | self.queue = queue.Queue(max_prefetch) 67 | self.generator = generator 68 | self.local_rank = local_rank 69 | self.daemon = True 70 | self.exit_event = threading.Event() 71 | self.start() 72 | 73 | def run(self): 74 | torch.cuda.set_device(self.local_rank) 75 | for item in self.generator: 76 | if self.exit_event.is_set(): 77 | break 78 | self.queue.put(item) 79 | self.queue.put(None) 80 | 81 | def next(self): 82 | next_item = self.queue.get() 83 | if next_item is None: 84 | raise StopIteration 85 | return next_item 86 | 87 | # Python 3 compatibility 88 | def __next__(self): 89 | return self.next() 90 | 91 | def __iter__(self): 92 | return self 93 | 94 | 95 | class DataLoaderX(DataLoader): 96 | def __init__(self, **kwargs): 97 | super().__init__(**kwargs) 98 | local_rank = dist.get_rank() 99 | self.stream = torch.cuda.Stream(local_rank) # create a new cuda stream in each process 100 | self.local_rank = local_rank 101 | 102 | def __iter__(self): 103 | self.iter = super().__iter__() 104 | self.iter = BackgroundGenerator(self.iter, self.local_rank) 105 | self.preload() 106 | return self 107 | 108 | def _shutdown_background_thread(self): 109 | if not self.iter.is_alive(): 110 | # avoid re-entrance or ill-conditioned thread state 111 | return 112 | 113 | # Set exit event to True for background threading stopping 114 | self.iter.exit_event.set() 115 | 116 | # Exhaust all remaining elements, so that the queue becomes empty, 117 | # and the thread should quit 118 | for _ in self.iter: 119 | pass 120 | 121 | # Waiting for background thread to quit 122 | self.iter.join() 123 | 124 | def preload(self): 125 | self.batch = next(self.iter, None) 126 | if self.batch is None: 127 | return None 128 | with torch.cuda.stream(self.stream): 129 | # if isinstance(self.batch[0], torch.Tensor): 130 | # self.batch[0] = self.batch[0].to(device=self.local_rank, non_blocking=True) 131 | for k, v in enumerate(self.batch): 132 | if isinstance(self.batch[k], torch.Tensor): 133 | self.batch[k] = self.batch[k].to(device=self.local_rank, non_blocking=True) 134 | 135 | def __next__(self): 136 | torch.cuda.current_stream().wait_stream( 137 | self.stream 138 | ) # wait tensor to put on GPU 139 | batch = self.batch 140 | if batch is None: 141 | raise StopIteration 142 | self.preload() 143 | return batch 144 | 145 | # Signal for shutting down background thread 146 | def shutdown(self): 147 | # If the dataloader is to be freed, shutdown its BackgroundGenerator 148 | self._shutdown_background_thread() 149 | -------------------------------------------------------------------------------- /SOLIDER/batch_engine.py: -------------------------------------------------------------------------------- 1 | import math 2 | import time 3 | 4 | import numpy as np 5 | import torch 6 | from torch.nn.utils import clip_grad_norm_ 7 | from tqdm import tqdm 8 | 9 | from tools.distributed import reduce_tensor 10 | from tools.utils import AverageMeter, to_scalar, time_str 11 | 12 | 13 | def logits4pred(criterion, logits_list): 14 | if criterion.__class__.__name__.lower() in ['bceloss']: 15 | logits = logits_list[0] 16 | probs = logits.sigmoid() 17 | else: 18 | assert False, f"{criterion.__class__.__name__.lower()} not exits" 19 | 20 | return probs, logits 21 | 22 | 23 | def batch_trainer(cfg, args, epoch, model, model_ema, train_loader, criterion, optimizer, loss_w=[1, ], scheduler=None, tb_writer=None): 24 | model.train() 25 | epoch_time = time.time() 26 | 27 | loss_meter = AverageMeter() 28 | subloss_meters = [AverageMeter() for i in range(len(loss_w))] 29 | 30 | batch_num = len(train_loader) 31 | gt_list = [] 32 | preds_probs = [] 33 | preds_logits = [] 34 | imgname_list = [] 35 | loss_mtr_list = [] 36 | 37 | 38 | for step, (imgs, gt_label, imgname) in enumerate(train_loader): 39 | iter_num = epoch * len(train_loader) + step 40 | 41 | batch_time = time.time() 42 | imgs, gt_label = imgs.cuda(), gt_label.cuda() 43 | train_logits, feat = model(imgs, gt_label) 44 | 45 | 46 | loss_list, loss_mtr = criterion(train_logits, gt_label) 47 | 48 | train_loss = 0 49 | 50 | for i, l in enumerate(loss_w): 51 | train_loss += loss_list[i] * l 52 | 53 | optimizer.zero_grad() 54 | train_loss.backward() 55 | 56 | # for name, param in model.named_parameters(): 57 | # if param.grad is None: 58 | # print("NO " + name) 59 | # else: 60 | # print("YES " + name) 61 | 62 | if cfg.TRAIN.CLIP_GRAD: 63 | clip_grad_norm_(model.parameters(), max_norm=10.0) # make larger learning rate works 64 | 65 | optimizer.step() 66 | ft_lr = optimizer.param_groups[0]['lr'] 67 | fresh_lr = optimizer.param_groups[1]['lr'] 68 | 69 | if cfg.TRAIN.LR_SCHEDULER.TYPE == 'warmup_cosine' and scheduler is not None: 70 | scheduler.step(iter_num+1) 71 | 72 | if tb_writer is not None: 73 | tb_writer.add_scalar('lr/step', fresh_lr, epoch) 74 | 75 | if model_ema is not None: 76 | model_ema.update(model) 77 | 78 | torch.cuda.synchronize() 79 | 80 | if len(loss_list) > 1: 81 | for i, meter in enumerate(subloss_meters): 82 | meter.update( 83 | to_scalar(reduce_tensor(loss_list[i], args.world_size) 84 | if args.distributed else loss_list[i])) 85 | loss_meter.update(to_scalar(reduce_tensor(train_loss, args.world_size) if args.distributed else train_loss)) 86 | 87 | train_probs, train_logits = logits4pred(criterion, train_logits) 88 | 89 | gt_list.append(gt_label.cpu().numpy()) 90 | preds_probs.append(train_probs.detach().cpu().numpy()) 91 | preds_logits.append(train_logits.detach().cpu().numpy()) 92 | 93 | imgname_list.append(imgname) 94 | 95 | log_interval = 50 96 | 97 | if (step + 1) % log_interval == 0 or (step + 1) % len(train_loader) == 0: 98 | if args.local_rank == 0: 99 | print(f'{time_str()}, ' 100 | f'Step {step}/{batch_num} in Ep {epoch}, ' 101 | f'LR: [{ft_lr:.1e}, {fresh_lr:.1e}] ' 102 | f'Time: {time.time() - batch_time:.2f}s , ' 103 | f'train_loss: {loss_meter.avg:.4f}, ') 104 | 105 | #print([f'{meter.avg:.4f}' for meter in subloss_meters]) 106 | 107 | # break 108 | 109 | train_loss = loss_meter.avg 110 | 111 | gt_label = np.concatenate(gt_list, axis=0) 112 | preds_probs = np.concatenate(preds_probs, axis=0) 113 | 114 | if args.local_rank == 0: 115 | print(f'Epoch {epoch}, LR {fresh_lr}, Train_Time {time.time() - epoch_time:.2f}s, Loss: {loss_meter.avg:.4f}') 116 | 117 | return train_loss, gt_label, preds_probs, imgname_list, preds_logits, loss_mtr_list 118 | 119 | 120 | def valid_trainer(cfg, args, epoch, model, valid_loader, criterion, loss_w=[1, ]): 121 | model.eval() 122 | loss_meter = AverageMeter() 123 | subloss_meters = [AverageMeter() for i in range(len(loss_w))] 124 | 125 | preds_probs = [] 126 | preds_logits = [] 127 | gt_list = [] 128 | imgname_list = [] 129 | loss_mtr_list = [] 130 | 131 | with torch.no_grad(): 132 | for step, (imgs, gt_label, imgname) in enumerate(tqdm(valid_loader)): 133 | imgs = imgs.cuda() 134 | gt_label = gt_label.cuda() 135 | gt_list.append(gt_label.cpu().numpy()) 136 | gt_label[gt_label == -1] = 0 137 | valid_logits, feat = model(imgs, gt_label) 138 | 139 | 140 | loss_list, loss_mtr = criterion(valid_logits, gt_label) 141 | valid_loss = 0 142 | for i, l in enumerate(loss_list): 143 | valid_loss += loss_w[i] * l 144 | 145 | valid_probs, valid_logits = logits4pred(criterion, valid_logits) 146 | preds_probs.append(valid_probs.cpu().numpy()) 147 | preds_logits.append(valid_logits.cpu().numpy()) 148 | 149 | if len(loss_list) > 1: 150 | for i, meter in enumerate(subloss_meters): 151 | meter.update( 152 | to_scalar(reduce_tensor(loss_list[i], args.world_size) if args.distributed else loss_list[i])) 153 | loss_meter.update(to_scalar(reduce_tensor(valid_loss, args.world_size) if args.distributed else valid_loss)) 154 | 155 | torch.cuda.synchronize() 156 | 157 | imgname_list.append(imgname) 158 | 159 | valid_loss = loss_meter.avg 160 | 161 | if args.local_rank == 0: 162 | print([f'{meter.avg:.4f}' for meter in subloss_meters]) 163 | 164 | gt_label = np.concatenate(gt_list, axis=0) 165 | preds_probs = np.concatenate(preds_probs, axis=0) 166 | preds_logits = np.concatenate(preds_logits, axis=0) 167 | 168 | return valid_loss, gt_label, preds_probs, imgname_list, preds_logits, loss_mtr_list 169 | -------------------------------------------------------------------------------- /config/defaults.py: -------------------------------------------------------------------------------- 1 | from yacs.config import CfgNode as CN 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Convention about Training / Test specific parameters 5 | # ----------------------------------------------------------------------------- 6 | # Whenever an argument can be either used for training or for testing, the 7 | # corresponding name will be post-fixed by a _TRAIN for a training parameter, 8 | 9 | # ----------------------------------------------------------------------------- 10 | # Config definition 11 | # ----------------------------------------------------------------------------- 12 | 13 | _C = CN() 14 | # ----------------------------------------------------------------------------- 15 | # MODEL 16 | # ----------------------------------------------------------------------------- 17 | _C.MODEL = CN() 18 | # Using cuda or cpu for training 19 | _C.MODEL.DEVICE = "cuda" 20 | # Name of backbone 21 | _C.MODEL.TYPE = '' 22 | # Model name 23 | _C.MODEL.NAME = '' 24 | 25 | 26 | # If train loss include center loss, options: 'yes' or 'no'. Loss with center loss has different optimizer configuration 27 | _C.MODEL.IF_WITH_CENTER = 'no' 28 | 29 | _C.MODEL.ID_LOSS_TYPE = 'softmax' 30 | _C.MODEL.ID_LOSS_WEIGHT = 1.0 31 | _C.MODEL.TRIPLET_LOSS_WEIGHT = 1.0 32 | 33 | _C.MODEL.METRIC_LOSS_TYPE = 'triplet' 34 | # If train with multi-gpu ddp mode, options: 'True', 'False' 35 | _C.MODEL.DIST_TRAIN = False 36 | # If train with soft triplet loss, options: 'True', 'False' 37 | _C.MODEL.NO_MARGIN = False 38 | # If train with label smooth, options: 'on', 'off' 39 | _C.MODEL.IF_LABELSMOOTH = 'on' 40 | # If train with arcface loss, options: 'True', 'False' 41 | _C.MODEL.COS_LAYER = False 42 | # Dimension of the attribute list 43 | _C.MODEL.META_DIMS = [] 44 | _C.MODEL.CLOTH_XISHU = 3 45 | # Add attributes in model, options: 'True', 'False' 46 | _C.MODEL.ADD_META = False 47 | # Mask cloth attributes, options: 'True', 'False' 48 | _C.MODEL.MASK_META = False 49 | # Add cloth embedding only, options: 'True', 'False' 50 | _C.MODEL.CLOTH_ONLY = False 51 | # ID number of GPU 52 | _C.MODEL.DEVICE_ID = '0' 53 | 54 | # ----------------------------------------------------------------------------- 55 | # Train settings 56 | # ----------------------------------------------------------------------------- 57 | _C.TRAIN = CN() 58 | _C.TRAIN.START_EPOCH = 1 59 | 60 | 61 | 62 | # ----------------------------------------------------------------------------- 63 | # Data settings 64 | # ----------------------------------------------------------------------------- 65 | _C.DATA = CN() 66 | # Batch size for a single GPU, could be overwritten by command line argument 67 | _C.DATA.BATCH_SIZE = 8 68 | # Dataset name 69 | _C.DATA.DATASET = 'imagenet' 70 | # Input image size 71 | _C.DATA.IMG_HEIGHT = 224 72 | _C.DATA.IMG_WIDTH = 224 73 | # Pin CPU memory in DataLoader for more efficient (sometimes) transfer to GPU. 74 | _C.DATA.PIN_MEMORY = True 75 | # Number of data loading threads 76 | _C.DATA.NUM_WORKERS = 4 77 | # Data root 78 | _C.DATA.ROOT = '/data/Data/ReIDData' 79 | # Number of instances 80 | _C.DATA.NUM_INSTANCES = 2 #8 81 | # Batch size during testing 82 | _C.DATA.TEST_BATCH = 128 83 | # Data sampling strategy 84 | _C.DATA.SAMPLER = 'softmax_triplet' 85 | # Extract data containing attributes during data processing, options: 'True', 'False' 86 | _C.DATA.AUX_INFO = True 87 | # Filename containing attributes 88 | _C.DATA.META_DIR = 'PAR_PETA_105.txt' 89 | _C.DATA.RANDOM_NOISE = False 90 | _C.DATA.RANDOM_PROP = 0.05 91 | 92 | 93 | # ----------------------------------------------------------------------------- 94 | # Augmentation settings 95 | # ----------------------------------------------------------------------------- 96 | _C.AUG = CN() 97 | 98 | # Random crop prob 99 | _C.AUG.RC_PROB = 0.5 100 | # Random erase prob 101 | _C.AUG.RE_PROB = 0.5 102 | # Random flip prob 103 | _C.AUG.RF_PROB = 0.5 104 | 105 | # ----------------------------------------------------------------------------- 106 | # Testing settings 107 | # ----------------------------------------------------------------------------- 108 | _C.TEST = CN() 109 | # Whether to use center crop when testing 110 | _C.TEST.CROP = True 111 | 112 | # ---------------------------------------------------------------------------- # 113 | # Solver 114 | # ---------------------------------------------------------------------------- # 115 | _C.SOLVER = CN() 116 | # Name of optimizer 117 | _C.SOLVER.OPTIMIZER_NAME = "Adam" 118 | # Number of max epoches 119 | _C.SOLVER.MAX_EPOCHS = 100 120 | # Base learning rate 121 | _C.SOLVER.BASE_LR = 3e-4 122 | _C.SOLVER.WARMUP_LR = 7.8125e-07 123 | # Whether using larger learning rate for fc layer 124 | _C.SOLVER.LARGE_FC_LR = False 125 | # Factor of learning bias 126 | _C.SOLVER.BIAS_LR_FACTOR = 1 127 | # Factor of learning bias 128 | _C.SOLVER.SEED = 1234 129 | # Momentum 130 | _C.SOLVER.MOMENTUM = 0.9 131 | # Margin of triplet loss 132 | _C.SOLVER.MARGIN = 0.3 133 | # Learning rate of SGD to learn the centers of center loss 134 | _C.SOLVER.CENTER_LR = 0.5 135 | # Balanced weight of center loss 136 | _C.SOLVER.CENTER_LOSS_WEIGHT = 0.0005 137 | 138 | # Settings of weight decay 139 | _C.SOLVER.WEIGHT_DECAY = 0.0005 140 | _C.SOLVER.WEIGHT_DECAY_BIAS = 0.0005 141 | 142 | # decay rate of learning rate 143 | _C.SOLVER.GAMMA = 0.1 144 | # decay step of learning rate 145 | _C.SOLVER.STEPS = (40, 60) 146 | # warm up factor 147 | _C.SOLVER.WARMUP_FACTOR = 0.01 148 | # warm up epochs 149 | _C.SOLVER.WARMUP_EPOCHS = 20 150 | # method of warm up, option: 'constant','linear' 151 | _C.SOLVER.WARMUP_METHOD = "linear" 152 | 153 | _C.SOLVER.COSINE_MARGIN = 0.5 154 | _C.SOLVER.COSINE_SCALE = 30 155 | 156 | # epoch number of saving checkpoints 157 | _C.SOLVER.CHECKPOINT_PERIOD = 10 158 | # iteration of display training log 159 | _C.SOLVER.LOG_PERIOD = 100 160 | # epoch number of validation 161 | _C.SOLVER.EVAL_PERIOD = 1 162 | 163 | 164 | # ---------------------------------------------------------------------------- # 165 | # TEST 166 | # ---------------------------------------------------------------------------- # 167 | 168 | _C.TEST = CN() 169 | # Path to trained model 170 | _C.TEST.WEIGHT = "" 171 | # Whether feature is nomalized before test, if yes, it is equivalent to cosine distance 172 | _C.TEST.FEAT_NORM = 'yes' 173 | # Test using images only 174 | _C.TEST.TYPE = 'image_only' 175 | # ---------------------------------------------------------------------------- # 176 | # Misc options 177 | # ---------------------------------------------------------------------------- # 178 | # Path to checkpoint and saved log of trained model 179 | _C.OUTPUT_DIR = "" 180 | -------------------------------------------------------------------------------- /SOLIDER/dataset/pedes_attr/annotation.md: -------------------------------------------------------------------------------- 1 | ### dataset split 2 | - PETA 19000, train 9500, val 1900, test 7600 3 | - RAP 41585, train 33268, test 8317 4 | - PA100K 100000, train 80000, val 10000, test 10000 5 | - RAPv2 84928, train, 50957 trainset, 16986 valset, 16985 testset 6 | 7 | 8 | ## 统一属性顺序 9 | 1. head region 10 | 2. upper region 11 | 3. lower region 12 | 4. foot region 13 | 5. accessory/bag 14 | 6. age 15 | 7. gender 16 | 8. others 17 | 18 | 19 | 20 | ### PETA (35 in 105) 21 | num_ingroup = [5, 10, 6, 4, 5, 5] 22 | 23 | - 'accessoryHat','accessoryMuffler','accessoryNothing','accessorySunglasses','hairLong' [10, 18, 19, 30, 15] 5 24 | - 'upperBodyCasual', 'upperBodyFormal', 'upperBodyJacket', 'upperBodyLogo', 'upperBodyPlaid', 'upperBodyShortSleeve', 'upperBodyThinStripes', 'upperBodyTshirt','upperBodyOther','upperBodyVNeck' [7, 9, 11, 14, 21, 26, 29, 32, 33, 34] 10 25 | - 'lowerBodyCasual', 'lowerBodyFormal', 'lowerBodyJeans', 'lowerBodyShorts', 'lowerBodyShortSkirt','lowerBodyTrousers' [6, 8, 12, 25, 27, 31] 6 26 | - 'footwearLeatherShoes', 'footwearSandals', 'footwearShoes', 'footwearSneaker' [13, 23, 24, 28] 4 27 | - 'carryingBackpack', 'carryingOther', 'carryingMessengerBag', 'carryingNothing', 'carryingPlasticBags' [4, 5, 17, 20, 22] 5 28 | 29 | - 'personalLess30','personalLess45','personalLess60','personalLarger60', [0, 1, 2, 3] 4 30 | - 'personalMale', [16] 1 31 | 32 | permutation = [10, 18, 19, 30, 15, 7, 9, 11, 14, 21, 26, 29, 32, 33, 34, 6, 8, 12, 25, 27, 31, 13, 23, 24, 28, 4, 5, 17, 20, 22, 0, 1, 2, 3, 16] 33 | 34 | ##### not evaluated attributes 35 | - color: 36 | ['upperBodyBlack', 'upperBodyBlue', 'upperBodyBrown', 'upperBodyGreen', 'upperBodyGrey', 'upperBodyOrange', 'upperBodyPink', 'upperBodyPurple', 'upperBodyRed', 'upperBodyWhite', 'upperBodyYellow', 37 | 'lowerBodyBlack', 'lowerBodyBlue', 'lowerBodyBrown', 'lowerBodyGreen', 'lowerBodyGrey', 'lowerBodyOrange', 'lowerBodyPink', 'lowerBodyPurple', 'lowerBodyRed', 'lowerBodyWhite', 'lowerBodyYellow', 38 | 'hairBlack', 'hairBlue', 'hairBrown', 'hairGreen', 'hairGrey', 'hairOrange', 'hairPink', 'hairPurple', 'hairRed', 'hairWhite', 'hairYellow', 39 | 'footwearBlack', 'footwearBlue', 'footwearBrown', 'footwearGreen', 'footwearGrey', 'footwearOrange', 'footwearPink', 'footwearPurple', 'footwearRed', 'footwearWhite', 'footwearYellow'] 40 | - extra: 41 | ['accessoryHeadphone', 'personalLess15', 'carryingBabyBuggy', 'hairBald', 'footwearBoots', 'lowerBodyCapri', 'carryingShoppingTro', 'carryingUmbrella', 'personalFemale', 'carryingFolder', 'accessoryHairBand', 42 | 'lowerBodyHotPants', 'accessoryKerchief', 'lowerBodyLongSkirt', 'upperBodyLongSleeve', 'lowerBodyPlaid', 'lowerBodyThinStripes', 'carryingLuggageCase', 'upperBodyNoSleeve', 'hairShort', 'footwearStocking', 43 | 'upperBodySuit', 'carryingSuitcase', 'lowerBodySuits', 'upperBodySweater', 'upperBodyThickStripes'] 44 | 45 | 46 | ### PA100K (26) 47 | num_in_group = [2, 6, 6, 1, 4, 7] 48 | 49 | - 'Hat','Glasses', [7,8] 2 50 | - 'ShortSleeve','LongSleeve','UpperStride','UpperLogo','UpperPlaid','UpperSplice', [13,14,15,16,17,18] 6 51 | - 'LowerStripe','LowerPattern','LongCoat','Trousers','Shorts','Skirt&Dress', [19,20,21,22,23,24] 6 52 | - 'boots' [25] 1 53 | - 'HandBag','ShoulderBag','Backpack','HoldObjectsInFront', [9,10,11,12] 4 54 | 55 | - 'AgeOver60','Age18-60','AgeLess18', [1,2,3] 3 56 | - 'Female' [0] 1 57 | - 'Front','Side','Back', [4,5,6] 3 58 | 59 | permutation = [7,8,13,14,15,16,17,18,19,20,21,22,23,24,25,9,10,11,12,1,2,3,0,4,5,6] 60 | 61 | ### RAPv1 (51) 62 | 63 | num_ingroup = [6, 9, 6, 5, 8, 17] 64 | 65 | - head 6:'hs-BaldHead','hs-LongHair','hs-BlackHair','hs-Hat','hs-Glasses','hs-Muffler', [9, 10, 11, 12, 13, 14,] 66 | - upper body 9:'ub-Shirt','ub-Sweater','ub-Vest','ub-TShirt','ub-Cotton','ub-Jacket','ub-SuitUp','ub-Tight','ub-ShortSleeve',[15, 16, 17, 18, 19, 20, 21, 22, 23,] 67 | - lower body 6:'lb-LongTrousers','lb-Skirt','lb-ShortSkirt','lb-Dress','lb-Jeans','lb-TightTrousers', [24, 25,26, 27, 28, 29,] 68 | - footwear 5:'shoes-Leather','shoes-Sport','shoes-Boots','shoes-Cloth','shoes-Casual', [30, 31, 32, 33, 34,] 69 | - accessory 8 [35, 36, 37, 38, 39, 40, 41, 42] : 70 | 'attach-Backpack','attach-SingleShoulderBag','attach-HandBag','attach-Box','attach-PlasticBag','attach-PaperBag','attach-HandTrunk','attach-Other', 71 | 72 | - age 3:'AgeLess16','Age17-30','Age31-45', 1:4 [1, 2, 3,] 73 | - gender 1:'Female', 0 [0,] 74 | - body shape 3:'BodyFat','BodyNormal','BodyThin',4:7 [4, 5, 6,] 75 | - role 2:'Customer','Clerk', 7:9 [ 7, 8,] 76 | - action 8:'action-Calling','action-Talking','action-Gathering','action-Holding','action-Pusing','action-Pulling','action-CarrybyArm','action-CarrybyHand' 77 | [43, 44, 45, 46, 47, 48, 49, 50] 78 | 79 | permutation = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 80 | 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 1, 2, 3, 0, 4, 5, 6, 7, 8, 43, 44, 45, 46, 81 | 47, 48, 49, 50] 82 | 83 | - color: 29 84 | ['up-Black', 'up-White', 'up-Gray', 'up-Red', 'up-Green', 'up-Blue', 'up-Yellow', 'up-Brown', 'up-Purple', 'up-Pink', 'up-Orange', 'up-Mixture', 85 | 'low-Black', 'low-White', 'low-Gray', 'low-Red', 'low-Green', 'low-Blue', 'low-Yellow', 'low-Mixture', 86 | 'shoes-Black', 'shoes-White', 'shoes-Gray', 'shoes-Red', 'shoes-Green', 'shoes-Blue', 'shoes-Yellow', 'shoes-Brown', 'shoes-Mixture'] 87 | 88 | - extra: 12 89 | ['faceFront', 'faceBack', 'faceLeft', 'faceRight', 90 | 'occlusionLeft', 'occlusionRight', 'occlusionUp', 'occlusionDown', 'occlusion-Environment', 'occlusion-Attachment', 'occlusion-Person', 'occlusion-Other'] 91 | 92 | ### RAPv2 (54) 93 | num_ingroup = [5, 10, 6, 6, 8, 19] 94 | 95 | - head 5:'hs-BaldHead', 'hs-LongHair', 'hs-BlackHair', 'hs-Hat', 'hs-Glasses', [10,11,12,13,14] 96 | - upper body 10:'ub-Shirt','ub-Sweater','ub-Vest','ub-TShirt','ub-Cotton','ub-Jacket','ub-SuitUp','ub-Tight','ub-ShortSleeve','ub-Others' 97 | [15, 16, 17, 18, 19, 20, 21, 22, 23, 24] 98 | - lower body 6:'lb-LongTrousers','lb-Skirt','lb-ShortSkirt','lb-Dress','lb-Jeans','lb-TightTrousers', [25 ,26, 27, 28, 29, 30] 99 | - footwear 6:'shoes-Leather', 'shoes-Sports', 'shoes-Boots', 'shoes-Cloth', 'shoes-Casual', 'shoes-Other', [31, 32, 33, 34, 35, 36] 100 | - accessory 8 [37, 38, 39, 40, 41, 42, 43, 44] : 101 | 'attachment-Backpack','attachment-ShoulderBag','attachment-HandBag','attachment-Box','attachment-PlasticBag','attachment-PaperBag','attachment-HandTrunk','attachment-Other' 102 | 103 | - age 4:'AgeLess16', 'Age17-30', 'Age31-45', 'Age46-60', [1, 2, 3, 4] 104 | - gender 1:'Female', [0,] 105 | - body shape 3:'BodyFat','BodyNormal','BodyThin',4:7 [5, 6, 7] 106 | - role 2:'Customer','Employee', [ 8, 9,] 107 | - action 9:'action-Calling','action-Talking','action-Gathering','action-Holding','action-Pushing','action-Pulling','action-CarryingByArm','action-CarryingByHand','action-Other' 108 | [45, 46, 47, 48, 49, 50, 51, 52, 53] 109 | 110 | permutation = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 111 | 36, 37, 38, 39, 40, 41, 42, 43, 44, 1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 45, 46, 47, 48, 49, 50, 51, 52, 53] 112 | -------------------------------------------------------------------------------- /SOLIDER/demo_PETA_prcc.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import os 4 | os.environ['CUDA_VISIBLE_DEVICES'] = '0' 5 | 6 | from dataset.augmentation import get_transform 7 | from models.model_factory import build_backbone, build_classifier 8 | 9 | import torch 10 | import os.path as osp 11 | from PIL import Image 12 | from configs import cfg, update_config 13 | 14 | from models.base_block import FeatClassifier 15 | from tools.function import get_model_log_path, get_reload_weight 16 | from tools.utils import set_seed, str2bool 17 | set_seed(605) 18 | 19 | clas_name = ['accessoryHat','accessoryMuffler','accessoryNothing','accessorySunglasses','hairLong' , 20 | 'upperBodyCasual', 'upperBodyFormal', 'upperBodyJacket', 'upperBodyLogo', 'upperBodyPlaid', 'upperBodyShortSleeve', 21 | 'upperBodyThinStripes', 'upperBodyTshirt','upperBodyOther','upperBodyVNeck', 22 | 'lowerBodyCasual', 'lowerBodyFormal', 'lowerBodyJeans', 'lowerBodyShorts', 'lowerBodyShortSkirt','lowerBodyTrousers', 23 | 'footwearLeatherShoes', 'footwearSandals', 'footwearShoes', 'footwearSneaker' , 24 | 'carryingBackpack', 'carryingOther', 'carryingMessengerBag', 'carryingNothing', 'carryingPlasticBags' , 25 | 'personalLess30','personalLess45','personalLess60','personalLarger60','personalMale', 26 | 'upperBodyBlack', 'upperBodyBlue', 'upperBodyBrown', 'upperBodyGreen', 'upperBodyGrey', 'upperBodyOrange', 27 | 'upperBodyPink', 'upperBodyPurple', 'upperBodyRed', 'upperBodyWhite', 'upperBodyYellow', 'lowerBodyBlack', 28 | 'lowerBodyBlue', 'lowerBodyBrown', 'lowerBodyGreen', 'lowerBodyGrey', 'lowerBodyOrange', 'lowerBodyPink', 29 | 'lowerBodyPurple', 'lowerBodyRed', 'lowerBodyWhite', 'lowerBodyYellow', 'hairBlack', 'hairBlue', 'hairBrown', 30 | 'hairGreen', 'hairGrey', 'hairOrange', 'hairPink', 'hairPurple', 'hairRed', 'hairWhite', 'hairYellow', 'footwearBlack', 31 | 'footwearBlue', 'footwearBrown', 'footwearGreen', 'footwearGrey', 'footwearOrange', 'footwearPink', 'footwearPurple', 32 | 'footwearRed', 'footwearWhite', 'footwearYellow','accessoryHeadphone', 'personalLess15', 'carryingBabyBuggy', 'hairBald', 33 | 'footwearBoots', 'lowerBodyCapri', 'carryingShoppingTro', 'carryingUmbrella', 'personalFemale', 'carryingFolder', 34 | 'accessoryHairBand', 'lowerBodyHotPants', 'accessoryKerchief', 'lowerBodyLongSkirt', 'upperBodyLongSleeve', 35 | 'lowerBodyPlaid', 'lowerBodyThinStripes', 'carryingLuggageCase', 'upperBodyNoSleeve', 'hairShort', 'footwearStocking', 36 | 'upperBodySuit', 'carryingSuitcase', 'lowerBodySuits', 'upperBodySweater', 'upperBodyThickStripes'] 37 | 38 | 39 | def main(cfg, args): 40 | exp_dir = os.path.join('exp_result', cfg.DATASET.NAME) 41 | model_dir, log_dir = get_model_log_path(exp_dir, cfg.NAME) 42 | 43 | train_tsfm, valid_tsfm = get_transform(cfg) 44 | print(valid_tsfm) 45 | 46 | backbone, c_output = build_backbone(cfg.BACKBONE.TYPE, cfg.BACKBONE.MULTISCALE) 47 | 48 | 49 | classifier = build_classifier(cfg.CLASSIFIER.NAME)( 50 | # nattr=79, 51 | nattr=105, 52 | c_in=c_output, 53 | bn=cfg.CLASSIFIER.BN, 54 | pool=cfg.CLASSIFIER.POOLING, 55 | scale =cfg.CLASSIFIER.SCALE 56 | ) 57 | 58 | model = FeatClassifier(backbone, classifier) 59 | 60 | if torch.cuda.is_available(): 61 | model = torch.nn.DataParallel(model).cuda() 62 | 63 | model = get_reload_weight(model_dir, model, pth='') # Change weights path 64 | model.eval() 65 | 66 | with torch.no_grad(): 67 | for name in os.listdir(args.test_img+'/rgb'): 68 | if name == 'train' or name == 'val': 69 | pdirs = glob.glob(osp.join(args.test_img+'/rgb/'+name, '*')) 70 | pdirs.sort() 71 | for pdir in pdirs: 72 | pid = int(osp.basename(pdir)) 73 | img_dirs = glob.glob(osp.join(pdir, '*.jpg')) 74 | for img_dir in img_dirs: 75 | img = Image.open(img_dir) 76 | img = img.convert("RGB") 77 | img = valid_tsfm(img).cuda() 78 | img = img.view(1, *img.size()) 79 | valid_logits, attns = model(img) 80 | valid_probs = torch.sigmoid(valid_logits[0]).cpu().numpy() 81 | valid_probs = valid_probs[0]>0.5 82 | for i, val in enumerate(valid_probs): 83 | if val: 84 | with open(args.test_img + '/' + 'PAR_PETA_105.txt', 'a', 85 | encoding='utf-8') as f: 86 | f.write(img_dir+' '+ str(i)+' '+ str(1) + '\n') 87 | else: 88 | with open(args.test_img + '/' + 'PAR_PETA_105.txt', 'a', 89 | encoding='utf-8') as f: 90 | f.write(img_dir+' '+ str(i)+' '+ str(0) + '\n') 91 | else: 92 | for cam in ['A', 'B', 'C']: 93 | pdirs = glob.glob(osp.join(args.test_img+'/rgb/test', cam, '*')) 94 | for pdir in pdirs: 95 | pid = int(osp.basename(pdir)) 96 | img_dirs = glob.glob(osp.join(pdir, '*.jpg')) 97 | for img_dir in img_dirs: 98 | img = Image.open(img_dir) 99 | img = img.convert("RGB") 100 | img = valid_tsfm(img).cuda() 101 | img = img.view(1, *img.size()) 102 | valid_logits, attns = model(img) 103 | valid_probs = torch.sigmoid(valid_logits[0]).cpu().numpy() 104 | valid_probs = valid_probs[0]>0.5 105 | for i, val in enumerate(valid_probs): 106 | if val: 107 | with open(args.test_img + '/' + 'PAR_PETA_105.txt', 'a', 108 | encoding='utf-8') as f: 109 | f.write(img_dir +' '+ str(i) +' '+ str(1) + '\n') 110 | else: 111 | with open(args.test_img + '/' + 'PAR_PETA_105.txt', 'a', 112 | encoding='utf-8') as f: 113 | f.write(img_dir +' '+ str(i) +' '+ str(0) + '\n') 114 | 115 | def argument_parser(): 116 | parser = argparse.ArgumentParser(description="attribute recognition", 117 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 118 | parser.add_argument( 119 | "--test_img", help="test images", type=str, 120 | default="../prcc", 121 | ) 122 | parser.add_argument( 123 | "--cfg", help="decide which cfg to use", type=str,default='../MADE/SOLIDER/configs/peta_zs.yaml' 124 | ) 125 | parser.add_argument("--debug", type=str2bool, default="true") 126 | 127 | 128 | args = parser.parse_args() 129 | 130 | return args 131 | 132 | if __name__ == '__main__': 133 | args = argument_parser() 134 | update_config(cfg, args) 135 | 136 | main(cfg, args) --------------------------------------------------------------------------------