Réseaux de neurones récurrents (RNN) avec Keras

Traduction du Recursive Neural Network Guide de Tensorflow.org. Le document traite à la fois des capacités intégrées de Keras / Tensorflow 2.0 pour un maillage rapide, ainsi que de la possibilité de personnaliser les couches et les cellules. Les cas et les limites de l'utilisation du noyau CuDNN sont également considérés, ce qui permet d'accélérer le processus d'apprentissage du réseau neuronal.



Les réseaux de neurones récursifs (RNN) sont une classe de réseaux de neurones qui sont bons pour modéliser des données série, telles que les séries chronologiques ou le langage naturel.

Si schématiquement, la couche RNN utilise une boucle forpour itérer sur une séquence ordonnée dans le temps, tout en stockant dans un état interne, des informations codées sur les étapes qu'il a déjà vues.

Keras API RNN est conçu avec un accent sur:

Facilité d'utilisation : intégrée dans les couches tf.keras.layers.RNN, tf.keras.layers.LSTM, tf.keras.layers.GRUvous permettent de construire rapidement un modèle récursif sans avoir à définir des paramètres de configuration complexes.

Personnalisation facile : vous pouvez également définir votre propre couche de cellules RNN (partie intérieure de la bouclefor) avec un comportement personnalisé et l'utiliser avec une couche commune de `tf.keras.layers.RNN` (la boucle` for` elle-même). Cela vous permettra de prototyper rapidement diverses idées de recherche de manière flexible, avec un minimum de code.

Installation


from __future__ import absolute_import, division, print_function, unicode_literals

import collections
import matplotlib.pyplot as plt
import numpy as np

import tensorflow as tf

from tensorflow.keras import layers

Construire un modèle simple


Keras a trois couches RNN intégrées:

  1. tf.keras.layers.SimpleRNN, un RNN entièrement connecté dans lequel la sortie de l'étape de temps précédente doit être passée à l'étape suivante.
  2. tf.keras.layers.GRU, proposé pour la première fois dans l'article Étudier des phrases en utilisant le codec RNN pour la traduction automatique statistique
  3. tf.keras.layers.LSTM, d'abord proposé dans l'article Mémoire à court terme à long terme

Début 2015, Keras a présenté les premières implémentations Python open source réutilisables et LSTM et GRU.

Voici un exemple d'un Sequentialmodèle qui traite des séquences d'entiers en imbriquant chaque entier dans un vecteur à 64 dimensions, puis en traitant des séquences de vecteurs à l'aide d'une couche LSTM.

model = tf.keras.Sequential()
#   Embedding      1000, 
#     64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))

#   LSTM  128  .
model.add(layers.LSTM(128))

#   Dense  10    softmax.
model.add(layers.Dense(10))

model.summary()

Sorties et états


Par défaut, la sortie de la couche RNN contient un vecteur par élément. Ce vecteur est la sortie de la dernière cellule RNN contenant des informations sur la séquence d'entrée entière. La dimension de cette sortie (batch_size, units), où unitscorrespond à l'argument unitspassé au constructeur de couche.

La couche RNN peut également renvoyer la séquence de sortie entière pour chaque élément (un vecteur pour chaque étape), si vous le spécifiez return_sequences=True. La dimension de cette sortie est (batch_size, timesteps, units).

model = tf.keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))

#  GRU  3D   (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))

#  SimpleRNN  2D   (batch_size, 128)
model.add(layers.SimpleRNN(128))

model.add(layers.Dense(10))

model.summary()

De plus, la couche RNN peut retourner son ou ses états internes finaux.

Les états renvoyés peuvent être utilisés ultérieurement pour reprendre l'exécution du RNN ou pour initialiser un autre RNN . Ce paramètre est généralement utilisé dans le modèle codeur-décodeur, séquence à séquence, où l'état final du codeur est utilisé pour l'état initial du décodeur.

Pour que la couche RNN retourne son état interne, définissez le paramètre return_statesur value Truelors de la création de la couche. Notez qu'il y a LSTM2 tenseurs d'état, et GRUun seul.

Pour ajuster l'état initial d'un calque, appelez simplement le calque avec un argument supplémentaire initial_state.

