{"id":2322,"date":"2019-07-02T19:00:59","date_gmt":"2019-07-02T19:00:59","guid":{"rendered":"https:\/\/www.aiproblog.com\/index.php\/2019\/07\/02\/how-to-explore-the-gan-latent-space-when-generating-faces\/"},"modified":"2019-07-02T19:00:59","modified_gmt":"2019-07-02T19:00:59","slug":"how-to-explore-the-gan-latent-space-when-generating-faces","status":"publish","type":"post","link":"https:\/\/www.aiproblog.com\/index.php\/2019\/07\/02\/how-to-explore-the-gan-latent-space-when-generating-faces\/","title":{"rendered":"How to Explore the GAN Latent Space When Generating Faces"},"content":{"rendered":"<p>Author: Jason Brownlee<\/p>\n<div>\n<p>Generative Adversarial Networks, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images.<\/p>\n<p>The generative model in the GAN architecture learns to map points in the latent space to generated images. The latent space has no meaning other than the meaning applied to it via the generative model. Yet, the latent space has structure that can be explored, such as by interpolating between points and performing vector arithmetic between points in latent space which have meaningful and targeted effects on the generated images.<\/p>\n<p>In this tutorial, you will discover how to develop a generative adversarial network for face generation and explore the structure of latent space and the effect on generated faces.<\/p>\n<p>After completing this tutorial, you will know:<\/p>\n<ul>\n<li>How to develop a generative adversarial network for generating faces.<\/li>\n<li>How to interpolate between points in latent space and generate images that morph from one face to another.<\/li>\n<li>How to perform vector arithmetic in latent space and achieve targeted results in the resulting generated faces.<\/li>\n<\/ul>\n<p>Let\u2019s get started.<\/p>\n<div id=\"attachment_8152\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8152\" class=\"size-full wp-image-8152\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/07\/How-to-Interpolate-and-Perform-Vector-Arithmetic-With-Faces-Using-a-Generative-Adversarial-Network.jpg\" alt=\"How to Interpolate and Perform Vector Arithmetic With Faces Using a Generative Adversarial Network\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/07\/How-to-Interpolate-and-Perform-Vector-Arithmetic-With-Faces-Using-a-Generative-Adversarial-Network.jpg 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/07\/How-to-Interpolate-and-Perform-Vector-Arithmetic-With-Faces-Using-a-Generative-Adversarial-Network-300x225.jpg 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8152\" class=\"wp-caption-text\">How to Interpolate and Perform Vector Arithmetic With Faces Using a Generative Adversarial Network.<br \/>Photo by <a href=\"https:\/\/www.flickr.com\/photos\/107640324@N05\/45701362542\">Intermountain Forest Service<\/a>, some rights reserved.<\/p>\n<\/div>\n<h2>Tutorial Overview<\/h2>\n<p>This tutorial is divided into five parts; they are:<\/p>\n<ol>\n<li>Vector Arithmetic in Latent Space<\/li>\n<li>Large-Scale CelebFaces Dataset (CelebA)<\/li>\n<li>How to Prepare CelebA Faces Dataset<\/li>\n<li>How to Develop a Generative Adversarial Network<\/li>\n<li>How to Explore the Latent Space for Generated Faces<\/li>\n<\/ol>\n<h2>Vector Arithmetic in Latent Space<\/h2>\n<p>The generator model in the GAN architecture takes a point from the latent space as input and generates a new image.<\/p>\n<p>The latent space itself has no meaning. Typically it is a 100-dimensional hypersphere with each variable drawn from a <a href=\"https:\/\/machinelearningmastery.com\/statistical-data-distributions\/\">Gaussian distribution<\/a> with a mean of zero and a standard deviation of one. Through training, the generator learns to map points into the latent space with specific output images and this mapping will be different each time the model is trained.<\/p>\n<p>The latent space has structure when interpreted by the generator model, and this structure can be queried and navigated for a given model.<\/p>\n<p>Typically, new images are generated using random points in the latent space. Taken a step further, points in the latent space can be constructed (e.g. all 0s, all 0.5s, or all 1s) and used as input or a query to generate a specific image.<\/p>\n<p>A series of points can be created on a linear path between two points in the latent space, such as two generated images. These points can be used to generate a series of images that show a transition between the two generated images.<\/p>\n<p>Finally, the points in the latent space can be kept and used in simple vector arithmetic to create new points in the latent space that, in turn, can be used to generate images. This is an interesting idea, as it allows for the intuitive and targeted generation of images.<\/p>\n<p>The important 2015 paper by Alec Radford, et al. titled \u201c<a href=\"https:\/\/arxiv.org\/abs\/1511.06434\">Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks<\/a>\u201d introduced a stable model configuration for training deep convolutional neural network models as part of the GAN architecture.<\/p>\n<p>In the paper, the authors explored the latent space for GANs fit on a number of different training datasets, most notably a dataset of celebrity faces. They demonstrated two interesting aspects.<\/p>\n<p>The first was the vector arithmetic with faces. For example, a face of a smiling woman minus the face of a neutral woman plus the face of a neutral man resulted in the face of a smiling man.<\/p>\n<pre class=\"crayon-plain-tag\">smiling woman - neutral woman + neutral man = smiling man<\/pre>\n<p>Specifically, the arithmetic was performed on the points in the latent space for the resulting faces. Actually on the average of multiple faces with a given characteristic, to provide a more robust result.<\/p>\n<div id=\"attachment_8141\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8141\" class=\"size-large wp-image-8141\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Example-of-Vector-Arithmetic-on-Points-in-the-Latent-Space-for-Generating-Faces-with-a-GAN-1024x531.png\" alt=\"Example of Vector Arithmetic on Points in the Latent Space for Generating Faces with a GAN\" width=\"1024\" height=\"531\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Vector-Arithmetic-on-Points-in-the-Latent-Space-for-Generating-Faces-with-a-GAN-1024x531.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Vector-Arithmetic-on-Points-in-the-Latent-Space-for-Generating-Faces-with-a-GAN-300x155.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Vector-Arithmetic-on-Points-in-the-Latent-Space-for-Generating-Faces-with-a-GAN-768x398.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Vector-Arithmetic-on-Points-in-the-Latent-Space-for-Generating-Faces-with-a-GAN.png 1104w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8141\" class=\"wp-caption-text\">Example of Vector Arithmetic on Points in the Latent Space for Generating Faces With a GAN.<br \/>Taken from Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks.<\/p>\n<\/div>\n<p>The second demonstration was the transition between two generated faces, specifically by creating a linear path through the latent dimension between the points that generated two faces and then generating all of the faces for the points along the path.<\/p>\n<div id=\"attachment_8142\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8142\" class=\"size-large wp-image-8142\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Example-of-Faces-on-a-Path-Between-Two-GAN-Generated-Faces-1024x606.png\" alt=\"Example of Faces on a Path Between Two GAN Generated Faces\" width=\"1024\" height=\"606\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Faces-on-a-Path-Between-Two-GAN-Generated-Faces-1024x606.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Faces-on-a-Path-Between-Two-GAN-Generated-Faces-300x178.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Faces-on-a-Path-Between-Two-GAN-Generated-Faces-768x455.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Faces-on-a-Path-Between-Two-GAN-Generated-Faces.png 1088w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8142\" class=\"wp-caption-text\">Example of Faces on a Path Between Two GAN Generated Faces.<br \/>Taken from Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks.<\/p>\n<\/div>\n<p>Exploring the structure of the latent space for a GAN model is both interesting for the problem domain and helps to develop an intuition for what has been learned by the generator model.<\/p>\n<p>In this tutorial, we will develop a GAN for generating photos of faces, then explore the latent space for the model with vector arithmetic.<\/p>\n<h2>Large-Scale CelebFaces Dataset (CelebA)<\/h2>\n<p>The first step is to select a dataset of faces.<\/p>\n<p>In this tutorial, we will use the <a href=\"http:\/\/mmlab.ie.cuhk.edu.hk\/projects\/CelebA.html\">Large-scale CelebFaces Attributes Dataset<\/a>, referred to as CelebA. This dataset was developed and published by Ziwei Liu, et al. for their 2015 paper titled \u201c<a href=\"https:\/\/arxiv.org\/abs\/1509.06451\">From Facial Parts Responses to Face Detection: A Deep Learning Approach<\/a>.\u201d<\/p>\n<p>The dataset provides about 200,000 photographs of celebrity faces along with annotations for what appears in given photos, such as glasses, face shape, hats, hair type, etc. As part of the dataset, the authors provide a version of each photo centered on the face and cropped to the portrait with varying sizes around 150 pixels wide and 200 pixels tall. We will use this as the basis for developing our GAN model.<\/p>\n<p>The dataset can be easily downloaded from the Kaggle webpage. Note: this may require an account with Kaggle.<\/p>\n<ul>\n<li><a href=\"https:\/\/www.kaggle.com\/jessicali9530\/celeba-dataset\">CelebFaces Attributes (CelebA) Dataset<\/a><\/li>\n<\/ul>\n<p>Specifically, download the file \u201c<em>img_align_celeba.zip<\/em>\u201d which is about 1.3 gigabytes. To do this, click on the filename on the Kaggle website and then click the download icon.<\/p>\n<p>The download might take a while depending on the speed of your internet connection.<\/p>\n<p>After downloading, unzip the archive.<\/p>\n<p>This will create a new directory named \u201c<em>img_align_celeba<\/em>\u201d that contains all of the images with filenames like <em>202599.jpg<\/em> and <em>202598.jpg<\/em>.<\/p>\n<p>Next, we can look at preparing the raw images for modeling.<\/p>\n<h2>How to Prepare CelebA Faces Dataset<\/h2>\n<p>The first step is to develop code to load the images.<\/p>\n<p>We can use the <a href=\"https:\/\/machinelearningmastery.com\/how-to-load-and-manipulate-images-for-deep-learning-in-python-with-pil-pillow\/\">Pillow library<\/a> to load a given image file, convert it to RGB format (if needed) and return an array of pixel data. The load_image() function below implements this.<\/p>\n<pre class=\"crayon-plain-tag\"># load an image as an rgb numpy array\r\ndef load_image(filename):\r\n\t# load image from file\r\n\timage = Image.open(filename)\r\n\t# convert to RGB, if needed\r\n\timage = image.convert('RGB')\r\n\t# convert to array\r\n\tpixels = asarray(image)\r\n\treturn pixels<\/pre>\n<p>Next, we can enumerate the directory of images, load each as an array of pixels in turn, and return an array with all of the images.<\/p>\n<p>There are 200K images in the dataset, which is probably more than we need so we can also limit the number of images to load with an argument. The <em>load_faces()<\/em> function below implements this.<\/p>\n<pre class=\"crayon-plain-tag\"># load images and extract faces for all images in a directory\r\ndef load_faces(directory, n_faces):\r\n\tfaces = list()\r\n\t# enumerate files\r\n\tfor filename in listdir(directory):\r\n\t\t# load the image\r\n\t\tpixels = load_image(directory + filename)\r\n\t\t# store\r\n\t\tfaces.append(pixels)\r\n\t\t# stop once we have enough\r\n\t\tif len(faces) >= n_faces:\r\n\t\t\tbreak\r\n\treturn asarray(faces)<\/pre>\n<p>Finally, once the images are loaded, we can plot them using the <em>imshow()<\/em> function from the matplotlib library.<\/p>\n<p>The <em>plot_faces()<\/em> function below does this, plotting images arranged into in a square.<\/p>\n<pre class=\"crayon-plain-tag\"># plot a list of loaded faces\r\ndef plot_faces(faces, n):\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(faces[i])\r\n\tpyplot.show()<\/pre>\n<p>Tying this together, the complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># load and plot faces\r\nfrom os import listdir\r\nfrom numpy import asarray\r\nfrom PIL import Image\r\nfrom matplotlib import pyplot\r\n\r\n# load an image as an rgb numpy array\r\ndef load_image(filename):\r\n\t# load image from file\r\n\timage = Image.open(filename)\r\n\t# convert to RGB, if needed\r\n\timage = image.convert('RGB')\r\n\t# convert to array\r\n\tpixels = asarray(image)\r\n\treturn pixels\r\n\r\n# load images and extract faces for all images in a directory\r\ndef load_faces(directory, n_faces):\r\n\tfaces = list()\r\n\t# enumerate files\r\n\tfor filename in listdir(directory):\r\n\t\t# load the image\r\n\t\tpixels = load_image(directory + filename)\r\n\t\t# store\r\n\t\tfaces.append(pixels)\r\n\t\t# stop once we have enough\r\n\t\tif len(faces) >= n_faces:\r\n\t\t\tbreak\r\n\treturn asarray(faces)\r\n\r\n# plot a list of loaded faces\r\ndef plot_faces(faces, n):\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(faces[i])\r\n\tpyplot.show()\r\n\r\n# directory that contains all images\r\ndirectory = 'img_align_celeba\/'\r\n# load and extract all faces\r\nfaces = load_faces(directory, 25)\r\nprint('Loaded: ', faces.shape)\r\n# plot faces\r\nplot_faces(faces, 5)<\/pre>\n<p>Running the example loads a total of 25 images from the directory, then summarizes the size of the returned array.<\/p>\n<pre class=\"crayon-plain-tag\">Loaded: (25, 218, 178, 3)<\/pre>\n<p>Finally, the 25 images are plotted in a 5\u00d75 square.<\/p>\n<div id=\"attachment_8143\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8143\" class=\"size-large wp-image-8143\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-a-Sample-of-25-Faces-from-the-Celebrity-Faces-Dataset-1024x768.png\" alt=\"Plot of a Sample of 25 Faces from the Celebrity Faces Dataset\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-a-Sample-of-25-Faces-from-the-Celebrity-Faces-Dataset-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-a-Sample-of-25-Faces-from-the-Celebrity-Faces-Dataset-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-a-Sample-of-25-Faces-from-the-Celebrity-Faces-Dataset-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-a-Sample-of-25-Faces-from-the-Celebrity-Faces-Dataset.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8143\" class=\"wp-caption-text\">Plot of a Sample of 25 Faces from the Celebrity Faces Dataset<\/p>\n<\/div>\n<p>When working with a GAN, it is easier to model a dataset if all of the images are small and square in shape.<\/p>\n<p>Further, as we are only interested in the face in each photo, and not the background, we can perform face detection and extract only the face before resizing the result to a fixed size.<\/p>\n<p>There are many ways to perform face detection. In this case, we will use a pre-trained Multi-Task Cascaded Convolutional Neural Network, or MTCNN. This is a state-of-the-art deep learning model for face detection, described in the 2016 paper titled \u201c<a href=\"https:\/\/arxiv.org\/abs\/1604.02878\">Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks<\/a>.\u201d<\/p>\n<p>We will use the implementation provided by Iv\u00e1n de Paz Centeno in the <a href=\"https:\/\/github.com\/ipazc\/mtcnn\">ipazc\/mtcnn project<\/a>. This library can be installed via pip as follows:<\/p>\n<pre class=\"crayon-plain-tag\">sudo pip install mtcnn<\/pre>\n<p>We can confirm that the library was installed correctly by importing the library and printing the version; for example:<\/p>\n<pre class=\"crayon-plain-tag\"># confirm mtcnn was installed correctly\r\nimport mtcnn\r\n# print version\r\nprint(mtcnn.__version__)<\/pre>\n<p>Running the example prints the current version of the library.<\/p>\n<pre class=\"crayon-plain-tag\">0.0.8<\/pre>\n<p>The MTCNN model is very easy to use.<\/p>\n<p>First, an instance of the MTCNN model is created, then the <em>detect_faces()<\/em> function can be called passing in the pixel data for one image. The result is a list of detected faces, with a bounding box defined in pixel offset values.<\/p>\n<pre class=\"crayon-plain-tag\">...\r\n# prepare model\r\nmodel = MTCNN()\r\n# detect face in the image\r\nfaces = model.detect_faces(pixels)\r\n# extract details of the face\r\nx1, y1, width, height = faces[0]['box']<\/pre>\n<p>We can update our example to extract the face from each loaded photo and resize the extracted face pixels to a fixed size. In this case, we will use the square shape of 80\u00d780 pixels.<\/p>\n<p>The <em>extract_face()<\/em> function below implements this, taking the MTCNN model and pixel values for a single photograph as arguments and returning an 80x80x3 array of pixel values with just the face, or None if no face was detected (which can happen rarely).<\/p>\n<pre class=\"crayon-plain-tag\"># extract the face from a loaded image and resize\r\ndef extract_face(model, pixels, required_size=(80, 80)):\r\n\t# detect face in the image\r\n\tfaces = model.detect_faces(pixels)\r\n\t# skip cases where we could not detect a face\r\n\tif len(faces) == 0:\r\n\t\treturn None\r\n\t# extract details of the face\r\n\tx1, y1, width, height = faces[0]['box']\r\n\t# force detected pixel values to be positive (bug fix)\r\n\tx1, y1 = abs(x1), abs(y1)\r\n\t# convert into coordinates\r\n\tx2, y2 = x1 + width, y1 + height\r\n\t# retrieve face pixels\r\n\tface_pixels = pixels[y1:y2, x1:x2]\r\n\t# resize pixels to the model size\r\n\timage = Image.fromarray(face_pixels)\r\n\timage = image.resize(required_size)\r\n\tface_array = asarray(image)\r\n\treturn face_array<\/pre>\n<p>We can now update the <em>load_faces()<\/em> function to extract the face from the loaded photo and store that in the list of faces returned.<\/p>\n<pre class=\"crayon-plain-tag\"># load images and extract faces for all images in a directory\r\ndef load_faces(directory, n_faces):\r\n\t# prepare model\r\n\tmodel = MTCNN()\r\n\tfaces = list()\r\n\t# enumerate files\r\n\tfor filename in listdir(directory):\r\n\t\t# load the image\r\n\t\tpixels = load_image(directory + filename)\r\n\t\t# get face\r\n\t\tface = extract_face(model, pixels)\r\n\t\tif face is None:\r\n\t\t\tcontinue\r\n\t\t# store\r\n\t\tfaces.append(face)\r\n\t\tprint(len(faces), face.shape)\r\n\t\t# stop once we have enough\r\n\t\tif len(faces) >= n_faces:\r\n\t\t\tbreak\r\n\treturn asarray(faces)<\/pre>\n<p>Tying this together, the complete example is listed below.<\/p>\n<p>In this case, we increase the total number of loaded faces to 50,000 to provide a good training dataset for our GAN model.<\/p>\n<pre class=\"crayon-plain-tag\"># example of extracting and resizing faces into a new dataset\r\nfrom os import listdir\r\nfrom numpy import asarray\r\nfrom numpy import savez_compressed\r\nfrom PIL import Image\r\nfrom mtcnn.mtcnn import MTCNN\r\nfrom matplotlib import pyplot\r\n\r\n# load an image as an rgb numpy array\r\ndef load_image(filename):\r\n\t# load image from file\r\n\timage = Image.open(filename)\r\n\t# convert to RGB, if needed\r\n\timage = image.convert('RGB')\r\n\t# convert to array\r\n\tpixels = asarray(image)\r\n\treturn pixels\r\n\r\n# extract the face from a loaded image and resize\r\ndef extract_face(model, pixels, required_size=(80, 80)):\r\n\t# detect face in the image\r\n\tfaces = model.detect_faces(pixels)\r\n\t# skip cases where we could not detect a face\r\n\tif len(faces) == 0:\r\n\t\treturn None\r\n\t# extract details of the face\r\n\tx1, y1, width, height = faces[0]['box']\r\n\t# force detected pixel values to be positive (bug fix)\r\n\tx1, y1 = abs(x1), abs(y1)\r\n\t# convert into coordinates\r\n\tx2, y2 = x1 + width, y1 + height\r\n\t# retrieve face pixels\r\n\tface_pixels = pixels[y1:y2, x1:x2]\r\n\t# resize pixels to the model size\r\n\timage = Image.fromarray(face_pixels)\r\n\timage = image.resize(required_size)\r\n\tface_array = asarray(image)\r\n\treturn face_array\r\n\r\n# load images and extract faces for all images in a directory\r\ndef load_faces(directory, n_faces):\r\n\t# prepare model\r\n\tmodel = MTCNN()\r\n\tfaces = list()\r\n\t# enumerate files\r\n\tfor filename in listdir(directory):\r\n\t\t# load the image\r\n\t\tpixels = load_image(directory + filename)\r\n\t\t# get face\r\n\t\tface = extract_face(model, pixels)\r\n\t\tif face is None:\r\n\t\t\tcontinue\r\n\t\t# store\r\n\t\tfaces.append(face)\r\n\t\tprint(len(faces), face.shape)\r\n\t\t# stop once we have enough\r\n\t\tif len(faces) >= n_faces:\r\n\t\t\tbreak\r\n\treturn asarray(faces)\r\n\r\n# directory that contains all images\r\ndirectory = 'img_align_celeba\/'\r\n# load and extract all faces\r\nall_faces = load_faces(directory, 50000)\r\nprint('Loaded: ', all_faces.shape)\r\n# save in compressed format\r\nsavez_compressed('img_align_celeba.npz', all_faces)<\/pre>\n<p>Running the example may take a few minutes given the larger number of faces to be loaded.<\/p>\n<p>At the end of the run, the array of extracted and resized faces is saved as a compressed NumPy array with the filename \u2018<em>img_align_celeba.npz<\/em>\u2018.<\/p>\n<p>The prepared dataset can then be loaded any time, as follows.<\/p>\n<pre class=\"crayon-plain-tag\"># load the prepared dataset\r\nfrom numpy import load\r\n# load the face dataset\r\ndata = load('img_align_celeba.npz')\r\nfaces = data['arr_0']\r\nprint('Loaded: ', faces.shape)<\/pre>\n<p>Loading the dataset summarizes the shape of the array, showing 50K images with the size of 80\u00d780 pixels and three color channels.<\/p>\n<pre class=\"crayon-plain-tag\">Loaded: (50000, 80, 80, 3)<\/pre>\n<p>We are now ready to develop a GAN model to generate faces using this dataset.<\/p>\n<h2>How to Develop a Generative Adversarial Network<\/h2>\n<p>In this section, we will develop a GAN for the faces dataset that we have prepared.<\/p>\n<p>The first step is to define the models.<\/p>\n<p>The discriminator model takes as input one 80\u00d780 color image an outputs a binary prediction as to whether the image is real (<em>class=1<\/em>) or fake (<em>class=0<\/em>). It is implemented as a modest convolutional neural network using best practices for GAN design such as using the <a href=\"https:\/\/machinelearningmastery.com\/rectified-linear-activation-function-for-deep-learning-neural-networks\/\">LeakyReLU activation function<\/a> with a slope of 0.2, using a <a href=\"https:\/\/machinelearningmastery.com\/padding-and-stride-for-convolutional-neural-networks\/\">2\u00d72 stride to downsample<\/a>, and the <a href=\"https:\/\/machinelearningmastery.com\/adam-optimization-algorithm-for-deep-learning\/\">adam version of stochastic gradient descent<\/a> with a learning rate of 0.0002 and a momentum of 0.5<\/p>\n<p>The <em>define_discriminator()<\/em> function below implements this, defining and compiling the discriminator model and returning it. The input shape of the image is parameterized as a default function argument in case you want to re-use the function for your own image data later.<\/p>\n<pre class=\"crayon-plain-tag\"># define the standalone discriminator model\r\ndef define_discriminator(in_shape=(80,80,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(128, (5,5), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 40x40\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 20x30\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 10x10\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 5x5\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model<\/pre>\n<p>The generator model takes as input a point in the latent space and outputs a single 80\u00d780 color image.<\/p>\n<p>This is achieved by using a fully connected layer to interpret the point in the latent space and provide sufficient activations that can be reshaped into many copies (in this case 128) of a low-resolution version of the output image (e.g. 5\u00d75). This is then upsampled four times, doubling the size and quadrupling the area of the activations each time using transpose convolutional layers. The model uses best practices such as the LeakyReLU activation, a kernel size that is a factor of the stride size, and a hyperbolic tangent (tanh) activation function in the output layer.<\/p>\n<p>The <em>define_generator()<\/em> function below defines the generator model but intentionally does not compile it as it is not trained directly, then returns the model. The size of the latent space is parameterized as a function argument.<\/p>\n<pre class=\"crayon-plain-tag\"># define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 5x5 feature maps\r\n\tn_nodes = 128 * 5 * 5\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((5, 5, 128)))\r\n\t# upsample to 10x10\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 20x20\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 40x40\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 80x80\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer 80x80x3\r\n\tmodel.add(Conv2D(3, (5,5), activation='tanh', padding='same'))\r\n\treturn model<\/pre>\n<p>Next, a GAN model can be defined that combines both the generator model and the discriminator model into one larger model. This larger model will be used to train the model weights in the generator, using the output and error calculated by the discriminator model. The discriminator model is trained separately, and as such, the model weights are marked as not trainable in this larger GAN model to ensure that only the weights of the generator model are updated. This change to the trainability of the discriminator weights only has an effect when training the combined GAN model, not when training the discriminator standalone.<\/p>\n<p>This larger GAN model takes as input a point in the latent space, uses the generator model to generate an image, which is fed as input to the discriminator model, then output or classified as real or fake.<\/p>\n<p>The <em>define_gan()<\/em> function below implements this, taking the already-defined generator and discriminator models as input.<\/p>\n<pre class=\"crayon-plain-tag\"># define the combined generator and discriminator model, for updating the generator\r\ndef define_gan(g_model, d_model):\r\n\t# make weights in the discriminator not trainable\r\n\td_model.trainable = False\r\n\t# connect them\r\n\tmodel = Sequential()\r\n\t# add generator\r\n\tmodel.add(g_model)\r\n\t# add the discriminator\r\n\tmodel.add(d_model)\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\r\n\treturn model<\/pre>\n<p>Now that we have defined the GAN model, we need to train it. But, before we can train the model, we require input data.<\/p>\n<p>The first step is to load and scale the pre-processed faces dataset. The saved NumPy array can be loaded, as we did in the previous section, then the <a href=\"https:\/\/machinelearningmastery.com\/how-to-manually-scale-image-pixel-data-for-deep-learning\/\">pixel values must be scaled<\/a> to the range [-1,1] to match the output of the generator model.<\/p>\n<p>The <em>load_real_samples()<\/em> function below implements this, returning the loaded and scaled image data ready for modeling.<\/p>\n<pre class=\"crayon-plain-tag\"># load and prepare training images\r\ndef load_real_samples():\r\n\t# load the face dataset\r\n\tdata = load('img_align_celeba.npz')\r\n\tX = data['arr_0']\r\n\t# convert from unsigned ints to floats\r\n\tX = X.astype('float32')\r\n\t# scale from [0,255] to [-1,1]\r\n\tX = (X - 127.5) \/ 127.5\r\n\treturn X<\/pre>\n<p>We will require one batch (or a half) batch of real images from the dataset each update to the GAN model. A simple way to achieve this is to select a random sample of images from the dataset each time.<\/p>\n<p>The <em>generate_real_samples()<\/em> function below implements this, taking the prepared dataset as an argument, selecting and returning a random sample of face images and their corresponding class label for the discriminator, specifically <em>class=1<\/em>, indicating that they are real images.<\/p>\n<pre class=\"crayon-plain-tag\"># select real samples\r\ndef generate_real_samples(dataset, n_samples):\r\n\t# choose random instances\r\n\tix = randint(0, dataset.shape[0], n_samples)\r\n\t# retrieve selected images\r\n\tX = dataset[ix]\r\n\t# generate 'real' class labels (1)\r\n\ty = ones((n_samples, 1))\r\n\treturn X, y<\/pre>\n<p>Next, we need inputs for the generator model. These are random points from the latent space, specifically <a href=\"https:\/\/machinelearningmastery.com\/how-to-generate-random-numbers-in-python\/\">Gaussian distributed random variables<\/a>.<\/p>\n<p>The <em>generate_latent_points()<\/em> function implements this, taking the size of the latent space as an argument and the number of points required and returning them as a batch of input samples for the generator model.<\/p>\n<pre class=\"crayon-plain-tag\"># generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tx_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn x_input<\/pre>\n<p>Next, we need to use the points in the latent space as input to the generator in order to generate new images.<\/p>\n<p>The <em>generate_fake_samples()<\/em> function below implements this, taking the generator model and size of the latent space as arguments, then generating points in the latent space and using them as input to the generator model. The function returns the generated images and their corresponding class label for the discriminator model, specifically class=0 to indicate they are fake or generated.<\/p>\n<pre class=\"crayon-plain-tag\"># use the generator to generate n fake examples, with class labels\r\ndef generate_fake_samples(g_model, latent_dim, n_samples):\r\n\t# generate points in latent space\r\n\tx_input = generate_latent_points(latent_dim, n_samples)\r\n\t# predict outputs\r\n\tX = g_model.predict(x_input)\r\n\t# create 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y<\/pre>\n<p>We are now ready to fit the GAN models.<\/p>\n<p>The model is fit for 100 training epochs, which is arbitrary, as the model begins generating plausible faces after perhaps the first few epochs. A batch size of 128 samples is used, and each training epoch involves 50,000\/128 or about 390 batches of real and fake samples and updates to the model.<\/p>\n<p>First, the discriminator model is updated for a half batch of real samples, then a half batch of fake samples, together forming one batch of weight updates. The generator is then updated via the combined GAN model. Importantly, the class label is set to 1 or real for the fake samples. This has the effect of updating the generator toward getting better at generating real samples on the next batch.<\/p>\n<p>The <em>train()<\/em> function below implements this, taking the defined models, dataset, and size of the latent dimension as arguments and parameterizing the number of epochs and batch size with default arguments.<\/p>\n<pre class=\"crayon-plain-tag\"># train the generator and discriminator\r\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=128):\r\n\tbat_per_epo = int(dataset.shape[0] \/ n_batch)\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_epochs):\r\n\t\t# enumerate batches over the training set\r\n\t\tfor j in range(bat_per_epo):\r\n\t\t\t# get randomly selected 'real' samples\r\n\t\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss1, _ = d_model.train_on_batch(X_real, y_real)\r\n\t\t\t# generate 'fake' examples\r\n\t\t\tX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss2, _ = d_model.train_on_batch(X_fake, y_fake)\r\n\t\t\t# prepare points in latent space as input for the generator\r\n\t\t\tX_gan = generate_latent_points(latent_dim, n_batch)\r\n\t\t\t# create inverted labels for the fake samples\r\n\t\t\ty_gan = ones((n_batch, 1))\r\n\t\t\t# update the generator via the discriminator's error\r\n\t\t\tg_loss = gan_model.train_on_batch(X_gan, y_gan)\r\n\t\t\t# summarize loss on this batch\r\n\t\t\tprint('>%d, %d\/%d, d1=%.3f, d2=%.3f g=%.3f' %\r\n\t\t\t\t(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))\r\n\t\t# evaluate the model performance, sometimes\r\n\t\tif (i+1) % 10 == 0:\r\n\t\t\tsummarize_performance(i, g_model, d_model, dataset, latent_dim)<\/pre>\n<p>You will note that every 10 training epochs, the <em>summarize_performance()<\/em> function is called.<\/p>\n<p>There is currently no reliable way to automatically evaluate the quality of generated images. Therefore, we must generate images periodically during training and save the model at these times. This both provides a check-point that we can later load and use to generate images, and a way to safeguard against the training process failing, which can happen.<\/p>\n<p>Below defines the <em>summarize_performance()<\/em> and <em>save_plot()<\/em> functions.<\/p>\n<p>The <em>summarize_performance()<\/em> function generates samples and evaluates the performance of the discriminator on real and fake samples. The classification accuracy is reported and might provide insight into model performance. The <em>save_plot()<\/em> is called to create and save a plot of the generated images, and then the model is saved to a file.<\/p>\n<pre class=\"crayon-plain-tag\"># create and save a plot of generated images\r\ndef save_plot(examples, epoch, n=10):\r\n\t# scale from [-1,1] to [0,1]\r\n\texamples = (examples + 1) \/ 2.0\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i])\r\n\t# save plot to file\r\n\tfilename = 'generated_plot_e%03d.png' % (epoch+1)\r\n\tpyplot.savefig(filename)\r\n\tpyplot.close()\r\n\r\n# evaluate the discriminator, plot generated images, save generator model\r\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=100):\r\n\t# prepare real samples\r\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\r\n\t# evaluate discriminator on real examples\r\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\r\n\t# prepare fake examples\r\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\r\n\t# evaluate discriminator on fake examples\r\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\r\n\t# summarize discriminator performance\r\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\r\n\t# save plot\r\n\tsave_plot(x_fake, epoch)\r\n\t# save the generator model tile file\r\n\tfilename = 'generator_model_%03d.h5' % (epoch+1)\r\n\tg_model.save(filename)<\/pre>\n<p>We can then define the size of the latent space, define all three models, and train them on the loaded face dataset.<\/p>\n<pre class=\"crayon-plain-tag\"># size of the latent space\r\nlatent_dim = 100\r\n# create the discriminator\r\nd_model = define_discriminator()\r\n# create the generator\r\ng_model = define_generator(latent_dim)\r\n# create the gan\r\ngan_model = define_gan(g_model, d_model)\r\n# load image data\r\ndataset = load_real_samples()\r\n# train model\r\ntrain(g_model, d_model, gan_model, dataset, latent_dim)<\/pre>\n<p>Tying all of this together, the complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of a gan for generating faces\r\nfrom numpy import load\r\nfrom numpy import zeros\r\nfrom numpy import ones\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom keras.optimizers import Adam\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Dense\r\nfrom keras.layers import Reshape\r\nfrom keras.layers import Flatten\r\nfrom keras.layers import Conv2D\r\nfrom keras.layers import Conv2DTranspose\r\nfrom keras.layers import LeakyReLU\r\nfrom keras.layers import Dropout\r\nfrom matplotlib import pyplot\r\n\r\n# define the standalone discriminator model\r\ndef define_discriminator(in_shape=(80,80,3)):\r\n\tmodel = Sequential()\r\n\t# normal\r\n\tmodel.add(Conv2D(128, (5,5), padding='same', input_shape=in_shape))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 40x40\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 20x30\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 10x10\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# downsample to 5x5\r\n\tmodel.add(Conv2D(128, (5,5), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# classifier\r\n\tmodel.add(Flatten())\r\n\tmodel.add(Dropout(0.4))\r\n\tmodel.add(Dense(1, activation='sigmoid'))\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])\r\n\treturn model\r\n\r\n# define the standalone generator model\r\ndef define_generator(latent_dim):\r\n\tmodel = Sequential()\r\n\t# foundation for 5x5 feature maps\r\n\tn_nodes = 128 * 5 * 5\r\n\tmodel.add(Dense(n_nodes, input_dim=latent_dim))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\tmodel.add(Reshape((5, 5, 128)))\r\n\t# upsample to 10x10\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 20x20\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 40x40\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# upsample to 80x80\r\n\tmodel.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))\r\n\tmodel.add(LeakyReLU(alpha=0.2))\r\n\t# output layer 80x80x3\r\n\tmodel.add(Conv2D(3, (5,5), activation='tanh', padding='same'))\r\n\treturn model\r\n\r\n# define the combined generator and discriminator model, for updating the generator\r\ndef define_gan(g_model, d_model):\r\n\t# make weights in the discriminator not trainable\r\n\td_model.trainable = False\r\n\t# connect them\r\n\tmodel = Sequential()\r\n\t# add generator\r\n\tmodel.add(g_model)\r\n\t# add the discriminator\r\n\tmodel.add(d_model)\r\n\t# compile model\r\n\topt = Adam(lr=0.0002, beta_1=0.5)\r\n\tmodel.compile(loss='binary_crossentropy', optimizer=opt)\r\n\treturn model\r\n\r\n# load and prepare training images\r\ndef load_real_samples():\r\n\t# load the face dataset\r\n\tdata = load('img_align_celeba.npz')\r\n\tX = data['arr_0']\r\n\t# convert from unsigned ints to floats\r\n\tX = X.astype('float32')\r\n\t# scale from [0,255] to [-1,1]\r\n\tX = (X - 127.5) \/ 127.5\r\n\treturn X\r\n\r\n# select real samples\r\ndef generate_real_samples(dataset, n_samples):\r\n\t# choose random instances\r\n\tix = randint(0, dataset.shape[0], n_samples)\r\n\t# retrieve selected images\r\n\tX = dataset[ix]\r\n\t# generate 'real' class labels (1)\r\n\ty = ones((n_samples, 1))\r\n\treturn X, y\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tx_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn x_input\r\n\r\n# use the generator to generate n fake examples, with class labels\r\ndef generate_fake_samples(g_model, latent_dim, n_samples):\r\n\t# generate points in latent space\r\n\tx_input = generate_latent_points(latent_dim, n_samples)\r\n\t# predict outputs\r\n\tX = g_model.predict(x_input)\r\n\t# create 'fake' class labels (0)\r\n\ty = zeros((n_samples, 1))\r\n\treturn X, y\r\n\r\n# create and save a plot of generated images\r\ndef save_plot(examples, epoch, n=10):\r\n\t# scale from [-1,1] to [0,1]\r\n\texamples = (examples + 1) \/ 2.0\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i])\r\n\t# save plot to file\r\n\tfilename = 'generated_plot_e%03d.png' % (epoch+1)\r\n\tpyplot.savefig(filename)\r\n\tpyplot.close()\r\n\r\n# evaluate the discriminator, plot generated images, save generator model\r\ndef summarize_performance(epoch, g_model, d_model, dataset, latent_dim, n_samples=100):\r\n\t# prepare real samples\r\n\tX_real, y_real = generate_real_samples(dataset, n_samples)\r\n\t# evaluate discriminator on real examples\r\n\t_, acc_real = d_model.evaluate(X_real, y_real, verbose=0)\r\n\t# prepare fake examples\r\n\tx_fake, y_fake = generate_fake_samples(g_model, latent_dim, n_samples)\r\n\t# evaluate discriminator on fake examples\r\n\t_, acc_fake = d_model.evaluate(x_fake, y_fake, verbose=0)\r\n\t# summarize discriminator performance\r\n\tprint('>Accuracy real: %.0f%%, fake: %.0f%%' % (acc_real*100, acc_fake*100))\r\n\t# save plot\r\n\tsave_plot(x_fake, epoch)\r\n\t# save the generator model tile file\r\n\tfilename = 'generator_model_%03d.h5' % (epoch+1)\r\n\tg_model.save(filename)\r\n\r\n# train the generator and discriminator\r\ndef train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=128):\r\n\tbat_per_epo = int(dataset.shape[0] \/ n_batch)\r\n\thalf_batch = int(n_batch \/ 2)\r\n\t# manually enumerate epochs\r\n\tfor i in range(n_epochs):\r\n\t\t# enumerate batches over the training set\r\n\t\tfor j in range(bat_per_epo):\r\n\t\t\t# get randomly selected 'real' samples\r\n\t\t\tX_real, y_real = generate_real_samples(dataset, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss1, _ = d_model.train_on_batch(X_real, y_real)\r\n\t\t\t# generate 'fake' examples\r\n\t\t\tX_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)\r\n\t\t\t# update discriminator model weights\r\n\t\t\td_loss2, _ = d_model.train_on_batch(X_fake, y_fake)\r\n\t\t\t# prepare points in latent space as input for the generator\r\n\t\t\tX_gan = generate_latent_points(latent_dim, n_batch)\r\n\t\t\t# create inverted labels for the fake samples\r\n\t\t\ty_gan = ones((n_batch, 1))\r\n\t\t\t# update the generator via the discriminator's error\r\n\t\t\tg_loss = gan_model.train_on_batch(X_gan, y_gan)\r\n\t\t\t# summarize loss on this batch\r\n\t\t\tprint('>%d, %d\/%d, d1=%.3f, d2=%.3f g=%.3f' %\r\n\t\t\t\t(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))\r\n\t\t# evaluate the model performance, sometimes\r\n\t\tif (i+1) % 10 == 0:\r\n\t\t\tsummarize_performance(i, g_model, d_model, dataset, latent_dim)\r\n\r\n# size of the latent space\r\nlatent_dim = 100\r\n# create the discriminator\r\nd_model = define_discriminator()\r\n# create the generator\r\ng_model = define_generator(latent_dim)\r\n# create the gan\r\ngan_model = define_gan(g_model, d_model)\r\n# load image data\r\ndataset = load_real_samples()\r\n# train model\r\ntrain(g_model, d_model, gan_model, dataset, latent_dim)<\/pre>\n<p>Running the example may take a long time on modest hardware.<\/p>\n<p>I recommend running the example on GPU hardware. If you need help, you can get started quickly by using an AWS EC2 instance to train the model. See the tutorial:<\/p>\n<ul>\n<li><a href=\"https:\/\/machinelearningmastery.com\/develop-evaluate-large-deep-learning-models-keras-amazon-web-services\/\">How to Setup Amazon AWS EC2 GPUs to Train Keras Deep Learning Models (step-by-step)<\/a><\/li>\n<\/ul>\n<p>The loss for the discriminator on real and fake samples, as well as the loss for the generator, is reported after each batch.<\/p>\n<p>Your specific results will vary given the stochastic nature of the learning algorithm.<\/p>\n<pre class=\"crayon-plain-tag\">>1, 1\/390, d1=0.699, d2=0.696 g=0.692\r\n>1, 2\/390, d1=0.541, d2=0.702 g=0.686\r\n>1, 3\/390, d1=0.213, d2=0.742 g=0.656\r\n>1, 4\/390, d1=0.013, d2=0.806 g=0.656\r\n>1, 5\/390, d1=0.012, d2=0.772 g=0.682\r\n...<\/pre>\n<p>The discriminator loss may crash down to values of 0.0 for real and generated samples.<\/p>\n<p>If this happens, it is an example of a training failure from which the model is likely to not recover and you should restart the training process.<\/p>\n<pre class=\"crayon-plain-tag\">...\r\n>34, 130\/390, d1=0.844, d2=8.434 g=3.450\r\n>34, 131\/390, d1=1.233, d2=12.021 g=3.541\r\n>34, 132\/390, d1=1.183, d2=15.759 g=0.000\r\n>34, 133\/390, d1=0.000, d2=15.942 g=0.006\r\n>34, 134\/390, d1=0.081, d2=15.942 g=0.000\r\n>34, 135\/390, d1=0.000, d2=15.942 g=0.000\r\n...<\/pre>\n<p>Review the generated plots and select a model based on the best quality images.<\/p>\n<p>The model should begin to generate faces after about 30 training epochs.<\/p>\n<p>The faces are not completely clear, but it is obvious that they are faces, with all the right things (hair, eyes, nose, mouth) in roughly the right places.<\/p>\n<div id=\"attachment_8144\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8144\" class=\"size-full wp-image-8144\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Example-of-Celebrity-Faces-Generated-by-a-Generative-Adversarial-Network.png\" alt=\"Example of Celebrity Faces Generated by a Generative Adversarial Network\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Celebrity-Faces-Generated-by-a-Generative-Adversarial-Network.png 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Example-of-Celebrity-Faces-Generated-by-a-Generative-Adversarial-Network-300x225.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8144\" class=\"wp-caption-text\">Example of Celebrity Faces Generated by a Generative Adversarial Network<\/p>\n<\/div>\n<h2>How to Explore the Latent Space for Generated Faces<\/h2>\n<p>In this section, we will use our trained GAN model as the basis for exploring the latent space.<\/p>\n<h3>How to Load Model and Generate Faces<\/h3>\n<p>The first step is to load the saved model and confirm that it can generate plausible faces.<\/p>\n<p>The model can be loaded using the <em>load_model()<\/em> function in the Keras API. We can then generate a number of random points in the latent space and use them as input to the loaded model to generate new faces. The faces can then be plotted.<\/p>\n<p>The complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of loading the generator model and generating images\r\nfrom numpy import asarray\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom keras.models import load_model\r\nfrom matplotlib import pyplot\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples, n_classes=10):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tz_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn z_input\r\n\r\n# create a plot of generated images\r\ndef plot_generated(examples, n):\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.show()\r\n\r\n# load model\r\nmodel = load_model('generator_model_030.h5')\r\n# generate images\r\nlatent_points = generate_latent_points(100, 25)\r\n# generate images\r\nX  = model.predict(latent_points)\r\n# scale from [-1,1] to [0,1]\r\nX = (X + 1) \/ 2.0\r\n# plot the result\r\nplot_generated(X, 5)<\/pre>\n<p>Running the example first loads the saved model.<\/p>\n<p>Then, 25 random points in the 100-dimensional latent space are created, provided to the generator model to create 25 images of faces, which are then plotted in a 5\u00d75 grid.<\/p>\n<div id=\"attachment_8145\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8145\" class=\"size-large wp-image-8145\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model-1024x768.png\" alt=\"Plot of Randomly Generated Faces Using the Loaded GAN Model\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Randomly-Generated-Faces-Using-the-Loaded-GAN-Model.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8145\" class=\"wp-caption-text\">Plot of Randomly Generated Faces Using the Loaded GAN Model<\/p>\n<\/div>\n<h3>How to Interpolate Between Generated Faces<\/h3>\n<p>Next, we can create an interpolation path between two points in the latent space and generate faces along this path.<\/p>\n<p>The simplest interpolation we can use is a linear or uniform interpolation between two points in the latent space. We can achieve this using the <a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/generated\/numpy.linspace.html\">linspace() NumPy function<\/a> to calculate ratios of the contribution from two points, then enumerate these ratios and construct a vector for each ratio.<\/p>\n<p>The <em>interpolate_points()<\/em> function below implements this and returns a series of linearly interpolated vectors between two points in latent space, including the first and last point.<\/p>\n<pre class=\"crayon-plain-tag\"># uniform interpolation between two points in latent space\r\ndef interpolate_points(p1, p2, n_steps=10):\r\n\t# interpolate ratios between the points\r\n\tratios = linspace(0, 1, num=n_steps)\r\n\t# linear interpolate vectors\r\n\tvectors = list()\r\n\tfor ratio in ratios:\r\n\t\tv = (1.0 - ratio) * p1 + ratio * p2\r\n\t\tvectors.append(v)\r\n\treturn asarray(vectors)<\/pre>\n<p>We can then generate two points in the latent space, perform the interpolation, then generate an image for each interpolated vector.<\/p>\n<p>The result will be a series of images that transition between the two original images. The example below demonstrates this for two faces.<\/p>\n<pre class=\"crayon-plain-tag\"># example of interpolating between generated faces\r\nfrom numpy import asarray\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom numpy import linspace\r\nfrom keras.models import load_model\r\nfrom matplotlib import pyplot\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples, n_classes=10):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tz_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn z_input\r\n\r\n# uniform interpolation between two points in latent space\r\ndef interpolate_points(p1, p2, n_steps=10):\r\n\t# interpolate ratios between the points\r\n\tratios = linspace(0, 1, num=n_steps)\r\n\t# linear interpolate vectors\r\n\tvectors = list()\r\n\tfor ratio in ratios:\r\n\t\tv = (1.0 - ratio) * p1 + ratio * p2\r\n\t\tvectors.append(v)\r\n\treturn asarray(vectors)\r\n\r\n# create a plot of generated images\r\ndef plot_generated(examples, n):\r\n\t# plot images\r\n\tfor i in range(n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(1, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.show()\r\n\r\n# load model\r\nmodel = load_model('generator_model_030.h5')\r\n# generate points in latent space\r\npts = generate_latent_points(100, 2)\r\n# interpolate points in latent space\r\ninterpolated = interpolate_points(pts[0], pts[1])\r\n# generate images\r\nX = model.predict(interpolated)\r\n# scale from [-1,1] to [0,1]\r\nX = (X + 1) \/ 2.0\r\n# plot the result\r\nplot_generated(X, len(interpolated))<\/pre>\n<p>Running the example calculates the interpolation path between the two points in latent space, generates images for each, and plots the result.<\/p>\n<p>You can see the clear linear progression in ten steps from the first face on the left to the final face on the right.<\/p>\n<div id=\"attachment_8146\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8146\" class=\"size-large wp-image-8146\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-the-Linear-Interpolation-Between-Two-GAN-Generated-Faces-1024x768.png\" alt=\"Plot Showing the Linear Interpolation Between Two GAN Generated Faces\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-the-Linear-Interpolation-Between-Two-GAN-Generated-Faces-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-the-Linear-Interpolation-Between-Two-GAN-Generated-Faces-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-the-Linear-Interpolation-Between-Two-GAN-Generated-Faces-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-the-Linear-Interpolation-Between-Two-GAN-Generated-Faces.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8146\" class=\"wp-caption-text\">Plot Showing the Linear Interpolation Between Two GAN Generated Faces<\/p>\n<\/div>\n<p>We can update the example to repeat this process multiple times so we can see the transition between multiple generated faces on a single plot.<\/p>\n<p>The complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of interpolating between generated faces\r\nfrom numpy import asarray\r\nfrom numpy import vstack\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom numpy import linspace\r\nfrom keras.models import load_model\r\nfrom matplotlib import pyplot\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples, n_classes=10):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tz_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn z_input\r\n\r\n# uniform interpolation between two points in latent space\r\ndef interpolate_points(p1, p2, n_steps=10):\r\n\t# interpolate ratios between the points\r\n\tratios = linspace(0, 1, num=n_steps)\r\n\t# linear interpolate vectors\r\n\tvectors = list()\r\n\tfor ratio in ratios:\r\n\t\tv = (1.0 - ratio) * p1 + ratio * p2\r\n\t\tvectors.append(v)\r\n\treturn asarray(vectors)\r\n\r\n# create a plot of generated images\r\ndef plot_generated(examples, n):\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.show()\r\n\r\n# load model\r\nmodel = load_model('generator_model_030.h5')\r\n# generate points in latent space\r\nn = 20\r\npts = generate_latent_points(100, n)\r\n# interpolate pairs\r\nresults = None\r\nfor i in range(0, n, 2):\r\n\t# interpolate points in latent space\r\n\tinterpolated = interpolate_points(pts[i], pts[i+1])\r\n\t# generate images\r\n\tX = model.predict(interpolated)\r\n\t# scale from [-1,1] to [0,1]\r\n\tX = (X + 1) \/ 2.0\r\n\tif results is None:\r\n\t\tresults = X\r\n\telse:\r\n\t\tresults = vstack((results, X))\r\n# plot the result\r\nplot_generated(results, 10)<\/pre>\n<p>Running the example creates 10 different face starting points and 10 matching face endpoints, and the linear interpolation between each.<\/p>\n<div id=\"attachment_8147\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8147\" class=\"size-large wp-image-8147\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Linear-Interpolation-Between-Two-GAN-Generated-Faces-1024x768.png\" alt=\"Plot Showing Multiple Linear Interpolations Between Two GAN Generated Faces\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Linear-Interpolation-Between-Two-GAN-Generated-Faces-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Linear-Interpolation-Between-Two-GAN-Generated-Faces-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Linear-Interpolation-Between-Two-GAN-Generated-Faces-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Linear-Interpolation-Between-Two-GAN-Generated-Faces.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8147\" class=\"wp-caption-text\">Plot Showing Multiple Linear Interpolations Between Two GAN Generated Faces<\/p>\n<\/div>\n<p>In these cases, we have performed a linear interpolation which assumes that the latent space is uniformly distributed hypercube. Technically, our chosen latent space is a 100-dimension hypersphere or multimodal Gaussian distribution.<\/p>\n<p>There is a mathematical function called the spherical linear interpolation function, or \u2018<a href=\"https:\/\/en.wikipedia.org\/wiki\/Slerp\">Slerp<\/a>,\u2019 that should be used when interpolating this space to ensure the curving of the space is taken into account. For more details, I recommend reading the <a href=\"https:\/\/github.com\/soumith\/dcgan.torch\/issues\/14\">Issue on Linear Interpolation in Soumith Chintala\u2019s dcgan.torch project<\/a>. In that project, an implementation of the Slerp function for Python is provided that we can use as the basis for our own Slerp function, provided below:<\/p>\n<pre class=\"crayon-plain-tag\"># spherical linear interpolation (slerp)\r\ndef slerp(val, low, high):\r\n\tomega = arccos(clip(dot(low\/norm(low), high\/norm(high)), -1, 1))\r\n\tso = sin(omega)\r\n\tif so == 0:\r\n\t\t# L'Hopital's rule\/LERP\r\n\t\treturn (1.0-val) * low + val * high\r\n\treturn sin((1.0-val)*omega) \/ so * low + sin(val*omega) \/ so * high<\/pre>\n<p>This function can be called from our <em>interpolate_points()<\/em> function instead of performing the manual linear interpolation.<\/p>\n<p>The complete example with this change is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of interpolating between generated faces\r\nfrom numpy import asarray\r\nfrom numpy import vstack\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom numpy import arccos\r\nfrom numpy import clip\r\nfrom numpy import dot\r\nfrom numpy import sin\r\nfrom numpy import linspace\r\nfrom numpy.linalg import norm\r\nfrom keras.models import load_model\r\nfrom matplotlib import pyplot\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples, n_classes=10):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tz_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn z_input\r\n\r\n# spherical linear interpolation (slerp)\r\ndef slerp(val, low, high):\r\n\tomega = arccos(clip(dot(low\/norm(low), high\/norm(high)), -1, 1))\r\n\tso = sin(omega)\r\n\tif so == 0:\r\n\t\t# L'Hopital's rule\/LERP\r\n\t\treturn (1.0-val) * low + val * high\r\n\treturn sin((1.0-val)*omega) \/ so * low + sin(val*omega) \/ so * high\r\n\r\n# uniform interpolation between two points in latent space\r\ndef interpolate_points(p1, p2, n_steps=10):\r\n\t# interpolate ratios between the points\r\n\tratios = linspace(0, 1, num=n_steps)\r\n\t# linear interpolate vectors\r\n\tvectors = list()\r\n\tfor ratio in ratios:\r\n\t\tv = slerp(ratio, p1, p2)\r\n\t\tvectors.append(v)\r\n\treturn asarray(vectors)\r\n\r\n# create a plot of generated images\r\ndef plot_generated(examples, n):\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.show()\r\n\r\n# load model\r\nmodel = load_model('generator_model_030.h5')\r\n# generate points in latent space\r\nn = 20\r\npts = generate_latent_points(100, n)\r\n# interpolate pairs\r\nresults = None\r\nfor i in range(0, n, 2):\r\n\t# interpolate points in latent space\r\n\tinterpolated = interpolate_points(pts[i], pts[i+1])\r\n\t# generate images\r\n\tX = model.predict(interpolated)\r\n\t# scale from [-1,1] to [0,1]\r\n\tX = (X + 1) \/ 2.0\r\n\tif results is None:\r\n\t\tresults = X\r\n\telse:\r\n\t\tresults = vstack((results, X))\r\n# plot the result\r\nplot_generated(results, 10)<\/pre>\n<p>The result is 10 more transitions between generated faces, this time using the correct Slerp interpolation method.<\/p>\n<p>The difference is subtle but somehow visually more correct.<\/p>\n<div id=\"attachment_8148\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8148\" class=\"size-large wp-image-8148\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Seraphically-Linear-Interpolation-Between-Two-GAN-Generated-Faces-1024x768.png\" alt=\"Plot Showing Multiple Seraphically Linear Interpolation Between Two GAN Generated Faces\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Seraphically-Linear-Interpolation-Between-Two-GAN-Generated-Faces-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Seraphically-Linear-Interpolation-Between-Two-GAN-Generated-Faces-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Seraphically-Linear-Interpolation-Between-Two-GAN-Generated-Faces-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-Showing-Multiple-Seraphically-Linear-Interpolation-Between-Two-GAN-Generated-Faces.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8148\" class=\"wp-caption-text\">Plot Showing Multiple Seraphically Linear Interpolation Between Two GAN Generated Faces<\/p>\n<\/div>\n<h3>How to Perform Vector Arithmetic With Faces<\/h3>\n<p>Finally, we can explore the latent space by performing vector arithmetic with the generated faces<\/p>\n<p>First, we must generate a large number of faces and save both the faces and their corresponding latent vectors. We can then review the plot of generated faces and select faces with features we\u2019re interested in, note their index (number), and retrieve their latent space vectors for manipulation.<\/p>\n<p>The example below will load the GAN model and use it to generate 100 random faces.<\/p>\n<pre class=\"crayon-plain-tag\"># example of loading the generator model and generating images\r\nfrom numpy import asarray\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom keras.models import load_model\r\nfrom matplotlib import pyplot\r\nfrom numpy import savez_compressed\r\n\r\n# generate points in latent space as input for the generator\r\ndef generate_latent_points(latent_dim, n_samples, n_classes=10):\r\n\t# generate points in the latent space\r\n\tx_input = randn(latent_dim * n_samples)\r\n\t# reshape into a batch of inputs for the network\r\n\tz_input = x_input.reshape(n_samples, latent_dim)\r\n\treturn z_input\r\n\r\n# create a plot of generated images\r\ndef plot_generated(examples, n):\r\n\t# plot images\r\n\tfor i in range(n * n):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(n, n, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.savefig('generated_faces.png')\r\n\tpyplot.close()\r\n\r\n# load model\r\nmodel = load_model('generator_model_030.h5')\r\n# generate points in latent space\r\nlatent_points = generate_latent_points(100, 100)\r\n# save points\r\nsavez_compressed('latent_points.npz', latent_points)\r\n# generate images\r\nX  = model.predict(latent_points)\r\n# scale from [-1,1] to [0,1]\r\nX = (X + 1) \/ 2.0\r\n# save plot\r\nplot_generated(X, 10)<\/pre>\n<p>Running the example loads the model, generates faces, and saves the latent vectors and generated faces.<\/p>\n<p>The latent vectors are saved to a compressed NumPy array with the filename \u2018<em>latent_points.npz<\/em>\u2018. The 100 generated faces are plotted in a 10\u00d710 grid and saved in a file named \u2018<em>generated_faces.png<\/em>\u2018.<\/p>\n<p>In this case, we have a good collection of faces to work with. Each face has an index that we can use to retrieve the latent vector. For example, the first face is 1, which corresponds to the first vector in the saved array (index 0).<\/p>\n<p>We will perform the operation:<\/p>\n<pre class=\"crayon-plain-tag\">smiling woman - neutral woman + neutral man = smiling man<\/pre>\n<p>Therefore, we need three faces for each of smiling woman, neutral woman, and neutral man.<\/p>\n<p>In this case, we will use the following indexes in the image:<\/p>\n<ul>\n<li>Smiling Woman: 92, 98, 99<\/li>\n<li>Neutral Woman: 9, 21, 79<\/li>\n<li>Neutral Man: 10, 30, 45<\/li>\n<\/ul>\n<div id=\"attachment_8149\" style=\"width: 650px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8149\" class=\"size-full wp-image-8149\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-100-Generated-Faces-Used-as-the-Basis-for-Vector-Arithmetic-with-Faces.png\" alt=\"Plot of 100 Generated Faces Used as the Basis for Vector Arithmetic with Faces\" width=\"640\" height=\"480\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-100-Generated-Faces-Used-as-the-Basis-for-Vector-Arithmetic-with-Faces.png 640w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-100-Generated-Faces-Used-as-the-Basis-for-Vector-Arithmetic-with-Faces-300x225.png 300w\" sizes=\"(max-width: 640px) 100vw, 640px\"><\/p>\n<p id=\"caption-attachment-8149\" class=\"wp-caption-text\">Plot of 100 Generated Faces Used as the Basis for Vector Arithmetic with Faces<\/p>\n<\/div>\n<p>Now that we have latent vectors to work with and a target arithmetic, we can get started.<\/p>\n<p>First, we can specify our preferred images and load the saved NumPy array of latent points.<\/p>\n<pre class=\"crayon-plain-tag\"># retrieve specific points\r\nsmiling_woman_ix = [92, 98, 99]\r\nneutral_woman_ix = [9, 21, 79]\r\nneutral_man_ix = [10, 30, 45]\r\n# load the saved latent points\r\ndata = load('latent_points.npz')\r\npoints = data['arr_0']<\/pre>\n<p>Next, we can retrieve each vector and calculate the average for each vector type (e.g. smiling woman). We could perform vector arithmetic with single images directly, but we will get a more robust result if we work with an average of a few faces with the desired property.<\/p>\n<p>The <em>average_points()<\/em> function below takes the loaded array of latent space points, retrieves each, calculates the average, and returns all of the vectors.<\/p>\n<pre class=\"crayon-plain-tag\"># average list of latent space vectors\r\ndef average_points(points, ix):\r\n\t# convert to zero offset points\r\n\tzero_ix = [i-1 for i in ix]\r\n\t# retrieve required points\r\n\tvectors = points[zero_ix]\r\n\t# average the vectors\r\n\tavg_vector = mean(vectors, axis=0)\r\n\t# combine original and avg vectors\r\n\tall_vectors = vstack((vectors, avg_vector))\r\n\treturn all_vectors<\/pre>\n<p>We can now use this function to retrieve all of the required points in latent space and generate images.<\/p>\n<pre class=\"crayon-plain-tag\"># average vectors\r\nsmiling_woman = average_points(points, smiling_woman_ix)\r\nneutral_woman = average_points(points, neutral_woman_ix)\r\nneutral_man = average_points(points, neutral_man_ix)\r\n# combine all vectors\r\nall_vectors = vstack((smiling_woman, neutral_woman, neutral_man))\r\n# generate images\r\nimages = model.predict(all_vectors)\r\n# scale pixel values\r\nimages = (images + 1) \/ 2.0\r\nplot_generated(images, 3, 4)<\/pre>\n<p>Finally, we can use the average vectors to perform vector arithmetic in latent space and plot the result.<\/p>\n<pre class=\"crayon-plain-tag\"># smiling woman - neutral woman + neutral man = smiling man\r\nresult_vector = smiling_woman[-1] - neutral_woman[-1] + neutral_man[-1]\r\n# generate image\r\nresult_vector = expand_dims(result_vector, 0)\r\nresult_image = model.predict(result_vector)\r\n# scale pixel values\r\nresult_image = (result_image + 1) \/ 2.0\r\npyplot.imshow(result_image[0])\r\npyplot.show()<\/pre>\n<p>Tying this together, the complete example is listed below.<\/p>\n<pre class=\"crayon-plain-tag\"># example of loading the generator model and generating images\r\nfrom numpy import asarray\r\nfrom numpy.random import randn\r\nfrom numpy.random import randint\r\nfrom keras.models import load_model\r\nfrom matplotlib import pyplot\r\nfrom numpy import load\r\nfrom numpy import mean\r\nfrom numpy import vstack\r\nfrom numpy import expand_dims\r\n\r\n# average list of latent space vectors\r\ndef average_points(points, ix):\r\n\t# convert to zero offset points\r\n\tzero_ix = [i-1 for i in ix]\r\n\t# retrieve required points\r\n\tvectors = points[zero_ix]\r\n\t# average the vectors\r\n\tavg_vector = mean(vectors, axis=0)\r\n\t# combine original and avg vectors\r\n\tall_vectors = vstack((vectors, avg_vector))\r\n\treturn all_vectors\r\n\r\n# create a plot of generated images\r\ndef plot_generated(examples, rows, cols):\r\n\t# plot images\r\n\tfor i in range(rows * cols):\r\n\t\t# define subplot\r\n\t\tpyplot.subplot(rows, cols, 1 + i)\r\n\t\t# turn off axis\r\n\t\tpyplot.axis('off')\r\n\t\t# plot raw pixel data\r\n\t\tpyplot.imshow(examples[i, :, :])\r\n\tpyplot.show()\r\n\r\n# load model\r\nmodel = load_model('generator_model_030.h5')\r\n# retrieve specific points\r\nsmiling_woman_ix = [92, 98, 99]\r\nneutral_woman_ix = [9, 21, 79]\r\nneutral_man_ix = [10, 30, 45]\r\n# load the saved latent points\r\ndata = load('latent_points.npz')\r\npoints = data['arr_0']\r\n# average vectors\r\nsmiling_woman = average_points(points, smiling_woman_ix)\r\nneutral_woman = average_points(points, neutral_woman_ix)\r\nneutral_man = average_points(points, neutral_man_ix)\r\n# combine all vectors\r\nall_vectors = vstack((smiling_woman, neutral_woman, neutral_man))\r\n# generate images\r\nimages = model.predict(all_vectors)\r\n# scale pixel values\r\nimages = (images + 1) \/ 2.0\r\nplot_generated(images, 3, 4)\r\n# smiling woman - neutral woman + neutral man = smiling man\r\nresult_vector = smiling_woman[-1] - neutral_woman[-1] + neutral_man[-1]\r\n# generate image\r\nresult_vector = expand_dims(result_vector, 0)\r\nresult_image = model.predict(result_vector)\r\n# scale pixel values\r\nresult_image = (result_image + 1) \/ 2.0\r\npyplot.imshow(result_image[0])\r\npyplot.show()<\/pre>\n<p>Running the example first loads the points in latent space for our specific images, calculates the average of the points, and generates the faces for the points.<\/p>\n<p>We can see that, indeed, our selected faces were retrieved correctly and that the average of the points in the vector space captures the salient feature we are going for on each line (e.g. smiling woman, neutral woman, etc.).<\/p>\n<div id=\"attachment_8150\" style=\"width: 1034px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8150\" class=\"size-large wp-image-8150\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-Selected-Generated-Faces-and-the-Average-Generated-Face-for-Each-Row-1024x768.png\" alt=\"Plot of Selected Generated Faces and the Average Generated Face for Each Row\" width=\"1024\" height=\"768\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Selected-Generated-Faces-and-the-Average-Generated-Face-for-Each-Row-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Selected-Generated-Faces-and-the-Average-Generated-Face-for-Each-Row-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Selected-Generated-Faces-and-the-Average-Generated-Face-for-Each-Row-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-Selected-Generated-Faces-and-the-Average-Generated-Face-for-Each-Row.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\"><\/p>\n<p id=\"caption-attachment-8150\" class=\"wp-caption-text\">Plot of Selected Generated Faces and the Average Generated Face for Each Row<\/p>\n<\/div>\n<p>Next, vector arithmetic is performed and the result is a smiling man, as we would expect.<\/p>\n<div id=\"attachment_8151\" style=\"width: 310px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-8151\" class=\"wp-image-8151 size-medium\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Resulting-Generated-Face-Based-on-Vector-Arithmetic-in-Latent-Space-300x225.png\" alt=\"Plot of the Resulting Generated Face Based on Vector Arithmetic in Latent Space\" width=\"300\" height=\"225\" srcset=\"http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Resulting-Generated-Face-Based-on-Vector-Arithmetic-in-Latent-Space-300x225.png 300w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Resulting-Generated-Face-Based-on-Vector-Arithmetic-in-Latent-Space-768x576.png 768w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Resulting-Generated-Face-Based-on-Vector-Arithmetic-in-Latent-Space-1024x768.png 1024w, http:\/\/3qeqpr26caki16dnhd19sv6by6v.wpengine.netdna-cdn.com\/wp-content\/uploads\/2019\/05\/Plot-of-the-Resulting-Generated-Face-Based-on-Vector-Arithmetic-in-Latent-Space.png 1280w\" sizes=\"(max-width: 300px) 100vw, 300px\"><\/p>\n<p id=\"caption-attachment-8151\" class=\"wp-caption-text\">Plot of the Resulting Generated Face Based on Vector Arithmetic in Latent Space<\/p>\n<\/div>\n<h2>Extensions<\/h2>\n<p>This section lists some ideas for extending the tutorial that you may wish to explore.<\/p>\n<ul>\n<li><strong>Additional Arithmetic<\/strong>. Try arithmetic with different image features or different arithmetic and review the results of the generated faces.<\/li>\n<li><strong>Additional Interpolation<\/strong>. Try interpolating between three or more points in latent space and review the results of the generated faces.<\/li>\n<li><strong>Tune Model<\/strong>. Update the GAN model configuration so that training is more stable and better quality faces can be generated.<\/li>\n<\/ul>\n<p>If you explore any of these extensions, I\u2019d love to know.<br \/>\nPost your findings in the comments below.<\/p>\n<h2>Further Reading<\/h2>\n<p>This section provides more resources on the topic if you are looking to go deeper.<\/p>\n<h3>Books<\/h3>\n<ul>\n<li>Chapter 20. Deep Generative Models, <a href=\"https:\/\/amzn.to\/2YuwVjL\">Deep Learning<\/a>, 2016.<\/li>\n<li>Chapter 8. Generative Deep Learning, <a href=\"https:\/\/amzn.to\/2U2bHuP\">Deep Learning with Python<\/a>, 2017.<\/li>\n<\/ul>\n<h3>Papers<\/h3>\n<ul>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1406.2661\">Generative Adversarial Networks<\/a>, 2014.<\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1511.06434\">Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks<\/a>, 2015<\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1701.00160\">Tutorial: Generative Adversarial Networks, NIPS<\/a>, 2016.<\/li>\n<li><a href=\"https:\/\/arxiv.org\/abs\/1604.02878\">Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks<\/a>, 2016.<\/li>\n<\/ul>\n<h3>API<\/h3>\n<ul>\n<li><a href=\"https:\/\/keras.io\/datasets\/\">Keras Datasets API<\/a>.<\/li>\n<li><a href=\"https:\/\/keras.io\/models\/sequential\/\">Keras Sequential Model API<\/a><\/li>\n<li><a href=\"https:\/\/keras.io\/layers\/convolutional\/\">Keras Convolutional Layers API<\/a><\/li>\n<li><a href=\"https:\/\/keras.io\/getting-started\/faq\/#how-can-i-freeze-keras-layers\">How can I \u201cfreeze\u201d Keras layers?<\/a><\/li>\n<li><a href=\"https:\/\/matplotlib.org\/api\/\">MatplotLib API<\/a><\/li>\n<li><a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/routines.random.html\">NumPy Random sampling (numpy.random) API<\/a><\/li>\n<li><a href=\"https:\/\/docs.scipy.org\/doc\/numpy\/reference\/routines.array-manipulation.html\">NumPy Array manipulation routines<\/a><\/li>\n<\/ul>\n<h3>Articles<\/h3>\n<ul>\n<li><a href=\"http:\/\/mmlab.ie.cuhk.edu.hk\/projects\/CelebA.html\">Large-scale CelebFaces Attributes (CelebA) Dataset.<\/a><\/li>\n<li><a href=\"https:\/\/www.kaggle.com\/jessicali9530\/celeba-dataset\">CelebFaces Attributes (CelebA) Dataset, Kaggle<\/a>.<\/li>\n<li><a href=\"https:\/\/medium.com\/coinmonks\/celebrity-face-generation-using-gans-tensorflow-implementation-eaa2001eef86\">Celebrity Face Generation using GANs (Tensorflow Implementation), 2018<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/ipazc\/mtcnn\">MTCNN Face Detection Project, GitHub<\/a>.<\/li>\n<li><a href=\"https:\/\/github.com\/soumith\/dcgan.torch\/issues\/14\">linear interpolation?, dcgan.torch Project, GitHub<\/a>.<\/li>\n<\/ul>\n<h2>Summary<\/h2>\n<p>In this tutorial, you discovered how to develop a generative adversarial network for face generation and explore the structure of latent space and the effect on generated faces.<\/p>\n<p>Specifically, you learned:<\/p>\n<ul>\n<li>How to develop a generative adversarial network for generating faces.<\/li>\n<li>How to interpolate between points in latent space and generate images that morph from one face to another.<\/li>\n<li>How to perform vector arithmetic in latent space and achieve targeted results in the resulting generated faces.<\/li>\n<\/ul>\n<p>Do you have any questions?<br \/>\nAsk your questions in the comments below and I will do my best to answer.<\/p>\n<p>The post <a rel=\"nofollow\" href=\"https:\/\/machinelearningmastery.com\/how-to-interpolate-and-perform-vector-arithmetic-with-faces-using-a-generative-adversarial-network\/\">How to Explore the GAN Latent Space When Generating Faces<\/a> appeared first on <a rel=\"nofollow\" href=\"https:\/\/machinelearningmastery.com\/\">Machine Learning Mastery<\/a>.<\/p>\n<\/div>\n<p><a href=\"https:\/\/machinelearningmastery.com\/how-to-interpolate-and-perform-vector-arithmetic-with-faces-using-a-generative-adversarial-network\/\">Go to Source<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Author: Jason Brownlee Generative Adversarial Networks, or GANs, are an architecture for training generative models, such as deep convolutional neural networks for generating images. The [&hellip;] <span class=\"read-more-link\"><a class=\"read-more\" href=\"https:\/\/www.aiproblog.com\/index.php\/2019\/07\/02\/how-to-explore-the-gan-latent-space-when-generating-faces\/\">Read More<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":2323,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"footnotes":""},"categories":[24],"tags":[],"_links":{"self":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts\/2322"}],"collection":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/comments?post=2322"}],"version-history":[{"count":0,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts\/2322\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/media\/2323"}],"wp:attachment":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/media?parent=2322"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/categories?post=2322"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/tags?post=2322"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}