.. _sec_dcgan:
Mạng đối thủ tạo phức tạp sâu
=============================
Trong :numref:`sec_basic_gan`, 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
:cite:`Radford.Metz.Chintala.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ế.
.. raw:: html
`__. Đầu tiên tải
xuống, trích xuất và tải tập dữ liệu này.
.. raw:: html
.. raw:: html
.. code:: python
#@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)
.. parsed-literal::
:class: output
Downloading ../data/pokemon.zip from http://d2l-data.s3-accelerate.amazonaws.com/pokemon.zip...
.. raw:: html
.. raw:: html
.. code:: python
#@save
d2l.DATA_HUB['pokemon'] = (d2l.DATA_URL + 'pokemon.zip',
'c065c0e2593b8b161a2d7873e42418bf6a21106c')
data_dir = d2l.download_extract('pokemon')
pokemon = torchvision.datasets.ImageFolder(data_dir)
.. parsed-literal::
:class: output
Downloading ../data/pokemon.zip from http://d2l-data.s3-accelerate.amazonaws.com/pokemon.zip...
.. raw:: html
.. raw:: html
.. code:: python
#@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))
.. parsed-literal::
:class: output
Downloading ../data/pokemon.zip from http://d2l-data.s3-accelerate.amazonaws.com/pokemon.zip...
Found 40597 files belonging to 721 classes.
.. raw:: html
.. raw:: html
Chúng tôi thay đổi kích thước mỗi hình ảnh thành :math:`64\times 64`.
Việc chuyển đổi ``ToTensor`` sẽ chiếu giá trị pixel thành
:math:`[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 :math:`[-1, 1]`. Do đó, chúng tôi bình thường hóa
dữ liệu với :math:`0.5` trung bình và độ lệch chuẩn :math:`0.5` để phù
hợp với phạm vi giá trị.
.. raw:: html
.. raw:: html
.. code:: python
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())
.. raw:: html
.. raw:: html
.. code:: python
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())
.. raw:: html
.. raw:: html
.. code:: python
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)
.. raw:: html
.. raw:: html
Hãy để chúng tôi hình dung 20 hình ảnh đầu tiên.
.. raw:: html
.. raw:: html
.. code:: python
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
.. figure:: output_dcgan_2541de_39_0.svg
.. raw:: html
.. raw:: html
.. code:: python
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
.. figure:: output_dcgan_2541de_42_0.svg
.. raw:: html
.. raw:: html
.. code:: python
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)
.. figure:: output_dcgan_2541de_45_0.svg
.. raw:: html
.. raw:: html
Các máy phát điện
-----------------
Máy phát điện cần ánh xạ biến nhiễu :math:`\mathbf z\in\mathbb R^d`, một
vector chiều dài-\ :math:`d`, đến một hình ảnh RGB với chiều rộng và
chiều cao là :math:`64\times 64`. Trong :numref:`sec_fcn`, 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 :numref:`sec_transposed_conv`) để 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.
.. raw:: html
.. raw:: html
.. code:: python
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)))
.. raw:: html
.. raw:: html
.. code:: python
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)))
.. raw:: html
.. raw:: html
.. code:: python
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)))
.. raw:: html
.. raw:: html
Mặc định, lớp ghép chuyển tiếp sử dụng hạt nhân :math:`k_h = k_w = 4`,
một bước tiến :math:`s_h = s_w = 2` và đệm :math:`p_h = p_w = 1`. Với
hình dạng đầu vào là :math:`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.
.. math::
\begin{aligned}
n_h^{'} \times n_w^{'} &= [(n_h k_h - (n_h-1)(k_h-s_h)- 2p_h] \times [(n_w k_w - (n_w-1)(k_w-s_w)- 2p_w]\\
&= [(k_h + s_h (n_h-1)- 2p_h] \times [(k_w + s_w (n_w-1)- 2p_w]\\
&= [(4 + 2 \times (16-1)- 2 \times 1] \times [(4 + 2 \times (16-1)- 2 \times 1]\\
&= 32 \times 32 .\\
\end{aligned}
.. raw:: html
.. raw:: html
.. code:: python
x = np.zeros((2, 3, 16, 16))
g_blk = G_block(20)
g_blk.initialize()
g_blk(x).shape
.. parsed-literal::
:class: output
(2, 20, 32, 32)
.. raw:: html
.. raw:: html
.. code:: python
x = torch.zeros((2, 3, 16, 16))
g_blk = G_block(20)
g_blk(x).shape
.. parsed-literal::
:class: output
torch.Size([2, 20, 32, 32])
.. raw:: html
.. raw:: html
.. code:: python
x = tf.zeros((2, 16, 16, 3)) # Channel last convention
g_blk = G_block(20)
g_blk(x).shape
.. parsed-literal::
:class: output
TensorShape([2, 32, 32, 20])
.. raw:: html
.. raw:: html
Nếu thay đổi lớp phức tạp chuyển tiếp thành hạt nhân :math:`4\times 4`,
:math:`1\times 1` sải bước và không đệm. Với kích thước đầu vào là
:math:`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.
.. raw:: html
.. raw:: html
.. code:: python
x = np.zeros((2, 3, 1, 1))
g_blk = G_block(20, strides=1, padding=0)
g_blk.initialize()
g_blk(x).shape
.. parsed-literal::
:class: output
(2, 20, 4, 4)
.. raw:: html
.. raw:: html
.. code:: python
x = torch.zeros((2, 3, 1, 1))
g_blk = G_block(20, strides=1, padding=0)
g_blk(x).shape
.. parsed-literal::
:class: output
torch.Size([2, 20, 4, 4])
.. raw:: html
.. raw:: html
.. code:: python
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
.. parsed-literal::
:class: output
TensorShape([2, 4, 4, 20])
.. raw:: html
.. raw:: html
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
:math:`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 :math:`64\times 64` mong muốn và giảm kích thước kênh xuống
:math:`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 :math:`(-1, 1)`.
.. raw:: html
.. raw:: html
.. code:: python
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)
.. raw:: html
.. raw:: html
.. code:: python
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)
.. raw:: html
.. raw:: html
.. code:: python
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")
])
.. raw:: html
.. raw:: html
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.
.. raw:: html
.. raw:: html
.. code:: python
x = np.zeros((1, 100, 1, 1))
net_G.initialize()
net_G(x).shape
.. parsed-literal::
:class: output
(1, 3, 64, 64)
.. raw:: html
.. raw:: html
.. code:: python
x = torch.zeros((1, 100, 1, 1))
net_G(x).shape
.. parsed-literal::
:class: output
torch.Size([1, 3, 64, 64])
.. raw:: html
.. raw:: html
.. code:: python
x = tf.zeros((1, 1, 1, 100))
net_G(x).shape
.. parsed-literal::
:class: output
TensorShape([1, 64, 64, 3])
.. raw:: html
.. raw:: html
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
:math:`\alpha \in[0, 1]`, định nghĩa của nó là
.. math:: \textrm{leaky ReLU}(x) = \begin{cases}x & \text{if}\ x > 0\\ \alpha x &\text{otherwise}\end{cases}.
Như có thể thấy, nó là bình thường ReLU nếu :math:`\alpha=0`, và một
chức năng nhận dạng nếu :math:`\alpha=1`. Đối với
:math:`\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.
.. raw:: html
.. raw:: html
.. code:: python
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)
.. figure:: output_dcgan_2541de_111_0.svg
.. raw:: html
.. raw:: html
.. code:: python
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)
.. figure:: output_dcgan_2541de_114_0.svg
.. raw:: html
.. raw:: html
.. code:: python
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)
.. figure:: output_dcgan_2541de_117_0.svg
.. raw:: html
.. raw:: html
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.
.. raw:: html
.. raw:: html
.. code:: python
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)))
.. raw:: html
.. raw:: html
.. code:: python
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)))
.. raw:: html
.. raw:: html
.. code:: python
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)))
.. raw:: html
.. raw:: html
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
:numref:`sec_padding`. Ví dụ, cho một hình dạng đầu vào
:math:`n_h = n_w = 16`, với một hình dạng hạt nhân
:math:`k_h = k_w = 4`, một hình sải chân :math:`s_h = s_w = 2`, và một
hình dạng đệm :math:`p_h = p_w = 1`, hình dạng đầu ra sẽ là:
.. math::
\begin{aligned}
n_h^{'} \times n_w^{'} &= \lfloor(n_h-k_h+2p_h+s_h)/s_h\rfloor \times \lfloor(n_w-k_w+2p_w+s_w)/s_w\rfloor\\
&= \lfloor(16-4+2\times 1+2)/2\rfloor \times \lfloor(16-4+2\times 1+2)/2\rfloor\\
&= 8 \times 8 .\\
\end{aligned}
.. raw:: html
.. raw:: html
.. code:: python
x = np.zeros((2, 3, 16, 16))
d_blk = D_block(20)
d_blk.initialize()
d_blk(x).shape
.. parsed-literal::
:class: output
(2, 20, 8, 8)
.. raw:: html
.. raw:: html
.. code:: python
x = torch.zeros((2, 3, 16, 16))
d_blk = D_block(20)
d_blk(x).shape
.. parsed-literal::
:class: output
torch.Size([2, 20, 8, 8])
.. raw:: html
.. raw:: html
.. code:: python
x = tf.zeros((2, 16, 16, 3))
d_blk = D_block(20)
d_blk(x).shape
.. parsed-literal::
:class: output
TensorShape([2, 8, 8, 20])
.. raw:: html
.. raw:: html
Người phân biệt đối xử là một tấm gương của máy phát điện.
.. raw:: html
.. raw:: html
.. code:: python
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)
.. raw:: html
.. raw:: html
.. code:: python
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)
.. raw:: html
.. raw:: html
.. code:: python
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)
])
.. raw:: html
.. raw:: html
Nó sử dụng một lớp phức tạp với kênh đầu ra :math:`1` làm lớp cuối cùng
để có được một giá trị dự đoán duy nhất.
.. raw:: html
.. raw:: html
.. code:: python
x = np.zeros((1, 3, 64, 64))
net_D.initialize()
net_D(x).shape
.. parsed-literal::
:class: output
(1, 1, 1, 1)
.. raw:: html
.. raw:: html
.. code:: python
x = torch.zeros((1, 3, 64, 64))
net_D(x).shape
.. parsed-literal::
:class: output
torch.Size([1, 1, 1, 1])
.. raw:: html
.. raw:: html
.. code:: python
x = tf.zeros((1, 64, 64, 3))
net_D(x).shape
.. parsed-literal::
:class: output
TensorShape([1, 1, 1, 1])
.. raw:: html
.. raw:: html
Đào tạo
-------
So với GAN cơ bản trong :numref:`sec_basic_gan`, 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 :math:`\beta_1` trong
Adam (:numref:`sec_adam`) từ :math:`0.9` thành :math:`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.
.. raw:: html
.. raw:: html
.. code:: python
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)}')
.. raw:: html
.. raw:: html
.. code:: python
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)}')
.. raw:: html
.. raw:: html
.. code:: python
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)}')
.. raw:: html
.. raw:: html
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.
.. raw:: html
.. raw:: html
.. code:: python
latent_dim, lr, num_epochs = 100, 0.005, 20
train(net_D, net_G, data_iter, num_epochs, lr, latent_dim)
.. parsed-literal::
:class: output
loss_D 0.383, loss_G 3.322, 2592.6 examples/sec on gpu(0)
.. figure:: output_dcgan_2541de_183_1.svg
.. raw:: html
.. raw:: html
.. code:: python
latent_dim, lr, num_epochs = 100, 0.005, 20
train(net_D, net_G, data_iter, num_epochs, lr, latent_dim)
.. parsed-literal::
:class: output
loss_D 0.154, loss_G 7.906, 1062.5 examples/sec on cuda:0
.. figure:: output_dcgan_2541de_186_1.svg
.. raw:: html
.. raw:: html
.. code:: python
latent_dim, lr, num_epochs = 100, 0.0005, 40
train(net_D, net_G, data_iter, num_epochs, lr, latent_dim)
.. parsed-literal::
:class: output
loss_D 0.223, loss_G 3.809, 2240.4 examples/sec on
.. figure:: output_dcgan_2541de_189_1.svg
.. raw:: html
.. raw:: html
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.
Bài tập
-------
1. Đ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ỉ?
2. Á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.
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html