---
title: "Rekurencyjne sieci neuronowe"
output: 
  html_notebook: 
    theme: cerulean
    highlight: textmate
---

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



## Warstwa rekurencyjna w pakiecie Keras

Proces, który zaimplementowaliśmy przy użyciu biblioteki Numpy, jest odpowiednikiem prostej warstwy o nazwie layer_simple_rnn pakietu Keras:

```{r eval=FALSE}
layer_simple_rnn(units = 32)
```

Pomiędzy tymi implementacjami jest jednak pewna mała różnica: warstwa layer_simple_rnn dzieli sekwencje na wsady (operację taką wykonują wszystkie warstwy Keras) — nie przetwarza pojedynczych sekwencji tak, jak to miało miejsce w przykładzie zaimplementowanym za pomocą prostego kodu R. W związku z tym warstwa ta przyjmuje obiekty wejściowe o kształcie (rozmiar_wsadu, kroki_czasu, cechy_wejściowe), a nie (kroki_czasu, cechy_wejściowe).

Warstwa layer_simple_rnn, podobnie jak wszystkie rekurencyjne warstwy pakietu Keras, może być uruchomiona w dwóch trybach: może zwracać pełne sekwencje kolejnych obiektów wyjściowych dla każdego kroku czasu (trójwymiarowe tensory o kształcie (rozmiar_wsadu, kroki_czasu, cechy_wyjściowe)) lub tylko ostatnie obiekty wyjściowe poszczególnych sekwencji wejściowych (dwuwymiarowe tensory o kształcie (rozmiar_wsadu, cechy_wyjściowe)). Wybór trybu pracy jest dokonywany za pomocą argumentu return_sequences. Przeanalizujmy przykład, w którym zastosowano warstwę SimpleRNN, a dane wyjściowe są zwracane tylko podczas przetwarzania ostatniego kroku czasu:



```{r}
library(keras)
model <- keras_model_sequential() %>% 
  layer_embedding(input_dim = 10000, output_dim = 32) %>% 
  layer_simple_rnn(units = 32)

summary(model)
```

```{r}
model <- keras_model_sequential() %>% 
  layer_embedding(input_dim = 10000, output_dim = 32) %>% 
  layer_simple_rnn(units = 32, return_sequences = TRUE)

summary(model)
```

Czasami warto utworzyć stos składający się z kilku warstw rekurencyjnych. Zwiększa to siłę tworzenia reprezentacji przez sieć. Przy takiej konfiguracji wszystkie warstwy pośrednie muszą zwracać pełną sekwencję obiektów wyjściowych:

```{r}
model <- keras_model_sequential() %>% 
  layer_embedding(input_dim = 10000, output_dim = 32) %>% 
  layer_simple_rnn(units = 32, return_sequences = TRUE) %>% 
  layer_simple_rnn(units = 32, return_sequences = TRUE) %>%
  layer_simple_rnn(units = 32, return_sequences = TRUE) %>%
  layer_simple_rnn(units = 32)  # Ostatnia warstwa zwraca tylko ostatni obiekt wyjściowy.

summary(model)
```

Teraz użyjmy takiego modelu w celu rozwiązania problemu klasyfikacji recenzji filmów. Zacznijmy od wstępnej obróbki danych:

```{r}
library(keras)

max_features <- 10000  # Liczba słów traktowanych jako cechy.
maxlen <- 500  # Ucina recenzje po tej liczbie słów należących do zbioru max_features słów najczęściej występujących w zbiorze.
batch_size <- 32

cat("Ładowanie danych...\n")
imdb <- dataset_imdb(num_words = max_features)
c(c(input_train, y_train), c(input_test, y_test)) %<-% imdb 
cat(length(input_train), "sekwencje treningowe\n")
cat(length(input_test), "sekwencje testowe")

cat("Sekwencje (próbki x czas)\n")
input_train <- pad_sequences(input_train, maxlen = maxlen)
input_test <- pad_sequences(input_test, maxlen = maxlen)
cat("Kształt obiektu input_train:", dim(input_train), "\n")
cat("Kształt obiektu input_test:", dim(input_test), "\n")
```

Przeprowadźmy proces trenowania prostej rekurencyjnej sieci przy użyciu warstwy layer_embedding i warstwy layer_simple_rnn.

```{r, echo=TRUE, results='hide'}
model <- keras_model_sequential() %>%
  layer_embedding(input_dim = max_features, output_dim = 32) %>%
  layer_simple_rnn(units = 32) %>%
  layer_dense(units = 1, activation = "sigmoid")

model %>% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = c("acc")
)

history <- model %>% fit(
  input_train, y_train,
  epochs = 10,
  batch_size = 128,
  validation_split = 0.2
)
```

Teraz możemy wyświetlić wykresy dokładności i straty w procesach trenowania i walidacji:

```{r}
plot(history)
```

Pierwsze maksymalnie uproszczone rozwiązanie, przedstawione w rozdziale 3., doprowadziło do uzyskania dokładności testowej na poziomie 88%. Niestety ta mała rekurencyjna sieć działa o wiele słabiej od wspomnianego rozwiązania (uzyskujemy dokładność walidacyjną na poziomie zaledwie 85%). Problem ten wynika częściowo z tego, że nie przetwarzamy pełnych sekwencji, a tylko 500 pierwszych słów, a więc sieć rekurencyjna ma dostęp do mniejszej ilości informacji niż model przedstawiony w rozdziale 3. Ponadto warstwa layer_simple_rnn nie sprawdza się najlepiej podczas przetwarzania długich sekwencji takich jak tekst. W takiej sytuacji lepiej sprawdzają się inne warstwy rekurencyjne. Przyjrzyjmy się bardziej zaawansowanym warstwom tego typu.

## Warstwy LSTM i GRU

Czas przyjrzeć się praktycznemu przykładowi zastosowania warstwy layer_lstm. Skonfigurujemy model, w którym znajdzie się taka warstwa, i wytrenujemy go na zbiorze danych IMDB (patrz rysunek 6.13). Przypomina on zaprezentowany wcześniej model z warstwą layer_simple_rnn. Określimy tylko liczbę wymiarów obiektu wyjściowego warstwy layer_lstm. Pozostałe argumenty tej warstwy (jest ich wiele) pozostawimy przy wartościach domyślnych. Ustawienia domyślne pakietu Keras są przemyślane i zwykle „po prostu działają” bez konieczności poświęcania dużej ilości czasu na ręczne dostrajanie parametrów.

```{r, echo=TRUE, results='hide'}
model <- keras_model_sequential() %>% 
  layer_embedding(input_dim = max_features, output_dim = 32) %>% 
  layer_lstm(units = 32) %>% 
  layer_dense(units = 1, activation = "sigmoid")

model %>% compile(
  optimizer = "rmsprop", 
  loss = "binary_crossentropy", 
  metrics = c("acc")
)

history <- model %>% fit(
  input_train, y_train,
  epochs = 10,
  batch_size = 128,
  validation_split = 0.2
)
```

```{r}
plot(history)
```