Skip to content
This repository was archived by the owner on Jan 29, 2024. It is now read-only.

Commit f073969

Browse files
committed
Initially completed.
1 parent 049983e commit f073969

File tree

19 files changed

+1790
-40
lines changed

19 files changed

+1790
-40
lines changed

checkpoint/DogCat/Exp-1/config.yaml

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
DATALOADER:
2+
BATCH_SIZE: 64
3+
NUM_INSTANCES: 4
4+
NUM_WORKERS: 4
5+
DATASETS:
6+
NAME: Market1501
7+
ROOT: /home/hzh/data
8+
DEVICE: cuda
9+
DEVICE_ID: 1
10+
INPUT:
11+
HF_PROB: 0.5
12+
PIXEL_MEAN: [0.485, 0.456, 0.406]
13+
PIXEL_STD: [0.229, 0.224, 0.225]
14+
SIZE_TEST: [256, 128]
15+
SIZE_TRAIN: [256, 128]
16+
MODEL:
17+
ARCH: resnet50
18+
NAME: baseline
19+
STRIDE: 2
20+
OUTPUT_DIR: ./checkpoint/DogCat/Exp-1
21+
SCHEDULER:
22+
GAMMA: 0.5
23+
NAME: StepLR
24+
STEP: 10
25+
SOLVER:
26+
BASE_LR: 0.001
27+
CHECK_PERIOD: 5
28+
EVAL_PERIOD: 1
29+
LOSS: softmax_triplet
30+
MARGIN: 0.3
31+
MAX_EPOCHS: 320
32+
MOMENTUM: 0.9
33+
NESTEROV: True
34+
OPTIMIZER_NAME: SGD
35+
PRINT_FREQ: 10
36+
WEIGHT_DECAY: 0.0005
37+
TEST:
38+
BATCH_SIZE: 128

checkpoint/DogCat/Exp-1/log.txt

+914
Large diffs are not rendered by default.

config/default.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
# MODEL
1313
# -----------------------------------------------------------------------------
1414
_C.MODEL = CN()
15-
_C.MODEL.NAME = 'ResModel'
16-
_C.MODEL.BACKBONE = 'ResNet50'
17-
_C.MODEL.PRETRAINED = True
15+
_C.MODEL.NAME = 'baseline'
16+
_C.MODEL.ARCH = 'resnet50'
17+
_C.MODEL.STRIDE = 2
1818

1919
# -----------------------------------------------------------------------------
2020
# INPUT
@@ -50,6 +50,7 @@
5050
# DataLoader
5151
# -----------------------------------------------------------------------------
5252
_C.DATALOADER = CN()
53+
# Sampler
5354
# Number of data loading threads
5455
_C.DATALOADER.NUM_WORKERS = 4
5556
# Number of instance for one batch
@@ -61,14 +62,17 @@
6162
# ---------------------------------------------------------------------------- #
6263
_C.SOLVER = CN()
6364
# Sampler for data loading
64-
_C.SOLVER.LOSS = 'softmax'
65+
_C.SOLVER.LOSS = 'softmax_triplet'
66+
_C.SOLVER.MARGIN = 0.3
67+
6568
_C.SOLVER.MAX_EPOCHS = 120
6669
_C.SOLVER.OPTIMIZER_NAME = "Adam"
6770

6871
_C.SOLVER.BASE_LR = 3e-4
6972
# SGD
7073
# _C.SOLVER.BASE_LR = 0.01
7174
_C.SOLVER.NESTEROV = True
75+
_C.SOLVER.MOMENTUM = 0.9
7276

7377
# Adam
7478
_C.SOLVER.WEIGHT_DECAY = 0.0005

configs/sample.yaml

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
OUTPUT_DIR: "./checkpoint/DogCat/Exp-1"
22
DEVICE: "cuda"
3-
DEVICE_ID: ('1,2,3')
3+
DEVICE_ID: ('1')
44

55
MODEL:
6-
NAME: 'ResModel'
7-
BACKBONE: 'ResNet50'
8-
PRETRAINED: True
6+
NAME: 'baseline'
7+
ARCH: 'resnet50'
98

109
INPUT:
1110
SIZE_TRAIN: [256, 128]
@@ -17,15 +16,15 @@ DATASETS:
1716
ROOT: '/home/hzh/data'
1817

