@@ -0,0 +1,253 @@
1+import matplotlib.pyplot as plt
2+3+from collections import OrderedDict
4+5+import torch
6+import torchvision
7+from torch import nn, optim, autograd
8+from torchvision import utils
9+10+import pytorch_lightning as pl
11+12+13+class WGANGenerator(nn.Module):
14+def __init__(self, latent_dim, img_size, img_channels=3, n_filters=16, n_blocks=3):
15+super().__init__()
16+17+self.n_filters = n_filters
18+self.init_size = img_size // (2**n_blocks)
19+20+self.l1 = nn.Sequential(
21+nn.Linear(latent_dim, n_filters * self.init_size * self.init_size)
22+ )
23+24+def block(in_filters, out_filters=None):
25+if out_filters is None:
26+out_filters = 2*in_filters
27+28+return [
29+nn.BatchNorm2d(in_filters),
30+nn.ConvTranspose2d(in_filters, out_filters, kernel_size=3, stride=2, padding=1, output_padding=1),
31+ ]
32+33+convs = []
34+for i in range(n_blocks-1):
35+convs.extend(block((2**i) * n_filters))
36+37+self.conv_blocks = nn.Sequential(
38+*convs,
39+*block((2**(n_blocks-1)) * n_filters, img_channels),
40+nn.Tanh(),
41+ )
42+43+def forward(self, z):
44+"""
45+ Takes a (batch_size, latent_dim) tensor of noise
46+ Returns a (batch_size, img_channels, img_size, img_size) generated images
47+ """
48+out = self.l1(z)
49+out = out.view(out.size(0), self.n_filters, self.init_size, self.init_size)
50+return self.conv_blocks(out)
51+52+53+class WGANCritic(nn.Module):
54+def __init__(self, img_size, img_channels=3, n_filters=16, n_blocks=3):
55+super().__init__()
56+57+def block(in_filters, out_filters=None, normalise=True):
58+if out_filters is None:
59+out_filters = in_filters*2
60+61+block = [
62+nn.Conv2d(in_filters, out_filters, kernel_size=3, stride=2, padding=1),
63+nn.LeakyReLU(0.2, inplace=True),
64+nn.Dropout2d(0.25),
65+ ]
66+67+if normalise:
68+block.append(nn.BatchNorm2d(out_filters, 0.8))
69+70+return block
71+72+convs = []
73+for i in range(n_blocks-1):
74+convs.extend(block((2**i) * n_filters))
75+76+self.conv_blocks = nn.Sequential(
77+*block(img_channels, n_filters, normalise=False),
78+*convs,
79+ )
80+81+ds_size = img_size // 2 ** (n_blocks)
82+final_filters = (2**(n_blocks-1)) * n_filters
83+84+self.adv_layer = nn.Sequential(
85+nn.Linear(final_filters * ds_size * ds_size, 1),
86+ )
87+88+def forward(self, img):
89+"""
90+ Takes a (batch_size, img_channels, img_size, img_size) generated or real images
91+ Returns a (batch_size, 1) tensor of probabilities that the input is real
92+ """
93+out = self.conv_blocks(img)
94+out = out.view(out.size(0), -1)
95+96+return self.adv_layer(out)
97+98+99+class WGAN(pl.LightningModule):
100+def __init__(self,
101+latent_dim,
102+img_size,
103+args,
104+output_img_path=None,
105+img_channels=3):
106+107+super().__init__()
108+109+self.latent_dim = latent_dim
110+self.img_size = img_size
111+self.lr = args.learning_rate
112+self.c = args.weight_clip_thres
113+self.gp_lambda = args.gp_lambda
114+self.output_img_path = output_img_path
115+self.use_gp = args.use_gp
116+117+self.g = WGANGenerator(
118+latent_dim=latent_dim,
119+img_size=img_size,
120+img_channels=img_channels,
121+n_filters=args.n_filters,
122+n_blocks=args.n_blocks,
123+ )
124+125+self.d = WGANCritic(
126+img_size=img_size,
127+img_channels=img_channels,
128+n_filters=args.n_filters,
129+n_blocks=args.n_blocks,
130+ )
131+132+self.output_z = torch.randn(16, self.latent_dim)
133+self.epoch_n = 0
134+135+def forward(self, x):
136+return self.g(x)
137+138+def gradient_penalty(self, real_img, fake_img):
139+# Create random uniformly distributed weights
140+eta = torch.zeros((real_img.size(0), 1, 1, 1)).type_as(real_img)
141+eta.uniform_()
142+143+# Create interpolated image
144+interpolated_img = eta*real_img + (1-eta)*fake_img.detach()
145+interpolated_img.requires_grad_(True)
146+147+# Calculate the score of the interpolated image
148+interpolated_score = self.d(interpolated_img)
149+150+# Calculate the gradients of the score
151+grads = autograd.grad(
152+outputs=interpolated_score,
153+inputs=interpolated_img,
154+grad_outputs=torch.ones(real_img.size(0), 1).type_as(real_img),
155+create_graph=True,
156+retain_graph=True
157+ )[0]
158+159+# Return the norm of the gradients times lambda
160+return ((grads.norm(2, dim=1) - 1) ** 2).mean() * self.gp_lambda
161+162+def training_step(self, batch, batch_idx, optimizer_idx):
163+x, y = batch
164+batch_size = x.size(0)
165+166+# Generate comparison images at the start of each epoch using real images
167+if batch_idx == 0 and optimizer_idx == 0:
168+self.plot_figs(x[0:16, :, :, :].detach())
169+170+# Sample some noise
171+z = torch.randn(batch_size, self.latent_dim)
172+z = z.type_as(x)
173+174+# Generate a fake image using the noise
175+fake_img = self.g(z)
176+177+# Train the generator
178+if optimizer_idx == 0:
179+# Compute the loss using the fake images and the labels
180+fake_score = self.d(fake_img)
181+182+# We want the score of the fake image to be as large as possible
183+g_loss = -fake_score.mean()
184+185+tqdm_dict = {"g_loss": g_loss}
186+output = OrderedDict(
187+ {"loss": g_loss, "progress_bar": tqdm_dict, "log": tqdm_dict}
188+ )
189+190+return output
191+192+# Train the critic
193+elif optimizer_idx == 1:
194+if not self.use_gp:
195+# Clip weights in the critic to [-c, c]
196+for p in self.d.parameters():
197+p.data.clamp(-self.c, self.c)
198+199+# Compute the critic score on both the real and fake data
200+fake_score = self.d(fake_img.detach())
201+real_score = self.d(x)
202+203+# Loss is equal to EM distance + gradient penalty (if enabled)
204+if self.use_gp:
205+d_loss = fake_score.mean() - real_score.mean() + self.gradient_penalty(x, fake_img)
206+else:
207+d_loss = fake_score.mean() - real_score.mean()
208+209+tqdm_dict = {
210+"d_loss": d_loss,
211+"real_score": real_score.mean(),
212+"fake_score": fake_score.mean(),
213+ }
214+215+output = OrderedDict(
216+ {"loss": d_loss, "progress_bar": tqdm_dict, "log": tqdm_dict}
217+ )
218+219+return output
220+221+def configure_optimizers(self):
222+opt_g = optim.RMSprop(self.g.parameters(), lr=self.lr)
223+opt_d = optim.RMSprop(self.d.parameters(), lr=self.lr)
224+return [opt_g, opt_d], []
225+226+def optimizer_step(self, current_epoch, batch_idx, optimizer, optimizer_idx, second_order_closure):
227+# Update the generator every 3 steps
228+if optimizer_idx == 0:
229+if batch_idx % 3 == 0:
230+optimizer.step()
231+optimizer.zero_grad()
232+233+# Update the critic every step
234+if optimizer_idx == 1:
235+optimizer.step()
236+optimizer.zero_grad()
237+238+def plot_figs(self, real_imgs):
239+"""
240+ A simple helper function to log generated images throughout training
241+ """
242+243+self.epoch_n += 1
244+245+gen_imgs = self(self.output_z.type_as(real_imgs))
246+247+grid = torchvision.utils.make_grid(0.5*real_imgs + 0.5, nrow=4)
248+self.logger.experiment.add_image("real_imgs", grid, self.epoch_n)
249+torchvision.utils.save_image(grid, self.output_img_path / f"real_imgs_{self.epoch_n}.png")
250+251+grid = torchvision.utils.make_grid(0.5*gen_imgs + 0.5, nrow=4)
252+self.logger.experiment.add_image("gen_imgs", grid, self.epoch_n)
253+torchvision.utils.save_image(grid, self.output_img_path / f"gen_imgs_{self.epoch_n}.png")