.. _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
mxnetpytorchtensorflow
.. raw:: html
.. code:: python from mxnet import gluon, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() .. raw:: html
.. raw:: html
.. code:: python import warnings import torch import torchvision from torch import nn from d2l import torch as d2l .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf from d2l import tensorflow as d2l .. raw:: html
.. raw:: html
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. .. 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