Deep One-Class Learning

We train a One-Class model (that is, a model that does not need class labels) on MNIST, using Deep SVDD. SVDD places a single center \(\mu\) in the output space of a model \(f_{\theta}\). During training, the parameters \(\theta\) are adjusted to minimize the (squared) sum of the distances of representations \(f_{\theta}(x)\) to this center. Thus, the model is trained to map the training samples close to the center. The idea is that the model learns to map only IN samples close to the center, and not OOD samples. The distance to the center can be used as outlier score.

We test the model against FashionMNIST.

First, some imports etc.

18 import torch
19 from torch import nn
20 from torch.optim import Adam
21 from torch.utils.data import DataLoader
22 from torchvision.datasets import MNIST, FashionMNIST
23 from torchvision.transforms import ToTensor
24
25 from pytorch_ood.loss import DeepSVDDLoss
26 from pytorch_ood.utils import OODMetrics, ToUnknown, fix_random_seed
27
28 fix_random_seed(1234)
29
30 device = "cuda:0"

Next, we define a simple model. As described in the original paper of Deep SVDD, this model must not use biases in linear layers or convolutions, since this would lead to a trivial solution for the optimization problem.

38 class Model(nn.Module):
39     """ """
40
41     def __init__(self):
42         super().__init__()
43         self.c1 = nn.Conv2d(1, 16, 3, padding=1, bias=False)
44         self.pool = nn.MaxPool2d(2)
45         self.c2 = nn.Conv2d(16, 32, 3, padding=1, bias=False)
46         self.c3 = nn.Conv2d(32, 64, 3, padding=1, bias=False)
47
48         self.layer5 = nn.Linear(576, 128, bias=False)
49         self.layer6 = nn.Linear(128, 2, bias=False)
50
51     def forward(self, x):
52         batch_size = x.shape[0]
53         x = self.c1(x).relu()
54         x = self.pool(x)
55         x = self.c2(x).relu()
56         x = self.pool(x)
57         x = self.c3(x).relu()
58         x = self.pool(x)
59         x = x.reshape(batch_size, -1)
60         x = self.layer5(x).relu()
61         x = self.layer6(x)
62         return x

Setup training and test data. Mark FashionMNIST as OOD with ToUnknown()

68 train_dataset = MNIST(root="data", download=True, train=True, transform=ToTensor())
69 train_loader = DataLoader(train_dataset, num_workers=5, batch_size=128, shuffle=True)
70
71 test_dataset_in = MNIST(root="data", download=True, train=False, transform=ToTensor())
72 test_dataset_out = FashionMNIST(
73     root="data",
74     download=True,
75     train=False,
76     transform=ToTensor(),
77     target_transform=ToUnknown(),
78 )
79
80 test_loader = DataLoader(
81     test_dataset_out + test_dataset_in, shuffle=False, num_workers=5, batch_size=256
82 )

Setup model, optimizer and training criterion (SVDD). Initialize the center of SVDD with the mean over the dataset

87 model = Model().to(device)
88 opti = Adam(model.parameters(), lr=0.001)
89
90 with torch.no_grad():
91     d = [model(x.to(device)) for x, y in train_loader]
92     center = torch.concat(d).mean(dim=0).cpu()
93
94 print(center)
95
96 criterion = DeepSVDDLoss(n_dim=2, center=center).to(device)

Define a function to the model and print some metrics

101 def test():
102     """ """
103     model.eval()
104     metrics = OODMetrics()
105
106     with torch.no_grad():
107         for x, y in test_loader:
108             z = model(x.to(device))
109             # calculate distance of points to the center in output space
110             distances = criterion.distance(z)
111             metrics.update(distances, y)
112
113     print(metrics.compute())
114     model.train()

Train model and test at the end of each epoch

119 for epoch in range(20):
120     print(f"Epoch {epoch}")
121     for x, _ in train_loader:
122         z = model(x.to(device))
123         # since this is a one-class method, we do not have to provide any class labels
124         loss = criterion(z)
125         opti.zero_grad()
126         loss.backward()
127         opti.step()
128
129     test()

Gallery generated by Sphinx-Gallery