├── .gitignore ├── README.md ├── baseline ├── __init__.py ├── dataset │ ├── Dataset.py │ ├── __init__.py │ └── add_transforms.py ├── model │ ├── DeepMAR.py │ ├── __init__.py │ └── resnet.py └── utils │ ├── __init__.py │ ├── evaluate.py │ └── utils.py ├── dataset ├── demo │ ├── .gitsave │ └── demo_image.png ├── pa100k │ └── .gitsave ├── peta │ └── .gitsave ├── rap │ └── .gitsave └── rap2 │ └── .gitsave └── script ├── dataset ├── transform_pa100k.py ├── transform_peta.py ├── transform_rap.py └── transform_rap2.py └── experiment ├── demo.py ├── test.sh ├── train.sh └── train_deepmar_resnet50.py /.gitignore: -------------------------------------------------------------------------------- 1 | ## General 2 | exp 3 | dataset/peta/* 4 | dataset/pa100k/* 5 | dataset/rap/* 6 | dataset/rap2/* 7 | !dataset/peta/.gitsave 8 | !dataset/rap/.gitsave 9 | !dataset/pa100k/.gitsave 10 | !dataset/rap2/.gitsave 11 | 12 | # Compiled Object files 13 | *.slo 14 | *.lo 15 | *.o 16 | *.cuo 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | 27 | # Compiled protocol buffers 28 | *.pb.h 29 | *.pb.cc 30 | *_pb2.py 31 | 32 | # Compiled python 33 | *.pyc 34 | 35 | # Compiled MATLAB 36 | *.mex* 37 | 38 | # IPython notebook checkpoints 39 | .ipynb_checkpoints 40 | 41 | # Editor temporaries 42 | *.swp 43 | *~ 44 | 45 | # Sublime Text settings 46 | *.sublime-workspace 47 | *.sublime-project 48 | 49 | # Eclipse Project settings 50 | *.*project 51 | .settings 52 | 53 | # QtCreator files 54 | *.user 55 | 56 | # PyCharm files 57 | .idea 58 | 59 | # Visual Studio Code files 60 | .vscode 61 | 62 | # OSX dir files 63 | .DS_Store 64 | 65 | ## Caffe 66 | 67 | # User's build configuration 68 | Makefile.config 69 | 70 | # Data and models are either 71 | # 1. reference, and not casually committed 72 | # 2. custom, and live on their own unless they're deliberated contributed 73 | data/* 74 | models/* 75 | *.caffemodel 76 | *.caffemodel.h5 77 | *.solverstate 78 | *.solverstate.h5 79 | *.binaryproto 80 | *leveldb 81 | *lmdb 82 | 83 | # build, distribute, and bins (+ python proto bindings) 84 | build 85 | .build_debug/* 86 | .build_release/* 87 | distribute/* 88 | *.testbin 89 | *.bin 90 | python/caffe/proto/ 91 | cmake_build 92 | .cmake_build 93 | 94 | # Generated documentation 95 | docs/_site 96 | docs/_includes 97 | docs/gathered 98 | _site 99 | doxygen 100 | docs/dev 101 | 102 | # LevelDB files 103 | *.sst 104 | *.ldb 105 | LOCK 106 | LOG* 107 | CURRENT 108 | MANIFEST-* 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Implement of Deep Multi-attribute Recognition model under ResNet50 backbone network 2 | 3 | ## Preparation 4 | 5 | 6 | **Prerequisite: Python 2.7 and Pytorch 0.3.1** 7 | 8 | 1. Install [Pytorch](https://pytorch.org/) 9 | 10 | 2. Download and prepare the dataset as follow: 11 | 12 | a. PETA [Baidu Yun](https://pan.baidu.com/s/1q8nsydT7xkDjZJOxvPcoEw), passwd: 5vep, or [Google Drive](https://drive.google.com/open?id=1q4cux17K3zNBgIrDV4FtcHJPLzXNKfYG). 13 | 14 | ``` 15 | ./dataset/peta/images/*.png 16 | ./dataset/peta/PETA.mat 17 | ./dataset/peta/README 18 | ``` 19 | ``` 20 | python script/dataset/transform_peta.py 21 | ``` 22 | 23 | b. RAP [Google Drive](https://drive.google.com/open?id=1FkXlpbk3R-M_vkvM8ByeAZVAMzN6vUOr). 24 | ``` 25 | ./dataset/rap/RAP_dataset/*.png 26 | ./dataset/rap/RAP_annotation/RAP_annotation.mat 27 | ``` 28 | ``` 29 | python script/dataset/transform_rap.py 30 | ``` 31 | 32 | c. PA100K [Links](https://drive.google.com/drive/folders/0B5_Ra3JsEOyOUlhKM0VPZ1ZWR2M) 33 | ``` 34 | ./dataset/pa100k/data/*.png 35 | ./dataset/pa100k/annotation.mat 36 | ``` 37 | ``` 38 | python script/dataset/transform_pa100k.py 39 | ``` 40 | 41 | d. RAP(v2) [Links](https://drive.google.com/open?id=1hoPIB5NJKf3YGMvLFZnIYG5JDcZTxHph). 42 | ``` 43 | ./dataset/rap2/RAP_dataset/*.png 44 | ./dataset/rap2/RAP_annotation/RAP_annotation.mat 45 | ``` 46 | ``` 47 | python script/dataset/transform_rap2.py 48 | ``` 49 | 50 | 51 | ## Train the model 52 | 53 | 54 | ``` 55 | sh script/experiment/train.sh 56 | ``` 57 | 58 | 59 | ## Test the model 60 | 61 | 62 | ``` 63 | sh script/experiment/test.sh 64 | ``` 65 | 66 | 67 | 68 | ## Demo 69 | 70 | 71 | ``` 72 | python script/experiment/demo.py 73 | ``` 74 | 75 | 76 | 77 | ## Citation 78 | 79 | Please cite this paper in your publications if it helps your research: 80 | 81 | 82 | ``` 83 | @inproceedings{li2015deepmar, 84 | author = {Dangwei Li and Xiaotang Chen and Kaiqi Huang}, 85 | title = {Multi-attribute Learning for Pedestrian Attribute Recognition in Surveillance Scenarios}, 86 | booktitle = {ACPR}, 87 | pages={111--115}, 88 | year = {2015} 89 | } 90 | ``` 91 | 92 | ## Thanks 93 | 94 | 95 | Partial codes are based on the repository from [Houjing Huang](https://github.com/huanghoujing). 96 | 97 | The code should only be used for academic research. 98 | 99 | 100 | -------------------------------------------------------------------------------- /baseline/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/baseline/__init__.py -------------------------------------------------------------------------------- /baseline/dataset/Dataset.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | import os 3 | from PIL import Image 4 | import numpy as np 5 | import cPickle as pickle 6 | import copy 7 | 8 | class AttDataset(data.Dataset): 9 | """ 10 | person attribute dataset interface 11 | """ 12 | def __init__( 13 | self, 14 | dataset, 15 | partition, 16 | split='train', 17 | partition_idx=0, 18 | transform=None, 19 | target_transform=None, 20 | **kwargs): 21 | if os.path.exists( dataset ): 22 | self.dataset = pickle.load(open(dataset)) 23 | else: 24 | print dataset + ' does not exist in dataset.' 25 | raise ValueError 26 | if os.path.exists( partition ): 27 | self.partition = pickle.load(open(partition)) 28 | else: 29 | print partition + ' does not exist in dataset.' 30 | raise ValueError 31 | if not self.partition.has_key(split): 32 | print split + ' does not exist in dataset.' 33 | raise ValueError 34 | 35 | if partition_idx > len(self.partition[split])-1: 36 | print 'partition_idx is out of range in partition.' 37 | raise ValueError 38 | 39 | self.transform = transform 40 | self.target_transform = target_transform 41 | 42 | # create image, label based on the selected partition and dataset split 43 | self.root_path = self.dataset['root'] 44 | self.att_name = [self.dataset['att_name'][i] for i in self.dataset['selected_attribute']] 45 | self.image = [] 46 | self.label = [] 47 | for idx in self.partition[split][partition_idx]: 48 | self.image.append(self.dataset['image'][idx]) 49 | label_tmp = np.array(self.dataset['att'][idx])[self.dataset['selected_attribute']].tolist() 50 | self.label.append(label_tmp) 51 | 52 | def __getitem__(self, index): 53 | """ 54 | Args: 55 | index (int): Index 56 | Returns: 57 | tuple: (image, target) where target is the index of the target class 58 | """ 59 | imgname, target = self.image[index], self.label[index] 60 | # load image and labels 61 | imgname = os.path.join(self.dataset['root'], imgname) 62 | img = Image.open(imgname) 63 | if self.transform is not None: 64 | img = self.transform( img ) 65 | 66 | # default no transform 67 | target = np.array(target).astype(np.float32) 68 | target[target == 0] = -1 69 | target[target == 2] = 0 70 | if self.target_transform is not None: 71 | target = self.transform( target ) 72 | 73 | return img, target 74 | 75 | # useless for personal batch sampler 76 | def __len__(self): 77 | return len(self.image) 78 | 79 | 80 | -------------------------------------------------------------------------------- /baseline/dataset/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/baseline/dataset/__init__.py -------------------------------------------------------------------------------- /baseline/dataset/add_transforms.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import numbers 4 | __all__ = ["AddPad", "AddCrop"] 5 | 6 | class AddCrop(object): 7 | def __init__(self, size): 8 | self.size = size # two 9 | assert len(self.size) == 2 10 | def __repr__(self): 11 | return self.__class__.__name__ + '(size={0})'.format(self.size) 12 | def __call__(self, img): 13 | shape = img.shape # 3*H*W 14 | h_high = shape[1] - self.size[0] 15 | w_high = shape[2] - self.size[1] 16 | h_start = np.random.randint(low=0, high=h_high) 17 | w_start = np.random.randint(low=0, high=w_high) 18 | return img[:, h_start: h_start+self.size[0], w_start: w_start+self.size[1]] 19 | 20 | class AddPad(object): 21 | def __init__(self, padding, fill=0): 22 | self.padding = padding 23 | self.fill = fill 24 | if isinstance(self.padding, numbers.Number): 25 | self.pad_l = int(self.padding) 26 | self.pad_r = int(self.padding) 27 | self.pad_u = int(self.padding) 28 | self.pad_d = int(self.padding) 29 | elif isinstance(self.padding, (list, tuple)) and len(self.padding) == 4: 30 | self.pad_l = int(self.padding[0]) 31 | self.pad_r = int(self.padding[1]) 32 | self.pad_u = int(self.padding[2]) 33 | self.pad_d = int(self.padding[3]) 34 | else: 35 | print "The type of padding is not right." 36 | raise ValueError 37 | if self.pad_l <0 or self.pad_r < 0 or self.pad_u < 0 or self.pad_d < 0: 38 | raise ValueError 39 | if isinstance(self.fill, numbers.Number): 40 | self.fill_value = [self.fill] 41 | elif isinstance(self.fill, list): 42 | self.fill_value = self.fill 43 | 44 | def __repr__(self): 45 | return self.__class__.__name__ + '(padding={0})'.format(self.padding) 46 | 47 | def __call__(self, img): 48 | """ 49 | Args: 50 | img: a 3-dimensional torch tensor with shape [R,G,B]*H*W 51 | Returns: 52 | img: a 3-dimensional padded tensor with shape [R,G,B]*H'*W' 53 | """ 54 | if not (self.pad_l or self.pad_r or self.pad_u or self.pad_d): 55 | return img 56 | shape = img.shape 57 | img_ = torch.rand(shape[0], shape[1]+self.pad_u+self.pad_d, \ 58 | shape[2]+self.pad_l+self.pad_r) 59 | for i in range(shape[0]): 60 | img_[i, 0:self.pad_u, :] = self.fill_value[i%len(self.fill_value)] 61 | img_[i, -(self.pad_d+1):-1, :] = self.fill_value[i%len(self.fill_value)] 62 | img_[i, :, 0:self.pad_l] = self.fill_value[i%len(self.fill_value)] 63 | img_[i, :, -(self.pad_r+1):-1] = self.fill_value[i%len(self.fill_value)] 64 | img_[i, self.pad_u:self.pad_u+shape[1], self.pad_l:self.pad_l+shape[2]] = img[i, :, :] 65 | return img_ 66 | -------------------------------------------------------------------------------- /baseline/model/DeepMAR.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.init as init 4 | import torch.nn.functional as F 5 | from torch.autograd import Variable 6 | import numpy as np 7 | from .resnet import resnet50 8 | 9 | 10 | class DeepMAR_ResNet50(nn.Module): 11 | def __init__( 12 | self, 13 | **kwargs 14 | ): 15 | super(DeepMAR_ResNet50, self).__init__() 16 | 17 | # init the necessary parameter for netwokr structure 18 | if kwargs.has_key('num_att'): 19 | self.num_att = kwargs['num_att'] 20 | else: 21 | self.num_att = 35 22 | if kwargs.has_key('last_conv_stride'): 23 | self.last_conv_stride = kwargs['last_conv_stride'] 24 | else: 25 | self.last_conv_stride = 2 26 | if kwargs.has_key('drop_pool5'): 27 | self.drop_pool5 = kwargs['drop_pool5'] 28 | else: 29 | self.drop_pool5 = True 30 | if kwargs.has_key('drop_pool5_rate'): 31 | self.drop_pool5_rate = kwargs['drop_pool5_rate'] 32 | else: 33 | self.drop_pool5_rate = 0.5 34 | if kwargs.has_key('pretrained'): 35 | self.pretrained = kwargs['pretrained'] 36 | else: 37 | self.pretrained = True 38 | 39 | self.base = resnet50(pretrained=self.pretrained, last_conv_stride=self.last_conv_stride) 40 | 41 | self.classifier = nn.Linear(2048, self.num_att) 42 | init.normal(self.classifier.weight, std=0.001) 43 | init.constant(self.classifier.bias, 0) 44 | 45 | def forward(self, x): 46 | x = self.base(x) 47 | x = F.avg_pool2d(x, x.shape[2:]) 48 | x = x.view(x.size(0), -1) 49 | if self.drop_pool5: 50 | x = F.dropout(x, p=self.drop_pool5_rate, training=self.training) 51 | x = self.classifier(x) 52 | return x 53 | 54 | class DeepMAR_ResNet50_ExtractFeature(object): 55 | """ 56 | A feature extraction function 57 | """ 58 | def __init__(self, model, **kwargs): 59 | self.model = model 60 | 61 | def __call__(self, imgs): 62 | old_train_eval_model = self.model.training 63 | 64 | # set the model to be eval 65 | self.model.eval() 66 | 67 | # imgs should be Variable 68 | if not isinstance(imgs, Variable): 69 | print 'imgs should be type: Variable' 70 | raise ValueError 71 | score = self.model(imgs) 72 | score = score.data.cpu().numpy() 73 | 74 | self.model.train(old_train_eval_model) 75 | 76 | return score 77 | -------------------------------------------------------------------------------- /baseline/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/baseline/model/__init__.py -------------------------------------------------------------------------------- /baseline/model/resnet.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import math 3 | import torch.utils.model_zoo as model_zoo 4 | 5 | 6 | __all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', 7 | 'resnet152'] 8 | 9 | 10 | model_urls = { 11 | 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', 12 | 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', 13 | 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', 14 | 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', 15 | 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', 16 | } 17 | 18 | 19 | def conv3x3(in_planes, out_planes, stride=1): 20 | """3x3 convolution with padding""" 21 | return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, 22 | padding=1, bias=False) 23 | 24 | 25 | class BasicBlock(nn.Module): 26 | expansion = 1 27 | 28 | def __init__(self, inplanes, planes, stride=1, downsample=None): 29 | super(BasicBlock, self).__init__() 30 | self.conv1 = conv3x3(inplanes, planes, stride) 31 | self.bn1 = nn.BatchNorm2d(planes) 32 | self.relu = nn.ReLU(inplace=True) 33 | self.conv2 = conv3x3(planes, planes) 34 | self.bn2 = nn.BatchNorm2d(planes) 35 | self.downsample = downsample 36 | self.stride = stride 37 | 38 | def forward(self, x): 39 | residual = x 40 | 41 | out = self.conv1(x) 42 | out = self.bn1(out) 43 | out = self.relu(out) 44 | 45 | out = self.conv2(out) 46 | out = self.bn2(out) 47 | 48 | if self.downsample is not None: 49 | residual = self.downsample(x) 50 | 51 | out += residual 52 | out = self.relu(out) 53 | 54 | return out 55 | 56 | 57 | class Bottleneck(nn.Module): 58 | expansion = 4 59 | 60 | def __init__(self, inplanes, planes, stride=1, downsample=None): 61 | super(Bottleneck, self).__init__() 62 | self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) 63 | self.bn1 = nn.BatchNorm2d(planes) 64 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, 65 | padding=1, bias=False) 66 | self.bn2 = nn.BatchNorm2d(planes) 67 | self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) 68 | self.bn3 = nn.BatchNorm2d(planes * 4) 69 | self.relu = nn.ReLU(inplace=True) 70 | self.downsample = downsample 71 | self.stride = stride 72 | 73 | def forward(self, x): 74 | residual = x 75 | 76 | out = self.conv1(x) 77 | out = self.bn1(out) 78 | out = self.relu(out) 79 | 80 | out = self.conv2(out) 81 | out = self.bn2(out) 82 | out = self.relu(out) 83 | 84 | out = self.conv3(out) 85 | out = self.bn3(out) 86 | 87 | if self.downsample is not None: 88 | residual = self.downsample(x) 89 | 90 | out += residual 91 | out = self.relu(out) 92 | 93 | return out 94 | 95 | 96 | class ResNet(nn.Module): 97 | 98 | def __init__(self, block, layers, last_conv_stride=2): 99 | self.inplanes = 64 100 | super(ResNet, self).__init__() 101 | self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, 102 | bias=False) 103 | self.bn1 = nn.BatchNorm2d(64) 104 | self.relu = nn.ReLU(inplace=True) 105 | self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) 106 | self.layer1 = self._make_layer(block, 64, layers[0]) 107 | self.layer2 = self._make_layer(block, 128, layers[1], stride=2) 108 | self.layer3 = self._make_layer(block, 256, layers[2], stride=2) 109 | self.layer4 = self._make_layer(block, 512, layers[3], stride=last_conv_stride) 110 | 111 | for m in self.modules(): 112 | if isinstance(m, nn.Conv2d): 113 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 114 | m.weight.data.normal_(0, math.sqrt(2. / n)) 115 | elif isinstance(m, nn.BatchNorm2d): 116 | m.weight.data.fill_(1) 117 | m.bias.data.zero_() 118 | 119 | def _make_layer(self, block, planes, blocks, stride=1): 120 | downsample = None 121 | if stride != 1 or self.inplanes != planes * block.expansion: 122 | downsample = nn.Sequential( 123 | nn.Conv2d(self.inplanes, planes * block.expansion, 124 | kernel_size=1, stride=stride, bias=False), 125 | nn.BatchNorm2d(planes * block.expansion), 126 | ) 127 | 128 | layers = [] 129 | layers.append(block(self.inplanes, planes, stride, downsample)) 130 | self.inplanes = planes * block.expansion 131 | for i in range(1, blocks): 132 | layers.append(block(self.inplanes, planes)) 133 | 134 | return nn.Sequential(*layers) 135 | 136 | def forward(self, x): 137 | x = self.conv1(x) 138 | x = self.bn1(x) 139 | x = self.relu(x) 140 | x = self.maxpool(x) 141 | 142 | x = self.layer1(x) 143 | x = self.layer2(x) 144 | x = self.layer3(x) 145 | x = self.layer4(x) 146 | 147 | return x 148 | 149 | def remove_fc(state_dict): 150 | """ Remove the fc layer parameter from state_dict. """ 151 | for key, value in state_dict.items(): 152 | if key.startswith('fc.'): 153 | del state_dict[key] 154 | return state_dict 155 | 156 | 157 | def resnet18(pretrained=False, **kwargs): 158 | """Constructs a ResNet-18 model. 159 | 160 | Args: 161 | pretrained (bool): If True, returns a model pre-trained on ImageNet 162 | """ 163 | model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs) 164 | if pretrained: 165 | model.load_state_dict(remove_fc(model_zoo.load_url(model_urls['resnet18']))) 166 | return model 167 | 168 | 169 | def resnet34(pretrained=False, **kwargs): 170 | """Constructs a ResNet-34 model. 171 | 172 | Args: 173 | pretrained (bool): If True, returns a model pre-trained on ImageNet 174 | """ 175 | model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs) 176 | if pretrained: 177 | model.load_state_dict(remove_fc(model_zoo.load_url(model_urls['resnet34']))) 178 | return model 179 | 180 | 181 | def resnet50(pretrained=False, **kwargs): 182 | """Constructs a ResNet-50 model. 183 | 184 | Args: 185 | pretrained (bool): If True, returns a model pre-trained on ImageNet 186 | """ 187 | model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) 188 | if pretrained: 189 | model.load_state_dict(remove_fc(model_zoo.load_url(model_urls['resnet50']))) 190 | return model 191 | 192 | 193 | def resnet101(pretrained=False, **kwargs): 194 | """Constructs a ResNet-101 model. 195 | 196 | Args: 197 | pretrained (bool): If True, returns a model pre-trained on ImageNet 198 | """ 199 | model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs) 200 | if pretrained: 201 | model.load_state_dict(remove_fc(model_zoo.load_url(model_urls['resnet101']))) 202 | return model 203 | 204 | 205 | def resnet152(pretrained=False, **kwargs): 206 | """Constructs a ResNet-152 model. 207 | 208 | Args: 209 | pretrained (bool): If True, returns a model pre-trained on ImageNet 210 | """ 211 | model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs) 212 | if pretrained: 213 | model.load_state_dict(remove_fc(model_zoo.load_url(model_urls['resnet152']))) 214 | return model 215 | -------------------------------------------------------------------------------- /baseline/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/baseline/utils/__init__.py -------------------------------------------------------------------------------- /baseline/utils/evaluate.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | from torch.autograd import Variable 4 | import numpy as np 5 | import copy 6 | import time 7 | import sys 8 | 9 | def extract_feat(feat_func, dataset, **kwargs): 10 | """ 11 | extract feature for images 12 | """ 13 | test_loader = torch.utils.data.DataLoader( 14 | dataset = dataset, batch_size = 32, 15 | num_workers = 2, pin_memory = True) 16 | # extract feature for all the images of test/val identities 17 | start_time = time.time() 18 | total_eps = len(test_loader) 19 | N = len(dataset.image) 20 | start = 0 21 | for ep, (imgs, labels) in enumerate(test_loader): 22 | imgs_var = Variable(imgs, volatile=True).cuda() 23 | feat_tmp = feat_func( imgs_var ) 24 | batch_size = feat_tmp.shape[0] 25 | if ep == 0: 26 | feat = np.zeros((N, feat_tmp.size/batch_size)) 27 | feat[start:start+batch_size, :] = feat_tmp.reshape((batch_size, -1)) 28 | start += batch_size 29 | end_time = time.time() 30 | print('{} batches done, total {:.2f}s'.format(total_eps, end_time-start_time)) 31 | return feat 32 | 33 | # attribute recognition evaluation 34 | def attribute_evaluate(feat_func, dataset, **kwargs): 35 | print "extracting features for attribute recognition" 36 | pt_result = extract_feat(feat_func, dataset) 37 | # obain the attributes from the attribute dictionary 38 | print "computing attribute recognition result" 39 | N = pt_result.shape[0] 40 | L = pt_result.shape[1] 41 | gt_result = np.zeros(pt_result.shape) 42 | # get the groundtruth attributes 43 | for idx, label in enumerate(dataset.label): 44 | gt_result[idx, :] = label 45 | pt_result[pt_result>=0] = 1 46 | pt_result[pt_result<0] = 0 47 | return attribute_evaluate_lidw(gt_result, pt_result) 48 | 49 | def attribute_evaluate_lidw(gt_result, pt_result): 50 | """ 51 | Input: 52 | gt_result, pt_result, N*L, with 0/1 53 | Output: 54 | result 55 | a dictionary, including label-based and instance-based evaluation 56 | label-based: label_pos_acc, label_neg_acc, label_acc 57 | instance-based: instance_acc, instance_precision, instance_recall, instance_F1 58 | """ 59 | # obtain the label-based and instance-based accuracy 60 | # compute the label-based accuracy 61 | if gt_result.shape != pt_result.shape: 62 | print 'Shape beteen groundtruth and predicted results are different' 63 | # compute the label-based accuracy 64 | result = {} 65 | gt_pos = np.sum((gt_result == 1).astype(float), axis=0) 66 | gt_neg = np.sum((gt_result == 0).astype(float), axis=0) 67 | pt_pos = np.sum((gt_result == 1).astype(float) * (pt_result == 1).astype(float), axis=0) 68 | pt_neg = np.sum((gt_result == 0).astype(float) * (pt_result == 0).astype(float), axis=0) 69 | label_pos_acc = 1.0*pt_pos/gt_pos 70 | label_neg_acc = 1.0*pt_neg/gt_neg 71 | label_acc = (label_pos_acc + label_neg_acc)/2 72 | result['label_pos_acc'] = label_pos_acc 73 | result['label_neg_acc'] = label_neg_acc 74 | result['label_acc'] = label_acc 75 | # compute the instance-based accuracy 76 | # precision 77 | gt_pos = np.sum((gt_result == 1).astype(float), axis=1) 78 | pt_pos = np.sum((pt_result == 1).astype(float), axis=1) 79 | floatersect_pos = np.sum((gt_result == 1).astype(float)*(pt_result == 1).astype(float), axis=1) 80 | union_pos = np.sum(((gt_result == 1)+(pt_result == 1)).astype(float),axis=1) 81 | # avoid empty label in predicted results 82 | cnt_eff = float(gt_result.shape[0]) 83 | for iter, key in enumerate(gt_pos): 84 | if key == 0: 85 | union_pos[iter] = 1 86 | pt_pos[iter] = 1 87 | gt_pos[iter] = 1 88 | cnt_eff = cnt_eff - 1 89 | continue 90 | if pt_pos[iter] == 0: 91 | pt_pos[iter] = 1 92 | instance_acc = np.sum(floatersect_pos/union_pos)/cnt_eff 93 | instance_precision = np.sum(floatersect_pos/pt_pos)/cnt_eff 94 | instance_recall = np.sum(floatersect_pos/gt_pos)/cnt_eff 95 | floatance_F1 = 2*instance_precision*instance_recall/(instance_precision+instance_recall) 96 | result['instance_acc'] = instance_acc 97 | result['instance_precision'] = instance_precision 98 | result['instance_recall'] = instance_recall 99 | result['instance_F1'] = floatance_F1 100 | return result 101 | -------------------------------------------------------------------------------- /baseline/utils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cPickle as pickle 3 | import datetime 4 | import time 5 | # from contextlib import contextmanger 6 | import torch 7 | from torch.autograd import Variable 8 | import random 9 | import numpy as np 10 | 11 | def time_str(fmt=None): 12 | if fmt is None: 13 | fmt = '%Y-%m-%d_%H:%M:%S' 14 | return datetime.datetime.today().strftime(fmt) 15 | 16 | def str2bool(v): 17 | return v.lower() in ("yes", "true", "1") 18 | 19 | def is_iterable(obj): 20 | return hasattr(obj, '__len__') 21 | 22 | def to_scalar(vt): 23 | """ 24 | transform a 1-length pytorch Variable or Tensor to scalar 25 | """ 26 | if isinstance(vt, Variable): 27 | return vt.data.cpu().numpy().flatten()[0] 28 | if torch.is_tensor(vt): 29 | return vt.cpu().numpy().flatten()[0] 30 | raise TypeError('Input should be a variable or tensor') 31 | 32 | def set_seed(rand_seed): 33 | np.random.seed( rand_seed ) 34 | random.seed( rand_seed ) 35 | torch.backends.cudnn.enabled = True 36 | torch.manual_seed( rand_seed ) 37 | torch.cuda.manual_seed( rand_seed ) 38 | 39 | def may_mkdir(fname): 40 | if not os.path.exists(os.path.dirname(os.path.abspath(fname))): 41 | os.makedirs(os.path.dirname(os.path.abspath(fname))) 42 | 43 | class AverageMeter(object): 44 | """ 45 | Computes and stores the average and current value 46 | """ 47 | def __init__(self): 48 | self.val = 0 49 | self.avg = 0 50 | self.sum = 0 51 | self.count = 0 52 | def reset(self): 53 | self.val = 0 54 | self.avg = 0 55 | self.sum = 0 56 | self.count = 0 57 | def update(self, val, n=1): 58 | self.val = val 59 | self.sum += val * n 60 | self.count += n 61 | self.avg = float(self.sum) / (self.count + 1e-10) 62 | 63 | class RunningAverageMeter(object): 64 | """ 65 | Computes and stores the running average and current value 66 | """ 67 | def __init__(self, hist=0.99): 68 | self.val = None 69 | self.avg = None 70 | self.hist = hist 71 | 72 | def reset(self): 73 | self.val = None 74 | self.avg = None 75 | 76 | def update(self, val): 77 | if self.avg is None: 78 | self.avg = val 79 | else: 80 | self.avg = self.avg * self.hist + val * (1 - self.hist) 81 | self.val = val 82 | 83 | class RecentAverageMeter(object): 84 | """ 85 | Stores and computes the average of recent values 86 | """ 87 | def __init__(self, hist_size=100): 88 | self.hist_size = hist_size 89 | self.fifo = [] 90 | self.val = 0 91 | 92 | def reset(self): 93 | self.fifo = [] 94 | self.val = 0 95 | 96 | def update(self, value): 97 | self.val = val 98 | self.fifo.append(val) 99 | if len(self.fifo) > self.hist_size: 100 | del self.fifo[0] 101 | @property 102 | def avg(self): 103 | assert len(self.fifo) > 0 104 | return float(sum(self.fifo)) / len(self.fifo) 105 | 106 | class ReDirectSTD(object): 107 | """ 108 | overwrites the sys.stdout or sys.stderr 109 | Args: 110 | fpath: file path 111 | console: one of ['stdout', 'stderr'] 112 | immediately_visiable: False 113 | Usage example: 114 | ReDirectSTD('stdout.txt', 'stdout', False) 115 | ReDirectSTD('stderr.txt', 'stderr', False) 116 | """ 117 | def __init__(self, fpath=None, console='stdout', immediately_visiable=False): 118 | import sys 119 | import os 120 | assert console in ['stdout', 'stderr'] 121 | self.console = sys.stdout if console == "stdout" else sys.stderr 122 | self.file = fpath 123 | self.f = None 124 | self.immediately_visiable = immediately_visiable 125 | if fpath is not None: 126 | # Remove existing log file 127 | if os.path.exists(fpath): 128 | os.remove(fpath) 129 | if console == 'stdout': 130 | sys.stdout = self 131 | else: 132 | sys.stderr = self 133 | 134 | def __del__(self): 135 | self.close() 136 | 137 | def __enter__(self): 138 | pass 139 | 140 | def __exit__(self, **args): 141 | self.close() 142 | 143 | def write(self, msg): 144 | self.console.write(msg) 145 | if self.file is not None: 146 | if not os.path.exists(os.path.dirname(os.path.abspath(self.file))): 147 | os.mkdir(os.path.dirname(os.path.abspath(self.file))) 148 | if self.immediately_visiable: 149 | with open(self.file, 'a') as f: 150 | f.write(msg) 151 | else: 152 | if self.f is None: 153 | self.f = open(self.file, 'w') 154 | self.f.write(msg) 155 | 156 | def flush(self): 157 | self.console.flush() 158 | if self.f is not None: 159 | self.f.flush() 160 | import os 161 | os.fsync(self.f.fileno()) 162 | 163 | def close(self): 164 | self.console.close() 165 | if self.f is not None: 166 | self.f.close() 167 | 168 | def find_index(seq, item): 169 | for i, x in enumerate(seq): 170 | if item == x: 171 | return i 172 | return -1 173 | 174 | def set_devices(sys_device_ids): 175 | """ 176 | Args: 177 | sys_device_ids: a tuple; which GPUs to use 178 | e.g. sys_device_ids = (), only use cpu 179 | sys_device_ids = (3,), use the 4-th gpu 180 | sys_device_ids = (0, 1, 2, 3,), use the first 4 gpus 181 | sys_device_ids = (0, 2, 4,), use the 1, 3 and 5 gpus 182 | """ 183 | import os 184 | visiable_devices = '' 185 | for i in sys_device_ids: 186 | visiable_devices += '{}, '.format(i) 187 | os.environ['CUDA_VISIBLE_DEVICES'] = visiable_devices 188 | # Return wrappers 189 | # Models and user defined Variables/Tensors would be transferred to 190 | # the first device 191 | device_id = 0 if len(sys_device_ids) > 0 else -1 192 | 193 | def transfer_optims(optims, device_id=-1): 194 | for optim in optims: 195 | if isinstance(optim, torch.optim.Optimizer): 196 | transfer_optim_state(optim.state, device_id=device_id) 197 | 198 | def transfer_optim_state(state, device_id=-1): 199 | for key, val in state.items(): 200 | if isinstance(val, dict): 201 | transfer_optim_state(val, device_id=device_id) 202 | elif isinstance(val, Variable): 203 | raise RuntimeError("Oops, state[{}] is a Variable!".format(key)) 204 | elif isinstance(val, torch.nn.Parameter): 205 | raise RuntimeError("Oops, state[{}] is a Parameter!".format(key)) 206 | else: 207 | try: 208 | if device_id == -1: 209 | state[key] = val.cpu() 210 | else: 211 | state[key] = val.cuda(device=device_id) 212 | except: 213 | pass 214 | 215 | 216 | def load_state_dict(model, src_state_dict): 217 | """ 218 | copy parameter from src_state_dict to model 219 | Arguments: 220 | model: A torch.nn.Module object 221 | src_state_dict: a dict containing parameters and persistent buffers 222 | """ 223 | from torch.nn import Parameter 224 | dest_state_dict = model.state_dict() 225 | for name, param in src_state_dict.items(): 226 | if name not in dest_state_dict: 227 | continue 228 | if isinstance(param, Parameter): 229 | param = param.data 230 | try: 231 | dest_state_dict[name].copy_(param) 232 | except Exception, msg: 233 | print("Warning: Error occurs when copying '{}': {}" 234 | .format(name, str(msg))) 235 | 236 | src_missing = set(dest_state_dict.keys()) - set(src_state_dict.keys()) 237 | if len(src_missing) > 0: 238 | print ("Keys not found in source state_dict: ") 239 | for n in src_missing: 240 | print('\t', n) 241 | 242 | dest_missint = set(src_state_dict.keys()) - set(dest_state_dict.keys()) 243 | if len(dest_missint): 244 | print ("Keys not found in destination state_dict: ") 245 | for n in dest_missint: 246 | print('\t', n) 247 | 248 | def load_ckpt(modules_optims, ckpt_file, load_to_cpu=True, verbose=True): 249 | """ 250 | load state_dict of module & optimizer from file 251 | Args: 252 | modules_optims: A two-element list which contains module and optimizer 253 | ckpt_file: the check point file 254 | load_to_cpu: Boolean, whether to transform tensors in model & optimizer to cpu type 255 | """ 256 | map_location = (lambda storage, loc: storage) if load_to_cpu else None 257 | ckpt = torch.load(ckpt_file, map_location=map_location) 258 | for m, sd in zip(modules_optims, ckpt['state_dicts']): 259 | m.load_state_dict(sd) 260 | if verbose: 261 | print("Resume from ckpt {}, \nepoch: {}, scores: {}".format( 262 | ckpt_file, ckpt['ep'], ckpt['scores'])) 263 | return ckpt['ep'], ckpt['scores'] 264 | 265 | def save_ckpt(modules_optims, ep, scores, ckpt_file): 266 | """ 267 | save state_dict of modules/optimizers to file 268 | Args: 269 | modules_optims: a two-element list which contains a module and a optimizer 270 | ep: the current epoch number 271 | scores: the performance of current module 272 | ckpt_file: the check point file path 273 | Note: 274 | torch.save() reserves device type and id of tensors to save. 275 | So when loading ckpt, you have to inform torch.load() to load these tensors 276 | to cpu or your desired gpu, if you change devices. 277 | """ 278 | state_dicts = [m.state_dict() for m in modules_optims] 279 | ckpt = dict(state_dicts = state_dicts, 280 | ep = ep, 281 | scores = scores) 282 | if not os.path.exists(os.path.dirname(os.path.abspath(ckpt_file))): 283 | os.mkdir(os.path.dirname(os.path.abspath(ckpt_file))) 284 | torch.save(ckpt, ckpt_file) 285 | 286 | def adjust_lr_staircase(param_groups, base_lrs, ep, decay_at_epochs, factor): 287 | """ Multiplied by a factor at the beging of specified epochs. Different 288 | params groups specify thier own base learning rates. 289 | Args: 290 | param_groups: a list of params 291 | base_lrs: starting learning rate, len(base_lrs) = len(params_groups) 292 | ep: current epoch, ep >= 1 293 | decay_at_epochs: a list or tuple; learning rates are multiplied by a factor 294 | at the begining of these epochs 295 | factor: a number in range (0, 1) 296 | Example: 297 | base_lrs = [0.1, 0.01] 298 | decay_at_epochs = [51, 101] 299 | factor = 0.1 300 | Note: 301 | It is meant to be called at the begining of an epoch 302 | """ 303 | assert len(base_lrs) == len(param_groups), \ 304 | 'You should specify base lr for each param group.' 305 | assert ep >= 1, "Current epoch number should be >= 1" 306 | 307 | if ep not in decay_at_epochs: 308 | return 309 | 310 | ind = find_index(decay_at_epochs, ep) 311 | for i, (g, base_lr) in enumerate(zip(param_groups, base_lrs)): 312 | g['lr'] = base_lr * factor ** (ind + 1) 313 | print('=====> Param group {}: lr adjusted to {:.10f}' 314 | .format(i, g['lr']).rstrip('0')) 315 | 316 | def may_set_mode(maybe_modules, mode): 317 | """ 318 | maybe_modules, an object or a list of objects. 319 | """ 320 | assert mode in ['train', 'eval'] 321 | if not is_iterable(maybe_modules): 322 | maybe_modules = [maybe_modules] 323 | for m in maybe_modules: 324 | if isinstance(m, torch.nn.Module): 325 | if mode == 'train': 326 | m.train() 327 | else: 328 | m.eval() 329 | -------------------------------------------------------------------------------- /dataset/demo/.gitsave: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/dataset/demo/.gitsave -------------------------------------------------------------------------------- /dataset/demo/demo_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/dataset/demo/demo_image.png -------------------------------------------------------------------------------- /dataset/pa100k/.gitsave: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/dataset/pa100k/.gitsave -------------------------------------------------------------------------------- /dataset/peta/.gitsave: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/dataset/peta/.gitsave -------------------------------------------------------------------------------- /dataset/rap/.gitsave: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/dataset/rap/.gitsave -------------------------------------------------------------------------------- /dataset/rap2/.gitsave: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangweili/pedestrian-attribute-recognition-pytorch/468ae58cf49d09931788f378e4b3d4cc2f171c22/dataset/rap2/.gitsave -------------------------------------------------------------------------------- /script/dataset/transform_pa100k.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import cPickle as pickle 5 | from scipy.io import loadmat 6 | 7 | np.random.seed(0) 8 | random.seed(0) 9 | 10 | def make_dir(path): 11 | if os.path.exists(path): 12 | pass 13 | else: 14 | os.mkdir(path) 15 | 16 | def generate_data_description(save_dir): 17 | """ 18 | create a dataset description file, which consists of images, labels 19 | """ 20 | dataset = dict() 21 | dataset['description'] = 'pa100k' 22 | dataset['root'] = './dataset/pa100k/data/' 23 | dataset['image'] = [] 24 | dataset['att'] = [] 25 | dataset['att_name'] = [] 26 | dataset['selected_attribute'] = range(26) 27 | # load ANNOTATION.MAT 28 | data = loadmat(open('./dataset/pa100k/annotation.mat', 'r')) 29 | for idx in range(26): 30 | dataset['att_name'].append(data['attributes'][idx][0][0]) 31 | 32 | for idx in range(80000): 33 | dataset['image'].append(data['train_images_name'][idx][0][0]) 34 | dataset['att'].append(data['train_label'][idx, :].tolist()) 35 | 36 | for idx in range(10000): 37 | dataset['image'].append(data['val_images_name'][idx][0][0]) 38 | dataset['att'].append(data['val_label'][idx, :].tolist()) 39 | 40 | for idx in range(10000): 41 | dataset['image'].append(data['test_images_name'][idx][0][0]) 42 | dataset['att'].append(data['test_label'][idx, :].tolist()) 43 | 44 | with open(os.path.join(save_dir, 'pa100k_dataset.pkl'), 'w+') as f: 45 | pickle.dump(dataset, f) 46 | 47 | def create_trainvaltest_split(traintest_split_file): 48 | """ 49 | create a dataset split file, which consists of index of the train/val/test splits 50 | """ 51 | partition = dict() 52 | partition['trainval'] = [] 53 | partition['train'] = [] 54 | partition['val'] = [] 55 | partition['test'] = [] 56 | partition['weight_trainval'] = [] 57 | partition['weight_train'] = [] 58 | # load ANNOTATION.MAT 59 | data = loadmat(open('./dataset/pa100k/annotation.mat', 'r')) 60 | train = range(80000) 61 | val = [i+80000 for i in range(10000)] 62 | test = [i+90000 for i in range(10000)] 63 | trainval = train + val 64 | partition['train'].append(train) 65 | partition['val'].append(val) 66 | partition['trainval'].append(trainval) 67 | partition['test'].append(test) 68 | # weight 69 | train_label = data['train_label'].astype('float32') 70 | trainval_label = np.concatenate((data['train_label'], data['val_label']), axis=0).astype('float32') 71 | weight_train = np.mean(train_label==1, axis=0).tolist() 72 | weight_trainval = np.mean(trainval_label==1, axis=0).tolist() 73 | 74 | partition['weight_trainval'].append(weight_trainval) 75 | partition['weight_train'].append(weight_train) 76 | 77 | with open(traintest_split_file, 'w+') as f: 78 | pickle.dump(partition, f) 79 | 80 | if __name__ == "__main__": 81 | import argparse 82 | parser = argparse.ArgumentParser(description="pa100k dataset") 83 | parser.add_argument( 84 | '--save_dir', 85 | type=str, 86 | default='./dataset/pa100k/') 87 | parser.add_argument( 88 | '--traintest_split_file', 89 | type=str, 90 | default="./dataset/pa100k/pa100k_partition.pkl") 91 | args = parser.parse_args() 92 | save_dir = args.save_dir 93 | traintest_split_file = args.traintest_split_file 94 | 95 | generate_data_description(save_dir) 96 | create_trainvaltest_split(traintest_split_file) 97 | -------------------------------------------------------------------------------- /script/dataset/transform_peta.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import cPickle as pickle 5 | from scipy.io import loadmat 6 | 7 | np.random.seed(0) 8 | random.seed(0) 9 | 10 | def make_dir(path): 11 | if os.path.exists(path): 12 | pass 13 | else: 14 | os.mkdir(path) 15 | 16 | def generate_data_description(save_dir): 17 | """ 18 | create a dataset description file, which consists of images, labels 19 | """ 20 | dataset = dict() 21 | dataset['description'] = 'peta' 22 | dataset['root'] = './dataset/peta/images/' 23 | dataset['image'] = [] 24 | dataset['att'] = [] 25 | dataset['att_name'] = [] 26 | dataset['selected_attribute'] = range(35) 27 | # load PETA.MAT 28 | data = loadmat(open('./dataset/peta/PETA.mat', 'r')) 29 | for idx in range(105): 30 | dataset['att_name'].append(data['peta'][0][0][1][idx,0][0]) 31 | 32 | for idx in range(19000): 33 | dataset['image'].append('%05d.png'%(idx+1)) 34 | dataset['att'].append(data['peta'][0][0][0][idx, 4:].tolist()) 35 | with open(os.path.join(save_dir, 'peta_dataset.pkl'), 'w+') as f: 36 | pickle.dump(dataset, f) 37 | 38 | def create_trainvaltest_split(traintest_split_file): 39 | """ 40 | create a dataset split file, which consists of index of the train/val/test splits 41 | """ 42 | partition = dict() 43 | partition['trainval'] = [] 44 | partition['train'] = [] 45 | partition['val'] = [] 46 | partition['test'] = [] 47 | partition['weight_trainval'] = [] 48 | partition['weight_train'] = [] 49 | # load PETA.MAT 50 | data = loadmat(open('./dataset/peta/PETA.mat', 'r')) 51 | for idx in range(5): 52 | train = (data['peta'][0][0][3][idx][0][0][0][0][:,0]-1).tolist() 53 | val = (data['peta'][0][0][3][idx][0][0][0][1][:,0]-1).tolist() 54 | test = (data['peta'][0][0][3][idx][0][0][0][2][:,0]-1).tolist() 55 | trainval = train + val 56 | partition['train'].append(train) 57 | partition['val'].append(val) 58 | partition['trainval'].append(trainval) 59 | partition['test'].append(test) 60 | # weight 61 | weight_trainval = np.mean(data['peta'][0][0][0][trainval, 4:].astype('float32')==1, axis=0).tolist() 62 | weight_train = np.mean(data['peta'][0][0][0][train, 4:].astype('float32')==1, axis=0).tolist() 63 | partition['weight_trainval'].append(weight_trainval) 64 | partition['weight_train'].append(weight_train) 65 | with open(traintest_split_file, 'w+') as f: 66 | pickle.dump(partition, f) 67 | 68 | if __name__ == "__main__": 69 | import argparse 70 | parser = argparse.ArgumentParser(description="peta dataset") 71 | parser.add_argument( 72 | '--save_dir', 73 | type=str, 74 | default='./dataset/peta/') 75 | parser.add_argument( 76 | '--traintest_split_file', 77 | type=str, 78 | default="./dataset/peta/peta_partition.pkl") 79 | args = parser.parse_args() 80 | save_dir = args.save_dir 81 | traintest_split_file = args.traintest_split_file 82 | 83 | generate_data_description(save_dir) 84 | create_trainvaltest_split(traintest_split_file) 85 | -------------------------------------------------------------------------------- /script/dataset/transform_rap.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import cPickle as pickle 5 | from scipy.io import loadmat 6 | 7 | np.random.seed(0) 8 | random.seed(0) 9 | 10 | def make_dir(path): 11 | if os.path.exists(path): 12 | pass 13 | else: 14 | os.mkdir(path) 15 | 16 | def generate_data_description(save_dir): 17 | """ 18 | create a dataset description file, which consists of images, labels 19 | """ 20 | dataset = dict() 21 | dataset['description'] = 'rap' 22 | dataset['root'] = './dataset/rap/RAP_dataset/' 23 | dataset['image'] = [] 24 | dataset['att'] = [] 25 | dataset['att_name'] = [] 26 | dataset['selected_attribute'] = range(51) 27 | # load Rap_annotation.mat 28 | data = loadmat(open('./dataset/rap/RAP_annotation/RAP_annotation.mat', 'r')) 29 | for idx in range(51): 30 | dataset['att_name'].append(data['RAP_annotation'][0][0][6][idx][0][0]) 31 | 32 | for idx in range(41585): 33 | dataset['image'].append(data['RAP_annotation'][0][0][5][idx][0][0]) 34 | dataset['att'].append(data['RAP_annotation'][0][0][1][idx, :].tolist()) 35 | 36 | with open(os.path.join(save_dir, 'rap_dataset.pkl'), 'w+') as f: 37 | pickle.dump(dataset, f) 38 | 39 | def create_trainvaltest_split(traintest_split_file): 40 | """ 41 | create a dataset split file, which consists of index of the train/val/test splits 42 | """ 43 | partition = dict() 44 | partition['trainval'] = [] 45 | partition['test'] = [] 46 | partition['weight_trainval'] = [] 47 | # load RAP_annotation.mat 48 | data = loadmat(open('./dataset/rap/RAP_annotation/RAP_annotation.mat', 'r')) 49 | for idx in range(5): 50 | trainval = (data['RAP_annotation'][0][0][0][idx][0][0][0][0][0,:]-1).tolist() 51 | test = (data['RAP_annotation'][0][0][0][idx][0][0][0][1][0,:]-1).tolist() 52 | partition['trainval'].append(trainval) 53 | partition['test'].append(test) 54 | # weight 55 | weight_trainval = np.mean(data['RAP_annotation'][0][0][1][trainval, :].astype('float32')==1, axis=0).tolist() 56 | partition['weight_trainval'].append(weight_trainval) 57 | with open(traintest_split_file, 'w+') as f: 58 | pickle.dump(partition, f) 59 | 60 | if __name__ == "__main__": 61 | import argparse 62 | parser = argparse.ArgumentParser(description="rap dataset") 63 | parser.add_argument( 64 | '--save_dir', 65 | type=str, 66 | default='./dataset/rap/') 67 | parser.add_argument( 68 | '--traintest_split_file', 69 | type=str, 70 | default="./dataset/rap/rap_partition.pkl") 71 | args = parser.parse_args() 72 | save_dir = args.save_dir 73 | traintest_split_file = args.traintest_split_file 74 | 75 | generate_data_description(save_dir) 76 | create_trainvaltest_split(traintest_split_file) 77 | -------------------------------------------------------------------------------- /script/dataset/transform_rap2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import random 4 | import cPickle as pickle 5 | from scipy.io import loadmat 6 | 7 | np.random.seed(0) 8 | random.seed(0) 9 | 10 | def make_dir(path): 11 | if os.path.exists(path): 12 | pass 13 | else: 14 | os.mkdir(path) 15 | 16 | def generate_data_description(save_dir): 17 | """ 18 | create a dataset description file, which consists of images, labels 19 | """ 20 | dataset = dict() 21 | dataset['description'] = 'rap2' 22 | dataset['root'] = './dataset/rap2/RAP_dataset/' 23 | dataset['image'] = [] 24 | dataset['att'] = [] 25 | dataset['att_name'] = [] 26 | # load RAP_annotation.mat 27 | data = loadmat(open('./dataset/rap2/RAP_annotation/RAP_annotation.mat', 'r')) 28 | dataset['selected_attribute'] = (data['RAP_annotation'][0][0][3][0,:]-1).tolist() 29 | for idx in range(152): 30 | dataset['att_name'].append(data['RAP_annotation'][0][0][2][idx][0][0]) 31 | 32 | for idx in range(84928): 33 | dataset['image'].append(data['RAP_annotation'][0][0][0][idx][0][0]) 34 | dataset['att'].append(data['RAP_annotation'][0][0][1][idx, :].tolist()) 35 | 36 | with open(os.path.join(save_dir, 'rap2_dataset.pkl'), 'w+') as f: 37 | pickle.dump(dataset, f) 38 | 39 | def create_trainvaltest_split(traintest_split_file): 40 | """ 41 | create a dataset split file, which consists of index of the train/val/test splits 42 | """ 43 | partition = dict() 44 | partition['train'] = [] 45 | partition['val'] = [] 46 | partition['trainval'] = [] 47 | partition['test'] = [] 48 | partition['weight_train'] = [] 49 | partition['weight_trainval'] = [] 50 | # load RAP_annotation.mat 51 | data = loadmat(open('./dataset/rap2/RAP_annotation/RAP_annotation.mat', 'r')) 52 | for idx in range(5): 53 | train = (data['RAP_annotation'][0][0][4][0, idx][0][0][0][0,:]-1).tolist() 54 | val = (data['RAP_annotation'][0][0][4][0, idx][0][0][1][0,:]-1).tolist() 55 | test = (data['RAP_annotation'][0][0][4][0, idx][0][0][2][0,:]-1).tolist() 56 | trainval = train + val 57 | partition['trainval'].append(trainval) 58 | partition['train'].append(train) 59 | partition['val'].append(val) 60 | partition['test'].append(test) 61 | # weight 62 | weight_train = np.mean(data['RAP_annotation'][0][0][1][train, :].astype('float32')==1, axis=0).tolist() 63 | weight_trainval = np.mean(data['RAP_annotation'][0][0][1][trainval, :].astype('float32')==1, axis=0).tolist() 64 | partition['weight_train'].append(weight_train) 65 | partition['weight_trainval'].append(weight_trainval) 66 | 67 | with open(traintest_split_file, 'w+') as f: 68 | pickle.dump(partition, f) 69 | 70 | if __name__ == "__main__": 71 | import argparse 72 | parser = argparse.ArgumentParser(description="rap2 dataset") 73 | parser.add_argument( 74 | '--save_dir', 75 | type=str, 76 | default='./dataset/rap2/') 77 | parser.add_argument( 78 | '--traintest_split_file', 79 | type=str, 80 | default="./dataset/rap2/rap2_partition.pkl") 81 | args = parser.parse_args() 82 | save_dir = args.save_dir 83 | traintest_split_file = args.traintest_split_file 84 | 85 | generate_data_description(save_dir) 86 | create_trainvaltest_split(traintest_split_file) 87 | -------------------------------------------------------------------------------- /script/experiment/demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | import random 5 | import math 6 | 7 | import torch 8 | import torchvision.transforms as transforms 9 | from torch.autograd import Variable 10 | from torch.nn.parallel import DataParallel 11 | import cPickle as pickle 12 | import time 13 | import argparse 14 | from PIL import Image, ImageFont, ImageDraw 15 | 16 | from baseline.model.DeepMAR import DeepMAR_ResNet50 17 | from baseline.utils.utils import str2bool 18 | from baseline.utils.utils import save_ckpt, load_ckpt 19 | from baseline.utils.utils import load_state_dict 20 | from baseline.utils.utils import set_devices 21 | from baseline.utils.utils import set_seed 22 | 23 | 24 | class Config(object): 25 | def __init__(self): 26 | 27 | parser = argparse.ArgumentParser() 28 | parser.add_argument('-d', '--sys_device_ids', type=eval, default=(0,)) 29 | parser.add_argument('--set_seed', type=str2bool, default=False) 30 | # model 31 | parser.add_argument('--resize', type=eval, default=(224, 224)) 32 | parser.add_argument('--last_conv_stride', type=int, default=2, choices=[1,2]) 33 | # demo image 34 | parser.add_argument('--demo_image', type=str, default='./dataset/demo/demo_image.png') 35 | ## dataset parameter 36 | parser.add_argument('--dataset', type=str, default='peta', 37 | choices=['peta','rap', 'pa100k']) 38 | # utils 39 | parser.add_argument('--load_model_weight', type=str2bool, default=True) 40 | parser.add_argument('--model_weight_file', type=str, default='./exp/deepmar_resnet50/peta/partition0/run1/model/ckpt_epoch150.pth') 41 | args = parser.parse_args() 42 | 43 | # gpu ids 44 | self.sys_device_ids = args.sys_device_ids 45 | 46 | # random 47 | self.set_seed = args.set_seed 48 | if self.set_seed: 49 | self.rand_seed = 0 50 | else: 51 | self.rand_seed = None 52 | self.resize = args.resize 53 | self.mean = [0.485, 0.456, 0.406] 54 | self.std = [0.229, 0.224, 0.225] 55 | 56 | # utils 57 | self.load_model_weight = args.load_model_weight 58 | self.model_weight_file = args.model_weight_file 59 | if self.load_model_weight: 60 | if self.model_weight_file == '': 61 | print 'Please input the model_weight_file if you want to load model weight' 62 | raise ValueError 63 | # dataset 64 | datasets = dict() 65 | datasets['peta'] = './dataset/peta/peta_dataset.pkl' 66 | datasets['rap'] = './dataset/rap/rap_dataset.pkl' 67 | datasets['pa100k'] = './dataset/pa100k/pa100k_dataset.pkl' 68 | 69 | if args.dataset in datasets: 70 | dataset = pickle.load(open(datasets[args.dataset])) 71 | else: 72 | print '%s does not exist.'%(args.dataset) 73 | raise ValueError 74 | self.att_list = [dataset['att_name'][i] for i in dataset['selected_attribute']] 75 | 76 | # demo image 77 | self.demo_image = args.demo_image 78 | 79 | # model 80 | model_kwargs = dict() 81 | model_kwargs['num_att'] = len(self.att_list) 82 | model_kwargs['last_conv_stride'] = args.last_conv_stride 83 | self.model_kwargs = model_kwargs 84 | 85 | ### main function ### 86 | cfg = Config() 87 | 88 | # dump the configuration to log. 89 | import pprint 90 | print('-' * 60) 91 | print('cfg.__dict__') 92 | pprint.pprint(cfg.__dict__) 93 | print('-' * 60) 94 | 95 | # set the random seed 96 | if cfg.set_seed: 97 | set_seed( cfg.rand_seed ) 98 | # init the gpu ids 99 | set_devices(cfg.sys_device_ids) 100 | 101 | # dataset 102 | normalize = transforms.Normalize(mean=cfg.mean, std=cfg.std) 103 | test_transform = transforms.Compose([ 104 | transforms.Resize(cfg.resize), 105 | transforms.ToTensor(), 106 | normalize,]) 107 | 108 | ### Att model ### 109 | model = DeepMAR_ResNet50(**cfg.model_kwargs) 110 | 111 | # load model weight if necessary 112 | if cfg.load_model_weight: 113 | map_location = (lambda storage, loc:storage) 114 | ckpt = torch.load(cfg.model_weight_file, map_location=map_location) 115 | model.load_state_dict(ckpt['state_dicts'][0]) 116 | 117 | model.cuda() 118 | model.eval() 119 | 120 | # load one image 121 | img = Image.open(cfg.demo_image) 122 | img_trans = test_transform( img ) 123 | img_trans = torch.unsqueeze(img_trans, dim=0) 124 | img_var = Variable(img_trans).cuda() 125 | score = model(img_var).data.cpu().numpy() 126 | 127 | # show the score in command line 128 | for idx in range(len(cfg.att_list)): 129 | if score[0, idx] >= 0: 130 | print '%s: %.2f'%(cfg.att_list[idx], score[0, idx]) 131 | 132 | # show the score in the image 133 | img = img.resize(size=(256, 512), resample=Image.BILINEAR) 134 | draw = ImageDraw.Draw(img) 135 | positive_cnt = 0 136 | for idx in range(len(cfg.att_list)): 137 | if score[0, idx] >= 0: 138 | txt = '%s: %.2f'%(cfg.att_list[idx], score[0, idx]) 139 | draw.text((10, 10 + 10*positive_cnt), txt, (255, 0, 0)) 140 | positive_cnt += 1 141 | img.save('./dataset/demo/demo_image_result.png') 142 | -------------------------------------------------------------------------------- /script/experiment/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | python ./script/experiment/train_deepmar_resnet50.py \ 4 | --sys_device_ids="(0,)" \ 5 | --dataset=peta \ 6 | --partition_idx=0 \ 7 | --test_split=test \ 8 | --resize="(224,224)" \ 9 | --exp_subpath=deepmar_resnet50 \ 10 | --run=1 \ 11 | --test_only=True \ 12 | --load_model_weight=True \ 13 | --model_weight_file='./exp/deepmar_resnet50/peta/partition0/run1/model/ckpt_epoch150.pth' 14 | -------------------------------------------------------------------------------- /script/experiment/train.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | python ./script/experiment/train_deepmar_resnet50.py \ 4 | --sys_device_ids="(0,)" \ 5 | --dataset=peta \ 6 | --partition_idx=0 \ 7 | --split=trainval \ 8 | --test_split=test \ 9 | --batch_size=32 \ 10 | --resize="(224,224)" \ 11 | --exp_subpath=deepmar_resnet50 \ 12 | --new_params_lr=0.001 \ 13 | --finetuned_params_lr=0.001 \ 14 | --staircase_decay_at_epochs="(50,100)" \ 15 | --total_epochs=150 \ 16 | --epochs_per_val=10\ 17 | --epochs_per_save=50 \ 18 | --drop_pool5=True \ 19 | --drop_pool5_rate=0.5 \ 20 | --run=1 \ 21 | --resume=False \ 22 | --ckpt_file= \ 23 | --test_only=False \ 24 | --model_weight_file= \ 25 | -------------------------------------------------------------------------------- /script/experiment/train_deepmar_resnet50.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | import random 5 | import math 6 | 7 | import torch 8 | import torch.optim as optim 9 | import torchvision.transforms as transforms 10 | import torch.nn.functional as F 11 | import torch.backends.cudnn as cudnn 12 | from torch.autograd import Variable 13 | from torch.nn.parallel import DataParallel 14 | import cPickle as pickle 15 | import time 16 | import argparse 17 | 18 | from baseline.dataset import add_transforms 19 | from baseline.dataset.Dataset import AttDataset 20 | from baseline.model.DeepMAR import DeepMAR_ResNet50 21 | from baseline.model.DeepMAR import DeepMAR_ResNet50_ExtractFeature 22 | from baseline.utils.evaluate import attribute_evaluate 23 | from baseline.utils.utils import str2bool 24 | from baseline.utils.utils import transfer_optim_state 25 | from baseline.utils.utils import time_str 26 | from baseline.utils.utils import save_ckpt, load_ckpt 27 | from baseline.utils.utils import load_state_dict 28 | from baseline.utils.utils import ReDirectSTD 29 | from baseline.utils.utils import adjust_lr_staircase 30 | from baseline.utils.utils import set_devices 31 | from baseline.utils.utils import AverageMeter 32 | from baseline.utils.utils import to_scalar 33 | from baseline.utils.utils import may_set_mode 34 | from baseline.utils.utils import may_mkdir 35 | from baseline.utils.utils import set_seed 36 | 37 | class Config(object): 38 | def __init__(self): 39 | 40 | parser = argparse.ArgumentParser() 41 | parser.add_argument('-d', '--sys_device_ids', type=eval, default=(0,)) 42 | parser.add_argument('--set_seed', type=str2bool, default=False) 43 | ## dataset parameter 44 | parser.add_argument('--dataset', type=str, default='peta', 45 | choices=['peta','rap', 'pa100k', 'rap2']) 46 | parser.add_argument('--split', type=str, default='trainval', 47 | choices=['trainval', 'train']) 48 | parser.add_argument('--test_split', type=str, default='test') 49 | parser.add_argument('--partition_idx', type=int, default=0) 50 | parser.add_argument('--resize', type=eval, default=(224, 224)) 51 | parser.add_argument('--mirror', type=str2bool, default=True) 52 | parser.add_argument('--batch_size', type=int, default=32) 53 | parser.add_argument('--workers', type=int, default=2) 54 | # model 55 | parser.add_argument('--num_att', type=int, default=35) 56 | parser.add_argument('--pretrained', type=str2bool, default=True) 57 | parser.add_argument('--last_conv_stride', type=int, default=2, choices=[1,2]) 58 | parser.add_argument('--drop_pool5', type=str2bool, default=True) 59 | parser.add_argument('--drop_pool5_rate', type=float, default=0.5) 60 | 61 | parser.add_argument('--sgd_weight_decay', type=float, default=0.0005) 62 | parser.add_argument('--sgd_momentum', type=float, default=0.9) 63 | parser.add_argument('--new_params_lr', type=float, default=0.001) 64 | parser.add_argument('--finetuned_params_lr', type=float, default=0.001) 65 | parser.add_argument('--staircase_decay_at_epochs', type=eval, 66 | default=(51, )) 67 | parser.add_argument('--staircase_decay_multiple_factor', type=float, 68 | default=0.1) 69 | parser.add_argument('--total_epochs', type=int, default=150) 70 | parser.add_argument('--weighted_entropy', type=str2bool, default=True) 71 | # utils 72 | parser.add_argument('--resume', type=str2bool, default=False) 73 | parser.add_argument('--ckpt_file', type=str, default='') 74 | parser.add_argument('--load_model_weight', type=str2bool, default=False) 75 | parser.add_argument('--model_weight_file', type=str, default='') 76 | parser.add_argument('--test_only', type=str2bool, default=False) 77 | parser.add_argument('--exp_dir', type=str, default='') 78 | parser.add_argument('--exp_subpath', type=str, default='deepmar_resnet50') 79 | parser.add_argument('--log_to_file', type=str2bool, default=True) 80 | parser.add_argument('--steps_per_log', type=int, default=20) 81 | parser.add_argument('--epochs_per_val', type=int, default=10) 82 | parser.add_argument('--epochs_per_save', type=int, default=50) 83 | parser.add_argument('--run', type=int, default=1) 84 | args = parser.parse_args() 85 | 86 | # gpu ids 87 | self.sys_device_ids = args.sys_device_ids 88 | # random 89 | self.set_seed = args.set_seed 90 | if self.set_seed: 91 | self.rand_seed = 0 92 | else: 93 | self.rand_seed = None 94 | # run time index 95 | self.run = args.run 96 | # Dataset # 97 | datasets = dict() 98 | datasets['peta'] = './dataset/peta/peta_dataset.pkl' 99 | datasets['rap'] = './dataset/rap/rap_dataset.pkl' 100 | datasets['pa100k'] = './dataset/pa100k/pa100k_dataset.pkl' 101 | datasets['rap2'] = './dataset/rap2/rap2_dataset.pkl' 102 | partitions = dict() 103 | partitions['peta'] = './dataset/peta/peta_partition.pkl' 104 | partitions['rap'] = './dataset/rap/rap_partition.pkl' 105 | partitions['pa100k'] = './dataset/pa100k/pa100k_partition.pkl' 106 | partitions['rap2'] = './dataset/rap2/rap2_partition.pkl' 107 | 108 | self.dataset_name = args.dataset 109 | if not datasets.has_key(args.dataset) or not partitions.has_key(args.dataset): 110 | print "Please select the right dataset name." 111 | raise ValueError 112 | else: 113 | self.dataset = datasets[args.dataset] 114 | self.partition = partitions[args.dataset] 115 | self.partition_idx = args.partition_idx 116 | self.split = args.split 117 | self.test_split = args.test_split 118 | self.resize = args.resize 119 | self.mirror = args.mirror 120 | self.mean = [0.485, 0.456, 0.406] 121 | self.std = [0.229, 0.224, 0.225] 122 | self.batch_size = args.batch_size 123 | self.workers = args.workers 124 | # optimization 125 | self.sgd_momentum = args.sgd_momentum 126 | self.sgd_weight_decay = args.sgd_weight_decay 127 | self.new_params_lr = args.new_params_lr 128 | self.finetuned_params_lr = args.finetuned_params_lr 129 | self.staircase_decay_at_epochs = args.staircase_decay_at_epochs 130 | self.staircase_decay_multiple_factor = args.staircase_decay_multiple_factor 131 | self.total_epochs = args.total_epochs 132 | self.weighted_entropy = args.weighted_entropy 133 | 134 | # utils 135 | self.resume = args.resume 136 | self.ckpt_file = args.ckpt_file 137 | if self.resume: 138 | if self.ckpt_file == '': 139 | print 'Please input the ckpt_file if you want to resume training' 140 | raise ValueError 141 | self.load_model_weight = args.load_model_weight 142 | self.model_weight_file = args.model_weight_file 143 | if self.load_model_weight: 144 | if self.model_weight_file == '': 145 | print 'Please input the model_weight_file if you want to load model weight' 146 | raise ValueError 147 | self.test_only = args.test_only 148 | self.exp_dir = args.exp_dir 149 | self.exp_subpath = args.exp_subpath 150 | self.log_to_file = args.log_to_file 151 | self.steps_per_log = args.steps_per_log 152 | self.epochs_per_val = args.epochs_per_val 153 | self.epochs_per_save = args.epochs_per_save 154 | self.run = args.run 155 | 156 | # for model 157 | model_kwargs = dict() 158 | model_kwargs['num_att'] = args.num_att 159 | model_kwargs['last_conv_stride'] = args.last_conv_stride 160 | model_kwargs['drop_pool5'] = args.drop_pool5 161 | model_kwargs['drop_pool5_rate'] = args.drop_pool5_rate 162 | self.model_kwargs = model_kwargs 163 | # for evaluation 164 | self.test_kwargs = dict() 165 | 166 | if self.exp_dir == '': 167 | self.exp_dir = os.path.join('exp', 168 | '{}'.format(self.exp_subpath), 169 | '{}'.format(self.dataset_name), 170 | 'partition{}'.format(self.partition_idx), 171 | 'run{}'.format(self.run)) 172 | self.stdout_file = os.path.join(self.exp_dir, \ 173 | 'log', 'stdout_{}.txt'.format(time_str())) 174 | self.stderr_file = os.path.join(self.exp_dir, \ 175 | 'log', 'stderr_{}.txt'.format(time_str())) 176 | may_mkdir(self.stdout_file) 177 | 178 | ### main function ### 179 | cfg = Config() 180 | 181 | # log 182 | if cfg.log_to_file: 183 | ReDirectSTD(cfg.stdout_file, 'stdout', False) 184 | ReDirectSTD(cfg.stderr_file, 'stderr', False) 185 | 186 | # dump the configuration to log. 187 | import pprint 188 | print('-' * 60) 189 | print('cfg.__dict__') 190 | pprint.pprint(cfg.__dict__) 191 | print('-' * 60) 192 | 193 | # set the random seed 194 | if cfg.set_seed: 195 | set_seed( cfg.rand_seed ) 196 | # init the gpu ids 197 | set_devices(cfg.sys_device_ids) 198 | 199 | # dataset 200 | normalize = transforms.Normalize(mean=cfg.mean, std=cfg.std) 201 | transform = transforms.Compose([ 202 | transforms.Resize(cfg.resize), 203 | transforms.RandomHorizontalFlip(), 204 | transforms.ToTensor(), # 3*H*W, [0, 1] 205 | normalize,]) # normalize with mean/std 206 | # by a subset of attributes 207 | train_set = AttDataset( 208 | dataset = cfg.dataset, 209 | partition = cfg.partition, 210 | split = cfg.split, 211 | partition_idx= cfg.partition_idx, 212 | transform = transform) 213 | 214 | num_att = len(train_set.dataset['selected_attribute']) 215 | cfg.model_kwargs['num_att'] = num_att 216 | 217 | train_loader = torch.utils.data.DataLoader( 218 | dataset = train_set, 219 | batch_size = cfg.batch_size, 220 | shuffle = True, 221 | num_workers = cfg.workers, 222 | pin_memory = True, 223 | drop_last = False) 224 | 225 | test_transform = transforms.Compose([ 226 | transforms.Resize(cfg.resize), 227 | transforms.ToTensor(), 228 | normalize,]) 229 | test_set = AttDataset( 230 | dataset = cfg.dataset, 231 | partition = cfg.partition, 232 | split = cfg.test_split, 233 | partition_idx = cfg.partition_idx, 234 | transform = test_transform) 235 | ### Att model ### 236 | model = DeepMAR_ResNet50(**cfg.model_kwargs) 237 | 238 | # Wrap the model after set_devices, data parallel 239 | model_w = torch.nn.DataParallel(model) 240 | 241 | # using the weighted cross entropy loss 242 | if cfg.weighted_entropy: 243 | rate = np.array(train_set.partition['weight_' + cfg.split][cfg.partition_idx]) 244 | rate = rate[train_set.dataset['selected_attribute']].tolist() 245 | else: 246 | rate = None 247 | # compute the weight of positive and negative 248 | if rate is None: 249 | weight_pos = [1 for i in range(num_att)] 250 | weight_neg = [1 for i in range(num_att)] 251 | else: 252 | if len(rate) != num_att: 253 | print "the length of rate should be equal to %d" % (num_att) 254 | raise ValueError 255 | weight_pos = [] 256 | weight_neg = [] 257 | for idx, v in enumerate(rate): 258 | weight_pos.append(math.exp(1.0 - v)) 259 | weight_neg.append(math.exp(v)) 260 | criterion = F.binary_cross_entropy_with_logits 261 | 262 | # Optimizer 263 | finetuned_params = [] 264 | new_params = [] 265 | for n, p in model.named_parameters(): 266 | if n.find('classifier') >=0: 267 | new_params.append(p) 268 | else: 269 | finetuned_params.append(p) 270 | param_groups = [{'params': finetuned_params, 'lr': cfg.finetuned_params_lr}, 271 | {'params': new_params, 'lr': cfg.new_params_lr}] 272 | 273 | optimizer = optim.SGD( 274 | param_groups, 275 | momentum = cfg.sgd_momentum, 276 | weight_decay = cfg.sgd_weight_decay) 277 | # bind the model and optimizer 278 | modules_optims = [model, optimizer] 279 | 280 | # load model weight if necessary 281 | if cfg.load_model_weight: 282 | map_location = (lambda storage, loc:storage) 283 | ckpt = torch.load(cfg.model_weight_file, map_location=map_location) 284 | model.load_state_dict(ckpt['state_dicts'][0], strict=False) 285 | 286 | ### Resume or not ### 287 | if cfg.resume: 288 | # store the model, optimizer, epoch 289 | start_epoch, scores = load_ckpt(modules_optims, cfg.ckpt_file) 290 | else: 291 | start_epoch = 0 292 | 293 | model_w = torch.nn.DataParallel(model) 294 | model_w.cuda() 295 | transfer_optim_state(state=optimizer.state, device_id=0) 296 | 297 | # cudnn.benchmark = True 298 | # for evaluation 299 | feat_func_att = DeepMAR_ResNet50_ExtractFeature(model=model_w) 300 | 301 | def attribute_evaluate_subfunc(feat_func, test_set, **test_kwargs): 302 | """ evaluate the attribute recognition precision """ 303 | result = attribute_evaluate(feat_func, test_set, **test_kwargs) 304 | print '-' * 60 305 | print 'Evaluation on %s set:' % (cfg.test_split) 306 | print 'Label-based evaluation: \n mA: %.4f'%(np.mean(result['label_acc'])) 307 | print 'Instance-based evaluation: \n Acc: %.4f, Prec: %.4f, Rec: %.4f, F1: %.4f' \ 308 | %(result['instance_acc'], result['instance_precision'], result['instance_recall'], result['instance_F1']) 309 | print '-' * 60 310 | 311 | # print the model into log 312 | print model 313 | # test only 314 | if cfg.test_only: 315 | print 'test with feat_func_att' 316 | attribute_evaluate_subfunc(feat_func_att, test_set, **cfg.test_kwargs) 317 | sys.exit(0) 318 | 319 | # training 320 | for epoch in range(start_epoch, cfg.total_epochs): 321 | # adjust the learning rate 322 | adjust_lr_staircase( 323 | optimizer.param_groups, 324 | [cfg.finetuned_params_lr, cfg.new_params_lr], 325 | epoch + 1, 326 | cfg.staircase_decay_at_epochs, 327 | cfg.staircase_decay_multiple_factor) 328 | 329 | may_set_mode(modules_optims, 'train') 330 | # recording loss 331 | loss_meter = AverageMeter() 332 | dataset_L = len(train_loader) 333 | ep_st = time.time() 334 | 335 | for step, (imgs, targets) in enumerate(train_loader): 336 | 337 | step_st = time.time() 338 | imgs_var = Variable(imgs).cuda() 339 | targets_var = Variable(targets).cuda() 340 | 341 | score = model_w(imgs_var) 342 | 343 | # compute the weight 344 | weights = torch.zeros(targets_var.shape) 345 | for i in range(targets_var.shape[0]): 346 | for j in range(targets_var.shape[1]): 347 | if targets_var.data.cpu()[i, j] == -1: 348 | weights[i, j] = weight_neg[j] 349 | elif targets_var.data.cpu()[i, j] == 1: 350 | weights[i, j] = weight_pos[j] 351 | else: 352 | weights[i, j] = 0 353 | 354 | # loss for the attribute classification, average over the batch size 355 | targets_var[targets_var == -1] = 0 356 | loss = criterion(score, targets_var, weight=Variable(weights.cuda()))*num_att 357 | 358 | optimizer.zero_grad() 359 | loss.backward() 360 | optimizer.step() 361 | 362 | ############ 363 | # step log # 364 | ############ 365 | loss_meter.update(to_scalar(loss)) 366 | 367 | if (step+1) % cfg.steps_per_log == 0 or (step+1)%len(train_loader) == 0: 368 | log = '{}, Step {}/{} in Ep {}, {:.2f}s, loss:{:.4f}'.format( \ 369 | time_str(), step+1, dataset_L, epoch+1, time.time()-step_st, loss_meter.val) 370 | print(log) 371 | 372 | ############## 373 | # epoch log # 374 | ############## 375 | log = 'Ep{}, {:.2f}s, loss {:.4f}'.format( 376 | epoch+1, time.time() - ep_st, loss_meter.avg) 377 | print(log) 378 | 379 | # model ckpt 380 | if (epoch + 1) % cfg.epochs_per_save == 0 or epoch+1 == cfg.total_epochs: 381 | ckpt_file = os.path.join(cfg.exp_dir, 'model', 'ckpt_epoch%d.pth'%(epoch+1)) 382 | save_ckpt(modules_optims, epoch+1, 0, ckpt_file) 383 | 384 | ########################## 385 | # test on validation set # 386 | ########################## 387 | if (epoch + 1) % cfg.epochs_per_val == 0 or epoch+1 == cfg.total_epochs: 388 | print 'att test with feat_func_att' 389 | attribute_evaluate_subfunc(feat_func_att, test_set, **cfg.test_kwargs) 390 | --------------------------------------------------------------------------------