Halaman ini diterjemahkan oleh Cloud Translation API.
Switch to English

Jaringan Neural Berulang (RNN) dengan Keras

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.

Secara skematis, lapisan RNN menggunakan loop for untuk mengulangi langkah waktu suatu urutan, sambil mempertahankan status internal yang mengkodekan informasi tentang langkah waktu yang telah dilihatnya sejauh ini.

API Keras RNN dirancang dengan fokus pada:

  • Kemudahan penggunaan : lapisan keras.layers.RNN , keras.layers.LSTM , keras.layers.GRU memungkinkan Anda membuat model berulang dengan cepat tanpa harus membuat pilihan konfigurasi yang sulit.

  • Kemudahan penyesuaian : Anda juga dapat menentukan lapisan sel RNN Anda sendiri (bagian dalam perulangan for ) dengan perilaku khusus, dan menggunakannya dengan lapisan keras.layers.RNN generik (perulangan for itu sendiri). Ini memungkinkan Anda dengan cepat membuat prototipe ide penelitian yang berbeda dengan cara yang fleksibel dengan kode minimal.

Mendirikan

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 , RNN yang sepenuhnya terhubung di mana output dari langkah waktu sebelumnya akan diumpankan ke langkah waktu berikutnya.

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

  3. keras.layers.LSTM , pertama kali diusulkan di Hochreiter & Schmidhuber, 1997 .

Pada awal 2015, Keras memiliki implementasi LSTM dan GRU open-source pertama yang dapat digunakan kembali.

Berikut adalah contoh sederhana model Sequential yang memproses urutan bilangan bulat, menyematkan setiap bilangan bulat ke dalam vektor 64 dimensi, kemudian memproses urutan vektor menggunakan lapisan 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
_________________________________________________________________

RNN bawaan mendukung sejumlah fitur berguna:

  • Dropout berulang, melalui argumen dropout dan recurrent_dropout
  • Kemampuan untuk memproses urutan masukan secara terbalik, melalui argumen go_backwards
  • Loop unrolling (yang dapat menyebabkan percepatan besar saat memproses urutan pendek pada CPU), melalui argumen unroll
  • ... dan banyak lagi.

Untuk informasi lebih lanjut, lihat dokumentasi RNN 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 dari keluaran ini adalah (batch_size, units) dimana units sesuai dengan argumen units diteruskan ke konstruktor layer.

Lapisan RNN juga dapat mengembalikan seluruh urutan keluaran untuk setiap sampel (satu vektor per langkah waktu per sampel), jika Anda menyetel return_sequences=True . Bentuk output ini adalah (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 akhirnya. Status yang dikembalikan dapat digunakan untuk melanjutkan eksekusi RNN nanti, atau untuk menginisialisasi RNN lain . Setelan ini biasanya digunakan dalam model urutan-ke-urutan encoder-decoder, di mana status akhir encoder digunakan sebagai status awal decoder.

Untuk mengkonfigurasi lapisan RNN untuk mengembalikan keadaan internalnya, setel parameter return_state ke True saat membuat lapisan. Perhatikan bahwa LSTM memiliki 2 tensor status, tetapi GRU hanya memiliki satu.

Untuk mengkonfigurasi status awal layer, panggil saja layer dengan argumen kata kunci tambahan initial_state . Perhatikan bahwa bentuk status harus sesuai dengan ukuran satuan 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: "functional_1"
__________________________________________________________________________________________________
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 batch urutan input, sel RNN hanya memproses satu langkah waktu.

Sel adalah bagian dalam loop for dari lapisan RNN. Membungkus sel di dalam lapisan keras.layers.RNN memberi Anda lapisan yang mampu memproses sekumpulan urutan, misalnya RNN(LSTMCell(10)) .

Secara matematis, RNN(LSTMCell(10)) menghasilkan hasil yang sama dengan LSTM(10) . Faktanya, implementasi lapisan ini di TF v1.x hanya membuat sel RNN yang sesuai dan membungkusnya dalam lapisan RNN. Namun, penggunaan lapisan GRU dan LSTM 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 dengan kelas keras.layers.RNN generik, membuatnya sangat mudah untuk mengimplementasikan arsitektur RNN kustom 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 diasumsikan tidak bergantung 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 untuk memasukkan urutan yang lebih pendek ini secara berurutan ke dalam lapisan RNN tanpa menyetel ulang status lapisan. Dengan begitu, lapisan dapat menyimpan informasi tentang keseluruhan urutan, meskipun hanya melihat satu sub-urutan dalam satu waktu.

Anda dapat melakukan ini dengan menyetel stateful=True di konstruktor.

Jika Anda memiliki barisan 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)

Saat Anda ingin menghapus status, Anda dapat menggunakan layer.reset_states() .

Berikut 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 Status RNN

layer.weights() terekam dari lapisan RNN tidak termasuk dalam layer.weights() . Jika Anda ingin menggunakan kembali status dari lapisan RNN, Anda dapat mengambil nilai status dengan layer.states dan menggunakannya sebagai status awal untuk lapisan baru melalui API fungsional Keras seperti new_layer(inputs, initial_state=layer.states) , atau model subclassing.

