![]() | ![]() | ![]() | ![]() |
pengantar
Jaringan saraf berulang (RNN) adalah kelas jaringan saraf yang kuat untuk memodelkan data urutan seperti deret waktu atau bahasa alami.
Skematis, lapisan RNN menggunakan for
loop untuk iterate atas timesteps berurutan, sambil mempertahankan keadaan internal yang mengkodekan informasi tentang timesteps telah melihat begitu jauh.
Keras RNN API dirancang dengan fokus pada:
Kemudahan penggunaan: built-in
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
lapisan memungkinkan Anda untuk cepat membangun model berulang tanpa harus membuat pilihan konfigurasi yang sulit.Kemudahan kustomisasi: Anda juga dapat menentukan lapisan Anda sendiri sel RNN (bagian dalam dari
for
loop) dengan perilaku kustom, dan menggunakannya dengan generikkeras.layers.RNN
lapisan (yangfor
lingkaran itu sendiri). Ini memungkinkan Anda dengan cepat membuat prototipe berbagai ide penelitian dengan cara yang fleksibel dengan kode minimal.
Mempersiapkan
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Lapisan RNN bawaan: contoh sederhana
Ada tiga lapisan RNN bawaan di Keras:
keras.layers.SimpleRNN
, sebuah RNN sepenuhnya terhubung di mana output dari timestep sebelumnya untuk diberi makan untuk timestep berikutnya.keras.layers.GRU
, pertama kali diusulkan pada Cho et al., 2014 .keras.layers.LSTM
, pertama kali diusulkan pada Hochreiter & Schmidhuber 1997 .
Pada awal 2015, Keras memiliki implementasi Python open-source pertama yang dapat digunakan kembali dari LSTM dan GRU.
Berikut ini adalah contoh sederhana dari Sequential
model yang proses urutan bilangan bulat, sematan setiap bilangan bulat menjadi vektor 64-dimensi, kemudian memproses urutan vektor menggunakan LSTM
lapisan.
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 _________________________________________________________________
RNN bawaan mendukung sejumlah fitur berguna:
- Jebolan berulang, melalui
dropout
danrecurrent_dropout
argumen - Kemampuan untuk memproses urutan input secara terbalik, melalui
go_backwards
argumen - Lingkaran membuka gulungan (yang dapat menyebabkan percepatan besar saat memproses urutan pendek pada CPU), melalui
unroll
argumen - ...dan banyak lagi.
Untuk informasi lebih lanjut, lihat RNN dokumentasi API .
Keluaran dan status
Secara default, output dari lapisan RNN berisi satu vektor per sampel. Vektor ini adalah keluaran sel RNN yang sesuai dengan langkah waktu terakhir, yang berisi informasi tentang seluruh urutan masukan. Bentuk output ini (batch_size, units)
di mana units
berkorespondensi dengan units
argumen dilewatkan ke konstruktor layer.
Lapisan A RNN juga dapat mengembalikan seluruh urutan output untuk setiap sampel (satu vektor per timestep per sampel), jika Anda menetapkan return_sequences=True
. Bentuk output ini (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 _________________________________________________________________
Selain itu, lapisan RNN dapat mengembalikan status internal terakhirnya. Negara itu kembali dapat digunakan untuk melanjutkan eksekusi RNN kemudian, atau untuk menginisialisasi RNN lain . Pengaturan ini umumnya digunakan dalam model urutan-ke-urutan encoder-decoder, di mana keadaan akhir pembuat enkode digunakan sebagai keadaan awal dekoder.
Untuk mengkonfigurasi lapisan RNN untuk kembali keadaan internal, mengatur return_state
parameter untuk True
saat membuat layer. Perhatikan bahwa LSTM
memiliki 2 tensor negara, tetapi GRU
hanya memiliki satu.
Untuk mengkonfigurasi keadaan awal dari lapisan, sebut layer dengan tambahan argumen kata kunci initial_state
. Perhatikan bahwa bentuk keadaan harus sesuai dengan ukuran unit lapisan, seperti pada contoh di bawah ini.
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 __________________________________________________________________________________________________
Lapisan RNN dan sel RNN
Selain lapisan RNN bawaan, RNN API juga menyediakan API tingkat sel. Tidak seperti lapisan RNN, yang memproses seluruh kumpulan urutan input, sel RNN hanya memproses satu langkah waktu.
Sel adalah bagian dalam for
loop lapisan RNN. Membungkus sel di dalam keras.layers.RNN
lapisan memberikan lapisan mampu memproses batch urutan, misalnya RNN(LSTMCell(10))
.
Secara matematis, RNN(LSTMCell(10))
menghasilkan hasil yang sama seperti LSTM(10)
. Faktanya, implementasi lapisan ini di TF v1.x hanya membuat sel RNN yang sesuai dan membungkusnya dalam lapisan RNN. Namun menggunakan built-in GRU
dan LSTM
lapisan memungkinkan penggunaan CuDNN dan Anda mungkin melihat kinerja yang lebih baik.
Ada tiga sel RNN bawaan, masing-masing sesuai dengan lapisan RNN yang cocok.
keras.layers.SimpleRNNCell
sesuai denganSimpleRNN
lapisan.keras.layers.GRUCell
sesuai denganGRU
lapisan.keras.layers.LSTMCell
sesuai denganLSTM
lapisan.
Abstraksi sel, bersama-sama dengan generik keras.layers.RNN
kelas, membuatnya sangat mudah untuk menerapkan kustom RNN arsitektur untuk penelitian Anda.
Statefulness lintas-batch
Saat memproses urutan yang sangat panjang (mungkin tak terbatas), Anda mungkin ingin menggunakan pola statefulness lintas batch.
Biasanya, keadaan internal lapisan RNN diatur ulang setiap kali melihat kumpulan baru (yaitu setiap sampel yang dilihat oleh lapisan dianggap tidak tergantung pada masa lalu). Lapisan hanya akan mempertahankan status saat memproses sampel yang diberikan.
Jika Anda memiliki urutan yang sangat panjang, akan berguna untuk memecahnya menjadi urutan yang lebih pendek, dan memasukkan urutan yang lebih pendek ini secara berurutan ke dalam lapisan RNN tanpa mengatur ulang status lapisan. Dengan begitu, layer dapat menyimpan informasi tentang keseluruhan urutan, meskipun hanya melihat satu sub-urutan dalam satu waktu.
Anda dapat melakukan ini dengan menetapkan stateful=True
dalam constructor.
Jika Anda memiliki urutan s = [t0, t1, ... t1546, t1547]
, Anda akan membaginya menjadi misalnya
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Kemudian Anda akan memprosesnya melalui:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Bila Anda ingin menghapus negara, Anda dapat menggunakan layer.reset_states()
.
Berikut adalah contoh lengkapnya:
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()
Penggunaan Kembali Negara Bagian RNN
Negara tercatat dari lapisan RNN tidak termasuk dalam layer.weights()
. Jika Anda ingin menggunakan kembali negara dari lapisan RNN, Anda dapat mengambil nilai-negara dengan layer.states
dan menggunakannya sebagai keadaan awal untuk layer baru melalui Keras API fungsional seperti new_layer(inputs, initial_state=layer.states)
, atau subkelas model.
Harap perhatikan juga bahwa model sekuensial mungkin tidak digunakan dalam kasus ini karena hanya mendukung lapisan dengan input dan output tunggal, input tambahan dari status awal tidak memungkinkan untuk digunakan di sini.
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 dua arah
Untuk urutan selain deret waktu (misalnya teks), sering kali model RNN dapat bekerja lebih baik jika tidak hanya memproses urutan dari awal hingga akhir, tetapi juga mundur. Misalnya, untuk memprediksi kata berikutnya dalam sebuah kalimat, seringkali berguna untuk memiliki konteks di sekitar kata tersebut, bukan hanya kata-kata yang mendahuluinya.
Keras menyediakan API mudah bagi Anda untuk membangun RNNs dua arah seperti: yang 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 _________________________________________________________________
Di bawah tenda, Bidirectional
akan menyalin lapisan RNN berlalu dalam, dan membalik go_backwards
bidang lapisan baru disalin, sehingga akan memproses masukan dalam urutan terbalik.
Output dari Bidirectional
RNN akan, secara default, gabungan dari output lapisan depan dan output lapisan mundur. Jika Anda membutuhkan perilaku penggabungan yang berbeda, misalnya Rangkaian, mengubah merge_mode
parameter dalam Bidirectional
pembungkus konstruktor. Untuk rincian lebih lanjut tentang Bidirectional
, silakan cek dokumentasi API .
Optimalisasi kinerja dan kernel CuDNN
Di TensorFlow 2.0, lapisan LSTM dan GRU bawaan telah diperbarui untuk memanfaatkan kernel CuDNN secara default saat GPU tersedia. Dengan perubahan ini, sebelum keras.layers.CuDNNLSTM/CuDNNGRU
lapisan telah usang, dan Anda dapat membangun model Anda tanpa khawatir tentang hardware akan berjalan pada.
Karena kernel CuDNN dibangun dengan asumsi-asumsi tertentu, ini berarti lapisan tidak akan dapat menggunakan kernel CuDNN jika Anda mengubah default dari LSTM atau GRU lapisan built-in. Misalnya:
- Mengubah
activation
fungsi daritanh
ke sesuatu yang lain. - Mengubah
recurrent_activation
fungsi darisigmoid
ke sesuatu yang lain. - Menggunakan
recurrent_dropout
> 0. - Mengatur
unroll
untuk Benar, yang pasukan LSTM / GRU membusuk batintf.while_loop
menjadi membuka gulunganfor
lingkaran. - Pengaturan
use_bias
ke False. - Menggunakan masking ketika data input tidak sepenuhnya diisi dengan benar (jika topeng sesuai dengan data yang diisi dengan benar, CuDNN masih dapat digunakan. Ini adalah kasus yang paling umum).
Untuk daftar rinci kendala, silakan lihat dokumentasi untuk LSTM dan GRU lapisan.
Menggunakan kernel CuDNN jika tersedia
Mari kita buat model LSTM sederhana untuk menunjukkan perbedaan kinerja.
Kami akan menggunakan urutan input urutan baris digit MNIST (memperlakukan setiap baris piksel sebagai langkah waktu), dan kami akan memprediksi label digit.
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
Mari kita muat dataset 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]
Mari buat instance model dan latih.
Kami memilih sparse_categorical_crossentropy
sebagai fungsi kerugian untuk model. Output dari model memiliki bentuk [batch_size, 10]
. Target untuk model adalah vektor bilangan bulat, masing-masing bilangan bulat berada dalam kisaran 0 hingga 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>
Sekarang, mari kita bandingkan dengan model yang tidak menggunakan 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>
Saat dijalankan pada mesin dengan GPU NVIDIA dan CuDNN terpasang, model yang dibuat dengan CuDNN jauh lebih cepat untuk dilatih dibandingkan dengan model yang menggunakan kernel TensorFlow biasa.
Model berkemampuan CuDNN yang sama juga dapat digunakan untuk menjalankan inferensi di lingkungan khusus CPU. The tf.device
penjelasan di bawah ini hanya memaksa penempatan perangkat. Model akan berjalan pada CPU secara default jika tidak ada GPU yang tersedia.
Anda tidak perlu khawatir tentang perangkat keras yang Anda gunakan lagi. Bukankah itu cukup keren?
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 dengan input daftar/dict, atau input bersarang
Struktur bersarang memungkinkan pelaksana untuk memasukkan lebih banyak informasi dalam satu langkah waktu. Misalnya, bingkai video dapat memiliki input audio dan video secara bersamaan. Bentuk data dalam hal ini dapat berupa:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
Dalam contoh lain, data tulisan tangan dapat memiliki koordinat x dan y untuk posisi pena saat ini, serta informasi tekanan. Jadi representasi datanya bisa:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
Kode berikut memberikan contoh cara membuat sel RNN kustom yang menerima input terstruktur tersebut.
Tentukan sel khusus yang mendukung input/output bersarang
Lihat Membuat Lapisan baru & Model melalui subclassing untuk rincian tentang menulis lapisan Anda sendiri.
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}
Bangun model RNN dengan input/output bersarang
Mari membangun model Keras yang menggunakan keras.layers.RNN
lapisan dan sel kustom kami hanya didefinisikan.
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"])
Latih model dengan data yang dihasilkan secara acak
Karena tidak ada kumpulan data kandidat yang baik untuk model ini, kami menggunakan data Numpy acak untuk demonstrasi.
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>
Dengan Keras keras.layers.RNN
lapisan, Anda hanya diharapkan untuk mendefinisikan logika matematika untuk langkah individu dalam urutan, dan keras.layers.RNN
lapisan akan menangani urutan iterasi untuk Anda. Ini adalah cara yang sangat ampuh untuk membuat prototipe jenis RNN baru dengan cepat (misalnya varian LSTM).
Untuk lebih jelasnya, silahkan kunjungi dokumentasi API .