GitHub

@@ -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")

Read the original on github.com ↗