Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Powtarzalne sieci neuronowe (RNN) z Keras

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

Wprowadzenie

Rekurencyjne sieci neuronowe (RNN) to klasa sieci neuronowych, która jest potężna w modelowaniu danych sekwencyjnych, takich jak szeregi czasowe lub język naturalny.

Schematycznie, warstwa RNN używa pętli for do iteracji po krokach czasowych sekwencji, przy jednoczesnym zachowaniu stanu wewnętrznego, który koduje informacje o krokach, które do tej pory widziała.

Interfejs API Keras RNN został zaprojektowany z naciskiem na:

  • Łatwość użytkowania : wbudowane keras.layers.RNN , keras.layers.LSTM , keras.layers.GRU umożliwiają szybkie tworzenie powtarzających się modeli bez konieczności dokonywania trudnych wyborów konfiguracyjnych.

  • Łatwość dostosowywania : możesz także zdefiniować własną warstwę komórek RNN ​​(wewnętrzną część pętli for ) z niestandardowym zachowaniem i używać jej z ogólną warstwą keras.layers.RNN (samą pętlą for ). Pozwala to na szybkie prototypowanie różnych pomysłów badawczych w elastyczny sposób przy minimalnej ilości kodu.

Ustawiać

 import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
 

Wbudowane warstwy RNN: prosty przykład

W Keras są trzy wbudowane warstwy RNN:

  1. keras.layers.SimpleRNN , w pełni połączony RNN, w którym dane wyjściowe z poprzedniego etapu czasu mają być przesyłane do następnego etapu.

  2. keras.layers.GRU , po raz pierwszy zaproponowany w Cho i in., 2014 .

  3. keras.layers.LSTM , po raz pierwszy zaproponowany w Hochreiter & Schmidhuber, 1997 .

Na początku 2015 r. Keras miał pierwsze wielokrotne implementacje LSTM i GRU w języku Python typu open source.

Oto prosty przykład modelu Sequential który przetwarza sekwencje liczb całkowitych, osadza każdą liczbę całkowitą w 64-wymiarowym wektorze, a następnie przetwarza sekwencję wektorów przy użyciu warstwy LSTM .

 model = keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))

# Add a Dense layer with 10 units.
model.add(layers.Dense(10))

model.summary()
 
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 64)          64000     
_________________________________________________________________
lstm (LSTM)                  (None, 128)               98816     
_________________________________________________________________
dense (Dense)                (None, 10)                1290      
=================================================================
Total params: 164,106
Trainable params: 164,106
Non-trainable params: 0
_________________________________________________________________

Wbudowane sieci RNN obsługują szereg przydatnych funkcji:

  • Powtarzające się porzucanie za pośrednictwem argumentów dropout i recurrent_dropout
  • Możliwość odwrotnego przetwarzania sekwencji wejściowej za pomocą argumentu go_backwards
  • Rozwijanie pętli (co może prowadzić do dużego przyspieszenia podczas przetwarzania krótkich sekwencji na CPU), poprzez argument unroll
  • ...i więcej.

Aby uzyskać więcej informacji, zobacz dokumentację interfejsu API RNN .

Wyjścia i stany

Domyślnie wynik warstwy RNN zawiera jeden wektor na próbkę. Ten wektor jest wyjściem komórki RNN odpowiadającym ostatniemu krokowi czasowemu, zawierającym informacje o całej sekwencji wejściowej. Kształt tego wyniku to (batch_size, units) gdzie units odpowiadają argumentowi units przekazanemu do konstruktora warstwy.

Warstwa RNN może również zwrócić całą sekwencję wyników dla każdej próbki (jeden wektor na krok czasu na próbkę), jeśli ustawisz return_sequences=True . Kształt tego wyniku to (batch_size, timesteps, units) .

 model = keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))

# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))

model.add(layers.Dense(10))

model.summary()
 
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, None, 64)          64000     
_________________________________________________________________
gru (GRU)                    (None, None, 256)         247296    
_________________________________________________________________
simple_rnn (SimpleRNN)       (None, 128)               49280     
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 361,866
Trainable params: 361,866
Non-trainable params: 0
_________________________________________________________________

