---
title: "Generowanie obrazów"
output: 
  html_notebook: 
    theme: cerulean
    highlight: textmate
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(warning = FALSE, message = FALSE)
```


## Wariacyjne autoenkodery


Wariacyjne autoenkodery zostały opracowane jednocześnie przez Kingmę i Wellinga w grudniu 2013 r.  i Rezende, Mohameda i Wierstrę w styczniu 2014 r.. Sá to generatywne modele spraw-dzające się szczególnie w zadaniach edycji obrazu przy użyciu wektorów koncepcyjnych. Wariacyjne autoenkodery to nowoczesne podejście do autoenkoderów — sieci, która ma na celu zakodowanie danych wejściowych w postaci niskopoziomowej niejawnej przestrzeni, a następnie ich dekodowanie — jest to połączenie uczenia głębokiego z wnioskowaniem bayesowskim.

Klasyczny autoenkoder obrazu przetwarza obraz wejściowy, mapuje go na niejawną przestrzeń wektorową z zastosowaniem modułu kodera, a następnie dekoduje go do postaci wejściowej o takich samych wymiarach jak obraz oryginalny przy użyciu modułu dekodera. Autoenkoder jest trenowany tak, aby mógł dokonać rekonstrukcji obrazów wejściowych. Nakładając różne ograniczenia na wyjście kodera, można zmusić autoenkoder do uczenia się interesujących nas niejawnych reprezentacji danych. Zwykle kod ma być niskopoziomowy i rzadki (powinien zawierać głównie zera). W takim przypadku koder działa tak, jakby kompresował dane wejściowe do postaci formy składającej się z kilku bitów informacji.

![Autoencoder](img\8_4a.png)

W praktyce takie klasyczne autoenkodery nie pozwalają na uzyskanie szczególnie przydatnej przestrzeni o ładnej strukturze. Nie są one również zbyt wydajne w roli mechanizmów kompresji danych. W związku z tym wyszły z użycia. Technika kodowania VAE usprawniła działanie autoenkoderów, dodając do nich nieco magii statystyki, dzięki której są one w stanie uczyć się ciągłych przestrzeni charakteryzujących się określoną strukturą. W ten sposób powstało solidne narzędzie służące do generowania obrazów.

Koder VAE zamiast kompresować obraz wejściowy do określonej formy kodu niejawnej przestrzeni, zamienia obraz w parametry rozkładu statystycznego: średnią i wariancję. Oznacza to założenie, że obraz wejściowy został wygenerowany przez proces statystyczny i podczas kodowania i dekodowania należy wziąć pod uwagę losowość tego procesu. Koder VAE korzysta następnie z parametrów średniej i wariancji w celu losowego próbkowania jednego elementu rozkładu i zdekodowania go z powrotem do oryginalnej postaci. Stochastyczność tego procesu poprawia jego siłę i zmusza niejawną przestrzeń do zapisywania wszędzie reprezentacji mających znaczenie: każdy punkt próbkowany w tej przestrzeni jest dekodowany do postaci poprawnego obiektu wejściowego.


![VAE](img\8_4b.png)

Z technicznego punktu widzenia koder VAE działa w następujący sposób:
* 1.	Moduł kodera zamienia próbki wejściowe input_img na dwa parametry niejawnej przestrzeni reprezentacji: z_mean i z_log_variance.
* 2.	Punkt z jest losowo próbkowany z niejawnego rozkładu normalnego, co ma doprowadzić do wygenerowania obrazu wejściowego za pomocą działania z = z_mean + exp(z_log_variance) * epsilon, gdzie epsilon jest losowym tensorem o małych wartościach.
* 3.	Moduł dekodera mapuje ten punkt w niejawnej przestrzeni z powrotem na formę oryginalnego obrazu.

Parametry kodera VAE są trenowane przy użyciu dwóch funkcji straty: straty rekonstrukcji, która wymusza dopasowanie dekodowanych próbek do obrazów wejściowych, i straty regularyzacji, która wspiera tworzenie niejawnych przestrzeni o poprawnej formie, a także umożliwia zmniejszenie nadmiernego dopasowania do treningowego zbioru danych. 

Przeanalizujmy szybko implementację kodera VAE w pakiecie Keras. Schematycznie wygląda ona tak:


```{r, eval=FALSE}
# EKodowanie obrazu wejściowego za pomocą parametrów średniej i wariancji.
c(z_mean, z_log_variance) %<% encoder(input_img)