1918
DATALOADER:
20-
NUM_WORKERS: 2
19+
NUM_WORKERS: 4
2120
BATCH_SIZE: 64
22-
NUM_INSTANCES: 16
21+
NUM_INSTANCES: 4
2322

2423
SOLVER:
25-
LOSS: 'softmax'
26-
OPTIMIZER_NAME: 'Adam'
24+
LOSS: 'softmax_triplet'
25+
OPTIMIZER_NAME: 'SGD'
2726
MAX_EPOCHS: 320
28-
BASE_LR: 0.00005
27+
BASE_LR: 0.001
2928
WEIGHT_DECAY: 0.0005
3029

3130
CHECK_PERIOD: 5
@@ -34,7 +33,7 @@ SOLVER:
3433

3534
SCHEDULER:
3635
NAME: 'StepLR'
37-
STEP: 2
36+
STEP: 10
3837
GAMMA: 0.5
3938

4039
TEST:

configs/sampleAdam.yaml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
OUTPUT_DIR: "./checkpoint/DogCat/Exp-1"
2+
DEVICE: "cuda"
3+
DEVICE_ID: ('1')
4+
5+
MODEL:
6+
NAME: 'baseline'
7+
ARCH: 'resnet50'
8+
9+
INPUT:
10+
SIZE_TRAIN: [256, 128]
11+
SIZE_TEST: [256, 128]
12+
HF_PROB: 0.5 # random horizontal flip
13+
14+
DATASETS:
15+
NAME: 'Market1501'
16+
ROOT: '/home/hzh/data'
17+
18+
DATALOADER:
19+
NUM_WORKERS: 2
20+
BATCH_SIZE: 64
21+
NUM_INSTANCES: 16
22+
23+
SOLVER:
24+
LOSS: 'softmax_triplet'
25+
OPTIMIZER_NAME: 'Adam'
26+
MAX_EPOCHS: 320
27+
BASE_LR: 0.005
28+
WEIGHT_DECAY: 0.0005
29+
30+
CHECK_PERIOD: 5
31+
EVAL_PERIOD: 1
32+
PRINT_FREQ: 10
33+
34+
SCHEDULER:
35+
NAME: 'StepLR'
36+
STEP: 2
37+
GAMMA: 0.5
38+
39+
TEST:
40+
BATCH_SIZE: 128
41+
42+

data/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def make_loader_dsap():
2020

2121
def make_loader(cfg):
2222
_data = ReIDDataset(dataset_dir=cfg.DATASETS.NAME, root=cfg.DATASETS.ROOT)
23+
num_train_pids = _data.num_train_pids
2324

2425
train_loader = DataLoader(ImageData(_data.train, TrainTransform(p=0.5)),
2526
sampler=RandomIdentitySampler(_data.train, cfg.DATALOADER.NUM_INSTANCES),
@@ -34,4 +35,4 @@ def make_loader(cfg):
3435
batch_size=cfg.DATALOADER.BATCH_SIZE, num_workers=cfg.DATALOADER.NUM_WORKERS,
3536
pin_memory=True)
3637

37-
return train_loader, query_loader, gallery_loader
38+
return train_loader, query_loader, gallery_loader, num_train_pids

