├── .DS_Store ├── GraphReID ├── .DS_Store ├── DistributedTrainSYSU30k │ └── README.md ├── EvaluatingYourOwnMethod │ ├── README.md │ ├── model.py │ └── test_sysu_combine.py ├── UnaryTerm_Only │ ├── .DS_Store │ ├── README.md │ ├── model.py │ ├── myImage.py │ ├── mySampler.py │ ├── mySelect.py │ ├── random_erasing.py │ ├── train_weak.py │ └── work_dirs │ │ └── readme.md └── UnaryTerm_PairwiseTerm_WeaklyTripletLoss │ ├── .DS_Store │ ├── README.md │ ├── __init__.py │ ├── data │ ├── .DS_Store │ ├── __init__.py │ ├── common.py │ ├── cuhk03_folder.py │ ├── market1501.py │ ├── market1501_folder.py │ ├── msmt17_folder.py │ └── sampler.py │ ├── demo.sh │ ├── loss │ ├── .DS_Store │ ├── CrossEntropyLoss.py │ ├── __init__.py │ └── triplet.py │ ├── main.py │ ├── model │ ├── .DS_Store │ ├── __init__.py │ └── mgn.py │ ├── option.py │ ├── requirements.txt │ ├── trainer.py │ ├── utils │ ├── .DS_Store │ ├── functions.py │ ├── n_adam.py │ ├── nadam.py │ ├── random_erasing.py │ ├── re_ranking.py │ └── utility.py │ └── work_dirs │ └── readme.md ├── README.md ├── Self-Supervised-ReID ├── README.md ├── resnet.py └── test_sysu_combine.py ├── Using_SYSU30k_Test_Set ├── README.md ├── model.py └── test_sysu_combine.py ├── Using_SYSU30k_Train_Set ├── .DS_Store ├── DistributedTrainSYSU30k │ └── README.md └── README.md ├── sysu-30k-example.png └── sysu-30k-statistics.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/.DS_Store -------------------------------------------------------------------------------- /GraphReID/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/.DS_Store -------------------------------------------------------------------------------- /GraphReID/DistributedTrainSYSU30k/README.md: -------------------------------------------------------------------------------- 1 | 2 | The distributed training code will be released here. -------------------------------------------------------------------------------- /GraphReID/EvaluatingYourOwnMethod/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Download the test set 4 | 5 | | Dataset | Link to download | baidu pan code | 6 | |:------------------:|:------------------:|:------------------:| 7 | | sysu_test_set_all.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 8 | 9 | 10 | ## Data organization 11 | 12 | At last, the folder looks like: 13 | 14 | The folder looks like: 15 | 16 | ``` 17 | │ ├── sysu_test_set_all 18 | │ | ├── gallery 19 | │ | | ├── 000028311 20 | │ | | | ├── 000028311_c1_1.jpg 21 | │ | | ├── 000028312 22 | │ | | | ├── 000028312_c1_1.jpg 23 | │ | | ├── 000028313 24 | │ | | | ├── 000028313_c1_1.jpg 25 | │ | | ├── 000028314 26 | │ | | | ├── 000028314_c1_1.jpg 27 | │ | | ├── ... 28 | │ | | | ├── ... 29 | │ | | ├── 000029309 30 | │ | | | ├── 000029309_c1_1.jpg 31 | │ | | ├── 000029310 32 | │ | | | ├── 000029310_c1_1.jpg 33 | │ | | ├── 0000others 34 | │ | | | ├── 0000others_c1_1.jpg 35 | │ | | | ├── ... 36 | │ | | | ├── ... 37 | │ | ├── query 38 | │ | | ├── 000028311 39 | │ | | | ├── 000028311_c2_2.jpg 40 | │ | | ├── 000028312 41 | │ | | | ├── 000028312_c2_2.jpg 42 | │ | | ├── 000028313 43 | │ | | | ├── 000028313_c2_2.jpg 44 | │ | | ├── 000028314 45 | │ | | | ├── 000028314_c2_2.jpg 46 | │ | | ├── ... 47 | │ | | | ├── ... 48 | │ | | ├── 000029309 49 | │ | | | ├── 000029309_c2_2.jpg 50 | │ | | ├── 000029310 51 | │ | | | ├── 000029310_c2_2.jpg 52 | ``` 53 | 54 | We fix the train/test partitioning. In the test set, we choose 1,000 images belonging to 1,000 different person IDs to form the query set. As the scalability is vital for the practicability of Re-ID systems, we propose to challenge a Re-ID model's scalability by providing a gallery set containing a vast volume of distractors for validation. Specifically, for each probe, there is only one matching person image as the correct answer in the gallery. At the same time, there are 478,730 mismatching person images as the wrong answer in the gallery. Thus, the evaluation protocol is to search for a needle in the ocean, just like the police search a massive amount of videos for a criminal. We use the rank-1 accuracy as the evaluation metric. 55 | 56 | 57 | 58 | # For a fair evaluation, please refer to the evaluation code in Using_SYSU30k_Test_Set/test_sysu_combine.py 59 | -------------------------------------------------------------------------------- /GraphReID/EvaluatingYourOwnMethod/model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from torch.nn import init 4 | from torchvision import models 5 | from torch.autograd import Variable 6 | 7 | ###################################################################### 8 | def weights_init_kaiming(m): 9 | classname = m.__class__.__name__ 10 | # print(classname) 11 | if classname.find('Conv') != -1: 12 | init.kaiming_normal(m.weight.data, a=0, mode='fan_in') 13 | elif classname.find('Linear') != -1: 14 | init.kaiming_normal(m.weight.data, a=0, mode='fan_out') 15 | init.constant(m.bias.data, 0.0) 16 | elif classname.find('BatchNorm1d') != -1: 17 | init.normal(m.weight.data, 1.0, 0.02) 18 | init.constant(m.bias.data, 0.0) 19 | 20 | def weights_init_classifier(m): 21 | classname = m.__class__.__name__ 22 | if classname.find('Linear') != -1: 23 | init.normal(m.weight.data, std=0.001) 24 | init.constant(m.bias.data, 0.0) 25 | 26 | # Defines the new fc layer and classification layer 27 | # |--Linear--|--bn--|--relu--|--Linear--| 28 | class ClassBlock(nn.Module): 29 | def __init__(self, input_dim, class_num, dropout=True, relu=True, num_bottleneck=512): 30 | super(ClassBlock, self).__init__() 31 | #add_block = [] 32 | #add_block += [nn.Linear(input_dim, num_bottleneck)] 33 | #add_block += [nn.BatchNorm1d(num_bottleneck)] 34 | #if relu: 35 | # add_block += [nn.LeakyReLU(0.1)] 36 | #if dropout: 37 | # add_block += [nn.Dropout(p=0.5)] 38 | #add_block = nn.Sequential(*add_block) 39 | #add_block.apply(weights_init_kaiming) 40 | 41 | classifier = [] 42 | classifier += [nn.Linear(2048, class_num)] 43 | classifier = nn.Sequential(*classifier) 44 | classifier.apply(weights_init_classifier) 45 | 46 | #self.add_block = add_block 47 | self.classifier = classifier 48 | def forward(self, x): 49 | #x = self.add_block(x) 50 | x = self.classifier(x) 51 | return x 52 | 53 | # Define the ResNet50-based Model 54 | class ft_net(nn.Module): 55 | 56 | def __init__(self, class_num ): 57 | super(ft_net, self).__init__() 58 | model_ft = models.resnet50(pretrained=True) 59 | # avg pooling to global pooling 60 | model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1)) 61 | self.model = model_ft 62 | self.classifier = ClassBlock(2048, class_num) 63 | 64 | def forward(self, x): 65 | x = self.model.conv1(x) 66 | x = self.model.bn1(x) 67 | x = self.model.relu(x) 68 | x = self.model.maxpool(x) 69 | x = self.model.layer1(x) 70 | x = self.model.layer2(x) 71 | x = self.model.layer3(x) 72 | x = self.model.layer4(x) 73 | x = self.model.avgpool(x) 74 | x = torch.squeeze(x) 75 | x = self.classifier(x) 76 | return x 77 | 78 | # Define the DenseNet121-based Model 79 | class ft_net_dense(nn.Module): 80 | 81 | def __init__(self, class_num ): 82 | super().__init__() 83 | model_ft = models.densenet121(pretrained=True) 84 | model_ft.features.avgpool = nn.AdaptiveAvgPool2d((1,1)) 85 | model_ft.fc = nn.Sequential() 86 | self.model = model_ft 87 | # For DenseNet, the feature dim is 1024 88 | self.classifier = ClassBlock(1024, class_num) 89 | 90 | def forward(self, x): 91 | x = self.model.features(x) 92 | x = torch.squeeze(x) 93 | x = self.classifier(x) 94 | return x 95 | 96 | # Define the ResNet50-based Model (Middle-Concat) 97 | # In the spirit of "The Devil is in the Middle: Exploiting Mid-level Representations for Cross-Domain Instance Matching." Yu, Qian, et al. arXiv:1711.08106 (2017). 98 | class ft_net_middle(nn.Module): 99 | 100 | def __init__(self, class_num ): 101 | super(ft_net_middle, self).__init__() 102 | model_ft = models.resnet50(pretrained=True) 103 | # avg pooling to global pooling 104 | model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1)) 105 | self.model = model_ft 106 | self.classifier = ClassBlock(2048+1024, class_num) 107 | 108 | def forward(self, x): 109 | x = self.model.conv1(x) 110 | x = self.model.bn1(x) 111 | x = self.model.relu(x) 112 | x = self.model.maxpool(x) 113 | x = self.model.layer1(x) 114 | x = self.model.layer2(x) 115 | x = self.model.layer3(x) 116 | # x0 n*1024*1*1 117 | x0 = self.model.avgpool(x) 118 | x = self.model.layer4(x) 119 | # x1 n*2048*1*1 120 | x1 = self.model.avgpool(x) 121 | x = torch.cat((x0,x1),1) 122 | x = torch.squeeze(x) 123 | x = self.classifier(x) 124 | return x 125 | 126 | # Part Model proposed in Yifan Sun etal. (2018) 127 | class PCB(nn.Module): 128 | def __init__(self, class_num ): 129 | super(PCB, self).__init__() 130 | 131 | self.part = 6 # We cut the pool5 to 6 parts 132 | model_ft = models.resnet50(pretrained=True) 133 | self.model = model_ft 134 | self.avgpool = nn.AdaptiveAvgPool2d((self.part,1)) 135 | self.dropout = nn.Dropout(p=0.5) 136 | # remove the final downsample 137 | self.model.layer4[0].downsample[0].stride = (1,1) 138 | self.model.layer4[0].conv2.stride = (1,1) 139 | # define 6 classifiers 140 | for i in range(self.part): 141 | name = 'classifier'+str(i) 142 | setattr(self, name, ClassBlock(2048, class_num, True, False, 256)) 143 | 144 | def forward(self, x): 145 | x = self.model.conv1(x) 146 | x = self.model.bn1(x) 147 | x = self.model.relu(x) 148 | x = self.model.maxpool(x) 149 | 150 | x = self.model.layer1(x) 151 | x = self.model.layer2(x) 152 | x = self.model.layer3(x) 153 | x = self.model.layer4(x) 154 | x = self.avgpool(x) 155 | x = self.dropout(x) 156 | part = {} 157 | predict = {} 158 | # get six part feature batchsize*2048*6 159 | for i in range(self.part): 160 | part[i] = torch.squeeze(x[:,:,i]) 161 | name = 'classifier'+str(i) 162 | c = getattr(self,name) 163 | predict[i] = c(part[i]) 164 | 165 | # sum prediction 166 | #y = predict[0] 167 | #for i in range(self.part-1): 168 | # y += predict[i+1] 169 | y = [] 170 | for i in range(self.part): 171 | y.append(predict[i]) 172 | return y 173 | 174 | class PCB_test(nn.Module): 175 | def __init__(self,model): 176 | super(PCB_test,self).__init__() 177 | self.part = 6 178 | self.model = model.model 179 | self.avgpool = nn.AdaptiveAvgPool2d((self.part,1)) 180 | # remove the final downsample 181 | self.model.layer4[0].downsample[0].stride = (1,1) 182 | self.model.layer4[0].conv2.stride = (1,1) 183 | 184 | def forward(self, x): 185 | x = self.model.conv1(x) 186 | x = self.model.bn1(x) 187 | x = self.model.relu(x) 188 | x = self.model.maxpool(x) 189 | 190 | x = self.model.layer1(x) 191 | x = self.model.layer2(x) 192 | x = self.model.layer3(x) 193 | x = self.model.layer4(x) 194 | x = self.avgpool(x) 195 | y = x.view(x.size(0),x.size(1),x.size(2)) 196 | return y 197 | 198 | # debug model structure 199 | #net = ft_net(767) 200 | #net = ft_net_dense(767) 201 | #print(net) 202 | #input = Variable(torch.FloatTensor(8, 3, 224, 224)) 203 | #output = net(input) 204 | #print('net output size:') 205 | #print(output.shape) 206 | -------------------------------------------------------------------------------- /GraphReID/EvaluatingYourOwnMethod/test_sysu_combine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import print_function, division 4 | 5 | import argparse 6 | import torch 7 | import torch.nn as nn 8 | import torch.optim as optim 9 | from torch.optim import lr_scheduler 10 | from torch.autograd import Variable 11 | import numpy as np 12 | import torchvision 13 | from torchvision import datasets, models, transforms 14 | import time 15 | import os 16 | import scipy.io 17 | from model import ft_net, ft_net_dense, PCB, PCB_test 18 | 19 | ###################################################################### 20 | # Options 21 | # -------- 22 | os.environ['CUDA_VISIBLE_DEVICES'] = '7' 23 | parser = argparse.ArgumentParser(description='Training') 24 | parser.add_argument('--gpu_ids',default='0', type=str,help='gpu_ids: e.g. 0 0,1,2 0,2') 25 | parser.add_argument('--which_epoch',default='last', type=str, help='0,1,2,3...or last') 26 | parser.add_argument('--test_dir',default='/home/zzd/Market/pytorch',type=str, help='./test_data') 27 | parser.add_argument('--name', default='ft_ResNet50', type=str, help='save model path') 28 | parser.add_argument('--batchsize', default=100, type=int, help='batchsize') 29 | parser.add_argument('--use_dense', action='store_true', help='use densenet121' ) 30 | parser.add_argument('--PCB', action='store_true', help='use PCB' ) 31 | 32 | opt = parser.parse_args() 33 | 34 | str_ids = opt.gpu_ids.split(',') 35 | #which_epoch = opt.which_epoch 36 | name = opt.name 37 | test_dir = opt.test_dir 38 | 39 | gpu_ids = [] 40 | for str_id in str_ids: 41 | id = int(str_id) 42 | if id >=0: 43 | gpu_ids.append(id) 44 | 45 | # set gpu ids 46 | if len(gpu_ids)>0: 47 | torch.cuda.set_device(gpu_ids[0]) 48 | 49 | ###################################################################### 50 | # Load Data 51 | # --------- 52 | # 53 | # We will use torchvision and torch.utils.data packages for loading the 54 | # data. 55 | # 56 | data_transforms = transforms.Compose([ 57 | transforms.Resize((256,128), interpolation=3), 58 | # transforms.CenterCrop((256,128)), 59 | transforms.ToTensor(), 60 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 61 | ############### Ten Crop 62 | #transforms.TenCrop(224), 63 | #transforms.Lambda(lambda crops: torch.stack( 64 | # [transforms.ToTensor()(crop) 65 | # for crop in crops] 66 | # )), 67 | #transforms.Lambda(lambda crops: torch.stack( 68 | # [transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(crop) 69 | # for crop in crops] 70 | # )) 71 | ]) 72 | 73 | if opt.PCB: 74 | data_transforms = transforms.Compose([ 75 | transforms.Resize((384,192), interpolation=3), 76 | transforms.ToTensor(), 77 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 78 | ]) 79 | 80 | 81 | data_dir = test_dir 82 | image_datasets = {x: datasets.ImageFolder( os.path.join(data_dir,x) ,data_transforms) for x in ['gallery','query']} 83 | dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=opt.batchsize, 84 | shuffle=False, num_workers=16) for x in ['gallery','query']} 85 | 86 | class_names = image_datasets['query'].classes 87 | use_gpu = torch.cuda.is_available() 88 | 89 | ###################################################################### 90 | # Load model 91 | #--------------------------- 92 | def load_network(network): 93 | save_path = os.path.join(name,'net_%s.pth'%opt.which_epoch) 94 | network.load_state_dict(torch.load(save_path)) 95 | return network 96 | 97 | 98 | ###################################################################### 99 | # Extract feature 100 | # ---------------------- 101 | # 102 | # Extract feature from a trained model. 103 | # 104 | def fliplr(img): 105 | '''flip horizontal''' 106 | inv_idx = torch.arange(img.size(3)-1,-1,-1).long() # N x C x H x W 107 | img_flip = img.index_select(3,inv_idx) 108 | return img_flip 109 | 110 | def extract_feature(model,dataloaders): 111 | features = torch.FloatTensor() 112 | count = 0 113 | for data in dataloaders: 114 | img, label = data 115 | n, c, h, w = img.size() 116 | count += n 117 | print(count) 118 | if opt.use_dense: 119 | ff = torch.FloatTensor(n,1024).zero_() 120 | else: 121 | ff = torch.FloatTensor(n,28310).zero_() 122 | if opt.PCB: 123 | ff = torch.FloatTensor(n,2048,6).zero_() # we have four parts 124 | for i in range(2): 125 | if(i==1): 126 | img = fliplr(img) 127 | input_img = Variable(img.cuda()) 128 | outputs = model(input_img) 129 | # # outputs = torch.nn.functional.softmax(outputs,-1) 130 | f = outputs.data.cpu() 131 | #f = torch.nn.functional.softmax(f) 132 | ff = ff+f 133 | #o1 = model(Variable(img.cuda())) 134 | #f1 = o1.data.cpu() 135 | #fnorm = torch.norm(f1, p=2, dim=1, keepdim=True) 136 | #f1 = f1.div(fnorm.expand_as(f1)) 137 | #img = fliplr(img) 138 | #o2 = model(Variable(img.cuda())) 139 | #f2 = o2.data.cpu() 140 | #fnorm = torch.norm(f2, p=2, dim=1, keepdim=True) 141 | #f2 = f2.div(fnorm.expand_as(f2)) 142 | #ff = torch.cat((f1, f2), 1) 143 | # norm feature 144 | if opt.PCB: 145 | # feature size (n,2048,4) 146 | fnorm = torch.norm(ff, p=2, dim=1, keepdim=True) 147 | ff = ff.div(fnorm.expand_as(ff)) 148 | ff = ff.view(ff.size(0), -1) 149 | else: 150 | fnorm = torch.norm(ff, p=2, dim=1, keepdim=True) 151 | ff = ff.div(fnorm.expand_as(ff)) 152 | #ff = ff 153 | 154 | features = torch.cat((features,ff), 0) 155 | return features 156 | 157 | def get_id(img_path): 158 | camera_id = [] 159 | labels = [] 160 | for path, v in img_path: 161 | filename = path.split('/')[-1] 162 | #label = filename[0:4] 163 | label = filename.split('_')[0] 164 | camera = filename.split('c')[1] 165 | if label[0:10]=='0000others': 166 | labels.append(-1000) 167 | else: 168 | labels.append(int(label)) 169 | camera_id.append(int(camera[0])) 170 | return camera_id, labels 171 | 172 | gallery_path = image_datasets['gallery'].imgs 173 | query_path = image_datasets['query'].imgs 174 | 175 | gallery_cam,gallery_label = get_id(gallery_path) 176 | query_cam,query_label = get_id(query_path) 177 | 178 | ###################################################################### 179 | # Load Collected data Trained model 180 | print('-------test-----------') 181 | if opt.use_dense: 182 | model_structure = ft_net_dense(28310) 183 | else: 184 | model_structure = ft_net(28310) 185 | 186 | if opt.PCB: 187 | model_structure = PCB(28310) 188 | 189 | model_structure = nn.DataParallel(model_structure) 190 | model = load_network(model_structure) 191 | 192 | # Remove the final fc layer and classifier layer 193 | #print(model) 194 | if not opt.PCB: 195 | model.module.model.fc = nn.Sequential() 196 | # model.classifier = nn.Sequential() 197 | else: 198 | model = PCB_test(model) 199 | 200 | print(model) 201 | 202 | # Change to test mode 203 | model = model.eval() 204 | if use_gpu: 205 | model = model.cuda() 206 | 207 | # Extract feature 208 | gallery_feature = extract_feature(model,dataloaders['gallery']) 209 | query_feature = extract_feature(model,dataloaders['query']) 210 | 211 | 212 | import scipy.io 213 | import torch 214 | import numpy as np 215 | import time 216 | 217 | ####################################################################### 218 | # Evaluate 219 | def evaluate(qf,ql,qc,gf,gl,gc): 220 | query = qf 221 | score = np.dot(gf,query) 222 | # predict index 223 | index = np.argsort(score) #from small to large 224 | index = index[::-1] 225 | #index = index[0:2000] 226 | # good index 227 | query_index = np.argwhere(gl==ql) 228 | camera_index = np.argwhere(gc==qc) 229 | 230 | good_index = np.setdiff1d(query_index, camera_index, assume_unique=True) 231 | junk_index1 = np.argwhere(gl==-1) 232 | junk_index2 = np.intersect1d(query_index, camera_index) 233 | junk_index = np.append(junk_index2, junk_index1) #.flatten()) 234 | 235 | CMC_tmp = compute_mAP(index, good_index, junk_index) 236 | return CMC_tmp 237 | 238 | 239 | def compute_mAP(index, good_index, junk_index): 240 | ap = 0 241 | cmc = torch.IntTensor(len(index)).zero_() 242 | if good_index.size==0: # if empty 243 | cmc[0] = -1 244 | return ap,cmc 245 | 246 | # remove junk_index 247 | mask = np.in1d(index, junk_index, invert=True) 248 | index = index[mask] 249 | 250 | # find good_index index 251 | ngood = len(good_index) 252 | mask = np.in1d(index, good_index) 253 | rows_good = np.argwhere(mask==True) 254 | rows_good = rows_good.flatten() 255 | 256 | cmc[rows_good[0]:] = 1 257 | for i in range(ngood): 258 | d_recall = 1.0/ngood 259 | precision = (i+1)*1.0/(rows_good[i]+1) 260 | if rows_good[i]!=0: 261 | old_precision = i*1.0/rows_good[i] 262 | else: 263 | old_precision=1.0 264 | ap = ap + d_recall*(old_precision + precision)/2 265 | 266 | return ap, cmc 267 | 268 | 269 | 270 | 271 | query_feature = query_feature.numpy() # result['query_f'] 272 | query_cam = np.asarray(query_cam) #result['query_cam'][0] 273 | query_label = np.asarray(query_label)#result['query_label'][0] 274 | gallery_feature = gallery_feature.numpy()#result['gallery_f'] 275 | gallery_cam = np.asarray(gallery_cam)#result['gallery_cam'][0] 276 | gallery_label = np.asarray(gallery_label)#result['gallery_label'][0] 277 | 278 | 279 | 280 | CMC = torch.IntTensor(len(gallery_label)).zero_() 281 | ap = 0.0 282 | #print(query_label) 283 | for i in range(len(query_label)): 284 | ap_tmp, CMC_tmp = evaluate(query_feature[i],query_label[i],query_cam[i],gallery_feature,gallery_label,gallery_cam) 285 | if CMC_tmp[0]==-1: 286 | continue 287 | CMC = CMC + CMC_tmp 288 | ap += ap_tmp 289 | print(i, CMC_tmp[0]) 290 | 291 | CMC = CMC.float() 292 | CMC = CMC/len(query_label) #average CMC 293 | print('Rank@1:%f Rank@5:%f Rank@10:%f mAP:%f'%(CMC[0],CMC[4],CMC[9],ap/len(query_label))) 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/UnaryTerm_Only/.DS_Store -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from torch.nn import init 4 | from torchvision import models 5 | from torch.autograd import Variable 6 | 7 | ###################################################################### 8 | def weights_init_kaiming(m): 9 | classname = m.__class__.__name__ 10 | # print(classname) 11 | if classname.find('Conv') != -1: 12 | init.kaiming_normal(m.weight.data, a=0, mode='fan_in') 13 | elif classname.find('Linear') != -1: 14 | init.kaiming_normal(m.weight.data, a=0, mode='fan_out') 15 | init.constant(m.bias.data, 0.0) 16 | elif classname.find('BatchNorm1d') != -1: 17 | init.normal(m.weight.data, 1.0, 0.02) 18 | init.constant(m.bias.data, 0.0) 19 | 20 | def weights_init_classifier(m): 21 | classname = m.__class__.__name__ 22 | if classname.find('Linear') != -1: 23 | init.normal(m.weight.data, std=0.001) 24 | init.constant(m.bias.data, 0.0) 25 | 26 | # Defines the new fc layer and classification layer 27 | # |--Linear--|--bn--|--relu--|--Linear--| 28 | class ClassBlock(nn.Module): 29 | def __init__(self, input_dim, class_num, dropout=True, relu=True, num_bottleneck=512): 30 | super(ClassBlock, self).__init__() 31 | #add_block = [] 32 | #add_block += [nn.Linear(input_dim, num_bottleneck)] 33 | #add_block += [nn.BatchNorm1d(num_bottleneck)] 34 | #if relu: 35 | # add_block += [nn.LeakyReLU(0.1)] 36 | #if dropout: 37 | # add_block += [nn.Dropout(p=0.5)] 38 | #add_block = nn.Sequential(*add_block) 39 | #add_block.apply(weights_init_kaiming) 40 | 41 | classifier = [] 42 | classifier += [nn.Linear(2048, class_num)] 43 | classifier = nn.Sequential(*classifier) 44 | classifier.apply(weights_init_classifier) 45 | 46 | #self.add_block = add_block 47 | self.classifier = classifier 48 | def forward(self, x): 49 | #x = self.add_block(x) 50 | x = self.classifier(x) 51 | return x 52 | 53 | # Define the ResNet50-based Model 54 | class ft_net(nn.Module): 55 | 56 | def __init__(self, class_num ): 57 | super(ft_net, self).__init__() 58 | model_ft = models.resnet50(pretrained=True) 59 | # avg pooling to global pooling 60 | model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1)) 61 | self.model = model_ft 62 | self.classifier = ClassBlock(2048, class_num) 63 | 64 | def forward(self, x): 65 | x = self.model.conv1(x) 66 | x = self.model.bn1(x) 67 | x = self.model.relu(x) 68 | x = self.model.maxpool(x) 69 | x = self.model.layer1(x) 70 | x = self.model.layer2(x) 71 | x = self.model.layer3(x) 72 | x = self.model.layer4(x) 73 | x = self.model.avgpool(x) 74 | x = torch.squeeze(x) 75 | x = self.classifier(x) 76 | return x 77 | 78 | # Define the DenseNet121-based Model 79 | class ft_net_dense(nn.Module): 80 | 81 | def __init__(self, class_num ): 82 | super().__init__() 83 | model_ft = models.densenet121(pretrained=True) 84 | model_ft.features.avgpool = nn.AdaptiveAvgPool2d((1,1)) 85 | model_ft.fc = nn.Sequential() 86 | self.model = model_ft 87 | # For DenseNet, the feature dim is 1024 88 | self.classifier = ClassBlock(1024, class_num) 89 | 90 | def forward(self, x): 91 | x = self.model.features(x) 92 | x = torch.squeeze(x) 93 | x = self.classifier(x) 94 | return x 95 | 96 | # Define the ResNet50-based Model (Middle-Concat) 97 | # In the spirit of "The Devil is in the Middle: Exploiting Mid-level Representations for Cross-Domain Instance Matching." Yu, Qian, et al. arXiv:1711.08106 (2017). 98 | class ft_net_middle(nn.Module): 99 | 100 | def __init__(self, class_num ): 101 | super(ft_net_middle, self).__init__() 102 | model_ft = models.resnet50(pretrained=True) 103 | # avg pooling to global pooling 104 | model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1)) 105 | self.model = model_ft 106 | self.classifier = ClassBlock(2048+1024, class_num) 107 | 108 | def forward(self, x): 109 | x = self.model.conv1(x) 110 | x = self.model.bn1(x) 111 | x = self.model.relu(x) 112 | x = self.model.maxpool(x) 113 | x = self.model.layer1(x) 114 | x = self.model.layer2(x) 115 | x = self.model.layer3(x) 116 | # x0 n*1024*1*1 117 | x0 = self.model.avgpool(x) 118 | x = self.model.layer4(x) 119 | # x1 n*2048*1*1 120 | x1 = self.model.avgpool(x) 121 | x = torch.cat((x0,x1),1) 122 | x = torch.squeeze(x) 123 | x = self.classifier(x) 124 | return x 125 | 126 | # Part Model proposed in Yifan Sun etal. (2018) 127 | class PCB(nn.Module): 128 | def __init__(self, class_num ): 129 | super(PCB, self).__init__() 130 | 131 | self.part = 6 # We cut the pool5 to 6 parts 132 | model_ft = models.resnet50(pretrained=True) 133 | self.model = model_ft 134 | self.avgpool = nn.AdaptiveAvgPool2d((self.part,1)) 135 | self.dropout = nn.Dropout(p=0.5) 136 | # remove the final downsample 137 | self.model.layer4[0].downsample[0].stride = (1,1) 138 | self.model.layer4[0].conv2.stride = (1,1) 139 | # define 6 classifiers 140 | for i in range(self.part): 141 | name = 'classifier'+str(i) 142 | setattr(self, name, ClassBlock(2048, class_num, True, False, 256)) 143 | 144 | def forward(self, x): 145 | x = self.model.conv1(x) 146 | x = self.model.bn1(x) 147 | x = self.model.relu(x) 148 | x = self.model.maxpool(x) 149 | 150 | x = self.model.layer1(x) 151 | x = self.model.layer2(x) 152 | x = self.model.layer3(x) 153 | x = self.model.layer4(x) 154 | x = self.avgpool(x) 155 | x = self.dropout(x) 156 | part = {} 157 | predict = {} 158 | # get six part feature batchsize*2048*6 159 | for i in range(self.part): 160 | part[i] = torch.squeeze(x[:,:,i]) 161 | name = 'classifier'+str(i) 162 | c = getattr(self,name) 163 | predict[i] = c(part[i]) 164 | 165 | # sum prediction 166 | #y = predict[0] 167 | #for i in range(self.part-1): 168 | # y += predict[i+1] 169 | y = [] 170 | for i in range(self.part): 171 | y.append(predict[i]) 172 | return y 173 | 174 | class PCB_test(nn.Module): 175 | def __init__(self,model): 176 | super(PCB_test,self).__init__() 177 | self.part = 6 178 | self.model = model.model 179 | self.avgpool = nn.AdaptiveAvgPool2d((self.part,1)) 180 | # remove the final downsample 181 | self.model.layer4[0].downsample[0].stride = (1,1) 182 | self.model.layer4[0].conv2.stride = (1,1) 183 | 184 | def forward(self, x): 185 | x = self.model.conv1(x) 186 | x = self.model.bn1(x) 187 | x = self.model.relu(x) 188 | x = self.model.maxpool(x) 189 | 190 | x = self.model.layer1(x) 191 | x = self.model.layer2(x) 192 | x = self.model.layer3(x) 193 | x = self.model.layer4(x) 194 | x = self.avgpool(x) 195 | y = x.view(x.size(0),x.size(1),x.size(2)) 196 | return y 197 | 198 | # debug model structure 199 | #net = ft_net(767) 200 | #net = ft_net_dense(767) 201 | #print(net) 202 | #input = Variable(torch.FloatTensor(8, 3, 224, 224)) 203 | #output = net(input) 204 | #print('net output size:') 205 | #print(output.shape) 206 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/myImage.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | 3 | from PIL import Image 4 | 5 | import os 6 | import os.path 7 | 8 | 9 | def has_file_allowed_extension(filename, extensions): 10 | """Checks if a file is an allowed extension. 11 | 12 | Args: 13 | filename (string): path to a file 14 | 15 | Returns: 16 | bool: True if the filename ends with a known image extension 17 | """ 18 | filename_lower = filename.lower() 19 | return any(filename_lower.endswith(ext) for ext in extensions) 20 | 21 | 22 | def find_classes(dir): 23 | classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))] 24 | # print(classes) 25 | classes.sort() 26 | class_to_idx = {classes[i]: i for i in range(len(classes))} 27 | return classes, class_to_idx 28 | 29 | 30 | def make_dataset(dir, class_to_idx, extensions): 31 | images = [] 32 | dir = os.path.expanduser(dir) 33 | for target in sorted(os.listdir(dir)): 34 | d = os.path.join(dir, target) 35 | if not os.path.isdir(d): 36 | continue 37 | 38 | for root, _, fnames in sorted(os.walk(d)): 39 | for fname in sorted(fnames): 40 | if has_file_allowed_extension(fname, extensions): 41 | path = os.path.join(root, fname) 42 | item = (path, class_to_idx[target]) 43 | images.append(item) 44 | 45 | return images 46 | 47 | 48 | class DatasetFolder(data.Dataset): 49 | """A generic data loader where the samples are arranged in this way: :: 50 | 51 | root/class_x/xxx.ext 52 | root/class_x/xxy.ext 53 | root/class_x/xxz.ext 54 | 55 | root/class_y/123.ext 56 | root/class_y/nsdf3.ext 57 | root/class_y/asd932_.ext 58 | 59 | Args: 60 | root (string): Root directory path. 61 | loader (callable): A function to load a sample given its path. 62 | extensions (list[string]): A list of allowed extensions. 63 | transform (callable, optional): A function/transform that takes in 64 | a sample and returns a transformed version. 65 | E.g, ``transforms.RandomCrop`` for images. 66 | target_transform (callable, optional): A function/transform that takes 67 | in the target and transforms it. 68 | 69 | Attributes: 70 | classes (list): List of the class names. 71 | class_to_idx (dict): Dict with items (class_name, class_index). 72 | samples (list): List of (sample path, class_index) tuples 73 | """ 74 | 75 | def __init__(self, root, loader, extensions, transform=None, target_transform=None): 76 | classes, class_to_idx = find_classes(root) 77 | samples = make_dataset(root, class_to_idx, extensions) 78 | print(len(samples), 'sample len aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') 79 | if len(samples) == 0: 80 | raise(RuntimeError("Found 0 files in subfolders of: " + root + "\n" 81 | "Supported extensions are: " + ",".join(extensions))) 82 | 83 | self.root = root 84 | self.loader = loader 85 | self.extensions = extensions 86 | 87 | self.classes = classes 88 | self.class_to_idx = class_to_idx 89 | self.samples = samples 90 | 91 | self.transform = transform 92 | self.target_transform = target_transform 93 | 94 | def __getitem__(self, index): 95 | """ 96 | Args: 97 | index (int): Index 98 | 99 | Returns: 100 | tuple: (sample, target) where target is class_index of the target class. 101 | """ 102 | # print(index) 103 | path, target = self.samples[index] 104 | sample = self.loader(path) 105 | if self.transform is not None: 106 | sample = self.transform(sample) 107 | if self.target_transform is not None: 108 | target = self.target_transform(target) 109 | 110 | return sample, target 111 | 112 | 113 | def __len__(self): 114 | return len(self.samples) 115 | 116 | def __repr__(self): 117 | fmt_str = 'Dataset ' + self.__class__.__name__ + '\n' 118 | fmt_str += ' Number of datapoints: {}\n'.format(self.__len__()) 119 | fmt_str += ' Root Location: {}\n'.format(self.root) 120 | tmp = ' Transforms (if any): ' 121 | fmt_str += '{0}{1}\n'.format(tmp, self.transform.__repr__().replace('\n', '\n' + ' ' * len(tmp))) 122 | tmp = ' Target Transforms (if any): ' 123 | fmt_str += '{0}{1}'.format(tmp, self.target_transform.__repr__().replace('\n', '\n' + ' ' * len(tmp))) 124 | return fmt_str 125 | 126 | 127 | 128 | IMG_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif', '.JPEG'] 129 | 130 | 131 | def pil_loader(path): 132 | # open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835) 133 | with open(path, 'rb') as f: 134 | img = Image.open(f) 135 | return img.convert('RGB') 136 | 137 | 138 | def accimage_loader(path): 139 | import accimage 140 | try: 141 | return accimage.Image(path) 142 | except IOError: 143 | # Potentially a decoding problem, fall back to PIL.Image 144 | return pil_loader(path) 145 | 146 | 147 | def default_loader(path): 148 | from torchvision import get_image_backend 149 | if get_image_backend() == 'accimage': 150 | return accimage_loader(path) 151 | else: 152 | return pil_loader(path) 153 | 154 | 155 | class ImageFolder(DatasetFolder): 156 | """A generic data loader where the images are arranged in this way: :: 157 | 158 | root/dog/xxx.png 159 | root/dog/xxy.png 160 | root/dog/xxz.png 161 | 162 | root/cat/123.png 163 | root/cat/nsdf3.png 164 | root/cat/asd932_.png 165 | 166 | Args: 167 | root (string): Root directory path. 168 | transform (callable, optional): A function/transform that takes in an PIL image 169 | and returns a transformed version. E.g, ``transforms.RandomCrop`` 170 | target_transform (callable, optional): A function/transform that takes in the 171 | target and transforms it. 172 | loader (callable, optional): A function to load an image given its path. 173 | 174 | Attributes: 175 | classes (list): List of the class names. 176 | class_to_idx (dict): Dict with items (class_name, class_index). 177 | imgs (list): List of (image path, class_index) tuples 178 | """ 179 | def __init__(self, root, transform=None, target_transform=None, 180 | loader=default_loader): 181 | super(ImageFolder, self).__init__(root, loader, IMG_EXTENSIONS, 182 | transform=transform, 183 | target_transform=target_transform) 184 | self.imgs = self.samples 185 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/mySampler.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.utils.data.sampler import Sampler 3 | import numpy as np 4 | import time 5 | import pickle 6 | 7 | class mySampler(Sampler): 8 | def __init__(self,data_source, batchsize, ids_num_per_batch): 9 | # batchsize=batchsize1/3 10 | self.data_source = data_source 11 | self.batchsize = batchsize 12 | self.iters_per_epoch = len(data_source)//batchsize 13 | # self.labels_list = labels_list 14 | self.ids_num_per_batch = ids_num_per_batch 15 | self.img_per_id = batchsize//ids_num_per_batch 16 | # get id dictionary (2d), each ids has xxx imgs 17 | # self.id_dic = {} 18 | # since = time.time() 19 | # for i in range(len(data_source)): 20 | # if i % 10000 == 0: 21 | # print(['processing ' + str(i) + ' images !!!!!!!!!']) 22 | # time_elapsed = time.time() - since 23 | # time_left = time_elapsed / (i+1) * len(data_source) 24 | # print('used time : {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60)) 25 | # print('left time : {:.0f}m {:.0f}s'.format(time_left // 60, time_left % 60)) 26 | # # if i % 1000000 == 0: 27 | # # self._save_obj(self.id_dic, 'id_dic_' + str(i)) 28 | # one_id = data_source[i][1] 29 | # self.id_dic.setdefault(one_id,[]) 30 | # self.id_dic[one_id].append(i) 31 | # # self._save_obj(self.id_dic, 'id_dic') 32 | self.id_dic = self._load_obj('id_dic') 33 | self.id_num = len(self.id_dic) 34 | # print(self.id_dic) 35 | def __len__(self): 36 | return self.iters_per_epoch*self.batchsize 37 | 38 | def _save_obj(self, obj, name ): 39 | with open('obj_sysu30k/'+ name + '.pkl', 'wb') as f: 40 | pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL) 41 | 42 | def _load_obj(self, name ): 43 | with open('obj_sysu30k/' + name + '.pkl', 'rb') as f: 44 | return pickle.load(f) 45 | def _get_index(self, theSet, theNumber): 46 | if len(theSet) >= theNumber: 47 | tt = np.random.choice(theSet, size=theNumber, replace=False) 48 | else: 49 | tt = np.random.choice(theSet, size=theNumber, replace=True) 50 | return tt 51 | def __iter__(self): 52 | # select ids_num_per_batch 53 | ret = [] 54 | mode = np.asarray([0,1,2]) 55 | mode = np.tile(mode, [self.ids_num_per_batch // 3]) 56 | mode = np.expand_dims(mode, 0) 57 | mode = np.tile(mode, [self.iters_per_epoch, 1]) 58 | for i in range(self.iters_per_epoch): 59 | t = torch.randperm(self.id_num) 60 | for j in range(self.ids_num_per_batch): 61 | idx = t[j] 62 | idx_l1 = max(t[j]-1, 0) 63 | idx_l2 = max(t[j]-2, 0) 64 | idx_r1 = min(t[j]+1, self.id_num-1) 65 | idx_r2 = min(t[j]+2, self.id_num-1) 66 | if mode[i][j] == 0: 67 | tt = self._get_index(self.id_dic[idx], self.img_per_id) 68 | elif mode[i][j] == 1: 69 | t1 = self._get_index(self.id_dic[idx_l1], self.img_per_id // 3) 70 | t2 = self._get_index(self.id_dic[idx], self.img_per_id // 3) 71 | t3 = self._get_index(self.id_dic[idx_r1], self.img_per_id - self.img_per_id // 3 * 2) 72 | tt = np.concatenate((t1, t2, t3), axis = 0) 73 | else: 74 | t1 = self._get_index(self.id_dic[idx_l1], self.img_per_id // 5) 75 | t2 = self._get_index(self.id_dic[idx_l2], self.img_per_id // 5) 76 | t3 = self._get_index(self.id_dic[idx], self.img_per_id // 5) 77 | t4 = self._get_index(self.id_dic[idx_r1], self.img_per_id // 5) 78 | t5 = self._get_index(self.id_dic[idx_r2], self.img_per_id - self.img_per_id // 5 * 4) 79 | tt = np.concatenate((t1, t2, t3, t4, t5), axis = 0) 80 | ret.extend(tt) 81 | return iter(ret) 82 | 83 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/mySelect.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def mySelect(scores,labels): 3 | # scores: 80*1267ids, where 1267 is dims 4 | # labels: 80 labels, 80 = 10ids*8pic/id 5 | nums = scores.shape[0] # 80 6 | dims = scores.shape[1] # 1267 7 | # numpy to list 8 | labels_list = labels.tolist() 9 | # unique ids 10 | id_set = set(labels_list) 11 | # set to list 12 | id_list = list(id_set) 13 | id_num = len(id_list) 14 | imgs_num_per_id = labels.shape[0]//id_num ### 15 | labels_new = labels 16 | 17 | # print('labels_list:',id_list) 18 | # print('id_list:',id_list) 19 | scores = scores[:,id_list] ##** 20 | ############################################################## 21 | for i in range(id_num): 22 | id_has_cards = np.zeros((id_num,1)) #local,0~9, actually, e.g.,2,33,56,77,...,1265 23 | # id_has_cards_ind = np.zeros((id_num,nums)) 24 | id_has_cards_scores=[[] for i in range(id_num)] 25 | id_has_cards_ind=[[] for i in range(id_num)] 26 | for j in range(nums): 27 | one_card = scores[j,:] 28 | higher_scores_ind = np.argmax(one_card,axis = 0) ### exclude neg scores 29 | higher_scores = np.max(one_card,axis = 0) ### exclude neg scores 30 | if higher_scores < -10000.0: 31 | continue 32 | # id_local_ind = id_list.index(higher_scores_ind[0]) ##* 33 | id_local_ind = higher_scores_ind 34 | id_has_cards_ind[id_local_ind].append(j) 35 | id_has_cards_scores[id_local_ind].append(higher_scores) 36 | id_has_cards[id_local_ind,0] = id_has_cards[id_local_ind,0] + 1 37 | # find the biggest one 38 | biggest_one_ind = np.argmax(id_has_cards,axis = 0) ### 39 | # step 1: set something, set all dims -1.0 scores if it is topk, = discard this card 40 | # if not topk, set one dims = 0.0 41 | attention_cards = id_has_cards_ind[biggest_one_ind[0]] 42 | attention_cards_scores = id_has_cards_scores[biggest_one_ind[0]] 43 | attention_cards_scores_np = np.array(attention_cards_scores) 44 | 45 | # sort: 46 | attention_cards_order_ind = np.argsort(-attention_cards_scores_np) 47 | # print('****************************************************************') 48 | # print('scores:',scores) 49 | # print('id_has_cards:',id_has_cards) 50 | # print('id_has_cards_scores:',id_has_cards_scores) 51 | # print('id_has_cards_ind:',id_has_cards_ind) 52 | # print('attention_cards_scores_np:',attention_cards_scores_np) 53 | # print('attention_cards_order_ind:',attention_cards_order_ind) 54 | 55 | assert(attention_cards_order_ind.shape[0]>=imgs_num_per_id) 56 | # for j in range(attention_cards_order_ind.shape[0]): 57 | for j in range(imgs_num_per_id): 58 | bigman_one_card = attention_cards[attention_cards_order_ind[j]] 59 | scores[bigman_one_card,:] = -100000.0 60 | scores[:,biggest_one_ind[0]] = -100000.0 61 | # step 2: update labels 62 | attention_cards = np.array(attention_cards) 63 | labels_new[attention_cards] = id_list[biggest_one_ind[0]] # > topk will be covered #** 64 | return labels_new 65 | 66 | if __name__ == '__main__': 67 | scores = np.random.rand(6,7) 68 | labels = np.array([1,0,1,2,0,2]) 69 | labels_new = mySelect(scores,labels) 70 | print('labels_new:',labels_new) 71 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/random_erasing.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from torchvision.transforms import * 4 | 5 | from PIL import Image 6 | import random 7 | import math 8 | import numpy as np 9 | import torch 10 | 11 | class RandomErasing(object): 12 | """ Randomly selects a rectangle region in an image and erases its pixels. 13 | 'Random Erasing Data Augmentation' by Zhong et al. 14 | See https://arxiv.org/pdf/1708.04896.pdf 15 | Args: 16 | probability: The probability that the Random Erasing operation will be performed. 17 | sl: Minimum proportion of erased area against input image. 18 | sh: Maximum proportion of erased area against input image. 19 | r1: Minimum aspect ratio of erased area. 20 | mean: Erasing value. 21 | """ 22 | 23 | def __init__(self, probability = 0.5, sl = 0.02, sh = 0.4, r1 = 0.3, mean=[0.4914, 0.4822, 0.4465]): 24 | self.probability = probability 25 | self.mean = mean 26 | self.sl = sl 27 | self.sh = sh 28 | self.r1 = r1 29 | 30 | def __call__(self, img): 31 | 32 | if random.uniform(0, 1) > self.probability: 33 | return img 34 | 35 | for attempt in range(100): 36 | area = img.size()[1] * img.size()[2] 37 | 38 | target_area = random.uniform(self.sl, self.sh) * area 39 | aspect_ratio = random.uniform(self.r1, 1/self.r1) 40 | 41 | h = int(round(math.sqrt(target_area * aspect_ratio))) 42 | w = int(round(math.sqrt(target_area / aspect_ratio))) 43 | 44 | if w < img.size()[2] and h < img.size()[1]: 45 | x1 = random.randint(0, img.size()[1] - h) 46 | y1 = random.randint(0, img.size()[2] - w) 47 | if img.size()[0] == 3: 48 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 49 | img[1, x1:x1+h, y1:y1+w] = self.mean[1] 50 | img[2, x1:x1+h, y1:y1+w] = self.mean[2] 51 | else: 52 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 53 | return img 54 | 55 | return img 56 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/train_weak.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import print_function, division 4 | 5 | import argparse 6 | import torch 7 | import torch.nn as nn 8 | import torch.optim as optim 9 | from torch.optim import lr_scheduler 10 | from torch.autograd import Variable 11 | import numpy as np 12 | import torchvision 13 | from torchvision import datasets, models, transforms 14 | import matplotlib 15 | matplotlib.use('agg') 16 | import matplotlib.pyplot as plt 17 | from PIL import Image 18 | import time 19 | import os 20 | from model import ft_net, ft_net_dense, PCB 21 | from random_erasing import RandomErasing 22 | import json 23 | from myImage import ImageFolder 24 | from mySampler import mySampler 25 | from mySelect import mySelect 26 | import multiprocessing 27 | 28 | ###################################################################### 29 | # Options 30 | # -------- 31 | os.environ['CUDA_VISIBLE_DEVICES'] = '6,7' 32 | parser = argparse.ArgumentParser(description='Training') 33 | parser.add_argument('--gpu_ids',default='0', type=str,help='gpu_ids: e.g. 0 0,1,2 0,2') 34 | parser.add_argument('--name',default='ft_ResNet50', type=str, help='output model name') 35 | parser.add_argument('--data_dir',default='/home/zzd/Market/pytorch',type=str, help='training dir path') 36 | parser.add_argument('--train_all', action='store_true', help='use all training data' ) 37 | parser.add_argument('--color_jitter', action='store_true', help='use color jitter in training' ) 38 | parser.add_argument('--batchsize', default=180, type=int, help='batchsize') 39 | parser.add_argument('--erasing_p', default=0, type=float, help='Random Erasing probability, in [0,1]') 40 | parser.add_argument('--use_dense', action='store_true', help='use densenet121' ) 41 | parser.add_argument('--PCB', action='store_true', help='use PCB+ResNet50' ) 42 | parser.add_argument('--idnum', default=12, type=int, help='id num per batchsize') 43 | opt = parser.parse_args() 44 | 45 | data_dir = opt.data_dir 46 | name = opt.name 47 | str_ids = opt.gpu_ids.split(',') 48 | gpu_ids = [] 49 | for str_id in str_ids: 50 | gid = int(str_id) 51 | if gid >=0: 52 | gpu_ids.append(gid) 53 | 54 | # set gpu ids 55 | # if len(gpu_ids)>0: 56 | # torch.cuda.set_device(gpu_ids[0]) 57 | #print(gpu_ids[0]) 58 | 59 | # CUDA_VISIBLE_DEVICES=0,2,4 60 | 61 | 62 | ###################################################################### 63 | # Load Data 64 | # --------- 65 | # 66 | 67 | transform_train_list = [ 68 | #transforms.RandomResizedCrop(size=128, scale=(0.75,1.0), ratio=(0.75,1.3333), interpolation=3), #Image.BICUBIC) 69 | transforms.Resize((224,112), interpolation=3), 70 | transforms.RandomCrop((192,96)), 71 | transforms.RandomHorizontalFlip(), 72 | transforms.ToTensor(), 73 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 74 | ] 75 | 76 | transform_val_list = [ 77 | transforms.Resize(size=(192,96),interpolation=3), #Image.BICUBIC 78 | transforms.ToTensor(), 79 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 80 | ] 81 | 82 | if opt.PCB: 83 | transform_train_list = [ 84 | transforms.Resize((384,192), interpolation=3), 85 | transforms.RandomHorizontalFlip(), 86 | transforms.ToTensor(), 87 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 88 | ] 89 | transform_val_list = [ 90 | transforms.Resize(size=(384,192),interpolation=3), #Image.BICUBIC 91 | transforms.ToTensor(), 92 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 93 | ] 94 | 95 | if opt.erasing_p>0: 96 | transform_train_list = transform_train_list + [RandomErasing(probability = opt.erasing_p, mean=[0.0, 0.0, 0.0])] 97 | 98 | if opt.color_jitter: 99 | transform_train_list = [transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0)] + transform_train_list 100 | 101 | # print(transform_train_list) 102 | data_transforms = { 103 | 'train': transforms.Compose( transform_train_list ), 104 | 'val': transforms.Compose(transform_val_list), 105 | } 106 | 107 | 108 | train_all = '' 109 | if opt.train_all: 110 | train_all = '_all' 111 | 112 | image_datasets = {} 113 | print('aaaaaaaaaaaaaaaaaaaaaaaaaaa') 114 | image_datasets['train'] = ImageFolder(os.path.join(data_dir, 'train' + train_all), 115 | data_transforms['train']) 116 | print(image_datasets['train']) 117 | image_datasets['val'] = ImageFolder(os.path.join(data_dir, 'val'), 118 | data_transforms['val']) 119 | 120 | 121 | #============================================================================= add sampler 122 | mysamplers={} 123 | print('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') 124 | # print([image_datasets['train'][i] for i in range(10)]) 125 | # label_list = [image_datasets['train'][i][1] for i in range(len(image_datasets['train']))] 126 | # print('ccccccccccccccccccccccccccccccccccccccccccccccccc') 127 | # print('label_list:',label_list) 128 | # print('ens_num:',opt.ens_num) 129 | mysamplers['train'] = mySampler(image_datasets['train'],opt.batchsize,opt.idnum) 130 | 131 | # label_list = [image_datasets['val'][i][1] for i in range(len(image_datasets['val']))] 132 | # mysamplers['val'] = mySampler(image_datasets['val'],opt.batchsize,label_list,opt.idnum) 133 | #============================================================================= end sampler 134 | # dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=opt.batchsize, 135 | # shuffle=True, num_workers=16) 136 | # for x in ['train', 'val']} 137 | 138 | parallel = min(40, multiprocessing.cpu_count() // 7 * 7) # assuming hyperthreading 139 | print(parallel, 'ccccccccccccccccccccccccccccccccccccccc') 140 | 141 | dataloaders = {'train': torch.utils.data.DataLoader(image_datasets['train'], 142 | batch_size=opt.batchsize, sampler=mysamplers['train'], num_workers=parallel),'val':torch.utils.data.DataLoader(image_datasets['val'], 143 | batch_size=opt.batchsize, shuffle=True, num_workers=parallel)} 144 | 145 | dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']} 146 | class_names = image_datasets['train'].classes 147 | 148 | print(len(class_names), 'aaaaaaaaaaaaaaaaaaaaaaaaa') 149 | 150 | use_gpu = torch.cuda.is_available() 151 | 152 | # inputs, classes = next(iter(dataloaders['train'])) 153 | 154 | ###################################################################### 155 | # Training the model 156 | # ------------------ 157 | # 158 | # Now, let's write a general function to train a model. Here, we will 159 | # illustrate: 160 | # 161 | # - Scheduling the learning rate 162 | # - Saving the best model 163 | # 164 | # In the following, parameter ``scheduler`` is an LR scheduler object from 165 | # ``torch.optim.lr_scheduler``. 166 | 167 | y_loss = {} # loss history 168 | y_loss['train'] = [] 169 | y_loss['val'] = [] 170 | y_err = {} 171 | y_err['train'] = [] 172 | y_err['val'] = [] 173 | 174 | def train_model(model, criterion, optimizer, scheduler, num_epochs=25): 175 | since = time.time() 176 | 177 | best_model_wts = model.state_dict() 178 | best_acc = 0.0 179 | 180 | for epoch in range(num_epochs): 181 | print('Epoch {}/{}'.format(epoch, num_epochs - 1)) 182 | print('-' * 10) 183 | 184 | # Each epoch has a training and validation phase 185 | for phase in ['train', 'val']: 186 | if phase == 'train': 187 | scheduler.step() 188 | model.train(True) # Set model to training mode 189 | else: 190 | model.train(False) # Set model to evaluate mode 191 | 192 | running_loss = 0.0 193 | running_corrects = 0 194 | # Iterate over data. 195 | # mode = np.asarray([0,1,2]) 196 | # mode = np.tile(mode, [self.ids_num_per_batch // 3]) 197 | # mode = np.tile(mode, [self.iters_per_epoch]) 198 | countt = 0 199 | time_space = len(dataloaders[phase])//100 200 | for data in dataloaders[phase]: 201 | # time_elapsed = time.time() - since 202 | # print('times used: {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60)) 203 | 204 | countt += 1 205 | # print(dataloaders[phase]) 206 | # print(len(dataloaders[phase]), 'aaaaaaaaaaaaaaaaaaaaaaaaa') 207 | # print(len(data), 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb') 208 | if time_space > 0: 209 | if countt%time_space == 1: 210 | time_elapsed = time.time() - since 211 | time_left = time_elapsed / (countt+1) * len(dataloaders[phase]) 212 | print('times used: {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60)) 213 | print('expected epoch time : {:.0f}m {:.0f}s'.format(time_left // 60, time_left % 60)) 214 | # get the inputs 215 | inputs, labels = data 216 | #print(inputs.shape) 217 | # wrap them in Variable 218 | if use_gpu: 219 | inputs = Variable(inputs.cuda()) 220 | labels = Variable(labels.cuda()) 221 | else: 222 | inputs, labels = Variable(inputs), Variable(labels) 223 | 224 | # zero the parameter gradients 225 | optimizer.zero_grad() 226 | 227 | # forward 228 | outputs = model(inputs) 229 | 230 | outputs_np = outputs.data.cpu().numpy() 231 | labels_np = labels.data.cpu().numpy() 232 | 233 | if not opt.PCB: 234 | _, preds = torch.max(outputs.data, 1) 235 | if phase == 'train': 236 | bag_size = opt.batchsize // opt.idnum 237 | labels_new = np.zeros((int(opt.batchsize))).astype(int) 238 | for i in range(opt.idnum): 239 | # print(outputs_np, labels_np) 240 | bag_target = mySelect(outputs_np[int(i*bag_size):int((i+1)*bag_size),:],labels_np[int(i*bag_size):int((i+1)*bag_size)]) 241 | labels_new[int(i*bag_size):int((i+1)*bag_size)] = bag_target 242 | labels_new = torch.from_numpy(labels_new).cuda() 243 | labels_new = Variable(labels_new) 244 | loss = criterion(outputs, labels_new) 245 | else: 246 | loss = criterion(outputs, labels) 247 | else: 248 | part = {} 249 | sm = nn.Softmax(dim=1) 250 | num_part = 6 251 | for i in range(num_part): 252 | part[i] = outputs[i] 253 | 254 | score = sm(part[0]) + sm(part[1]) +sm(part[2]) + sm(part[3]) +sm(part[4]) +sm(part[5]) 255 | _, preds = torch.max(score.data, 1) 256 | 257 | loss = criterion(part[0], labels) 258 | for i in range(num_part-1): 259 | loss += criterion(part[i+1], labels) 260 | 261 | # backward + optimize only if in training phase 262 | if phase == 'train': 263 | loss.backward() 264 | optimizer.step() 265 | 266 | # statistics 267 | running_loss += loss.data[0] 268 | running_corrects += torch.sum(preds == labels.data) 269 | # time_elapsed = time.time() - since 270 | # print('times used: {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60), '---------------------------') 271 | 272 | epoch_loss = running_loss / dataset_sizes[phase] 273 | epoch_acc = running_corrects / dataset_sizes[phase] 274 | 275 | print('{} Loss: {:.4f} Acc: {:.4f}'.format( 276 | phase, epoch_loss, epoch_acc)) 277 | 278 | y_loss[phase].append(epoch_loss) 279 | y_err[phase].append(1.0-epoch_acc) 280 | # deep copy the model 281 | if phase == 'val': 282 | last_model_wts = model.state_dict() 283 | # if epoch%10 == 9: 284 | save_network(model, epoch) 285 | draw_curve(epoch) 286 | 287 | print() 288 | 289 | time_elapsed = time.time() - since 290 | print('Training complete in {:.0f}m {:.0f}s'.format( 291 | time_elapsed // 60, time_elapsed % 60)) 292 | #print('Best val Acc: {:4f}'.format(best_acc)) 293 | 294 | # load best model weights 295 | model.load_state_dict(last_model_wts) 296 | save_network(model, 'last') 297 | return model 298 | 299 | 300 | ###################################################################### 301 | # Draw Curve 302 | #--------------------------- 303 | x_epoch = [] 304 | fig = plt.figure() 305 | ax0 = fig.add_subplot(121, title="loss") 306 | ax1 = fig.add_subplot(122, title="top1err") 307 | def draw_curve(current_epoch): 308 | x_epoch.append(current_epoch) 309 | ax0.plot(x_epoch, y_loss['train'], 'bo-', label='train') 310 | ax0.plot(x_epoch, y_loss['val'], 'ro-', label='val') 311 | ax1.plot(x_epoch, y_err['train'], 'bo-', label='train') 312 | ax1.plot(x_epoch, y_err['val'], 'ro-', label='val') 313 | if current_epoch == 0: 314 | ax0.legend() 315 | ax1.legend() 316 | fig.savefig( os.path.join('./work_dirs',name,'train.jpg')) 317 | 318 | ###################################################################### 319 | # Save model 320 | #--------------------------- 321 | def save_network(network, epoch_label): 322 | save_filename = 'net_%s.pth'% epoch_label 323 | save_path = os.path.join('./work_dirs',name,save_filename) 324 | torch.save(network.cpu().state_dict(), save_path) 325 | if torch.cuda.is_available: 326 | network.cuda(gpu_ids[0]) 327 | 328 | 329 | ###################################################################### 330 | # Finetuning the convnet 331 | # ---------------------- 332 | # 333 | # Load a pretrainied model and reset final fully connected layer. 334 | # 335 | 336 | if opt.use_dense: 337 | model = ft_net_dense(len(class_names)) 338 | else: 339 | model = ft_net(len(class_names)) 340 | 341 | if opt.PCB: 342 | model = PCB(len(class_names)) 343 | 344 | 345 | if use_gpu: 346 | model = model.cuda() 347 | # model = nn.DataParallel(model).cuda() 348 | 349 | # print(model.model) 350 | 351 | # ct = 0 352 | # for child in model.model.children(): 353 | # # print(child, ct, '----------------------------------') 354 | # if ct < 7: 355 | # for param in child.parameters(): 356 | # param.requires_grad = False 357 | # ct += 1 358 | 359 | criterion = nn.CrossEntropyLoss() 360 | 361 | linear_lr = 0.01 #0.1 # opt.batchsize / 90. 362 | if not opt.PCB: 363 | ignored_params = list(map(id, model.model.fc.parameters() )) + list(map(id, model.classifier.parameters() )) 364 | base_params = filter(lambda p: id(p) not in ignored_params, model.parameters()) 365 | base_params = filter(lambda p: p.requires_grad, base_params) 366 | optimizer_ft = optim.SGD([ 367 | {'params': base_params, 'lr': 0.01 * linear_lr}, 368 | {'params': model.model.fc.parameters(), 'lr': 0.1 * linear_lr}, 369 | {'params': model.classifier.parameters(), 'lr': 0.1 * linear_lr} 370 | ], weight_decay=5e-4, momentum=0.9, nesterov=True) 371 | else: 372 | ignored_params = list(map(id, model.model.fc.parameters() )) 373 | ignored_params += (list(map(id, model.classifier0.parameters() )) 374 | +list(map(id, model.classifier1.parameters() )) 375 | +list(map(id, model.classifier2.parameters() )) 376 | +list(map(id, model.classifier3.parameters() )) 377 | +list(map(id, model.classifier4.parameters() )) 378 | +list(map(id, model.classifier5.parameters() )) 379 | #+list(map(id, model.classifier6.parameters() )) 380 | #+list(map(id, model.classifier7.parameters() )) 381 | ) 382 | base_params = filter(lambda p: id(p) not in ignored_params, model.parameters()) 383 | optimizer_ft = optim.SGD([ 384 | {'params': base_params, 'lr': 0.01}, 385 | {'params': model.model.fc.parameters(), 'lr': 0.1}, 386 | {'params': model.classifier0.parameters(), 'lr': 0.1}, 387 | {'params': model.classifier1.parameters(), 'lr': 0.1}, 388 | {'params': model.classifier2.parameters(), 'lr': 0.1}, 389 | {'params': model.classifier3.parameters(), 'lr': 0.1}, 390 | {'params': model.classifier4.parameters(), 'lr': 0.1}, 391 | {'params': model.classifier5.parameters(), 'lr': 0.1}, 392 | #{'params': model.classifier6.parameters(), 'lr': 0.01}, 393 | #{'params': model.classifier7.parameters(), 'lr': 0.01} 394 | ], weight_decay=5e-4, momentum=0.9, nesterov=True) 395 | 396 | # Decay LR by a factor of 0.1 every 40 epochs 397 | exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=8, gamma=0.1) 398 | model = nn.DataParallel(model) 399 | 400 | ###################################################################### 401 | # Train and evaluate 402 | # ^^^^^^^^^^^^^^^^^^ 403 | 404 | # 405 | dir_name = os.path.join('./work_dirs',name) 406 | if not os.path.isdir(dir_name): 407 | os.mkdir(dir_name) 408 | 409 | # save opts 410 | with open('%s/opts.json'%dir_name,'w') as fp: 411 | json.dump(vars(opt), fp, indent=1) 412 | 413 | #model.load_state_dict(torch.load('')) 414 | #model.load_state_dict('') 415 | model = train_model(model, criterion, optimizer_ft, exp_lr_scheduler, 416 | num_epochs=8) 417 | 418 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_Only/work_dirs/readme.md: -------------------------------------------------------------------------------- 1 | During training, checkpoints and logs are saved in this folder, which will occupies much memory of the hard disk. If you want to save your weights somewhere else, please use symlink, for example: 2 | 3 | ```shell 4 | ln -s /data1/wangguangrun/GraphReID_work_dirs work_dirs 5 | ``` 6 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/.DS_Store -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # The MGN implementation is based on [seathiefwang/MGN-pytorch](https://github.com/seathiefwang/MGN-pytorch). Please acknowledge the contribution of [seathiefwang/MGN-pytorch](https://github.com/seathiefwang/MGN-pytorch). 4 | 5 | ------------------------- 6 | 7 | # Multiple Granularity Network 8 | Reproduction of paper:[Learning Discriminative Features with Multiple Granularities for Person Re-Identification](https://arxiv.org/abs/1804.01438v1) 9 | 10 | ## Dependencies 11 | 12 | - Python >= 3.5 13 | - PyTorch >= 0.4.0 14 | - TorchVision 15 | - Matplotlib 16 | - Argparse 17 | - Sklearn 18 | - Pillow 19 | - Numpy 20 | - Scipy 21 | - Tqdm 22 | 23 | ## Train 24 | 25 | ### Prepare training data 26 | 27 | Download Market1501 training data.[here](http://www.liangzheng.org/Project/project_reid.html) 28 | 29 | ### Begin to train 30 | 31 | In the demo.sh file, add the Market1501 directory to --datadir 32 | 33 | run `sh demo.sh` 34 | 35 | ## Result 36 | 37 | | | mAP | rank1 | rank3 | rank5 | rank10 | 38 | | :------: | :------: | :------: | :------: | :------: | :------: | 39 | | 2018-7-22 | 92.17 | 94.60 | 96.53 | 97.06 | 98.01 | 40 | | 2018-7-24 | 93.53 | 95.34 | 97.06 | 97.68 | 98.49 | 41 | | last | 93.83 | 95.78 | 97.21 | 97.83 | 98.43 | 42 | 43 | Download model file in [here](https://pan.baidu.com/s/1DbZsT16yIITTkmjRW1ifWQ) 44 | 45 | 46 | ## The architecture of Multiple Granularity Network (MGN) 47 | ![Multiple Granularity Network](https://pic2.zhimg.com/80/v2-90a8763a0b7aa86d9152492eb3f85899_hd.jpg) 48 | 49 | Figure . Multiple Granularity Network architecture. 50 | 51 | ```text 52 | @ARTICLE{2018arXiv180401438W, 53 | author = {{Wang}, G. and {Yuan}, Y. and {Chen}, X. and {Li}, J. and {Zhou}, X.}, 54 | title = "{Learning Discriminative Features with Multiple Granularities for Person Re-Identification}", 55 | journal = {ArXiv e-prints}, 56 | archivePrefix = "arXiv", 57 | eprint = {1804.01438}, 58 | primaryClass = "cs.CV", 59 | keywords = {Computer Science - Computer Vision and Pattern Recognition}, 60 | year = 2018, 61 | month = apr, 62 | adsurl = {http://adsabs.harvard.edu/abs/2018arXiv180401438W}, 63 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 64 | } 65 | ``` -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from torchvision import transforms 3 | from utils.random_erasing import RandomErasing 4 | from data.sampler import RandomSampler 5 | from torch.utils.data import dataloader 6 | 7 | class Data: 8 | def __init__(self, args): 9 | 10 | train_list = [ 11 | transforms.Resize((args.height, args.width), interpolation=3), 12 | transforms.RandomHorizontalFlip(), 13 | transforms.ToTensor(), 14 | transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 15 | ] 16 | if args.random_erasing: 17 | train_list.append(RandomErasing(probability=args.probability, mean=[0.0, 0.0, 0.0])) 18 | 19 | train_transform = transforms.Compose(train_list) 20 | 21 | test_transform = transforms.Compose([ 22 | transforms.Resize((args.height, args.width), interpolation=3), 23 | transforms.ToTensor(), 24 | transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 25 | ]) 26 | 27 | if not args.test_only: 28 | module_train = import_module('data.' + args.data_train.lower()) 29 | self.trainset = getattr(module_train, args.data_train)(args, train_transform, 'train') 30 | self.train_loader = dataloader.DataLoader(self.trainset, 31 | sampler=RandomSampler(self.trainset,args.batchid,batch_image=args.batchimage), 32 | #shuffle=True, 33 | batch_size=args.batchid * args.batchimage, 34 | num_workers=args.nThread) 35 | else: 36 | self.train_loader = None 37 | 38 | if args.data_test in ['Market1501', 'Market1501_folder', 'MSMT17_folder', 'CUHK03_folder', 'SYSU30K_folder']: 39 | module = import_module('data.' + args.data_train.lower()) 40 | self.testset = getattr(module, args.data_test)(args, test_transform, 'test') 41 | self.queryset = getattr(module, args.data_test)(args, test_transform, 'query') 42 | 43 | else: 44 | raise Exception() 45 | 46 | self.test_loader = dataloader.DataLoader(self.testset, batch_size=args.batchtest, num_workers=args.nThread) 47 | self.query_loader = dataloader.DataLoader(self.queryset, batch_size=args.batchtest, num_workers=args.nThread) 48 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/.DS_Store -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from torchvision import transforms 3 | from utils.random_erasing import RandomErasing 4 | from data.sampler import RandomSampler 5 | from torch.utils.data import dataloader 6 | 7 | class Data: 8 | def __init__(self, args): 9 | 10 | train_list = [ 11 | transforms.Resize((args.height, args.width), interpolation=3), 12 | transforms.RandomHorizontalFlip(), 13 | transforms.ToTensor(), 14 | transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 15 | ] 16 | if args.random_erasing: 17 | train_list.append(RandomErasing(probability=args.probability, mean=[0.0, 0.0, 0.0])) 18 | 19 | train_transform = transforms.Compose(train_list) 20 | 21 | test_transform = transforms.Compose([ 22 | transforms.Resize((args.height, args.width), interpolation=3), 23 | transforms.ToTensor(), 24 | transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 25 | ]) 26 | 27 | if not args.test_only: 28 | module_train = import_module('data.' + args.data_train.lower()) 29 | self.trainset = getattr(module_train, args.data_train)(args, train_transform, 'train') 30 | self.train_loader = dataloader.DataLoader(self.trainset, 31 | sampler=RandomSampler(self.trainset,args.batchid,batch_image=args.batchimage), 32 | #shuffle=True, 33 | batch_size=args.batchid * args.batchimage, 34 | num_workers=args.nThread) 35 | else: 36 | self.train_loader = None 37 | 38 | if args.data_test in ['Market1501', 'Market1501_folder', 'MSMT17_folder', 'CUHK03_folder', 'SYSU30K_folder']: 39 | module = import_module('data.' + args.data_train.lower()) 40 | self.testset = getattr(module, args.data_test)(args, test_transform, 'test') 41 | self.queryset = getattr(module, args.data_test)(args, test_transform, 'query') 42 | 43 | else: 44 | raise Exception() 45 | 46 | self.test_loader = dataloader.DataLoader(self.testset, batch_size=args.batchtest, num_workers=args.nThread) 47 | self.query_loader = dataloader.DataLoader(self.queryset, batch_size=args.batchtest, num_workers=args.nThread) 48 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | 6 | def list_pictures(directory, ext='jpg|jpeg|bmp|png|ppm'): 7 | assert os.path.isdir(directory), 'dataset is not exists!{}'.format(directory) 8 | 9 | return sorted([os.path.join(root, f) 10 | for root, _, files in os.walk(directory) for f in files 11 | if re.match(r'([\w]+\.(?:' + ext + '))', f)]) 12 | 13 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/cuhk03_folder.py: -------------------------------------------------------------------------------- 1 | from data.common import list_pictures 2 | 3 | from torch.utils.data import dataset 4 | from torchvision.datasets.folder import default_loader 5 | 6 | 7 | class CUHK03_folder(dataset.Dataset): 8 | def __init__(self, args, transform, dtype): 9 | 10 | self.transform = transform 11 | self.loader = default_loader 12 | 13 | data_path = args.datadir 14 | if dtype == 'train': 15 | data_path += '/bounding_box_train' 16 | elif dtype == 'test': 17 | data_path += '/bounding_box_test' 18 | else: 19 | data_path += '/query' 20 | 21 | self.imgs = [path for path in list_pictures(data_path) if self.id(path) != -1] 22 | # print(self.imgs) 23 | 24 | self._id2label = {_id: idx for idx, _id in enumerate(self.unique_ids)} 25 | 26 | def __getitem__(self, index): 27 | path = self.imgs[index] 28 | target = self._id2label[self.id(path)] 29 | 30 | img = self.loader(path) 31 | if self.transform is not None: 32 | img = self.transform(img) 33 | 34 | return img, target 35 | 36 | def __len__(self): 37 | return len(self.imgs) 38 | 39 | @staticmethod 40 | def id(file_path): 41 | """ 42 | :param file_path: unix style file path 43 | :return: person id 44 | """ 45 | return int(file_path.split('/')[-1].split('_')[0]) 46 | 47 | @staticmethod 48 | def camera(file_path): 49 | """ 50 | :param file_path: unix style file path 51 | :return: camera id 52 | """ 53 | return int(file_path.split('/')[-1].split('_')[1][1]) 54 | 55 | @property 56 | def ids(self): 57 | """ 58 | :return: person id list corresponding to dataset image paths 59 | """ 60 | return [self.id(path) for path in self.imgs] 61 | 62 | @property 63 | def unique_ids(self): 64 | """ 65 | :return: unique person ids in ascending order 66 | """ 67 | return sorted(set(self.ids)) 68 | 69 | @property 70 | def cameras(self): 71 | """ 72 | :return: camera id list corresponding to dataset image paths 73 | """ 74 | return [self.camera(path) for path in self.imgs] 75 | 76 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/market1501.py: -------------------------------------------------------------------------------- 1 | from data.common import list_pictures 2 | 3 | from torch.utils.data import dataset 4 | from torchvision.datasets.folder import default_loader 5 | 6 | class Market1501(dataset.Dataset): 7 | def __init__(self, args, transform, dtype): 8 | 9 | self.transform = transform 10 | self.loader = default_loader 11 | 12 | data_path = args.datadir 13 | if dtype == 'train': 14 | data_path += '/bounding_box_train' 15 | elif dtype == 'test': 16 | data_path += '/bounding_box_test' 17 | else: 18 | data_path += '/query' 19 | 20 | self.imgs = [path for path in list_pictures(data_path) if self.id(path) != -1] 21 | # print(self.imgs) 22 | 23 | self._id2label = {_id: idx for idx, _id in enumerate(self.unique_ids)} 24 | 25 | def __getitem__(self, index): 26 | path = self.imgs[index] 27 | target = self._id2label[self.id(path)] 28 | 29 | img = self.loader(path) 30 | if self.transform is not None: 31 | img = self.transform(img) 32 | 33 | return img, target 34 | 35 | def __len__(self): 36 | return len(self.imgs) 37 | 38 | @staticmethod 39 | def id(file_path): 40 | """ 41 | :param file_path: unix style file path 42 | :return: person id 43 | """ 44 | return int(file_path.split('/')[-1].split('_')[0]) 45 | 46 | @staticmethod 47 | def camera(file_path): 48 | """ 49 | :param file_path: unix style file path 50 | :return: camera id 51 | """ 52 | return int(file_path.split('/')[-1].split('_')[1][1]) 53 | 54 | @property 55 | def ids(self): 56 | """ 57 | :return: person id list corresponding to dataset image paths 58 | """ 59 | return [self.id(path) for path in self.imgs] 60 | 61 | @property 62 | def unique_ids(self): 63 | """ 64 | :return: unique person ids in ascending order 65 | """ 66 | return sorted(set(self.ids)) 67 | 68 | @property 69 | def cameras(self): 70 | """ 71 | :return: camera id list corresponding to dataset image paths 72 | """ 73 | return [self.camera(path) for path in self.imgs] 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/market1501_folder.py: -------------------------------------------------------------------------------- 1 | from data.common import list_pictures 2 | 3 | from torch.utils.data import dataset 4 | from torchvision.datasets.folder import default_loader 5 | 6 | 7 | class Market1501_folder(dataset.Dataset): 8 | def __init__(self, args, transform, dtype): 9 | 10 | self.transform = transform 11 | self.loader = default_loader 12 | 13 | data_path = args.datadir 14 | if dtype == 'train': 15 | data_path += '/train_all' 16 | elif dtype == 'test': 17 | data_path += '/gallery' 18 | else: 19 | data_path += '/query' 20 | 21 | self.imgs = [path for path in list_pictures(data_path) if self.id(path) != -1] 22 | # print(self.imgs) 23 | 24 | self._id2label = {_id: idx for idx, _id in enumerate(self.unique_ids)} 25 | 26 | def __getitem__(self, index): 27 | path = self.imgs[index] 28 | target = self._id2label[self.id(path)] 29 | 30 | img = self.loader(path) 31 | if self.transform is not None: 32 | img = self.transform(img) 33 | 34 | return img, target 35 | 36 | def __len__(self): 37 | return len(self.imgs) 38 | 39 | @staticmethod 40 | def id(file_path): 41 | """ 42 | :param file_path: unix style file path 43 | :return: person id 44 | """ 45 | return int(file_path.split('/')[-1].split('_')[0]) 46 | 47 | @staticmethod 48 | def camera(file_path): 49 | """ 50 | :param file_path: unix style file path 51 | :return: camera id 52 | """ 53 | return int(file_path.split('/')[-1].split('_')[1][1]) 54 | 55 | @property 56 | def ids(self): 57 | """ 58 | :return: person id list corresponding to dataset image paths 59 | """ 60 | return [self.id(path) for path in self.imgs] 61 | 62 | @property 63 | def unique_ids(self): 64 | """ 65 | :return: unique person ids in ascending order 66 | """ 67 | return sorted(set(self.ids)) 68 | 69 | @property 70 | def cameras(self): 71 | """ 72 | :return: camera id list corresponding to dataset image paths 73 | """ 74 | return [self.camera(path) for path in self.imgs] 75 | 76 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/msmt17_folder.py: -------------------------------------------------------------------------------- 1 | from data.common import list_pictures 2 | 3 | from torch.utils.data import dataset 4 | from torchvision.datasets.folder import default_loader 5 | 6 | 7 | class MSMT17_folder(dataset.Dataset): 8 | def __init__(self, args, transform, dtype): 9 | 10 | self.transform = transform 11 | self.loader = default_loader 12 | 13 | data_path = args.datadir 14 | if dtype == 'train': 15 | data_path += '/train' 16 | elif dtype == 'test': 17 | data_path += '/gallery' 18 | else: 19 | data_path += '/query' 20 | 21 | self.imgs = [path for path in list_pictures(data_path) if self.id(path) != -1] 22 | # print(self.imgs) 23 | 24 | self._id2label = {_id: idx for idx, _id in enumerate(self.unique_ids)} 25 | 26 | def __getitem__(self, index): 27 | path = self.imgs[index] 28 | target = self._id2label[self.id(path)] 29 | 30 | img = self.loader(path) 31 | if self.transform is not None: 32 | img = self.transform(img) 33 | 34 | return img, target 35 | 36 | def __len__(self): 37 | return len(self.imgs) 38 | 39 | @staticmethod 40 | def id(file_path): 41 | """ 42 | :param file_path: unix style file path 43 | :return: person id 44 | """ 45 | return int(file_path.split('/')[-1].split('_')[0]) 46 | 47 | @staticmethod 48 | def camera(file_path): 49 | """ 50 | :param file_path: unix style file path 51 | :return: camera id 52 | """ 53 | return int(file_path.split('/')[-1].split('_')[2]) 54 | 55 | @property 56 | def ids(self): 57 | """ 58 | :return: person id list corresponding to dataset image paths 59 | """ 60 | return [self.id(path) for path in self.imgs] 61 | 62 | @property 63 | def unique_ids(self): 64 | """ 65 | :return: unique person ids in ascending order 66 | """ 67 | return sorted(set(self.ids)) 68 | 69 | @property 70 | def cameras(self): 71 | """ 72 | :return: camera id list corresponding to dataset image paths 73 | """ 74 | return [self.camera(path) for path in self.imgs] 75 | 76 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/data/sampler.py: -------------------------------------------------------------------------------- 1 | import random 2 | import collections 3 | from torch.utils.data import sampler 4 | 5 | class RandomSampler(sampler.Sampler): 6 | def __init__(self, data_source, batch_id, batch_image): 7 | super(RandomSampler, self).__init__(data_source) 8 | 9 | self.data_source = data_source 10 | self.batch_image = batch_image 11 | self.batch_id = batch_id 12 | 13 | self._id2index = collections.defaultdict(list) 14 | for idx, path in enumerate(data_source.imgs): 15 | _id = data_source.id(path) 16 | self._id2index[_id].append(idx) 17 | 18 | def __iter__(self): 19 | unique_ids = self.data_source.unique_ids 20 | random.shuffle(unique_ids) 21 | 22 | imgs = [] 23 | for _id in unique_ids: 24 | imgs.extend(self._sample(self._id2index[_id], self.batch_image)) 25 | return iter(imgs) 26 | 27 | def __len__(self): 28 | return len(self._id2index) * self.batch_image 29 | 30 | @staticmethod 31 | def _sample(population, k): 32 | if len(population) < k: 33 | population = population * k 34 | return random.sample(population, k) -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/demo.sh: -------------------------------------------------------------------------------- 1 | # Train 2 | # CUDA_VISIBLE_DEVICES=1,2 python3 main.py --datadir /data1/wangguangrun/dataset/market1501/ --bagid 2 --batchid 16 --batchtest 32 --test_every 100 --epochs 300 --decay_type step_250_290 --loss 1*CrossEntropy+2*Triplet --margin 1.2 --save adam_weak_market --nGPU 2 --lr 2e-4 --optimizer ADAM --random_erasing --reset --re_rank --amsgrad 3 | 4 | # test 5 | # CUDA_VISIBLE_DEVICES=1,2 python3 main.py --datadir /data1/wangguangrun/dataset/market1501/ --bagid 2 --batchid 16 --batchtest 32 --test_every 100 --epochs 300 --decay_type step_250_290 --loss 1*CrossEntropy --margin 1.2 --save adam_test --nGPU 2 --lr 2e-4 --optimizer ADAM --random_erasing --re_rank --amsgrad --test_only --pre_train /home/wangguangrun/pytorch-image-model/MGN-pytorch/work_dirs/adam_weak_hard_cse_tri/model/model_latest.pt 6 | 7 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/loss/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/loss/.DS_Store -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/loss/CrossEntropyLoss.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import torch 4 | from torch import nn 5 | from torch.nn import functional as F 6 | 7 | class CrossEntropyLoss(nn.Module): 8 | """Cross entropy loss with soft label. 9 | """ 10 | def __init__(self, use_gpu=True): 11 | super(CrossEntropyLoss, self).__init__() 12 | self.use_gpu = use_gpu 13 | self.logsoftmax = nn.LogSoftmax(dim=1) 14 | self.softmax = nn.Softmax(dim=1) 15 | self.cse_loss = nn.CrossEntropyLoss() 16 | 17 | def forward(self, inputs, targets, low_level_feat): 18 | """ 19 | """ 20 | # log_probs = self.logsoftmax(inputs) 21 | 22 | # targets = torch.zeros(log_probs.size()).scatter_(1, targets.unsqueeze(1).data.cpu(), 1) 23 | if self.use_gpu: 24 | targets = targets.cuda() 25 | targets = targets.type(torch.cuda.FloatTensor) 26 | targets = torch.autograd.Variable(targets, requires_grad=False) 27 | 28 | # targets = 0.9 * targets + 0.1 / 751 29 | # loss = (- targets * log_probs).mean(0).sum() 30 | input_softmax = self.softmax(inputs) 31 | targets = torch.argmax(targets * input_softmax, -1) 32 | 33 | n = inputs.size(0) 34 | mask = targets.expand(n, n).eq(targets.expand(n, n).t()) 35 | mask = 1.0 - mask.float() 36 | 37 | # print(low_level_feat.size()) 38 | dist = torch.pow(low_level_feat, 2).sum(dim=1, keepdim=True).expand(n, n) 39 | dist = dist + dist.t() 40 | dist.addmm_(1, -2, low_level_feat, low_level_feat.t()) 41 | # dist = dist.clamp(min=1e-12).sqrt() # for numerical stability 42 | sigma = 1. 43 | dist = torch.exp(- dist / sigma) 44 | 45 | # inner_prod = -torch.matmul(input_softmax, torch.log(input_softmax.t())) 46 | 47 | inner_prod = -torch.matmul(input_softmax, input_softmax.t()) 48 | 49 | loss_pairwise = dist * inner_prod * mask 50 | loss_pairwise = loss_pairwise.mean() 51 | 52 | # for i in range(n): 53 | # dist_ap.append(dist[i][mask[i]].max().unsqueeze(0)) 54 | # dist_an.append(dist[i][mask[i] == 0].min().unsqueeze(0)) 55 | loss = self.cse_loss(inputs, targets) + 2. * loss_pairwise 56 | return loss 57 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/loss/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from importlib import import_module 4 | 5 | import matplotlib 6 | matplotlib.use('Agg') 7 | import matplotlib.pyplot as plt 8 | 9 | import torch 10 | import torch.nn as nn 11 | 12 | from loss.triplet import TripletLoss, TripletSemihardLoss 13 | from loss.CrossEntropyLoss import CrossEntropyLoss 14 | 15 | class Loss(nn.modules.loss._Loss): 16 | def __init__(self, args, ckpt): 17 | super(Loss, self).__init__() 18 | print('[INFO] Making loss...') 19 | 20 | self.nGPU = args.nGPU 21 | self.args = args 22 | self.loss = [] 23 | self.loss_module = nn.ModuleList() 24 | for loss in args.loss.split('+'): 25 | weight, loss_type = loss.split('*') 26 | if loss_type == 'CrossEntropy': 27 | loss_function = CrossEntropyLoss() 28 | elif loss_type == 'Triplet': 29 | loss_function = TripletLoss(args.margin) 30 | 31 | self.loss.append({ 32 | 'type': loss_type, 33 | 'weight': float(weight), 34 | 'function': loss_function 35 | }) 36 | 37 | 38 | if len(self.loss) > 1: 39 | self.loss.append({'type': 'Total', 'weight': 0, 'function': None}) 40 | 41 | for l in self.loss: 42 | if l['function'] is not None: 43 | print('{:.3f} * {}'.format(l['weight'], l['type'])) 44 | self.loss_module.append(l['function']) 45 | 46 | self.log = torch.Tensor() 47 | 48 | device = torch.device('cpu' if args.cpu else 'cuda') 49 | self.loss_module.to(device) 50 | 51 | if args.load != '': self.load(ckpt.dir, cpu=args.cpu) 52 | if not args.cpu and args.nGPU > 1: 53 | self.loss_module = nn.DataParallel( 54 | self.loss_module, range(args.nGPU) 55 | ) 56 | 57 | def forward(self, outputs, labels): 58 | losses = [] 59 | for i, l in enumerate(self.loss): 60 | if self.args.model == 'MGN' and l['type'] == 'Triplet': 61 | loss = [l['function'](output, labels) for output in outputs[1:4]] 62 | loss = sum(loss) / len(loss) 63 | effective_loss = l['weight'] * loss 64 | losses.append(effective_loss) 65 | self.log[-1, i] += effective_loss.item() 66 | elif self.args.model == 'MGN' and l['function'] is not None: 67 | loss = [l['function'](output, labels, outputs[-1]) for output in outputs[4:-1]] 68 | loss = sum(loss) / len(loss) 69 | effective_loss = l['weight'] * loss 70 | losses.append(effective_loss) 71 | self.log[-1, i] += effective_loss.item() 72 | else: 73 | pass 74 | loss_sum = sum(losses) 75 | if len(self.loss) > 1: 76 | self.log[-1, -1] += loss_sum.item() 77 | 78 | return loss_sum 79 | 80 | def start_log(self): 81 | self.log = torch.cat((self.log, torch.zeros(1, len(self.loss)))) 82 | 83 | def end_log(self, batches): 84 | self.log[-1].div_(batches) 85 | 86 | def display_loss(self, batch): 87 | n_samples = batch + 1 88 | log = [] 89 | for l, c in zip(self.loss, self.log[-1]): 90 | log.append('[{}: {:.4f}]'.format(l['type'], c / n_samples)) 91 | 92 | return ''.join(log) 93 | 94 | def plot_loss(self, apath, epoch): 95 | axis = np.linspace(1, epoch, epoch) 96 | for i, l in enumerate(self.loss): 97 | label = '{} Loss'.format(l['type']) 98 | #fig = plt.figure() 99 | #plt.title(label) 100 | #plt.plot(axis, self.log[:, i].numpy(), label=label) 101 | #plt.legend() 102 | #plt.xlabel('Epochs') 103 | #plt.ylabel('Loss') 104 | #plt.grid(True) 105 | #plt.savefig('{}/loss_{}.jpg'.format(apath, l['type'])) 106 | #plt.close(fig) 107 | 108 | def step(self): 109 | for l in self.get_loss_module(): 110 | if hasattr(l, 'scheduler'): 111 | l.scheduler.step() 112 | 113 | def get_loss_module(self): 114 | if self.nGPU == 1: 115 | return self.loss_module 116 | else: 117 | return self.loss_module.module 118 | 119 | def save(self, apath): 120 | torch.save(self.state_dict(), os.path.join(apath, 'loss.pt')) 121 | torch.save(self.log, os.path.join(apath, 'loss_log.pt')) 122 | 123 | def load(self, apath, cpu=False): 124 | if cpu: 125 | kwargs = {'map_location': lambda storage, loc: storage} 126 | else: 127 | kwargs = {} 128 | 129 | self.load_state_dict(torch.load( 130 | os.path.join(apath, 'loss.pt'), 131 | **kwargs 132 | )) 133 | self.log = torch.load(os.path.join(apath, 'loss_log.pt')) 134 | for l in self.loss_module: 135 | if hasattr(l, 'scheduler'): 136 | for _ in range(len(self.log)): l.scheduler.step() 137 | 138 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/loss/triplet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import torch 4 | from torch import nn 5 | from torch.nn import functional as F 6 | 7 | class TripletSemihardLoss(nn.Module): 8 | """ 9 | Shape: 10 | - Input: :math:`(N, C)` where `C = number of channels` 11 | - Target: :math:`(N)` 12 | - Output: scalar. 13 | """ 14 | 15 | def __init__(self, device, margin=0, size_average=True): 16 | super(TripletSemihardLoss, self).__init__() 17 | self.margin = margin 18 | self.size_average = size_average 19 | self.device = device 20 | 21 | def forward(self, input, target): 22 | y_true = target.int().unsqueeze(-1) 23 | same_id = torch.eq(y_true, y_true.t()).type_as(input) 24 | 25 | pos_mask = same_id 26 | neg_mask = 1 - same_id 27 | 28 | def _mask_max(input_tensor, mask, axis=None, keepdims=False): 29 | input_tensor = input_tensor - 1e6 * (1 - mask) 30 | _max, _idx = torch.max(input_tensor, dim=axis, keepdim=keepdims) 31 | return _max, _idx 32 | 33 | def _mask_min(input_tensor, mask, axis=None, keepdims=False): 34 | input_tensor = input_tensor + 1e6 * (1 - mask) 35 | _min, _idx = torch.min(input_tensor, dim=axis, keepdim=keepdims) 36 | return _min, _idx 37 | 38 | # output[i, j] = || feature[i, :] - feature[j, :] ||_2 39 | dist_squared = torch.sum(input ** 2, dim=1, keepdim=True) + \ 40 | torch.sum(input.t() ** 2, dim=0, keepdim=True) - \ 41 | 2.0 * torch.matmul(input, input.t()) 42 | dist = dist_squared.clamp(min=1e-16).sqrt() 43 | 44 | pos_max, pos_idx = _mask_max(dist, pos_mask, axis=-1) 45 | neg_min, neg_idx = _mask_min(dist, neg_mask, axis=-1) 46 | 47 | # loss(x, y) = max(0, -y * (x1 - x2) + margin) 48 | y = torch.ones(same_id.size()[0]).to(self.device) 49 | return F.margin_ranking_loss(neg_min.float(), 50 | pos_max.float(), 51 | y, 52 | self.margin, 53 | self.size_average) 54 | 55 | class TripletLoss_full(nn.Module): 56 | """Triplet loss with hard positive/negative mining. 57 | 58 | Reference: 59 | Hermans et al. In Defense of the Triplet Loss for Person Re-Identification. arXiv:1703.07737. 60 | 61 | Code imported from https://github.com/Cysu/open-reid/blob/master/reid/loss/triplet.py. 62 | 63 | Args: 64 | margin (float): margin for triplet. 65 | """ 66 | def __init__(self, margin=0.3, mutual_flag = False): 67 | super(TripletLoss, self).__init__() 68 | self.margin = margin 69 | self.ranking_loss = nn.MarginRankingLoss(margin=margin) 70 | self.mutual = mutual_flag 71 | 72 | def forward(self, inputs, targets): 73 | """ 74 | Args: 75 | inputs: feature matrix with shape (batch_size, feat_dim) 76 | targets: ground truth labels with shape (num_classes) 77 | """ 78 | n = inputs.size(0) 79 | #inputs = 1. * inputs / (torch.norm(inputs, 2, dim=-1, keepdim=True).expand_as(inputs) + 1e-12) 80 | # Compute pairwise distance, replace by the official when merged 81 | dist = torch.pow(inputs, 2).sum(dim=1, keepdim=True).expand(n, n) 82 | dist = dist + dist.t() 83 | dist.addmm_(1, -2, inputs, inputs.t()) 84 | dist = dist.clamp(min=1e-12).sqrt() # for numerical stability 85 | # For each anchor, find the hardest positive and negative 86 | mask = targets.expand(n, n).eq(targets.expand(n, n).t()) 87 | dist_ap, dist_an = [], [] 88 | for i in range(n): 89 | dist_ap.append(dist[i][mask[i]].max().unsqueeze(0)) 90 | # print(mask[i], dist[i], dist[i][mask[i]]) 91 | dist_an.append(dist[i][mask[i] == 0].min().unsqueeze(0)) 92 | dist_ap = torch.cat(dist_ap) 93 | dist_an = torch.cat(dist_an) 94 | # Compute ranking hinge loss 95 | y = torch.ones_like(dist_an) 96 | loss = self.ranking_loss(dist_an, dist_ap, y) 97 | if self.mutual: 98 | return loss, dist 99 | return loss 100 | 101 | 102 | 103 | class TripletLoss(nn.Module): 104 | """Triplet loss with hard positive/negative mining. 105 | 106 | Reference: 107 | Hermans et al. In Defense of the Triplet Loss for Person Re-Identification. arXiv:1703.07737. 108 | 109 | Code imported from https://github.com/Cysu/open-reid/blob/master/reid/loss/triplet.py. 110 | 111 | Args: 112 | margin (float): margin for triplet. 113 | """ 114 | def __init__(self, margin=0.3, mutual_flag = False): 115 | super(TripletLoss, self).__init__() 116 | self.margin = margin 117 | self.ranking_loss = nn.MarginRankingLoss(margin=margin) 118 | self.mutual = mutual_flag 119 | 120 | def forward(self, inputs, targets): 121 | """ 122 | Args: 123 | inputs: feature matrix with shape (batch_size, feat_dim) 124 | targets: ground truth labels with shape (num_classes) 125 | """ 126 | n = inputs.size(0) 127 | #inputs = 1. * inputs / (torch.norm(inputs, 2, dim=-1, keepdim=True).expand_as(inputs) + 1e-12) 128 | # Compute pairwise distance, replace by the official when merged 129 | dist = torch.pow(inputs, 2).sum(dim=1, keepdim=True).expand(n, n) 130 | dist = dist + dist.t() 131 | dist.addmm_(1, -2, inputs, inputs.t()) 132 | dist = dist.clamp(min=1e-12).sqrt() # for numerical stability 133 | # For each anchor, find the hardest positive and negative 134 | # mask = targets.expand(n, n).eq(targets.expand(n, n).t()) 135 | c = targets.size(1) 136 | targets_a = targets.view(1, -1, c) 137 | targets_b = targets.view(-1, 1, c) 138 | mask = targets_a * targets_b 139 | mask = torch.sum(mask, -1) 140 | dist_ap, dist_an = [], [] 141 | for i in range(n): 142 | dist_ap.append(dist[i][mask[i] > 0].median().unsqueeze(0)) 143 | # dist_ap.append(dist[i][mask[i] > 0].median().unsqueeze(0)) 144 | #dist_ap.append(dist[i][mask[i] > 0].max().unsqueeze(0)) 145 | # print(mask[i], dist[i], dist[i][mask[i]]) 146 | dist_an.append(dist[i][mask[i] == 0].min().unsqueeze(0)) 147 | dist_ap = torch.cat(dist_ap) 148 | dist_an = torch.cat(dist_an) 149 | # Compute ranking hinge loss 150 | y = torch.ones_like(dist_an) 151 | loss = self.ranking_loss(dist_an, dist_ap, y) 152 | if self.mutual: 153 | return loss, dist 154 | return loss 155 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/main.py: -------------------------------------------------------------------------------- 1 | import data 2 | import loss 3 | import torch 4 | import model 5 | from trainer import Trainer 6 | 7 | from option import args 8 | import utils.utility as utility 9 | 10 | ckpt = utility.checkpoint(args) 11 | 12 | loader = data.Data(args) 13 | model = model.Model(args, ckpt) 14 | loss = loss.Loss(args, ckpt) if not args.test_only else None 15 | trainer = Trainer(args, model, loss, loader, ckpt) 16 | 17 | n = 0 18 | while not trainer.terminate(): 19 | n += 1 20 | trainer.train() 21 | if (args.test_every!=0 and n%args.test_every==0) or n == 298 or n == 398 or n == 498 : 22 | trainer.test() 23 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/model/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/model/.DS_Store -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/model/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from importlib import import_module 3 | 4 | import torch 5 | import torch.nn as nn 6 | 7 | class Model(nn.Module): 8 | def __init__(self, args, ckpt): 9 | super(Model, self).__init__() 10 | print('[INFO] Making model...') 11 | 12 | self.device = torch.device('cpu' if args.cpu else 'cuda') 13 | self.nGPU = args.nGPU 14 | self.save_models = args.save_models 15 | 16 | module = import_module('model.' + args.model.lower()) 17 | self.model = module.make_model(args).to(self.device) 18 | 19 | if not args.cpu and args.nGPU > 1: 20 | self.model = nn.DataParallel(self.model, range(args.nGPU)) 21 | 22 | self.load( 23 | ckpt.dir, 24 | pre_train=args.pre_train, 25 | resume=args.resume, 26 | cpu=args.cpu 27 | ) 28 | 29 | def forward(self, x): 30 | return self.model(x) 31 | 32 | def get_model(self): 33 | if self.nGPU == 1: 34 | return self.model 35 | else: 36 | return self.model.module 37 | 38 | def save(self, apath, epoch, is_best=False): 39 | target = self.get_model() 40 | torch.save( 41 | target.state_dict(), 42 | os.path.join(apath, 'model', 'model_latest.pt') 43 | ) 44 | if is_best: 45 | torch.save( 46 | target.state_dict(), 47 | os.path.join(apath, 'model', 'model_best.pt') 48 | ) 49 | 50 | if self.save_models: 51 | torch.save( 52 | target.state_dict(), 53 | os.path.join(apath, 'model', 'model_{}.pt'.format(epoch)) 54 | ) 55 | 56 | def load(self, apath, pre_train='', resume=-1, cpu=False): 57 | if cpu: 58 | kwargs = {'map_location': lambda storage, loc: storage} 59 | else: 60 | kwargs = {} 61 | 62 | if resume == -1: 63 | self.get_model().load_state_dict( 64 | torch.load( 65 | os.path.join(apath, 'model', 'model_latest.pt'), 66 | **kwargs 67 | ), 68 | strict=False 69 | ) 70 | elif resume == 0: 71 | if pre_train != '': 72 | print('Loading model from {}'.format(pre_train)) 73 | self.get_model().load_state_dict( 74 | torch.load(pre_train, **kwargs), 75 | strict=False 76 | ) 77 | else: 78 | self.get_model().load_state_dict( 79 | torch.load( 80 | os.path.join(apath, 'model', 'model_{}.pt'.format(resume)), 81 | **kwargs 82 | ), 83 | strict=False 84 | ) -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/model/mgn.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | import torch 4 | from torch import nn 5 | import torch.nn.functional as F 6 | 7 | from torchvision.models.resnet import resnet50, Bottleneck 8 | 9 | def make_model(args): 10 | return MGN(args) 11 | 12 | class MGN(nn.Module): 13 | def __init__(self, args): 14 | super(MGN, self).__init__() 15 | num_classes = args.num_classes 16 | 17 | resnet = resnet50(pretrained=True) 18 | 19 | # self.backone = nn.Sequential( 20 | # resnet.conv1, 21 | # resnet.bn1, 22 | # resnet.relu, 23 | # resnet.maxpool, 24 | # resnet.layer1, 25 | # resnet.layer2, 26 | # resnet.layer3[0], 27 | # ) 28 | 29 | self.backone1 = nn.Sequential( 30 | resnet.conv1, 31 | resnet.bn1, 32 | resnet.relu, 33 | resnet.maxpool, 34 | resnet.layer1, 35 | ) 36 | 37 | self.backone2 = nn.Sequential( 38 | resnet.layer2, 39 | resnet.layer3[0], 40 | ) 41 | 42 | 43 | 44 | res_conv4 = nn.Sequential(*resnet.layer3[1:]) 45 | 46 | res_g_conv5 = resnet.layer4 47 | 48 | res_p_conv5 = nn.Sequential( 49 | Bottleneck(1024, 512, downsample=nn.Sequential(nn.Conv2d(1024, 2048, 1, bias=False), nn.BatchNorm2d(2048))), 50 | Bottleneck(2048, 512), 51 | Bottleneck(2048, 512)) 52 | res_p_conv5.load_state_dict(resnet.layer4.state_dict()) 53 | 54 | self.p1 = nn.Sequential(copy.deepcopy(res_conv4), copy.deepcopy(res_g_conv5)) 55 | self.p2 = nn.Sequential(copy.deepcopy(res_conv4), copy.deepcopy(res_p_conv5)) 56 | self.p3 = nn.Sequential(copy.deepcopy(res_conv4), copy.deepcopy(res_p_conv5)) 57 | 58 | 59 | if args.pool == 'max': 60 | pool2d = nn.MaxPool2d 61 | elif args.pool == 'avg': 62 | pool2d = nn.AvgPool2d 63 | else: 64 | raise Exception() 65 | 66 | self.maxpool_zg_p1 = pool2d(kernel_size=(12, 4)) 67 | self.maxpool_zg_p2 = pool2d(kernel_size=(24, 8)) 68 | self.maxpool_zg_p3 = pool2d(kernel_size=(24, 8)) 69 | self.maxpool_zp2 = pool2d(kernel_size=(12, 8)) 70 | self.maxpool_zp3 = pool2d(kernel_size=(8, 8)) 71 | 72 | self.maxpool_low_level_feat = pool2d(kernel_size=(96, 32)) 73 | 74 | reduction = nn.Sequential(nn.Conv2d(2048, args.feats, 1, bias=False), nn.BatchNorm2d(args.feats), nn.ReLU()) 75 | 76 | self._init_reduction(reduction) 77 | self.reduction_0 = copy.deepcopy(reduction) 78 | self.reduction_1 = copy.deepcopy(reduction) 79 | self.reduction_2 = copy.deepcopy(reduction) 80 | self.reduction_3 = copy.deepcopy(reduction) 81 | self.reduction_4 = copy.deepcopy(reduction) 82 | self.reduction_5 = copy.deepcopy(reduction) 83 | self.reduction_6 = copy.deepcopy(reduction) 84 | self.reduction_7 = copy.deepcopy(reduction) 85 | 86 | #self.fc_id_2048_0 = nn.Linear(2048, num_classes) 87 | self.fc_id_2048_0 = nn.Linear(args.feats, num_classes) 88 | self.fc_id_2048_1 = nn.Linear(args.feats, num_classes) 89 | self.fc_id_2048_2 = nn.Linear(args.feats, num_classes) 90 | 91 | self.fc_id_256_1_0 = nn.Linear(args.feats, num_classes) 92 | self.fc_id_256_1_1 = nn.Linear(args.feats, num_classes) 93 | self.fc_id_256_2_0 = nn.Linear(args.feats, num_classes) 94 | self.fc_id_256_2_1 = nn.Linear(args.feats, num_classes) 95 | self.fc_id_256_2_2 = nn.Linear(args.feats, num_classes) 96 | 97 | self._init_fc(self.fc_id_2048_0) 98 | self._init_fc(self.fc_id_2048_1) 99 | self._init_fc(self.fc_id_2048_2) 100 | 101 | self._init_fc(self.fc_id_256_1_0) 102 | self._init_fc(self.fc_id_256_1_1) 103 | self._init_fc(self.fc_id_256_2_0) 104 | self._init_fc(self.fc_id_256_2_1) 105 | self._init_fc(self.fc_id_256_2_2) 106 | 107 | @staticmethod 108 | def _init_reduction(reduction): 109 | # conv 110 | nn.init.kaiming_normal_(reduction[0].weight, mode='fan_in') 111 | #nn.init.constant_(reduction[0].bias, 0.) 112 | 113 | # bn 114 | nn.init.normal_(reduction[1].weight, mean=1., std=0.02) 115 | nn.init.constant_(reduction[1].bias, 0.) 116 | 117 | @staticmethod 118 | def _init_fc(fc): 119 | nn.init.kaiming_normal_(fc.weight, mode='fan_out') 120 | #nn.init.normal_(fc.weight, std=0.001) 121 | nn.init.constant_(fc.bias, 0.) 122 | 123 | def forward(self, x): 124 | 125 | x = self.backone1(x) 126 | 127 | low_level_feat = self.maxpool_low_level_feat(x) 128 | low_level_feat = low_level_feat.view(-1, 256) 129 | 130 | x = self.backone2(x) 131 | 132 | p1 = self.p1(x) 133 | p2 = self.p2(x) 134 | p3 = self.p3(x) 135 | 136 | zg_p1 = self.maxpool_zg_p1(p1) 137 | zg_p2 = self.maxpool_zg_p2(p2) 138 | zg_p3 = self.maxpool_zg_p3(p3) 139 | 140 | zp2 = self.maxpool_zp2(p2) 141 | z0_p2 = zp2[:, :, 0:1, :] 142 | z1_p2 = zp2[:, :, 1:2, :] 143 | 144 | zp3 = self.maxpool_zp3(p3) 145 | z0_p3 = zp3[:, :, 0:1, :] 146 | z1_p3 = zp3[:, :, 1:2, :] 147 | z2_p3 = zp3[:, :, 2:3, :] 148 | 149 | fg_p1 = self.reduction_0(zg_p1).squeeze(dim=3).squeeze(dim=2) 150 | fg_p2 = self.reduction_1(zg_p2).squeeze(dim=3).squeeze(dim=2) 151 | fg_p3 = self.reduction_2(zg_p3).squeeze(dim=3).squeeze(dim=2) 152 | f0_p2 = self.reduction_3(z0_p2).squeeze(dim=3).squeeze(dim=2) 153 | f1_p2 = self.reduction_4(z1_p2).squeeze(dim=3).squeeze(dim=2) 154 | f0_p3 = self.reduction_5(z0_p3).squeeze(dim=3).squeeze(dim=2) 155 | f1_p3 = self.reduction_6(z1_p3).squeeze(dim=3).squeeze(dim=2) 156 | f2_p3 = self.reduction_7(z2_p3).squeeze(dim=3).squeeze(dim=2) 157 | 158 | ''' 159 | l_p1 = self.fc_id_2048_0(zg_p1.squeeze(dim=3).squeeze(dim=2)) 160 | l_p2 = self.fc_id_2048_1(zg_p2.squeeze(dim=3).squeeze(dim=2)) 161 | l_p3 = self.fc_id_2048_2(zg_p3.squeeze(dim=3).squeeze(dim=2)) 162 | ''' 163 | l_p1 = self.fc_id_2048_0(fg_p1) 164 | l_p2 = self.fc_id_2048_1(fg_p2) 165 | l_p3 = self.fc_id_2048_2(fg_p3) 166 | 167 | l0_p2 = self.fc_id_256_1_0(f0_p2) 168 | l1_p2 = self.fc_id_256_1_1(f1_p2) 169 | l0_p3 = self.fc_id_256_2_0(f0_p3) 170 | l1_p3 = self.fc_id_256_2_1(f1_p3) 171 | l2_p3 = self.fc_id_256_2_2(f2_p3) 172 | 173 | predict = torch.cat([fg_p1, fg_p2, fg_p3, f0_p2, f1_p2, f0_p3, f1_p3, f2_p3], dim=1) 174 | 175 | return predict, fg_p1, fg_p2, fg_p3, l_p1, l_p2, l_p3, l0_p2, l1_p2, l0_p3, l1_p3, l2_p3, low_level_feat 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/option.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | parser = argparse.ArgumentParser(description='MGN') 4 | 5 | parser.add_argument('--nThread', type=int, default=2, help='number of threads for data loading') 6 | parser.add_argument('--cpu', action='store_true', help='use cpu only') 7 | parser.add_argument('--nGPU', type=int, default=1, help='number of GPUs') 8 | 9 | parser.add_argument("--datadir", type=str, default="Market-1501-v15.09.15", help='dataset directory') 10 | parser.add_argument('--data_train', type=str, default='Market1501', help='train dataset name') 11 | parser.add_argument('--data_test', type=str, default='Market1501', help='test dataset name') 12 | 13 | parser.add_argument('--image_folder', action='store_true', help='dataset is in form of image folder') 14 | 15 | parser.add_argument('--reset', action='store_true', help='reset the training') 16 | parser.add_argument("--epochs", type=int, default=80, help='number of epochs to train') 17 | parser.add_argument('--test_every', type=int, default=20, help='do test per every N epochs') 18 | parser.add_argument("--batchid", type=int, default=16, help='the batch for id') 19 | parser.add_argument("--bagid", type=int, default=1, help='the number of id for per bag') 20 | parser.add_argument("--batchimage", type=int, default=4, help='the batch of per id') 21 | parser.add_argument("--batchtest", type=int, default=32, help='input batch size for test') 22 | parser.add_argument('--test_only', action='store_true', help='set this option to test the model') 23 | 24 | parser.add_argument('--model', default='MGN', help='model name') 25 | parser.add_argument('--loss', type=str, default='1*CrossEntropy+1*Triplet', help='loss function configuration') 26 | 27 | parser.add_argument('--act', type=str, default='relu', help='activation function') 28 | parser.add_argument('--pool', type=str, default='avg', help='pool function') 29 | parser.add_argument('--feats', type=int, default=256, help='number of feature maps') 30 | parser.add_argument('--height', type=int, default=384, help='height of the input image') 31 | parser.add_argument('--width', type=int, default=128, help='width of the input image') 32 | parser.add_argument('--num_classes', type=int, default=751, help='') 33 | 34 | 35 | parser.add_argument("--lr", type=float, default=2e-4, help='learning rate') 36 | parser.add_argument('--optimizer', default='ADAM', choices=('SGD','ADAM','NADAM','RMSprop'), help='optimizer to use (SGD | ADAM | NADAM | RMSprop)') 37 | parser.add_argument('--momentum', type=float, default=0.9, help='SGD momentum') 38 | parser.add_argument('--dampening', type=float, default=0, help='SGD dampening') 39 | parser.add_argument('--nesterov', action='store_true', help='SGD nesterov') 40 | parser.add_argument('--beta1', type=float, default=0.9, help='ADAM beta1') 41 | parser.add_argument('--beta2', type=float, default=0.999, help='ADAM beta2') 42 | parser.add_argument('--amsgrad', action='store_true', help='ADAM amsgrad') 43 | parser.add_argument('--epsilon', type=float, default=1e-8, help='ADAM epsilon for numerical stability') 44 | parser.add_argument('--gamma', type=float, default=0.1, help='learning rate decay factor for step decay') 45 | parser.add_argument('--weight_decay', type=float, default=5e-4, help='weight decay') 46 | parser.add_argument('--decay_type', type=str, default='step', help='learning rate decay type') 47 | parser.add_argument('--lr_decay', type=int, default=60, help='learning rate decay per N epochs') 48 | 49 | parser.add_argument("--margin", type=float, default=1.2, help='') 50 | parser.add_argument("--re_rank", action='store_true', help='') 51 | parser.add_argument("--random_erasing", action='store_true', help='') 52 | parser.add_argument("--probability", type=float, default=0.5, help='') 53 | 54 | parser.add_argument("--savedir", type=str, default='saved_models', help='directory name to save') 55 | parser.add_argument("--outdir", type=str, default='out', help='') 56 | parser.add_argument("--resume", type=int, default=0, help='resume from specific checkpoint') 57 | parser.add_argument('--save', type=str, default='test', help='file name to save') 58 | parser.add_argument('--load', type=str, default='', help='file name to load') 59 | parser.add_argument('--save_models', action='store_true', help='save all intermediate models') 60 | parser.add_argument('--pre_train', type=str, default='', help='pre-trained model directory') 61 | 62 | args = parser.parse_args() 63 | 64 | for arg in vars(args): 65 | if vars(args)[arg] == 'True': 66 | vars(args)[arg] = True 67 | elif vars(args)[arg] == 'False': 68 | vars(args)[arg] = False 69 | 70 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/requirements.txt: -------------------------------------------------------------------------------- 1 | torchvision 2 | matplotlib 3 | argparse 4 | sklearn 5 | pytorch 6 | pillow 7 | numpy 8 | scipy 9 | tqdm 10 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/trainer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import numpy as np 4 | import utils.utility as utility 5 | from scipy.spatial.distance import cdist 6 | from utils.functions import cmc, mean_ap 7 | from utils.re_ranking import re_ranking 8 | import torch.nn.functional as F 9 | 10 | 11 | from PIL import Image 12 | from torchvision import transforms 13 | 14 | class Trainer(): 15 | def __init__(self, args, model, loss, loader, ckpt): 16 | self.args = args 17 | self.train_loader = loader.train_loader 18 | self.query_loader = loader.query_loader 19 | self.test_loader = loader.test_loader 20 | # 21 | 22 | self.queryset = loader.queryset 23 | self.testset = loader.testset 24 | 25 | self.ckpt = ckpt 26 | self.model = model 27 | self.loss = loss 28 | self.lr = 0. 29 | self.optimizer = utility.make_optimizer(args, self.model) 30 | self.scheduler = utility.make_scheduler(args, self.optimizer) 31 | self.device = torch.device('cpu' if args.cpu else 'cuda') 32 | 33 | if args.load != '': 34 | self.optimizer.load_state_dict( 35 | torch.load(os.path.join(ckpt.dir, 'optimizer.pt')) 36 | ) 37 | for _ in range(len(ckpt.log)*args.test_every): self.scheduler.step() 38 | 39 | def full_to_weak(self, x, bagid, batchid, batchimage, num_classes): 40 | # print(x) 41 | x = F.one_hot(x, num_classes) 42 | x = x.view(batchid//bagid, bagid, batchimage, num_classes) 43 | x = torch.sum(x, dim=1, keepdim=True).type(torch.cuda.FloatTensor) 44 | x = x / bagid 45 | # x = x.expand(batchid//bagid, bagid, batchimage, num_classes) 46 | x = torch.cat([x]*bagid, dim = 1) 47 | # print(torch.argmax(x[0,0,0,:]), torch.argmax(x[0,1,0,:]) ) 48 | 49 | # print(x.size()) 50 | x = x.contiguous().view(-1, num_classes) 51 | x = 1. * x 52 | return x 53 | 54 | def train(self): 55 | self.scheduler.step() 56 | # self.loss.step() 57 | epoch = self.scheduler.last_epoch + 1 58 | lr = self.scheduler.get_lr()[0] 59 | if lr != self.lr: 60 | self.ckpt.write_log('[INFO] Epoch: {}\tLearning rate: {:.2e}'.format(epoch, lr)) 61 | self.lr = lr 62 | self.loss.start_log() 63 | self.model.train() 64 | 65 | for batch, (inputs, labels) in enumerate(self.train_loader): 66 | inputs = inputs.to(self.device) 67 | labels = labels.to(self.device) 68 | 69 | if inputs.size()[0] != self.args.batchid * self.args.batchimage: 70 | continue 71 | 72 | self.optimizer.zero_grad() 73 | outputs = self.model(inputs) 74 | 75 | labels_bag = self.full_to_weak(labels, self.args.bagid, self.args.batchid, self.args.batchimage, self.args.num_classes) 76 | # labels_deduced = deduce_label(outputs, labels_bag) 77 | 78 | loss = self.loss(outputs, labels_bag) 79 | 80 | loss.backward() 81 | self.optimizer.step() 82 | 83 | self.ckpt.write_log('\r[INFO] [{}/{}]\t{}/{}\t{}'.format( 84 | epoch, self.args.epochs, 85 | batch + 1, len(self.train_loader), 86 | self.loss.display_loss(batch)), 87 | end='' if batch+1 != len(self.train_loader) else '\n') 88 | 89 | self.loss.end_log(len(self.train_loader)) 90 | 91 | def train_full(self): 92 | self.scheduler.step() 93 | # self.loss.step() 94 | epoch = self.scheduler.last_epoch + 1 95 | lr = self.scheduler.get_lr()[0] 96 | if lr != self.lr: 97 | self.ckpt.write_log('[INFO] Epoch: {}\tLearning rate: {:.2e}'.format(epoch, lr)) 98 | self.lr = lr 99 | self.loss.start_log() 100 | self.model.train() 101 | 102 | for batch, (inputs, labels) in enumerate(self.train_loader): 103 | inputs = inputs.to(self.device) 104 | labels = labels.to(self.device) 105 | 106 | self.optimizer.zero_grad() 107 | outputs = self.model(inputs) 108 | loss = self.loss(outputs, labels) 109 | loss.backward() 110 | self.optimizer.step() 111 | 112 | self.ckpt.write_log('\r[INFO] [{}/{}]\t{}/{}\t{}'.format( 113 | epoch, self.args.epochs, 114 | batch + 1, len(self.train_loader), 115 | self.loss.display_loss(batch)), 116 | end='' if batch+1 != len(self.train_loader) else '\n') 117 | 118 | self.loss.end_log(len(self.train_loader)) 119 | 120 | def test(self): 121 | epoch = self.scheduler.last_epoch + 1 122 | self.ckpt.write_log('\n[INFO] Test:') 123 | self.model.eval() 124 | 125 | self.ckpt.add_log(torch.zeros(1, 5)) 126 | qf = self.extract_feature(self.query_loader).numpy() 127 | gf = self.extract_feature(self.test_loader).numpy() 128 | 129 | if self.args.re_rank: 130 | q_g_dist = np.dot(qf, np.transpose(gf)) 131 | q_q_dist = np.dot(qf, np.transpose(qf)) 132 | g_g_dist = np.dot(gf, np.transpose(gf)) 133 | dist = re_ranking(q_g_dist, q_q_dist, g_g_dist) 134 | else: 135 | dist = cdist(qf, gf) 136 | r = cmc(dist, self.queryset.ids, self.testset.ids, self.queryset.cameras, self.testset.cameras, 137 | separate_camera_set=False, 138 | # separate_camera_set=True, 139 | single_gallery_shot=False, 140 | # single_gallery_shot=True, 141 | first_match_break=True) 142 | m_ap = mean_ap(dist, self.queryset.ids, self.testset.ids, self.queryset.cameras, self.testset.cameras) 143 | 144 | self.ckpt.log[-1, 0] = m_ap 145 | self.ckpt.log[-1, 1] = r[0] 146 | self.ckpt.log[-1, 2] = r[2] 147 | self.ckpt.log[-1, 3] = r[4] 148 | self.ckpt.log[-1, 4] = r[9] 149 | best = self.ckpt.log.max(0) 150 | self.ckpt.write_log( 151 | '[INFO] mAP: {:.4f} rank1: {:.4f} rank3: {:.4f} rank5: {:.4f} rank10: {:.4f} (Best: {:.4f} @epoch {})'.format( 152 | m_ap, 153 | r[0], r[2], r[4], r[9], 154 | best[0][0], 155 | (best[1][0] + 1)*self.args.test_every 156 | ) 157 | ) 158 | if not self.args.test_only: 159 | self.ckpt.save(self, epoch, is_best=((best[1][0] + 1)*self.args.test_every == epoch)) 160 | 161 | def fliphor(self, inputs): 162 | inv_idx = torch.arange(inputs.size(3)-1,-1,-1).long() # N x C x H x W 163 | return inputs.index_select(3,inv_idx) 164 | 165 | 166 | def save_images(self, data, i): 167 | # if not os.path.exists(processed_image_path): 168 | # os.mkdir(processed_image_path) 169 | # one_folder = os.path.join(processed_image_path,relative_folder) 170 | # if not os.path.exists(one_folder): 171 | # os.mkdir(one_folder) 172 | # full_path = os.path.join(one_folder,filename) 173 | # # restoreed_img_data = tensor_to_PIL(data) 174 | unloader_ = transforms.ToPILImage() 175 | reverse_mean = [-0.485, -0.456, -0.406] 176 | reverse_std = [1/0.229, 1/0.224, 1/0.225] 177 | # norm_ = transforms.Normalize(reverse_mean, reverse_std) 178 | 179 | # reverse_mean = [-0.485, -0.456, -0.406] 180 | # reverse_std = [1/0.229, 1/0.224, 1/0.225] 181 | # recreated_im = copy.copy(im_as_var.data.numpy()[0]) 182 | for c in range(3): 183 | data[c,:,:] = data[c,:,:]/reverse_std[c] 184 | data[c,:,:] = data[c,:,:]-reverse_mean[c] 185 | data[data > 1] = 1 186 | data[data < 0] = 0 187 | data = data * 255 188 | 189 | # data = data.type(torch.FloatTensor) 190 | data = data.type(torch.uint8) 191 | 192 | 193 | # print('before data:',data) 194 | # data=norm_(data.squeeze(0)) 195 | # print('after data:',data) 196 | # data=data.squeeze(0) 197 | image = data.cpu().clone() 198 | # image = image.squeeze(0) 199 | image = unloader_(image) 200 | 201 | # write image 202 | image.save('/home/wangguangrun/pytorch-image-model/MGN-pytorch/work_dirs/images/' + str(i) + '.jpg') 203 | 204 | 205 | def extract_feature(self, loader): 206 | features = torch.FloatTensor() 207 | count = 0 208 | flag = False 209 | for (inputs, labels) in loader: 210 | 211 | if flag: 212 | cc = 0 213 | for xx in range(10): 214 | cc += 1 215 | img_to_save = inputs[xx,:,:,:] 216 | self.save_images(img_to_save, cc) 217 | flag = False 218 | 219 | if count%10 ==0: 220 | print(float(count)/len(loader), len(loader) ) 221 | count += 1 222 | ff = torch.FloatTensor(inputs.size(0), 2048).zero_() 223 | for i in range(2): 224 | if i==1: 225 | inputs = self.fliphor(inputs) 226 | input_img = inputs.to(self.device) 227 | outputs = self.model(input_img) 228 | f = outputs[0].data.cpu() 229 | ff = ff + f 230 | 231 | fnorm = torch.norm(ff, p=2, dim=1, keepdim=True) 232 | ff = ff.div(fnorm.expand_as(ff)) 233 | 234 | features = torch.cat((features, ff), 0) 235 | return features 236 | 237 | def terminate(self): 238 | if self.args.test_only: 239 | self.test() 240 | return True 241 | else: 242 | epoch = self.scheduler.last_epoch + 1 243 | return epoch >= self.args.epochs 244 | 245 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/.DS_Store -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/functions.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import numpy as np 3 | import torch 4 | from sklearn.metrics import average_precision_score 5 | 6 | 7 | def _unique_sample(ids_dict, num): 8 | mask = np.zeros(num, dtype=np.bool) 9 | for _, indices in ids_dict.items(): 10 | i = np.random.choice(indices) 11 | mask[i] = True 12 | return mask 13 | 14 | 15 | def cmc(distmat, query_ids=None, gallery_ids=None, 16 | query_cams=None, gallery_cams=None, topk=100, 17 | separate_camera_set=False, 18 | single_gallery_shot=False, 19 | first_match_break=False): 20 | m, n = distmat.shape 21 | # Fill up default values 22 | if query_ids is None: 23 | query_ids = np.arange(m) 24 | if gallery_ids is None: 25 | gallery_ids = np.arange(n) 26 | if query_cams is None: 27 | query_cams = np.zeros(m).astype(np.int32) 28 | if gallery_cams is None: 29 | gallery_cams = np.ones(n).astype(np.int32) 30 | # Ensure numpy array 31 | query_ids = np.asarray(query_ids) 32 | gallery_ids = np.asarray(gallery_ids) 33 | query_cams = np.asarray(query_cams) 34 | gallery_cams = np.asarray(gallery_cams) 35 | # Sort and find correct matches 36 | indices = np.argsort(distmat, axis=1) 37 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis]) 38 | 39 | a = indices.astype(int) 40 | b = matches.astype(int) 41 | np.savetxt('/home/wangguangrun/pytorch-image-model/MGN-pytorch/work_dirs/list_images/indices.txt', a, '%d') 42 | np.savetxt('/home/wangguangrun/pytorch-image-model/MGN-pytorch/work_dirs/list_images/matches.txt', b, '%d') 43 | 44 | # Compute CMC for each query 45 | ret = np.zeros(topk) 46 | num_valid_queries = 0 47 | 48 | 49 | for i in range(m): 50 | # Filter out the same id and same camera 51 | valid = ((gallery_ids[indices[i]] != query_ids[i]) | 52 | (gallery_cams[indices[i]] != query_cams[i])) 53 | if separate_camera_set: 54 | # Filter out samples from same camera 55 | valid &= (gallery_cams[indices[i]] != query_cams[i]) 56 | if not np.any(matches[i, valid]): 57 | continue 58 | if single_gallery_shot: 59 | repeat = 10 60 | gids = gallery_ids[indices[i][valid]] 61 | inds = np.where(valid)[0] 62 | ids_dict = defaultdict(list) 63 | for j, x in zip(inds, gids): 64 | ids_dict[x].append(j) # indexes + ids 65 | else: 66 | repeat = 1 67 | for _ in range(repeat): 68 | if single_gallery_shot: 69 | # Randomly choose one instance for each id 70 | sampled = (valid & _unique_sample(ids_dict, len(valid))) 71 | index = np.nonzero(matches[i, sampled])[0] 72 | else: 73 | index = np.nonzero(matches[i, valid])[0] ## 这是因为第0个是自己。要去掉自己。 74 | 75 | delta = 1. / (len(index) * repeat) 76 | for j, k in enumerate(index): 77 | # print(j) 78 | if k - j >= topk: 79 | break 80 | if first_match_break: 81 | # print(indices, k - j, 'aaaaaaaaaaaaaaaaaaaaaa') 82 | ret[k - j] += 1 # 差代表去掉不满足条件的那些,递补排序 83 | break 84 | ret[k - j] += delta 85 | num_valid_queries += 1 86 | if num_valid_queries == 0: 87 | raise RuntimeError("No valid query") 88 | return ret.cumsum() / num_valid_queries 89 | 90 | 91 | def mean_ap(distmat, query_ids=None, gallery_ids=None, 92 | query_cams=None, gallery_cams=None): 93 | m, n = distmat.shape 94 | # Fill up default values 95 | if query_ids is None: 96 | query_ids = np.arange(m) 97 | if gallery_ids is None: 98 | gallery_ids = np.arange(n) 99 | if query_cams is None: 100 | query_cams = np.zeros(m).astype(np.int32) 101 | if gallery_cams is None: 102 | gallery_cams = np.ones(n).astype(np.int32) 103 | # Ensure numpy array 104 | query_ids = np.asarray(query_ids) 105 | gallery_ids = np.asarray(gallery_ids) 106 | query_cams = np.asarray(query_cams) 107 | gallery_cams = np.asarray(gallery_cams) 108 | # Sort and find correct matches 109 | indices = np.argsort(distmat, axis=1) 110 | matches = (gallery_ids[indices] == query_ids[:, np.newaxis]) 111 | # Compute AP for each query 112 | aps = [] 113 | for i in range(m): 114 | # Filter out the same id and same camera 115 | valid = ((gallery_ids[indices[i]] != query_ids[i]) | 116 | (gallery_cams[indices[i]] != query_cams[i])) 117 | y_true = matches[i, valid] 118 | y_score = -distmat[i][indices[i]][valid] 119 | if not np.any(y_true): 120 | continue 121 | aps.append(average_precision_score(y_true, y_score)) 122 | if len(aps) == 0: 123 | raise RuntimeError("No valid query") 124 | return np.mean(aps) 125 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/n_adam.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Mar 14, 2018 3 | @author: jyzhang 4 | ''' 5 | 6 | import math 7 | import torch 8 | from torch.optim import Optimizer 9 | 10 | 11 | class NAdam(torch.optim.Optimizer): 12 | """Implements Nesterov-accelerated Adam algorithm according to Keras. 13 | 14 | parameter name alias in different algorithms 15 | NAdam Keras 054_report 16 | exp_avg m_t m_t 17 | exp_avg_prime prime{m}_t prime{m}_t 18 | exp_avg_bar \\bar{m}_t bar{m}_t 19 | exp_avg_sq v_t n_t 20 | exp_avg_sq_prime prime{v}_t prime{n}_t 21 | beta1 beta_1 mu 22 | beta2 beta_2 v=0.999 23 | 24 | It has been proposed in `Incorporating Nesterov Momentum into Adam`_. 25 | Arguments: 26 | params (iterable): iterable of parameters to optimize or dicts defining 27 | parameter groups 28 | lr (float, optional): learning rate (default: 1e-3) 29 | betas (Tuple[float, float], optional): coefficients used for computing 30 | running averages of gradient and its square (default: (0.9, 0.999)) 31 | eps (float, optional): term added to the denominator to improve 32 | numerical stability (default: 1e-8) 33 | weight_decay (float, optional): weight decay (L2 penalty) (default: 0), 34 | but not used in NAdam 35 | schedule_decay (float, optional): coefficients used for computing 36 | moment schedule (default: 0.004) 37 | .. _Incorporating Nesterov Momentum into Adam 38 | http://cs229.stanford.edu/proj2015/054_report.pdf 39 | .. _On the importance of initialization and momentum in deep learning 40 | http://www.cs.toronto.edu/~fritz/absps/momentum.pdf 41 | """ 42 | 43 | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, 44 | weight_decay=0, schedule_decay=0.004): 45 | if not 0.0 <= betas[0] < 1.0: 46 | raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0])) 47 | if not 0.0 <= betas[1] < 1.0: 48 | raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1])) 49 | defaults = dict(lr=lr, betas=betas, eps=eps, 50 | weight_decay=weight_decay, schedule_decay=schedule_decay) 51 | super(NAdam, self).__init__(params, defaults) 52 | 53 | def __setstate__(self, state): 54 | super(NAdam, self).__setstate__(state) 55 | 56 | def step(self, closure=None): 57 | """Performs a single optimization step. 58 | Arguments: 59 | closure (callable, optional): A closure that reevaluates the model 60 | and returns the loss. 61 | """ 62 | loss = None 63 | if closure is not None: 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 | grad = p.grad.data 71 | if grad.is_sparse: 72 | raise RuntimeError('NAdam does not support sparse gradients') 73 | 74 | state = self.state[p] 75 | 76 | # State initialization 77 | if len(state) == 0: 78 | state['step'] = 0 79 | # Exponential moving average of gradient values 80 | state['exp_avg'] = torch.zeros_like(p.data) 81 | # Exponential moving average of squared gradient values 82 | state['exp_avg_sq'] = torch.zeros_like(p.data) 83 | # \mu^{t} 84 | state['m_schedule'] = 1. 85 | 86 | exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq'] 87 | 88 | beta1, beta2 = group['betas'] 89 | 90 | schedule_decay = group['schedule_decay'] 91 | 92 | state['step'] += 1 93 | 94 | if group['weight_decay'] != 0: 95 | grad = grad.add(group['weight_decay'], p.data) 96 | 97 | # calculate the momentum cache \mu^{t} and \mu^{t+1} 98 | momentum_cache_t = beta1 * ( \ 99 | 1. - 0.5 * (pow(0.96, state['step'] * schedule_decay))) 100 | momentum_cache_t_1 = beta1 * ( \ 101 | 1. - 0.5 * (pow(0.96, (state['step'] + 1) * schedule_decay))) 102 | m_schedule_new = state['m_schedule'] * momentum_cache_t 103 | m_schedule_next = state['m_schedule'] * momentum_cache_t * momentum_cache_t_1 104 | 105 | # Decay the first and second moment running average coefficient 106 | exp_avg.mul_(beta1).add_(1 - beta1, grad) 107 | exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad) 108 | 109 | g_prime = torch.div( grad, 1. - m_schedule_new) 110 | exp_avg_prime = torch.div( exp_avg, 1. - m_schedule_next ) 111 | exp_avg_sq_prime = torch.div(exp_avg_sq, 1. - pow(beta2, state['step'])) 112 | 113 | exp_avg_bar = torch.add( (1. - momentum_cache_t) * g_prime, \ 114 | momentum_cache_t_1, exp_avg_prime ) 115 | 116 | denom = exp_avg_sq_prime.sqrt().add_(group['eps']) 117 | 118 | step_size = group['lr'] 119 | 120 | p.data.addcdiv_(-step_size, exp_avg_bar, denom) 121 | 122 | return loss -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/nadam.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.optim import Optimizer 3 | 4 | 5 | class Nadam(Optimizer): 6 | """Implements Nadam algorithm (a variant of Adam based on Nesterov momentum). 7 | It has been proposed in `Incorporating Nesterov Momentum into Adam`__. 8 | Arguments: 9 | params (iterable): iterable of parameters to optimize or dicts defining 10 | parameter groups 11 | lr (float, optional): learning rate (default: 2e-3) 12 | betas (Tuple[float, float], optional): coefficients used for computing 13 | running averages of gradient and its square 14 | eps (float, optional): term added to the denominator to improve 15 | numerical stability (default: 1e-8) 16 | weight_decay (float, optional): weight decay (L2 penalty) (default: 0) 17 | schedule_decay (float, optional): momentum schedule decay (default: 4e-3) 18 | __ http://cs229.stanford.edu/proj2015/054_report.pdf 19 | __ http://www.cs.toronto.edu/~fritz/absps/momentum.pdf 20 | """ 21 | 22 | def __init__(self, params, lr=2e-3, betas=(0.9, 0.999), eps=1e-8, 23 | weight_decay=0, schedule_decay=4e-3): 24 | defaults = dict(lr=lr, betas=betas, eps=eps, 25 | weight_decay=weight_decay, schedule_decay=schedule_decay) 26 | super(Nadam, self).__init__(params, defaults) 27 | 28 | def step(self, closure=None): 29 | """Performs a single optimization step. 30 | Arguments: 31 | closure (callable, optional): A closure that reevaluates the model 32 | and returns the loss. 33 | """ 34 | loss = None 35 | if closure is not None: 36 | loss = closure() 37 | 38 | for group in self.param_groups: 39 | for p in group['params']: 40 | if p.grad is None: 41 | continue 42 | grad = p.grad.data 43 | state = self.state[p] 44 | 45 | # State initialization 46 | if len(state) == 0: 47 | state['step'] = 0 48 | state['m_schedule'] = 1. 49 | state['exp_avg'] = grad.new().resize_as_(grad).zero_() 50 | state['exp_avg_sq'] = grad.new().resize_as_(grad).zero_() 51 | 52 | # Warming momentum schedule 53 | m_schedule = state['m_schedule'] 54 | schedule_decay = group['schedule_decay'] 55 | exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq'] 56 | beta1, beta2 = group['betas'] 57 | eps = group['eps'] 58 | 59 | state['step'] += 1 60 | 61 | if group['weight_decay'] != 0: 62 | grad = grad.add(group['weight_decay'], p.data) 63 | 64 | momentum_cache_t = beta1 * \ 65 | (1. - 0.5 * (0.96 ** (state['step'] * schedule_decay))) 66 | momentum_cache_t_1 = beta1 * \ 67 | (1. - 0.5 * 68 | (0.96 ** ((state['step'] + 1) * schedule_decay))) 69 | m_schedule_new = m_schedule * momentum_cache_t 70 | m_schedule_next = m_schedule * momentum_cache_t * momentum_cache_t_1 71 | state['m_schedule'] = m_schedule_new 72 | 73 | # Decay the first and second moment running average coefficient 74 | bias_correction2 = 1 - beta2 ** state['step'] 75 | 76 | exp_avg.mul_(beta1).add_(1 - beta1, grad) 77 | exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad) 78 | exp_avg_sq_prime = exp_avg_sq.div(1. - bias_correction2) 79 | 80 | denom = exp_avg_sq_prime.sqrt_().add_(group['eps']) 81 | 82 | p.data.addcdiv_(-group['lr']*(1. - momentum_cache_t)/(1. - m_schedule_new), grad, denom) 83 | p.data.addcdiv_(-group['lr']*momentum_cache_t_1/(1. - m_schedule_next), exp_avg, denom) 84 | 85 | return loss -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/random_erasing.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from torchvision.transforms import * 4 | 5 | from PIL import Image 6 | import random 7 | import math 8 | import numpy as np 9 | import torch 10 | 11 | class RandomErasing(object): 12 | """ Randomly selects a rectangle region in an image and erases its pixels. 13 | 'Random Erasing Data Augmentation' by Zhong et al. 14 | See https://arxiv.org/pdf/1708.04896.pdf 15 | Args: 16 | probability: The probability that the Random Erasing operation will be performed. 17 | sl: Minimum proportion of erased area against input image. 18 | sh: Maximum proportion of erased area against input image. 19 | r1: Minimum aspect ratio of erased area. 20 | mean: Erasing value. 21 | """ 22 | 23 | def __init__(self, probability = 0.5, sl = 0.02, sh = 0.4, r1 = 0.3, mean=[0.4914, 0.4822, 0.4465]): 24 | self.probability = probability 25 | self.mean = mean 26 | self.sl = sl 27 | self.sh = sh 28 | self.r1 = r1 29 | 30 | def __call__(self, img): 31 | 32 | if random.uniform(0, 1) > self.probability: 33 | return img 34 | 35 | for attempt in range(100): 36 | area = img.size()[1] * img.size()[2] 37 | 38 | target_area = random.uniform(self.sl, self.sh) * area 39 | aspect_ratio = random.uniform(self.r1, 1/self.r1) 40 | 41 | h = int(round(math.sqrt(target_area * aspect_ratio))) 42 | w = int(round(math.sqrt(target_area / aspect_ratio))) 43 | 44 | if w < img.size()[2] and h < img.size()[1]: 45 | x1 = random.randint(0, img.size()[1] - h) 46 | y1 = random.randint(0, img.size()[2] - w) 47 | if img.size()[0] == 3: 48 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 49 | img[1, x1:x1+h, y1:y1+w] = self.mean[1] 50 | img[2, x1:x1+h, y1:y1+w] = self.mean[2] 51 | else: 52 | img[0, x1:x1+h, y1:y1+w] = self.mean[0] 53 | return img 54 | 55 | return img 56 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/re_ranking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Mon Jun 26 14:46:56 2017 5 | @author: luohao 6 | Modified by Houjing Huang, 2017-12-22. 7 | - This version accepts distance matrix instead of raw features. 8 | - The difference of `/` division between python 2 and 3 is handled. 9 | - numpy.float16 is replaced by numpy.float32 for numerical precision. 10 | 11 | Modified by Zhedong Zheng, 2018-1-12. 12 | - replace sort with topK, which save about 30s. 13 | """ 14 | 15 | """ 16 | CVPR2017 paper:Zhong Z, Zheng L, Cao D, et al. Re-ranking Person Re-identification with k-reciprocal Encoding[J]. 2017. 17 | url:http://openaccess.thecvf.com/content_cvpr_2017/papers/Zhong_Re-Ranking_Person_Re-Identification_CVPR_2017_paper.pdf 18 | Matlab version: https://github.com/zhunzhong07/person-re-ranking 19 | """ 20 | 21 | """ 22 | API 23 | q_g_dist: query-gallery distance matrix, numpy array, shape [num_query, num_gallery] 24 | q_q_dist: query-query distance matrix, numpy array, shape [num_query, num_query] 25 | g_g_dist: gallery-gallery distance matrix, numpy array, shape [num_gallery, num_gallery] 26 | k1, k2, lambda_value: parameters, the original paper is (k1=20, k2=6, lambda_value=0.3) 27 | Returns: 28 | final_dist: re-ranked distance, numpy array, shape [num_query, num_gallery] 29 | """ 30 | 31 | 32 | import numpy as np 33 | 34 | def k_reciprocal_neigh( initial_rank, i, k1): 35 | forward_k_neigh_index = initial_rank[i,:k1+1] 36 | backward_k_neigh_index = initial_rank[forward_k_neigh_index,:k1+1] 37 | fi = np.where(backward_k_neigh_index==i)[0] 38 | return forward_k_neigh_index[fi] 39 | 40 | def re_ranking(q_g_dist, q_q_dist, g_g_dist, k1=20, k2=6, lambda_value=0.3): 41 | # The following naming, e.g. gallery_num, is different from outer scope. 42 | # Don't care about it. 43 | original_dist = np.concatenate( 44 | [np.concatenate([q_q_dist, q_g_dist], axis=1), 45 | np.concatenate([q_g_dist.T, g_g_dist], axis=1)], 46 | axis=0) 47 | original_dist = 2. - 2 * original_dist #np.power(original_dist, 2).astype(np.float32) 48 | original_dist = np.transpose(1. * original_dist/np.max(original_dist,axis = 0)) 49 | V = np.zeros_like(original_dist).astype(np.float32) 50 | #initial_rank = np.argsort(original_dist).astype(np.int32) 51 | # top K1+1 52 | initial_rank = np.argpartition( original_dist, range(1,k1+1) ) 53 | 54 | query_num = q_g_dist.shape[0] 55 | all_num = original_dist.shape[0] 56 | 57 | for i in range(all_num): 58 | # k-reciprocal neighbors 59 | k_reciprocal_index = k_reciprocal_neigh( initial_rank, i, k1) 60 | k_reciprocal_expansion_index = k_reciprocal_index 61 | for j in range(len(k_reciprocal_index)): 62 | candidate = k_reciprocal_index[j] 63 | candidate_k_reciprocal_index = k_reciprocal_neigh( initial_rank, candidate, int(np.around(k1/2))) 64 | if len(np.intersect1d(candidate_k_reciprocal_index,k_reciprocal_index))> 2./3*len(candidate_k_reciprocal_index): 65 | k_reciprocal_expansion_index = np.append(k_reciprocal_expansion_index,candidate_k_reciprocal_index) 66 | 67 | k_reciprocal_expansion_index = np.unique(k_reciprocal_expansion_index) 68 | weight = np.exp(-original_dist[i,k_reciprocal_expansion_index]) 69 | V[i,k_reciprocal_expansion_index] = 1.*weight/np.sum(weight) 70 | 71 | original_dist = original_dist[:query_num,] 72 | if k2 != 1: 73 | V_qe = np.zeros_like(V,dtype=np.float32) 74 | for i in range(all_num): 75 | V_qe[i,:] = np.mean(V[initial_rank[i,:k2],:],axis=0) 76 | V = V_qe 77 | del V_qe 78 | del initial_rank 79 | invIndex = [] 80 | for i in range(all_num): 81 | invIndex.append(np.where(V[:,i] != 0)[0]) 82 | 83 | jaccard_dist = np.zeros_like(original_dist,dtype = np.float32) 84 | 85 | for i in range(query_num): 86 | temp_min = np.zeros(shape=[1,all_num],dtype=np.float32) 87 | indNonZero = np.where(V[i,:] != 0)[0] 88 | indImages = [] 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]],V[indImages[j],indNonZero[j]]) 92 | jaccard_dist[i] = 1-temp_min/(2.-temp_min) 93 | 94 | final_dist = jaccard_dist*(1-lambda_value) + original_dist*lambda_value 95 | del original_dist 96 | del V 97 | del jaccard_dist 98 | final_dist = final_dist[:query_num,query_num:] 99 | return final_dist 100 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/utils/utility.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | import matplotlib 5 | matplotlib.use('Agg') 6 | import matplotlib.pyplot as plt 7 | 8 | import numpy as np 9 | import scipy.misc as misc 10 | 11 | import torch 12 | import torch.optim as optim 13 | from utils.nadam import Nadam 14 | from utils.n_adam import NAdam 15 | import torch.optim.lr_scheduler as lrs 16 | 17 | class checkpoint(): 18 | def __init__(self, args): 19 | self.args = args 20 | self.log = torch.Tensor() 21 | now = datetime.datetime.now().strftime('%Y-%m-%d-%H:%M:%S') 22 | 23 | if args.load == '': 24 | if args.save == '': args.save = now 25 | self.dir = 'work_dirs/' + args.save 26 | else: 27 | self.dir = 'work_dirs/' + args.load 28 | if not os.path.exists(self.dir): 29 | args.load = '' 30 | else: 31 | self.log = torch.load(self.dir + '/map_log.pt') 32 | print('Continue from epoch {}...'.format(len(self.log)*args.test_every)) 33 | 34 | if args.reset: 35 | os.system('rm -rf ' + self.dir) 36 | args.load = '' 37 | 38 | def _make_dir(path): 39 | if not os.path.exists(path): os.makedirs(path) 40 | 41 | _make_dir(self.dir) 42 | _make_dir(self.dir + '/model') 43 | _make_dir(self.dir + '/results') 44 | 45 | open_type = 'a' if os.path.exists(self.dir + '/log.txt') else 'w' 46 | self.log_file = open(self.dir + '/log.txt', open_type) 47 | with open(self.dir + '/config.txt', open_type) as f: 48 | f.write(now + '\n\n') 49 | for arg in vars(args): 50 | f.write('{}: {}\n'.format(arg, getattr(args, arg))) 51 | f.write('\n') 52 | 53 | def save(self, trainer, epoch, is_best=False): 54 | trainer.model.save(self.dir, epoch, is_best=is_best) 55 | trainer.loss.save(self.dir) 56 | trainer.loss.plot_loss(self.dir, epoch) 57 | 58 | self.plot_map_rank(epoch) 59 | torch.save(self.log, os.path.join(self.dir, 'map_log.pt')) 60 | torch.save( 61 | trainer.optimizer.state_dict(), 62 | os.path.join(self.dir, 'optimizer.pt') 63 | ) 64 | 65 | def add_log(self, log): 66 | self.log = torch.cat([self.log, log]) 67 | 68 | def write_log(self, log, refresh=False, end='\n'): 69 | print(log, end=end) 70 | if end != '': 71 | self.log_file.write(log + end) 72 | if refresh: 73 | self.log_file.close() 74 | self.log_file = open(self.dir + '/log.txt', 'a') 75 | 76 | def done(self): 77 | self.log_file.close() 78 | 79 | def plot_map_rank(self, epoch): 80 | axis = np.linspace(1, epoch, self.log.size(0)) 81 | label = 'Reid on {}'.format(self.args.data_test) 82 | labels = ['mAP','rank1','rank3','rank5','rank10'] 83 | fig = plt.figure() 84 | plt.title(label) 85 | for i in range(len(labels)): 86 | plt.plot(axis, self.log[:, i].numpy(), label=labels[i]) 87 | 88 | plt.legend() 89 | plt.xlabel('Epochs') 90 | plt.ylabel('mAP/rank') 91 | plt.grid(True) 92 | plt.savefig('{}/test_{}.jpg'.format(self.dir, self.args.data_test)) 93 | plt.close(fig) 94 | 95 | def save_results(self, filename, save_list, scale): 96 | pass 97 | 98 | def make_optimizer(args, model): 99 | trainable = filter(lambda x: x.requires_grad, model.parameters()) 100 | 101 | if args.optimizer == 'SGD': 102 | optimizer_function = optim.SGD 103 | kwargs = { 104 | 'momentum': args.momentum, 105 | 'dampening': args.dampening, 106 | 'nesterov': args.nesterov 107 | } 108 | elif args.optimizer == 'ADAM': 109 | optimizer_function = optim.Adam 110 | kwargs = { 111 | 'betas': (args.beta1, args.beta2), 112 | 'eps': args.epsilon, 113 | 'amsgrad': args.amsgrad 114 | } 115 | elif args.optimizer == 'NADAM': 116 | optimizer_function = NAdam 117 | kwargs = { 118 | 'betas': (args.beta1, args.beta2), 119 | 'eps': args.epsilon 120 | } 121 | elif args.optimizer == 'RMSprop': 122 | optimizer_function = optim.RMSprop 123 | kwargs = { 124 | 'eps': args.epsilon, 125 | 'momentum': args.momentum 126 | } 127 | else: 128 | raise Exception() 129 | 130 | kwargs['lr'] = args.lr 131 | kwargs['weight_decay'] = args.weight_decay 132 | 133 | return optimizer_function(trainable, **kwargs) 134 | 135 | def make_scheduler(args, optimizer): 136 | if args.decay_type == 'step': 137 | scheduler = lrs.StepLR( 138 | optimizer, 139 | step_size=args.lr_decay, 140 | gamma=args.gamma 141 | ) 142 | elif args.decay_type.find('step') >= 0: 143 | milestones = args.decay_type.split('_') 144 | milestones.pop(0) 145 | milestones = list(map(lambda x: int(x), milestones)) 146 | scheduler = lrs.MultiStepLR( 147 | optimizer, 148 | milestones=milestones, 149 | gamma=args.gamma 150 | ) 151 | 152 | return scheduler 153 | 154 | -------------------------------------------------------------------------------- /GraphReID/UnaryTerm_PairwiseTerm_WeaklyTripletLoss/work_dirs/readme.md: -------------------------------------------------------------------------------- 1 | During training, checkpoints and logs are saved in this folder, which will occupies much memory of the hard disk. If you want to save your weights somewhere else, please use symlink, for example: 2 | 3 | ```shell 4 | ln -s /data1/wangguangrun/GraphReID_work_dirs work_dirs 5 | ``` 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SYSU-30k dataset, code, and pretrained model 2 | 3 | The Dataset, code, and pretrained model of "Weakly Supervised Person Re-ID: Differentiable Graphical Learning and A New Benchmark" https://arxiv.org/abs/1904.03845. 4 | 5 | ## Updated News 6 | 7 | ### Self-Supervised Person Re-ID (2022-05-22) 8 | 9 | Code for self-supervised person re-ID has been released. 10 | 11 | ### Self-Supervised Person Re-ID (2022-05-14) 12 | 13 | According to our experiments in self-supervised representation learning, we found that SYSU-30k can examine the quality of SSL algorithms. The code for training SSL can be found at [here](https://github.com/wanggrun/triplet). The code to test SSL is close to the test code in this project. Currently, an SSL method has achieved a 27.5% rank-1 accuracy on the SYSU-30k benchmark! 14 | 15 | ### Dataset in Google Drive (2021-11-04) 16 | 17 | The SYSU-30k dataset is also uploaded to Google driver. Both Google driver and Baidu pan are availble now. 18 | 19 | 20 | ## The source code of our weakly supervised re-ID is originally written by [**Guangcong Wang**](https://wanggcong.github.io) who has rich experiences in person re-ID ([follow his github](https://github.com/Wanggcong)), and is partially revised by [Guangrun Wang](https://wanggrun.github.io/). 21 | 22 | ## Leaderboard on SYSU-30k. 23 | 24 | The leaderboard is also at ["paperswithcode.com"](https://paperswithcode.com/sota/person-re-identification-on-sysu-30k) 25 | 26 | | Supervision | Method | Rank-1 | 27 | |:------------------:|:------------------:|:------------------:| 28 | | Generalization | [DARI [1]](https://arxiv.org/abs/1604.04377) | 11.2 | 29 | | Generalization | [DF [2]](https://arxiv.org/abs/1512.03622) | 10.3 | 30 | | Generalization | [ResNet-50 [3]](https://arxiv.org/abs/1512.03385) | 20.1 | 31 | | Generalization | [Local-CNN [4]](https://dl.acm.org/doi/10.1145/3240508.3240645) | 23.0 | 32 | | Generalization | [MGN [5]](https://arxiv.org/abs/1804.01438) | 23.6 | 33 | | Generalization | [IICS [6]](https://arxiv.org/abs/2103.11658) | 36.0 | 34 | | Weakly Supervised | [DGL [7]](https://arxiv.org/abs/1904.03845) | 26.9 | 35 | | Self Supervised | [SimCLR [8]](https://arxiv.org/abs/2002.05709) | 10.9 | 36 | | Self Supervised | [MoCo v2 [9]](https://arxiv.org/abs/2003.04297) | 11.6 | 37 | | Self Supervised | [BYOL [10]](https://arxiv.org/abs/2006.07733) | 12.7 | 38 | | Self Supervised | [Triplet [11]](https://arxiv.org/abs/2104.08760) | 14.8 | 39 | 40 | [1] Guangrun Wang, Liang Lin, Shengyong Ding, Ya Li, Qing Wang: DARI: Distance Metric and Representation Integration for Person Verification. AAAI 2016: 3611-3617 41 | 42 | [2] Shengyong Ding, Liang Lin, Guangrun Wang, Hongyang Chao: Deep feature learning with relative distance comparison for person re-identification. Pattern Recognit. 48(10): 2993-3003 (2015) 43 | 44 | [3] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun: Deep Residual Learning for Image Recognition. CVPR 2016: 770-778 45 | 46 | [4] Jiwei Yang, Xu Shen, Xinmei Tian, Houqiang Li, Jianqiang Huang, Xian-Sheng Hua: Local Convolutional Neural Networks for Person Re-Identification. ACM Multimedia 2018: 1074-1082 47 | 48 | [5] Guanshuo Wang, Yufeng Yuan, Xiong Chen, Jiwei Li, Xi Zhou: Learning Discriminative Features with Multiple Granularities for Person Re-Identification. ACM Multimedia 2018: 274-282 49 | 50 | [6] Shiyu Xuan, Shiliang Zhang: Intra-Inter Camera Similarity for Unsupervised Person Re-Identification. CVPR 2021: 11926-11935 51 | 52 | [7] Guangrun Wang, Guangcong Wang, Xujie Zhang, Jianhuang Lai, Zhengtao Yu, Liang Lin: Weakly Supervised Person Re-ID: Differentiable Graphical Learning and a New Benchmark. IEEE Trans. Neural Networks Learn. Syst. 32(5): 2142-2156 (2021) 53 | 54 | [8] Ting Chen, Simon Kornblith, Mohammad Norouzi, Geoffrey E. Hinton: A Simple Framework for Contrastive Learning of Visual Representations. ICML 2020: 1597-1607 55 | 56 | [9] Xinlei Chen, Haoqi Fan, Ross B. Girshick, Kaiming He: Improved Baselines with Momentum Contrastive Learning. CoRR abs/2003.04297 (2020) 57 | 58 | [10] Jean-Bastien Grill, Florian Strub, Florent Altché, Corentin Tallec, Pierre H. Richemond, Elena Buchatskaya, Carl Doersch, Bernardo Ávila Pires, Zhaohan Guo, Mohammad Gheshlaghi Azar, Bilal Piot, Koray Kavukcuoglu, Rémi Munos, Michal Valko: Bootstrap Your Own Latent - A New Approach to Self-Supervised Learning. NeurIPS 2020 59 | 60 | [11] Guangrun Wang, Keze Wang, Guangcong Wang, Philip H.S. Torr, and Liang Lin, "Solving Inefficiency of Self-supervised Representation Learning" [C]. in Proc. of IEEE International Conference on Computer Vision (ICCV), 2021. 61 | 62 | ## Statistic of the dataset 63 | 64 | SYSU-30k contains 30k categories of persons, which is about 20 times large rthan CUHK03 (1.3k categories)and Market1501 (1.5k categories), and 30 times larger than ImageNet (1k categories). SYSU-30k contains 29,606,918 images. Moreover, SYSU-30k provides not only a large platform for the weakly supervised ReID problem but also a more challenging test set that is consistent with the realistic setting for standard evaluation. Figure 1 shows some samples from the SYSU-30k dataset. 65 | 66 | 67 | 68 | ### Table 1: Comparision with existing Re-ID datasets. 69 | 70 | | Dataset | CUHK03 | Market-1501 | Duke | MSMT17 | CUHK01 | PRID | VIPeR | CAVIAR | SYSU-30k| 71 | |:------------:|:------------:|:------------:|:------------:|:------------:|:------------:|:------------:|:------------:|:------------:|:------------:| 72 | | Categories | 1,467 | 1,501. | 1,812 | 4,101 | 971 | 934 | 632 | 72 | 30,508 | 73 | | Scene | Indoor | Outdoor | Outdoor |Indoor, Outdoor| Indoor | Outdoor | Outdoor | Indoor |Indoor, Outdoor| 74 | | Annotation | Strong | Strong | Strong | Strong | Strong | Strong | Strong | Strong | Weak | 75 | | Cameras | 2 | 6 | 8 | 15 | 10 | 2 | 2 | 2 | Countless | 76 | | Images | 28,192 | 32,668 | 36,411 | 126,441 | 3,884 | 1,134 | 1,264 | 610 | 29,606,918 | 77 | 78 | 79 | ### Table 2: Comparison with ImageNet-1k. 80 | 81 | 82 | | Dataset | ImageNet-1k | SYSU-30k | 83 | |:------------------:|:------------------:|:------------------:| 84 | | Categories | 1,000 | 30,508 | 85 | | Images | 1,280,000 | 29,606,918 | 86 | | Annotation | Strong | Weak | 87 | 88 | ### Figure 1: The statistics of SYSU-30k. 89 | 90 | 91 |