# Wyciąganie punktu z przestrzeni przy użyciu małej losowej wartości epsilon.
z <- z_mean + exp(z_log_variance) * epsilon 

# Dekodowanie obrazu.
reconstructed_img <- decoder(z) 

# Tworzenie instancji modelu autoenkodera mapującego obraz wejściowy na jego rekonstrukcję.
model <- keras_model(input_img, reconstructed_img)

```

Poniższy kod pokazuje sieć kodera mapującą obrazy na parametry rozkładu prawdopodobieństwa umieszczone w przestrzeni o niejawnym charakterze. W praktyce jest to prosta sieć konwo-lucyjna przypisująca obraz wejściowy x do dwóch wektorów: z_mean i z_log_var.

```{r}
library(keras)

img_shape <- c(28, 28, 1)
batch_size <- 16
latent_dim <- 2L  # Liczba wymiarów niejawnej przestrzeni: pracujemy z przestrzenią dwuwymiarową.

input_img <- layer_input(shape = img_shape)

x <- input_img %>% 
  layer_conv_2d(filters = 32, kernel_size = 3, padding = "same", 
                activation = "relu") %>% 
  layer_conv_2d(filters = 64, kernel_size = 3, padding = "same", 
                activation = "relu", strides = c(2, 2)) %>%
  layer_conv_2d(filters = 64, kernel_size = 3, padding = "same", 
                activation = "relu") %>%
  layer_conv_2d(filters = 64, kernel_size = 3, padding = "same", 
                activation = "relu") 

shape_before_flattening <- k_int_shape(x)

x <- x %>% 
  layer_flatten() %>% 
  layer_dense(units = 32, activation = "relu")

z_mean <- x %>% 
  layer_dense(units = latent_dim)

z_log_var <- x %>% 
  layer_dense(units = latent_dim)
```

Oto kod pozwalający na użycie parametrów rozkładu statystycz-nego z_mean i z_log_var, które z założenia mają pozwalać na utworzenie obrazu input_img w celu wygenerowania punktu z niejawnej przestrzeni. Część kodu R korzystającego z zaplecza pakietu Keras obudowujemy warstwą layer_lambda. W Keras wszystko musi być warstwą, a więc kod, który nie należy do wbudowanej warstwy, powinien mieć formę warstwy layer_lambda lub innej samodzielnie zdefiniowanej warstwy.

```{r}
sampling <- function(args) {
  c(z_mean, z_log_var) %<-% args
  epsilon <- k_random_normal(shape = list(k_shape(z_mean)[1], latent_dim),
                             mean = 0, stddev = 1)
  z_mean + k_exp(z_log_var) * epsilon
}

z <- list(z_mean, z_log_var) %>% 
  layer_lambda(sampling)
```

Poniższy fragment kodu przedstawia implementację dekodera. Wektor z jest modyfikowany tak, aby uzyskał wymiary obrazu, a następnie używanych jest kilka warstw konwolucyjnych w celu wygenerowania ostatecznej postaci obrazu wyjściowego mającego takie same wymiary jak oryginalny obraz input_img.

```{r}
# Wejście wektora z.
decoder_input <- layer_input(k_int_shape(z)[-1])

x <- decoder_input %>% 
  # Zwiększanie rozdzielczości obiektu wejściowego.
  layer_dense(units = prod(as.integer(shape_before_flattening[-1])),
              activation = "relu") %>% 
  # Zmiana kształtu wektora w celu uzyskania map cech o takim samym kształcie jak kształt mapy cech przed ostatnią warstwą flatten modułu kodującego.
  layer_reshape(target_shape = shape_before_flattening[-1]) %>% 
  # Warstwy używane w celu odkodowania wektora z do formy mapy cech 
  # o takim samym rozmiarze jak oryginalny obraz wejściowy.
  layer_conv_2d_transpose(filters = 32, kernel_size = 3, padding = "same",
                          activation = "relu", strides = c(2, 2)) %>%  
  layer_conv_2d(filters = 1, kernel_size = 3, padding = "same",
                activation = "sigmoid")  

# Tworzenie instancji modelu dekodera zamieniającego obiekt decoder_input na zdekodowany obraz.
decoder <- keras_model(decoder_input, x)

