Skip to content

Commit a2d2ce0

Browse files
committed
Initial commit
0 parents  commit a2d2ce0

File tree

5 files changed

+126858
-0
lines changed

5 files changed

+126858
-0
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Deep Convolutional Generative Adversarial Networks
2+
3+
[PyTorch DCGAN Tutorial](https://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html)의 Tensorflow implementation 및 한국어 번역.
4+
5+
데이터셋은 Celeb-A Faces dataset 사용.
6+
7+
## Usage
8+
9+
```plaintext
10+
usage: train.py [-h] [--dataroot DATAROOT] [--workers WORKERS]
11+
[--batch-size BATCH_SIZE] [--image-size IMAGE_SIZE] [--nz NZ]
12+
[--ngf NGF] [--ndf NDF] [--niter NITER] [--lr LR]
13+
[--beta1 BETA1] [--dry-run] [--ngpu NGPU] [--outf OUTF]
14+
[--log-dir LOG_DIR] [--ckpt-dir CKPT_DIR]
15+
[--manual-seed MANUAL_SEED]
16+
17+
optional arguments:
18+
-h, --help show this help message and exit
19+
--dataroot DATAROOT path to dataset (default: data/celeba)
20+
--workers WORKERS number of data loading workers (default: 2)
21+
--batch-size BATCH_SIZE
22+
input batch size (default: 128)
23+
--image-size IMAGE_SIZE
24+
the height / width of the input image to network
25+
(default: 64)
26+
--nz NZ size of the latent z vector (default: 100)
27+
--ngf NGF
28+
--ndf NDF
29+
--niter NITER number of epochs to train for (default: 25)
30+
--lr LR learning rate (default: 0.0002)
31+
--beta1 BETA1 beta1 for adam (default: 0.5)
32+
--dry-run check a single training cycle works (default: False)
33+
--ngpu NGPU number of GPUs to use (default: 1)
34+
--outf OUTF folder to output images (default: samples)
35+
--log-dir LOG_DIR log folder to save training progresses (default: logs)
36+
--ckpt-dir CKPT_DIR checkpoint folder to save model checkpoints (default:
37+
ckpt)
38+
--manual-seed MANUAL_SEED
39+
manual seed (default: None)
40+
41+
```

dcgan_tutorial.ipynb

Lines changed: 126522 additions & 0 deletions
Large diffs are not rendered by default.

images/dcgan_generator.png

83.4 KB
Loading

train.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import random
2+
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
3+
from datetime import datetime
4+
from pathlib import Path
5+
6+
import tensorflow as tf
7+
from tensorflow.keras import Input, Sequential
8+
from tensorflow.keras.initializers import RandomNormal
9+
from tensorflow.keras.layers import BatchNormalization, Conv2D, Conv2DTranspose, Reshape
10+
from tensorflow.keras.layers import Activation, LeakyReLU, ReLU
11+
from tensorflow.keras.losses import BinaryCrossentropy, Reduction
12+
from tensorflow.keras.optimizers import Adam
13+
from tensorflow.keras.preprocessing.image import array_to_img
14+
15+
import vutils
16+
17+
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
18+
parser.add_argument('--dataroot', default='data/celeba', type=Path, help='path to dataset')
19+
parser.add_argument('--workers', default=2, type=int, help='number of data loading workers')
20+
parser.add_argument('--batch-size', default=128, type=int, help='input batch size')
21+
parser.add_argument('--image-size', default=64, type=int, help='the height / width of the input image to network')
22+
parser.add_argument('--nz', default=100, type=int, help='size of the latent z vector')
23+
parser.add_argument('--ngf', default=64, type=int)
24+
parser.add_argument('--ndf', default=64, type=int)
25+
parser.add_argument('--niter', default=25, type=int, help='number of epochs to train for')
26+
parser.add_argument('--lr', default=0.0002, type=float, help='learning rate')
27+
parser.add_argument('--beta1', default=0.5, type=float, help='beta1 for adam')
28+
parser.add_argument('--dry-run', action='store_true', help='check a single training cycle works')
29+
parser.add_argument('--ngpu', default=1, type=int, help='number of GPUs to use')
30+
parser.add_argument('--outf', default='samples', type=Path, help='folder to output images')
31+
parser.add_argument('--log-dir', default='logs', type=Path, help='log folder to save training progresses')
32+
parser.add_argument('--ckpt-dir', default='ckpt', help='checkpoint folder to save model checkpoints')
33+
parser.add_argument('--manual-seed', type=int, help='manual seed')
34+
35+
opt = parser.parse_args()
36+
37+
if not opt.manual_seed:
38+
opt.manual_seed = random.randint(1, 10000)
39+
print(f'Random Seed: {opt.manual_seed}')
40+
random.seed(opt.manual_seed)
41+
tf.random.set_seed(opt.manual_seed)
42+
43+
if opt.ngpu <= 0:
44+
strategy = tf.distribute.OneDeviceStrategy(device='/cpu:0')
45+
elif opt.ngpu == 1:
46+
strategy = tf.distribute.OneDeviceStrategy(device='/gpu:0')
47+
else:
48+
strategy = tf.distribute.MirroredStrategy(devices=[f'/gpu:{i}' for i in range(opt.ngpu)])
49+
print(f'Device type: {"CPU" if opt.ngpu <= 0 else "GPU"}')
50+
print(f'Number of devices: {strategy.num_replicas_in_sync}')
51+
52+
# Number of channels in the training images. For color images this is 3
53+
nc = 3
54+
55+
56+
def parse_image(filename):
57+
image = tf.io.read_file(filename)
58+
image = tf.image.decode_jpeg(image)
59+
image = tf.image.convert_image_dtype(image, tf.float32)
60+
image = 2 * image - 1
61+
shape = tf.shape(image)
62+
# center cropping
63+
h, w = shape[-3], shape[-2]
64+
if h > w:
65+
cropped_image = tf.image.crop_to_bounding_box(image, (h - w) // 2, 0, w, w)
66+
else:
67+
cropped_image = tf.image.crop_to_bounding_box(image, 0, (w - h) // 2, h, h)
68+
69+
image = tf.image.resize(cropped_image, [opt.image_size, opt.image_size])
70+
return image
71+
72+
73+
# Create the dataset
74+
dataset = tf.data.Dataset.list_files(str(opt.dataroot/'*/*')) \
75+
.map(parse_image, num_parallel_calls=opt.workers) \
76+
.batch(opt.batch_size)
77+
dataset_dist = strategy.experimental_distribute_dataset(dataset)
78+
79+
with strategy.scope():
80+
# Custom weights initialization called on netG and netD
81+
initializer = RandomNormal(0, 0.02)
82+
83+
netG = Sequential([
84+
Input(shape=(1, 1, opt.nz)),
85+
# input is Z, going into a convolution
86+
Conv2DTranspose(opt.ngf * 8, 4, 1, 'valid', use_bias=False, kernel_initializer=initializer),
87+
BatchNormalization(),
88+
ReLU(),
89+
# state size. 4 x 4 x (ngf*16)
90+
Conv2DTranspose(opt.ngf * 8, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
91+
BatchNormalization(),
92+
ReLU(),
93+
# state size. 8 x 8 x (ngf*8)
94+
Conv2DTranspose(opt.ngf * 4, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
95+
BatchNormalization(),
96+
ReLU(),
97+
# state size. 16 x 16 x (ngf*2)
98+
Conv2DTranspose(opt.ngf * 2, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
99+
BatchNormalization(),
100+
ReLU(),
101+
# state size. 32 x 32 x (ngf)
102+
Conv2DTranspose(nc, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
103+
Activation(tf.nn.tanh, name='tanh')
104+
# state size. 64 x 64 x (nc)
105+
], name='generator')
106+
netG.summary()
107+
108+
netD = Sequential([
109+
Input(shape=(opt.image_size, opt.image_size, nc)),
110+
# input is 64 x 64 x (nc)
111+
Conv2D(opt.ndf, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
112+
LeakyReLU(0.2),
113+
# state size. 32 x 32 x (ndf)
114+
Conv2D(opt.ndf * 2, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
115+
BatchNormalization(),
116+
LeakyReLU(0.2),
117+
# state size. 16 x 16 x (ndf*2)
118+
Conv2D(opt.ndf * 4, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
119+
BatchNormalization(),
120+
LeakyReLU(0.2),
121+
# state size. 8 x 8 x (ndf*4)
122+
Conv2D(opt.ndf * 8, 4, 2, 'same', use_bias=False, kernel_initializer=initializer),
123+
BatchNormalization(),
124+
LeakyReLU(0.2),
125+
# state size. 4 x 4 x (ndf*8)
126+
Conv2D(1, 4, 1, 'valid', use_bias=False, kernel_initializer=initializer),
127+
# state size. 1 x 1 x 1
128+
Reshape((1,))
129+
], name='discriminator')
130+
netD.summary()
131+
132+
# Initialize BCELoss function
133+
criterion = BinaryCrossentropy(from_logits=True, reduction=Reduction.NONE)
134+
135+
136+
def compute_loss(y_true, y_pred):
137+
per_example_loss = criterion(y_true, y_pred)
138+
return tf.nn.compute_average_loss(per_example_loss, global_batch_size=opt.batch_size)
139+
140+
141+
# Setup Adam optimizers for both G and D
142+
optimizerD = Adam(learning_rate=opt.lr, beta_1=opt.beta1)
143+
optimizerG = Adam(learning_rate=opt.lr, beta_1=opt.beta1)
144+
145+
# Setup model checkpoint
146+
ckpt = tf.train.Checkpoint(epoch=tf.Variable(1, trainable=False, name='epoch'),
147+
step=tf.Variable(0, trainable=False, name='step'),
148+
optimizerD=optimizerD,
149+
optimizerG=optimizerG,
150+
netD=netD,
151+
netG=netG)
152+
ckpt_manager = tf.train.CheckpointManager(ckpt, opt.ckpt_dir, max_to_keep=None)
153+
ckpt_manager.restore_or_initialize()
154+
if ckpt_manager.latest_checkpoint:
155+
print(f'Restored from {ckpt_manager.latest_checkpoint}')
156+
else:
157+
print('Initializing from scratch.')
158+
159+
# Create batch of latent vectors that we will use to visualize
160+
# the progression of the generator
161+
fixed_noise = tf.random.normal([64, 1, 1, opt.nz])
162+
163+
if opt.dry_run:
164+
opt.niter = 1
165+
166+
# Set up a log directory
167+
file_writer = tf.summary.create_file_writer(str(opt.log_dir/datetime.now().strftime('%Y%m%d-%H%M%S')))
168+
169+
# Set up a sample output directory
170+
opt.outf.mkdir(parents=True, exist_ok=True)
171+
172+
173+
def train_step(data):
174+
############################
175+
# (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
176+
###########################
177+
## Train with all-real batch
178+
# Format batch
179+
batch_size = data.shape[0]
180+
# Generate batch of latent vectors
181+
noise = tf.random.normal([batch_size, 1, 1, opt.nz])
182+
# Generate fake image batch with G
183+
fake = netG(noise, training=True)
184+
with tf.GradientTape(persistent=True) as tape:
185+
# Forward pass real batch through D
186+
real_output = netD(data, training=True)
187+
# Calculate loss on all-real batch
188+
errD_real = compute_loss(tf.ones_like(real_output), real_output)
189+
# Classify all fake batch with D
190+
fake_output = netD(fake, training=True)
191+
# Calculate D's loss on the all-fake batch
192+
errD_fake = compute_loss(tf.zeros_like(fake_output), fake_output)
193+
# Calculate gradients for D in backward pass
194+
gradients_real = tape.gradient(errD_real, netD.trainable_variables)
195+
gradients_fake = tape.gradient(errD_fake, netD.trainable_variables)
196+
# Add the gradients from the all-real and all-fake batches
197+
accumulated_gradients = [g1 + g2 for g1, g2 in zip(gradients_real, gradients_fake)]
198+
# Update D
199+
optimizerD.apply_gradients(zip(accumulated_gradients, netD.trainable_variables))
200+
D_x = tf.math.reduce_mean(tf.math.sigmoid(real_output))
201+
D_G_z1 = tf.math.reduce_mean(tf.math.sigmoid(fake_output))
202+
errD = errD_real + errD_fake
203+
204+
############################
205+
# (2) Update G network: maximize log(D(G(z)))
206+
###########################
207+
with tf.GradientTape() as tape:
208+
# Since we just updated D, perform another forward pass of all-fake batch through D
209+
fake = netG(noise, training=True)
210+
fake_output = netD(fake, training=True)
211+
# Calculate G's loss based on this output
212+
# fake labels are real for generator cost
213+
errG = compute_loss(tf.ones_like(fake_output), fake_output)
214+
# Calculate gradients for G
215+
gradients = tape.gradient(errG, netG.trainable_variables)
216+
# Update G
217+
optimizerG.apply_gradients(zip(gradients, netG.trainable_variables))
218+
D_G_z2 = tf.math.reduce_mean(tf.math.sigmoid(fake_output))
219+
220+
return errD, errG, D_x, D_G_z1, D_G_z2
221+
222+
223+
@tf.function
224+
def distributed_train_step(dist_inputs):
225+
errD, errG, D_x, D_G_z1, D_G_z2 = strategy.run(train_step, args=(dist_inputs,))
226+
return strategy.reduce(tf.distribute.ReduceOp.SUM, errD, axis=None), \
227+
strategy.reduce(tf.distribute.ReduceOp.SUM, errG, axis=None), \
228+
strategy.reduce(tf.distribute.ReduceOp.MEAN, D_x, axis=None), \
229+
strategy.reduce(tf.distribute.ReduceOp.MEAN, D_G_z1, axis=None), \
230+
strategy.reduce(tf.distribute.ReduceOp.MEAN, D_G_z2, axis=None)
231+
232+
233+
for epoch in range(int(ckpt.epoch.numpy()), opt.niter + 1):
234+
for i, data in enumerate(dataset_dist):
235+
errD, errG, D_x, D_G_z1, D_G_z2 = distributed_train_step(data)
236+
# Output training stats
237+
if i % 50 == 0:
238+
print(f'[{epoch}/{opt.niter}][{i}/{len(dataset)}]\t'
239+
f'Loss_D: {errD:.4f}\t'
240+
f'Loss_G: {errG:.4f}\t'
241+
f'D(x): {D_x:.4f}\t'
242+
f'D(G(z)): {D_G_z1:.4f} / {D_G_z2:.4f}')
243+
if opt.dry_run:
244+
break
245+
# Log training stats
246+
ckpt.step.assign_add(1)
247+
step = int(ckpt.step.numpy())
248+
with file_writer.as_default():
249+
tf.summary.scalar('errD', errD, step=step)
250+
tf.summary.scalar('errG', errG, step=step)
251+
tf.summary.scalar('D_x', D_x, step=step)
252+
tf.summary.scalar('D_G_z1', D_G_z1, step=step)
253+
tf.summary.scalar('D_G_z2', D_G_z2, step=step)
254+
if opt.dry_run:
255+
break
256+
# Check how the generator is doing by saving G's output on fixed_noise
257+
fake = netG(fixed_noise, training=False)
258+
# Scale it back to [0, 1]
259+
fake = (fake + 1) / 2
260+
img_grid = vutils.make_grid(fake)
261+
with file_writer.as_default():
262+
tf.summary.image('Generated images', img_grid[tf.newaxis, ...], step=epoch)
263+
img = array_to_img(img_grid * 255, scale=False)
264+
img.save(opt.outf/f'fake_samples_epoch_{epoch:03d}.png')
265+
266+
save_path = ckpt_manager.save()
267+
print(f'Saved checkpoint at epoch {epoch}: {save_path}')
268+
ckpt.epoch.assign_add(1)

vutils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import math
2+
3+
import tensorflow as tf
4+
5+
6+
def make_grid(tensor,
7+
nrow: int = 8,
8+
padding: int = 2,
9+
pad_value: int = 0):
10+
# make the mini-batch of images into a grid
11+
nmaps = tensor.shape[0]
12+
xmaps = min(nrow, nmaps)
13+
ymaps = int(math.ceil(float(nmaps) / xmaps))
14+
height, width = int(tensor.shape[1] + padding), int(tensor.shape[2] + padding)
15+
num_channels = tensor.shape[3]
16+
grid = tf.fill([height * ymaps + padding, width * xmaps + padding, num_channels],
17+
tf.constant(pad_value, tensor.dtype))
18+
grid = tf.Variable(grid)
19+
k = 0
20+
for y in range(ymaps):
21+
for x in range(xmaps):
22+
if k >= nmaps:
23+
break
24+
placeholder = grid[y * height + padding:(y + 1) * height, x * width + padding:(x + 1) * width]
25+
placeholder.assign(tf.identity(tensor[k]))
26+
k += 1
27+
return grid.read_value()

0 commit comments

Comments
 (0)