pix2pix: Tłumaczenie obrazu na obraz z warunkowym GAN

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten samouczek pokazuje, jak zbudować i wytrenować warunkową generatywną sieć adwersarza (cGAN) o nazwie pix2pix, która uczy się mapowania z obrazów wejściowych na obrazy wyjściowe, zgodnie z opisem w Translacja obrazu na obraz za pomocą warunkowych sieci adwersarzy autorstwa Isola i in. (2017). pix2pix nie jest specyficzny dla aplikacji — można go zastosować do szerokiego zakresu zadań, w tym do syntezy zdjęć z map etykiet, generowania kolorowych zdjęć z obrazów czarno-białych, przekształcania zdjęć Google Maps w obrazy lotnicze, a nawet przekształcania szkiców w zdjęcia.

W tym przykładzie Twoja sieć wygeneruje obrazy fasad budynków przy użyciu bazy danych fasad CMP udostępnionej przez Centrum Percepcji Maszyn na Czeskim Uniwersytecie Technicznym w Pradze . Krótko mówiąc, użyjesz wstępnie przetworzonej kopii tego zbioru danych stworzonej przez autorów pix2pix.

W pix2pix cGAN warunkujesz obrazy wejściowe i generujesz odpowiednie obrazy wyjściowe. cGAN zostały po raz pierwszy zaproponowane w Warunkowych Generacyjnych Sieciach Przeciwnych (Mirza i Osindero, 2014)

Architektura Twojej sieci będzie zawierać:

  • Generator o architekturze opartej na U-Net .
  • Dyskryminator reprezentowany przez splotowy klasyfikator PatchGAN (zaproponowany w artykule pix2pix ).

Zwróć uwagę, że każda epoka może zająć około 15 sekund na pojedynczym GPU V100.

Poniżej znajduje się kilka przykładów danych wyjściowych generowanych przez cGAN pix2pix po treningu dla 200 epok na zestawie danych elewacji (80k kroków).

przykładowe wyjście_1przykładowe wyjście_2

Importuj TensorFlow i inne biblioteki

import tensorflow as tf

import os
import pathlib
import time
import datetime

from matplotlib import pyplot as plt
from IPython import display

Załaduj zbiór danych

Pobierz dane bazy danych fasad CMP (30 MB). Dodatkowe zbiory danych są dostępne w tym samym formacie tutaj . W Colab możesz wybrać inne zbiory danych z menu rozwijanego. Zauważ, że niektóre inne zestawy danych są znacznie większe ( edges2handbags to 8GB).

dataset_name = "facades"
_URL = f'http://efrosgans.eecs.berkeley.edu/pix2pix/datasets/{dataset_name}.tar.gz'

path_to_zip = tf.keras.utils.get_file(
    fname=f"{dataset_name}.tar.gz",
    origin=_URL,
    extract=True)

path_to_zip  = pathlib.Path(path_to_zip)

PATH = path_to_zip.parent/dataset_name
Downloading data from http://efrosgans.eecs.berkeley.edu/pix2pix/datasets/facades.tar.gz
30171136/30168306 [==============================] - 19s 1us/step
30179328/30168306 [==============================] - 19s 1us/step
list(PATH.parent.iterdir())
[PosixPath('/home/kbuilder/.keras/datasets/facades.tar.gz'),
 PosixPath('/home/kbuilder/.keras/datasets/YellowLabradorLooking_new.jpg'),
 PosixPath('/home/kbuilder/.keras/datasets/facades'),
 PosixPath('/home/kbuilder/.keras/datasets/mnist.npz')]

Każdy oryginalny obraz ma rozmiar 256 x 512 zawiera dwa obrazy 256 x 256 :

sample_image = tf.io.read_file(str(PATH / 'train/1.jpg'))
sample_image = tf.io.decode_jpeg(sample_image)
print(sample_image.shape)
(256, 512, 3)
plt.figure()
plt.imshow(sample_image)
<matplotlib.image.AxesImage at 0x7f35a3653c90>

png

Musisz oddzielić rzeczywiste obrazy elewacji budynku od obrazów etykiet architektury — wszystkie będą miały rozmiar 256 x 256 .

Zdefiniuj funkcję, która ładuje pliki obrazów i wyświetla dwa tensory obrazu:

