Jaringan Saraf Berulang (RNN) dengan Keras

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

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 generik keras.layers.RNN lapisan (yang for 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:

  1. keras.layers.SimpleRNN , sebuah RNN sepenuhnya terhubung di mana output dari timestep sebelumnya untuk diberi makan untuk timestep berikutnya.

  2. keras.layers.GRU , pertama kali diusulkan pada Cho et al., 2014 .

  3. 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 dan recurrent_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.

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 dari tanh ke sesuatu yang lain.
  • Mengubah recurrent_activation fungsi dari sigmoid ke sesuatu yang lain.
  • Menggunakan recurrent_dropout > 0.
  • Mengatur unroll untuk Benar, yang pasukan LSTM / GRU membusuk batin tf.while_loop menjadi membuka gulungan for 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

png

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 .