Menu iconMenu iconGenerative Deep Learning Updated Edition
Generative Deep Learning Updated Edition

Chapter 4: Project Face Generation with GANs

4.4 Generating New Faces

After training our GAN model, the next exciting step is to generate new faces. This section will guide you through the process of generating new images using the trained generator model. We will cover how to generate images from random noise, how to save these images, and how to fine-tune the generator to improve the quality of the generated faces.

4.4.1 Generating Images from Random Noise

The generator model, once trained, can take a random noise vector as input and transform it into a realistic face image. This noise vector, often called a latent vector, is sampled from a standard normal distribution. The process of generating new faces involves sampling multiple latent vectors and passing them through the generator.

Example: Generating Images Code

Here’s how you can generate and visualize new face images using the trained generator model:

import numpy as np
import matplotlib.pyplot as plt

# Function to generate and plot new faces
def generate_and_plot_faces(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot new faces
latent_dim = 100
generate_and_plot_faces(generator, latent_dim, n_samples=10)

The 'generate_and_plot_faces' function takes a generator (presumably a trained model, like a Generative Adversarial Network), a given latent dimension, and a specified number of samples.

The function generates 'noise' following a normal distribution, that is then input into the generator model to create the 'generated_images'. These images are rescaled from a [-1, 1] range to [0, 255] to match the standard RGB scale.

The images are then plotted on a grid using matplotlib. Each image is displayed as a subplot in a single row. The 'latent_dim' variable is set to 100 and the function is called to generate and plot 10 new faces.

4.4.2 Saving Generated Images

To save the generated images for further use or sharing, we can utilize image processing libraries such as PIL (Python Imaging Library). This allows us to save the generated images in various formats such as PNG or JPEG.

Example: Saving Images Code

Here’s how you can save the generated images to disk:

from PIL import Image

# Function to generate and save new faces
def generate_and_save_faces(generator, latent_dim, n_samples=10, save_dir='generated_faces'):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    for i in range(n_samples):
        img = Image.fromarray(generated_images[i])
        img.save(f'{save_dir}/face_{i}.png')

# Generate and save new faces
generate_and_save_faces(generator, latent_dim, n_samples=10)

The function generate_and_save_faces takes in a generator model, a latent dimension size, a number of samples, and a directory to save the images in. It generates random noise vectors, uses those as input to the generator to create new images, scales the pixel values of the generated images to be between 0 and 255, and then saves the images as .png files in the specified directory.

4.4.3 Fine-Tuning the Generator

While our generator may already produce impressive results, there are always ways to fine-tune and improve the quality of the generated images. Fine-tuning can involve adjusting the model architecture, hyperparameters, or training process. Here are some strategies:

  1. Hyperparameter Tuning:
    • Experiment with different learning rates, batch sizes, and optimizer settings to find the best combination for your dataset.
  2. Data Augmentation:
    • Increase the diversity of the training data by applying various augmentation techniques such as rotation, zoom, and color shifts.
  3. Architectural Improvements:
    • Experiment with different generator and discriminator architectures, such as adding more layers, using different types of layers, or adjusting the layer sizes.
  4. Longer Training:
    • Train the model for more epochs or use early stopping with checkpoints to ensure the best performing model is saved.
  5. Progressive Growing:
    • Start with a lower resolution and gradually increase the resolution of the images during training. This technique can help the model learn more effectively at each scale.

Example: Fine-Tuning Code

Here’s how you can adjust the learning rate and re-train the model for additional epochs:

# Adjust the learning rate and recompile the models
learning_rate = 0.0002
generator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)

discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy', metrics=['accuracy'])

discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
generated_img = generator(gan_input)
gan_output = discriminator(generated_img)
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer=generator_optimizer, loss='binary_crossentropy')

# Continue training with the adjusted learning rate
for epoch in range(epochs, epochs + 5000):
    idx = np.random.randint(0, train_images.shape[0], batch_size)
    real_images = train_images[idx]

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    fake_images = generator.predict(noise)

    d_loss_real = discriminator.train_on_batch(real_images, real)
    d_loss_fake = discriminator.train_on_batch(fake_images, fake)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    g_loss = gan.train_on_batch(noise, real)

    if epoch % sample_interval == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
        plot_generated_images(epoch, generator)

    if epoch % 1000 == 0:
        generator.save(f'generator_epoch_{epoch}.h5')
        discriminator.save(f'discriminator_epoch_{epoch}.h5')