Ponadto warstwa RNN może przywrócić swój końcowy stan (y) wewnętrzny. Zwrócone stany mogą zostać użyte do wznowienia wykonywania RNN później lub do zainicjowania innego RNN . To ustawienie jest powszechnie używane w modelu sekwencji kodera-dekodera-do-sekwencji, w którym stan końcowy kodera jest używany jako stan początkowy dekodera.

Aby skonfigurować warstwę RNN tak, aby return_state swój stan wewnętrzny, ustaw parametr return_state na True podczas tworzenia warstwy. Zauważ, że LSTM ma 2 tensory stanu, ale GRU ma tylko jeden.

Aby skonfigurować początkowy stan warstwy, po prostu wywołaj warstwę z dodatkowym argumentem słowa kluczowego initial_state . Zwróć uwagę, że kształt stanu musi pasować do wielkości jednostki warstwy, jak w poniższym przykładzie.

 encoder_vocab = 1000
decoder_vocab = 2000

encoder_input = layers.Input(shape=(None,))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(
    encoder_input
)

# Return states in addition to output
output, state_h, state_c = layers.LSTM(64, return_state=True, name="encoder")(
    encoder_embedded
)
encoder_state = [state_h, state_c]

decoder_input = layers.Input(shape=(None,))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(
    decoder_input
)

# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(64, name="decoder")(
    decoder_embedded, initial_state=encoder_state
)
output = layers.Dense(10)(decoder_output)

model = keras.Model([encoder_input, decoder_input], output)
model.summary()
 
Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            [(None, None)]       0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            [(None, None)]       0                                            
__________________________________________________________________________________________________
embedding_2 (Embedding)         (None, None, 64)     64000       input_1[0][0]                    
__________________________________________________________________________________________________
embedding_3 (Embedding)         (None, None, 64)     128000      input_2[0][0]                    
__________________________________________________________________________________________________
encoder (LSTM)                  [(None, 64), (None,  33024       embedding_2[0][0]                
__________________________________________________________________________________________________
decoder (LSTM)                  (None, 64)           33024       embedding_3[0][0]                
                                                                 encoder[0][1]                    
                                                                 encoder[0][2]                    
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 10)           650         decoder[0][0]                    
==================================================================================================
Total params: 258,698
Trainable params: 258,698
Non-trainable params: 0
__________________________________________________________________________________________________

Warstwy RNN i komórki RNN

Oprócz wbudowanych warstw RNN, interfejs API RNN zapewnia również interfejsy API na poziomie komórki. W przeciwieństwie do warstw RNN, które przetwarzają całe partie sekwencji wejściowych, komórka RNN przetwarza tylko jeden krok czasu.

Komórka jest wnętrzem pętli for warstwy RNN. Opakowanie komórki wewnątrz warstwy keras.layers.RNN daje warstwę zdolną do przetwarzania partii sekwencji, np. RNN(LSTMCell(10)) .

Matematycznie RNN(LSTMCell(10)) daje taki sam wynik jak LSTM(10) . W rzeczywistości implementacja tej warstwy w TF v1.x polegała tylko na utworzeniu odpowiedniej komórki RNN i zawinięciu jej w warstwę RNN. Jednak użycie wbudowanych warstw GRU i LSTM umożliwia korzystanie z CuDNN i możesz zauważyć lepszą wydajność.

Istnieją trzy wbudowane komórki RNN, każda z nich odpowiada pasującej warstwie RNN.

Abstrakcja komórki, wraz z ogólną klasą keras.layers.RNN , bardzo ułatwia wdrażanie niestandardowych architektur RNN do badań.

Stanowość między partiami

Podczas przetwarzania bardzo długich sekwencji (prawdopodobnie nieskończonych), możesz chcieć użyć wzorca stanowości między wsadami .

Zwykle stan wewnętrzny warstwy RNN jest resetowany za każdym razem, gdy widzi nową partię (tj. Zakłada się, że każda próbka widziana przez warstwę jest niezależna od przeszłości). Warstwa zachowa stan tylko podczas przetwarzania danej próbki.

Jeśli jednak masz bardzo długie sekwencje, warto podzielić je na krótsze sekwencje i wprowadzić te krótsze sekwencje sekwencyjnie do warstwy RNN bez resetowania stanu warstwy. W ten sposób warstwa może zachować informacje o całej sekwencji, mimo że w danym momencie widzi tylko jedną sekwencję podrzędną.

Możesz to zrobić, ustawiając w konstruktorze stateful=True .

Jeśli masz sekwencję s = [t0, t1, ... t1546, t1547] , możesz podzielić ją np.

 s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
 

Następnie możesz to przetworzyć za pomocą:

 lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
  output = lstm_layer(s)
 

Jeśli chcesz wyczyścić stan, możesz użyć layer.reset_states() .

Oto pełny przykład:

 paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)

lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)

# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()

 

Ponowne wykorzystanie stanu RNN

Zarejestrowane stany warstwy RNN nie są uwzględniane w sekcji layer.weights() . Jeśli chcesz ponownie użyć stanu z warstwy RNN, możesz pobrać wartość stanów według layer.states i użyć jej jako stanu początkowego dla nowej warstwy za pośrednictwem funkcjonalnego interfejsu API Keras, takiego jak new_layer(inputs, initial_state=layer.states) lub podklasy modeli.

Należy również pamiętać, że model sekwencyjny może nie być używany w tym przypadku, ponieważ obsługuje on tylko warstwy z jednym wejściem i wyjściem, dodatkowe wejście stanu początkowego uniemożliwia użycie tutaj.

 paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)

lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)

existing_state = lstm_layer.states

new_lstm_layer = layers.LSTM(64)
new_output = new_lstm_layer(paragraph3, initial_state=existing_state)

 

Dwukierunkowe sieci RNN

W przypadku sekwencji innych niż szeregi czasowe (np. Tekst) często jest tak, że model RNN może działać lepiej, jeśli nie tylko przetwarza sekwencję od początku do końca, ale także wstecz. Na przykład, aby przewidzieć następne słowo w zdaniu, często warto mieć kontekst wokół tego słowa, a nie tylko słowa, które występują przed nim.

Keras zapewnia łatwe API do tworzenia dwukierunkowych numerów RNN: keras.layers.Bidirectional wrapper.

 model = keras.Sequential()

model.add(
    layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10))
)
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))

model.summary()
 
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
bidirectional (Bidirectional (None, 5, 128)            38400     
_________________________________________________________________
bidirectional_1 (Bidirection (None, 64)                41216     
_________________________________________________________________
dense_3 (Dense)              (None, 10)                650       
=================================================================
Total params: 80,266
Trainable params: 80,266
Non-trainable params: 0
_________________________________________________________________

Pod maską, Bidirectional skopiuje przekazaną warstwę RNN i go_backwards pole go_backwards nowo skopiowanej warstwy, aby przetworzyć dane wejściowe w odwrotnej kolejności.

Dane wyjściowe Bidirectional RNN będą domyślnie sumą danych wyjściowych warstwy przedniej i warstwy wyjściowej wstecznej. Jeśli potrzebujesz innego zachowania przy scalaniu, np. Konkatenacji, zmień parametr merge_mode w konstruktorze Bidirectional opakowania. Więcej informacji na temat Bidirectional można znaleźć w dokumentacji interfejsu API .

Optymalizacja wydajności i jądra CuDNN

W TensorFlow 2.0 wbudowane warstwy LSTM i GRU zostały zaktualizowane, aby domyślnie wykorzystywać jądra CuDNN, gdy dostępny jest procesor graficzny. Dzięki tej zmianie poprzednie warstwy keras.layers.CuDNNLSTM/CuDNNGRU stały się przestarzałe i możesz zbudować swój model bez martwienia się o sprzęt, na którym będzie działać.

Ponieważ jądro CuDNN jest zbudowane przy pewnych założeniach, oznacza to, że warstwa nie będzie mogła korzystać z jądra CuDNN, jeśli zmienisz wartości domyślne wbudowanych warstw LSTM lub GRU . Na przykład:

  • Zmiana funkcji activation z tanh na coś innego.
  • Zmiana funkcji recurrent_activation z sigmoid na inną.
  • Korzystanie z recurrent_dropout > 0.
  • Ustawienie unroll na True, co zmusza LSTM / GRU do dekompozycji wewnętrznego tf.while_loop na rozwiniętą pętlę for .
  • Ustawienie use_bias na False.
  • Używanie maskowania, gdy dane wejściowe nie są dokładnie wypełnione prawidłowo (jeśli maska ​​odpowiada ściśle poprawnym danym wypełnionym, nadal można używać CuDNN. Jest to najczęstszy przypadek).

Szczegółowa lista ograniczeń znajduje się w dokumentacji warstw LSTM i GRU .

Korzystanie z jądra CuDNN, jeśli są dostępne

Zbudujmy prosty model LSTM, aby zademonstrować różnicę w wydajności.

Jako sekwencje wejściowe użyjemy sekwencji wierszy cyfr MNIST (traktując każdy wiersz pikseli jako krok czasu) i przewidujemy etykietę cyfry.

 batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28

units = 64
output_size = 10  # labels are from 0 to 9

# Build the RNN model
def build_model(allow_cudnn_kernel=True):
    # CuDNN is only available at the layer level, and not at the cell level.
    # This means `LSTM(units)` will use the CuDNN kernel,
    # while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
    if allow_cudnn_kernel:
        # The LSTM layer with default options uses CuDNN.
        lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))
    else:
        # Wrapping a LSTMCell in a RNN layer will not use CuDNN.
        lstm_layer = keras.layers.RNN(
            keras.layers.LSTMCell(units), input_shape=(None, input_dim)
        )
    model = keras.models.Sequential(
        [
            lstm_layer,
            keras.layers.BatchNormalization(),
            keras.layers.Dense(output_size),
        ]
    )
    return model

 