Notez que la dimension doit correspondre à la dimension de l'élément de calque, comme dans l'exemple suivant.

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)

#       
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)

#  2     LSTM    
decoder_output = layers.LSTM(
    64, name='decoder')(decoder_embedded, initial_state=encoder_state)
output = layers.Dense(10)(decoder_output)

model = tf.keras.Model([encoder_input, decoder_input], output)
model.summary()

Couches RNN et cellules RNN


L'API RNN, en plus des couches RNN intégrées, fournit également des API au niveau des cellules. Contrairement aux couches RNN, qui traitent des paquets entiers de séquences d'entrée, une cellule RNN ne traite qu'un seul pas de temps.

La cellule est à l'intérieur du cycle de la forcouche RNN. Envelopper une cellule avec une couche tf.keras.layers.RNNvous donne une couche capable de traiter des paquets de séquence, par exemple RNN(LSTMCell(10)).

Mathématiquement, RNN(LSTMCell(10))cela donne le même résultat que LSTM(10). En fait, l'implémentation de cette couche à l'intérieur de TF v1.x consistait uniquement à créer la cellule RNN correspondante et à l'envelopper dans la couche RNN. Cependant, l'utilisation de couches intégrées GRUet LSTMpermet l'utilisation de CuDNN qui peut vous donner de meilleures performances.

Il existe trois cellules RNN intégrées, chacune correspondant à sa propre couche RNN.

  • tf.keras.layers.SimpleRNNCellcorrespond au calque SimpleRNN.
  • tf.keras.layers.GRUCellcorrespond au calque GRU.
  • tf.keras.layers.LSTMCellcorrespond au calque LSTM.

L'abstraction d'une cellule avec une classe commune tf.keras.layers.RNNfacilite l'implémentation d'architectures RNN personnalisées pour votre recherche.

État de sauvegarde inter-lots


Lors du traitement de longues séquences (éventuellement sans fin), vous souhaiterez peut-être utiliser le modèle d’ état croisé .

Habituellement, l'état interne de la couche RNN est réinitialisé avec chaque nouveau paquet de données (c'est-à-dire que chaque exemple qui voit la couche est supposé être indépendant du passé). La couche ne conservera son état que pendant la durée de traitement de cet élément.

Cependant, si vous avez de très longues séquences, il est utile de les décomposer en séquences plus courtes et de les transférer à tour de rôle dans la couche RNN sans réinitialiser l'état de la couche. Ainsi, une couche peut stocker des informations sur la séquence entière, bien qu'elle ne verra qu'une seule sous-séquence à la fois.

Vous pouvez le faire en définissant `stateful = True` dans le constructeur.

Si vous avez la séquence `s = [t0, t1, ... t1546, t1547]`, vous pouvez la diviser par exemple en:

s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]

Ensuite, vous pouvez le traiter avec:

lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
  output = lstm_layer(s)

Lorsque vous souhaitez nettoyer la condition, utilisez layer.reset_states().
Remarque: Dans ce cas, il est supposé que l'exemple ide ce package est une continuation de l'exemple du ipackage précédent. Cela signifie que tous les packages contiennent le même nombre d'éléments (taille du package). Par exemple, si le package contient [sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100], le package suivant doit contenir [sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200].
Voici un exemple complet:

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()      initial_state.
#  initial_state   ,      .
lstm_layer.reset_states()

RNN bidirectionnel


Pour les séquences autres que les séries chronologiques (par exemple les textes), il arrive souvent que le modèle RNN fonctionne mieux s'il traite la séquence non seulement du début à la fin, mais vice versa. Par exemple, pour prédire le mot suivant dans une phrase, il est souvent utile de connaître le contexte autour du mot, et pas seulement les mots devant lui.

Keras fournit une API simple pour créer de tels RNN bidirectionnels: un wrapper tf.keras.layers.Bidirectional.

model = tf.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()

Sous le capot, la Bidirectionalcouche RNN transférée go_backwardssera copiée et le champ de la couche nouvellement copiée sera retourné , et ainsi les données d'entrée seront traitées dans l'ordre inverse.

