| '''
|
| # author: Zhiyuan Yan
|
| # email: zhiyuanyan@link.cuhk.edu.cn
|
| # date: 2023-0706
|
| # description: Class for the CapsuleNetDetector
|
|
|
| Functions in the Class are summarized as:
|
| 1. __init__: Initialization
|
| 2. build_backbone: Backbone-building
|
| 3. build_loss: Loss-function-building
|
| 4. features: Feature-extraction
|
| 5. classifier: Classification
|
| 6. get_losses: Loss-computation
|
| 7. get_train_metrics: Training-metrics-computation
|
| 8. get_test_metrics: Testing-metrics-computation
|
| 9. forward: Forward-propagation
|
|
|
| Reference:
|
| @inproceedings{nguyen2019capsule,
|
| title={Capsule-forensics: Using capsule networks to detect forged images and videos},
|
| author={Nguyen, Huy H and Yamagishi, Junichi and Echizen, Isao},
|
| booktitle={ICASSP 2019-2019 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)},
|
| pages={2307--2311},
|
| year={2019},
|
| organization={IEEE}
|
| }
|
|
|
| GitHub Reference:
|
| https://github.com/niyunsheng/CORE
|
| '''
|
|
|
| import os
|
| import datetime
|
| import numpy as np
|
| from sklearn import metrics
|
| from typing import Union
|
| from collections import defaultdict
|
|
|
| import torch
|
| import torch.nn as nn
|
| import torch.nn.functional as F
|
| import torch.optim as optim
|
| from torch.nn import DataParallel
|
| from torch.utils.tensorboard import SummaryWriter
|
|
|
| from metrics.base_metrics_class import calculate_metrics_for_train
|
|
|
| from .base_detector import AbstractDetector
|
| from detectors import DETECTOR
|
| from networks import BACKBONE
|
| from loss import LOSSFUNC
|
|
|
| import torchvision.models as models
|
|
|
| @DETECTOR.register_module(module_name='capsule_net')
|
| class CapsuleNetDetector(AbstractDetector):
|
| def __init__(self, config):
|
| super().__init__()
|
| self.config = config
|
| self.backbone = self.build_backbone(config)
|
| self.loss_func = self.build_loss(config)
|
|
|
| self.num_classes = config['num_classes']
|
| self.vgg_ext = VggExtractor()
|
| self.fea_ext = FeatureExtractor()
|
| self.fea_ext.apply(self.weights_init)
|
|
|
| self.NO_CAPS = 10
|
| self.routing_stats = RoutingLayer(num_input_capsules=self.NO_CAPS, num_output_capsules= self.num_classes, data_in=8, data_out=4, num_iterations=2)
|
|
|
| def build_backbone(self, config):
|
| ...
|
|
|
| def build_loss(self, config):
|
|
|
| loss_class = LOSSFUNC[config['loss_func']]
|
| loss_func = loss_class()
|
| return loss_func
|
|
|
| def features(self, data_dict: dict) -> torch.tensor:
|
| input = self.vgg_ext(data_dict['image'])
|
| feature = self.fea_ext(input)
|
| return feature
|
|
|
| def classifier(self, features: torch.tensor) -> torch.tensor:
|
| z = self.routing_stats(features, random = False, dropout = 0.0)
|
|
|
| classes = F.softmax(z, dim=-1)
|
| class_ = classes.detach()
|
| class_ = class_.mean(dim=1)
|
| return classes, class_
|
|
|
| def get_losses(self, data_dict: dict, pred_dict: dict) -> dict:
|
| label = data_dict['label']
|
| pred = pred_dict['cls']
|
| classes = pred_dict['classes']
|
| loss = self.loss_func(classes, label)
|
| loss_dict = {'overall': loss}
|
| return loss_dict
|
|
|
| def get_train_metrics(self, data_dict: dict, pred_dict: dict) -> dict:
|
| label = data_dict['label']
|
| pred = pred_dict['cls']
|
|
|
| auc, eer, acc, ap = calculate_metrics_for_train(label.detach(), pred.detach())
|
| metric_batch_dict = {'acc': acc, 'auc': auc, 'eer': eer, 'ap': ap}
|
| return metric_batch_dict
|
|
|
| def forward(self, data_dict: dict, inference=False) -> dict:
|
|
|
| features = self.features(data_dict)
|
|
|
| preds, pred = self.classifier(features)
|
|
|
| prob = torch.softmax(pred, dim=1)[:, 1]
|
|
|
| pred_dict = {'cls': pred, 'prob': prob, 'feat': features, 'classes': preds}
|
| return pred_dict
|
|
|
| def weights_init(self, m):
|
| classname = m.__class__.__name__
|
| if classname.find('Conv') != -1:
|
| m.weight.data.normal_(0.0, 0.02)
|
| elif classname.find('BatchNorm') != -1:
|
| m.weight.data.normal_(1.0, 0.02)
|
| m.bias.data.fill_(0)
|
|
|
|
|
| class VggExtractor(nn.Module):
|
| def __init__(self, train=False):
|
| super(VggExtractor, self).__init__()
|
| self.vgg_1 = self.Vgg(models.vgg19(pretrained=True), 0, 18)
|
| if train:
|
| self.vgg_1.train(mode=True)
|
| self.freeze_gradient()
|
| else:
|
| self.vgg_1.eval()
|
|
|
| def Vgg(self, vgg, begin, end):
|
| features = nn.Sequential(*list(vgg.features.children())[begin:(end+1)])
|
| return features
|
|
|
| def freeze_gradient(self, begin=0, end=9):
|
| for i in range(begin, end+1):
|
| self.vgg_1[i].requires_grad = False
|
|
|
| def forward(self, input):
|
| return self.vgg_1(input)
|
|
|
| class FeatureExtractor(nn.Module):
|
| def __init__(self):
|
| super(FeatureExtractor, self).__init__()
|
| self.NO_CAPS = 10
|
| self.capsules = nn.ModuleList([
|
| nn.Sequential(
|
| nn.Conv2d(256, 64, kernel_size=3, stride=1, padding=1),
|
| nn.BatchNorm2d(64),
|
| nn.ReLU(),
|
| nn.Conv2d(64, 16, kernel_size=3, stride=1, padding=1),
|
| nn.BatchNorm2d(16),
|
| nn.ReLU(),
|
| StatsNet(),
|
|
|
| nn.Conv1d(2, 8, kernel_size=5, stride=2, padding=2),
|
| nn.BatchNorm1d(8),
|
| nn.Conv1d(8, 1, kernel_size=3, stride=1, padding=1),
|
| nn.BatchNorm1d(1),
|
| View(-1, 8),
|
| )
|
| for _ in range(self.NO_CAPS)]
|
| )
|
|
|
| def squash(self, tensor, dim):
|
| squared_norm = (tensor ** 2).sum(dim=dim, keepdim=True)
|
| scale = squared_norm / (1 + squared_norm)
|
| return scale * tensor / (torch.sqrt(squared_norm))
|
|
|
| def forward(self, x):
|
|
|
|
|
| outputs = [capsule(x) for capsule in self.capsules]
|
| output = torch.stack(outputs, dim=-1)
|
|
|
| return self.squash(output, dim=-1)
|
|
|
| class StatsNet(nn.Module):
|
| def __init__(self):
|
| super(StatsNet, self).__init__()
|
|
|
| def forward(self, x):
|
| x = x.view(x.data.shape[0], x.data.shape[1], x.data.shape[2]*x.data.shape[3])
|
|
|
| mean = torch.mean(x, 2)
|
| std = torch.std(x, 2)
|
|
|
| return torch.stack((mean, std), dim=1)
|
|
|
| class View(nn.Module):
|
| def __init__(self, *shape):
|
| super(View, self).__init__()
|
| self.shape = shape
|
|
|
| def forward(self, input):
|
| return input.view(self.shape)
|
|
|
|
|
| class RoutingLayer(nn.Module):
|
| def __init__(self, num_input_capsules, num_output_capsules, data_in, data_out, num_iterations):
|
| super(RoutingLayer, self).__init__()
|
|
|
| self.num_iterations = num_iterations
|
| self.route_weights = nn.Parameter(torch.randn(num_output_capsules, num_input_capsules, data_out, data_in))
|
|
|
|
|
| def squash(self, tensor, dim):
|
| squared_norm = (tensor ** 2).sum(dim=dim, keepdim=True)
|
| scale = squared_norm / (1 + squared_norm)
|
| return scale * tensor / (torch.sqrt(squared_norm))
|
|
|
| def forward(self, x, random, dropout):
|
|
|
|
|
| x = x.transpose(2, 1)
|
|
|
|
|
| if random:
|
|
|
| noise = torch.Tensor(0.01*torch.randn(*self.route_weights.size())).cuda()
|
| route_weights = self.route_weights + noise
|
| else:
|
| route_weights = self.route_weights
|
|
|
| priors = route_weights[:, None, :, :, :] @ x[None, :, :, :, None]
|
|
|
|
|
|
|
|
|
|
|
| priors = priors.transpose(1, 0)
|
|
|
|
|
| if dropout > 0.0:
|
|
|
| drop = torch.Tensor(torch.FloatTensor(*priors.size()).bernoulli(1.0- dropout)).cuda()
|
| priors = priors * drop
|
|
|
|
|
|
|
| logits = torch.Tensor(torch.zeros(*priors.size())).to(priors.device)
|
|
|
|
|
| num_iterations = self.num_iterations
|
|
|
| for i in range(num_iterations):
|
| probs = F.softmax(logits, dim=2)
|
| outputs = self.squash((probs * priors).sum(dim=2, keepdim=True), dim=3)
|
|
|
| if i != self.num_iterations - 1:
|
| delta_logits = priors * outputs
|
| logits = logits + delta_logits
|
|
|
|
|
| outputs = outputs.squeeze()
|
|
|
| if len(outputs.shape) == 3:
|
| outputs = outputs.transpose(2, 1).contiguous()
|
| else:
|
| outputs = outputs.unsqueeze_(dim=0).transpose(2, 1).contiguous()
|
|
|
|
|
| return outputs
|
|
|