Note
Go to the end to download the full example code.
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()