def load(image_file):
  # Read and decode an image file to a uint8 tensor
  image = tf.io.read_file(image_file)
  image = tf.io.decode_jpeg(image)

  # Split each image tensor into two tensors:
  # - one with a real building facade image
  # - one with an architecture label image 
  w = tf.shape(image)[1]
  w = w // 2
  input_image = image[:, w:, :]
  real_image = image[:, :w, :]

  # Convert both images to float32 tensors
  input_image = tf.cast(input_image, tf.float32)
  real_image = tf.cast(real_image, tf.float32)

  return input_image, real_image

Wykreśl próbkę obrazu wejściowego (obraz etykiety architektury) i rzeczywistego (zdjęcie elewacji budynku):

inp, re = load(str(PATH / 'train/100.jpg'))
# Casting to int for matplotlib to display the images
plt.figure()
plt.imshow(inp / 255.0)
plt.figure()
plt.imshow(re / 255.0)
<matplotlib.image.AxesImage at 0x7f35981a4910>

png

png

Jak opisano w artykule pix2pix , musisz zastosować losowe jittering i mirroring, aby wstępnie przetworzyć zbiór treningowy.

Zdefiniuj kilka funkcji, które:

  1. Zmień rozmiar każdego obrazu 256 x 256 na większą wysokość i szerokość — 286 x 286 .
  2. Przytnij go losowo do 256 x 256 .
  3. Losowo przerzuć obraz w poziomie, tj. od lewej do prawej (losowe odbicie lustrzane).
  4. Normalizuj obrazy do zakresu [-1, 1] .