It begins by adjusting the learning rate to 0.0002 and recompiling the models with this new learning rate. The discriminator's trainability is then set to False to freeze its weights when training the GAN.

In the training loop, real images are sampled from the training set and fake images are generated by the generator. The discriminator is trained on both these real and fake images. The generator is then trained to fool the discriminator using the GAN model.

Every few epochs (as defined by 'sample_interval'), the losses of the discriminator and generator and the accuracy of the discriminator are printed. Images generated by the generator are also plotted.

Every 1000 epochs, the current models of the generator and the discriminator are saved. This allows for the training process to be continued later from these models or for these models to be used to generate new data.

4.4.4 Evaluating the Generated Faces

Once you have generated faces, it's important to evaluate their quality both quantitatively and qualitatively. Quantitative evaluation can be done using metrics such as Inception Score (IS) and Fréchet Inception Distance (FID), as covered in previous sections. Qualitative evaluation involves visually inspecting the images to assess their realism and diversity.

Example: Evaluation Code

Here’s a brief recap of how to evaluate using Inception Score and FID:

# Calculate Inception Score
is_mean, is_std = calculate_inception_score(generated_images)
print(f"Inception Score: {is_mean} ± {is_std}")

# Calculate FID Score
real_images = x_train[np.random.choice(x_train.shape[0], 1000, replace=False)]
fid_score = calculate_fid(real_images, generated_images)
print(f"FID Score: {fid_score}")

The first part calculates the Inception Score of the generated images, which measures both the quality and diversity of the images. The score and its standard deviation are printed to the console.

The second part calculates the FID score between the generated images and a set of real images randomly selected from the training set. The FID score measures the similarity between the two sets of images; a lower FID indicates that the distributions of generated and real images are closer to one another. The calculated FID score is printed to the console.

By following these steps, you will be able to generate high-quality, realistic faces using your trained GAN model. Fine-tuning the model and evaluating the generated faces are crucial for achieving the best possible results.

4.4 Generating New Faces

After training our GAN model, the next exciting step is to generate new faces. This section will guide you through the process of generating new images using the trained generator model. We will cover how to generate images from random noise, how to save these images, and how to fine-tune the generator to improve the quality of the generated faces.

4.4.1 Generating Images from Random Noise

The generator model, once trained, can take a random noise vector as input and transform it into a realistic face image. This noise vector, often called a latent vector, is sampled from a standard normal distribution. The process of generating new faces involves sampling multiple latent vectors and passing them through the generator.

Example: Generating Images Code

Here’s how you can generate and visualize new face images using the trained generator model:

import numpy as np
import matplotlib.pyplot as plt

# Function to generate and plot new faces
def generate_and_plot_faces(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot new faces
latent_dim = 100
generate_and_plot_faces(generator, latent_dim, n_samples=10)

The 'generate_and_plot_faces' function takes a generator (presumably a trained model, like a Generative Adversarial Network), a given latent dimension, and a specified number of samples.

The function generates 'noise' following a normal distribution, that is then input into the generator model to create the 'generated_images'. These images are rescaled from a [-1, 1] range to [0, 255] to match the standard RGB scale.

The images are then plotted on a grid using matplotlib. Each image is displayed as a subplot in a single row. The 'latent_dim' variable is set to 100 and the function is called to generate and plot 10 new faces.

4.4.2 Saving Generated Images

To save the generated images for further use or sharing, we can utilize image processing libraries such as PIL (Python Imaging Library). This allows us to save the generated images in various formats such as PNG or JPEG.

Example: Saving Images Code

Here’s how you can save the generated images to disk:

from PIL import Image

# Function to generate and save new faces
def generate_and_save_faces(generator, latent_dim, n_samples=10, save_dir='generated_faces'):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    for i in range(n_samples):
        img = Image.fromarray(generated_images[i])
        img.save(f'{save_dir}/face_{i}.png')

# Generate and save new faces
generate_and_save_faces(generator, latent_dim, n_samples=10)

The function generate_and_save_faces takes in a generator model, a latent dimension size, a number of samples, and a directory to save the images in. It generates random noise vectors, uses those as input to the generator to create new images, scales the pixel values of the generated images to be between 0 and 255, and then saves the images as .png files in the specified directory.

4.4.3 Fine-Tuning the Generator

While our generator may already produce impressive results, there are always ways to fine-tune and improve the quality of the generated images. Fine-tuning can involve adjusting the model architecture, hyperparameters, or training process. Here are some strategies:

  1. Hyperparameter Tuning:
    • Experiment with different learning rates, batch sizes, and optimizer settings to find the best combination for your dataset.
  2. Data Augmentation:
    • Increase the diversity of the training data by applying various augmentation techniques such as rotation, zoom, and color shifts.
  3. Architectural Improvements:
    • Experiment with different generator and discriminator architectures, such as adding more layers, using different types of layers, or adjusting the layer sizes.
  4. Longer Training:
    • Train the model for more epochs or use early stopping with checkpoints to ensure the best performing model is saved.
  5. Progressive Growing:
    • Start with a lower resolution and gradually increase the resolution of the images during training. This technique can help the model learn more effectively at each scale.

Example: Fine-Tuning Code

Here’s how you can adjust the learning rate and re-train the model for additional epochs:

# Adjust the learning rate and recompile the models
learning_rate = 0.0002
generator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)

discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy', metrics=['accuracy'])

discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
generated_img = generator(gan_input)
gan_output = discriminator(generated_img)
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer=generator_optimizer, loss='binary_crossentropy')

# Continue training with the adjusted learning rate
for epoch in range(epochs, epochs + 5000):
    idx = np.random.randint(0, train_images.shape[0], batch_size)
    real_images = train_images[idx]

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    fake_images = generator.predict(noise)

    d_loss_real = discriminator.train_on_batch(real_images, real)
    d_loss_fake = discriminator.train_on_batch(fake_images, fake)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    g_loss = gan.train_on_batch(noise, real)

    if epoch % sample_interval == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
        plot_generated_images(epoch, generator)

    if epoch % 1000 == 0:
        generator.save(f'generator_epoch_{epoch}.h5')
        discriminator.save(f'discriminator_epoch_{epoch}.h5')

It begins by adjusting the learning rate to 0.0002 and recompiling the models with this new learning rate. The discriminator's trainability is then set to False to freeze its weights when training the GAN.

In the training loop, real images are sampled from the training set and fake images are generated by the generator. The discriminator is trained on both these real and fake images. The generator is then trained to fool the discriminator using the GAN model.

Every few epochs (as defined by 'sample_interval'), the losses of the discriminator and generator and the accuracy of the discriminator are printed. Images generated by the generator are also plotted.

Every 1000 epochs, the current models of the generator and the discriminator are saved. This allows for the training process to be continued later from these models or for these models to be used to generate new data.

4.4.4 Evaluating the Generated Faces

Once you have generated faces, it's important to evaluate their quality both quantitatively and qualitatively. Quantitative evaluation can be done using metrics such as Inception Score (IS) and Fréchet Inception Distance (FID), as covered in previous sections. Qualitative evaluation involves visually inspecting the images to assess their realism and diversity.

Example: Evaluation Code

Here’s a brief recap of how to evaluate using Inception Score and FID:

# Calculate Inception Score
is_mean, is_std = calculate_inception_score(generated_images)
print(f"Inception Score: {is_mean} ± {is_std}")

# Calculate FID Score
real_images = x_train[np.random.choice(x_train.shape[0], 1000, replace=False)]
fid_score = calculate_fid(real_images, generated_images)
print(f"FID Score: {fid_score}")

The first part calculates the Inception Score of the generated images, which measures both the quality and diversity of the images. The score and its standard deviation are printed to the console.

The second part calculates the FID score between the generated images and a set of real images randomly selected from the training set. The FID score measures the similarity between the two sets of images; a lower FID indicates that the distributions of generated and real images are closer to one another. The calculated FID score is printed to the console.

By following these steps, you will be able to generate high-quality, realistic faces using your trained GAN model. Fine-tuning the model and evaluating the generated faces are crucial for achieving the best possible results.

4.4 Generating New Faces

After training our GAN model, the next exciting step is to generate new faces. This section will guide you through the process of generating new images using the trained generator model. We will cover how to generate images from random noise, how to save these images, and how to fine-tune the generator to improve the quality of the generated faces.

