Last chance! 50% off DataCamp Premium
Sale ends in
Trong hướng dẫn này, bạn sẽ tìm hiểu Mạng Đối nghịch Sinh (GAN) là gì mà không đi sâu vào chi tiết toán học. Sau đó, bạn sẽ học cách viết mã một GAN đơn giản có thể tạo ra các chữ số!
Cách dễ nhất để hiểu GAN là gì là thông qua một phép so sánh đơn giản:
Giả sử có một cửa hàng mua một số loại rượu vang nhất định từ khách hàng để sau đó bán lại.

Tuy nhiên, có những khách hàng xấu bán rượu vang giả để lấy tiền. Trong trường hợp này, chủ cửa hàng phải có khả năng phân biệt giữa rượu giả và rượu thật.

Bạn có thể hình dung rằng ban đầu, kẻ làm giả có thể mắc rất nhiều sai sót khi cố gắng bán rượu giả và chủ cửa hàng sẽ dễ dàng nhận ra rượu đó không phải là thật. Do những thất bại này, kẻ làm giả sẽ tiếp tục thử các kỹ thuật khác nhau để mô phỏng rượu thật và một số sẽ cuối cùng thành công. Giờ đây, khi kẻ làm giả biết rằng một số kỹ thuật đã vượt qua được kiểm tra của chủ cửa hàng, hắn có thể tiếp tục cải thiện rượu giả dựa trên những kỹ thuật đó.
Đồng thời, chủ cửa hàng có thể nhận được phản hồi từ các chủ cửa hàng khác hoặc chuyên gia rượu rằng một số chai rượu cô ấy có không phải là nguyên bản. Điều này có nghĩa là chủ cửa hàng sẽ phải cải thiện cách xác định rượu là giả hay thật. Mục tiêu của kẻ làm giả là tạo ra rượu không thể phân biệt với rượu thật, còn mục tiêu của chủ cửa hàng là xác định chính xác rượu có thật hay không.
Sự cạnh tranh qua lại này chính là ý tưởng cốt lõi đằng sau GAN.
Dựa trên ví dụ ở trên, ta có thể xây dựng kiến trúc của một GAN.

Có hai thành phần chính trong GAN: bộ sinh (generator) và bộ phân biệt (discriminator). Chủ cửa hàng trong ví dụ được xem như một mạng phân biệt và thường là một mạng nơ-ron tích chập (vì GAN chủ yếu dùng cho các tác vụ hình ảnh) gán xác suất rằng ảnh là thật.
Kẻ làm giả được gọi là mạng sinh và cũng thường là một mạng nơ-ron tích chập (với các tầng giải tích chập). Mạng này nhận một vector nhiễu và xuất ra một ảnh. Khi huấn luyện mạng sinh, nó học được những vùng nào của ảnh cần cải thiện/thay đổi để bộ phân biệt khó phân biệt hơn giữa ảnh sinh ra và ảnh thật.
Mạng sinh liên tục tạo ra các ảnh ngày càng giống ảnh thật trong khi mạng phân biệt cố gắng xác định sự khác biệt giữa ảnh thật và ảnh giả. Mục tiêu cuối cùng là có một mạng sinh có thể tạo ra ảnh không thể phân biệt với ảnh thật.
Bây giờ bạn đã hiểu GAN là gì và các thành phần chính của nó, chúng ta có thể bắt đầu viết mã một phiên bản rất đơn giản. Bạn sẽ dùng Keras; nếu chưa quen với thư viện Python này, bạn nên đọc hướng dẫn này trước khi tiếp tục. Hướng dẫn này dựa trên GAN được phát triển tại đây.
Việc đầu tiên bạn cần làm là cài đặt các gói sau thông qua pip
kerasmatplotlibtensorflowtqdmBạn sẽ dùng matplotlib để vẽ biểu đồ, tensorflow làm thư viện backend cho Keras và tqdm để hiển thị thanh tiến trình đẹp cho mỗi epoch (vòng lặp).
Bước tiếp theo là tạo một script Python. Trong script này, trước tiên bạn cần import tất cả các mô-đun và hàm sẽ dùng. Phần giải thích cho từng cái sẽ được đưa ra khi chúng được sử dụng.
import os
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
from keras.layers import Input
from keras.models import Model, Sequential
from keras.layers.core import Dense, Dropout
from keras.layers.advanced_activations import LeakyReLU
from keras.datasets import mnist
from keras.optimizers import Adam
from keras import initializers
Bây giờ bạn muốn thiết lập một số biến:
# Let Keras know that we are using tensorflow as our backend engine
os.environ["KERAS_BACKEND"] = "tensorflow"
# To make sure that we can reproduce the experiment and get the same results
np.random.seed(10)
# The dimension of our random noise vector.
random_dim = 100
Trước khi bắt đầu xây dựng bộ phân biệt và bộ sinh, bạn nên thu thập và tiền xử lý dữ liệu. Bạn sẽ dùng bộ dữ liệu MNIST nổi tiếng gồm tập ảnh các chữ số đơn từ 0 đến 9.

