def test(self): # Load best model model = SiameseNet() _, _, _, model_state, _ = self.load_checkpoint(best=self.config.best) model.load_state_dict(model_state) if self.config.use_gpu: model.cuda() test_loader = get_test_loader(self.config.data_dir, self.config.way, self.config.test_trials, self.config.seed, self.config.num_workers, self.config.pin_memory) correct_sum = 0 num_test = test_loader.dataset.trials print(f"[*] Test on {num_test} pairs.") pbar = tqdm(enumerate(test_loader), total=num_test, desc="Test") with torch.no_grad(): for i, (x1, x2, _) in pbar: if self.config.use_gpu: x1, x2 = x1.to(self.device), x2.to(self.device) # compute log probabilities out = model(x1, x2) y_pred = torch.sigmoid(out) y_pred = torch.argmax(y_pred) if y_pred == 0: correct_sum += 1 pbar.set_postfix_str(f"accuracy: {correct_sum / num_test}") test_acc = (100. * correct_sum) / num_test print(f"Test Acc: {correct_sum}/{num_test} ({test_acc:.2f}%)")
class RobustClassifier(LightningModule): def __init__(self, args: argparse.Namespace = None): super().__init__() self.num_classes = args.num_classes self.latent_dim = args.latent_dim self.feature_dim = args.feature_dim self.batch_size = args.batch_size self.lr = args.lr self.class_noise_convertor = nn.ModuleDict({ str(k): nn.Sequential(nn.Linear(self.feature_dim, self.latent_dim), nn.ReLU(), nn.Linear(self.latent_dim, self.latent_dim)).to(self.device) for k in range(self.num_classes) }) self.generator = Generator(ngpu=1) self.generator.load_state_dict(torch.load(args.gen_weights)) self.class_identifier = SiameseNet() self.class_identifier.load_state_dict(torch.load(args.siamese_weights)) if args.generator_pre_train: self.generator.to(self.device).freeze() if args.siamese_pre_train: self.class_identifier.to(self.device).freeze() # self.class_similarity = nn.Linear(4096, 1) self.creterion = ContrastiveLoss(1) self.threshold = args.threshold self.train_acc = Accuracy() self.val_acc = Accuracy() self.test_acc = Accuracy() @staticmethod def add_to_argparse(parser): parser.add_argument("--num_classes", type=int, default=10, help="Number of Classes") parser.add_argument("--lr", type=float, default=3e-4) parser.add_argument("--threshold", type=float, default=0.5) parser.add_argument("--batch_size", type=int, default=16) parser.add_argument("--latent_dim", type=int, default=100) parser.add_argument("--feature_dim", type=int, default=100) parser.add_argument("--gen_weights", type=str, default="weights/gen_weights.pth") parser.add_argument("--siamese_weights", type=str, default="weights/siamese_weights.pth") parser.add_argument("--generator_pre_train", dest='generator_pre_train', default=True, action='store_true') parser.add_argument("--no_generator_pre_train", dest='generator_pre_train', default=True, action='store_false') parser.set_defaults(generator_pre_train=True) parser.add_argument("--siamese_pre_train", dest='siamese_pre_train', default=True, action='store_true') parser.add_argument("--no_siamese_pre_train", dest='siamese_pre_train', default=True, action='store_false') parser.set_defaults(siamese_pre_train=True) return parser def forward(self, x): batch_size = x.size(0) embeddings1, embeddings2 = torch.Tensor([]), torch.Tensor([]) scores = torch.ones(self.num_classes, batch_size) noise = torch.rand(batch_size, self.feature_dim, device=self.device) for class_idx, model in self.class_noise_convertor.items(): class_noise = model(noise).view(batch_size, -1, 1, 1) gen_imgs = self.generator(class_noise) embed1, embed2 = self.class_identifier(gen_imgs, x) embeddings1 = torch.cat( (embeddings1.to(self.device), embed1.to(self.device)), dim=0) embeddings2 = torch.cat( (embeddings2.to(self.device), embed2.to(self.device)), dim=0) scores[int(class_idx)] = nn.functional.cosine_similarity(embed1, embed2, dim=1) self.register_buffer("embeddings_1", embeddings1.view(-1, 4096)) self.register_buffer("embeddings_2", embeddings2.view(-1, 4096)) scores = torch.softmax(scores[scores > 0].view(batch_size, -1), dim=1) pred = torch.argmax(scores, dim=1).to( self.device ) #torch.Tensor([torch.argmax(img_score) if (img_score.max()-img_score.min())>0.5 else -1 for img_score in scores]).to(self.device) return pred def calculate_loss(self, y_true): siamese_labels = torch.Tensor([]) for class_idx in range(self.num_classes): temp = torch.from_numpy( np.where(y_true.cpu().numpy() == class_idx, 1, 0)) siamese_labels = torch.cat( (siamese_labels.to(self.device), temp.to(self.device)), dim=0) result = self.creterion(self.embeddings_1, self.embeddings_2, siamese_labels) return result def training_step(self, batch, batch_idx): img, y_true = batch img, y_true = img.to(self.device), y_true.to(self.device) y_pred = self(img) result = self.calculate_loss(y_true) self.train_acc(y_pred, y_true) self.log('train_acc', self.train_acc, on_epoch=True, on_step=False) self.log('train_loss', result, on_step=True) return result def validation_step(self, batch, batch_idx): img, y_true = batch img, y_true = img.to(self.device), y_true.to(self.device) y_pred = self(img) val_loss = self.calculate_loss(y_true) self.log('val_loss', val_loss, prog_bar=True) self.val_acc(y_pred, y_true) self.log('val_acc', self.val_acc, on_epoch=True, on_step=False) def test_step(self, batch, batch_idx): img, y_true = batch img, y_true = img.to(self.device), y_true.to(self.device) y_pred = self(img) self.test_acc(y_pred, y_true) self.log('test_acc', self.test_acc, on_epoch=True, on_step=False) def configure_optimizers(self): optimizer = optim.Adam(filter(lambda p: p.requires_grad_, self.parameters()), lr=self.lr) return [optimizer], []
def loadModel(path): model = SiameseNet() model.load_state_dict(torch.load(path, map_location=torch.device('cuda'))) model.cuda() model.eval() return model
class Trainer(object): """ Trainer encapsulates all the logic necessary for training the Siamese Network model. All hyperparameters are provided by the user in the config file. """ def __init__(self, config, data_loader, layer_hyperparams): """ Construct a new Trainer instance. Args ---- - config: object containing command line arguments. - data_loader: data iterator. - layer_hyperparams: dict containing layer-wise hyperparameters such as the initial learning rate, the end momentum, and the l2 regularization strength. """ self.config = config self.layer_hyperparams = layer_hyperparams if config.is_train: self.train_loader = data_loader[0] self.valid_loader = data_loader[1] self.num_train = len(self.train_loader.dataset) self.num_valid = self.valid_loader.dataset.trials else: self.test_loader = data_loader self.num_test = self.test_loader.dataset.trials self.model = SiameseNet() if config.use_gpu: self.model.cuda() # model params self.num_params = sum( [p.data.nelement() for p in self.model.parameters()]) self.num_model = get_num_model(config) self.num_layers = len(list(self.model.children())) print('[*] Number of model parameters: {:,}'.format(self.num_params)) # path params self.ckpt_dir = os.path.join(config.ckpt_dir, self.num_model) self.logs_dir = os.path.join(config.logs_dir, self.num_model) # misc params self.resume = config.resume self.use_gpu = config.use_gpu self.dtype = (torch.cuda.FloatTensor if self.use_gpu else torch.FloatTensor) # optimization params self.best = config.best self.best_valid_acc = 0. self.epochs = config.epochs self.start_epoch = 0 self.lr_patience = config.lr_patience self.train_patience = config.train_patience self.counter = 0 # grab layer-wise hyperparams self.init_lrs = self.layer_hyperparams['layer_init_lrs'] self.init_momentums = [config.init_momentum] * self.num_layers self.end_momentums = self.layer_hyperparams['layer_end_momentums'] self.l2_regs = self.layer_hyperparams['layer_l2_regs'] # compute temper rate for momentum if self.epochs == 1: f = lambda max, min: min else: f = lambda max, min: (max - min) / (self.epochs - 1) self.momentum_temper_rates = [ f(x, y) for x, y in zip(self.end_momentums, self.init_momentums) ] # set global learning rates and momentums self.lrs = self.init_lrs self.momentums = self.init_momentums # # initialize optimizer # optim_dict = [] # for i, layer in enumerate(self.model.children()): # group = {} # group['params'] = layer.parameters() # group['lr'] = self.lrs[i] # group['momentum'] = self.momentums[i] # group['weight_decay'] = self.l2_regs[i] # optim_dict.append(group) # self.optimizer = optim.SGD(optim_dict) # self.optimizer = optim.SGD( # self.model.parameters(), lr=1e-3, momentum=0.9, weight_decay=4e-4, # ) self.optimizer = optim.Adam( self.model.parameters(), lr=3e-4, weight_decay=6e-5, ) # # learning rate scheduler # self.scheduler = StepLR( # self.optimizer, step_size=self.lr_patience, gamma=0.99, # ) def train(self): if self.resume: self.load_checkpoint(best=False) # switch to train mode self.model.train() # create train and validation log files optim_file = open(os.path.join(self.logs_dir, 'optim.csv'), 'w') train_file = open(os.path.join(self.logs_dir, 'train.csv'), 'w') valid_file = open(os.path.join(self.logs_dir, 'valid.csv'), 'w') print("\n[*] Train on {} sample pairs, validate on {} trials".format( self.num_train, self.num_valid)) for epoch in range(self.start_epoch, self.epochs): # self.decay_lr() # self.temper_momentum(epoch) # # # log lrs and momentums # n = self.num_layers # msg = ( # "{}, " + ", ".join(["{}"] * n) + ", " + ", ".join(["{}"] * n) # ) # optim_file.write(msg.format( # epoch, *self.momentums, *self.lrs) # ) print('\nEpoch: {}/{}'.format(epoch + 1, self.epochs)) train_loss = self.train_one_epoch(epoch, train_file) valid_acc = self.validate(epoch, valid_file) # check for improvement is_best = valid_acc > self.best_valid_acc msg = "train loss: {:.3f} - val acc: {:.3f}" if is_best: msg += " [*]" self.counter = 0 print(msg.format(train_loss, valid_acc)) # checkpoint the model if not is_best: self.counter += 1 if self.counter > self.train_patience: print("[!] No improvement in a while, stopping training.") return self.best_valid_acc = max(valid_acc, self.best_valid_acc) self.save_checkpoint( { 'epoch': epoch + 1, 'model_state': self.model.state_dict(), 'optim_state': self.optimizer.state_dict(), 'best_valid_acc': self.best_valid_acc, }, is_best) # release resources optim_file.close() train_file.close() valid_file.close() def train_one_epoch(self, epoch, file): train_batch_time = AverageMeter() train_losses = AverageMeter() tic = time.time() with tqdm(total=self.num_train) as pbar: for i, (x1, x2, y) in enumerate(self.train_loader): if self.use_gpu: x1, x2, y = x1.cuda(), x2.cuda(), y.cuda() x1, x2, y = Variable(x1), Variable(x2), Variable(y) # split input pairs along the batch dimension batch_size = x1.shape[0] out = self.model(x1, x2) loss = F.binary_cross_entropy_with_logits(out, y) # compute gradients and update self.optimizer.zero_grad() loss.backward() self.optimizer.step() # store batch statistics toc = time.time() train_losses.update(loss.data[0], batch_size) train_batch_time.update(toc - tic) tic = time.time() pbar.set_description(("{:.1f}s - loss: {:.3f}".format( train_batch_time.val, train_losses.val, ))) pbar.update(batch_size) # log loss iter = (epoch * len(self.train_loader)) + i file.write('{},{}\n'.format(iter, train_losses.val)) return train_losses.avg def validate(self, epoch, file): # switch to evaluate mode self.model.eval() correct = 0 for i, (x1, x2) in enumerate(self.valid_loader): if self.use_gpu: x1, x2 = x1.cuda(), x2.cuda() x1, x2 = Variable(x1, volatile=True), Variable(x2, volatile=True) batch_size = x1.shape[0] # compute log probabilities out = self.model(x1, x2) log_probas = F.sigmoid(out) # get index of max log prob pred = log_probas.data.max(0)[1][0] if pred == 0: correct += 1 # compute acc and log valid_acc = (100. * correct) / self.num_valid iter = epoch file.write('{},{}\n'.format(iter, valid_acc)) return valid_acc def test(self): # load best model self.load_checkpoint(best=self.best) # switch to evaluate mode self.model.eval() correct = 0 for i, (x1, x2) in enumerate(self.test_loader): if self.use_gpu: x1, x2 = x1.cuda(), x2.cuda() x1, x2 = Variable(x1, volatile=True), Variable(x2, volatile=True) batch_size = x1.shape[0] # compute log probabilities out = self.model(x1, x2) log_probas = F.sigmoid(out) # get index of max log prob pred = log_probas.data.max(0)[1][0] if pred == 0: correct += 1 test_acc = (100. * correct) / self.num_test print("[*] Test Acc: {}/{} ({:.2f}%)".format(correct, self.num_test, test_acc)) def temper_momentum(self, epoch): """ This function linearly increases the per-layer momentum to a predefined ceiling over a set amount of epochs. """ if epoch == 0: return self.momentums = [ x + y for x, y in zip(self.momentums, self.momentum_temper_rates) ] for i, param_group in enumerate(self.optimizer.param_groups): param_group['momentum'] = self.momentums[i] def decay_lr(self): """ This function linearly decays the per-layer lr over a set amount of epochs. """ self.scheduler.step() for i, param_group in enumerate(self.optimizer.param_groups): self.lrs[i] = param_group['lr'] def save_checkpoint(self, state, is_best): filename = 'model_ckpt.tar' ckpt_path = os.path.join(self.ckpt_dir, filename) torch.save(state, ckpt_path) if is_best: filename = 'best_model_ckpt.tar' shutil.copyfile(ckpt_path, os.path.join(self.ckpt_dir, filename)) def load_checkpoint(self, best=False): print("[*] Loading model from {}".format(self.ckpt_dir)) filename = 'model_ckpt.tar' if best: filename = 'best_model_ckpt.tar' ckpt_path = os.path.join(self.ckpt_dir, filename) ckpt = torch.load(ckpt_path) # load variables from checkpoint self.start_epoch = ckpt['epoch'] self.best_valid_acc = ckpt['best_valid_acc'] self.model.load_state_dict(ckpt['model_state']) self.optimizer.load_state_dict(ckpt['optim_state']) if best: print("[*] Loaded {} checkpoint @ epoch {} " "with best valid acc of {:.3f}".format( filename, ckpt['epoch'], ckpt['best_valid_acc'])) else: print("[*] Loaded {} checkpoint @ epoch {}".format( filename, ckpt['epoch']))
def train(self): # Dataloader train_loader, valid_loader = get_train_validation_loader( self.config.data_dir, self.config.batch_size, self.config.num_train, self.config.augment, self.config.way, self.config.valid_trials, self.config.shuffle, self.config.seed, self.config.num_workers, self.config.pin_memory) # Model, Optimizer, criterion model = SiameseNet() if self.config.optimizer == "SGD": optimizer = optim.SGD(model.parameters(), lr=self.config.lr) else: optimizer = optim.Adam(model.parameters()) criterion = torch.nn.BCEWithLogitsLoss() if self.config.use_gpu: model.cuda() # Load check point if self.config.resume: start_epoch, best_epoch, best_valid_acc, model_state, optim_state = self.load_checkpoint( best=False) model.load_state_dict(model_state) optimizer.load_state_dict(optim_state) one_cycle = OneCyclePolicy(optimizer, self.config.lr, (self.config.epochs - start_epoch) * len(train_loader), momentum_rng=[0.85, 0.95]) else: best_epoch = 0 start_epoch = 0 best_valid_acc = 0 one_cycle = OneCyclePolicy(optimizer, self.config.lr, self.config.epochs * len(train_loader), momentum_rng=[0.85, 0.95]) # create tensorboard summary and add model structure. writer = SummaryWriter(os.path.join(self.config.logs_dir, 'logs'), filename_suffix=self.config.num_model) im1, im2, _ = next(iter(valid_loader)) writer.add_graph(model, [im1.to(self.device), im2.to(self.device)]) counter = 0 num_train = len(train_loader) num_valid = len(valid_loader) print( f"[*] Train on {len(train_loader.dataset)} sample pairs, validate on {valid_loader.dataset.trials} trials" ) # Train & Validation main_pbar = tqdm(range(start_epoch, self.config.epochs), initial=start_epoch, position=0, total=self.config.epochs, desc="Process") for epoch in main_pbar: train_losses = AverageMeter() valid_losses = AverageMeter() # TRAIN model.train() train_pbar = tqdm(enumerate(train_loader), total=num_train, desc="Train", position=1, leave=False) for i, (x1, x2, y) in train_pbar: if self.config.use_gpu: x1, x2, y = x1.to(self.device), x2.to(self.device), y.to( self.device) out = model(x1, x2) loss = criterion(out, y.unsqueeze(1)) # compute gradients and update optimizer.zero_grad() loss.backward() optimizer.step() one_cycle.step() # store batch statistics train_losses.update(loss.item(), x1.shape[0]) # log loss writer.add_scalar("Loss/Train", train_losses.val, epoch * len(train_loader) + i) train_pbar.set_postfix_str(f"loss: {train_losses.val:0.3f}") # VALIDATION model.eval() valid_acc = 0 correct_sum = 0 valid_pbar = tqdm(enumerate(valid_loader), total=num_valid, desc="Valid", position=1, leave=False) with torch.no_grad(): for i, (x1, x2, y) in valid_pbar: if self.config.use_gpu: x1, x2, y = x1.to(self.device), x2.to( self.device), y.to(self.device) # compute log probabilities out = model(x1, x2) loss = criterion(out, y.unsqueeze(1)) y_pred = torch.sigmoid(out) y_pred = torch.argmax(y_pred) if y_pred == 0: correct_sum += 1 # store batch statistics valid_losses.update(loss.item(), x1.shape[0]) # compute acc and log valid_acc = correct_sum / num_valid writer.add_scalar("Loss/Valid", valid_losses.val, epoch * len(valid_loader) + i) valid_pbar.set_postfix_str(f"accuracy: {valid_acc:0.3f}") writer.add_scalar("Acc/Valid", valid_acc, epoch) # check for improvement if valid_acc > best_valid_acc: is_best = True best_valid_acc = valid_acc best_epoch = epoch counter = 0 else: is_best = False counter += 1 # checkpoint the model if counter > self.config.train_patience: print("[!] No improvement in a while, stopping training.") return if is_best or epoch % 5 == 0 or epoch == self.config.epochs: self.save_checkpoint( { 'epoch': epoch, 'model_state': model.state_dict(), 'optim_state': optimizer.state_dict(), 'best_valid_acc': best_valid_acc, 'best_epoch': best_epoch, }, is_best) main_pbar.set_postfix_str( f"best acc: {best_valid_acc:.3f} best epoch: {best_epoch} ") tqdm.write( f"[{epoch}] train loss: {train_losses.avg:.3f} - valid loss: {valid_losses.avg:.3f} - valid acc: {valid_acc:.3f} {'[BEST]' if is_best else ''}" ) # release resources writer.close()
class QueryModel(): """ Get the confidence score for two PIL images Args :param model_path str: path to the model to be loaded Properties :param self.model nn.Module: Model Type to be used for inference. :param self.ckpt: Loaded model information """ def __init__(self, model_path, *args, **kwargs): """ - Load model from model_path - Load state to the model """ self.model = SiameseNet().cuda() self.ckpt = torch.load(model_path) self.model.load_state_dict(self.ckpt['model_state']) @staticmethod def resizeImage(image): """ resize image to model parameters. Args :param image PIL.Image: PIL Image Return :param image PIL.Image: resized image """ return image.resize((120, 80), Image.ANTIALIAS) # return image.resize((96, 64), Image.ANTIALIAS) def ImageToTensor(self, image): """ resize and transform PIL image to tensor Args :param image PIL.Image: PIL Image Return :param tensor torch.Tesnor: PyTorch Tensor """ image = self.resizeImage(image) transform = transforms.Compose([transforms.ToTensor()]) return torch.reshape(transform(image), [1, 1, 80, 120]) def getConfidence(self, tensor0, tensor1): """ get confidence on the two tensors from the model and apply sigmoid. Args :param tensor0 torch.Tensor: transformed image tensor to be used for inference :param tensor1 torch.Tensor: transformed image tensor to be used for inference Return :param output torch.Tensor: confidence tensor """ x0, x1 = Variable(tensor0).cuda(), Variable(tensor1).cuda() output = self.model(x0, x1) output = F.sigmoid(output) return output def predict(self, img0, img1): """ Infer confidence on the two images from the model and return it. Args :param img0 PIL.Image: first image to be compared :param img1 PIL.Image: second image to be compared Return :param output torch.Tensor: confidence tensor confidence > 0.5, Similar Pairs confidence < 0.5, Dissimilar Pairs """ img0_tensor = self.ImageToTensor(img0) img1_tensor = self.ImageToTensor(img1) output = self.getConfidence(img0_tensor, img1_tensor) return output