{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Utils","metadata":{"id":"tDU_M1lgxgEt"}},{"cell_type":"markdown","source":"## Import Libraries","metadata":{"id":"jSik8H7Mmr3A"}},{"cell_type":"code","source":"%%capture\n!pip install tensorflow-addons\n\nimport glob\nimport numpy as np\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom keras.models import Sequential\nfrom keras.layers import Conv2D, Conv2DTranspose, Dense, Flatten, Reshape\nfrom keras.layers import BatchNormalization, Dropout\nfrom keras.layers import ReLU, LeakyReLU, Activation\nfrom tensorflow.keras.optimizers import Adam","metadata":{"id":"Au6gKDsNiAO1"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset","metadata":{"id":"QOLa1zSan95V"}},{"cell_type":"markdown","source":"### Download Data","metadata":{"id":"e5k33QbKox0b"}},{"cell_type":"code","source":"# global parameters\nBUFFER_SIZE = 60000\nBATCH_SIZE = 32\nIMAGE_SIZE = [256, 256]\nGCS_PATH = \"gs://kds-5175c920505d7c7c3467a39e2ae429f690bd2f0766a90292f051d56f\"\nFILENAMES_MONET = tf.io.gfile.glob(str(GCS_PATH + '/monet_tfrec/*.tfrec'))\n\nprint('Monet TFRecord Files:', len(FILENAMES_MONET))","metadata":{"id":"saMgf04z2E24","outputId":"5c92a769-6667-438a-df84-26ea5207c8f8"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Load TFRecord file from the folder as bytes","metadata":{"id":"KCyaAmiAzAYG"}},{"cell_type":"code","source":"raw_monet_dataset = tf.data.TFRecordDataset(FILENAMES_MONET)","metadata":{"id":"jXUjE3OJynsM"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Create a dictionary describing the features","metadata":{"id":"Gk6O1NFazFdR"}},{"cell_type":"code","source":"feature_description = {\n    'image_name': tf.io.FixedLenFeature([], tf.string),\n    'image': tf.io.FixedLenFeature([], tf.string),\n    'target': tf.io.FixedLenFeature([], tf.string)\n}","metadata":{"id":"4_bFqV-9zUTv"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Function to extract images from the TFRecord file","metadata":{"id":"LFFlP3c4zZOL"}},{"cell_type":"code","source":"def parse_image_function(example):\n  example = tf.io.parse_single_example(example, feature_description)\n  image = tf.io.decode_jpeg(example['image'])\n  image = (tf.cast(image, tf.float32) / 127.5) - 1.0\n  image = tf.image.resize(image, IMAGE_SIZE)\n  \n  return image","metadata":{"id":"kL7yFt3jy3lq"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Extract images, shuffle and select a batch","metadata":{"id":"Si3N-7HN0W5c"}},{"cell_type":"code","source":"monet_dataset = raw_monet_dataset.map(parse_image_function)\nmonet_dataset = monet_dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE)","metadata":{"id":"ksUuTxmO0Wag"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Display Examples","metadata":{"id":"67f6NclN8h-U"}},{"cell_type":"markdown","source":"## Display GAN train results\nGets model history, display plots of generator loss and discriminator loss","metadata":{"id":"6XKHGZgiKb8Z"}},{"cell_type":"code","source":"def plot_loss(model_history, title):\n  plt.plot(model_history['generator_loss'], label='generator loss')\n  plt.title(f'{title} generator loss')\n  plt.ylabel('Loss')\n  plt.xlabel('Epoch')\n  plt.legend(loc='upper right')\n  plt.show()\n\n  plt.plot(model_history['discriminator_loss'], label='discriminator loss')\n  plt.title(f'{title} discriminator loss')\n  plt.ylabel('Loss')\n  plt.xlabel('Epoch')\n  plt.legend(loc='upper right')\n  plt.show()","metadata":{"id":"Co1tydIzKczx"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"monet_example = next(iter(monet_dataset))\n\ndef display_images(example):\n  plt.figure(figsize = (10, 10))\n\n  for n in range(8):\n    ax = plt.subplot(4, 4, n + 1)\n    plt.imshow(example[n] * 0.5 + 0.5)\n    plt.axis(\"off\")\n\ndisplay_images(monet_example)","metadata":{"id":"S1gNWxTsjVjx","outputId":"812eb36d-7510-4af3-acc9-a8b7871330de"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Deep Convolutional GAN","metadata":{"id":"opv96GRe1mxn"}},{"cell_type":"markdown","source":"### Discriminator\nThe discriminator receives the 256x256x3 image as input from the generator, and run it through the model.<Br>\nThe final layer is a fully connected layer that uses a sigmoid activation function and outputs the probability that an image is real rather than fake.","metadata":{"id":"Uo8BcN9t2mDC"}},{"cell_type":"markdown","source":"#### Define discriminator","metadata":{"id":"WReHky2a2mDD"}},{"cell_type":"code","source":"def build_discriminator():\n  model = Sequential()\n  model.add(Conv2D(32, (3, 3), strides=(1, 1), padding='same', input_shape=[256, 256, 3]))\n  model.add(LeakyReLU(0.2))\n  model.add(Dropout(0.3))\n  \n  model.add(Conv2D(64, (3, 3), strides=(2, 2), padding='same'))\n  model.add(BatchNormalization())\n  model.add(LeakyReLU(0.2))\n  model.add(Dropout(0.3))\n\n  model.add(Conv2D(128, (3, 3), strides=(2, 2), padding='same'))\n  model.add(BatchNormalization())\n  model.add(LeakyReLU(0.2))\n  model.add(Dropout(0.3))\n  \n  model.add(Conv2D(256, (3, 3), strides=(2, 2), padding='same'))\n  model.add(BatchNormalization())\n  model.add(LeakyReLU(0.2))\n  model.add(Dropout(0.3))\n  \n  model.add(Conv2D(512, (3, 3), strides=(2, 2), padding='same'))\n  model.add(BatchNormalization())\n  model.add(LeakyReLU(0.2))\n  model.add(Dropout(0.3))\n\n  model.add(Flatten())\n  model.add(Dense(1))\n  model.add(Activation('sigmoid'))\n\n  return model","metadata":{"id":"ZkeB49CB2mDD"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Custom train function\nCreate a custom training loop that trains the models and compute the losses.<Br>\nThe reason we do it is because the regular fit() function is not flexible enough since we use separated loss functions and optimizers for the generator and discriminator","metadata":{"id":"vY1lB7sWA64E"}},{"cell_type":"code","source":"def train_step(images):\n  # Create random noise vect\n  noise = tf.random.normal([BATCH_SIZE, noise_dim]) # creates BATCH_SIZE noise vectors, each will be generated by the generator\n\n  with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:\n    # Use random noise vector to generate images\n    generated_images = generator(noise, training=True)\n\n    # Discriminator evaluates the real and fake images\n    real_output = discriminator(images, training=True)\n    fake_output = discriminator(generated_images, training=True)\n\n    # Losses are computed for both the generator and discriminator\n    gen_loss = generator_loss(fake_output)\n    disc_loss = discriminator_loss(real_output, fake_output)\n\n  # Compute gradients\n  gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)\n  gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)\n\n  # Update optimizers\n  generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))\n  discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))\n  \n  return gen_loss, disc_loss","metadata":{"id":"BY7f_MEVBEFM"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Generate and plot samples\nfunction to plot the generated images from the GAN model","metadata":{"id":"n0Ftap4D8CDf"}},{"cell_type":"code","source":"def sample_images(generator, image_grid_rows=2, image_grid_columns=2):\n  # Sample random noise vector\n  noise = tf.random.normal([BATCH_SIZE, noise_dim])\n\n  # Generate images\n  gen_imgs = generator.predict(noise)\n\n  # Rescale image pixel values to [0, 1]\n  gen_imgs = 0.5 * gen_imgs + 0.5\n\n  # Set grid and plot images\n  fig, axs = plt.subplots(image_grid_rows, image_grid_columns, figsize=(7, 7), sharey=True, sharex=True)\n\n  count = 0\n  for i in range(image_grid_rows):\n    for j in range(image_grid_columns):\n      axs[i, j].imshow(gen_imgs[count, :, :, :])\n      axs[i, j].axis('off')\n      count += 1","metadata":{"id":"LkS_jXDd8GVm"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Custom train function\nCreate a custom training loop that trains the models and compute the losses.<Br>\nThe reason we do it is because the regular fit() function is not flexible enough since we use separated loss functions and optimizers for the generator and discriminator","metadata":{"id":"-FIHcD2b4_Hi"}},{"cell_type":"markdown","source":"Create a custom function that gets dataset and epochs and train the GAN on the number of epochs","metadata":{"id":"SIL9KOdo7IXc"}},{"cell_type":"code","source":"def train(dataset, epochs):\n  results = {\"generator_loss\": [],\n             \"discriminator_loss\": []\n             }\n  for epoch in range(epochs):\n    # Create empty lists to store losses \n    gen_loss_list = []\n    disc_loss_list = []\n\n    for image_batch in dataset:\n      t = train_step(image_batch)\n      gen_loss_list.append(t[0])\n      disc_loss_list.append(t[1])\n      \n      # Calculate total loss \n      g_loss = sum(gen_loss_list) / len(gen_loss_list)\n      d_loss = sum(disc_loss_list) / len(disc_loss_list)\n\n    print(f'Epoch {epoch + 1} [Generator loss: {g_loss:.3f}] [Discriminator loss: {d_loss:.3f}]')\n    \n    results[\"generator_loss\"].append(g_loss)\n    results[\"discriminator_loss\"].append(d_loss)\n\n    # output an image during the train\n    if (epoch + 1) % sample_interval == 0:\n      sample_images(generator)\n  \n  return results","metadata":{"id":"_ulnrlQE7UVv"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Experiment 3","metadata":{"id":"qXY8iPuvLke7"}},{"cell_type":"markdown","source":"### Generator\nThe generator takes in a random noise vector as input and reshapes it into a 8x8x1024 tensor through a fully connected layer.","metadata":{"id":"nNQBFkZcLkfC"}},{"cell_type":"markdown","source":"#### Define generator","metadata":{"id":"6SdreN7hLkfD"}},{"cell_type":"code","source":"def build_generator():\n  model = Sequential()\n  model.add(Dense(8*8*1024, input_shape=(100,)))\n  model.add(Reshape((8, 8, 1024)))\n\n  model.add(Conv2DTranspose(512, (3, 3), strides=(2, 2), padding='same'))\n  model.add(LeakyReLU(alpha=0.4))\n\n  model.add(Conv2DTranspose(256, (3, 3), strides=(2, 2), padding='same'))\n  model.add(LeakyReLU(alpha=0.4))\n  \n  model.add(Conv2DTranspose(128, (3, 3), strides=(2, 2), padding='same'))\n  model.add(LeakyReLU(alpha=0.4))\n  \n  model.add(Conv2DTranspose(64, (3, 3), strides=(2, 2), padding='same'))\n  model.add(LeakyReLU(alpha=0.4))\n  \n  model.add(Conv2DTranspose(32, (3, 3), strides=(2, 2), padding='same'))\n  model.add(LeakyReLU(alpha=0.4))\n\n  model.add(Conv2DTranspose(3, (3, 3), strides=(1, 1), padding='same'))\n  model.add(Activation('tanh'))\n\n  return model","metadata":{"id":"8WZv1BrhLkfD"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Create generator","metadata":{"id":"CeyupiLALkfD"}},{"cell_type":"code","source":"generator = build_generator()","metadata":{"id":"S13pySehLkfD"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from keras.utils.vis_utils import plot_model\nplot_model(generator, to_file='gan_plot.png', show_shapes=True, show_layer_names=True)","metadata":{"id":"4szgdZ7Oig8W","outputId":"751b188a-f6b6-40fb-f3e1-f73e943fe549"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Display an example of generated image","metadata":{"id":"_ZgpvPWwLkfD"}},{"cell_type":"code","source":"noise = tf.random.normal([1, 100])\ngenerated_image = generator(noise, training=False)\n\nplt.imshow(generated_image[0, :, :, 0])","metadata":{"outputId":"77565216-68d6-41e0-d02c-23228a633acc","id":"xG0NWwJKLkfD"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Discriminator\nThe discriminator receives the 256x256x3 image as input from the generator, and run it through the model.<Br>\nThe final layer is a fully connected layer that uses a sigmoid activation function and outputs the probability that an image is real rather than fake.","metadata":{"id":"H1xr7xkTLkfE"}},{"cell_type":"markdown","source":"#### Create discriminator","metadata":{"id":"aBcSIq3ILkfE"}},{"cell_type":"code","source":"discriminator = build_discriminator()","metadata":{"id":"AxZ0oG71LkfE"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"##### Display an example of discriminator decision\nclassify the image we have generated above and decide whether its real or not (1 real, 0 fake)","metadata":{"id":"90PWsFvFLkfE"}},{"cell_type":"code","source":"decision = discriminator(generated_image)\nprint(decision)","metadata":{"outputId":"b385278f-8411-42d6-bf4c-0f6a156d08d5","id":"S7Qu8RRRLkfE"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Optimizers\nCreate two separated optimizers for the generator and discriminator","metadata":{"id":"8gPlea4vLkfF"}},{"cell_type":"code","source":"generator_optimizer = tf.keras.optimizers.Adam(learning_rate=0.0008, beta_1=0.5)\ndiscriminator_optimizer = tf.keras.optimizers.Adam(learning_rate=0.0003, beta_1=0.5)","metadata":{"id":"6I8CTpW0LkfF"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Loss functions\nCreate two separated loss functions for the generator and discriminator","metadata":{"id":"tdVgnNpULkfF"}},{"cell_type":"code","source":"cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)\n\ndef generator_loss(fake_output):\n  return cross_entropy(tf.ones_like(fake_output), fake_output)\n\ndef discriminator_loss(real_output, fake_output):\n  real_loss = cross_entropy(tf.ones_like(real_output), real_output)  # tf.ones_like creates a tensor of all ones that has the same shape as the input\n  fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)  # tf.zeros_like creates a tensor of all zeros that has the same shape as the input\n  total_loss = real_loss + fake_loss\n\n  return total_loss","metadata":{"id":"16AyMRt_LkfF"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train model","metadata":{"id":"56qulKhXLkfF"}},{"cell_type":"markdown","source":"#### Set parameters","metadata":{"id":"ppkePqG2LkfG"}},{"cell_type":"code","source":"EPOCHS = 100\nnoise_dim = 100\nsample_interval = EPOCHS / 10\nnum_examples_to_generate = 16","metadata":{"id":"_AomBMrGLkfG"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Start train","metadata":{"id":"_yUqCuKsLkfG"}},{"cell_type":"code","source":"exp3_history = train(monet_dataset, EPOCHS)","metadata":{"outputId":"29b4c96d-9d87-43a9-f9cf-4aa4ec0add1a","id":"zd1-qQc6LkfG"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Display results","metadata":{"id":"xBIlfO77LkfG"}},{"cell_type":"code","source":"plot_loss(exp3_history, \"Experiment 3\")","metadata":{"outputId":"3daa27d8-6192-440d-cfdf-4455d1ce1305","id":"IgJ-KVHqLkfH"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\n\nimage_dir =  '../DCGAN_images'\nif not os.path.exists(image_dir):\n  os.makedirs(image_dir)\n\nfor i in range(10):\n  noise = tf.random.normal([BATCH_SIZE, noise_dim])\n  submission_imgs = generator.predict(noise)\n  submission_imgs = 0.5 * submission_imgs + 0.5\n  submission_imgs = (submission_imgs * 255).astype('uint8')\n  submission = Image.fromarray(submission_imgs[0, :, :, :])\n  submission.save(f\"{image_dir}/{i}.jpg\")","metadata":{"id":"AZl-aD6MggPQ"},"execution_count":null,"outputs":[]}]}