# The facade training set consist of 400 images
BUFFER_SIZE = 400
# The batch size of 1 produced better results for the U-Net in the original pix2pix experiment
BATCH_SIZE = 1
# Each image is 256x256 in size
IMG_WIDTH = 256
IMG_HEIGHT = 256
def resize(input_image, real_image, height, width):
  input_image = tf.image.resize(input_image, [height, width],
                                method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
  real_image = tf.image.resize(real_image, [height, width],
                               method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  return input_image, real_image
def random_crop(input_image, real_image):
  stacked_image = tf.stack([input_image, real_image], axis=0)
  cropped_image = tf.image.random_crop(
      stacked_image, size=[2, IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image[0], cropped_image[1]
# Normalizing the images to [-1, 1]
def normalize(input_image, real_image):
  input_image = (input_image / 127.5) - 1
  real_image = (real_image / 127.5) - 1

  return input_image, real_image
@tf.function()
def random_jitter(input_image, real_image):
  # Resizing to 286x286
  input_image, real_image = resize(input_image, real_image, 286, 286)

  # Random cropping back to 256x256
  input_image, real_image = random_crop(input_image, real_image)

  if tf.random.uniform(()) > 0.5:
    # Random mirroring
    input_image = tf.image.flip_left_right(input_image)
    real_image = tf.image.flip_left_right(real_image)

  return input_image, real_image

Możesz sprawdzić niektóre wstępnie przetworzone dane wyjściowe:

plt.figure(figsize=(6, 6))
for i in range(4):
  rj_inp, rj_re = random_jitter(inp, re)
  plt.subplot(2, 2, i + 1)
  plt.imshow(rj_inp / 255.0)
  plt.axis('off')
plt.show()

png

Po sprawdzeniu, czy ładowanie i przetwarzanie wstępne działa, zdefiniujmy kilka funkcji pomocniczych, które ładują i wstępnie przetwarzają zbiory uczące i testowe:

def load_image_train(image_file):
  input_image, real_image = load(image_file)
  input_image, real_image = random_jitter(input_image, real_image)
  input_image, real_image = normalize(input_image, real_image)

  return input_image, real_image
def load_image_test(image_file):
  input_image, real_image = load(image_file)
  input_image, real_image = resize(input_image, real_image,
                                   IMG_HEIGHT, IMG_WIDTH)
  input_image, real_image = normalize(input_image, real_image)

  return input_image, real_image

Zbuduj potok wejściowy za pomocą tf.data

train_dataset = tf.data.Dataset.list_files(str(PATH / 'train/*.jpg'))
train_dataset = train_dataset.map(load_image_train,
                                  num_parallel_calls=tf.data.AUTOTUNE)
train_dataset = train_dataset.shuffle(BUFFER_SIZE)
train_dataset = train_dataset.batch(BATCH_SIZE)
try:
  test_dataset = tf.data.Dataset.list_files(str(PATH / 'test/*.jpg'))
except tf.errors.InvalidArgumentError:
  test_dataset = tf.data.Dataset.list_files(str(PATH / 'val/*.jpg'))
test_dataset = test_dataset.map(load_image_test)
test_dataset = test_dataset.batch(BATCH_SIZE)

Zbuduj generator

Generatorem twojego pix2pix cGAN jest zmodyfikowany U-Net . Sieć U-Net składa się z kodera (downsamplera) i dekodera (upsamplera). (Więcej informacji na ten temat znajdziesz w samouczku dotyczącym segmentacji obrazów oraz na stronie projektu U-Net .)

  • Każdy blok w enkoderze to: Konwolucja -> Normalizacja wsadowa -> Leaky ReLU
  • Każdy blok w dekoderze to: Konwolucja transponowana -> Normalizacja wsadowa -> Odrzucenie (dotyczy pierwszych 3 bloków) -> ReLU
  • Między koderem a dekoderem występują połączenia pomijane (jak w U-Net).

Zdefiniuj downsampler (enkoder):

OUTPUT_CHANNELS = 3
def downsample(filters, size, apply_batchnorm=True):
  initializer = tf.random_normal_initializer(0., 0.02)

  result = tf.keras.Sequential()
  result.add(
      tf.keras.layers.Conv2D(filters, size, strides=2, padding='same',
                             kernel_initializer=initializer, use_bias=False))

  if apply_batchnorm:
    result.add(tf.keras.layers.BatchNormalization())

  result.add(tf.keras.layers.LeakyReLU())

  return result
down_model = downsample(3, 4)
down_result = down_model(tf.expand_dims(inp, 0))
print (down_result.shape)
(1, 128, 128, 3)

Zdefiniuj upsampler (dekoder):

def upsample(filters, size, apply_dropout=False):
  initializer = tf.random_normal_initializer(0., 0.02)

  result = tf.keras.Sequential()
  result.add(
    tf.keras.layers.Conv2DTranspose(filters, size, strides=2,
                                    padding='same',
                                    kernel_initializer=initializer,
                                    use_bias=False))

  result.add(tf.keras.layers.BatchNormalization())

  if apply_dropout:
      result.add(tf.keras.layers.Dropout(0.5))

  result.add(tf.keras.layers.ReLU())

  return result
up_model = upsample(3, 4)
up_result = up_model(down_result)
print (up_result.shape)
(1, 256, 256, 3)

Zdefiniuj generator za pomocą downsamplera i upsamplera:

def Generator():
  inputs = tf.keras.layers.Input(shape=[256, 256, 3])

  down_stack = [
    downsample(64, 4, apply_batchnorm=False),  # (batch_size, 128, 128, 64)
    downsample(128, 4),  # (batch_size, 64, 64, 128)
    downsample(256, 4),  # (batch_size, 32, 32, 256)
    downsample(512, 4),  # (batch_size, 16, 16, 512)
    downsample(512, 4),  # (batch_size, 8, 8, 512)
    downsample(512, 4),  # (batch_size, 4, 4, 512)
    downsample(512, 4),  # (batch_size, 2, 2, 512)
    downsample(512, 4),  # (batch_size, 1, 1, 512)
  ]

  up_stack = [
    upsample(512, 4, apply_dropout=True),  # (batch_size, 2, 2, 1024)
    upsample(512, 4, apply_dropout=True),  # (batch_size, 4, 4, 1024)
    upsample(512, 4, apply_dropout=True),  # (batch_size, 8, 8, 1024)
    upsample(512, 4),  # (batch_size, 16, 16, 1024)
    upsample(256, 4),  # (batch_size, 32, 32, 512)
    upsample(128, 4),  # (batch_size, 64, 64, 256)
    upsample(64, 4),  # (batch_size, 128, 128, 128)
  ]

  initializer = tf.random_normal_initializer(0., 0.02)
  last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, 4,
                                         strides=2,
                                         padding='same',
                                         kernel_initializer=initializer,
                                         activation='tanh')  # (batch_size, 256, 256, 3)

  x = inputs

  # Downsampling through the model
  skips = []
  for down in down_stack:
    x = down(x)
    skips.append(x)

  skips = reversed(skips[:-1])

  # Upsampling and establishing the skip connections
  for up, skip in zip(up_stack, skips):
    x = up(x)
    x = tf.keras.layers.Concatenate()([x, skip])

  x = last(x)

  return tf.keras.Model(inputs=inputs, outputs=x)

Wizualizuj architekturę modelu generatora:

generator = Generator()
tf.keras.utils.plot_model(generator, show_shapes=True, dpi=64)

png

Przetestuj generator:

gen_output = generator(inp[tf.newaxis, ...], training=False)
plt.imshow(gen_output[0, ...])
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
<matplotlib.image.AxesImage at 0x7f35cfd20610>

png

Zdefiniuj stratę generatora

GAN uczą się straty, która dostosowuje się do danych, podczas gdy cGAN uczą się straty strukturalnej, która karze możliwą strukturę, która różni się od wyjścia sieciowego i docelowego obrazu, jak opisano w artykule pix2pix .

  • Strata generatora jest sigmoidalną stratą w entropii krzyżowej generowanych obrazów i szeregu jedynków .
  • Artykuł pix2pix wspomina również o utracie L1, która jest MAE (średni błąd bezwzględny) między generowanym obrazem a obrazem docelowym.
  • Dzięki temu wygenerowany obraz staje się strukturalnie podobny do obrazu docelowego.
  • Wzór do obliczenia całkowitej straty generatora to gan_loss + LAMBDA * l1_loss , gdzie LAMBDA = 100 . O tej wartości zadecydowali autorzy artykułu.
LAMBDA = 100
loss_object = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def generator_loss(disc_generated_output, gen_output, target):
  gan_loss = loss_object(tf.ones_like(disc_generated_output), disc_generated_output)

  # Mean absolute error
  l1_loss = tf.reduce_mean(tf.abs(target - gen_output))

  total_gen_loss = gan_loss + (LAMBDA * l1_loss)

  return total_gen_loss, gan_loss, l1_loss

Procedura szkolenia dla generatora wygląda następująco:

Obraz aktualizacji generatora

Zbuduj dyskryminator

Dyskryminator w pix2pix cGAN jest zawiłym klasyfikatorem PatchGAN — próbuje on sklasyfikować, czy każda łatka obrazu jest prawdziwa, czy nie, jak opisano w artykule pix2pix .

  • Każdy blok w dyskryminatorze to: Konwolucja -> Normalizacja wsadowa -> Nieszczelna ReLU.
  • Kształt wyniku po ostatniej warstwie to (batch_size, 30, 30, 1) .
  • Każda łatka obrazu 30 x 30 na wyjściu klasyfikuje część 70 x 70 obrazu wejściowego.
  • Dyskryminator otrzymuje 2 wejścia:
    • Obraz wejściowy i obraz docelowy, który powinien zaklasyfikować jako rzeczywisty.
    • Obraz wejściowy i obraz wygenerowany (wyjście generatora), który powinien zaklasyfikować jako fałszywy.
    • Użyj tf.concat([inp, tar], axis=-1) , aby połączyć te 2 dane wejściowe.

Zdefiniujmy dyskryminator:

def Discriminator():
  initializer = tf.random_normal_initializer(0., 0.02)

  inp = tf.keras.layers.Input(shape=[256, 256, 3], name='input_image')
  tar = tf.keras.layers.Input(shape=[256, 256, 3], name='target_image')

  x = tf.keras.layers.concatenate([inp, tar])  # (batch_size, 256, 256, channels*2)

  down1 = downsample(64, 4, False)(x)  # (batch_size, 128, 128, 64)
  down2 = downsample(128, 4)(down1)  # (batch_size, 64, 64, 128)
  down3 = downsample(256, 4)(down2)  # (batch_size, 32, 32, 256)

  zero_pad1 = tf.keras.layers.ZeroPadding2D()(down3)  # (batch_size, 34, 34, 256)
  conv = tf.keras.layers.Conv2D(512, 4, strides=1,
                                kernel_initializer=initializer,
                                use_bias=False)(zero_pad1)  # (batch_size, 31, 31, 512)

  batchnorm1 = tf.keras.layers.BatchNormalization()(conv)

  leaky_relu = tf.keras.layers.LeakyReLU()(batchnorm1)

  zero_pad2 = tf.keras.layers.ZeroPadding2D()(leaky_relu)  # (batch_size, 33, 33, 512)

  last = tf.keras.layers.Conv2D(1, 4, strides=1,
                                kernel_initializer=initializer)(zero_pad2)  # (batch_size, 30, 30, 1)

  return tf.keras.Model(inputs=[inp, tar], outputs=last)

Wizualizuj architekturę modelu dyskryminatora:

discriminator = Discriminator()
tf.keras.utils.plot_model(discriminator, show_shapes=True, dpi=64)

png

Przetestuj dyskryminator:

disc_out = discriminator([inp[tf.newaxis, ...], gen_output], training=False)
plt.imshow(disc_out[0, ..., -1], vmin=-20, vmax=20, cmap='RdBu_r')
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7f35cec82c50>

png

Zdefiniuj stratę dyskryminacyjną

  • Funkcja discriminator_loss przyjmuje 2 dane wejściowe: obrazy rzeczywiste i obrazy wygenerowane .
  • real_loss to sigmoidalna utrata entropii krzyżowej rzeczywistych obrazów i tablicy jedynek (ponieważ są to rzeczywiste obrazy) .
  • generated_loss to sigmoidalna utrata entropii krzyżowej wygenerowanych obrazów i tablicy zer (ponieważ są to fałszywe obrazy) .
  • total_loss to suma real_loss i generated_loss .
def discriminator_loss(disc_real_output, disc_generated_output):
  real_loss = loss_object(tf.ones_like(disc_real_output), disc_real_output)

  generated_loss = loss_object(tf.zeros_like(disc_generated_output), disc_generated_output)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss

Poniżej przedstawiono procedurę szkolenia dyskryminatora.

Aby dowiedzieć się więcej o architekturze i hiperparametrach, zapoznaj się z artykułem pix2pix .

Obraz aktualizacji dyskryminatora

Zdefiniuj optymalizatory i punkt kontrolny-saver

generator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)

Generuj obrazy

Napisz funkcję do wykreślania niektórych obrazów podczas treningu.

  • Przekaż obrazy z zestawu testowego do generatora.
  • Generator następnie przetłumaczy obraz wejściowy na wyjście.
  • Ostatnim krokiem jest wykreślenie prognoz i voila !
def generate_images(model, test_input, tar):
  prediction = model(test_input, training=True)
  plt.figure(figsize=(15, 15))

  display_list = [test_input[0], tar[0], prediction[0]]
  title = ['Input Image', 'Ground Truth', 'Predicted Image']

  for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.title(title[i])
    # Getting the pixel values in the [0, 1] range to plot.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()

Przetestuj funkcję:

for example_input, example_target in test_dataset.take(1):
  generate_images(generator, example_input, example_target)

png

Trening

  • Dla każdego przykładu wejście generuje dane wyjściowe.
  • Dyskryminator odbiera input_image i wygenerowany obraz jako pierwszy sygnał wejściowy. Drugie dane wejściowe to input_image i target_image .
  • Następnie oblicz generator i stratę dyskryminatora.
  • Następnie oblicz gradienty strat w odniesieniu do zmiennych generatora i dyskryminatora (wejścia) i zastosuj je do optymalizatora.
  • Na koniec zarejestruj straty w TensorBoard.
log_dir="logs/"

summary_writer = tf.summary.create_file_writer(
  log_dir + "fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
@tf.function
def train_step(input_image, target, step):
  with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
    gen_output = generator(input_image, training=True)

    disc_real_output = discriminator([input_image, target], training=True)
    disc_generated_output = discriminator([input_image, gen_output], training=True)

    gen_total_loss, gen_gan_loss, gen_l1_loss = generator_loss(disc_generated_output, gen_output, target)
    disc_loss = discriminator_loss(disc_real_output, disc_generated_output)

  generator_gradients = gen_tape.gradient(gen_total_loss,
                                          generator.trainable_variables)
  discriminator_gradients = disc_tape.gradient(disc_loss,
                                               discriminator.trainable_variables)

  generator_optimizer.apply_gradients(zip(generator_gradients,
                                          generator.trainable_variables))
  discriminator_optimizer.apply_gradients(zip(discriminator_gradients,
                                              discriminator.trainable_variables))

  with summary_writer.as_default():
    tf.summary.scalar('gen_total_loss', gen_total_loss, step=step//1000)
    tf.summary.scalar('gen_gan_loss', gen_gan_loss, step=step//1000)
    tf.summary.scalar('gen_l1_loss', gen_l1_loss, step=step//1000)
    tf.summary.scalar('disc_loss', disc_loss, step=step//1000)

Rzeczywista pętla treningowa. Ponieważ w tym samouczku można uruchomić więcej niż jeden zestaw danych, a zestawy danych różnią się znacznie rozmiarem, pętla treningowa jest skonfigurowana do pracy w krokach, a nie w epokach.

  • Iteruje przez liczbę kroków.
  • Co 10 kroków drukuj kropkę ( . ).
  • Co 1k kroków: wyczyść wyświetlacz i uruchom generate_images , aby pokazać postęp.
  • Co 5k kroków: zapisz punkt kontrolny.
def fit(train_ds, test_ds, steps):
  example_input, example_target = next(iter(test_ds.take(1)))
  start = time.time()

  for step, (input_image, target) in train_ds.repeat().take(steps).enumerate():
    if (step) % 1000 == 0:
      display.clear_output(wait=True)

      if step != 0:
        print(f'Time taken for 1000 steps: {time.time()-start:.2f} sec\n')

      start = time.time()

      generate_images(generator, example_input, example_target)
      print(f"Step: {step//1000}k")

    train_step(input_image, target, step)

    # Training step
    if (step+1) % 10 == 0:
      print('.', end='', flush=True)


    # Save (checkpoint) the model every 5k steps
    if (step + 1) % 5000 == 0:
      checkpoint.save(file_prefix=checkpoint_prefix)

Ta pętla treningowa zapisuje logi, które możesz przeglądać w TensorBoard, aby monitorować postęp treningu.

Jeśli pracujesz na komputerze lokalnym, uruchomisz osobny proces TensorBoard. Podczas pracy w zeszycie uruchom przeglądarkę przed rozpoczęciem szkolenia w zakresie monitorowania za pomocą TensorBoard.

Aby uruchomić przeglądarkę, wklej następujące elementy do komórki kodu:

%load_ext tensorboard
%tensorboard --logdir {log_dir}

Na koniec uruchom pętlę treningową:

fit(train_dataset, test_dataset, steps=40000)
Time taken for 1000 steps: 36.53 sec

png

Step: 39k
....................................................................................................

Jeśli chcesz udostępnić wyniki TensorBoard publicznie , możesz przesłać logi do TensorBoard.dev , kopiując następujące elementy do komórki kodu.

tensorboard dev upload --logdir {log_dir}

Możesz zobaczyć wyniki poprzedniego uruchomienia tego notatnika na TensorBoard.dev .

TensorBoard.dev to zarządzane środowisko do hostingu, śledzenia i udostępniania eksperymentów ML ze wszystkimi.

Można go również dołączyć w tekście za pomocą <iframe> :

display.IFrame(
    src="https://tensorboard.dev/experiment/lZ0C6FONROaUMfjYkVyJqw",
    width="100%",
    height="1000px")

Interpretacja logów jest bardziej subtelna podczas trenowania GAN (lub cGAN jak pix2pix) w porównaniu z prostym modelem klasyfikacji lub regresji. Czego szukać:

  • Sprawdź, czy ani generator, ani model dyskryminatora nie "wygrał". Jeśli wartość gen_gan_loss lub disc_loss jest bardzo niska, oznacza to, że ten model dominuje nad innym i nie udaje Ci się trenować modelu połączonego.
  • Wartość log(2) = 0.69 jest dobrym punktem odniesienia dla tych strat, ponieważ wskazuje na zakłopotanie 2 – dyskryminator jest średnio równie niepewny co do dwóch opcji.
  • W przypadku disc_loss wartość poniżej 0.69 oznacza, że ​​dyskryminator radzi sobie lepiej niż losowo na połączonym zestawie obrazów rzeczywistych i wygenerowanych.
  • W przypadku gen_gan_loss , wartość poniżej 0.69 oznacza, że ​​generator radzi sobie lepiej niż losowy w oszukiwaniu dyskryminatora.
  • Wraz z postępem treningu, gen_l1_loss powinna spadać.

Przywróć najnowszy punkt kontrolny i przetestuj sieć

ls {checkpoint_dir}
checkpoint          ckpt-5.data-00000-of-00001
ckpt-1.data-00000-of-00001  ckpt-5.index
ckpt-1.index            ckpt-6.data-00000-of-00001
ckpt-2.data-00000-of-00001  ckpt-6.index
ckpt-2.index            ckpt-7.data-00000-of-00001
ckpt-3.data-00000-of-00001  ckpt-7.index
ckpt-3.index            ckpt-8.data-00000-of-00001
ckpt-4.data-00000-of-00001  ckpt-8.index
ckpt-4.index
# Restoring the latest checkpoint in checkpoint_dir
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f35cfd6b8d0>

Wygeneruj kilka obrazów za pomocą zestawu testowego

# Run the trained model on a few examples from the test set
for inp, tar in test_dataset.take(5):
  generate_images(generator, inp, tar)

png

png

png

png

png