Generate Artificial Faces with CelebA Progressive GAN Model

Econ 425T / Biostat 203B

Author

Dr. Hua Zhou @ UCLA

Published

March 6, 2023

Source: https://www.tensorflow.org/hub/tutorials/tf_hub_generative_image_module

Display system information for reproducibility.

import IPython
print(IPython.sys_info())
{'commit_hash': 'add5877a4',
 'commit_source': 'installation',
 'default_encoding': 'utf-8',
 'ipython_path': '/Users/huazhou/opt/anaconda3/lib/python3.9/site-packages/IPython',
 'ipython_version': '8.8.0',
 'os_name': 'posix',
 'platform': 'macOS-10.16-x86_64-i386-64bit',
 'sys_executable': '/Users/huazhou/opt/anaconda3/bin/python3',
 'sys_platform': 'darwin',
 'sys_version': '3.9.12 (main, Apr  5 2022, 01:56:13) \n[Clang 12.0.0 ]'}

Imports and function definitions:

from absl import logging

import imageio
import PIL.Image
import matplotlib.pyplot as plt
import numpy as np

import tensorflow as tf
tf.random.set_seed(0)

import tensorflow_hub as hub
from tensorflow_docs.vis import embed
import time

try:
  from google.colab import files
except ImportError:
  pass

from IPython import display
from skimage import transform

# We could retrieve this value from module.get_input_shapes() if we didn't know
# beforehand which module we will be using.
latent_dim = 512


# Interpolates between two vectors that are non-zero and don't both lie on a
# line going through origin. First normalizes v2 to have the same norm as v1. 
# Then interpolates between the two vectors on the hypersphere.
def interpolate_hypersphere(v1, v2, num_steps):
  v1_norm = tf.norm(v1)
  v2_norm = tf.norm(v2)
  v2_normalized = v2 * (v1_norm / v2_norm)

  vectors = []
  for step in range(num_steps):
    interpolated = v1 + (v2_normalized - v1) * step / (num_steps - 1)
    interpolated_norm = tf.norm(interpolated)
    interpolated_normalized = interpolated * (v1_norm / interpolated_norm)
    vectors.append(interpolated_normalized)
  return tf.stack(vectors)

# Simple way to display an image.
def display_image(image, fn = 'image.jpeg'):
  image = tf.constant(image)
  image = tf.image.convert_image_dtype(image, tf.uint8)
  PIL.Image.fromarray(image.numpy()).save(fn)
  return embed.embed_file(fn)

# Given a set of images, show an animation.
def animate(images, fn = 'animation.gif'):
  images = np.array(images)
  converted_images = np.clip(images * 255, 0, 255).astype(np.uint8)
  imageio.mimsave(fn, converted_images)
  return embed.embed_file(fn)

logging.set_verbosity(logging.ERROR)

1 Latent space interpolation

Latent space interpolation between two randomly initialized vectors. We will use a TF Hub module progan-128 that contains a pre-trained Progressive GAN.

progan = hub.load("https://tfhub.dev/google/progan-128/1").signatures['default']
progan
<ConcreteFunction pruned(latent_vector) at 0x7FF54F616CD0>

Interpolate between two latent random vectors:

def interpolate_between_vectors():
  v1 = tf.random.normal([latent_dim])
  v2 = tf.random.normal([latent_dim])

  # Creates a tensor with 25 steps of interpolation between v1 and v2.
  vectors = interpolate_hypersphere(v1, v2, 50)

  # Uses module to generate images from the latent space.
  interpolated_images = progan(vectors)['default']

  return interpolated_images

interpolated_images = interpolate_between_vectors()
animate(interpolated_images, 'anim1.gif')

2 Finding closest vector in latent space

Fix a target image. As an example use an image generated from the module or upload your own.

image_from_module_space = True  # @param { isTemplate:true, type:"boolean" }

def get_module_space_image():
  vector = tf.random.normal([1, latent_dim])
  images = progan(vector)['default'][0]
  return images

def upload_image():
  uploaded = files.upload()
  image = imageio.imread(uploaded[list(uploaded.keys())[0]])
  return transform.resize(image, [128, 128])

if image_from_module_space:
  target_image = get_module_space_image()
else:
  target_image = upload_image()

display_image(target_image, 'tgtimg.jpeg')

After defining a loss function between the target image and the image generated by a latent space variable, we can use gradient descent to find variable values that minimize the loss.

tf.random.set_seed(42)
initial_vector = tf.random.normal([1, latent_dim])
display_image(progan(initial_vector)['default'][0], 'genimg.jpeg')
def find_closest_latent_vector(initial_vector, num_optimization_steps,
                               steps_per_image):
  images = []
  losses = []

  vector = tf.Variable(initial_vector)  
  optimizer = tf.optimizers.Adam(learning_rate = 0.01)
  loss_fn = tf.losses.MeanAbsoluteError(reduction = "sum")

  for step in range(num_optimization_steps):
    if (step % 100) == 0:
      print()
    print('.', end = '')
    with tf.GradientTape() as tape:
      image = progan(vector.read_value())['default'][0]
      if (step % steps_per_image) == 0:
        images.append(image.numpy())
      target_image_difference = loss_fn(image, target_image[:,:,:3])
      # The latent vectors were sampled from a normal distribution. We can get
      # more realistic images if we regularize the length of the latent vector to 
      # the average length of vector from this distribution.
      regularizer = tf.abs(tf.norm(vector) - np.sqrt(latent_dim))

      loss = target_image_difference + regularizer
      losses.append(loss.numpy())
    grads = tape.gradient(loss, [vector])
    optimizer.apply_gradients(zip(grads, [vector]))

  return images, losses


num_optimization_steps = 200
steps_per_image = 5
images, loss = find_closest_latent_vector(
  initial_vector, 
  num_optimization_steps, 
  steps_per_image
  )

....................................................................................................
....................................................................................................
plt.figure()
plt.plot(loss)
plt.ylim([0, max(plt.ylim())])
(0.0, 6696.255889892578)
plt.show()

animate(np.stack(images), 'anim2.gif')

Compare the result to the target:

display_image(np.concatenate([images[-1], target_image], axis = 1), 'compare.jpeg')