![]() | ![]() | ![]() | ![]() |
introduzione
Le reti neurali ricorrenti (RNN) sono una classe di reti neurali potente per la modellazione di dati di sequenza come serie temporali o linguaggio naturale.
Schematicamente, uno strato RNN utilizza for
ciclo per scorrere i Timesteps di una sequenza, mantenendo uno stato interno che codifica le informazioni sui Timesteps ha visto finora.
L'API Keras RNN è progettata con particolare attenzione a:
Facilità d'uso: il built-in
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
strati consentono di costruire rapidamente modelli ricorrenti, senza dover fare scelte di configurazione difficili.Facilità di personalizzazione: è anche possibile definire il proprio strato di cellule RNN (la parte interna del
for
ciclo) con comportamento personalizzato, e utilizzarlo con il genericokeras.layers.RNN
strato (ilfor
ciclo stesso). Ciò consente di prototipare rapidamente diverse idee di ricerca in modo flessibile con un codice minimo.
Impostare
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Layer RNN integrati: un semplice esempio
Ci sono tre livelli RNN integrati in Keras:
keras.layers.SimpleRNN
, un RNN completamente collegato in cui l'uscita dal passo temporale precedente deve essere alimentata al successivo passo temporale.keras.layers.GRU
, prima proposto in Cho et al. 2014 .keras.layers.LSTM
, prima proposto in Hochreiter & Schmidhuber, 1997 .
All'inizio del 2015, Keras ha avuto le prime implementazioni Python open source riutilizzabili di LSTM e GRU.
Ecco un semplice esempio di un Sequential
modello che processi sequenze di numeri interi, incorpora ciascun intero in un vettore 64-dimensionale, quindi elabora la sequenza di vettori usando un LSTM
strato.
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 _________________________________________________________________
Gli RNN integrati supportano una serie di utili funzioni:
- Dropout ricorrente, tramite i
dropout
erecurrent_dropout
argomenti - Capacità di elaborare una sequenza di immissione in retromarcia, con il
go_backwards
argomento - Srotolamento Loop (che può portare ad un grande aumento di velocità durante l'elaborazione di brevi sequenze di CPU), tramite il
unroll
argomento - ...e altro ancora.
Per ulteriori informazioni, consultare la documentazione API RNN .
Uscite e stati
Per impostazione predefinita, l'output di un livello RNN contiene un singolo vettore per campione. Questo vettore è l'output della cella RNN corrispondente all'ultimo timestep, contenente informazioni sull'intera sequenza di input. La forma di questa uscita è (batch_size, units)
dove units
corrisponde alla units
argomento passato al costruttore del livello.
Strato A RNN può anche restituire l'intera sequenza di uscite per ciascun campione (un vettore per ogni passo temporale per campione), se si imposta return_sequences=True
. La forma di questa uscita è (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 _________________________________________________________________
Inoltre, uno strato RNN può restituire i suoi stati interni finali. Gli stati restituiti possono essere utilizzati per riprendere l'esecuzione RNN seguito, o per inizializzare un altro RNN . Questa impostazione è comunemente utilizzata nel modello da sequenza a sequenza encoder-decodificatore, in cui lo stato finale dell'encoder viene utilizzato come stato iniziale del decoder.
Per configurare un livello RNN per tornare il suo stato interno, impostare il return_state
parametro su True
durante la creazione dello strato. Si noti che LSTM
ha 2 tensori statali, ma GRU
ha una sola.
Per configurare lo stato iniziale dello strato, basta chiamare il livello con ulteriore argomento chiave initial_state
. Nota che la forma dello stato deve corrispondere alla dimensione dell'unità del livello, come nell'esempio seguente.
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 __________________________________________________________________________________________________
Strati RNN e celle RNN
Oltre ai livelli RNN incorporati, l'API RNN fornisce anche API a livello di cella. A differenza dei livelli RNN, che elaborano interi batch di sequenze di input, la cella RNN elabora solo un singolo timestep.
La cella è l'interno del for
ciclo di uno strato RNN. Avvolgendo una cella all'interno di una keras.layers.RNN
strato fornisce uno strato in grado di elaborare lotti di sequenze, ad esempio RNN(LSTMCell(10))
.
Matematicamente, RNN(LSTMCell(10))
produce lo stesso risultato di LSTM(10)
. In effetti, l'implementazione di questo livello in TF v1.x stava semplicemente creando la cella RNN corrispondente e avvolgendola in un livello RNN. Tuttavia utilizzando il built-in GRU
e LSTM
strati consentire l'utilizzo di CuDNN e si può vedere la migliore prestazione.
Ci sono tre celle RNN integrate, ognuna delle quali corrisponde al livello RNN corrispondente.
keras.layers.SimpleRNNCell
corrisponde allaSimpleRNN
strato.keras.layers.GRUCell
corrisponde allaGRU
strato.keras.layers.LSTMCell
corrisponde allaLSTM
strato.
L'astrazione delle cellule, insieme con il generico keras.layers.RNN
di classe, lo rendono molto facile da implementare architetture RNN personalizzati per la vostra ricerca.
Stateness cross-batch
Durante l'elaborazione di sequenze molto lunghe (possibilmente infinita), si consiglia di utilizzare il modello di statefulness cross-batch.
Normalmente, lo stato interno di un layer RNN viene ripristinato ogni volta che vede un nuovo batch (cioè ogni campione visto dal layer è considerato indipendente dal passato). Il livello manterrà uno stato solo durante l'elaborazione di un determinato campione.
Tuttavia, se si dispone di sequenze molto lunghe, è utile suddividerle in sequenze più brevi e alimentare queste sequenze più brevi in sequenza in un livello RNN senza ripristinare lo stato del livello. In questo modo, il livello può conservare le informazioni sull'intera sequenza, anche se vede solo una sottosequenza alla volta.
È possibile farlo impostando stateful=True
nel costruttore.
Se si dispone di una sequenza s = [t0, t1, ... t1546, t1547]
, si sarebbe dividerlo in pe
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Quindi lo elaborerai tramite:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Quando si desidera cancellare lo stato, è possibile utilizzare layer.reset_states()
.
Ecco un esempio completo:
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()
Riutilizzo dello stato RNN
Gli stati registrati dello strato RNN non sono inclusi nelle layer.weights()
. Se si desidera riutilizzare lo stato da uno strato RNN, è possibile recuperare il valore Uniti da layer.states
e usarlo come lo stato iniziale per un nuovo livello tramite l'API funzionale Keras come new_layer(inputs, initial_state=layer.states)
, o la sottoclasse del modello.
Si noti inoltre che il modello sequenziale potrebbe non essere utilizzato in questo caso poiché supporta solo livelli con input e output singoli, l'input aggiuntivo dello stato iniziale ne rende impossibile l'uso qui.
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)
RNN bidirezionali
Per sequenze diverse dalle serie temporali (ad es. testo), accade spesso che un modello RNN possa funzionare meglio se non solo elabora la sequenza dall'inizio alla fine, ma anche all'indietro. Ad esempio, per prevedere la parola successiva in una frase, è spesso utile avere il contesto intorno alla parola, non solo le parole che la precedono.
Keras fornisce un'API facile per voi per costruire tali RNR bidirezionali: il keras.layers.Bidirectional
involucro.
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 _________________________________________________________________
Sotto il cofano, Bidirectional
copierà strato RNN passata, e capovolgere il go_backwards
campo dello strato appena copiato, in modo che possa elaborare gli ingressi in ordine inverso.
L'uscita del Bidirectional
RNN sarà, per default, la concatenazione dell'uscita strato in avanti e l'uscita strato all'indietro. Se avete bisogno di un comportamento fusione diverso, ad esempio la concatenazione, modificare il merge_mode
parametro nella Bidirectional
costruttore di involucro. Per maggiori dettagli su Bidirectional
, si prega di consultare la documentazione API .
Ottimizzazione delle prestazioni e kernel CuDNN
In TensorFlow 2.0, i livelli LSTM e GRU integrati sono stati aggiornati per sfruttare i kernel CuDNN per impostazione predefinita quando è disponibile una GPU. Con questa modifica, i precedenti keras.layers.CuDNNLSTM/CuDNNGRU
strati sono stati deprecati, e si può costruire il modello senza preoccuparsi dell'hardware che verrà eseguito su.
Dal momento che il kernel CuDNN è costruito con determinati presupposti, questo significa che il livello non sarà in grado di utilizzare il kernel CuDNN se si modificano le impostazioni predefinite degli strati LSTM o GRU incorporati. Per esempio:
- Modifica della
activation
funzione daltanh
a qualcos'altro. - Modifica della
recurrent_activation
funzione dalsigmoid
a qualcos'altro. - Utilizzando
recurrent_dropout
> 0. - Impostazione
unroll
a True, che le forze LSTM / GRU per decomporre l'internotf.while_loop
in un srotolatofor
ciclo. - Impostazione
use_bias
False. - Utilizzo della mascheratura quando i dati di input non sono rigorosamente riempiti a destra (se la maschera corrisponde a dati rigorosamente riempiti a destra, CuDNN può ancora essere utilizzato. Questo è il caso più comune).
Per l'elenco dettagliato dei vincoli, si prega di consultare la documentazione per le LSTM e GRU strati.
Utilizzo dei kernel CuDNN quando disponibili
Costruiamo un semplice modello LSTM per dimostrare la differenza di prestazioni.
Utilizzeremo come sequenze di input la sequenza di righe di cifre MNIST (trattando ogni riga di pixel come un timestep) e prevederemo l'etichetta della cifra.
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
Carichiamo il set di dati 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]
Creiamo un'istanza del modello e la addestriamo.
Abbiamo scelto sparse_categorical_crossentropy
come la funzione di perdita per il modello. L'uscita del modello ha forma di [batch_size, 10]
. L'obiettivo per il modello è un vettore intero, ciascuno dei numeri interi è compreso tra 0 e 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 [==============================] - 6s 5ms/step - loss: 0.9510 - accuracy: 0.7029 - val_loss: 0.5633 - val_accuracy: 0.8209 <keras.callbacks.History at 0x7fc9942efad0>
Ora, confrontiamo con un modello che non utilizza il kernel 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 [==============================] - 34s 35ms/step - loss: 0.3894 - accuracy: 0.8846 - val_loss: 0.5677 - val_accuracy: 0.8045 <keras.callbacks.History at 0x7fc945fa2650>
Quando è in esecuzione su una macchina con una GPU NVIDIA e CuDNN installati, il modello creato con CuDNN è molto più veloce da addestrare rispetto al modello che utilizza il normale kernel TensorFlow.
Lo stesso modello abilitato per CuDNN può essere utilizzato anche per eseguire l'inferenza in un ambiente solo CPU. Il tf.device
annotazione di seguito è solo forzando il posizionamento del dispositivo. Il modello verrà eseguito su CPU per impostazione predefinita se non è disponibile alcuna GPU.
Semplicemente non devi più preoccuparti dell'hardware su cui stai utilizzando. Non è abbastanza bello?
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
RNN con input list/dict o input nidificati
Le strutture nidificate consentono agli implementatori di includere più informazioni in un unico passaggio temporale. Ad esempio, un fotogramma video potrebbe avere input audio e video contemporaneamente. La forma dei dati in questo caso potrebbe essere:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
In un altro esempio, i dati sulla scrittura a mano potrebbero avere sia le coordinate xey per la posizione corrente della penna, sia le informazioni sulla pressione. Quindi la rappresentazione dei dati potrebbe essere:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
Il codice seguente fornisce un esempio di come creare una cella RNN personalizzata che accetti tali input strutturati.
Definire una cella personalizzata che supporti l'input/output nidificato
Vedere Conoscere nuovi Livelli e Costruzioni via sottoclassi per i dettagli su come scrivere i propri livelli.
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}
Crea un modello RNN con input/output nidificato
Costruzione di un modello Let Keras che utilizza un keras.layers.RNN
livello e la cella personalizzato che abbiamo appena definito.
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"])
Addestrare il modello con dati generati casualmente
Poiché non esiste un buon set di dati candidato per questo modello, utilizziamo dati Numpy casuali per la dimostrazione.
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 [==============================] - 1s 26ms/step - loss: 0.7316 - rnn_1_loss: 0.2590 - rnn_1_1_loss: 0.4725 - rnn_1_accuracy: 0.1016 - rnn_1_1_accuracy: 0.0328 <keras.callbacks.History at 0x7fc5686e6f50>
Con il Keras keras.layers.RNN
livello, ci si aspetta solo di definire la logica matematica per singola fase all'interno della sequenza, e la keras.layers.RNN
strato gestirà l'iterazione sequenza per voi. È un modo incredibilmente potente per prototipare rapidamente nuovi tipi di RNN (ad esempio una variante LSTM).
Per ulteriori informazioni, si prega di visitare i documentazione API .