//  [python]
def evaluate():
    capsule_net.eval()
    test_loss = np.zeros(3)
    targets, preds_coarse, preds_fine, preds_dist = [], [], [], []

    with torch.no_grad():
        for batch_id, (data, target, _) in enumerate(test_loader):
            data, target = data.cuda(), target.cuda()
            p_coarse, avg_activation_map = capsule_net(data, target)
            p_coarse_length = get_vector_length(p_coarse)
            loss = capsule_loss(p_coarse_length, target)
            targets += [target]
            preds_coarse += [p_coarse_length]
            test_loss[0] += loss

            ##################################
            # Distillation (Localization and Refinement)
            ##################################
            crop_mask = F.interpolate(avg_activation_map, size=(data.size(2), data.size(3)), mode='bilinear') > options.theta_c
            crop_images = []
            for batch_index in range(crop_mask.size(0)):
                nonzero_indices = torch.nonzero(crop_mask[batch_index, 0, ...])
                height_min = nonzero_indices[:, 0].min()
                height_max = nonzero_indices[:, 0].max()
                width_min = nonzero_indices[:, 1].min()
                width_max = nonzero_indices[:, 1].max()
                crop_images.append(F.interpolate(
                    data[batch_index:batch_index + 1, :, height_min:height_max, width_min:width_max],
                    size=options.img_h, mode='bilinear'))
            crop_images = torch.cat(crop_images, dim=0)

            p_fine, _ = capsule_net(crop_images, target)
            p_fine_length = get_vector_length(p_fine)
            loss = capsule_loss(p_fine_length, target)
            preds_fine += [p_fine_length]
            test_loss[1] += loss

            p_dist = (p_coarse + p_fine) / 2
            p_dist_length = get_vector_length(p_dist)
            test_loss[2] += capsule_loss(p_dist_length, target)
            preds_dist += [p_dist_length]

        test_loss /= (batch_id + 1)
        # compute metrics
        metrics_coarse = compute_metrics(torch.cat(preds_coarse).cpu(), torch.cat(targets).cpu())
        metrics_fine = compute_metrics(torch.cat(preds_fine).cpu(), torch.cat(targets).cpu())
        metrics_dist = compute_metrics(torch.cat(preds_dist).cpu(), torch.cat(targets).cpu())

        plot_roc(metrics_coarse['fpr'], metrics_coarse['tpr'], metrics_coarse['auc'], save_dir+'/coarse.svg')
        plot_roc(metrics_fine['fpr'], metrics_fine['tpr'], metrics_fine['auc'], save_dir+'/fine.svg')
        plot_roc(metrics_dist['fpr'], metrics_dist['tpr'], metrics_dist['auc'], save_dir+'/distilled.svg')

        # display
        log_string(" - (Coarse-grained)     loss: {0:.4f}, acc: {1:.02%}, auc: {2:.02%}"
                   .format(test_loss[0], metrics_coarse['acc'], metrics_coarse['auc']))
        log_string(" - (Fine-grained)       loss: {0:.4f}, acc: {1:.02%}, auc: {2:.02%}"
                   .format(test_loss[1], metrics_fine['acc'], metrics_fine['auc']))
        log_string(" - (Distilled)          loss: {0:.4f}, acc: {1:.02%}, auc: {2:.02%}"
                   .format(test_loss[2], metrics_dist['acc'], metrics_dist['auc']))

        # compute the confusion matrix
        y_true = torch.cat(targets).cpu().numpy()
        y_pred = torch.cat(preds_dist).cpu().numpy().argmax(axis=1)
        conf_mat = confusion_matrix(y_true, y_pred)
        print(conf_mat)
        conf_mat = conf_mat.flatten()
        conf_mat_text = ''
        for val in conf_mat:
            conf_mat_text = conf_mat_text + str(val) + ', '
        log_string(conf_mat_text)