# Przyjmuje wektor z i zwraca jego zdekodowaną postać.
z_decoded <- decoder(z) 
```

Dualizm funkcji straty kodera VAE nie wpasowuje się w tradycyjne ramy funkcji próbującej w formie loss(input, target). W związku z tym zdefiniujemy dodatkową warstwę, która będzie wewnętrznie korzystać z metody add_loss w celu wygenerowania wartości straty.

```{r}
library(R6)

CustomVariationalLayer <- R6Class("CustomVariationalLayer",
                                  
  inherit = KerasLayer,
  
  public = list(
    
    vae_loss = function(x, z_decoded) {
      x <- k_flatten(x)
      z_decoded <- k_flatten(z_decoded)
      xent_loss <- metric_binary_crossentropy(x, z_decoded)
      kl_loss <- -5e-4 * k_mean(
        1 + z_log_var - k_square(z_mean) - k_exp(z_log_var), 
        axis = -1L
      )
      k_mean(xent_loss + kl_loss)
    },
    
    call = function(inputs, mask = NULL) {
      x <- inputs[[1]]
      z_decoded <- inputs[[2]]
      loss <- self$vae_loss(x, z_decoded)
      self$add_loss(loss, inputs = inputs)
      x
    }
  )
)

layer_variational <- function(object) { 
  create_layer(CustomVariationalLayer, object, list())
} 

# Wywołujemy własną warstwę na obiekcie wejściowym i odkodowanym obiekcie wyjściowym 
# w celu wygenerowania ostatecznego obiektu generowanego przez model.
y <- list(input_img, z_decoded) %>% 
  layer_variational() 
```

Teraz możemy utworzyć instancję modelu i ją wytrenować. Wartością straty zajęliśmy się w utworzonej ręcznie warstwie, a więc nie musimy określać zewnętrznej funkcji straty w czasie kompilacji (loss=NULL), co z kolei oznacza, że nie będziemy przekazywać docelowych danych w czasie trenowania (do funkcji fit() modelu przekazujemy tylko argument x_train).

```{r, echo=TRUE, results='hide'}
vae <- keras_model(input_img, y)

vae %>% compile(
  optimizer = "rmsprop",
  loss = NULL
)

# Trenowanie na zbiorze MNIST.
mnist <- dataset_mnist() 
c(c(x_train, y_train), c(x_test, y_test)) %<-% mnist

x_train <- x_train / 255
x_train <- array_reshape(x_train, dim =c(dim(x_train), 1))

x_test <- x_test / 255
x_test <- array_reshape(x_test, dim =c(dim(x_test), 1))

vae %>% fit(
  x = x_train, y = NULL,
  epochs = 10,
  batch_size = batch_size,
  validation_data = list(x_test, NULL)
)
```

Po wytrenowaniu modelu na zbiorze MNIST możemy użyć sieci de-coder w celu wygenerowania obrazów na podstawie dowolnej nie-jawnej przestrzeni wektorów:

```{r}
n <- 15            # Wyświetlamy siatkę 1515 cyfr (łącznie 255 cyfr).
digit_size <- 28   # Wysokość / szerokość cyfr (piksele)

# Transformacja liniowych współrzędnych przy użyciu funkcji qnorm 
# w celu wygenerowania wartości niejawnej zmiennej z (pracujemy z przestrzenią Gaussa).

grid_x <- qnorm(seq(0.05, 0.95, length.out = n))
grid_y <- qnorm(seq(0.05, 0.95, length.out = n))

op <- par(mfrow = c(n, n), mar = c(0,0,0,0), bg = "black")
for (i in 1:length(grid_x)) {
  yi <- grid_x[[i]]
  for (j in 1:length(grid_y)) {
    xi <- grid_y[[j]]
    z_sample <- matrix(c(xi, yi), nrow = 1, ncol = 2)
    z_sample <- t(replicate(batch_size, z_sample, simplify = "matrix"))
    x_decoded <- decoder %>% predict(z_sample, batch_size = batch_size)
    digit <- array_reshape(x_decoded[1,,,], dim = c(digit_size, digit_size))
    plot(as.raster(digit))
  }
}
par(op)
```

Wygenerowana siatka cyfr pokazuje całkowitą ciągłość rozkładu różnych klas cyfr — na kolejnych obra-zach widać, że cyfry stopniowo przechodzą w siebie. Kierunki w tej przestrzeni mają znaczenie: podążając w jednym z wybranych kierunków, otrzymujemy obraz przypominający bardziej cyfrę 4, a podrażając w innym otrzymujemy obraz przypominający bardziej cyfrę 1 itd.

