[PYTHON] [TensorFlow / Keras] La route pour assembler un RNN de votre structure préférée

introduction

** RNN (Recurrent Neural Network) est une forme de réseau neuronal qui prend des données chronologiques en entrée et détermine la sortie en utilisant "l'état" de l'heure précédente en plus de l'entrée de l'heure actuelle. La LSTM (Long Short-Term Memory) est-elle célèbre? Les données de séries chronologiques sont des données qui ont un sens pour toute la colonne, comme la vidéo et le texte. Un réseau de neurones ordinaire prend un certain format de données telles que des images et des caractères en entrée, mais lorsqu'il s'agit de vidéos et de textes dans lesquels ils sont alignés, non seulement des images individuelles (cadres) et des caractères, mais aussi leur disposition A aussi une grande signification. La structure qui gère bien ces données est RNN.

Cependant, je pense qu'il est difficile d'être honnête, contrairement aux couches ordinaires entièrement connectées. Moi aussi.

Donc, je veux d'abord comprendre ce que fait le RNN, puis je veux être en mesure de construire mon propre RNN qui utilise "l'état" de la fois précédente.

Environnement de vérification

Cible

Je voudrais résoudre les problèmes suivants (parce que j'étais en fait inquiet ...).

--Je ne comprends pas la différence entre RNN '', SimpleRNN '' et SimpleRNNCell ''. ――Je veux comprendre le contenu de couches telles que LSTM '' ――Je veux écrire moi-même une couche RNN non standard pour retester l'expérience du papier

[^ basiclstm]: Les bases de LSTM qui ne peuvent pas être entendues maintenant-BONJOUR CYBERNETIQUE

Ce serait bien si vous pouviez construire votre propre RNN en utilisant Keras en regardant la structure du réseau (graphique) et les formules comme décrit sur la page de référence [^ basiclstm].

Au contraire, les choses suivantes ne sont pas traitées sur cette page. J'écrirai peut-être un article à une autre occasion.

Notions de base sur RNN

La base de RNN est ** "La sortie dépend de l'entrée de l'heure actuelle et de" l'état "de l'heure précédente" **. La différence est que dans une couche normale entièrement connectée ou couche de convolution, la sortie dépend uniquement de l'entrée, mais dans RNN, les informations de l'entrée précédente peuvent également être utilisées. Vous pouvez décider vous-même de «l'état» que vous souhaitez apporter à la prochaine fois.

Comme le montre la figure sur le côté gauche ci-dessous, RNN peut être exprimé comme un réseau (cellule) avec une structure récursive, mais l'opération spécifique est sous la forme d'une extension de la boucle comme indiqué sur la figure sur le côté droit. Vous pouvez comprendre (source [^ 1]).

image.png

--Entrée: $ x_1, x_2, ..., x_t, ... $

Une fois donné

À

\begin{align}
s_t &= f(Ux_t + Ws_{t-1} + b) \\
o_t &= h(Vs_t)
\end{align}

Il est déterminé comme suit. Où $ U, V, W $ sont des matrices et $ b $ sont des vecteurs de colonnes, qui sont des poids de couche (paramètres à entraîner). $ f et h $ sont des fonctions d'activation. L'entrée / sortie et l'état $ x_t, o_t, s_t $ sont également des vecteurs colonnes.

Le RNN le plus simple

Tout d'abord, touchons RNN. En tant que RNN simple, considérons un réseau qui sort séquentiellement la somme partielle des nombres d'entrée (la somme de toutes les valeurs du début à ce point). A ce moment, la somme partielle est définie comme "état" et l'état est sorti tel quel. Par exemple, la sortie et l'état de l'entrée changeront comme indiqué dans le tableau ci-dessous.

t 1 2 3 4 5 6 7 ...
x_t 1 3 2 4 1 0 1
s_t 1 4 6 10 11 11 12
o_t 1 4 6 10 11 11 12

Dans TensorFlow + Keras, en utilisant une couche appelée `` tf.keras.layers.SimpleRNN ''

\begin{align}
o_t = s_t = f(Ux_t + Ws_{t-1} + b) \tag{1}
\end{align}

Vous pouvez définir un réseau du formulaire. Si vous définissez $ f (x) = x $ et entraînez une séquence de nombres et leurs sommes partielles,

\begin{align}
o_t = s_t = Ux_t + Ws_{t-1} + b
\end{align}

On s'attend à ce que le poids de se rapproche de $ U = W = 1, ; b = 0 $ (cette fois, nous avons affaire à des valeurs unidimensionnelles, vous pouvez donc considérer $ U, W, b $ comme des scalaires. Hmm).

Essayez d'apprendre avec le code ci-dessous. Une séquence de nombres aléatoires d'une longueur de 30 et une séquence de sommes partielles calculées à partir de celles-ci sont données pour l'apprentissage.

first.py


import tensorflow as tf 
import numpy as np 
from tensorflow.keras import Sequential 
from tensorflow.keras.layers import SimpleRNN
from tensorflow.keras.optimizers import SGD

tf.random.set_seed(111)
np.random.seed(111)

model = Sequential([
    SimpleRNN(1, activation=None, input_shape=(None, 1), return_sequences=True)
])
model.compile(optimizer=SGD(lr=0.0001), loss="mean_squared_error")

n = 51200
x = np.random.random((n, 30, 1))
y = x.cumsum(axis=1)

model.fit(x, y, batch_size=512, epochs=100)

model.layers[0].weights
# [<tf.Variable 'simple_rnn/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[0.6021545]], dtype=float32)>,
#  <tf.Variable 'simple_rnn/recurrent_kernel:0' shape=(1, 1) dtype=float32, numpy=array([[1.0050855]], dtype=float32)>,
#  <tf.Variable 'simple_rnn/bias:0' shape=(1,) dtype=float32, numpy=array([0.20719269], dtype=float32)>]

model.predict(np.ones((1, 30, 1)) * 0.5).flatten()
# array([ 0.5082699,  1.0191246,  1.5325773,  2.0486412,  2.5673294,
#         3.0886555,  3.6126328,  4.1392746,  4.6685944,  5.2006063,
#         5.7353234,  6.27276  ,  6.8129296,  7.3558464,  7.901524 ,
#         8.449977 ,  9.00122  ,  9.555265 , 10.112128 , 10.6718235,
#        11.2343645, 11.799767 , 12.368044 , 12.939212 , 13.513284 ,
#        14.090276 , 14.670201 , 15.253077 , 15.838916 , 16.427734 ],
#       dtype=float32)

L'erreur semble grande, mais comme il s'agit d'un échantillon, ce serait bien si vous pouviez saisir l'atmosphère (la précision de sortie n'est pas recherchée ici). Ici, à la suite de l'apprentissage

\begin{align}
o_t = s_t = 0.6022x_t + 1.0051s_{t-1} + 0.2072
\end{align}

Sera obtenu.

Explication de SimpleRNN

SimpleRNN(1, activation=None, input_shape=(None, 1), return_sequences=True)

Consultez la documentation officielle pour plus de détails. tf.keras.layers.SimpleRNN | TensorFlow Core v2.1.0

Réécriture avec RNN

Le code qui crée le modèle ci-dessus équivaut à:

from tensorflow.keras.layers import RNN, SimpleRNN, SimpleRNNCell

model = Sequential([
    #SimpleRNN(1, activation=None, input_shape=(None, 1), return_sequences=True) 
    RNN(SimpleRNNCell(1, activation=None), input_shape=(None, 1), return_sequences=True)
])

The cell is the inside of the for loop of a RNN layer. Wrapping a cell inside a tf.keras.layers.RNN layer gives you a layer capable of processing batches of sequences, e.g. RNN(LSTMCell(10)).

Recurrent Neural Networks (RNN) with Keras | TensorFlow Core

SimpleRNNCell '' définit une opération (cellule) pour un seul échantillon, et l'enfermer dans RNN () '' définit une couche pour traiter le lot.

En d'autres termes, vous devriez être en mesure de définir un RNN de votre structure préférée en définissant votre propre traitement basé sur des échantillons équivalent à SimpleRNNCell '' et en le plaçant dans RNN () ''.

Jusqu'à présent, nous avons compris la «différence entre» «RNN» et «SimpleRNN» et «SimpleRNNCell» mentionnée au début.

Jetons un coup d'œil au contenu de SimpleRNNCell

En vue de créer votre propre RNN, voyons d'abord ce que fait le `` SimpleRNNCell '' existant. Lorsque vous écrivez par vous-même, il doit s'agir d'un raccourci pour imiter d'abord le traitement existant.

(Aussi, je pense que vous pouvez vous référer à l'exemple de tf.keras.layers.RNN | TensorFlow Core v2.1.0)

Le code source de `` SimpleRNNCell '' se trouve ci-dessous. tensorflow/recurrent.py at v2.1.0 · tensorflow/tensorflow · GitHub

Jetons un coup d'œil au contenu en en tirant un extrait.

Tout d'abord, héritez de Layer '' dans la définition de classe. DropoutRNNCellMixin '' semble être hérité pour supporter Dropout, mais il est hors de question et ne sera pas mentionné ici.

recurrent.py


class SimpleRNNCell(DropoutRNNCellMixin, Layer):

Dans build (), le poids requis pour la couche est défini par ```add_weight. Non seulement RNN mais aussi Dense () '' etc. font la même chose. Correspondant à l'équation (1), kernel est $ U $, recurrent_kernel est $ W $ et `` biais '' est $ b $.

Ensuite, utilisez `` call () '' pour définir le traitement réel. C'est le plus important.

recurrent.py


  def call(self, inputs, states, training=None):
    prev_output = states[0]
    dp_mask = self.get_dropout_mask_for_cell(inputs, training)
    rec_dp_mask = self.get_recurrent_dropout_mask_for_cell(
        prev_output, training)

    if dp_mask is not None:
      h = K.dot(inputs * dp_mask, self.kernel)
    else:
      h = K.dot(inputs, self.kernel)
    if self.bias is not None:
      h = K.bias_add(h, self.bias)

    if rec_dp_mask is not None:
      prev_output = prev_output * rec_dp_mask
    output = h + K.dot(prev_output, self.recurrent_kernel)
    if self.activation is not None:
      output = self.activation(output)

    return output, [output]

L'entrée $ x_t $ est entrée dans ʻinputs``, et l'état $ s_ {t-1} $ (généré à l'instant précédent) est entré dans `states. States '' est passé comme une liste de chaque variable afin qu'elle puisse avoir plusieurs états. Par conséquent, seul `` states [0] '' est extrait en premier. Laissons de côté le traitement lié à Dropout et essayons d'extraire la partie principale

h = K.dot(inputs, self.kernel)
if self.bias is not None:
  h = K.bias_add(h, self.bias)
output = h + K.dot(prev_output, self.recurrent_kernel)
if self.activation is not None:
  output = self.activation(output)
return output, [output]

C'est tout. Dans TensorFlow, chaque échantillon d'entrée / sortie est représenté par un vecteur de ligne, donc l'ordre du produit matriciel est inversé, mais vous pouvez voir qu'il peut correspondre à l'équation (1). Le retour '' final renvoie la sortie de couche $ o_t $ et l'état $ s_t $ que vous voulez amener à la prochaine fois. L'état passé ici peut être reçu par call () '' à la prochaine fois. L'état est renvoyé sous forme de liste ainsi que l'argument. Si vous renvoyez plusieurs états ici, vous recevrez plusieurs états la prochaine fois.

Jetons un coup d'œil à LSTM

Ensuite, regardons la couche LSTM comme un exemple légèrement plus compliqué. Tout d'abord, essayez d'utiliser LSTM '' au lieu de SimpleRNN '' avec les mêmes paramètres de problème qu'avant.

lstm.py


import tensorflow as tf 
import numpy as np 
from tensorflow.keras import Sequential 
from tensorflow.keras.layers import LSTM
from tensorflow.keras.optimizers import SGD

tf.random.set_seed(111)
np.random.seed(111)

model = Sequential([
    LSTM(1, activation=None, input_shape=(None, 1), return_sequences=True)
])
model.compile(optimizer=SGD(lr=0.0001), loss="mean_squared_error")

n = 51200
x = np.random.random((n, 30, 1))
y = x.cumsum(axis=1)

model.fit(x, y, batch_size=512, epochs=100)

model.layers[0].weights                                                   
# [<tf.Variable 'lstm/kernel:0' shape=(1, 4) dtype=float32, numpy=
#  array([[ 0.11471224, -0.15296884,  0.82662594, -0.14256166]],
#        dtype=float32)>,
#  <tf.Variable 'lstm/recurrent_kernel:0' shape=(1, 4) dtype=float32, numpy=
#  array([[ 0.10575113,  0.16468772, -0.05777477,  0.20210776]],
#        dtype=float32)>,
#  <tf.Variable 'lstm/bias:0' shape=(4,) dtype=float32, numpy=array([0.4812489, 1.6566612, 1.1815464, 0.4349145], dtype=float32)>]

model.predict(np.ones((1, 30, 1)) * 0.5).flatten()
# array([ 0.59412843,  1.1486205 ,  1.6723596 ,  2.1724625 ,  2.6546886 ,
#         3.1237347 ,  3.5834525 ,  4.0370073 ,  4.486994  ,  4.93552   ,
#         5.38427   ,  5.8345466 ,  6.2873073 ,  6.7431927 ,  7.20255   ,
#         7.6654577 ,  8.131752  ,  8.601054  ,  9.072805  ,  9.546291  ,
#        10.0206785 , 10.495057  , 10.968457  , 11.439891  , 11.908364  ,
#        12.372919  , 12.832628  , 13.286626  , 13.734106  , 14.174344  ],
#       dtype=float32)

En fait, si vous voulez juste l'utiliser, j'ai essayé de l'utiliser dans l'article précédent (le réglage du problème est le même ...) Essayez return_sequences = True sur RNN (LSTM) -Qiita de Keras Cette fois, approfondissons un peu la partie implémentation.

Similaire à SimpleRNN '', il peut également être séparé en cellules de RNN '' pour obtenir un traitement équivalent [^ 2].

[^ 2]: En fait, l'utilisation de `` LSTM '' fournit une implémentation rapide de CuDNN (dans certains cas), donc si vous voulez juste utiliser LSTM, il est inutile de le séparer.

from tensorflow.keras.layers import LSTM, RNN, LSTMCell

model = Sequential([
    # LSTM(1, activation=None, input_shape=(None, 1), return_sequences=True)
    RNN(LSTMCell(1, activation=None), input_shape=(None, 1), return_sequences=True)
])

Après cela, nous nous concentrerons sur le traitement de la partie de cellule `` LSTMCell ''.

Formule LSTM

Avant de regarder l'implémentation, vérifions d'abord le traitement de LSTM. La signification théorique de chaque porte n'est pas mentionnée ici. (Les formules et les chiffres proviennent de [^ basiclstm])

20170506172239.png

\begin{align}
o_t &= σ \left( W_ox_t + R_oh_{t-1} + b_o \right) \tag{2.1}\\
f_t &= σ \left( W_fx_t + R_fh_{t-1} + b_f \right) \tag{2.2}\\
i_t &= σ \left( W_ix_t + R_ih_{t-1} + b_i \right) \tag{2.3}\\
z_t &= \tanh \left( W_zx_t + R_zh_{t-1} + b_z \right) \tag{2.4}\\
c_t &= i_t \otimes z_t+c_{t-1} \otimes f_t  \tag{2.5}\\
h_t &= o_t \otimes \tanh(c_t) \tag{2.6}
\end{align}

Cependant, $ \ otimes $ est le produit de chaque élément, $ \ sigma $ est la fonction sigmoïde et $ \ tanh $ est la tangente à deux courbes (tangente hyperbolique).

Décodage LSTMCell

Regardons l'implémentation de `` LSTMCell '' basée sur les équations (2.1) à (2.6). tensorflow/recurrent.py at v2.1.0 · tensorflow/tensorflow · GitHub

L'exportation d'une classe est identique à `` SimpleRNN ''.

recurrent.py


class LSTMCell(DropoutRNNCellMixin, Layer):

Le poids est défini par `` build () ''.

recurrent.py


  def build(self, input_shape):
    default_caching_device = _caching_device(self)
    input_dim = input_shape[-1]
    self.kernel = self.add_weight(
        shape=(input_dim, self.units * 4),
        name='kernel',
        initializer=self.kernel_initializer,
        regularizer=self.kernel_regularizer,
        constraint=self.kernel_constraint,
        caching_device=default_caching_device)
    self.recurrent_kernel = self.add_weight(
        shape=(self.units, self.units * 4),
        name='recurrent_kernel',
        initializer=self.recurrent_initializer,
        regularizer=self.recurrent_regularizer,
        constraint=self.recurrent_constraint,
        caching_device=default_caching_device)

    if self.use_bias:
      if self.unit_forget_bias:

        def bias_initializer(_, *args, **kwargs):
          return K.concatenate([
              self.bias_initializer((self.units,), *args, **kwargs),
              initializers.Ones()((self.units,), *args, **kwargs),
              self.bias_initializer((self.units * 2,), *args, **kwargs),
          ])
      else:
        bias_initializer = self.bias_initializer
      self.bias = self.add_weight(
          shape=(self.units * 4,),
          name='bias',
          initializer=bias_initializer,
          regularizer=self.bias_regularizer,
          constraint=self.bias_constraint,
          caching_device=default_caching_device)
    else:
      self.bias = None
    self.built = True

Outre les détails ici, veuillez noter qu'il existe plusieurs descriptions de self.units * 4 ''. En fait, noyaucontient une concaténation de quatre matrices $ W_o, W_f, W_i, W_z $ [^ 4]. De même, recurrent_kernel '' a quatre de $ R_o, R_f, R_i, R_z $ ensemble, et bias '' a quatre de b_o, b_f, b_i, b_z ''. Je les ai tous ensemble. Bien sûr, ce n'est pas une erreur de garder chacune d'elles en 4 variables (12 au total). Comme d'habitude, les lignes et les colonnes sont inversées par rapport à l'écriture dans une formule, donc en regardant la formule, il semble que le nombre de lignes sera quadruplé, mais dans le code, le nombre de colonnes est quadruplé.

[^ 4]: En plus de l'effet de réduction du nombre de variables, il y a un avantage que le contenu des fonctions d'activation dans les équations (2.1) à (2.4) peut être calculé ensemble. Si vous donnez ```implementation = 2lors de la création de LSTMCell () ``, il semble que l'implémentation qui calcule tous ensemble est utilisée.

`` call () '' est la partie principale du corps. Par souci de simplicité, seul le traitement pour «implémentation = 1» est affiché.

recurrent.py


  def call(self, inputs, states, training=None):
    h_tm1 = states[0]  # previous memory state
    c_tm1 = states[1]  # previous carry state
    (Abréviation)
      if 0 < self.dropout < 1.:
        (Abréviation)
      else:
        inputs_i = inputs
        inputs_f = inputs
        inputs_c = inputs
        inputs_o = inputs
      k_i, k_f, k_c, k_o = array_ops.split(
          self.kernel, num_or_size_splits=4, axis=1)
      x_i = K.dot(inputs_i, k_i)
      x_f = K.dot(inputs_f, k_f)
      x_c = K.dot(inputs_c, k_c)
      x_o = K.dot(inputs_o, k_o)
      if self.use_bias:
        b_i, b_f, b_c, b_o = array_ops.split(
            self.bias, num_or_size_splits=4, axis=0)
        x_i = K.bias_add(x_i, b_i)
        x_f = K.bias_add(x_f, b_f)
        x_c = K.bias_add(x_c, b_c)
        x_o = K.bias_add(x_o, b_o)

      if 0 < self.recurrent_dropout < 1.:
        (Abréviation)
      else:
        h_tm1_i = h_tm1
        h_tm1_f = h_tm1
        h_tm1_c = h_tm1
        h_tm1_o = h_tm1
      x = (x_i, x_f, x_c, x_o)
      h_tm1 = (h_tm1_i, h_tm1_f, h_tm1_c, h_tm1_o)
      c, o = self._compute_carry_and_output(x, h_tm1, c_tm1)
    (Abréviation)
    h = o * self.activation(c)
    return h, [h, c]

Dans les équations (2.1) à (2.6), $ h_ {t-1} et c_ {t-1} $ sont utilisés comme informations sur le temps précédent. Par conséquent, les deux doivent avoir pour État. ** Comme mentionné ci-dessus, vous pouvez gérer plus d'un état en passant les états dans une liste.

Dans la première moitié, nous calculons quatre valeurs: $ W_ox_t + b_o, W_fx_t + b_f, W_ix_t + b_i, W_zx_t + b_z $. (Notez que $ W_z et b_z $ dans la formule (2.4) sont différents de k_c '' et b_cdans le code.) Dans la seconde moitié, nous utilisons _compute_carry_and_output () '' pour calculer les valeurs de $ c_t et o_t $. Enfin, calculez $ h_t $. Ici, $ h_t $ est sorti tel quel, et $ h_t et c_t $ sont retournés comme états à utiliser dans le calcul à la prochaine fois. La valeur par défaut pour «« activation »est« tanh », comme dans l'équation (2.6).

`` _compute_carry_and_output () '' est défini comme suit.

recurrent.py


  def _compute_carry_and_output(self, x, h_tm1, c_tm1):
    """Computes carry and output using split kernels."""
    x_i, x_f, x_c, x_o = x
    h_tm1_i, h_tm1_f, h_tm1_c, h_tm1_o = h_tm1
    i = self.recurrent_activation(
        x_i + K.dot(h_tm1_i, self.recurrent_kernel[:, :self.units]))
    f = self.recurrent_activation(x_f + K.dot(
        h_tm1_f, self.recurrent_kernel[:, self.units:self.units * 2]))
    c = f * c_tm1 + i * self.activation(x_c + K.dot(
        h_tm1_c, self.recurrent_kernel[:, self.units * 2:self.units * 3]))
    o = self.recurrent_activation(
        x_o + K.dot(h_tm1_o, self.recurrent_kernel[:, self.units * 3:]))
    return c, o

Dans chaque cas, le produit matriciel est calculé en utilisant seulement une partie de recurrent_kernel ''. La partie du produit matriciel est essentiellement $ R_ih_ {t-1}, R_fh_ {t-1}, R_zh_ {t-1}, R_oh_ {t-1} $. x_i, x_f, x_c, x_ocontiendra les valeurs calculées de $ W_ix_t + b_i, W_fx_t + b_f, W_zx_t + b_z, W_ox_t + b_o $ Vous pouvez maintenant calculer le contenu de la fonction d'activation. La fonction d'activation recurrent_activationcorrespond à la fonction sigmoïde dans les équations (2.1) à (2.3), mais la valeur par défaut semble être hard_sigmoid`` [^ 5]. Le reste est tel que défini dans la formule.

[^ 5]: Le hard_sigmoid de Keras est max (0, min (1, (0.2 * x) + 0.5)) --Qiita

Essayez d'assembler RNN vous-même

Sur la base du contenu jusqu'à présent, je voudrais implémenter et essayer la forme dérivée de LSTM proposée dans l'article, etc. par moi-même! Je vais vous présenter un exemple du flux quand j'ai réfléchi.

Je voudrais un exemple simple, donc je vais essayer le LSTM simplifié (S-LSTM) proposé dans Wu (2016) [^ 6].

Tout d'abord, je citerai la formule de l'article original. Cependant, afin de faire correspondre la notation avec d'autres expressions de cette page, la position de l'indice a été modifiée et la version généralisée de $ \ delta, g $ a été changée en $ \ sigma, \ tanh $. Je fais.

\begin{align}
f_t &=\sigma(W_fx_t+R_fh_{t−1}+b_f) \\
c_t &=f_t \otimes c_{t−1}+ (1−f_t) \otimes \tanh (W_c x_t+R_ch_{t−1}+b_c) \\
h_t &=\tanh (c_t)
\end{align}

De quel état s'agit-il?

Tout d'abord, trouvons ce que vous devriez avoir comme état à partir de la formule. Vous devez avoir une variable comme état qui utilise les informations de l'heure précédente, c'est-à-dire qui fait référence à l'indice ** dans $ t-1 $. Par conséquent, cette fois, nous avons $ h_t et c_t $ comme états. ** **

Quel est le poids?

Les poids (paramètres que vous voulez apprendre) sont $ W_f, R_f, b_f, W_c, R_c, b_c $. Par rapport au LSTM ordinaire, le nombre de poids est divisé par deux.

la mise en oeuvre

LSTMCell hérite de Layer, mais lorsque vous le faites vous-même, il semble préférable d'hériter de tf.keras.layers.AbstractRNNCell. tf.keras.layers.AbstractRNNCell | TensorFlow Core v2.1.0

This is the base class for implementing RNN cells with custom behavior.

Pour build () '', cela ressemblerait-il à ceci s'il était modifié en fonction de l'implémentation LSTM? * 4 '' est remplacé par `` * 2 '' pour exclure les pièces qui ne sont pas directement liées à l'abandon.

  def build(self, input_shape):
    input_dim = input_shape[-1]
    self.kernel = self.add_weight(
        shape=(input_dim, self.units * 2),
        name='kernel',
        initializer=self.kernel_initializer,
        regularizer=self.kernel_regularizer,
        constraint=self.kernel_constraint)
    self.recurrent_kernel = self.add_weight(
        shape=(self.units, self.units * 2),
        name='recurrent_kernel',
        initializer=self.recurrent_initializer,
        regularizer=self.recurrent_regularizer,
        constraint=self.recurrent_constraint)

    if self.use_bias:
      self.bias = self.add_weight(
          shape=(self.units * 2,),
          name='bias',
          initializer=self.bias_initializer,
          regularizer=self.bias_regularizer,
          constraint=self.bias_constraint)
    else:
      self.bias = None
    self.built = True

Pour call (), implémentez uniquement le traitement équivalent à ```implementation = 1. Peut-être quelque chose comme ça. Notez que les entrées '' et les états '' sont tf.Tensor '', donc n'utilisez pas de traitement pour ndarray '' tel que np.dot ''. , tf.math, tf.linalg '', tf.keras.backend '', etc. Veuillez utiliser la fonction qui gère `` Tensor ''. Je veux m'habituer à l'objet Tensor de TensorFlow --Qiita

  def call(self, inputs, states, training=None):
    h_tm1 = states[0]  # previous memory state
    c_tm1 = states[1]  # previous carry state

    k_f, k_c = array_ops.split(
          self.kernel, num_or_size_splits=2, axis=1)
    x_f = K.dot(inputs, k_f)
    x_c = K.dot(inputs, k_c)
    if self.use_bias:
      b_f, b_c = array_ops.split(
          self.bias, num_or_size_splits=2, axis=0)
      x_f = K.bias_add(x_f, b_f)
      x_c = K.bias_add(x_c, b_c)

    f = self.recurrent_activation(x_f + K.dot(
        h_tm1, self.recurrent_kernel[:, :self.units]))
    c = f * c_tm1 + (1 - f) * self.activation(x_c + K.dot(
        h_tm1, self.recurrent_kernel[:, self.units:]))

    h = self.activation(c)
    return h, [h, c]

Code entier

slstm.py


import tensorflow as tf 
import numpy as np 
from tensorflow.keras import Sequential 
from tensorflow.keras.layers import RNN, AbstractRNNCell
from tensorflow.keras.optimizers import SGD
from tensorflow.python.keras import activations, constraints, initializers, regularizers
from tensorflow.python.keras import backend as K
from tensorflow.python.keras.utils import tf_utils
from tensorflow.python.ops import array_ops

class SLSTMCell(AbstractRNNCell):
  def __init__(self,
               units,
               activation='tanh',
               recurrent_activation='hard_sigmoid',
               use_bias=True,
               kernel_initializer='glorot_uniform',
               recurrent_initializer='orthogonal',
               bias_initializer='zeros',
               kernel_regularizer=None,
               recurrent_regularizer=None,
               bias_regularizer=None,
               kernel_constraint=None,
               recurrent_constraint=None,
               bias_constraint=None,
               **kwargs):

    super(SLSTMCell, self).__init__(**kwargs)
    self.units = units
    self.activation = activations.get(activation)
    self.recurrent_activation = activations.get(recurrent_activation)
    self.use_bias = use_bias

    self.kernel_initializer = initializers.get(kernel_initializer)
    self.recurrent_initializer = initializers.get(recurrent_initializer)
    self.bias_initializer = initializers.get(bias_initializer)

    self.kernel_regularizer = regularizers.get(kernel_regularizer)
    self.recurrent_regularizer = regularizers.get(recurrent_regularizer)
    self.bias_regularizer = regularizers.get(bias_regularizer)

    self.kernel_constraint = constraints.get(kernel_constraint)
    self.recurrent_constraint = constraints.get(recurrent_constraint)
    self.bias_constraint = constraints.get(bias_constraint)

  @property
  def state_size(self):
    return [self.units, self.units]

  def build(self, input_shape):
    input_dim = input_shape[-1]
    self.kernel = self.add_weight(
        shape=(input_dim, self.units * 2),
        name='kernel',
        initializer=self.kernel_initializer,
        regularizer=self.kernel_regularizer,
        constraint=self.kernel_constraint)
    self.recurrent_kernel = self.add_weight(
        shape=(self.units, self.units * 2),
        name='recurrent_kernel',
        initializer=self.recurrent_initializer,
        regularizer=self.recurrent_regularizer,
        constraint=self.recurrent_constraint)

    if self.use_bias:
      self.bias = self.add_weight(
          shape=(self.units * 2,),
          name='bias',
          initializer=self.bias_initializer,
          regularizer=self.bias_regularizer,
          constraint=self.bias_constraint)
    else:
      self.bias = None
    self.built = True

  def call(self, inputs, states, training=None):
    h_tm1 = states[0]  # previous memory state
    c_tm1 = states[1]  # previous carry state

    k_f, k_c = array_ops.split(
          self.kernel, num_or_size_splits=2, axis=1)
    x_f = K.dot(inputs, k_f)
    x_c = K.dot(inputs, k_c)
    if self.use_bias:
      b_f, b_c = array_ops.split(
          self.bias, num_or_size_splits=2, axis=0)
      x_f = K.bias_add(x_f, b_f)
      x_c = K.bias_add(x_c, b_c)

    f = self.recurrent_activation(x_f + K.dot(
        h_tm1, self.recurrent_kernel[:, :self.units]))
    c = f * c_tm1 + (1 - f) * self.activation(x_c + K.dot(
        h_tm1, self.recurrent_kernel[:, self.units:]))

    h = self.activation(c)
    return h, [h, c]

tf.random.set_seed(111)
np.random.seed(111)

model = Sequential([
    RNN(SLSTMCell(1, activation=None), input_shape=(None, 1), return_sequences=True)
])
model.compile(optimizer=SGD(lr=0.0001), loss="mean_squared_error")

n = 51200
x = np.random.random((n, 30, 1))
y = x.cumsum(axis=1)

model.fit(x, y, batch_size=512, epochs=100)

model.layers[0].weights
# [<tf.Variable 'rnn/kernel:0' shape=(1, 2) dtype=float32, numpy=array([[-0.79614836,  0.03041089]], dtype=float32)>,
#  <tf.Variable 'rnn/recurrent_kernel:0' shape=(1, 2) dtype=float32, numpy=array([[0.08143749, 1.0668359 ]], dtype=float32)>,
#  <tf.Variable 'rnn/bias:0' shape=(2,) dtype=float32, numpy=array([0.6330045, 1.0431471], dtype=float32)>]

model.predict(np.ones((1, 30, 1)) * 0.5).flatten()
# array([ 0.47944844,  0.96489847,  1.4559155 ,  1.9520411 ,  2.4527955 ,
#         2.9576783 ,  3.466171  ,  3.9777386 ,  4.4918313 ,  5.007888  ,
#         5.5253367 ,  6.0435996 ,  6.5620937 ,  7.0802336 ,  7.597435  ,
#         8.113117  ,  8.626705  ,  9.13763   ,  9.645338  , 10.149284  ,
#        10.648943  , 11.143805  , 11.633378  , 12.117197  , 12.594816  ,
#        13.065814  , 13.529797  , 13.986397  , 14.435274  , 14.876117  ],
#       dtype=float32)

Je ne sais pas si la mise en œuvre est correcte, mais cela fonctionne comme ça, alors disons OK.

Quel est l'état initial?

En fait, l'état (état initial) au début de l'entrée est $ h_0 = c_0 = 0 $. Il est défini dans `ʻAbstractRNNCell``.

recurrent.py


  def get_initial_state(self, inputs=None, batch_size=None, dtype=None):
    return _generate_zero_filled_state_for_cell(self, inputs, batch_size, dtype)

C'est bien pour ce problème, mais si vous voulez que l'état initial soit une valeur différente, vous pouvez le changer en remplaçant `` get_initial_state () '' dans la destination d'héritage. Par exemple, si vous voulez commencer par $ h_0 = 1 $, ce sera comme suit.

  def get_initial_state(self, inputs=None, batch_size=None, dtype=None):
    h_0 = tf.ones([batch_size, self.units], dtype)
    c_0 = tf.zeros([batch_size, self.units], dtype)
    return [h_0, c_0]

Essayez de changer le libellé des données d'entraînement afin qu'il soit également "somme partielle +1".

n = 51200
x = np.random.random((n, 30, 1))
y = x.cumsum(axis=1) + 1

model.fit(x, y, batch_size=512, epochs=100)

model.predict(np.ones((1, 30, 1)) * 0.5).flatten()                      
# array([ 1.0134857,  1.5777774,  2.140834 ,  2.702304 ,  3.2618384,
#         3.8190937,  4.3737316,  4.92542  ,  5.473833 ,  6.018653 ,
#         6.5595713,  7.0962873,  7.62851  ,  8.15596  ,  8.678368 ,
#         9.195474 ,  9.707033 , 10.2128105, 10.712584 , 11.206142 ,
#        11.693292 , 12.173846 , 12.647637 , 13.114506 , 13.574308 ,
#        14.026913 , 14.472202 , 14.91007  , 15.3404255, 15.763187 ],
#       dtype=float32)

Il semble y avoir beaucoup d'erreurs, mais j'ai obtenu le résultat d'une manière ou d'une autre.

Résumé

J'ai écrit comment utiliser RNN dans TensorFlow + Keras et comment personnaliser RNN pour le réexamen des articles. Ce n'est pas difficile si vous utilisez simplement RNN et LSTM comme une boîte noire, mais lorsque vous essayez de comprendre le traitement interne, les matériaux de référence (surtout en japonais) sont surprenants. J'espère que cet article abaisse la barre pour la gestion des RNN et des LSTM.

Recommended Posts

[TensorFlow / Keras] La route pour assembler un RNN de votre structure préférée
La route de la compilation vers Python 3 avec Thrift
La voie de la mise à jour de Splunkbase avec votre propre application Splunk compatible avec Python v2 / v3
Tout en conservant la structure du modèle de classification d'image (mnist), attachez un auto-encodeur pour améliorer la précision de bout en bout. [tensorflow, keras, mnist, autoencder]
Identifiez le nom de l'image de la fleur avec des keras (flux tenseur)
[Neo4J] ④ Essayez de gérer la structure du graphe avec Cypher
J'ai essayé d'implémenter Grad-CAM avec keras et tensorflow
J'ai essayé de trouver la classe alternative avec tensorflow
La route vers Pythonista
La route vers Djangoist
2020/02 Python 3.7 + TensorFlow 2.1 + Keras 2.3.1 + YOLOv3 Détection d'objets avec la dernière version
[TensorFlow 2 / Keras] Comment exécuter l'apprentissage avec CTC Loss dans Keras
J'ai essayé de trouver la moyenne de plusieurs colonnes avec TensorFlow
Encouragez votre participation à votre calendrier de l'Avent avec Errbot
Comment manipuler le DOM dans iframe avec Selenium
La route pour télécharger Matplotlib
Essayez TensorFlow MNIST avec RNN
Comment connaître la structure interne d'un objet en Python
Créez un alias pour Route53 vers CloudFront avec l'API AWS
[Python] Explique comment utiliser la fonction format avec un exemple