La sortie de ` BidirectionalRNN par défaut sera la somme de la sortie de la couche avant et de la sortie de la couche inverse. Si vous avez besoin d'un autre comportement de fusion, par exemple concaténation, modifiez le paramètre `merge_mode` dans le constructeur de wrapper` bidirectionnel`.

Optimisation des performances et noyau CuDNN dans TensorFlow 2.0


Dans TensorFlow 2.0, les couches LSTM et GRU intégrées sont utilisables par défaut des cœurs CuDNN si un processeur graphique est disponible. Avec ce changement, les couches précédentes keras.layers.CuDNNLSTM/CuDNNGRUsont obsolètes et vous pouvez construire votre modèle sans vous soucier de l'équipement sur lequel il fonctionnera.

Étant donné que le noyau CuDNN est construit avec certaines hypothèses, cela signifie que la couche ne pourra pas utiliser la couche du noyau CuDNN si vous modifiez les paramètres par défaut des couches LSTM ou GRU intégrées . Par exemple.

  • Changer une fonction activationde tanhquelque chose d'autre.
  • Changer une fonction recurrent_activationde sigmoidquelque chose d'autre.
  • Utilisation recurrent_dropout> 0.
  • Un réglage unrollsur True, ce qui provoque LSTM / GRU pour décomposer le interne tf.while_loopdans une boucle déployée for.
  • Réglez use_biassur False.
  • Utiliser des masques lorsque les données d'entrée ne sont pas justifiées à droite (si le masque correspond aux données strictement alignées à droite, CuDNN peut toujours être utilisé. C'est le cas le plus courant).

Si possible, utilisez des noyaux CuDNN


batch_size = 64
#    MNIST    (batch_size, 28, 28).
#     (28, 28) (   ).
input_dim = 28

units = 64
output_size = 10  #   0  9

#  RNN 
def build_model(allow_cudnn_kernel=True):
  # CuDNN     ,     .
  #   `LSTM(units)`    CuDNN,
  #   RNN(LSTMCell(units))   non-CuDNN .
  if allow_cudnn_kernel:
    #  LSTM      CuDNN.
    lstm_layer = tf.keras.layers.LSTM(units, input_shape=(None, input_dim))
  else:
    #  LSTMCell  RNN    CuDNN.
    lstm_layer = tf.keras.layers.RNN(
        tf.keras.layers.LSTMCell(units),
        input_shape=(None, input_dim))
  model = tf.keras.models.Sequential([
      lstm_layer,
      tf.keras.layers.BatchNormalization(),
      tf.keras.layers.Dense(output_size)]
  )
  return model

Chargement de l'ensemble de données MNIST


mnist = tf.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]

Créez une instance du modèle et compilez-la


Nous avons choisi sparse_categorical_crossentropyen fonction des pertes. La sortie du modèle a une dimension [batch_size, 10]. La réponse du modèle est un vecteur entier, chacun des nombres est compris entre 0 et 9.

model = build_model(allow_cudnn_kernel=True)

model.compile(loss=tf.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=5)

Construisez un nouveau modèle sans noyau CuDNN


slow_model = build_model(allow_cudnn_kernel=False)
slow_model.set_weights(model.get_weights())
slow_model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
                   optimizer='sgd', 
                   metrics=['accuracy'])
slow_model.fit(x_train, y_train, 
               validation_data=(x_test, y_test), 
               batch_size=batch_size,
               epochs=1)  #         .

Comme vous pouvez le voir, le modèle construit avec CuDNN est beaucoup plus rapide pour la formation que le modèle utilisant le noyau TensorFlow habituel.

Le même modèle avec prise en charge CuDNN peut être utilisé pour la sortie dans un environnement à processeur unique. L'annotation tf.deviceindique simplement l'appareil utilisé. Le modèle s'exécutera par défaut sur le CPU si le GPU n'est pas disponible.

Vous n'avez simplement pas à vous soucier du matériel sur lequel vous travaillez. N'est-ce pas cool?

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'))

RNN avec entrée liste / dictionnaire ou entrée imbriquée


Les structures imbriquées vous permettent d'inclure plus d'informations en une seule fois. Par exemple, une image vidéo peut contenir simultanément des entrées audio et vidéo. La dimension des données dans ce cas peut être:

[batch, timestep, {\"video\": [height, width, channel], \"audio\": [frequency]}]

Dans un autre exemple, les données manuscrites peuvent avoir à la fois des coordonnées x et y pour la position actuelle du stylet, ainsi que des informations de pression. Ainsi, les données peuvent être représentées comme suit:

[batch, timestep, {\"location\": [x, y], \"pressure\": [force]}]

Le code suivant crée un exemple de cellule RNN personnalisée qui fonctionne avec une telle entrée structurée.

Définissez une cellule utilisateur prenant en charge les entrées / sorties imbriquées


NestedInput = collections.namedtuple('NestedInput', ['feature1', 'feature2'])
NestedState = collections.namedtuple('NestedState', ['state1', 'state2'])

class NestedCell(tf.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 = NestedState(state1=unit_1, 
                                  state2=tf.TensorShape([unit_2, unit_3]))
    self.output_size = (unit_1, tf.TensorShape([unit_2, unit_3]))
    super(NestedCell, self).__init__(**kwargs)

  def build(self, input_shapes):
    # #  input_shape  2 , [(batch, i1), (batch, i2, i3)]
    input_1 = input_shapes.feature1[1]
    input_2, input_3 = input_shapes.feature2[1:]

    self.kernel_1 = self.add_weight(
        shape=(input_1, self.unit_1), initializer='uniform', name='kernel_1')
    self.kernel_2_3 = self.add_weight(
        shape=(input_2, input_3, self.unit_2, self.unit_3),
        initializer='uniform',
        name='kernel_2_3')

  def call(self, inputs, states):
    #     [(batch, input_1), (batch, input_2, input_3)]
    #     [(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 = NestedState(state1=state_1, state2=state_2_3)

    return output, new_states

Créer un modèle RNN avec entrée / sortie imbriquée


Construisons un modèle Keras qui utilise une couche tf.keras.layers.RNNet une cellule personnalisée que nous venons de définir.

unit_1 = 10
unit_2 = 20
unit_3 = 30

input_1 = 32
input_2 = 64
input_3 = 32
batch_size = 64
num_batch = 100
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = tf.keras.layers.RNN(cell)

inp_1 = tf.keras.Input((None, input_1))
inp_2 = tf.keras.Input((None, input_2, input_3))

outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))

model = tf.keras.models.Model([inp_1, inp_2], outputs)

model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])unit_1 = 10
unit_2 = 20
unit_3 = 30

input_1 = 32
input_2 = 64
input_3 = 32
batch_size = 64
num_batch = 100
timestep = 50

cell = NestedCell(unit_1, unit_2, unit_3)
rnn = tf.keras.layers.RNN(cell)

inp_1 = tf.keras.Input((None, input_1))
inp_2 = tf.keras.Input((None, input_2, input_3))

outputs = rnn(NestedInput(feature1=inp_1, feature2=inp_2))

model = tf.keras.models.Model([inp_1, inp_2], outputs)

model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])

Former le modèle sur des données générées aléatoirement


Comme nous ne disposons pas d'un bon ensemble de données pour ce modèle, nous utilisons des données aléatoires générées par la bibliothèque Numpy pour la démonstration.

input_1_data = np.random.random((batch_size * num_batch, timestep, input_1))
input_2_data = np.random.random((batch_size * num_batch, timestep, input_2, input_3))
target_1_data = np.random.random((batch_size * num_batch, unit_1))
target_2_data = np.random.random((batch_size * num_batch, 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)

Avec une couche, tf.keras.layers.RNNvous n'avez qu'à déterminer la logique mathématique d'une seule étape dans la séquence, et la couche tf.keras.layers.RNNgérera l'itération de la séquence pour vous. C'est un moyen incroyablement puissant pour prototyper rapidement de nouveaux types de RNN (par exemple la variante LSTM).

Après vérification, la traduction apparaîtra également sur Tensorflow.org. Si vous souhaitez participer à la traduction de la documentation du site Web Tensorflow.org en russe, veuillez contacter personnellement ou commenter. Toutes les corrections et commentaires sont appréciés.

Source: https://habr.com/ru/post/undefined/


All Articles