17.2. Mạng đối thủ tạo phức tạp sâu¶
Trong Section 17.1, chúng tôi đã giới thiệu những ý tưởng cơ bản đằng sau cách GAN hoạt động. Chúng tôi đã chỉ ra rằng chúng có thể vẽ các mẫu từ một số phân phối đơn giản, dễ lấy mẫu, như phân phối thống nhất hoặc bình thường, và biến đổi chúng thành các mẫu dường như phù hợp với sự phân bố của một số tập dữ liệu. Và trong khi ví dụ của chúng tôi về việc phù hợp với phân phối 2D Gaussian đã vượt qua điểm, nó không đặc biệt thú vị.
Trong phần này, chúng tôi sẽ chứng minh cách bạn có thể sử dụng GAN để tạo ra hình ảnh thực tế. Chúng tôi sẽ dựa trên các mô hình của chúng tôi trên GAN phức tạp sâu (DCGAN) được giới thiệu trong [Radford et al., 2015]. Chúng tôi sẽ mượn kiến trúc phức tạp đã chứng minh thành công cho các vấn đề thị giác máy tính phân biệt đối xử và cho thấy cách thông qua GAN, chúng có thể được tận dụng để tạo ra hình ảnh thực tế.
from mxnet import gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
npx.set_np()
import warnings
import torch
import torchvision
from torch import nn
from d2l import torch as d2l
import tensorflow as tf
from d2l import tensorflow as d2l
17.2.1. Các Pokemon Dataset¶
Tập dữ liệu chúng tôi sẽ sử dụng là một bộ sưu tập các sprites Pokemon thu được từ pokemondb. Đầu tiên tải xuống, trích xuất và tải tập dữ liệu này.
#@save
d2l.DATA_HUB['pokemon'] = (d2l.DATA_URL + 'pokemon.zip',
'c065c0e2593b8b161a2d7873e42418bf6a21106c')
data_dir = d2l.download_extract('pokemon')
pokemon = gluon.data.vision.datasets.ImageFolderDataset(data_dir)
Downloading ../data/pokemon.zip from http://d2l-data.s3-accelerate.amazonaws.com/pokemon.zip...
#@save
d2l.DATA_HUB['pokemon'] = (d2l.DATA_URL + 'pokemon.zip',
'c065c0e2593b8b161a2d7873e42418bf6a21106c')
data_dir = d2l.download_extract('pokemon')
pokemon = torchvision.datasets.ImageFolder(data_dir)
Downloading ../data/pokemon.zip from http://d2l-data.s3-accelerate.amazonaws.com/pokemon.zip...
#@save
d2l.DATA_HUB['pokemon'] = (d2l.DATA_URL + 'pokemon.zip',
'c065c0e2593b8b161a2d7873e42418bf6a21106c')
data_dir = d2l.download_extract('pokemon')
batch_size = 256
pokemon = tf.keras.preprocessing.image_dataset_from_directory(
data_dir, batch_size=batch_size, image_size=(64, 64))
Downloading ../data/pokemon.zip from http://d2l-data.s3-accelerate.amazonaws.com/pokemon.zip...
Found 40597 files belonging to 721 classes.
Chúng tôi thay đổi kích thước mỗi hình ảnh thành \(64\times 64\).
Việc chuyển đổi ToTensor
sẽ chiếu giá trị pixel thành
\([0, 1]\), trong khi trình tạo của chúng tôi sẽ sử dụng hàm tánh để
có được đầu ra trong \([-1, 1]\). Do đó, chúng tôi bình thường hóa
dữ liệu với \(0.5\) trung bình và độ lệch chuẩn \(0.5\) để phù
hợp với phạm vi giá trị.
batch_size = 256
transformer = gluon.data.vision.transforms.Compose([
gluon.data.vision.transforms.Resize(64),
gluon.data.vision.transforms.ToTensor(),
gluon.data.vision.transforms.Normalize(0.5, 0.5)
])
data_iter = gluon.data.DataLoader(
pokemon.transform_first(transformer), batch_size=batch_size,
shuffle=True, num_workers=d2l.get_dataloader_workers())
batch_size = 256
transformer = torchvision.transforms.Compose([
torchvision.transforms.Resize((64, 64)),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(0.5, 0.5)
])
pokemon.transform = transformer
data_iter = torch.utils.data.DataLoader(
pokemon, batch_size=batch_size,
shuffle=True, num_workers=d2l.get_dataloader_workers())
def transform_func(X):
X = X / 255.
X = (X - 0.5) / (0.5)
return X
# For TF>=2.4 use `num_parallel_calls = tf.data.AUTOTUNE`
data_iter = pokemon.map(lambda x, y: (transform_func(x), y),
num_parallel_calls=tf.data.experimental.AUTOTUNE)
data_iter = data_iter.cache().shuffle(buffer_size=1000).prefetch(
buffer_size=tf.data.experimental.AUTOTUNE)
Hãy để chúng tôi hình dung 20 hình ảnh đầu tiên.
d2l.set_figsize((4, 4))
for X, y in data_iter:
imgs = X[0:20,:,:,:].transpose(0, 2, 3, 1)/2+0.5
d2l.show_images(imgs, num_rows=4, num_cols=5)
break
warnings.filterwarnings('ignore')
d2l.set_figsize((4, 4))
for X, y in data_iter:
imgs = X[0:20,:,:,:].permute(0, 2, 3, 1)/2+0.5
d2l.show_images(imgs, num_rows=4, num_cols=5)
break
d2l.set_figsize(figsize=(4, 4))
for X, y in data_iter.take(1):
imgs = X[:20, :, :, :] / 2 + 0.5
d2l.show_images(imgs, num_rows=4, num_cols=5)
17.2.2. Các máy phát điện¶
Máy phát điện cần ánh xạ biến nhiễu \(\mathbf z\in\mathbb R^d\), một vector chiều dài-\(d\), đến một hình ảnh RGB với chiều rộng và chiều cao là \(64\times 64\). Trong Section 13.11, chúng tôi đã giới thiệu mạng phức tạp hoàn toàn sử dụng lớp ghép chuyển tiếp (tham khảo Section 13.10) để phóng to kích thước đầu vào. Khối cơ bản của máy phát điện chứa một lớp phức tạp chuyển tiếp theo là chuẩn hóa hàng loạt và kích hoạt ReLU.
class G_block(nn.Block):
def __init__(self, channels, kernel_size=4,
strides=2, padding=1, **kwargs):
super(G_block, self).__init__(**kwargs)
self.conv2d_trans = nn.Conv2DTranspose(
channels, kernel_size, strides, padding, use_bias=False)
self.batch_norm = nn.BatchNorm()
self.activation = nn.Activation('relu')
def forward(self, X):
return self.activation(self.batch_norm(self.conv2d_trans(X)))
class G_block(nn.Module):
def __init__(self, out_channels, in_channels=3, kernel_size=4, strides=2,
padding=1, **kwargs):
super(G_block, self).__init__(**kwargs)
self.conv2d_trans = nn.ConvTranspose2d(in_channels, out_channels,
kernel_size, strides, padding, bias=False)
self.batch_norm = nn.BatchNorm2d(out_channels)
self.activation = nn.ReLU()
def forward(self, X):
return self.activation(self.batch_norm(self.conv2d_trans(X)))
class G_block(tf.keras.layers.Layer):
def __init__(self, out_channels, kernel_size=4, strides=2, padding="same",
**kwargs):
super().__init__(**kwargs)
self.conv2d_trans = tf.keras.layers.Conv2DTranspose(
out_channels, kernel_size, strides, padding, use_bias=False)
self.batch_norm = tf.keras.layers.BatchNormalization()
self.activation = tf.keras.layers.ReLU()
def call(self, X):
return self.activation(self.batch_norm(self.conv2d_trans(X)))
Mặc định, lớp ghép chuyển tiếp sử dụng hạt nhân \(k_h = k_w = 4\), một bước tiến \(s_h = s_w = 2\) và đệm \(p_h = p_w = 1\). Với hình dạng đầu vào là \(n_h^{'} \times n_w^{'} = 16 \times 16\), khối máy phát sẽ tăng gấp đôi chiều rộng và chiều cao của đầu vào.
x = np.zeros((2, 3, 16, 16))
g_blk = G_block(20)
g_blk.initialize()
g_blk(x).shape
(2, 20, 32, 32)
x = torch.zeros((2, 3, 16, 16))
g_blk = G_block(20)
g_blk(x).shape
torch.Size([2, 20, 32, 32])
x = tf.zeros((2, 16, 16, 3)) # Channel last convention
g_blk = G_block(20)
g_blk(x).shape
TensorShape([2, 32, 32, 20])
Nếu thay đổi lớp phức tạp chuyển tiếp thành hạt nhân \(4\times 4\), \(1\times 1\) sải bước và không đệm. Với kích thước đầu vào là \(1 \times 1\), đầu ra sẽ có chiều rộng và chiều cao của nó tăng 3 lần lượt.
x = np.zeros((2, 3, 1, 1))
g_blk = G_block(20, strides=1, padding=0)
g_blk.initialize()
g_blk(x).shape
(2, 20, 4, 4)
x = torch.zeros((2, 3, 1, 1))
g_blk = G_block(20, strides=1, padding=0)
g_blk(x).shape
torch.Size([2, 20, 4, 4])
x = tf.zeros((2, 1, 1, 3))
# `padding="valid"` corresponds to no padding
g_blk = G_block(20, strides=1, padding="valid")
g_blk(x).shape
TensorShape([2, 4, 4, 20])
Máy phát điện bao gồm bốn khối cơ bản giúp tăng cả chiều rộng và chiều cao đầu vào từ 1 lên 32. Đồng thời, đầu tiên nó chiếu biến tiềm ẩn thành \(64\times 8\) kênh, sau đó giảm một nửa các kênh mỗi lần. Cuối cùng, một lớp covolution được chuyển đổi được sử dụng để tạo ra đầu ra. Nó tiếp tục tăng gấp đôi chiều rộng và chiều cao để phù hợp với hình dạng \(64\times 64\) mong muốn và giảm kích thước kênh xuống \(3\). Hàm kích hoạt tanh được áp dụng cho các giá trị đầu ra dự án vào phạm vi \((-1, 1)\).
n_G = 64
net_G = nn.Sequential()
net_G.add(G_block(n_G*8, strides=1, padding=0), # Output: (64 * 8, 4, 4)
G_block(n_G*4), # Output: (64 * 4, 8, 8)
G_block(n_G*2), # Output: (64 * 2, 16, 16)
G_block(n_G), # Output: (64, 32, 32)
nn.Conv2DTranspose(
3, kernel_size=4, strides=2, padding=1, use_bias=False,
activation='tanh')) # Output: (3, 64, 64)
n_G = 64
net_G = nn.Sequential(
G_block(in_channels=100, out_channels=n_G*8,
strides=1, padding=0), # Output: (64 * 8, 4, 4)
G_block(in_channels=n_G*8, out_channels=n_G*4), # Output: (64 * 4, 8, 8)
G_block(in_channels=n_G*4, out_channels=n_G*2), # Output: (64 * 2, 16, 16)
G_block(in_channels=n_G*2, out_channels=n_G), # Output: (64, 32, 32)
nn.ConvTranspose2d(in_channels=n_G, out_channels=3,
kernel_size=4, stride=2, padding=1, bias=False),
nn.Tanh()) # Output: (3, 64, 64)
n_G = 64
net_G = tf.keras.Sequential([
# Output: (4, 4, 64 * 8)
G_block(out_channels=n_G*8, strides=1, padding="valid"),
G_block(out_channels=n_G*4), # Output: (8, 8, 64 * 4)
G_block(out_channels=n_G*2), # Output: (16, 16, 64 * 2)
G_block(out_channels=n_G), # Output: (32, 32, 64)
# Output: (64, 64, 3)
tf.keras.layers.Conv2DTranspose(
3, kernel_size=4, strides=2, padding="same", use_bias=False,
activation="tanh")
])
Tạo ra một biến tiềm ẩn 100 chiều để xác minh hình dạng đầu ra của máy phát điện.
x = np.zeros((1, 100, 1, 1))
net_G.initialize()
net_G(x).shape
(1, 3, 64, 64)
x = torch.zeros((1, 100, 1, 1))
net_G(x).shape
torch.Size([1, 3, 64, 64])
x = tf.zeros((1, 1, 1, 100))
net_G(x).shape
TensorShape([1, 64, 64, 3])
17.2.3. Phân biệt đối xử¶
Người phân biệt đối xử là một mạng mạng phức tạp bình thường ngoại trừ việc nó sử dụng ReLU bị rò rỉ làm chức năng kích hoạt của nó. Đưa ra \(\alpha \in[0, 1]\), định nghĩa của nó là
Như có thể thấy, nó là bình thường ReLU nếu \(\alpha=0\), và một chức năng nhận dạng nếu \(\alpha=1\). Đối với \(\alpha \in (0, 1)\), ReLU bị rò rỉ là một hàm phi tuyến cung cấp đầu ra không phải bằng không cho đầu vào âm. Nó nhằm mục đích khắc phục vấn đề ReLU đang chết mà một tế bào thần kinh luôn có thể tạo ra một giá trị âm và do đó không thể thực hiện bất kỳ tiến bộ nào vì gradient của ReLU là 0.
alphas = [0, .2, .4, .6, .8, 1]
x = np.arange(-2, 1, 0.1)
Y = [nn.LeakyReLU(alpha)(x).asnumpy() for alpha in alphas]
d2l.plot(x.asnumpy(), Y, 'x', 'y', alphas)
alphas = [0, .2, .4, .6, .8, 1]
x = torch.arange(-2, 1, 0.1)
Y = [nn.LeakyReLU(alpha)(x).detach().numpy() for alpha in alphas]
d2l.plot(x.detach().numpy(), Y, 'x', 'y', alphas)
alphas = [0, .2, .4, .6, .8, 1]
x = tf.range(-2, 1, 0.1)
Y = [tf.keras.layers.LeakyReLU(alpha)(x).numpy() for alpha in alphas]
d2l.plot(x.numpy(), Y, 'x', 'y', alphas)
Khối cơ bản của phân biệt đối xử là một lớp phức tạp tiếp theo là một lớp chuẩn hóa hàng loạt và kích hoạt ReLU bị rò rỉ. Các siêu tham số của lớp covolution tương tự như lớp covolution transpose trong khối máy phát.
class D_block(nn.Block):
def __init__(self, channels, kernel_size=4, strides=2,
padding=1, alpha=0.2, **kwargs):
super(D_block, self).__init__(**kwargs)
self.conv2d = nn.Conv2D(
channels, kernel_size, strides, padding, use_bias=False)
self.batch_norm = nn.BatchNorm()
self.activation = nn.LeakyReLU(alpha)
def forward(self, X):
return self.activation(self.batch_norm(self.conv2d(X)))
class D_block(nn.Module):
def __init__(self, out_channels, in_channels=3, kernel_size=4, strides=2,
padding=1, alpha=0.2, **kwargs):
super(D_block, self).__init__(**kwargs)
self.conv2d = nn.Conv2d(in_channels, out_channels, kernel_size,
strides, padding, bias=False)
self.batch_norm = nn.BatchNorm2d(out_channels)
self.activation = nn.LeakyReLU(alpha, inplace=True)
def forward(self, X):
return self.activation(self.batch_norm(self.conv2d(X)))
class D_block(tf.keras.layers.Layer):
def __init__(self, out_channels, kernel_size=4, strides=2, padding="same",
alpha=0.2, **kwargs):
super().__init__(**kwargs)
self.conv2d = tf.keras.layers.Conv2D(out_channels, kernel_size,
strides, padding, use_bias=False)
self.batch_norm = tf.keras.layers.BatchNormalization()
self.activation = tf.keras.layers.LeakyReLU(alpha)
def call(self, X):
return self.activation(self.batch_norm(self.conv2d(X)))
Một khối cơ bản với cài đặt mặc định sẽ giảm một nửa chiều rộng và chiều cao của các đầu vào, như chúng ta đã chứng minh trong Section 6.3. Ví dụ, cho một hình dạng đầu vào \(n_h = n_w = 16\), với một hình dạng hạt nhân \(k_h = k_w = 4\), một hình sải chân \(s_h = s_w = 2\), và một hình dạng đệm \(p_h = p_w = 1\), hình dạng đầu ra sẽ là:
x = np.zeros((2, 3, 16, 16))
d_blk = D_block(20)
d_blk.initialize()
d_blk(x).shape
(2, 20, 8, 8)
x = torch.zeros((2, 3, 16, 16))
d_blk = D_block(20)
d_blk(x).shape
torch.Size([2, 20, 8, 8])
x = tf.zeros((2, 16, 16, 3))
d_blk = D_block(20)
d_blk(x).shape
TensorShape([2, 8, 8, 20])
Người phân biệt đối xử là một tấm gương của máy phát điện.
n_D = 64
net_D = nn.Sequential()
net_D.add(D_block(n_D), # Output: (64, 32, 32)
D_block(n_D*2), # Output: (64 * 2, 16, 16)
D_block(n_D*4), # Output: (64 * 4, 8, 8)
D_block(n_D*8), # Output: (64 * 8, 4, 4)
nn.Conv2D(1, kernel_size=4, use_bias=False)) # Output: (1, 1, 1)
n_D = 64
net_D = nn.Sequential(
D_block(n_D), # Output: (64, 32, 32)
D_block(in_channels=n_D, out_channels=n_D*2), # Output: (64 * 2, 16, 16)
D_block(in_channels=n_D*2, out_channels=n_D*4), # Output: (64 * 4, 8, 8)
D_block(in_channels=n_D*4, out_channels=n_D*8), # Output: (64 * 8, 4, 4)
nn.Conv2d(in_channels=n_D*8, out_channels=1,
kernel_size=4, bias=False)) # Output: (1, 1, 1)
n_D = 64
net_D = tf.keras.Sequential([
D_block(n_D), # Output: (32, 32, 64)
D_block(out_channels=n_D*2), # Output: (16, 16, 64 * 2)
D_block(out_channels=n_D*4), # Output: (8, 8, 64 * 4)
D_block(out_channels=n_D*8), # Outupt: (4, 4, 64 * 64)
# Output: (1, 1, 1)
tf.keras.layers.Conv2D(1, kernel_size=4, use_bias=False)
])
Nó sử dụng một lớp phức tạp với kênh đầu ra \(1\) làm lớp cuối cùng để có được một giá trị dự đoán duy nhất.
x = np.zeros((1, 3, 64, 64))
net_D.initialize()
net_D(x).shape
(1, 1, 1, 1)
x = torch.zeros((1, 3, 64, 64))
net_D(x).shape
torch.Size([1, 1, 1, 1])
x = tf.zeros((1, 64, 64, 3))
net_D(x).shape
TensorShape([1, 1, 1, 1])
17.2.4. Đào tạo¶
So với GAN cơ bản trong Section 17.1, chúng tôi sử dụng
cùng một tốc độ học tập cho cả máy phát điện và phân biệt đối xử vì
chúng tương tự nhau. Ngoài ra, chúng tôi thay đổi \(\beta_1\) trong
Adam (Section 11.10) từ \(0.9\) thành \(0.5\). Nó làm
giảm độ mịn của động lượng, trung bình động có trọng số theo cấp số nhân
của gradient trong quá khứ, để chăm sóc các gradient thay đổi nhanh
chóng vì máy phát điện và người phân biệt đối xử chiến đấu với nhau. Bên
cạnh đó, tiếng ồn tạo ngẫu nhiên Z
, là một tensor 4-D và chúng tôi
đang sử dụng GPU để tăng tốc tính toán.
def train(net_D, net_G, data_iter, num_epochs, lr, latent_dim,
device=d2l.try_gpu()):
loss = gluon.loss.SigmoidBCELoss()
net_D.initialize(init=init.Normal(0.02), force_reinit=True, ctx=device)
net_G.initialize(init=init.Normal(0.02), force_reinit=True, ctx=device)
trainer_hp = {'learning_rate': lr, 'beta1': 0.5}
trainer_D = gluon.Trainer(net_D.collect_params(), 'adam', trainer_hp)
trainer_G = gluon.Trainer(net_G.collect_params(), 'adam', trainer_hp)
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[1, num_epochs], nrows=2, figsize=(5, 5),
legend=['discriminator', 'generator'])
animator.fig.subplots_adjust(hspace=0.3)
for epoch in range(1, num_epochs + 1):
# Train one epoch
timer = d2l.Timer()
metric = d2l.Accumulator(3) # loss_D, loss_G, num_examples
for X, _ in data_iter:
batch_size = X.shape[0]
Z = np.random.normal(0, 1, size=(batch_size, latent_dim, 1, 1))
X, Z = X.as_in_ctx(device), Z.as_in_ctx(device),
metric.add(d2l.update_D(X, Z, net_D, net_G, loss, trainer_D),
d2l.update_G(Z, net_D, net_G, loss, trainer_G),
batch_size)
# Show generated examples
Z = np.random.normal(0, 1, size=(21, latent_dim, 1, 1), ctx=device)
# Normalize the synthetic data to N(0, 1)
fake_x = net_G(Z).transpose(0, 2, 3, 1) / 2 + 0.5
imgs = np.concatenate(
[np.concatenate([fake_x[i * 7 + j] for j in range(7)], axis=1)
for i in range(len(fake_x)//7)], axis=0)
animator.axes[1].cla()
animator.axes[1].imshow(imgs.asnumpy())
# Show the losses
loss_D, loss_G = metric[0] / metric[2], metric[1] / metric[2]
animator.add(epoch, (loss_D, loss_G))
print(f'loss_D {loss_D:.3f}, loss_G {loss_G:.3f}, '
f'{metric[2] / timer.stop():.1f} examples/sec on {str(device)}')
def train(net_D, net_G, data_iter, num_epochs, lr, latent_dim,
device=d2l.try_gpu()):
loss = nn.BCEWithLogitsLoss(reduction='sum')
for w in net_D.parameters():
nn.init.normal_(w, 0, 0.02)
for w in net_G.parameters():
nn.init.normal_(w, 0, 0.02)
net_D, net_G = net_D.to(device), net_G.to(device)
trainer_hp = {'lr': lr, 'betas': [0.5,0.999]}
trainer_D = torch.optim.Adam(net_D.parameters(), **trainer_hp)
trainer_G = torch.optim.Adam(net_G.parameters(), **trainer_hp)
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[1, num_epochs], nrows=2, figsize=(5, 5),
legend=['discriminator', 'generator'])
animator.fig.subplots_adjust(hspace=0.3)
for epoch in range(1, num_epochs + 1):
# Train one epoch
timer = d2l.Timer()
metric = d2l.Accumulator(3) # loss_D, loss_G, num_examples
for X, _ in data_iter:
batch_size = X.shape[0]
Z = torch.normal(0, 1, size=(batch_size, latent_dim, 1, 1))
X, Z = X.to(device), Z.to(device)
metric.add(d2l.update_D(X, Z, net_D, net_G, loss, trainer_D),
d2l.update_G(Z, net_D, net_G, loss, trainer_G),
batch_size)
# Show generated examples
Z = torch.normal(0, 1, size=(21, latent_dim, 1, 1), device=device)
# Normalize the synthetic data to N(0, 1)
fake_x = net_G(Z).permute(0, 2, 3, 1) / 2 + 0.5
imgs = torch.cat(
[torch.cat([
fake_x[i * 7 + j].cpu().detach() for j in range(7)], dim=1)
for i in range(len(fake_x)//7)], dim=0)
animator.axes[1].cla()
animator.axes[1].imshow(imgs)
# Show the losses
loss_D, loss_G = metric[0] / metric[2], metric[1] / metric[2]
animator.add(epoch, (loss_D, loss_G))
print(f'loss_D {loss_D:.3f}, loss_G {loss_G:.3f}, '
f'{metric[2] / timer.stop():.1f} examples/sec on {str(device)}')
def train(net_D, net_G, data_iter, num_epochs, lr, latent_dim,
device=d2l.try_gpu()):
loss = tf.keras.losses.BinaryCrossentropy(
from_logits=True, reduction=tf.keras.losses.Reduction.SUM)
for w in net_D.trainable_variables:
w.assign(tf.random.normal(mean=0, stddev=0.02, shape=w.shape))
for w in net_G.trainable_variables:
w.assign(tf.random.normal(mean=0, stddev=0.02, shape=w.shape))
optimizer_hp = {"lr": lr, "beta_1": 0.5, "beta_2": 0.999}
optimizer_D = tf.keras.optimizers.Adam(**optimizer_hp)
optimizer_G = tf.keras.optimizers.Adam(**optimizer_hp)
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[1, num_epochs], nrows=2, figsize=(5, 5),
legend=['discriminator', 'generator'])
animator.fig.subplots_adjust(hspace=0.3)
for epoch in range(1, num_epochs + 1):
# Train one epoch
timer = d2l.Timer()
metric = d2l.Accumulator(3) # loss_D, loss_G, num_examples
for X, _ in data_iter:
batch_size = X.shape[0]
Z = tf.random.normal(mean=0, stddev=1,
shape=(batch_size, 1, 1, latent_dim))
metric.add(d2l.update_D(X, Z, net_D, net_G, loss, optimizer_D),
d2l.update_G(Z, net_D, net_G, loss, optimizer_G),
batch_size)
# Show generated examples
Z = tf.random.normal(mean=0, stddev=1, shape=(21, 1, 1, latent_dim))
# Normalize the synthetic data to N(0, 1)
fake_x = net_G(Z) / 2 + 0.5
imgs = tf.concat([tf.concat([fake_x[i * 7 + j] for j in range(7)],
axis=1)
for i in range(len(fake_x) // 7)], axis=0)
animator.axes[1].cla()
animator.axes[1].imshow(imgs)
# Show the losses
loss_D, loss_G = metric[0] / metric[2], metric[1] / metric[2]
animator.add(epoch, (loss_D, loss_G))
print(f'loss_D {loss_D:.3f}, loss_G {loss_G:.3f}, '
f'{metric[2] / timer.stop():.1f} examples/sec on {str(device)}')
Chúng tôi đào tạo mô hình với một số lượng nhỏ các kỷ nguyên chỉ để
trình diễn. Để có hiệu suất tốt hơn, biến num_epochs
có thể được đặt
thành một số lớn hơn.
latent_dim, lr, num_epochs = 100, 0.005, 20
train(net_D, net_G, data_iter, num_epochs, lr, latent_dim)
loss_D 0.383, loss_G 3.322, 2592.6 examples/sec on gpu(0)
latent_dim, lr, num_epochs = 100, 0.005, 20
train(net_D, net_G, data_iter, num_epochs, lr, latent_dim)
loss_D 0.154, loss_G 7.906, 1062.5 examples/sec on cuda:0
latent_dim, lr, num_epochs = 100, 0.0005, 40
train(net_D, net_G, data_iter, num_epochs, lr, latent_dim)
loss_D 0.223, loss_G 3.809, 2240.4 examples/sec on <tensorflow.python.eager.context._EagerDeviceContext object at 0x7f310c08ad40>
17.2.5. Tóm tắt¶
Kiến trúc DCGAN có bốn lớp phức tạp cho Discriminator và bốn lớp phức tạp “phân đoạn strided” cho Generator.
Discriminator là một sự phức tạp 4 lớp với chuẩn hóa hàng loạt (ngoại trừ lớp đầu vào của nó) và các kích hoạt ReLU bị rò rỉ.
Leaky ReLU là một hàm phi tuyến cung cấp một đầu ra không phải bằng không cho một đầu vào âm. Nó nhằm mục đích khắc phục vấn đề “sắp chết ReLU” và giúp các gradient chảy dễ dàng hơn thông qua kiến trúc.
17.2.6. Bài tập¶
Điều gì sẽ xảy ra nếu chúng ta sử dụng kích hoạt ReLU tiêu chuẩn thay vì ReLU bị rò rỉ?
Áp dụng DCGAN trên Fashion-MNIST và xem danh mục nào hoạt động tốt và loại nào không.