doevent commited on
Commit
a54c072
1 Parent(s): 3c7e686

Upload train_retrieval.py

Browse files
Files changed (1) hide show
  1. train_retrieval.py +345 -0
train_retrieval.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ * Copyright (c) 2022, salesforce.com, inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ * By Junnan Li
7
+ '''
8
+ import argparse
9
+ import os
10
+ import ruamel_yaml as yaml
11
+ import numpy as np
12
+ import random
13
+ import time
14
+ import datetime
15
+ import json
16
+ from pathlib import Path
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+ import torch.nn.functional as F
21
+ import torch.backends.cudnn as cudnn
22
+ import torch.distributed as dist
23
+ from torch.utils.data import DataLoader
24
+
25
+ from models.blip_retrieval import blip_retrieval
26
+ import utils
27
+ from utils import cosine_lr_schedule
28
+ from data import create_dataset, create_sampler, create_loader
29
+
30
+
31
+ def train(model, data_loader, optimizer, epoch, device, config):
32
+ # train
33
+ model.train()
34
+
35
+ metric_logger = utils.MetricLogger(delimiter=" ")
36
+ metric_logger.add_meter('lr', utils.SmoothedValue(window_size=1, fmt='{value:.6f}'))
37
+ metric_logger.add_meter('loss_itm', utils.SmoothedValue(window_size=1, fmt='{value:.4f}'))
38
+ metric_logger.add_meter('loss_ita', utils.SmoothedValue(window_size=1, fmt='{value:.4f}'))
39
+ header = 'Train Epoch: [{}]'.format(epoch)
40
+ print_freq = 50
41
+
42
+ for i,(image, caption, idx) in enumerate(metric_logger.log_every(data_loader, print_freq, header)):
43
+ image = image.to(device,non_blocking=True)
44
+ idx = idx.to(device,non_blocking=True)
45
+
46
+ if epoch>0:
47
+ alpha = config['alpha']
48
+ else:
49
+ alpha = config['alpha']*min(1,i/len(data_loader))
50
+
51
+ loss_ita, loss_itm = model(image, caption, alpha=alpha, idx=idx)
52
+ loss = loss_ita + loss_itm
53
+
54
+ optimizer.zero_grad()
55
+ loss.backward()
56
+ optimizer.step()
57
+
58
+ metric_logger.update(loss_itm=loss_itm.item())
59
+ metric_logger.update(loss_ita=loss_ita.item())
60
+ metric_logger.update(lr=optimizer.param_groups[0]["lr"])
61
+
62
+ # gather the stats from all processes
63
+ metric_logger.synchronize_between_processes()
64
+ print("Averaged stats:", metric_logger.global_avg())
65
+ return {k: "{:.3f}".format(meter.global_avg) for k, meter in metric_logger.meters.items()}
66
+
67
+
68
+ @torch.no_grad()
69
+ def evaluation(model, data_loader, device, config):
70
+ # test
71
+ model.eval()
72
+
73
+ metric_logger = utils.MetricLogger(delimiter=" ")
74
+ header = 'Evaluation:'
75
+
76
+ print('Computing features for evaluation...')
77
+ start_time = time.time()
78
+
79
+ texts = data_loader.dataset.text
80
+ num_text = len(texts)
81
+ text_bs = 256
82
+ text_ids = []
83
+ text_embeds = []
84
+ text_atts = []
85
+ for i in range(0, num_text, text_bs):
86
+ text = texts[i: min(num_text, i+text_bs)]
87
+ text_input = model.tokenizer(text, padding='max_length', truncation=True, max_length=35, return_tensors="pt").to(device)
88
+ text_output = model.text_encoder(text_input.input_ids, attention_mask = text_input.attention_mask, mode='text')
89
+ text_embed = F.normalize(model.text_proj(text_output.last_hidden_state[:,0,:]))
90
+ text_embeds.append(text_embed)
91
+ text_ids.append(text_input.input_ids)
92
+ text_atts.append(text_input.attention_mask)
93
+
94
+ text_embeds = torch.cat(text_embeds,dim=0)
95
+ text_ids = torch.cat(text_ids,dim=0)
96
+ text_atts = torch.cat(text_atts,dim=0)
97
+ text_ids[:,0] = model.tokenizer.enc_token_id
98
+
99
+ image_feats = []
100
+ image_embeds = []
101
+ for image, img_id in data_loader:
102
+ image = image.to(device)
103
+ image_feat = model.visual_encoder(image)
104
+ image_embed = model.vision_proj(image_feat[:,0,:])
105
+ image_embed = F.normalize(image_embed,dim=-1)
106
+
107
+ image_feats.append(image_feat.cpu())
108
+ image_embeds.append(image_embed)
109
+
110
+ image_feats = torch.cat(image_feats,dim=0)
111
+ image_embeds = torch.cat(image_embeds,dim=0)
112
+
113
+ sims_matrix = image_embeds @ text_embeds.t()
114
+ score_matrix_i2t = torch.full((len(data_loader.dataset.image),len(texts)),-100.0).to(device)
115
+
116
+ num_tasks = utils.get_world_size()
117
+ rank = utils.get_rank()
118
+ step = sims_matrix.size(0)//num_tasks + 1
119
+ start = rank*step
120
+ end = min(sims_matrix.size(0),start+step)
121
+
122
+ for i,sims in enumerate(metric_logger.log_every(sims_matrix[start:end], 50, header)):
123
+ topk_sim, topk_idx = sims.topk(k=config['k_test'], dim=0)
124
+
125
+ encoder_output = image_feats[start+i].repeat(config['k_test'],1,1).to(device)
126
+ encoder_att = torch.ones(encoder_output.size()[:-1],dtype=torch.long).to(device)
127
+ output = model.text_encoder(text_ids[topk_idx],
128
+ attention_mask = text_atts[topk_idx],
129
+ encoder_hidden_states = encoder_output,
130
+ encoder_attention_mask = encoder_att,
131
+ return_dict = True,
132
+ )
133
+ score = model.itm_head(output.last_hidden_state[:,0,:])[:,1]
134
+ score_matrix_i2t[start+i,topk_idx] = score + topk_sim
135
+
136
+ sims_matrix = sims_matrix.t()
137
+ score_matrix_t2i = torch.full((len(texts),len(data_loader.dataset.image)),-100.0).to(device)
138
+
139
+ step = sims_matrix.size(0)//num_tasks + 1
140
+ start = rank*step
141
+ end = min(sims_matrix.size(0),start+step)
142
+
143
+ for i,sims in enumerate(metric_logger.log_every(sims_matrix[start:end], 50, header)):
144
+
145
+ topk_sim, topk_idx = sims.topk(k=config['k_test'], dim=0)
146
+ encoder_output = image_feats[topk_idx].to(device)
147
+ encoder_att = torch.ones(encoder_output.size()[:-1],dtype=torch.long).to(device)
148
+ output = model.text_encoder(text_ids[start+i].repeat(config['k_test'],1),
149
+ attention_mask = text_atts[start+i].repeat(config['k_test'],1),
150
+ encoder_hidden_states = encoder_output,
151
+ encoder_attention_mask = encoder_att,
152
+ return_dict = True,
153
+ )
154
+ score = model.itm_head(output.last_hidden_state[:,0,:])[:,1]
155
+ score_matrix_t2i[start+i,topk_idx] = score + topk_sim
156
+
157
+ if args.distributed:
158
+ dist.barrier()
159
+ torch.distributed.all_reduce(score_matrix_i2t, op=torch.distributed.ReduceOp.SUM)
160
+ torch.distributed.all_reduce(score_matrix_t2i, op=torch.distributed.ReduceOp.SUM)
161
+
162
+ total_time = time.time() - start_time
163
+ total_time_str = str(datetime.timedelta(seconds=int(total_time)))
164
+ print('Evaluation time {}'.format(total_time_str))
165
+
166
+ return score_matrix_i2t.cpu().numpy(), score_matrix_t2i.cpu().numpy()
167
+
168
+
169
+
170
+ @torch.no_grad()
171
+ def itm_eval(scores_i2t, scores_t2i, txt2img, img2txt):
172
+
173
+ #Images->Text
174
+ ranks = np.zeros(scores_i2t.shape[0])
175
+ for index,score in enumerate(scores_i2t):
176
+ inds = np.argsort(score)[::-1]
177
+ # Score
178
+ rank = 1e20
179
+ for i in img2txt[index]:
180
+ tmp = np.where(inds == i)[0][0]
181
+ if tmp < rank:
182
+ rank = tmp
183
+ ranks[index] = rank
184
+
185
+ # Compute metrics
186
+ tr1 = 100.0 * len(np.where(ranks < 1)[0]) / len(ranks)
187
+ tr5 = 100.0 * len(np.where(ranks < 5)[0]) / len(ranks)
188
+ tr10 = 100.0 * len(np.where(ranks < 10)[0]) / len(ranks)
189
+
190
+ #Text->Images
191
+ ranks = np.zeros(scores_t2i.shape[0])
192
+
193
+ for index,score in enumerate(scores_t2i):
194
+ inds = np.argsort(score)[::-1]
195
+ ranks[index] = np.where(inds == txt2img[index])[0][0]
196
+
197
+ # Compute metrics
198
+ ir1 = 100.0 * len(np.where(ranks < 1)[0]) / len(ranks)
199
+ ir5 = 100.0 * len(np.where(ranks < 5)[0]) / len(ranks)
200
+ ir10 = 100.0 * len(np.where(ranks < 10)[0]) / len(ranks)
201
+
202
+ tr_mean = (tr1 + tr5 + tr10) / 3
203
+ ir_mean = (ir1 + ir5 + ir10) / 3
204
+ r_mean = (tr_mean + ir_mean) / 2
205
+
206
+ eval_result = {'txt_r1': tr1,
207
+ 'txt_r5': tr5,
208
+ 'txt_r10': tr10,
209
+ 'txt_r_mean': tr_mean,
210
+ 'img_r1': ir1,
211
+ 'img_r5': ir5,
212
+ 'img_r10': ir10,
213
+ 'img_r_mean': ir_mean,
214
+ 'r_mean': r_mean}
215
+ return eval_result
216
+
217
+
218
+ def main(args, config):
219
+ utils.init_distributed_mode(args)
220
+
221
+ device = torch.device(args.device)
222
+
223
+ # fix the seed for reproducibility
224
+ seed = args.seed + utils.get_rank()
225
+ torch.manual_seed(seed)
226
+ np.random.seed(seed)
227
+ random.seed(seed)
228
+ cudnn.benchmark = True
229
+
230
+ #### Dataset ####
231
+ print("Creating retrieval dataset")
232
+ train_dataset, val_dataset, test_dataset = create_dataset('retrieval_%s'%config['dataset'], config)
233
+
234
+ if args.distributed:
235
+ num_tasks = utils.get_world_size()
236
+ global_rank = utils.get_rank()
237
+ samplers = create_sampler([train_dataset], [True], num_tasks, global_rank) + [None, None]
238
+ else:
239
+ samplers = [None, None, None]
240
+
241
+ train_loader, val_loader, test_loader = create_loader([train_dataset, val_dataset, test_dataset],samplers,
242
+ batch_size=[config['batch_size_train']]+[config['batch_size_test']]*2,
243
+ num_workers=[4,4,4],
244
+ is_trains=[True, False, False],
245
+ collate_fns=[None,None,None])
246
+
247
+
248
+ #### Model ####
249
+ print("Creating model")
250
+ model = blip_retrieval(pretrained=config['pretrained'], image_size=config['image_size'], vit=config['vit'],
251
+ vit_grad_ckpt=config['vit_grad_ckpt'], vit_ckpt_layer=config['vit_ckpt_layer'],
252
+ queue_size=config['queue_size'], negative_all_rank=config['negative_all_rank'])
253
+
254
+ model = model.to(device)
255
+
256
+ model_without_ddp = model
257
+ if args.distributed:
258
+ model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
259
+ model_without_ddp = model.module
260
+
261
+ optimizer = torch.optim.AdamW(params=model.parameters(), lr=config['init_lr'], weight_decay=config['weight_decay'])
262
+
263
+ best = 0
264
+ best_epoch = 0
265
+
266
+ print("Start training")
267
+ start_time = time.time()
268
+
269
+ for epoch in range(0, config['max_epoch']):
270
+ if not args.evaluate:
271
+ if args.distributed:
272
+ train_loader.sampler.set_epoch(epoch)
273
+
274
+ cosine_lr_schedule(optimizer, epoch, config['max_epoch'], config['init_lr'], config['min_lr'])
275
+
276
+ train_stats = train(model, train_loader, optimizer, epoch, device, config)
277
+
278
+ score_val_i2t, score_val_t2i, = evaluation(model_without_ddp, val_loader, device, config)
279
+ score_test_i2t, score_test_t2i = evaluation(model_without_ddp, test_loader, device, config)
280
+
281
+ if utils.is_main_process():
282
+
283
+ val_result = itm_eval(score_val_i2t, score_val_t2i, val_loader.dataset.txt2img, val_loader.dataset.img2txt)
284
+ print(val_result)
285
+
286
+ if val_result['r_mean']>best:
287
+ save_obj = {
288
+ 'model': model_without_ddp.state_dict(),
289
+ 'optimizer': optimizer.state_dict(),
290
+ 'config': config,
291
+ 'epoch': epoch,
292
+ }
293
+ torch.save(save_obj, os.path.join(args.output_dir, 'checkpoint_best.pth'))
294
+ best = val_result['r_mean']
295
+ best_epoch = epoch
296
+
297
+ test_result = itm_eval(score_test_i2t, score_test_t2i, test_loader.dataset.txt2img, test_loader.dataset.img2txt)
298
+ print(test_result)
299
+
300
+ if args.evaluate:
301
+ log_stats = {**{f'val_{k}': v for k, v in val_result.items()},
302
+ **{f'test_{k}': v for k, v in test_result.items()},
303
+ }
304
+ with open(os.path.join(args.output_dir, "evaluate.txt"),"a") as f:
305
+ f.write(json.dumps(log_stats) + "\n")
306
+ else:
307
+ log_stats = {**{f'train_{k}': v for k, v in train_stats.items()},
308
+ **{f'val_{k}': v for k, v in val_result.items()},
309
+ **{f'test_{k}': v for k, v in test_result.items()},
310
+ 'epoch': epoch,
311
+ 'best_epoch': best_epoch,
312
+ }
313
+ with open(os.path.join(args.output_dir, "log.txt"),"a") as f:
314
+ f.write(json.dumps(log_stats) + "\n")
315
+
316
+ if args.evaluate:
317
+ break
318
+
319
+ dist.barrier()
320
+ torch.cuda.empty_cache()
321
+
322
+ total_time = time.time() - start_time
323
+ total_time_str = str(datetime.timedelta(seconds=int(total_time)))
324
+ print('Training time {}'.format(total_time_str))
325
+
326
+
327
+ if __name__ == '__main__':
328
+ parser = argparse.ArgumentParser()
329
+ parser.add_argument('--config', default='./configs/retrieval_flickr.yaml')
330
+ parser.add_argument('--output_dir', default='output/Retrieval_flickr')
331
+ parser.add_argument('--evaluate', action='store_true')
332
+ parser.add_argument('--device', default='cuda')
333
+ parser.add_argument('--seed', default=42, type=int)
334
+ parser.add_argument('--world_size', default=1, type=int, help='number of distributed processes')
335
+ parser.add_argument('--dist_url', default='env://', help='url used to set up distributed training')
336
+ parser.add_argument('--distributed', default=True, type=bool)
337
+ args = parser.parse_args()
338
+
339
+ config = yaml.load(open(args.config, 'r'), Loader=yaml.Loader)
340
+
341
+ Path(args.output_dir).mkdir(parents=True, exist_ok=True)
342
+
343
+ yaml.dump(config, open(os.path.join(args.output_dir, 'config.yaml'), 'w'))
344
+
345
+ main(args, config)