4.4.1 Generating Images from Random Noise

The generator model, once trained, can take a random noise vector as input and transform it into a realistic face image. This noise vector, often called a latent vector, is sampled from a standard normal distribution. The process of generating new faces involves sampling multiple latent vectors and passing them through the generator.

Example: Generating Images Code

Here’s how you can generate and visualize new face images using the trained generator model:

import numpy as np
import matplotlib.pyplot as plt

# Function to generate and plot new faces
def generate_and_plot_faces(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot new faces
latent_dim = 100
generate_and_plot_faces(generator, latent_dim, n_samples=10)

The 'generate_and_plot_faces' function takes a generator (presumably a trained model, like a Generative Adversarial Network), a given latent dimension, and a specified number of samples.

The function generates 'noise' following a normal distribution, that is then input into the generator model to create the 'generated_images'. These images are rescaled from a [-1, 1] range to [0, 255] to match the standard RGB scale.

The images are then plotted on a grid using matplotlib. Each image is displayed as a subplot in a single row. The 'latent_dim' variable is set to 100 and the function is called to generate and plot 10 new faces.

4.4.2 Saving Generated Images

To save the generated images for further use or sharing, we can utilize image processing libraries such as PIL (Python Imaging Library). This allows us to save the generated images in various formats such as PNG or JPEG.

Example: Saving Images Code

Here’s how you can save the generated images to disk:

from PIL import Image

# Function to generate and save new faces
def generate_and_save_faces(generator, latent_dim, n_samples=10, save_dir='generated_faces'):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    for i in range(n_samples):
        img = Image.fromarray(generated_images[i])
        img.save(f'{save_dir}/face_{i}.png')

# Generate and save new faces
generate_and_save_faces(generator, latent_dim, n_samples=10)

The function generate_and_save_faces takes in a generator model, a latent dimension size, a number of samples, and a directory to save the images in. It generates random noise vectors, uses those as input to the generator to create new images, scales the pixel values of the generated images to be between 0 and 255, and then saves the images as .png files in the specified directory.

4.4.3 Fine-Tuning the Generator

While our generator may already produce impressive results, there are always ways to fine-tune and improve the quality of the generated images. Fine-tuning can involve adjusting the model architecture, hyperparameters, or training process. Here are some strategies:

  1. Hyperparameter Tuning:
    • Experiment with different learning rates, batch sizes, and optimizer settings to find the best combination for your dataset.
  2. Data Augmentation:
    • Increase the diversity of the training data by applying various augmentation techniques such as rotation, zoom, and color shifts.
  3. Architectural Improvements:
    • Experiment with different generator and discriminator architectures, such as adding more layers, using different types of layers, or adjusting the layer sizes.
  4. Longer Training:
    • Train the model for more epochs or use early stopping with checkpoints to ensure the best performing model is saved.
  5. Progressive Growing:
    • Start with a lower resolution and gradually increase the resolution of the images during training. This technique can help the model learn more effectively at each scale.

Example: Fine-Tuning Code

Here’s how you can adjust the learning rate and re-train the model for additional epochs:

# Adjust the learning rate and recompile the models
learning_rate = 0.0002
generator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)

discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy', metrics=['accuracy'])

discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
generated_img = generator(gan_input)
gan_output = discriminator(generated_img)
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer=generator_optimizer, loss='binary_crossentropy')

# Continue training with the adjusted learning rate
for epoch in range(epochs, epochs + 5000):
    idx = np.random.randint(0, train_images.shape[0], batch_size)
    real_images = train_images[idx]

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    fake_images = generator.predict(noise)

    d_loss_real = discriminator.train_on_batch(real_images, real)
    d_loss_fake = discriminator.train_on_batch(fake_images, fake)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    g_loss = gan.train_on_batch(noise, real)

    if epoch % sample_interval == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
        plot_generated_images(epoch, generator)

    if epoch % 1000 == 0:
        generator.save(f'generator_epoch_{epoch}.h5')
        discriminator.save(f'discriminator_epoch_{epoch}.h5')

It begins by adjusting the learning rate to 0.0002 and recompiling the models with this new learning rate. The discriminator's trainability is then set to False to freeze its weights when training the GAN.