models/__init__.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,37 @@
77
-------------------------------------------------
88
"""
99

10+
import torch.nn as nn
11+
12+
from models.baseline import Baseline
13+
from models.losses import TripletLoss
14+
15+
MODEL = {
16+
'baseline': Baseline,
17+
}
18+
1019

1120
def make_model(cfg, num_classes):
12-
pass
21+
if cfg.MODEL.NAME not in MODEL:
22+
raise KeyError("Unknown model: ", cfg.MODEL.NAME)
23+
else:
24+
model = MODEL[cfg.MODEL.NAME](num_classes, arch=cfg.MODEL.ARCH, stride=cfg.MODEL.STRIDE)
25+
26+
return model
27+
28+
29+
def make_loss(cfg):
30+
xent_criterion = nn.CrossEntropyLoss()
31+
32+
if cfg.SOLVER.LOSS == 'softmax_triplet':
33+
embedding_criterion = TripletLoss(margin=cfg.SOLVER.MARGIN)
34+
35+
def criterion(softmax_y, triplet_y, labels):
36+
sum_loss = [embedding_criterion(output, labels)[0] for output in triplet_y] + \
37+
[xent_criterion(output, labels) for output in softmax_y]
38+
loss = sum(sum_loss)
39+
return loss
40+
41+
return criterion
42+
else:
43+
raise KeyError("Unknown loss: ", cfg.SOLVER.LOSS)

models/baseline.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __init__(self, num_classes, arch='resnet50', stride=1):
2929

3030
# backbone
3131
if arch not in FACTORY:
32-
raise KeyError("Unknown models: ", arch)
32+
raise KeyError("Unknown arch: ", arch)
3333
else:
3434
resnet = FACTORY[arch](pretrained=True)
3535
if stride == 1:
@@ -50,13 +50,13 @@ def __init__(self, num_classes, arch='resnet50', stride=1):
5050
self.gap = nn.AdaptiveAvgPool2d((1, 1))
5151

5252
self.bottleneck = nn.Sequential(
53-
nn.Linear(2048, 512),
54-
nn.BatchNorm1d(512),
53+
nn.Linear(2048, 1024),
54+
nn.BatchNorm1d(1024),
5555
nn.LeakyReLU(0.1),
5656
nn.Dropout(p=0.5)
5757
)
5858
self.bottleneck.apply(weights_init_kaiming)
59-
self.classifier = nn.Linear(512, self.num_classes)
59+
self.classifier = nn.Linear(1024, self.num_classes)
6060
self.classifier.apply(weights_init_classifier)
6161

6262
def forward(self, x):
@@ -67,7 +67,7 @@ def forward(self, x):
6767
if self.training:
6868
feat = self.bottleneck(global_feat)
6969
cls_score = self.classifier(feat)
70-
return [global_feat], [cls_score]
70+
return [cls_score], [global_feat]
7171
else:
7272
return global_feat
7373

models/losses/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""
2+
-------------------------------------------------
3+
File Name: __init__.py.py
4+
Author: Zhonghao Huang
5+
Date: 2019/9/10
6+
Description:
7+
-------------------------------------------------
8+
"""
9+
10+
from .triplet import TripletLoss