Harap perhatikan juga bahwa model sekuensial mungkin tidak digunakan dalam kasus ini karena hanya mendukung lapisan dengan masukan dan keluaran tunggal, masukan tambahan dari status awal membuatnya tidak mungkin 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), seringkali 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, sering kali berguna untuk memiliki konteks di sekitar kata, tidak hanya kata-kata yang muncul sebelumnya.

Keras menyediakan API yang mudah bagi Anda untuk membangun RNN dua arah seperti: pembungkus keras.layers.Bidirectional .

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 yang diteruskan, dan membalik bidang go_backwards dari lapisan yang baru disalin, sehingga itu akan memproses masukan dalam urutan terbalik.

Output dari RNN Bidirectional akan, secara default, jumlah output lapisan maju dan output lapisan belakang. Jika Anda memerlukan perilaku penggabungan yang berbeda, misalnya penggabungan, ubah parameter merge_mode di konstruktor pembungkus Bidirectional . Untuk detail lebih lanjut tentang Bidirectional , silakan periksa dokumen API .

Optimalisasi kinerja dan kernel CuDNN

Di TensorFlow 2.0, lapisan LSTM dan GRU bawaan telah diupdate untuk memanfaatkan kernel CuDNN secara default saat GPU tersedia. Dengan perubahan ini, lapisan keras.layers.CuDNNLSTM/CuDNNGRU sebelumnya tidak lagi digunakan, dan Anda dapat membangun model tanpa mengkhawatirkan perangkat keras yang akan menjalankannya.

Karena kernel CuDNN dibuat dengan asumsi tertentu, ini berarti lapisan tidak akan dapat menggunakan kernel CuDNN jika Anda mengubah default lapisan LSTM atau GRU bawaan . Misalnya:

  • Mengubah fungsi activation dari tanh ke yang lain.
  • Mengubah fungsi recurrent_activation dari sigmoid ke yang lain.
  • Menggunakan recurrent_dropout > 0.
  • Pengaturan unroll ke True, yang memaksa LSTM / GRU untuk mendekomposisi inner tf.while_loop menjadi loop for digulung.
  • Menyetel use_bias ke False.
  • Menggunakan masking ketika data input tidak diisi dengan benar (jika mask sesuai dengan data yang diisi dengan benar, CuDNN masih dapat digunakan. Ini adalah kasus yang paling umum).

Untuk daftar detail batasan, silakan lihat dokumentasi untuk lapisan LSTM dan GRU .

Menggunakan kernel CuDNN jika tersedia

Mari buat model LSTM sederhana untuk mendemonstrasikan perbedaan performa.

Kami akan menggunakan urutan masukan urutan baris digit MNIST (memperlakukan setiap baris piksel sebagai langkah waktu), dan kami akan memprediksi label digit tersebut.

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 muat set data 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 contoh model dan latih.

Kami memilih sparse_categorical_crossentropy sebagai fungsi kerugian untuk model. Keluaran model berbentuk [batch_size, 10] . Target model adalah vektor bilangan bulat, masing-masing bilangan bulat berada dalam kisaran 0 sampai 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 [==============================] - 4s 4ms/step - loss: 0.9189 - accuracy: 0.7123 - val_loss: 0.5157 - val_accuracy: 0.8371

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

Sekarang, mari 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 [==============================] - 30s 32ms/step - loss: 0.3873 - accuracy: 0.8826 - val_loss: 0.2837 - val_accuracy: 0.9107

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

Saat menjalankan mesin dengan GPU NVIDIA dan CuDNN yang diinstal, 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. Anotasi tf.device bawah ini hanya memaksa penempatan perangkat. Model akan berjalan di CPU secara default jika GPU tidak tersedia.

Anda tidak perlu khawatir tentang perangkat keras yang Anda gunakan lagi. Bukankah itu 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 masukan daftar / dikt, atau masukan bersarang

Struktur bersarang memungkinkan pelaksana untuk memasukkan lebih banyak informasi dalam satu langkah waktu. Misalnya, bingkai video dapat memiliki input audio dan video pada saat yang 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. Sehingga representasi datanya bisa berupa:

[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 bertingkat

Lihat Membuat Lapisan & Model baru melalui subclass untuk detail 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}

Buat model RNN dengan input / output bertingkat

Mari buat model Keras yang menggunakan lapisan keras.layers.RNN dan sel khusus yang baru saja kita tentukan.

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 dibuat 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 [==============================] - 0s 20ms/step - loss: 0.7370 - rnn_1_loss: 0.2622 - rnn_1_1_loss: 0.4748 - rnn_1_accuracy: 0.1016 - rnn_1_1_accuracy: 0.0358

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

Dengan lapisan Keras keras.layers.RNN , Anda hanya diharapkan untuk menentukan logika matematika untuk setiap langkah dalam urutan, dan lapisan keras.layers.RNN akan menangani iterasi urutan untuk Anda. Ini adalah cara yang sangat ampuh untuk membuat prototipe jenis RNN baru dengan cepat (misalnya varian LSTM).

Untuk lebih jelasnya, silakan kunjungi dokumen API .