In the training loop, real images are sampled from the training set and fake images are generated by the generator. The discriminator is trained on both these real and fake images. The generator is then trained to fool the discriminator using the GAN model.

Every few epochs (as defined by 'sample_interval'), the losses of the discriminator and generator and the accuracy of the discriminator are printed. Images generated by the generator are also plotted.

Every 1000 epochs, the current models of the generator and the discriminator are saved. This allows for the training process to be continued later from these models or for these models to be used to generate new data.

4.4.4 Evaluating the Generated Faces

Once you have generated faces, it's important to evaluate their quality both quantitatively and qualitatively. Quantitative evaluation can be done using metrics such as Inception Score (IS) and Fréchet Inception Distance (FID), as covered in previous sections. Qualitative evaluation involves visually inspecting the images to assess their realism and diversity.

Example: Evaluation Code

Here’s a brief recap of how to evaluate using Inception Score and FID:

# Calculate Inception Score
is_mean, is_std = calculate_inception_score(generated_images)
print(f"Inception Score: {is_mean} ± {is_std}")

# Calculate FID Score
real_images = x_train[np.random.choice(x_train.shape[0], 1000, replace=False)]
fid_score = calculate_fid(real_images, generated_images)
print(f"FID Score: {fid_score}")

The first part calculates the Inception Score of the generated images, which measures both the quality and diversity of the images. The score and its standard deviation are printed to the console.

The second part calculates the FID score between the generated images and a set of real images randomly selected from the training set. The FID score measures the similarity between the two sets of images; a lower FID indicates that the distributions of generated and real images are closer to one another. The calculated FID score is printed to the console.

By following these steps, you will be able to generate high-quality, realistic faces using your trained GAN model. Fine-tuning the model and evaluating the generated faces are crucial for achieving the best possible results.

4.4 Generating New Faces

After training our GAN model, the next exciting step is to generate new faces. This section will guide you through the process of generating new images using the trained generator model. We will cover how to generate images from random noise, how to save these images, and how to fine-tune the generator to improve the quality of the generated faces.

4.4.1 Generating Images from Random Noise

The generator model, once trained, can take a random noise vector as input and transform it into a realistic face image. This noise vector, often called a latent vector, is sampled from a standard normal distribution. The process of generating new faces involves sampling multiple latent vectors and passing them through the generator.

Example: Generating Images Code

Here’s how you can generate and visualize new face images using the trained generator model:

import numpy as np
import matplotlib.pyplot as plt

# Function to generate and plot new faces
def generate_and_plot_faces(generator, latent_dim, n_samples=10):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    plt.figure(figsize=(20, 2))
    for i in range(n_samples):
        plt.subplot(1, n_samples, i + 1)
        plt.imshow(generated_images[i])
        plt.axis('off')
    plt.show()

# Generate and plot new faces
latent_dim = 100
generate_and_plot_faces(generator, latent_dim, n_samples=10)

The 'generate_and_plot_faces' function takes a generator (presumably a trained model, like a Generative Adversarial Network), a given latent dimension, and a specified number of samples.

The function generates 'noise' following a normal distribution, that is then input into the generator model to create the 'generated_images'. These images are rescaled from a [-1, 1] range to [0, 255] to match the standard RGB scale.

The images are then plotted on a grid using matplotlib. Each image is displayed as a subplot in a single row. The 'latent_dim' variable is set to 100 and the function is called to generate and plot 10 new faces.

4.4.2 Saving Generated Images

To save the generated images for further use or sharing, we can utilize image processing libraries such as PIL (Python Imaging Library). This allows us to save the generated images in various formats such as PNG or JPEG.

Example: Saving Images Code

Here’s how you can save the generated images to disk:

from PIL import Image

# Function to generate and save new faces
def generate_and_save_faces(generator, latent_dim, n_samples=10, save_dir='generated_faces'):
    noise = np.random.normal(0, 1, (n_samples, latent_dim))
    generated_images = generator.predict(noise)
    generated_images = (generated_images * 127.5 + 127.5).astype(np.uint8)  # Rescale to [0, 255]

    for i in range(n_samples):
        img = Image.fromarray(generated_images[i])
        img.save(f'{save_dir}/face_{i}.png')

# Generate and save new faces
generate_and_save_faces(generator, latent_dim, n_samples=10)