models/losses/triplet.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
-------------------------------------------------
3+
File Name: triplet.py
4+
Author: Zhonghao Huang
5+
Date: 2019/9/10
6+
Description:
7+
-------------------------------------------------
8+
"""
9+
10+
import torch
11+
import torch.nn as nn
12+
13+
14+
def topk_mask(input, dim, K=10, **kwargs):
15+
index = input.topk(max(1, min(K, input.size(dim))), dim=dim, **kwargs)[1]
16+
return torch.autograd.Variable(torch.zeros_like(input.data)).scatter(dim, index, 1.0)
17+
18+
19+
def pdist(A, squared=False, eps=1e-4):
20+
prod = torch.mm(A, A.t())
21+
norm = prod.diag().unsqueeze(1).expand_as(prod)
22+
res = (norm + norm.t() - 2 * prod).clamp(min=0)
23+
return res if squared else res.clamp(min=eps).sqrt()
24+
25+
26+
def normalize(x, axis=-1):
27+
"""Normalizing to unit length along the specified dimension.
28+
Args:
29+
x: pytorch Variable
30+
Returns:
31+
x: pytorch Variable, same shape as input
32+
"""
33+
x = 1. * x / (torch.norm(x, 2, axis, keepdim=True).expand_as(x) + 1e-12)
34+
return x
35+
36+
37+
def euclidean_dist(x, y):
38+
"""
39+
Args:
40+
x: pytorch Variable, with shape [m, d]
41+
y: pytorch Variable, with shape [n, d]
42+
Returns:
43+
dist: pytorch Variable, with shape [m, n]
44+
"""
45+
m, n = x.size(0), y.size(0)
46+
xx = torch.pow(x, 2).sum(1, keepdim=True).expand(m, n)
47+
yy = torch.pow(y, 2).sum(1, keepdim=True).expand(n, m).t()
48+
dist = xx + yy
49+
dist.addmm_(1, -2, x, y.t())
50+
dist = dist.clamp(min=1e-12).sqrt() # for numerical stability
51+
return dist
52+
53+
54+
def hard_example_mining(dist_mat, labels, margin, return_inds=False):
55+
"""For each anchor, find the hardest positive and negative sample.
56+
Args:
57+
dist_mat: pytorch Variable, pair wise distance between samples, shape [N, N]
58+
labels: pytorch LongTensor, with shape [N]
59+
return_inds: whether to return the indices. Save time if `False`(?)
60+
Returns:
61+
dist_ap: pytorch Variable, distance(anchor, positive); shape [N]
62+
dist_an: pytorch Variable, distance(anchor, negative); shape [N]
63+
p_inds: pytorch LongTensor, with shape [N];
64+
indices of selected hard positive samples; 0 <= p_inds[i] <= N - 1
65+
n_inds: pytorch LongTensor, with shape [N];
66+
indices of selected hard negative samples; 0 <= n_inds[i] <= N - 1
67+
NOTE: Only consider the case in which all labels have same num of samples,
68+
thus we can cope with all anchors in parallel.
69+
"""
70+
71+
torch.set_printoptions(threshold=5000)
72+
assert len(dist_mat.size()) == 2
73+
assert dist_mat.size(0) == dist_mat.size(1)
74+
N = dist_mat.size(0)
75+
76+
# shape [N, N]
77+
is_pos = labels.expand(N, N).eq(labels.expand(N, N).t())
78+
is_neg = labels.expand(N, N).ne(labels.expand(N, N).t())
79+
# `dist_ap` means distance(anchor, positive)
80+
# both `dist_ap` and `relative_p_inds` with shape [N, 1]
81+
dist_ap, relative_p_inds = torch.max(
82+
dist_mat[is_pos].contiguous().view(N, -1), 1, keepdim=True)
83+
# `dist_an` means distance(anchor, negative)
84+
# both `dist_an` and `relative_n_inds` with shape [N, 1]
85+
dist_an, relative_n_inds = torch.min(
86+
dist_mat[is_neg].contiguous().view(N, -1), 1, keepdim=True)
87+
# shape [N]
88+
dist_ap = dist_ap.squeeze(1)
89+
dist_an = dist_an.squeeze(1)
90+
91+
if return_inds:
92+
# shape [N, N]
93+
ind = (labels.new().resize_as_(labels)
94+
.copy_(torch.arange(0, N).long())
95+
.unsqueeze(0).expand(N, N))
96+
# shape [N, 1]
97+
p_inds = torch.gather(
98+
ind[is_pos].contiguous().view(N, -1), 1, relative_p_inds.data)
99+
n_inds = torch.gather(
100+
ind[is_neg].contiguous().view(N, -1), 1, relative_n_inds.data)
101+
# shape [N]
102+
p_inds = p_inds.squeeze(1)
103+
n_inds = n_inds.squeeze(1)
104+
return dist_ap, dist_an, p_inds, n_inds
105+
106+
return dist_ap, dist_an
107+
108+
109+
class TripletLoss(object):
110+
"""Modified from Tong Xiao's open-reid (https://github.com/Cysu/open-reid).
111+
Related Triplet Loss theory can be found in paper 'In Defense of the Triplet
112+
Loss for Person Re-Identification'."""
113+
114+
def __init__(self, margin=None):
115+
self.margin = margin
116+
if margin is not None:
117+
self.ranking_loss = nn.MarginRankingLoss(margin=margin)
118+
else:
119+
self.ranking_loss = nn.SoftMarginLoss()
120+
121+
def __call__(self, global_feat, labels, normalize_feature=False):
122+
if normalize_feature:
123+
global_feat = normalize(global_feat, axis=-1)
124+
dist_mat = euclidean_dist(global_feat, global_feat)
125+
dist_ap, dist_an = hard_example_mining(dist_mat, labels, self.margin)
126+
y = dist_an.new().resize_as_(dist_an).fill_(1)
127+
if self.margin is not None:
128+
loss = self.ranking_loss(dist_an, dist_ap, y)
129+
else:
130+
loss = self.ranking_loss(dist_an - dist_ap, y)
131+
return loss, dist_ap, dist_an

0 commit comments

Comments
 (0)