![]() | ![]() | ![]() | ![]() |
Wstęp
Rekurencyjne sieci neuronowe (RNN) to klasa sieci neuronowych, która doskonale nadaje się do modelowania danych sekwencyjnych, takich jak szeregi czasowe lub język naturalny.
Schematycznie warstwa RNN wykorzystuje for
pętli do iteracyjnego timesteps sekwencji, przy zachowaniu stanu wewnętrznego, który koduje informacje o timesteps to ma jak do tej pory.
Keras RNN API został zaprojektowany z naciskiem na:
Łatwość użytkowania: wbudowany
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
warstw pozwalają szybko budować modele nawracające bez konieczności dokonywania trudnych wyborów konfiguracyjnych.Łatwość dostosowywania: Można również zdefiniować własną warstwę komórek RNN (wewnętrzną częścią
for
pętli) z niestandardowego zachowania i używać go z ogólnymkeras.layers.RNN
warstwy (wfor
pętli samego). Pozwala to na szybkie prototypowanie różnych pomysłów badawczych w elastyczny sposób przy minimalnym kodzie.
Ustawiać
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Wbudowane warstwy RNN: prosty przykład
Keras ma trzy wbudowane warstwy RNN:
keras.layers.SimpleRNN
, w pełni połączone RNN gdzie wyjście z poprzednim kroku to ma być podawany do następnego kroku to.keras.layers.GRU
, po raz pierwszy zaproponowana w Cho i wsp., 2014 r .keras.layers.LSTM
, po raz pierwszy zaproponowana w Hochreiter & Schmidhuber, 1997 .
Na początku 2015 r. Keras miał pierwsze wielokrotnego użytku implementacje open-source Pythona LSTM i GRU.
Oto przykład proste z Sequential
modelu procesów sekwencje liczb całkowitych, osadza każda liczba do 64-wymiarowego wektora, a następnie przetwarza ciąg wektorów stosując LSTM
warstwy.
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:
- Nawracające przerywania, za pośrednictwem
dropout
irecurrent_dropout
argumentów - Możliwość przetwarzania sekwencji wprowadzania w odwrotnym kierunku, przez
go_backwards
argumentu - Odwijanie pętli (co może prowadzić do dużego SpeedUp podczas obróbki krótkich sekwencji w CPU), za pośrednictwem
unroll
argumentu - ...i więcej.
Aby uzyskać więcej informacji, zobacz dokumentację API RNN .
Wyjścia i stany
Domyślnie wynik warstwy RNN zawiera jeden wektor na próbkę. Ten wektor to wyjście komórki RNN odpowiadające ostatniemu krokowi czasowemu, zawierające informacje o całej sekwencji wejściowej. Kształt tego wyjścia jest (batch_size, units)
, gdzie units
odpowiada do units
argumentu przekazywane do konstruktora warstwy.
Warstwa A RNN może powrócić całą sekwencję wyjściowych dla każdej próbki (jeden wektor według kroku to w próbce), jeśli zestaw return_sequences=True
. Kształt tego wyjścia jest (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 zwrócić swój końcowy stan wewnętrzny. Zwracany stany mogą być wykorzystane, aby wznowić wykonywanie RNN później, lub zainicjować inną RNN . To ustawienie jest powszechnie używane w modelu sekwencja-sekwencja koder-dekoder, w którym stan końcowy kodera jest używany jako stan początkowy dekodera.
Aby skonfigurować warstwę RNN powrotu swój stan wewnętrzny, ustaw return_state
parametru na True
podczas tworzenia warstwy. Zauważ, że LSTM
ma 2 tensory państwowe, ale GRU
ma tylko jeden.
Aby skonfigurować początkowy stan warstwy, po prostu zadzwoń z dodatkową warstwę kluczowego argumentu initial_state
. Zwróć uwagę, że kształt stanu musi odpowiadać rozmiarowi 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 czasowy.
Ogniwo jest wnętrze for
pętli warstwy RNN. Owijania komórki wewnątrz keras.layers.RNN
warstwy daje się warstwę zdolnego do przetwarzania porcji 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 na utworzeniu odpowiedniej komórki RNN i owinięciu jej w warstwę RNN. Jednak za pomocą wbudowanego w GRU
i LSTM
warstw umożliwiają zastosowanie CuDNN i można zobaczyć lepszą wydajność.
Istnieją trzy wbudowane komórki RNN, z których każda odpowiada pasującej warstwie RNN.
keras.layers.SimpleRNNCell
odpowiadaSimpleRNN
warstwy.keras.layers.GRUCell
odpowiadaGRU
warstwy.keras.layers.LSTMCell
odpowiadaLSTM
warstwy.
Abstrakcja komórkowej, wraz z ogólnym keras.layers.RNN
klasy sprawiają, że bardzo łatwo wdrożyć niestandardowe RNN architektur do badań.
Stanowość między partiami
Podczas przetwarzania bardzo długich sekwencji (prawdopodobnie nieskończone), można użyć wzoru cross-wsadowym statefulness.
Zwykle stan wewnętrzny warstwy RNN jest resetowany za każdym razem, gdy widzi nową partię (tzn. 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 podawać sekwencyjnie te krótsze sekwencje do warstwy RNN bez resetowania stanu warstwy. W ten sposób warstwa może zachować informacje o całej sekwencji, nawet jeśli widzi tylko jedną podsekwencję na raz.
Można to zrobić poprzez ustawienie stateful=True
w konstruktorze.
Jeśli masz sekwencji s = [t0, t1, ... t1546, t1547]
, byś podzielić ją na przykład
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Następnie przetworzysz to za pomocą:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Gdy chcesz, aby usunąć stan, można 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
Nagrane stany warstwy RNN nie są wliczone w layer.weights()
. Jeśli chcesz ponownie użyć stan z warstwą RNN można pobrać wartość Zjednoczonych przez layer.states
i używać go jako stan początkowy dla nowej warstwy za pośrednictwem interfejsu API funkcjonalnej Keras jak new_layer(inputs, initial_state=layer.states)
lub podklasy modelu.
Należy również pamiętać, że w tym przypadku model sekwencyjny może nie być używany, ponieważ obsługuje tylko warstwy z jednym wejściem i wyjściem, dodatkowe wejście stanu początkowego uniemożliwia użycie w tym przypadku.
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 zdarza się, ż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 przydaje się kontekst wokół tego słowa, a nie tylko słowa poprzedzające je.
Keras zapewnia łatwy API do budowania takich dwukierunkowych RNNs: the keras.layers.Bidirectional
opakowanie.
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 warstwę RNN przekazany w, i przerzucenie go_backwards
pole nowo skopiowanej warstwie, tak, że będzie przetwarzać wejść w odwrotnej kolejności.
Wyjście Bidirectional
RNN będzie domyślnie, konkatenacji wyjścia warstwy do przodu i do tyłu na wyjściu warstwy. Jeśli potrzebujesz innego zachowania łączenie, np konkatenacji, zmień merge_mode
parametr w Bidirectional
konstruktora otoki. Więcej informacji na temat Bidirectional
, należy sprawdzić w dokumentacji 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 wcześniejsze keras.layers.CuDNNLSTM/CuDNNGRU
warstwy były przestarzałe, a można zbudować swój model, nie martwiąc się o sprzęcie będzie działać dalej.
Ponieważ jądro CuDNN zbudowany jest z pewnych założeniach, oznacza to, że warstwa nie będzie w stanie wykorzystać jądro CuDNN po zmianie domyślnych wbudowanych LSTM lub GRU warstwach. Np:
- Zmiana
activation
funkcji ztanh
do czegoś innego. - Zmiana
recurrent_activation
funkcji zsigmoid
do czegoś innego. - Korzystanie
recurrent_dropout
> 0. - Ustawianie
unroll
True, której siły LSTM / GRU rozłożenia wewnętrznątf.while_loop
w produkt rozwiniętyfor
pętli. - Ustawianie
use_bias
False. - Korzystanie z maskowania, gdy dane wejściowe nie są wypełnione ściśle prawostronnie (jeśli maska odpowiada danym wypełnionym ściśle prawostronnie, CuDNN nadal może być używany. Jest to najczęstszy przypadek).
Dla szczegółowego wykazu ograniczeń, należy zapoznać się z dokumentacją dla LSTM i GRU warstwach.
Używanie jąder CuDNN, jeśli są dostępne
Zbudujmy prosty model LSTM, aby zademonstrować różnicę w wydajności.
Użyjemy jako sekwencji wejściowych sekwencji rzędów cyfr MNIST (traktując każdy rząd pikseli jako krok czasowy) i przewidzimy 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 wyszkolmy ją.
Wybieramy sparse_categorical_crossentropy
jako funkcji straty dla modelu. Wyjście modelu ma kształt [batch_size, 10]
. Celem modelu jest wektor całkowity, każda z 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 [==============================] - 6s 5ms/step - loss: 0.9510 - accuracy: 0.7029 - val_loss: 0.5633 - val_accuracy: 0.8209 <keras.callbacks.History at 0x7fc9942efad0>
Porównajmy teraz do modelu, 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 [==============================] - 34s 35ms/step - loss: 0.3894 - accuracy: 0.8846 - val_loss: 0.5677 - val_accuracy: 0.8045 <keras.callbacks.History at 0x7fc945fa2650>
Podczas pracy na maszynie z zainstalowanym procesorem graficznym NVIDIA i CuDNN model zbudowany za pomocą CuDNN jest znacznie szybszy do trenowania w porównaniu z modelem, który używa zwykłego jądra TensorFlow.
Ten sam model obsługujący CuDNN może być również używany do uruchamiania wnioskowania w środowisku zawierającym tylko procesor. tf.device
adnotacji poniżej jest po prostu zmusza umieszczenie urządzenia. Model będzie domyślnie działał na procesorze, jeśli nie jest dostępny procesor graficzny.
Po prostu nie musisz się już martwić o sprzęt, na którym pracujesz. Czy to nie jest 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
RNN z wejściami list/dict lub wejściami zagnieżdżonymi
Struktury zagnieżdżone umożliwiają realizatorom zawarcie większej ilości informacji w jednym kroku czasowym. Na przykład ramka wideo może mieć jednocześnie wejście audio i wideo. Kształtem danych w tym przypadku może być:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
W innym przykładzie dane pisma ręcznego mogą mieć współrzędne x i y dla bieżącej pozycji pióra, a także informacje o nacisku. Tak więc reprezentacja danych może wyglądać następująco:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
Poniższy kod przedstawia przykład, jak zbudować niestandardową komórkę RNN, która akceptuje takie ustrukturyzowane dane wejściowe.
Zdefiniuj niestandardową komórkę, która obsługuje zagnieżdżone wejście/wyjście
Patrz Nawiązywanie nowych warstw i modele poprzez instacji 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 danymi wejściowymi/wyjściowymi
Zbudować model Keras który wykorzystuje Chodźmy keras.layers.RNN
warstwę i komórkę niestandardową właśnie zdefiniowana.
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"])
Trenuj model z losowo generowanymi danymi
Ponieważ nie ma dobrego kandydującego zestawu danych 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 [==============================] - 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>
Z Keras keras.layers.RNN
warstwy, oczekuje się tylko do określenia logiki matematycznej dla pojedynczego kroku w sekwencji, a keras.layers.RNN
warstwa będzie obsługiwać sekwencji iteracji dla Ciebie. Jest to niesamowicie potężny sposób na szybkie prototypowanie nowych rodzajów sieci RNN (np. wariantu LSTM).
Aby uzyskać więcej informacji, prosimy odwiedzić docs API .