The function generate_and_save_faces takes in a generator model, a latent dimension size, a number of samples, and a directory to save the images in. It generates random noise vectors, uses those as input to the generator to create new images, scales the pixel values of the generated images to be between 0 and 255, and then saves the images as .png files in the specified directory.

4.4.3 Fine-Tuning the Generator

While our generator may already produce impressive results, there are always ways to fine-tune and improve the quality of the generated images. Fine-tuning can involve adjusting the model architecture, hyperparameters, or training process. Here are some strategies:

  1. Hyperparameter Tuning:
    • Experiment with different learning rates, batch sizes, and optimizer settings to find the best combination for your dataset.
  2. Data Augmentation:
    • Increase the diversity of the training data by applying various augmentation techniques such as rotation, zoom, and color shifts.
  3. Architectural Improvements:
    • Experiment with different generator and discriminator architectures, such as adding more layers, using different types of layers, or adjusting the layer sizes.
  4. Longer Training:
    • Train the model for more epochs or use early stopping with checkpoints to ensure the best performing model is saved.
  5. Progressive Growing:
    • Start with a lower resolution and gradually increase the resolution of the images during training. This technique can help the model learn more effectively at each scale.

Example: Fine-Tuning Code

Here’s how you can adjust the learning rate and re-train the model for additional epochs:

# Adjust the learning rate and recompile the models
learning_rate = 0.0002
generator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate, beta_1=0.5)

discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy', metrics=['accuracy'])

discriminator.trainable = False
gan_input = tf.keras.Input(shape=(latent_dim,))
generated_img = generator(gan_input)
gan_output = discriminator(generated_img)
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer=generator_optimizer, loss='binary_crossentropy')

# Continue training with the adjusted learning rate
for epoch in range(epochs, epochs + 5000):
    idx = np.random.randint(0, train_images.shape[0], batch_size)
    real_images = train_images[idx]

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    fake_images = generator.predict(noise)

    d_loss_real = discriminator.train_on_batch(real_images, real)
    d_loss_fake = discriminator.train_on_batch(fake_images, fake)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    g_loss = gan.train_on_batch(noise, real)

    if epoch % sample_interval == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")
        plot_generated_images(epoch, generator)

    if epoch % 1000 == 0:
        generator.save(f'generator_epoch_{epoch}.h5')
        discriminator.save(f'discriminator_epoch_{epoch}.h5')

It begins by adjusting the learning rate to 0.0002 and recompiling the models with this new learning rate. The discriminator's trainability is then set to False to freeze its weights when training the GAN.

In the training loop, real images are sampled from the training set and fake images are generated by the generator. The discriminator is trained on both these real and fake images. The generator is then trained to fool the discriminator using the GAN model.

Every few epochs (as defined by 'sample_interval'), the losses of the discriminator and generator and the accuracy of the discriminator are printed. Images generated by the generator are also plotted.

Every 1000 epochs, the current models of the generator and the discriminator are saved. This allows for the training process to be continued later from these models or for these models to be used to generate new data.

4.4.4 Evaluating the Generated Faces

Once you have generated faces, it's important to evaluate their quality both quantitatively and qualitatively. Quantitative evaluation can be done using metrics such as Inception Score (IS) and Fréchet Inception Distance (FID), as covered in previous sections. Qualitative evaluation involves visually inspecting the images to assess their realism and diversity.

Example: Evaluation Code

Here’s a brief recap of how to evaluate using Inception Score and FID:

# Calculate Inception Score
is_mean, is_std = calculate_inception_score(generated_images)
print(f"Inception Score: {is_mean} ± {is_std}")

# Calculate FID Score
real_images = x_train[np.random.choice(x_train.shape[0], 1000, replace=False)]
fid_score = calculate_fid(real_images, generated_images)
print(f"FID Score: {fid_score}")

The first part calculates the Inception Score of the generated images, which measures both the quality and diversity of the images. The score and its standard deviation are printed to the console.

The second part calculates the FID score between the generated images and a set of real images randomly selected from the training set. The FID score measures the similarity between the two sets of images; a lower FID indicates that the distributions of generated and real images are closer to one another. The calculated FID score is printed to the console.

By following these steps, you will be able to generate high-quality, realistic faces using your trained GAN model. Fine-tuning the model and evaluating the generated faces are crucial for achieving the best possible results.