def load_minst_data():
# load the data
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# normalize our inputs to be in the range[-1, 1]
x_train = (x_train.astype(np.float32) - 127.5)/127.5
# convert x_train with a shape of (60000, 28, 28) to (60000, 784) so we have
# 784 columns per row
x_train = x_train.reshape(60000, 784)
return (x_train, y_train, x_test, y_test)
Lưu ý rằng mnist.load_data() là một phần của Keras và cho phép bạn dễ dàng nhập bộ dữ liệu MNIST vào không gian làm việc.
Bây giờ, bạn có thể tạo các mạng sinh và phân biệt. Bạn sẽ dùng bộ tối ưu Adam cho cả hai mạng. Với cả bộ sinh và bộ phân biệt, bạn sẽ tạo một mạng nơ-ron có ba tầng ẩn với hàm kích hoạt là Leaky ReLU. Bạn cũng nên thêm các tầng dropout cho bộ phân biệt để cải thiện khả năng chống quá khớp trên ảnh chưa từng thấy.
# You will use the Adam optimizer
def get_optimizer():
return Adam(lr=0.0002, beta_1=0.5)
def get_generator(optimizer):
generator = Sequential()
generator.add(Dense(256, input_dim=random_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
generator.add(LeakyReLU(0.2))
generator.add(Dense(512))
generator.add(LeakyReLU(0.2))
generator.add(Dense(1024))
generator.add(LeakyReLU(0.2))
generator.add(Dense(784, activation='tanh'))
generator.compile(loss='binary_crossentropy', optimizer=optimizer)
return generator
def get_discriminator(optimizer):
discriminator = Sequential()
discriminator.add(Dense(1024, input_dim=784, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(512))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(256))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer)
return discriminator
Đã đến lúc kết hợp bộ sinh và bộ phân biệt lại với nhau!
def get_gan_network(discriminator, random_dim, generator, optimizer):
# We initially set trainable to False since we only want to train either the
# generator or discriminator at a time
discriminator.trainable = False
# gan input (noise) will be 100-dimensional vectors
gan_input = Input(shape=(random_dim,))
# the output of the generator (an image)
x = generator(gan_input)
# get the output of the discriminator (probability if the image is real or not)
gan_output = discriminator(x)
gan = Model(inputs=gan_input, outputs=gan_output)
gan.compile(loss='binary_crossentropy', optimizer=optimizer)
return gan
Để đầy đủ, bạn có thể tạo một hàm sẽ lưu lại ảnh được sinh ra sau mỗi 20 epoch. Vì đây không phải trọng tâm của bài học, bạn không cần phải hiểu tường tận hàm này.
# Create a wall of generated MNIST images
def plot_generated_images(epoch, generator, examples=100, dim=(10, 10), figsize=(10, 10)):
noise = np.random.normal(0, 1, size=[examples, random_dim])
generated_images = generator.predict(noise)
generated_images = generated_images.reshape(examples, 28, 28)
plt.figure(figsize=figsize)
for i in range(generated_images.shape[0]):
plt.subplot(dim[0], dim[1], i+1)
plt.imshow(generated_images[i], interpolation='nearest', cmap='gray_r')
plt.axis('off')
plt.tight_layout()
plt.savefig('gan_generated_image_epoch_%d.png' % epoch)
Bạn đã viết xong phần lớn mạng của mình. Việc còn lại là huấn luyện mạng này và xem các ảnh bạn tạo ra.
def train(epochs=1, batch_size=128):
# Get the training and testing data
x_train, y_train, x_test, y_test = load_minst_data()
# Split the training data into batches of size 128
batch_count = x_train.shape[0] / batch_size
# Build our GAN netowrk
adam = get_optimizer()
generator = get_generator(adam)
discriminator = get_discriminator(adam)
gan = get_gan_network(discriminator, random_dim, generator, adam)
for e in xrange(1, epochs+1):
print '-'*15, 'Epoch %d' % e, '-'*15
for _ in tqdm(xrange(batch_count)):
# Get a random set of input noise and images
noise = np.random.normal(0, 1, size=[batch_size, random_dim])
image_batch = x_train[np.random.randint(0, x_train.shape[0], size=batch_size)]
# Generate fake MNIST images
generated_images = generator.predict(noise)
X = np.concatenate([image_batch, generated_images])
# Labels for generated and real data
y_dis = np.zeros(2*batch_size)
# One-sided label smoothing
y_dis[:batch_size] = 0.9
# Train discriminator
discriminator.trainable = True
discriminator.train_on_batch(X, y_dis)
# Train generator
noise = np.random.normal(0, 1, size=[batch_size, random_dim])
y_gen = np.ones(batch_size)
discriminator.trainable = False
gan.train_on_batch(noise, y_gen)
if e == 1 or e % 20 == 0:
plot_generated_images(e, generator)
if __name__ == '__main__':
train(400, 128)
Sau khi huấn luyện 400 epoch, bạn có thể xem các ảnh đã được sinh. Nhìn vào ảnh tạo ra sau epoch đầu tiên, bạn sẽ thấy chúng chưa có cấu trúc rõ ràng; sau 40 epoch, các chữ số bắt đầu hình thành; và cuối cùng, ảnh sau 400 epoch cho thấy các chữ số rõ ràng dù vẫn còn vài cái khó nhận dạng.
| Kết quả sau 1 epoch | Kết quả sau 40 epoch | Kết quả sau 400 epoch |
|---|---|---|
!![]() |
![]() |
![]() |
Đoạn mã này mất khoảng 2 phút mỗi epoch trên CPU và đó là lý do chính đoạn mã này được chọn. Bạn có thể thử nghiệm bằng cách dùng nhiều epoch hơn và thêm nhiều (và khác) tầng cho cả bộ sinh và bộ phân biệt. Tuy nhiên, khi dùng các kiến trúc phức tạp và sâu hơn, thời gian chạy cũng sẽ tăng nếu bạn chỉ dùng CPU. Dù vậy, đừng để điều đó cản trở bạn thử nghiệm!
Chúc mừng, bạn đã hoàn thành hướng dẫn này, trong đó bạn đã học những điều cơ bản về Mạng Đối nghịch Sinh (GAN) theo cách trực quan! Bạn cũng đã triển khai mô hình đầu tiên với sự trợ giúp của thư viện Keras. Nếu muốn biết thêm về học sâu với Python, hãy cân nhắc khóa học Deep Learning in Python của DataCamp.
Tìm hiểu thêm về Python và Deep Learning
Khóa học
Khóa học
Khóa học
blog
Matt Crabtree
10 phút