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 ID 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.
19 import torch
20 from torch import nn
21 from torch.optim import Adam
22 from torch.utils.data import DataLoader
23 from torchvision.datasets import MNIST, FashionMNIST
24 from torchvision.transforms import ToTensor
25
26 from pytorch_ood.loss import DeepSVDDLoss
27 from pytorch_ood.utils import OODMetrics, ToUnknown, fix_random_seed
28
29 fix_random_seed(1234)
30
31 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.
39 class Model(nn.Module):
40 """ """
41
42 def __init__(self):
43 super().__init__()
44 self.c1 = nn.Conv2d(1, 16, 3, padding=1, bias=False)
45 self.pool = nn.MaxPool2d(2)
46 self.c2 = nn.Conv2d(16, 32, 3, padding=1, bias=False)
47 self.c3 = nn.Conv2d(32, 64, 3, padding=1, bias=False)
48
49 self.layer5 = nn.Linear(576, 128, bias=False)
50 self.layer6 = nn.Linear(128, 2, bias=False)
51
52 def forward(self, x):
53 batch_size = x.shape[0]
54 x = self.c1(x).relu()
55 x = self.pool(x)
56 x = self.c2(x).relu()
57 x = self.pool(x)
58 x = self.c3(x).relu()
59 x = self.pool(x)
60 x = x.reshape(batch_size, -1)
61 x = self.layer5(x).relu()
62 x = self.layer6(x)
63 return x
Setup training and test data. Mark FashionMNIST as OOD with ToUnknown()
69 train_dataset = MNIST(root="data", download=True, train=True, transform=ToTensor())
70 train_loader = DataLoader(train_dataset, num_workers=5, batch_size=128, shuffle=True)
71
72 test_dataset_in = MNIST(root="data", download=True, train=False, transform=ToTensor())
73 test_dataset_out = FashionMNIST(
74 root="data",
75 download=True,
76 train=False,
77 transform=ToTensor(),
78 target_transform=ToUnknown(),
79 )
80
81 test_loader = DataLoader(
82 test_dataset_out + test_dataset_in, shuffle=False, num_workers=5, batch_size=256
83 )
Setup model, optimizer and training criterion (SVDD). Initialize the center of SVDD with the mean over the dataset
88 model = Model().to(device)
89 opti = Adam(model.parameters(), lr=0.001)
90
91 with torch.no_grad():
92 d = [model(x.to(device)) for x, y in train_loader]
93 center = torch.concat(d).mean(dim=0).cpu()
94
95 print(center)
96
97 criterion = DeepSVDDLoss(n_dim=2, center=center).to(device)
Define a function to the model and print some metrics
102 def test():
103 """ """
104 model.eval()
105 metrics = OODMetrics()
106
107 with torch.no_grad():
108 for x, y in test_loader:
109 z = model(x.to(device))
110 # calculate distance of points to the center in output space
111 distances = criterion.distance(z)
112 metrics.update(distances, y)
113
114 print(metrics.compute())
115 model.train()
Train model and test at the end of each epoch
120 for epoch in range(20):
121 print(f"Epoch {epoch}")
122 for x, _ in train_loader:
123 z = model(x.to(device))
124 # since this is a one-class method, we do not have to provide any class labels
125 loss = criterion(z)
126 opti.zero_grad()
127 loss.backward()
128 opti.step()
129
130 test()