92 | 93 |

94 | 95 |

(a) summarizes the number of the bags with respect to the number of the images per bag. (b) and (c) compare SYSU-30k with the existing datasets in terms of image number and person IDs for both the entire dataset and the test set.

96 | 97 | 98 | ## Visualization of the dataset 99 | 100 | ### Figure 2: Example in SYSU-30k. 101 | 102 |

103 | 104 |

105 | 106 |

(a) training images in terms of bag; (b) their bag-level annotations; (c) test set.

107 | 108 | 109 | 110 | ## Download the dataset 111 | 112 | **Note** that our original training set occupies 462G's memory. We are not able to upload the original data taking up such a large memory. As a result, we downsample the train images from 288 * x resolution to 144 * x resolution with x representing the shortest edge. The compressed data sum up to 82.2G. 113 | 114 | The test set is uncompressed due to the appropriate memory size. We provide two ways to download the SYSU-30k dataset. 115 | 116 | ### (1) Download the dataset on Google driver 117 | Click [:arrow_down:](https://drive.google.com/drive/folders/1MTxZ4UN_mbxjByZgcAki-H10zDzzeyuJ?usp=sharing) for downloading. 118 | The folder looks like: 119 | 120 | ``` 121 | sysu-30k-released 122 | ├── train 123 | | ├── sysu_train_set_all_part1.tar 124 | | ├── sysu_train_set_all_part2.tar 125 | | ├── sysu_train_set_all_part3.tar 126 | | ├── sysu_train_set_all_part4.tar 127 | | ├── sysu_train_set_all_part5.tar 128 | | ├── sysu_train_set_all_part6.tar 129 | | ├── sysu_train_set_all_part7.tar 130 | | ├── sysu_train_set_all_part8.tar 131 | | ├── sysu_train_set_all_part9.tar 132 | | ├── sysu_train_set_all_part10.tar 133 | 134 | ├── test 135 | | ├── sysu_test_set_all.tar 136 | ├── train.txt (for weakly supervised training, "filename\n" in each line) 137 | ├── query.txt (for evaluation) 138 | ├── gallery.txt (for evaluation) 139 | ├── net_6.pth (pretrained model) 140 | ``` 141 | 142 | 143 | ### (2) Or download the dataset on Baidu driver 144 | 145 | #### Download the training set 146 | 147 | | Dataset | Link to download | baidu pan code | 148 | |:------------------:|:------------------:|:------------------:| 149 | | sysu_train_set_all_part1.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 150 | | sysu_train_set_all_part2.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 151 | | sysu_train_set_all_part3.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 152 | | sysu_train_set_all_part4.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 153 | | sysu_train_set_all_part5.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 154 | | sysu_train_set_all_part6.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 155 | | sysu_train_set_all_part7.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 156 | | sysu_train_set_all_part8.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 157 | | sysu_train_set_all_part9.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 158 | | sysu_train_set_all_part10.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 159 | 160 | 161 | #### Download the bag-level label for training set, the training list, and the validation list. 162 | 163 | | Dataset | Link to download | baidu pan code | 164 | |:------------------:|:------------------:|:------------------:| 165 | |bag_level_label.json | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 166 | |train.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 167 | |query.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 168 | |gallery.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 169 | 170 | 171 | #### Download the test set 172 | 173 | | Dataset | Link to download | baidu pan code | 174 | |:------------------:|:------------------:|:------------------:| 175 | | sysu_test_set_all.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 176 | 177 | 178 | ## Data organization 179 | 180 | At last, the folder looks like: 181 | 182 | ``` 183 | SYSU-30k-released 184 | ├── SYSU-30k-released 185 | │ ├── meta 186 | │ | ├── train.txt (for weakly supervised training, "filename\n" in each line) 187 | │ | ├── query.txt (for evaluation) 188 | │ | ├── gallery.txt (for evaluation) 189 | │ ├── sysu_train_set_all 190 | │ | ├── 0000000001 191 | │ | ├── 0000000002 192 | │ | ├── 0000000003 193 | │ | ├── 0000000004 194 | │ | ├── ... 195 | │ | ├── 0000028309 196 | │ | ├── 0000028310 197 | │ ├── sysu_test_set_all 198 | │ | ├── gallery 199 | │ | | ├── 000028311 200 | │ | | | ├── 000028311_c1_1.jpg 201 | │ | | ├── 000028312 202 | │ | | | ├── 000028312_c1_1.jpg 203 | │ | | ├── 000028313Leaderboard on SYSU-30k. 204 | │ | | | ├── 000028313_c1_1.jpg 205 | │ | | ├── 000028314 206 | │ | | | ├── 000028314_c1_1.jpg 207 | │ | | ├── ... 208 | │ | | | ├── ... 209 | │ | | ├── 000029309 210 | │ | | | ├── 000029309_c1_1.jpg 211 | │ | | ├── 000029310 212 | │ | | | ├── 000029310_c1_1.jpg 213 | │ | | ├── 0000others 214 | │ | | | ├── 0000others_c1_1.jpg 215 | │ | | | ├── ... 216 | │ | | | ├── ... 217 | │ | ├── query 218 | │ | | ├── 000028311 219 | │ | | | ├── 000028311_c2_2.jpg 220 | │ | | ├── 000028312 221 | │ | | | ├── 000028312_c2_2.jpg 222 | │ | | ├── 000028313 223 | │ | | | ├── 000028313_c2_2.jpg 224 | │ | | ├── 000028314 225 | │ | | | ├── 000028314_c2_2.jpg 226 | │ | | ├── ... 227 | │ | | | ├── ... 228 | │ | | ├── 000029309 229 | │ | | | ├── 000029309_c2_2.jpg 230 | │ | | ├── 000029310 231 | │ | | | ├── 000029310_c2_2.jpg 232 | ``` 233 | 234 | 235 | 236 | ## Evaluation metric 237 | 238 | We fix the train/test partitioning. In the test set, we choose 1,000 images belonging to 1,000 different person IDs to form the query set. As the scalability is vital for the practicability of Re-ID systems, we propose to challenge a Re-ID model's scalability by providing a gallery set containing a vast volume of distractors for validation. Specifically, for each probe, there is only one matching person image as the correct answer in the gallery. At the same time, there are 478,730 mismatching person images as the wrong answer in the gallery. Thus, the evaluation protocol is to search for a needle in the ocean, just like the police search a massive amount of videos for a criminal. We use the rank-1 accuracy as the evaluation metric. 239 | 240 | 241 | ## Due to the large number of mismatching person images, SYSU-30k test set is so challenging that existing methods may have poor performance on SYSU-30k test set. Therefore, no matter whether your method is trained on SYSU-30k training set, you are encouraged to evaluate your method in SYSU-30k test set. 242 | 243 | # For a fair evaluation, please refer to the evaluation code in Using_SYSU30k_Test_Set/test_sysu_combine.py. 244 | 245 | 246 | # At present, the performance of existing methods on the SYSU-30k test set is poor. Therefore, to report new results on the SYSU-30k test set is encouraged. 247 | 248 | ### Table 3: Results on SYSU-30k. 249 | 250 | | Supervision | Method | Rank-1 | 251 | |:------------------:|:------------------:|:------------------:| 252 | | Generalization | [DARI [1]](https://arxiv.org/abs/1604.04377) | 11.2 | 253 | | Generalization | [DF [2]](https://arxiv.org/abs/1512.03622) | 10.3 | 254 | | Generalization | [ResNet-50 [3]](https://arxiv.org/abs/1512.03385) | 20.1 | 255 | | Generalization | [Local-CNN [4]](https://dl.acm.org/doi/10.1145/3240508.3240645) | 23.0 | 256 | | Generalization | [MGN [5]](https://arxiv.org/abs/1804.01438) | 23.6 | 257 | | Generalization | [IICS [6]](https://arxiv.org/abs/2103.11658) | 36.0 | 258 | | Weakly Supervised | [DGL [7]](https://arxiv.org/abs/1904.03845) | 26.9 | 259 | | Self Supervised | [SimCLR [8]](https://arxiv.org/abs/2002.05709) | 10.9 | 260 | | Self Supervised | [MoCo v2 [9]](https://arxiv.org/abs/2003.04297) | 11.6 | 261 | | Self Supervised | [BYOL [10]](https://arxiv.org/abs/2006.07733) | 12.7 | 262 | | Self Supervised | [Triplet [11]](https://arxiv.org/abs/2104.08760) | 14.8 | 263 | 264 | [1] Guangrun Wang, Liang Lin, Shengyong Ding, Ya Li, Qing Wang: DARI: Distance Metric and Representation Integration for Person Verification. AAAI 2016: 3611-3617 265 | 266 | [2] Shengyong Ding, Liang Lin, Guangrun Wang, Hongyang Chao: Deep feature learning with relative distance comparison for person re-identification. Pattern Recognit. 48(10): 2993-3003 (2015) 267 | 268 | [3] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun: Deep Residual Learning for Image Recognition. CVPR 2016: 770-778 269 | 270 | [4] Jiwei Yang, Xu Shen, Xinmei Tian, Houqiang Li, Jianqiang Huang, Xian-Sheng Hua: Local Convolutional Neural Networks for Person Re-Identification. ACM Multimedia 2018: 1074-1082 271 | 272 | [5] Guanshuo Wang, Yufeng Yuan, Xiong Chen, Jiwei Li, Xi Zhou: Learning Discriminative Features with Multiple Granularities for Person Re-Identification. ACM Multimedia 2018: 274-282 273 | 274 | [6] Shiyu Xuan, Shiliang Zhang: Intra-Inter Camera Similarity for Unsupervised Person Re-Identification. CVPR 2021: 11926-11935 275 | 276 | [7] Guangrun Wang, Guangcong Wang, Xujie Zhang, Jianhuang Lai, Zhengtao Yu, Liang Lin: Weakly Supervised Person Re-ID: Differentiable Graphical Learning and a New Benchmark. IEEE Trans. Neural Networks Learn. Syst. 32(5): 2142-2156 (2021) 277 | 278 | [8] Ting Chen, Simon Kornblith, Mohammad Norouzi, Geoffrey E. Hinton: A Simple Framework for Contrastive Learning of Visual Representations. ICML 2020: 1597-1607 279 | 280 | [9] Xinlei Chen, Haoqi Fan, Ross B. Girshick, Kaiming He: Improved Baselines with Momentum Contrastive Learning. CoRR abs/2003.04297 (2020) 281 | 282 | [10] Jean-Bastien Grill, Florian Strub, Florent Altché, Corentin Tallec, Pierre H. Richemond, Elena Buchatskaya, Carl Doersch, Bernardo Ávila Pires, Zhaohan Guo, Mohammad Gheshlaghi Azar, Bilal Piot, Koray Kavukcuoglu, Rémi Munos, Michal Valko: Bootstrap Your Own Latent - A New Approach to Self-Supervised Learning. NeurIPS 2020 283 | 284 | [11] Guangrun Wang, Keze Wang, Guangcong Wang, Philip H.S. Torr, and Liang Lin, "Solving Inefficiency of Self-supervised Representation Learning" [C]. in Proc. of IEEE International Conference on Computer Vision (ICCV), 2021. 285 | 286 | 287 | # Pretrained models 288 | 289 | 290 | ### Download the pretrained model 291 | 292 | | Pretrained | Link to download | baidu pan code | 293 | |:------------------:|:------------------:|:------------------:| 294 | | ResNet50-sysu30k-2048-AsFeature/net_6.pth | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 295 | 296 | 297 | ### Requirements 298 | 299 | We have tested the following versions of OS and softwares: 300 | 301 | - Python 3.6+ 302 | - PyTorch 1.1 or higher 303 | - OS: Ubuntu 16.04/18.04 and CentOS 7.2 304 | - CUDA: 9.0/9.2/10.0/10.1 305 | 306 | 307 | ### Test with pretrained model 308 | 309 | Evaluating a trained model includes two steps, i.e., feature extraction (which is in fact classificaction probability vector) and metric score caculation. 310 | 311 | 312 | ```shell 313 | **Step 1**: python test_sysu.py --gpu_ids ${GPU_ID} --name ${NAME_OF_MODEL} --test_dir ${DIR_OF_TEST_SET} --which_epoch ${WHICH_EPOCH_OF_CHECKPOINT} --batchsize ${BATCH_SIZE} 314 | **Step 2**: python evaluate_sysu.py 315 | ``` 316 | Arguments are: 317 | - `--gpu_ids ${GPU_ID}`: the gpu IDs you use. 318 | - `--name ${NAME_OF_MODEL}`: the name of the model, which is also the dir of the saved checkpoints and logs 319 | - `--test_dir ${DIR_OF_TEST_SET}`: the dir of your test set 320 | - `--which_epoch ${WHICH_EPOCH_OF_CHECKPOINT}`: which epoch of the checkpoint you want to evaluate 321 | - `--batchsize ${BATCH_SIZE}`: the batch size for testing 322 | 323 | An example: 324 | ```shell 325 | cd SYSU-30k/GraphReID/ 326 | python test_sysu.py --gpu_ids 0 --name work_dirs/ResNet50-sysu30k-2048-AsFeature --test_dir /data1/wangguangrun/sysu_test_set_all/ --which_epoch 6 --batchsize 100 327 | python evaluate_sysu.py 328 | ``` 329 | 330 | **Note**: Due the huge consumption of hard disks, sometimes, the above two steps can be combined into one step, e.g.: 331 | ```shell 332 | cd SYSU-30k/GraphReID/ 333 | python test_sysu_combine.py --gpu_ids 0 --name work_dirs/ResNet50-sysu30k-2048-AsFeature --test_dir /data1/wangguangrun/sysu_test_set_all/ --which_epoch 6 --batchsize 100 334 | ``` 335 | 336 | 337 | ### Training a model in a weakly supervised manner 338 | 339 | **Note**: During training, checkpoints and logs are saved the folder named "work_dir", which will occupies much memory of the hard disk. If you want to save your weights somewhere else, please use symlink, for example: 340 | 341 | ```shell 342 | cd GraphReID 343 | ln -s /data1/wangguangrun/GraphReID work_dirs 344 | ``` 345 | 346 | **Training the full model On Market-1501** (including unary term, pairwise term, and weakly-supervised triplet loss): 347 | 348 | ```shell 349 | cd GraphReID 350 | CUDA_VISIBLE_DEVICES=1,2 python3 main.py --datadir /data1/wangguangrun/dataset/market1501/ --bagid 2 --batchid 16 --batchtest 32 --test_every 100 --epochs 300 --decay_type step_250_290 --loss 1*CrossEntropy+2*Triplet --margin 1.2 --save adam_weak_market --nGPU 2 --lr 2e-4 --optimizer ADAM --random_erasing --reset --re_rank --amsgrad 351 | ``` 352 | 353 | 354 | **Training with with only unary term On Market-1501**: 355 | 356 | ```shell 357 | cd GraphReID 358 | python train_weak.py --gpu_ids 2 --name ResNet50 --color_jitter --train_all --batchsize 32 --erasing_p 0.5 --data_dir /home/wangguangrun/weakly-reid/pytorch 359 | ``` 360 | 361 | 362 | # Agreement 363 | * The SYSU-30k dataset is available for non-commercial research purposes only. 364 | * You agree **NOT** to reproduce, duplicate, copy, sell, trade, resell or exploit for any commercial purposes, any portion of the images and any portion of derived data. 365 | * You agree **NOT** to further copy, publish or distribute any portion of the SYSU-30k dataset. Except, for internal use at a single site within the same organization it is allowed to make copies of the dataset. 366 | * The SYSU reserves the right to terminate your access to the SYSU-30k dataset at any time. 367 | * The person identities are released upon request for research purposes only. Please contact us for details. 368 | 369 | 370 | 371 | 372 | # Citation 373 | 374 | If you use these models in your research, please kindly cite: 375 | 376 | ``` 377 | @inproceedings{Wang2020Weakly_tnnls, 378 | title={Weakly Supervised Person Re-ID: Differentiable Graphical Learning and A New Benchmark}, 379 | author={Guangrun Wang and 380 | Guangcong Wang and 381 | Xujie Zhang and 382 | Jianhuang Lai and 383 | Zhengtao Yu and 384 | Liang Lin}, 385 | booktitle={ IEEE Transactions on Neural Networks and Learning Systems (T-NNLS)}, 386 | year={2020} 387 | } 388 | ``` 389 | -------------------------------------------------------------------------------- /Self-Supervised-ReID/README.md: -------------------------------------------------------------------------------- 1 | # Training and extracting weights: 2 | 3 | The code for training SSL can be found at [Solving Inefficiency of Self-supervised Representation Learning](https://github.com/wanggrun/triplet). 4 | 5 | ```shell 6 | CUDA_VISIBLE_DEVICES=3,5,6,7 bash tools/dist_train.sh configs/selfsup/triplet/r50_bs4096_accumulate4_ep10_fp16_triplet_gpu3090_sysu30k.py 4 --pretrained /scratch/local/ssd/guangrun/tmp/release_ep940.pth 7 | python tools/extract_backbone_weights.py work_dirs/selfsup/triplet/r50_bs4096_accumulate4_ep10_fp16_triplet_gpu3090_sysu30k/epoch_10.pth work_dirs/selfsup/triplet/extract/sysu_ep10.pth 8 | ``` 9 | 10 | # Testing: 11 | 12 | ```shell 13 | python test_sysu_combine.py --gpu_ids 0 --name debug --test_dir /scratch/local/ssd/guangrun/sysu_test_resize --which_epoch 10 --batchsize 100 14 | ``` 15 | -------------------------------------------------------------------------------- /Self-Supervised-ReID/resnet.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.utils.checkpoint as cp 3 | from mmcv.cnn import constant_init, kaiming_init 4 | from mmcv.runner import load_checkpoint 5 | from torch.nn.modules.batchnorm import _BatchNorm 6 | 7 | from openselfsup.utils import get_root_logger 8 | 9 | 10 | norm_cfg = { 11 | # format: layer_type: (abbreviation, module) 12 | 'BN': ('bn', nn.BatchNorm2d), 13 | 'SyncBN': ('bn', nn.SyncBatchNorm), 14 | 'GN': ('gn', nn.GroupNorm), 15 | # and potentially 'SN' 16 | } 17 | 18 | 19 | def build_norm_layer(cfg, num_features, postfix=''): 20 | """Build normalization layer. 21 | Args: 22 | cfg (dict): cfg should contain: 23 | type (str): identify norm layer type. 24 | layer args: args needed to instantiate a norm layer. 25 | requires_grad (bool): [optional] whether stop gradient updates 26 | num_features (int): number of channels from input. 27 | postfix (int, str): appended into norm abbreviation to 28 | create named layer. 29 | Returns: 30 | name (str): abbreviation + postfix 31 | layer (nn.Module): created norm layer 32 | """ 33 | assert isinstance(cfg, dict) and 'type' in cfg 34 | cfg_ = cfg.copy() 35 | 36 | layer_type = cfg_.pop('type') 37 | if layer_type not in norm_cfg: 38 | raise KeyError('Unrecognized norm type {}'.format(layer_type)) 39 | else: 40 | abbr, norm_layer = norm_cfg[layer_type] 41 | if norm_layer is None: 42 | raise NotImplementedError 43 | 44 | assert isinstance(postfix, (int, str)) 45 | name = abbr + str(postfix) 46 | 47 | requires_grad = cfg_.pop('requires_grad', True) 48 | cfg_.setdefault('eps', 1e-5) 49 | if layer_type != 'GN': 50 | layer = norm_layer(num_features, **cfg_) 51 | # if layer_type == 'SyncBN': 52 | # layer._specify_ddp_gpu_num(1) 53 | else: 54 | assert 'num_groups' in cfg_ 55 | layer = norm_layer(num_channels=num_features, **cfg_) 56 | 57 | for param in layer.parameters(): 58 | param.requires_grad = requires_grad 59 | 60 | return name, layer 61 | 62 | 63 | conv_cfg = { 64 | 'Conv': nn.Conv2d, 65 | } 66 | 67 | def build_conv_layer(cfg, *args, **kwargs): 68 | """Build convolution layer. 69 | Args: 70 | cfg (None or dict): Cfg should contain: 71 | type (str): Identify conv layer type. 72 | layer args: Args needed to instantiate a conv layer. 73 | Returns: 74 | nn.Module: Created conv layer. 75 | """ 76 | if cfg is None: 77 | cfg_ = dict(type='Conv') 78 | else: 79 | assert isinstance(cfg, dict) and 'type' in cfg 80 | cfg_ = cfg.copy() 81 | 82 | layer_type = cfg_.pop('type') 83 | if layer_type not in conv_cfg: 84 | raise KeyError('Unrecognized norm type {}'.format(layer_type)) 85 | else: 86 | conv_layer = conv_cfg[layer_type] 87 | 88 | layer = conv_layer(*args, **kwargs, **cfg_) 89 | 90 | return layer 91 | 92 | 93 | 94 | class BasicBlock(nn.Module): 95 | expansion = 1 96 | 97 | def __init__(self, 98 | inplanes, 99 | planes, 100 | stride=1, 101 | dilation=1, 102 | downsample=None, 103 | style='pytorch', 104 | with_cp=False, 105 | conv_cfg=None, 106 | norm_cfg=dict(type='BN')): 107 | super(BasicBlock, self).__init__() 108 | 109 | self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1) 110 | self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2) 111 | 112 | self.conv1 = build_conv_layer( 113 | conv_cfg, 114 | inplanes, 115 | planes, 116 | 3, 117 | stride=stride, 118 | padding=dilation, 119 | dilation=dilation, 120 | bias=False) 121 | self.add_module(self.norm1_name, norm1) 122 | self.conv2 = build_conv_layer( 123 | conv_cfg, planes, planes, 3, padding=1, bias=False) 124 | self.add_module(self.norm2_name, norm2) 125 | 126 | self.relu = nn.ReLU(inplace=True) 127 | self.downsample = downsample 128 | self.stride = stride 129 | self.dilation = dilation 130 | assert not with_cp 131 | 132 | @property 133 | def norm1(self): 134 | return getattr(self, self.norm1_name) 135 | 136 | @property 137 | def norm2(self): 138 | return getattr(self, self.norm2_name) 139 | 140 | def forward(self, x): 141 | identity = x 142 | 143 | out = self.conv1(x) 144 | out = self.norm1(out) 145 | out = self.relu(out) 146 | 147 | out = self.conv2(out) 148 | out = self.norm2(out) 149 | 150 | if self.downsample is not None: 151 | identity = self.downsample(x) 152 | 153 | out += identity 154 | out = self.relu(out) 155 | 156 | return out 157 | 158 | 159 | class Bottleneck(nn.Module): 160 | expansion = 4 161 | 162 | def __init__(self, 163 | inplanes, 164 | planes, 165 | stride=1, 166 | dilation=1, 167 | downsample=None, 168 | style='pytorch', 169 | with_cp=False, 170 | conv_cfg=None, 171 | norm_cfg=dict(type='BN')): 172 | """Bottleneck block for ResNet. 173 | If style is "pytorch", the stride-two layer is the 3x3 conv layer, 174 | if it is "caffe", the stride-two layer is the first 1x1 conv layer. 175 | """ 176 | super(Bottleneck, self).__init__() 177 | assert style in ['pytorch', 'caffe'] 178 | 179 | self.inplanes = inplanes 180 | self.planes = planes 181 | self.stride = stride 182 | self.dilation = dilation 183 | self.style = style 184 | self.with_cp = with_cp 185 | self.conv_cfg = conv_cfg 186 | self.norm_cfg = norm_cfg 187 | 188 | if self.style == 'pytorch': 189 | self.conv1_stride = 1 190 | self.conv2_stride = stride 191 | else: 192 | self.conv1_stride = stride 193 | self.conv2_stride = 1 194 | 195 | self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1) 196 | self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2) 197 | self.norm3_name, norm3 = build_norm_layer( 198 | norm_cfg, planes * self.expansion, postfix=3) 199 | 200 | self.conv1 = build_conv_layer( 201 | conv_cfg, 202 | inplanes, 203 | planes, 204 | kernel_size=1, 205 | stride=self.conv1_stride, 206 | bias=False) 207 | self.add_module(self.norm1_name, norm1) 208 | self.conv2 = build_conv_layer( 209 | conv_cfg, 210 | planes, 211 | planes, 212 | kernel_size=3, 213 | stride=self.conv2_stride, 214 | padding=dilation, 215 | dilation=dilation, 216 | bias=False) 217 | self.add_module(self.norm2_name, norm2) 218 | self.conv3 = build_conv_layer( 219 | conv_cfg, 220 | planes, 221 | planes * self.expansion, 222 | kernel_size=1, 223 | bias=False) 224 | self.add_module(self.norm3_name, norm3) 225 | 226 | self.relu = nn.ReLU(inplace=True) 227 | self.downsample = downsample 228 | 229 | @property 230 | def norm1(self): 231 | return getattr(self, self.norm1_name) 232 | 233 | @property 234 | def norm2(self): 235 | return getattr(self, self.norm2_name) 236 | 237 | @property 238 | def norm3(self): 239 | return getattr(self, self.norm3_name) 240 | 241 | def forward(self, x): 242 | 243 | def _inner_forward(x): 244 | identity = x 245 | 246 | out = self.conv1(x) 247 | out = self.norm1(out) 248 | out = self.relu(out) 249 | 250 | out = self.conv2(out) 251 | out = self.norm2(out) 252 | out = self.relu(out) 253 | 254 | out = self.conv3(out) 255 | out = self.norm3(out) 256 | 257 | if self.downsample is not None: 258 | identity = self.downsample(x) 259 | 260 | out += identity 261 | 262 | return out 263 | 264 | if self.with_cp and x.requires_grad: 265 | out = cp.checkpoint(_inner_forward, x) 266 | else: 267 | out = _inner_forward(x) 268 | 269 | out = self.relu(out) 270 | 271 | return out 272 | 273 | 274 | def make_res_layer(block, 275 | inplanes, 276 | planes, 277 | blocks, 278 | stride=1, 279 | dilation=1, 280 | style='pytorch', 281 | with_cp=False, 282 | conv_cfg=None, 283 | norm_cfg=dict(type='BN')): 284 | downsample = None 285 | if stride != 1 or inplanes != planes * block.expansion: 286 | downsample = nn.Sequential( 287 | build_conv_layer( 288 | conv_cfg, 289 | inplanes, 290 | planes * block.expansion, 291 | kernel_size=1, 292 | stride=stride, 293 | bias=False), 294 | build_norm_layer(norm_cfg, planes * block.expansion)[1], 295 | ) 296 | 297 | layers = [] 298 | layers.append( 299 | block( 300 | inplanes=inplanes, 301 | planes=planes, 302 | stride=stride, 303 | dilation=dilation, 304 | downsample=downsample, 305 | style=style, 306 | with_cp=with_cp, 307 | conv_cfg=conv_cfg, 308 | norm_cfg=norm_cfg)) 309 | inplanes = planes * block.expansion 310 | for i in range(1, blocks): 311 | layers.append( 312 | block( 313 | inplanes=inplanes, 314 | planes=planes, 315 | stride=1, 316 | dilation=dilation, 317 | style=style, 318 | with_cp=with_cp, 319 | conv_cfg=conv_cfg, 320 | norm_cfg=norm_cfg)) 321 | 322 | return nn.Sequential(*layers) 323 | 324 | 325 | class ResNet(nn.Module): 326 | """ResNet backbone. 327 | 328 | Args: 329 | depth (int): Depth of resnet, from {18, 34, 50, 101, 152}. 330 | in_channels (int): Number of input image channels. Normally 3. 331 | num_stages (int): Resnet stages, normally 4. 332 | strides (Sequence[int]): Strides of the first block of each stage. 333 | dilations (Sequence[int]): Dilation of each stage. 334 | out_indices (Sequence[int]): Output from which stages. 335 | style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two 336 | layer is the 3x3 conv layer, otherwise the stride-two layer is 337 | the first 1x1 conv layer. 338 | frozen_stages (int): Stages to be frozen (stop grad and set eval mode). 339 | -1 means not freezing any parameters. 340 | norm_cfg (dict): dictionary to construct and config norm layer. 341 | norm_eval (bool): Whether to set norm layers to eval mode, namely, 342 | freeze running stats (mean and var). Note: Effect on Batch Norm 343 | and its variants only. 344 | with_cp (bool): Use checkpoint or not. Using checkpoint will save some 345 | memory while slowing down the training speed. 346 | zero_init_residual (bool): whether to use zero init for last norm layer 347 | in resblocks to let them behave as identity. 348 | 349 | Example: 350 | >>> from openselfsup.models import ResNet 351 | >>> import torch 352 | >>> self = ResNet(depth=18) 353 | >>> self.eval() 354 | >>> inputs = torch.rand(1, 3, 32, 32) 355 | >>> level_outputs = self.forward(inputs) 356 | >>> for level_out in level_outputs: 357 | ... print(tuple(level_out.shape)) 358 | (1, 64, 8, 8) 359 | (1, 128, 4, 4) 360 | (1, 256, 2, 2) 361 | (1, 512, 1, 1) 362 | """ 363 | 364 | arch_settings = { 365 | 50: (Bottleneck, (3, 4, 6, 3)), 366 | } 367 | 368 | def __init__(self, 369 | depth, 370 | in_channels=3, 371 | num_stages=4, 372 | strides=(1, 2, 2, 2), 373 | dilations=(1, 1, 1, 1), 374 | out_indices=(0, 1, 2, 3, 4), 375 | style='pytorch', 376 | frozen_stages=-1, 377 | conv_cfg=None, 378 | norm_cfg=dict(type='BN', requires_grad=True), 379 | norm_eval=False, 380 | with_cp=False, 381 | zero_init_residual=False): 382 | super(ResNet, self).__init__() 383 | if depth not in self.arch_settings: 384 | raise KeyError('invalid depth {} for resnet'.format(depth)) 385 | self.depth = depth 386 | self.num_stages = num_stages 387 | assert num_stages >= 1 and num_stages <= 4 388 | self.strides = strides 389 | self.dilations = dilations 390 | assert len(strides) == len(dilations) == num_stages 391 | self.out_indices = out_indices 392 | assert max(out_indices) < num_stages + 1 393 | self.style = style 394 | self.frozen_stages = frozen_stages 395 | self.conv_cfg = conv_cfg 396 | self.norm_cfg = norm_cfg 397 | self.with_cp = with_cp 398 | self.norm_eval = norm_eval 399 | self.zero_init_residual = zero_init_residual 400 | self.block, stage_blocks = self.arch_settings[depth] 401 | self.stage_blocks = stage_blocks[:num_stages] 402 | self.inplanes = 64 403 | 404 | self._make_stem_layer(in_channels) 405 | 406 | self.res_layers = [] 407 | for i, num_blocks in enumerate(self.stage_blocks): 408 | stride = strides[i] 409 | dilation = dilations[i] 410 | planes = 64 * 2**i 411 | res_layer = make_res_layer( 412 | self.block, 413 | self.inplanes, 414 | planes, 415 | num_blocks, 416 | stride=stride, 417 | dilation=dilation, 418 | style=self.style, 419 | with_cp=with_cp, 420 | conv_cfg=conv_cfg, 421 | norm_cfg=norm_cfg) 422 | self.inplanes = planes * self.block.expansion 423 | layer_name = 'layer{}'.format(i + 1) 424 | self.add_module(layer_name, res_layer) 425 | self.res_layers.append(layer_name) 426 | 427 | self._freeze_stages() 428 | 429 | self.feat_dim = self.block.expansion * 64 * 2**( 430 | len(self.stage_blocks) - 1) 431 | 432 | @property 433 | def norm1(self): 434 | return getattr(self, self.norm1_name) 435 | 436 | def _make_stem_layer(self, in_channels): 437 | self.conv1 = build_conv_layer( 438 | self.conv_cfg, 439 | in_channels, 440 | 64, 441 | kernel_size=7, 442 | stride=2, 443 | padding=3, 444 | bias=False) 445 | self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1) 446 | self.add_module(self.norm1_name, norm1) 447 | self.relu = nn.ReLU(inplace=True) 448 | self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) 449 | 450 | def _freeze_stages(self): 451 | if self.frozen_stages >= 0: 452 | self.norm1.eval() 453 | for m in [self.conv1, self.norm1]: 454 | for param in m.parameters(): 455 | param.requires_grad = False 456 | 457 | for i in range(1, self.frozen_stages + 1): 458 | m = getattr(self, 'layer{}'.format(i)) 459 | m.eval() 460 | for param in m.parameters(): 461 | param.requires_grad = False 462 | 463 | def init_weights(self, pretrained=None): 464 | if isinstance(pretrained, str): 465 | logger = get_root_logger() 466 | load_checkpoint(self, pretrained, strict=True, logger=logger) 467 | elif pretrained is None: 468 | for m in self.modules(): 469 | if isinstance(m, nn.Conv2d): 470 | kaiming_init(m, mode='fan_in', nonlinearity='relu') 471 | elif isinstance(m, (_BatchNorm, nn.GroupNorm)): 472 | constant_init(m, 1) 473 | 474 | if self.zero_init_residual: 475 | for m in self.modules(): 476 | if isinstance(m, Bottleneck): 477 | constant_init(m.norm3, 0) 478 | elif isinstance(m, BasicBlock): 479 | constant_init(m.norm2, 0) 480 | else: 481 | raise TypeError('pretrained must be a str or None') 482 | 483 | def forward(self, x): 484 | outs = [] 485 | x = self.conv1(x) 486 | x = self.norm1(x) 487 | x = self.relu(x) # r50: 64x128x128 488 | if 0 in self.out_indices: 489 | outs.append(x) 490 | x = self.maxpool(x) # r50: 64x56x56 491 | for i, layer_name in enumerate(self.res_layers): 492 | res_layer = getattr(self, layer_name) 493 | x = res_layer(x) 494 | if i + 1 in self.out_indices: 495 | outs.append(x) 496 | # r50: 1-256x56x56; 2-512x28x28; 3-1024x14x14; 4-2048x7x7 497 | return tuple(outs) 498 | 499 | def train(self, mode=True): 500 | super(ResNet, self).train(mode) 501 | self._freeze_stages() 502 | if mode and self.norm_eval: 503 | for m in self.modules(): 504 | # trick: eval have effect on BatchNorm only 505 | if isinstance(m, _BatchNorm): 506 | m.eval() 507 | -------------------------------------------------------------------------------- /Self-Supervised-ReID/test_sysu_combine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import print_function, division 4 | 5 | import argparse 6 | import torch 7 | import torch.nn as nn 8 | import torch.optim as optim 9 | from torch.optim import lr_scheduler 10 | from torch.autograd import Variable 11 | import numpy as np 12 | import torchvision 13 | from torchvision import datasets, models, transforms 14 | import time 15 | import os 16 | import scipy.io 17 | from resnet import ResNet 18 | 19 | ###################################################################### 20 | # Options 21 | # -------- 22 | os.environ['CUDA_VISIBLE_DEVICES'] = '7' 23 | parser = argparse.ArgumentParser(description='Training') 24 | parser.add_argument('--gpu_ids',default='0', type=str,help='gpu_ids: e.g. 0 0,1,2 0,2') 25 | parser.add_argument('--which_epoch',default='last', type=str, help='0,1,2,3...or last') 26 | parser.add_argument('--test_dir',default='/home/zzd/Market/pytorch',type=str, help='./test_data') 27 | parser.add_argument('--name', default='ResNet50', type=str, help='save model path') 28 | parser.add_argument('--batchsize', default=100, type=int, help='batchsize') 29 | 30 | opt = parser.parse_args() 31 | 32 | str_ids = opt.gpu_ids.split(',') 33 | name = opt.name 34 | test_dir = opt.test_dir 35 | 36 | gpu_ids = [] 37 | for str_id in str_ids: 38 | id = int(str_id) 39 | if id >=0: 40 | gpu_ids.append(id) 41 | 42 | # set gpu ids 43 | if len(gpu_ids)>0: 44 | torch.cuda.set_device(gpu_ids[0]) 45 | 46 | ###################################################################### 47 | # Load Data 48 | # --------- 49 | # 50 | # We will use torchvision and torch.utils.data packages for loading the 51 | # data. 52 | # 53 | data_transforms = transforms.Compose([ 54 | transforms.Resize((256,128), interpolation=3), 55 | # transforms.CenterCrop((256,128)), 56 | transforms.ToTensor(), 57 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 58 | ]) 59 | 60 | 61 | 62 | data_dir = test_dir 63 | image_datasets = {x: datasets.ImageFolder( os.path.join(data_dir,x) ,data_transforms) for x in ['gallery','query']} 64 | dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=opt.batchsize, 65 | shuffle=False, num_workers=16) for x in ['gallery','query']} 66 | 67 | class_names = image_datasets['query'].classes 68 | use_gpu = torch.cuda.is_available() 69 | 70 | ###################################################################### 71 | # Load model 72 | #--------------------------- 73 | def load_network(network): 74 | save_path = os.path.join(name,'net_%s.pth'%opt.which_epoch) 75 | network.load_state_dict(torch.load(save_path)) 76 | return network 77 | 78 | 79 | ###################################################################### 80 | # Extract feature 81 | # ---------------------- 82 | # 83 | # Extract feature from a trained model. 84 | 85 | 86 | def extract_feature(model,dataloaders): 87 | features = torch.FloatTensor() 88 | count = 0 89 | for data in dataloaders: 90 | img, label = data 91 | n, c, h, w = img.size() 92 | count += n 93 | print(count) 94 | ff = torch.FloatTensor(n,2048).zero_() 95 | for i in range(2): 96 | if(i==1): 97 | img = img.flip(dims=[-1]) 98 | input_img = Variable(img.cuda()) 99 | outputs = model(input_img) 100 | f = outputs[0].mean(dim=[2,3]).data.cpu() 101 | ff = ff+f 102 | fnorm = torch.norm(ff, p=2, dim=1, keepdim=True) 103 | ff = ff.div(fnorm.expand_as(ff)) 104 | 105 | features = torch.cat((features,ff), 0) 106 | return features 107 | 108 | def get_id(img_path): 109 | camera_id = [] 110 | labels = [] 111 | for path, v in img_path: 112 | filename = path.split('/')[-1] 113 | #label = filename[0:4] 114 | label = filename.split('_')[0] 115 | camera = filename.split('c')[1] 116 | if label[0:10]=='0000others': 117 | labels.append(-1000) 118 | else: 119 | labels.append(int(label)) 120 | camera_id.append(int(camera[0])) 121 | return camera_id, labels 122 | 123 | gallery_path = image_datasets['gallery'].imgs 124 | query_path = image_datasets['query'].imgs 125 | 126 | gallery_cam,gallery_label = get_id(gallery_path) 127 | query_cam,query_label = get_id(query_path) 128 | 129 | ###################################################################### 130 | # Load Collected data Trained model 131 | print('-------test-----------') 132 | #model_structure = ft_net(28310) 133 | model_structure = ResNet(depth=50, in_channels=3, out_indices=[4], frozen_stages=4, 134 | norm_cfg=dict(type='BN', requires_grad=True), norm_eval=True) 135 | 136 | model_structure.init_weights(pretrained='/scratch/local/ssd/guangrun/triplet/work_dirs/selfsup/triplet/extract/sysu_ep10.pth') 137 | 138 | model = nn.DataParallel(model_structure) 139 | # model = load_network(model_structure) 140 | 141 | # Remove the final fc layer and classifier layer 142 | 143 | # model.module.model.fc = nn.Sequential() 144 | print(model) 145 | 146 | # Change to test mode 147 | model = model.eval() 148 | if use_gpu: 149 | model = model.cuda() 150 | 151 | # Extract feature 152 | gallery_feature = extract_feature(model,dataloaders['gallery']) 153 | query_feature = extract_feature(model,dataloaders['query']) 154 | 155 | 156 | import scipy.io 157 | import torch 158 | import numpy as np 159 | import time 160 | 161 | ####################################################################### 162 | # Evaluate 163 | def evaluate(qf,ql,qc,gf,gl,gc): 164 | query = qf 165 | score = np.dot(gf,query) 166 | # predict index 167 | index = np.argsort(score) #from small to large 168 | index = index[::-1] 169 | #index = index[0:2000] 170 | # good index 171 | query_index = np.argwhere(gl==ql) 172 | camera_index = np.argwhere(gc==qc) 173 | 174 | good_index = np.setdiff1d(query_index, camera_index, assume_unique=True) 175 | junk_index1 = np.argwhere(gl==-1) 176 | junk_index2 = np.intersect1d(query_index, camera_index) 177 | junk_index = np.append(junk_index2, junk_index1) #.flatten()) 178 | 179 | CMC_tmp = compute_mAP(index, good_index, junk_index) 180 | return CMC_tmp 181 | 182 | 183 | def compute_mAP(index, good_index, junk_index): 184 | ap = 0 185 | cmc = torch.IntTensor(len(index)).zero_() 186 | if good_index.size==0: # if empty 187 | cmc[0] = -1 188 | return ap,cmc 189 | 190 | # remove junk_index 191 | mask = np.in1d(index, junk_index, invert=True) 192 | index = index[mask] 193 | 194 | # find good_index index 195 | ngood = len(good_index) 196 | mask = np.in1d(index, good_index) 197 | rows_good = np.argwhere(mask==True) 198 | rows_good = rows_good.flatten() 199 | 200 | cmc[rows_good[0]:] = 1 201 | for i in range(ngood): 202 | d_recall = 1.0/ngood 203 | precision = (i+1)*1.0/(rows_good[i]+1) 204 | if rows_good[i]!=0: 205 | old_precision = i*1.0/rows_good[i] 206 | else: 207 | old_precision=1.0 208 | ap = ap + d_recall*(old_precision + precision)/2 209 | 210 | return ap, cmc 211 | 212 | 213 | query_feature = query_feature.numpy() # result['query_f'] 214 | query_cam = np.asarray(query_cam) #result['query_cam'][0] 215 | query_label = np.asarray(query_label)#result['query_label'][0] 216 | gallery_feature = gallery_feature.numpy()#result['gallery_f'] 217 | gallery_cam = np.asarray(gallery_cam)#result['gallery_cam'][0] 218 | gallery_label = np.asarray(gallery_label)#result['gallery_label'][0] 219 | 220 | 221 | CMC = torch.IntTensor(len(gallery_label)).zero_() 222 | ap = 0.0 223 | #print(query_label) 224 | for i in range(len(query_label)): 225 | ap_tmp, CMC_tmp = evaluate(query_feature[i],query_label[i],query_cam[i],gallery_feature,gallery_label,gallery_cam) 226 | if CMC_tmp[0]==-1: 227 | continue 228 | CMC = CMC + CMC_tmp 229 | ap += ap_tmp 230 | print(i, CMC_tmp[0]) 231 | 232 | CMC = CMC.float() 233 | CMC = CMC/len(query_label) #average CMC 234 | print('Rank@1:%f Rank@5:%f Rank@10:%f mAP:%f'%(CMC[0],CMC[4],CMC[9],ap/len(query_label))) 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /Using_SYSU30k_Test_Set/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Download the test set 4 | 5 | | Dataset | Link to download | baidu pan code | 6 | |:------------------:|:------------------:|:------------------:| 7 | | sysu_test_set_all.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 8 | 9 | 10 | ## Data organization 11 | 12 | At last, the folder looks like: 13 | 14 | The folder looks like: 15 | 16 | ``` 17 | SYSU-30k-released 18 | ├── SYSU-30k-released 19 | │ ├── meta 20 | │ | ├── train.txt (for weakly supervised training, "filename\n" in each line) 21 | │ | ├── query.txt (for evaluation) 22 | │ | ├── gallery.txt (for evaluation) 23 | │ ├── sysu_test_set_all 24 | │ | ├── gallery 25 | │ | | ├── 000028311 26 | │ | | | ├── 000028311_c1_1.jpg 27 | │ | | ├── 000028312 28 | │ | | | ├── 000028312_c1_1.jpg 29 | │ | | ├── 000028313 30 | │ | | | ├── 000028313_c1_1.jpg 31 | │ | | ├── 000028314 32 | │ | | | ├── 000028314_c1_1.jpg 33 | │ | | ├── ... 34 | │ | | | ├── ... 35 | │ | | ├── 000029309 36 | │ | | | ├── 000029309_c1_1.jpg 37 | │ | | ├── 000029310 38 | │ | | | ├── 000029310_c1_1.jpg 39 | │ | | ├── 0000others 40 | │ | | | ├── 0000others_c1_1.jpg 41 | │ | | | ├── ... 42 | │ | | | ├── ... 43 | │ | ├── query 44 | │ | | ├── 000028311 45 | │ | | | ├── 000028311_c2_2.jpg 46 | │ | | ├── 000028312 47 | │ | | | ├── 000028312_c2_2.jpg 48 | │ | | ├── 000028313 49 | │ | | | ├── 000028313_c2_2.jpg 50 | │ | | ├── 000028314 51 | │ | | | ├── 000028314_c2_2.jpg 52 | │ | | ├── ... 53 | │ | | | ├── ... 54 | │ | | ├── 000029309 55 | │ | | | ├── 000029309_c2_2.jpg 56 | │ | | ├── 000029310 57 | │ | | | ├── 000029310_c2_2.jpg 58 | ``` 59 | 60 | We fix the train/test partitioning. In the test set, we choose 1,000 images belonging to 1,000 different person IDs to form the query set. As the scalability is vital for the practicability of Re-ID systems, we propose to challenge a Re-ID model's scalability by providing a gallery set containing a vast volume of distractors for validation. Specifically, for each probe, there is only one matching person image as the correct answer in the gallery. At the same time, there are 478,730 mismatching person images as the wrong answer in the gallery. Thus, the evaluation protocol is to search for a needle in the ocean, just like the police search a massive amount of videos for a criminal. We use the rank-1 accuracy as the evaluation metric. 61 | 62 | 63 | 64 | # For a fair evaluation, please refer to the evaluation code in Using_SYSU30k_Test_Set/test_sysu_combine.py 65 | -------------------------------------------------------------------------------- /Using_SYSU30k_Test_Set/model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | from torch.nn import init 4 | from torchvision import models 5 | from torch.autograd import Variable 6 | 7 | ###################################################################### 8 | def weights_init_kaiming(m): 9 | classname = m.__class__.__name__ 10 | # print(classname) 11 | if classname.find('Conv') != -1: 12 | init.kaiming_normal(m.weight.data, a=0, mode='fan_in') 13 | elif classname.find('Linear') != -1: 14 | init.kaiming_normal(m.weight.data, a=0, mode='fan_out') 15 | init.constant(m.bias.data, 0.0) 16 | elif classname.find('BatchNorm1d') != -1: 17 | init.normal(m.weight.data, 1.0, 0.02) 18 | init.constant(m.bias.data, 0.0) 19 | 20 | def weights_init_classifier(m): 21 | classname = m.__class__.__name__ 22 | if classname.find('Linear') != -1: 23 | init.normal(m.weight.data, std=0.001) 24 | init.constant(m.bias.data, 0.0) 25 | 26 | # Defines the new fc layer and classification layer 27 | # |--Linear--|--bn--|--relu--|--Linear--| 28 | class ClassBlock(nn.Module): 29 | def __init__(self, input_dim, class_num, dropout=True, relu=True, num_bottleneck=512): 30 | super(ClassBlock, self).__init__() 31 | #add_block = [] 32 | #add_block += [nn.Linear(input_dim, num_bottleneck)] 33 | #add_block += [nn.BatchNorm1d(num_bottleneck)] 34 | #if relu: 35 | # add_block += [nn.LeakyReLU(0.1)] 36 | #if dropout: 37 | # add_block += [nn.Dropout(p=0.5)] 38 | #add_block = nn.Sequential(*add_block) 39 | #add_block.apply(weights_init_kaiming) 40 | 41 | classifier = [] 42 | classifier += [nn.Linear(2048, class_num)] 43 | classifier = nn.Sequential(*classifier) 44 | classifier.apply(weights_init_classifier) 45 | 46 | #self.add_block = add_block 47 | self.classifier = classifier 48 | def forward(self, x): 49 | #x = self.add_block(x) 50 | x = self.classifier(x) 51 | return x 52 | 53 | # Define the ResNet50-based Model 54 | class ft_net(nn.Module): 55 | 56 | def __init__(self, class_num ): 57 | super(ft_net, self).__init__() 58 | model_ft = models.resnet50(pretrained=True) 59 | # avg pooling to global pooling 60 | model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1)) 61 | self.model = model_ft 62 | self.classifier = ClassBlock(2048, class_num) 63 | 64 | def forward(self, x): 65 | x = self.model.conv1(x) 66 | x = self.model.bn1(x) 67 | x = self.model.relu(x) 68 | x = self.model.maxpool(x) 69 | x = self.model.layer1(x) 70 | x = self.model.layer2(x) 71 | x = self.model.layer3(x) 72 | x = self.model.layer4(x) 73 | x = self.model.avgpool(x) 74 | x = torch.squeeze(x) 75 | x = self.classifier(x) 76 | return x 77 | 78 | # Define the DenseNet121-based Model 79 | class ft_net_dense(nn.Module): 80 | 81 | def __init__(self, class_num ): 82 | super().__init__() 83 | model_ft = models.densenet121(pretrained=True) 84 | model_ft.features.avgpool = nn.AdaptiveAvgPool2d((1,1)) 85 | model_ft.fc = nn.Sequential() 86 | self.model = model_ft 87 | # For DenseNet, the feature dim is 1024 88 | self.classifier = ClassBlock(1024, class_num) 89 | 90 | def forward(self, x): 91 | x = self.model.features(x) 92 | x = torch.squeeze(x) 93 | x = self.classifier(x) 94 | return x 95 | 96 | # Define the ResNet50-based Model (Middle-Concat) 97 | # In the spirit of "The Devil is in the Middle: Exploiting Mid-level Representations for Cross-Domain Instance Matching." Yu, Qian, et al. arXiv:1711.08106 (2017). 98 | class ft_net_middle(nn.Module): 99 | 100 | def __init__(self, class_num ): 101 | super(ft_net_middle, self).__init__() 102 | model_ft = models.resnet50(pretrained=True) 103 | # avg pooling to global pooling 104 | model_ft.avgpool = nn.AdaptiveAvgPool2d((1,1)) 105 | self.model = model_ft 106 | self.classifier = ClassBlock(2048+1024, class_num) 107 | 108 | def forward(self, x): 109 | x = self.model.conv1(x) 110 | x = self.model.bn1(x) 111 | x = self.model.relu(x) 112 | x = self.model.maxpool(x) 113 | x = self.model.layer1(x) 114 | x = self.model.layer2(x) 115 | x = self.model.layer3(x) 116 | # x0 n*1024*1*1 117 | x0 = self.model.avgpool(x) 118 | x = self.model.layer4(x) 119 | # x1 n*2048*1*1 120 | x1 = self.model.avgpool(x) 121 | x = torch.cat((x0,x1),1) 122 | x = torch.squeeze(x) 123 | x = self.classifier(x) 124 | return x 125 | 126 | # Part Model proposed in Yifan Sun etal. (2018) 127 | class PCB(nn.Module): 128 | def __init__(self, class_num ): 129 | super(PCB, self).__init__() 130 | 131 | self.part = 6 # We cut the pool5 to 6 parts 132 | model_ft = models.resnet50(pretrained=True) 133 | self.model = model_ft 134 | self.avgpool = nn.AdaptiveAvgPool2d((self.part,1)) 135 | self.dropout = nn.Dropout(p=0.5) 136 | # remove the final downsample 137 | self.model.layer4[0].downsample[0].stride = (1,1) 138 | self.model.layer4[0].conv2.stride = (1,1) 139 | # define 6 classifiers 140 | for i in range(self.part): 141 | name = 'classifier'+str(i) 142 | setattr(self, name, ClassBlock(2048, class_num, True, False, 256)) 143 | 144 | def forward(self, x): 145 | x = self.model.conv1(x) 146 | x = self.model.bn1(x) 147 | x = self.model.relu(x) 148 | x = self.model.maxpool(x) 149 | 150 | x = self.model.layer1(x) 151 | x = self.model.layer2(x) 152 | x = self.model.layer3(x) 153 | x = self.model.layer4(x) 154 | x = self.avgpool(x) 155 | x = self.dropout(x) 156 | part = {} 157 | predict = {} 158 | # get six part feature batchsize*2048*6 159 | for i in range(self.part): 160 | part[i] = torch.squeeze(x[:,:,i]) 161 | name = 'classifier'+str(i) 162 | c = getattr(self,name) 163 | predict[i] = c(part[i]) 164 | 165 | # sum prediction 166 | #y = predict[0] 167 | #for i in range(self.part-1): 168 | # y += predict[i+1] 169 | y = [] 170 | for i in range(self.part): 171 | y.append(predict[i]) 172 | return y 173 | 174 | class PCB_test(nn.Module): 175 | def __init__(self,model): 176 | super(PCB_test,self).__init__() 177 | self.part = 6 178 | self.model = model.model 179 | self.avgpool = nn.AdaptiveAvgPool2d((self.part,1)) 180 | # remove the final downsample 181 | self.model.layer4[0].downsample[0].stride = (1,1) 182 | self.model.layer4[0].conv2.stride = (1,1) 183 | 184 | def forward(self, x): 185 | x = self.model.conv1(x) 186 | x = self.model.bn1(x) 187 | x = self.model.relu(x) 188 | x = self.model.maxpool(x) 189 | 190 | x = self.model.layer1(x) 191 | x = self.model.layer2(x) 192 | x = self.model.layer3(x) 193 | x = self.model.layer4(x) 194 | x = self.avgpool(x) 195 | y = x.view(x.size(0),x.size(1),x.size(2)) 196 | return y 197 | 198 | # debug model structure 199 | #net = ft_net(767) 200 | #net = ft_net_dense(767) 201 | #print(net) 202 | #input = Variable(torch.FloatTensor(8, 3, 224, 224)) 203 | #output = net(input) 204 | #print('net output size:') 205 | #print(output.shape) 206 | -------------------------------------------------------------------------------- /Using_SYSU30k_Test_Set/test_sysu_combine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import print_function, division 4 | 5 | import argparse 6 | import torch 7 | import torch.nn as nn 8 | import torch.optim as optim 9 | from torch.optim import lr_scheduler 10 | from torch.autograd import Variable 11 | import numpy as np 12 | import torchvision 13 | from torchvision import datasets, models, transforms 14 | import time 15 | import os 16 | import scipy.io 17 | from model import ft_net, ft_net_dense, PCB, PCB_test 18 | 19 | ###################################################################### 20 | # Options 21 | # -------- 22 | os.environ['CUDA_VISIBLE_DEVICES'] = '7' 23 | parser = argparse.ArgumentParser(description='Training') 24 | parser.add_argument('--gpu_ids',default='0', type=str,help='gpu_ids: e.g. 0 0,1,2 0,2') 25 | parser.add_argument('--which_epoch',default='last', type=str, help='0,1,2,3...or last') 26 | parser.add_argument('--test_dir',default='/home/zzd/Market/pytorch',type=str, help='./test_data') 27 | parser.add_argument('--name', default='ft_ResNet50', type=str, help='save model path') 28 | parser.add_argument('--batchsize', default=100, type=int, help='batchsize') 29 | parser.add_argument('--use_dense', action='store_true', help='use densenet121' ) 30 | parser.add_argument('--PCB', action='store_true', help='use PCB' ) 31 | 32 | opt = parser.parse_args() 33 | 34 | str_ids = opt.gpu_ids.split(',') 35 | #which_epoch = opt.which_epoch 36 | name = opt.name 37 | test_dir = opt.test_dir 38 | 39 | gpu_ids = [] 40 | for str_id in str_ids: 41 | id = int(str_id) 42 | if id >=0: 43 | gpu_ids.append(id) 44 | 45 | # set gpu ids 46 | if len(gpu_ids)>0: 47 | torch.cuda.set_device(gpu_ids[0]) 48 | 49 | ###################################################################### 50 | # Load Data 51 | # --------- 52 | # 53 | # We will use torchvision and torch.utils.data packages for loading the 54 | # data. 55 | # 56 | data_transforms = transforms.Compose([ 57 | transforms.Resize((256,128), interpolation=3), 58 | # transforms.CenterCrop((256,128)), 59 | transforms.ToTensor(), 60 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 61 | ############### Ten Crop 62 | #transforms.TenCrop(224), 63 | #transforms.Lambda(lambda crops: torch.stack( 64 | # [transforms.ToTensor()(crop) 65 | # for crop in crops] 66 | # )), 67 | #transforms.Lambda(lambda crops: torch.stack( 68 | # [transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(crop) 69 | # for crop in crops] 70 | # )) 71 | ]) 72 | 73 | if opt.PCB: 74 | data_transforms = transforms.Compose([ 75 | transforms.Resize((384,192), interpolation=3), 76 | transforms.ToTensor(), 77 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 78 | ]) 79 | 80 | 81 | data_dir = test_dir 82 | image_datasets = {x: datasets.ImageFolder( os.path.join(data_dir,x) ,data_transforms) for x in ['gallery','query']} 83 | dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=opt.batchsize, 84 | shuffle=False, num_workers=16) for x in ['gallery','query']} 85 | 86 | class_names = image_datasets['query'].classes 87 | use_gpu = torch.cuda.is_available() 88 | 89 | ###################################################################### 90 | # Load model 91 | #--------------------------- 92 | def load_network(network): 93 | save_path = os.path.join(name,'net_%s.pth'%opt.which_epoch) 94 | network.load_state_dict(torch.load(save_path)) 95 | return network 96 | 97 | 98 | ###################################################################### 99 | # Extract feature 100 | # ---------------------- 101 | # 102 | # Extract feature from a trained model. 103 | # 104 | def fliplr(img): 105 | '''flip horizontal''' 106 | inv_idx = torch.arange(img.size(3)-1,-1,-1).long() # N x C x H x W 107 | img_flip = img.index_select(3,inv_idx) 108 | return img_flip 109 | 110 | def extract_feature(model,dataloaders): 111 | features = torch.FloatTensor() 112 | count = 0 113 | for data in dataloaders: 114 | img, label = data 115 | n, c, h, w = img.size() 116 | count += n 117 | print(count) 118 | if opt.use_dense: 119 | ff = torch.FloatTensor(n,1024).zero_() 120 | else: 121 | ff = torch.FloatTensor(n,28310).zero_() 122 | if opt.PCB: 123 | ff = torch.FloatTensor(n,2048,6).zero_() # we have four parts 124 | for i in range(2): 125 | if(i==1): 126 | img = fliplr(img) 127 | input_img = Variable(img.cuda()) 128 | outputs = model(input_img) 129 | # # outputs = torch.nn.functional.softmax(outputs,-1) 130 | f = outputs.data.cpu() 131 | #f = torch.nn.functional.softmax(f) 132 | ff = ff+f 133 | #o1 = model(Variable(img.cuda())) 134 | #f1 = o1.data.cpu() 135 | #fnorm = torch.norm(f1, p=2, dim=1, keepdim=True) 136 | #f1 = f1.div(fnorm.expand_as(f1)) 137 | #img = fliplr(img) 138 | #o2 = model(Variable(img.cuda())) 139 | #f2 = o2.data.cpu() 140 | #fnorm = torch.norm(f2, p=2, dim=1, keepdim=True) 141 | #f2 = f2.div(fnorm.expand_as(f2)) 142 | #ff = torch.cat((f1, f2), 1) 143 | # norm feature 144 | if opt.PCB: 145 | # feature size (n,2048,4) 146 | fnorm = torch.norm(ff, p=2, dim=1, keepdim=True) 147 | ff = ff.div(fnorm.expand_as(ff)) 148 | ff = ff.view(ff.size(0), -1) 149 | else: 150 | fnorm = torch.norm(ff, p=2, dim=1, keepdim=True) 151 | ff = ff.div(fnorm.expand_as(ff)) 152 | #ff = ff 153 | 154 | features = torch.cat((features,ff), 0) 155 | return features 156 | 157 | def get_id(img_path): 158 | camera_id = [] 159 | labels = [] 160 | for path, v in img_path: 161 | filename = path.split('/')[-1] 162 | #label = filename[0:4] 163 | label = filename.split('_')[0] 164 | camera = filename.split('c')[1] 165 | if label[0:10]=='0000others': 166 | labels.append(-1000) 167 | else: 168 | labels.append(int(label)) 169 | camera_id.append(int(camera[0])) 170 | return camera_id, labels 171 | 172 | gallery_path = image_datasets['gallery'].imgs 173 | query_path = image_datasets['query'].imgs 174 | 175 | gallery_cam,gallery_label = get_id(gallery_path) 176 | query_cam,query_label = get_id(query_path) 177 | 178 | ###################################################################### 179 | # Load Collected data Trained model 180 | print('-------test-----------') 181 | if opt.use_dense: 182 | model_structure = ft_net_dense(28310) 183 | else: 184 | model_structure = ft_net(28310) 185 | 186 | if opt.PCB: 187 | model_structure = PCB(28310) 188 | 189 | model_structure = nn.DataParallel(model_structure) 190 | model = load_network(model_structure) 191 | 192 | # Remove the final fc layer and classifier layer 193 | #print(model) 194 | if not opt.PCB: 195 | model.module.model.fc = nn.Sequential() 196 | # model.classifier = nn.Sequential() 197 | else: 198 | model = PCB_test(model) 199 | 200 | print(model) 201 | 202 | # Change to test mode 203 | model = model.eval() 204 | if use_gpu: 205 | model = model.cuda() 206 | 207 | # Extract feature 208 | gallery_feature = extract_feature(model,dataloaders['gallery']) 209 | query_feature = extract_feature(model,dataloaders['query']) 210 | 211 | 212 | import scipy.io 213 | import torch 214 | import numpy as np 215 | import time 216 | 217 | ####################################################################### 218 | # Evaluate 219 | def evaluate(qf,ql,qc,gf,gl,gc): 220 | query = qf 221 | score = np.dot(gf,query) 222 | # predict index 223 | index = np.argsort(score) #from small to large 224 | index = index[::-1] 225 | #index = index[0:2000] 226 | # good index 227 | query_index = np.argwhere(gl==ql) 228 | camera_index = np.argwhere(gc==qc) 229 | 230 | good_index = np.setdiff1d(query_index, camera_index, assume_unique=True) 231 | junk_index1 = np.argwhere(gl==-1) 232 | junk_index2 = np.intersect1d(query_index, camera_index) 233 | junk_index = np.append(junk_index2, junk_index1) #.flatten()) 234 | 235 | CMC_tmp = compute_mAP(index, good_index, junk_index) 236 | return CMC_tmp 237 | 238 | 239 | def compute_mAP(index, good_index, junk_index): 240 | ap = 0 241 | cmc = torch.IntTensor(len(index)).zero_() 242 | if good_index.size==0: # if empty 243 | cmc[0] = -1 244 | return ap,cmc 245 | 246 | # remove junk_index 247 | mask = np.in1d(index, junk_index, invert=True) 248 | index = index[mask] 249 | 250 | # find good_index index 251 | ngood = len(good_index) 252 | mask = np.in1d(index, good_index) 253 | rows_good = np.argwhere(mask==True) 254 | rows_good = rows_good.flatten() 255 | 256 | cmc[rows_good[0]:] = 1 257 | for i in range(ngood): 258 | d_recall = 1.0/ngood 259 | precision = (i+1)*1.0/(rows_good[i]+1) 260 | if rows_good[i]!=0: 261 | old_precision = i*1.0/rows_good[i] 262 | else: 263 | old_precision=1.0 264 | ap = ap + d_recall*(old_precision + precision)/2 265 | 266 | return ap, cmc 267 | 268 | 269 | 270 | 271 | query_feature = query_feature.numpy() # result['query_f'] 272 | query_cam = np.asarray(query_cam) #result['query_cam'][0] 273 | query_label = np.asarray(query_label)#result['query_label'][0] 274 | gallery_feature = gallery_feature.numpy()#result['gallery_f'] 275 | gallery_cam = np.asarray(gallery_cam)#result['gallery_cam'][0] 276 | gallery_label = np.asarray(gallery_label)#result['gallery_label'][0] 277 | 278 | 279 | 280 | CMC = torch.IntTensor(len(gallery_label)).zero_() 281 | ap = 0.0 282 | #print(query_label) 283 | for i in range(len(query_label)): 284 | ap_tmp, CMC_tmp = evaluate(query_feature[i],query_label[i],query_cam[i],gallery_feature,gallery_label,gallery_cam) 285 | if CMC_tmp[0]==-1: 286 | continue 287 | CMC = CMC + CMC_tmp 288 | ap += ap_tmp 289 | print(i, CMC_tmp[0]) 290 | 291 | CMC = CMC.float() 292 | CMC = CMC/len(query_label) #average CMC 293 | print('Rank@1:%f Rank@5:%f Rank@10:%f mAP:%f'%(CMC[0],CMC[4],CMC[9],ap/len(query_label))) 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /Using_SYSU30k_Train_Set/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/Using_SYSU30k_Train_Set/.DS_Store -------------------------------------------------------------------------------- /Using_SYSU30k_Train_Set/DistributedTrainSYSU30k/README.md: -------------------------------------------------------------------------------- 1 | 2 | The distributed training code will be released here. -------------------------------------------------------------------------------- /Using_SYSU30k_Train_Set/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Download the dataset 3 | 4 | **Note** that our original training set occupies 462G's memory. We are not able to upload the original data taking up such a large memory. As a result, we downsample the train images from 288 * x resolution to 144 * x resolution with x representing the shortest edge. The compressed data sum up to 82.2G. 5 | 6 | The test set is uncompressed due to the appropriate memory size. 7 | 8 | ### Download the training set 9 | 10 | | Dataset | Link to download | baidu pan code | 11 | |:------------------:|:------------------:|:------------------:| 12 | | sysu_train_set_all_part1.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 13 | | sysu_train_set_all_part2.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 14 | | sysu_train_set_all_part3.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 15 | | sysu_train_set_all_part4.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 16 | | sysu_train_set_all_part5.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 17 | | sysu_train_set_all_part6.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 18 | | sysu_train_set_all_part7.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 19 | | sysu_train_set_all_part8.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 20 | | sysu_train_set_all_part9.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 21 | | sysu_train_set_all_part10.tar | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 22 | 23 | 24 | ### Download the bag-level label for training set, the training list, and the validation list. 25 | 26 | | Dataset | Link to download | baidu pan code | 27 | |:------------------:|:------------------:|:------------------:| 28 | |bag_level_label.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 29 | |train.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 30 | |query.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 31 | |gallery.txt | [:arrow_down:](https://pan.baidu.com/s/1Y9phSZ5jy02szFZB_KqlyQ) | 1qzv | 32 | 33 | 34 | 35 | ## Data organization 36 | 37 | At last, the folder looks like: 38 | 39 | ``` 40 | SYSU-30k-released 41 | ├── SYSU-30k-released 42 | │ ├── meta 43 | │ | ├── train.txt (for weakly supervised training, "filename\n" in each line) 44 | │ | ├── query.txt (for evaluation) 45 | │ | ├── gallery.txt (for evaluation) 46 | │ ├── sysu_train_set_all 47 | │ | ├── 0000000001 48 | │ | ├── 0000000002 49 | │ | ├── 0000000003 50 | │ | ├── 0000000004 51 | │ | ├── ... 52 | │ | ├── 0000028309 53 | │ | ├── 0000028310 54 | ``` 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /sysu-30k-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/sysu-30k-example.png -------------------------------------------------------------------------------- /sysu-30k-statistics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanggrun/SYSU-30k/d7c55710aecc68ab7d3bfe3bd1113fdc9013bf35/sysu-30k-statistics.png --------------------------------------------------------------------------------