| import math |
| import pickle |
| import numpy as np |
| import torch |
| import torch.nn as nn |
| import random |
| from collections import namedtuple |
|
|
| def calc_recalls(S): |
| """ |
| Computes recall at 1, 5, and 10 given a similarity matrix S. |
| By convention, rows of S are assumed to correspond to images and columns are captions. |
| """ |
| assert(S.dim() == 2) |
| assert(S.size(0) == S.size(1)) |
| if isinstance(S, torch.autograd.Variable): |
| S = S.data |
| n = S.size(0) |
| A2I_scores, A2I_ind = S.topk(10, 0) |
| I2A_scores, I2A_ind = S.topk(10, 1) |
| A_r1 = AverageMeter() |
| A_r5 = AverageMeter() |
| A_r10 = AverageMeter() |
| I_r1 = AverageMeter() |
| I_r5 = AverageMeter() |
| I_r10 = AverageMeter() |
| for i in range(n): |
| A_foundind = -1 |
| I_foundind = -1 |
| for ind in range(10): |
| if A2I_ind[ind, i] == i: |
| I_foundind = ind |
| if I2A_ind[i, ind] == i: |
| A_foundind = ind |
| |
| if A_foundind == 0: |
| A_r1.update(1) |
| else: |
| A_r1.update(0) |
| if I_foundind == 0: |
| I_r1.update(1) |
| else: |
| I_r1.update(0) |
| |
| if A_foundind >= 0 and A_foundind < 5: |
| A_r5.update(1) |
| else: |
| A_r5.update(0) |
| if I_foundind >= 0 and I_foundind < 5: |
| I_r5.update(1) |
| else: |
| I_r5.update(0) |
| |
| if A_foundind >= 0 and A_foundind < 10: |
| A_r10.update(1) |
| else: |
| A_r10.update(0) |
| if I_foundind >= 0 and I_foundind < 10: |
| I_r10.update(1) |
| else: |
| I_r10.update(0) |
|
|
| recalls = {'A_r1':A_r1.avg, 'A_r5':A_r5.avg, 'A_r10':A_r10.avg, |
| 'I_r1':I_r1.avg, 'I_r5':I_r5.avg, 'I_r10':I_r10.avg} |
| |
|
|
| return recalls |
|
|
| def computeMatchmap(I, A): |
| assert(I.dim() == 3) |
| assert(A.dim() == 2) |
| D = I.size(0) |
| H = I.size(1) |
| W = I.size(2) |
| T = A.size(1) |
| Ir = I.view(D, -1).t() |
| matchmap = torch.mm(Ir, A) |
| matchmap = matchmap.view(H, W, T) |
| return matchmap |
|
|
| def matchmapSim(M, simtype): |
| assert(M.dim() == 3) |
| if simtype == 'SISA': |
| return M.mean() |
| elif simtype == 'MISA': |
| M_maxH, _ = M.max(0) |
| M_maxHW, _ = M_maxH.max(0) |
| return M_maxHW.mean() |
| elif simtype == 'SIMA': |
| M_maxT, _ = M.max(2) |
| return M_maxT.mean() |
| else: |
| raise ValueError |
|
|
| def sampled_margin_rank_loss(image_outputs, audio_outputs, nframes, margin=1., simtype='MISA'): |
| """ |
| Computes the triplet margin ranking loss for each anchor image/caption pair |
| The impostor image/caption is randomly sampled from the minibatch |
| """ |
| assert(image_outputs.dim() == 4) |
| assert(audio_outputs.dim() == 3) |
| n = image_outputs.size(0) |
| loss = torch.zeros(1, device=image_outputs.device, requires_grad=True) |
| for i in range(n): |
| I_imp_ind = i |
| A_imp_ind = i |
| while I_imp_ind == i: |
| I_imp_ind = np.random.randint(0, n) |
| while A_imp_ind == i: |
| A_imp_ind = np.random.randint(0, n) |
| nF = nframes[i] |
| nFimp = nframes[A_imp_ind] |
| anchorsim = matchmapSim(computeMatchmap(image_outputs[i], audio_outputs[i][:, 0:nF]), simtype) |
| Iimpsim = matchmapSim(computeMatchmap(image_outputs[I_imp_ind], audio_outputs[i][:, 0:nF]), simtype) |
| Aimpsim = matchmapSim(computeMatchmap(image_outputs[i], audio_outputs[A_imp_ind][:, 0:nFimp]), simtype) |
| A2I_simdif = margin + Iimpsim - anchorsim |
| if (A2I_simdif.data > 0).all(): |
| loss = loss + A2I_simdif |
| I2A_simdif = margin + Aimpsim - anchorsim |
| if (I2A_simdif.data > 0).all(): |
| loss = loss + I2A_simdif |
| loss = loss / n |
| return loss |
|
|
| def compute_matchmap_similarity_matrix(image_outputs, audio_outputs, nframes, simtype='MISA'): |
| """ |
| Assumes image_outputs is a (batchsize, embedding_dim, rows, height) tensor |
| Assumes audio_outputs is a (batchsize, embedding_dim, 1, time) tensor |
| Returns similarity matrix S where images are rows and audios are along the columns |
| """ |
| assert(image_outputs.dim() == 4) |
| assert(audio_outputs.dim() == 3) |
| n = image_outputs.size(0) |
| S = torch.zeros(n, n, device=image_outputs.device) |
| for image_idx in range(n): |
| for audio_idx in range(n): |
| nF = max(1, nframes[audio_idx]) |
| S[image_idx, audio_idx] = matchmapSim(computeMatchmap(image_outputs[image_idx], audio_outputs[audio_idx][:, 0:nF]), simtype) |
| return S |
|
|
| def compute_pooldot_similarity_matrix(image_outputs, audio_outputs, nframes): |
| """ |
| Assumes image_outputs is a (batchsize, embedding_dim, rows, height) tensor |
| Assumes audio_outputs is a (batchsize, embedding_dim, 1, time) tensor |
| Returns similarity matrix S where images are rows and audios are along the columns |
| S[i][j] is computed as the dot product between the meanpooled embeddings of |
| the ith image output and jth audio output |
| """ |
| assert(image_outputs.dim() == 4) |
| assert(audio_outputs.dim() == 4) |
| n = image_outputs.size(0) |
| imagePoolfunc = nn.AdaptiveAvgPool2d((1, 1)) |
| pooled_image_outputs = imagePoolfunc(image_outputs).squeeze(3).squeeze(2) |
| audioPoolfunc = nn.AdaptiveAvgPool2d((1, 1)) |
| pooled_audio_outputs_list = [] |
| for idx in range(n): |
| nF = max(1, nframes[idx]) |
| pooled_audio_outputs_list.append(audioPoolfunc(audio_outputs[idx][:, :, 0:nF]).unsqueeze(0)) |
| pooled_audio_outputs = torch.cat(pooled_audio_outputs_list).squeeze(3).squeeze(2) |
| S = torch.mm(pooled_image_outputs, pooled_audio_outputs.t()) |
| return S |
|
|
| def one_imposter_index(i, N): |
| imp_ind = random.randint(0, N - 2) |
| if imp_ind == i: |
| imp_ind = N - 1 |
| return imp_ind |
|
|
| def basic_get_imposter_indices(N): |
| imposter_idc = [] |
| for i in range(N): |
| |
| imp_ind = one_imposter_index(i, N) |
| imposter_idc.append(imp_ind) |
| return imposter_idc |
|
|
| def semihardneg_triplet_loss_from_S(S, margin): |
| """ |
| Input: Similarity matrix S as an autograd.Variable |
| Output: The one-way triplet loss from rows of S to columns of S. Impostors are taken |
| to be the most similar point to the anchor that is still less similar to the anchor |
| than the positive example. |
| You would need to run this function twice, once with S and once with S.t(), |
| in order to compute the triplet loss in both directions. |
| """ |
| assert(S.dim() == 2) |
| assert(S.size(0) == S.size(1)) |
| N = S.size(0) |
| loss = torch.autograd.Variable(torch.zeros(1).type(S.data.type()), requires_grad=True) |
| |
| Sdiff = S - torch.diag(S).view(-1, 1) |
| eps = 1e-12 |
| |
| mask = (Sdiff < -eps).type(torch.LongTensor) |
| maskf = mask.type_as(S) |
| |
| Sp = maskf * Sdiff + (1 - maskf) * torch.min(Sdiff).detach() |
| |
| _, idc = Sp.max(dim=1) |
| idc = idc.data.cpu() |
| |
| has_neg = (mask.sum(dim=1) > 0).data.type(torch.LongTensor) |
| |
| random_imp_ind = torch.LongTensor(basic_get_imposter_indices(N)) |
| |
| imp_idc = has_neg * idc + (1 - has_neg) * random_imp_ind |
| |
| for i, imp in enumerate(imp_idc): |
| local_loss = Sdiff[i, imp] + margin |
| if (local_loss.data > 0).all(): |
| loss = loss + local_loss |
| loss = loss / N |
| return loss |
|
|
| def sampled_triplet_loss_from_S(S, margin): |
| """ |
| Input: Similarity matrix S as an autograd.Variable |
| Output: The one-way triplet loss from rows of S to columns of S. Imposters are |
| randomly sampled from the columns of S. |
| You would need to run this function twice, once with S and once with S.t(), |
| in order to compute the triplet loss in both directions. |
| """ |
| assert(S.dim() == 2) |
| assert(S.size(0) == S.size(1)) |
| N = S.size(0) |
| loss = torch.autograd.Variable(torch.zeros(1).type(S.data.type()), requires_grad=True) |
| |
| Sdiff = S - torch.diag(S).view(-1, 1) |
| imp_ind = torch.LongTensor(basic_get_imposter_indices(N)) |
| |
| for i, imp in enumerate(imp_ind): |
| local_loss = Sdiff[i, imp] + margin |
| if (local_loss.data > 0).all(): |
| loss = loss + local_loss |
| loss = loss / N |
| return loss |
|
|
| class AverageMeter(object): |
| """Computes and stores the average and current value""" |
| def __init__(self): |
| self.reset() |
|
|
| def reset(self): |
| self.val = 0 |
| self.avg = 0 |
| self.sum = 0 |
| self.count = 0 |
|
|
| def update(self, val, n=1): |
| self.val = val |
| self.sum += val * n |
| self.count += n |
| self.avg = self.sum / self.count |
|
|
| def adjust_learning_rate(base_lr, lr_decay, optimizer, epoch): |
| """Sets the learning rate to the initial LR decayed by 10 every lr_decay epochs""" |
| lr = base_lr * (0.1 ** (epoch // lr_decay)) |
| print('now learning rate changed to {:f}'.format(lr)) |
| for param_group in optimizer.param_groups: |
| param_group['lr'] = lr |
|
|
| def adjust_learning_rate2(base_lr, lr_decay, optimizer, epoch): |
| """Sets the learning rate to the initial LR decayed by 10 every lr_decay epochs""" |
| for param_group in optimizer.param_groups: |
| cur_lr = param_group['lr'] |
| print('current learing rate is {:f}'.format(lr)) |
| lr = cur_lr * 0.1 |
| print('now learning rate changed to {:f}'.format(lr)) |
| for param_group in optimizer.param_groups: |
| param_group['lr'] = lr |
|
|
|
|
| def load_progress(prog_pkl, quiet=False): |
| """ |
| load progress pkl file |
| Args: |
| prog_pkl(str): path to progress pkl file |
| Return: |
| progress(list): |
| epoch(int): |
| global_step(int): |
| best_epoch(int): |
| best_avg_r10(float): |
| """ |
| def _print(msg): |
| if not quiet: |
| print(msg) |
|
|
| with open(prog_pkl, "rb") as f: |
| prog = pickle.load(f) |
| epoch, global_step, best_epoch, best_avg_r10, _ = prog[-1] |
|
|
| _print("\nPrevious Progress:") |
| msg = "[%5s %7s %5s %7s %6s]" % ("epoch", "step", "best_epoch", "best_avg_r10", "time") |
| _print(msg) |
| return prog, epoch, global_step, best_epoch, best_avg_r10 |
|
|
| def count_parameters(model): |
| return sum([p.numel() for p in model.parameters() if p.requires_grad]) |
|
|
| PrenetConfig = namedtuple( |
| 'PrenetConfig', ['input_size', 'hidden_size', 'num_layers', 'dropout']) |
|
|
| RNNConfig = namedtuple( |
| 'RNNConfig', |
| ['input_size', 'hidden_size', 'num_layers', 'dropout', 'residual']) |