Załadujmy zbiór danych MNIST:

 mnist = keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]
 

Stwórzmy instancję modelu i wytrenujmy ją.

sparse_categorical_crossentropy funkcję straty dla modelu wybieramy sparse_categorical_crossentropy . Dane wyjściowe modelu mają kształt [batch_size, 10] . Celem modelu jest wektor całkowity, każda z tych liczb całkowitych mieści się w zakresie od 0 do 9.

 model = build_model(allow_cudnn_kernel=True)

model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer="sgd",
    metrics=["accuracy"],
)


model.fit(
    x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
 
938/938 [==============================] - 5s 5ms/step - loss: 0.9479 - accuracy: 0.6979 - val_loss: 0.5026 - val_accuracy: 0.8424

<tensorflow.python.keras.callbacks.History at 0x7fc1900c8128>

Teraz porównajmy z modelem, który nie korzysta z jądra CuDNN:

 noncudnn_model = build_model(allow_cudnn_kernel=False)
noncudnn_model.set_weights(model.get_weights())
noncudnn_model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer="sgd",
    metrics=["accuracy"],
)
noncudnn_model.fit(
    x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
 
938/938 [==============================] - 36s 38ms/step - loss: 0.3927 - accuracy: 0.8809 - val_loss: 0.2804 - val_accuracy: 0.9132

<tensorflow.python.keras.callbacks.History at 0x7fc144215cc0>

Podczas pracy na maszynie z zainstalowanym procesorem graficznym NVIDIA i CuDNN model zbudowany przy użyciu CuDNN jest znacznie szybszy w uczeniu w porównaniu z modelem, który korzysta ze zwykłego jądra TensorFlow.

Ten sam model z włączoną obsługą CuDNN może być również używany do uruchamiania wnioskowania w środowisku tylko z procesorem CPU. tf.device adnotacja tf.device wymusza tylko umieszczenie urządzenia. Model będzie domyślnie działał na procesorze, jeśli nie jest dostępny procesor graficzny.

Po prostu nie musisz już martwić się o sprzęt, na którym pracujesz. Czy to nie jest całkiem fajne?

 import matplotlib.pyplot as plt

with tf.device("CPU:0"):
    cpu_model = build_model(allow_cudnn_kernel=True)
    cpu_model.set_weights(model.get_weights())
    result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
    print(
        "Predicted result is: %s, target result is: %s" % (result.numpy(), sample_label)
    )
    plt.imshow(sample, cmap=plt.get_cmap("gray"))
 
Predicted result is: [3], target result is: 5

png

Numery RNN z danymi wejściowymi typu list / dict lub danymi wejściowymi zagnieżdżonymi

Struktury zagnieżdżone pozwalają realizatorom na zawarcie większej ilości informacji w jednym kroku czasowym. Na przykład klatka wideo może mieć jednocześnie wejście audio i wideo. Kształt danych w tym przypadku mógłby wyglądać następująco:

[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]

W innym przykładzie dane pisma ręcznego mogą mieć zarówno współrzędne xiy dla aktualnej pozycji pióra, jak i informacje o nacisku. Zatem reprezentacja danych mogłaby wyglądać następująco:

[batch, timestep, {"location": [x, y], "pressure": [force]}]

Poniższy kod zawiera przykład sposobu tworzenia niestandardowej komórki RNN, która akceptuje takie strukturalne dane wejściowe.

Zdefiniuj niestandardową komórkę, która obsługuje zagnieżdżone dane wejściowe / wyjściowe

Zobacz Tworzenie nowych warstw i modeli za pomocą podklas, aby uzyskać szczegółowe informacje na temat pisania własnych warstw.

 class NestedCell(keras.layers.Layer):
    def __init__(self, unit_1, unit_2, unit_3, **kwargs):
        self.unit_1 = unit_1
        self.unit_2 = unit_2
        self.unit_3 = unit_3
        self.state_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
        self.output_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
        super(NestedCell, self).__init__(**kwargs)

    def build(self, input_shapes):
        # expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
        i1 = input_shapes[0][1]
        i2 = input_shapes[1][1]
        i3 = input_shapes[1][2]

        self.kernel_1 = self.add_weight(
            shape=(i1, self.unit_1), initializer="uniform", name="kernel_1"
        )
        self.kernel_2_3 = self.add_weight(
            shape=(i2, i3, self.unit_2, self.unit_3),
            initializer="uniform",
            name="kernel_2_3",
        )

    def call(self, inputs, states):
        # inputs should be in [(batch, input_1), (batch, input_2, input_3)]
        # state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
        input_1, input_2 = tf.nest.flatten(inputs)
        s1, s2 = states

        output_1 = tf.matmul(input_1, self.kernel_1)
        output_2_3 = tf.einsum("bij,ijkl->bkl", input_2, self.kernel_2_3)
        state_1 = s1 + output_1
        state_2_3 = s2 + output_2_3

        output = (output_1, output_2_3)
        new_states = (state_1, state_2_3)

        return output, new_states

    def get_config(self):
        return {"unit_1": self.unit_1, "unit_2": unit_2, "unit_3": self.unit_3}

 

Zbuduj model RNN z zagnieżdżonymi wejściami / wyjściami

Zbudujmy model Keras, który wykorzystuje warstwę keras.layers.RNN i niestandardową komórkę, którą właśnie zdefiniowaliśmy.

 unit_1 = 10
unit_2 = 20
unit_3 = 30

i1 = 32
i2 = 64
i3 = 32
batch_size = 64
num_batches = 10
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = keras.layers.RNN(cell)

input_1 = keras.Input((None, i1))
input_2 = keras.Input((None, i2, i3))

outputs = rnn((input_1, input_2))

model = keras.models.Model([input_1, input_2], outputs)

model.compile(optimizer="adam", loss="mse", metrics=["accuracy"])
 

Wytrenuj model z losowo wygenerowanymi danymi

Ponieważ nie ma dobrego zestawu danych kandydata dla tego modelu, do demonstracji używamy losowych danych Numpy.

 input_1_data = np.random.random((batch_size * num_batches, timestep, i1))
input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3))
target_1_data = np.random.random((batch_size * num_batches, unit_1))
target_2_data = np.random.random((batch_size * num_batches, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]

model.fit(input_data, target_data, batch_size=batch_size)
 
10/10 [==============================] - 0s 22ms/step - loss: 0.7499 - rnn_1_loss: 0.2790 - rnn_1_1_loss: 0.4709 - rnn_1_accuracy: 0.0844 - rnn_1_1_accuracy: 0.0336

<tensorflow.python.keras.callbacks.History at 0x7fc21e1d1208>

Z warstwą Keras keras.layers.RNN oczekuje się, że zdefiniujesz logikę matematyczną dla poszczególnych kroków w sekwencji, a warstwa keras.layers.RNN zajmie się iteracją sekwencji za Ciebie. To niezwykle skuteczny sposób na szybkie prototypowanie nowych rodzajów RNN (np. Wariantu LSTM).

Aby uzyskać więcej informacji, odwiedź dokumentację interfejsu API .