[PYTHON] [TensorFlow / Keras] Der Weg zum Zusammenstellen eines RNN Ihrer Lieblingsstruktur

Einführung

** RNN (Recurrent Neural Network) ist eine Form eines neuronalen Netzwerks, das Zeitreihendaten als Eingabe verwendet und die Ausgabe unter Verwendung des "Status" der vorherigen Zeit zusätzlich zur Eingabe der aktuellen Zeit bestimmt. Ist LSTM (Long Short-Term Memory) berühmt? Zeitreihendaten sind Daten, die für die gesamte Spalte sinnvoll sind, z. B. Video und Text. Ein gewöhnliches neuronales Netzwerk verwendet ein bestimmtes Datenformat wie Bilder und Zeichen als Eingabe, aber beim Umgang mit Videos und Texten, in denen sie angeordnet sind, nicht nur einzelne Bilder (Frames) und Zeichen, sondern auch deren Anordnung Hat auch eine große Bedeutung. Die Struktur, die solche Daten gut verarbeitet, ist RNN.

Ich denke jedoch, dass es schwierig ist, ehrlich zu sein, im Gegensatz zu gewöhnlichen, vollständig verbundenen Schichten. Ich auch.

Zuerst möchte ich verstehen, was die RNN tut, und dann möchte ich meine eigene RNN erstellen können, die den "Status" der vorherigen Zeit verwendet.

Überprüfungsumgebung

Ziel

Ich möchte die folgenden Probleme lösen (weil ich mir tatsächlich Sorgen gemacht habe ...).

[^ basiclstm]: Die Grundlagen von LSTM, die jetzt nicht zu hören sind - HALLO CYBERNETICS

Es wäre schön, wenn Sie Ihre eigene RNN mit Keras erstellen könnten, indem Sie sich die Netzwerkstruktur (Grafik) und Formeln ansehen, wie auf der Referenzseite [^ basiclstm] beschrieben.

Im Gegenteil, die folgenden Dinge werden auf dieser Seite nicht behandelt. Vielleicht schreibe ich bei einer anderen Gelegenheit einen Artikel.

RNN-Grundlagen

Die Basis von RNN ist ** "Die Ausgabe hängt von der Eingabe der aktuellen Zeit und dem" Zustand "der vorherigen Zeit ab" **. Der Unterschied besteht darin, dass in einer normalen vollständig verbundenen Schicht oder Faltungsschicht die Ausgabe nur von der Eingabe abhängt, in RNN jedoch auch die Informationen der vorherigen Eingabe verwendet werden können. Sie können selbst entscheiden, welchen "Zustand" Sie zum nächsten Mal bringen möchten.

Wie in der Abbildung auf der linken Seite unten gezeigt, kann RNN als Netzwerk (Zelle) mit einer rekursiven Struktur ausgedrückt werden. Die spezifische Operation besteht jedoch in der Erweiterung der Schleife, wie in der Abbildung auf der rechten Seite gezeigt. Sie können verstehen (Quelle [^ 1]).

image.png

--Eingabe: $ x_1, x_2, ..., x_t, ... $

Wenn gegeben

Zu

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

Es wird wie folgt bestimmt. Wobei $ U, V, W $ Matrizen und $ b $ Spaltenvektoren sind, die Schichtgewichte sind (zu trainierende Parameter). $ f und h $ sind Aktivierungsfunktionen. Eingabe / Ausgabe und Zustand $ x_t, o_t, s_t $ sind ebenfalls Spaltenvektoren.

Die einfachste RNN

Lassen Sie uns zuerst RNN berühren. Stellen Sie sich als einfaches RNN ein Netzwerk vor, das nacheinander die Teilsumme der Eingangsnummern ausgibt (die Summe aller Werte vom Anfang bis zu diesem Punkt). Zu diesem Zeitpunkt ist die Teilsumme als "Zustand" definiert und der Zustand wird so ausgegeben, wie er ist. Beispielsweise ändern sich die Ausgabe und der Status für die Eingabe wie in der folgenden Tabelle gezeigt.

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

Verwenden Sie in TensorFlow + Keras eine Ebene mit dem Namen "tf.keras.layers.SimpleRNN"

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

Sie können ein Netzwerk des Formulars definieren. Wenn Sie $ f (x) = x $ setzen und eine Folge von Zahlen und deren Teilsummen trainieren,

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

Es wird erwartet, dass sich das Gewicht von $ U = W = 1, ; b = 0 $ nähert (diesmal handelt es sich um eindimensionale Werte, sodass Sie sich $ U, W, b $ als Skalare vorstellen können. Hmm).

Versuchen Sie, mit dem folgenden Code zu lernen. Für das Training wird eine Folge von Zufallszahlen mit einer Länge von 30 und eine daraus berechnete Folge von Teilsummen angegeben.

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)

Der Fehler sieht groß aus, aber da es sich um ein Beispiel handelt, wäre es schön, wenn Sie die Atmosphäre erfassen könnten (die Ausgabegenauigkeit wird hier nicht verfolgt). Hier als Ergebnis des Lernens

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

Wird erhalten.

Erklärung von SimpleRNN

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

Einzelheiten finden Sie in der offiziellen Dokumentation. tf.keras.layers.SimpleRNN | TensorFlow Core v2.1.0

Umschreiben mit RNN

Der Code, der das obige Modell erstellt, entspricht:

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 definiert eine Operation (Zelle) für eine einzelne Probe, und das Einschließen in RNN () definiert eine Schicht zum Verarbeiten des Stapels.

Mit anderen Worten, Sie sollten in der Lage sein, eine RNN Ihrer bevorzugten Struktur zu definieren, indem Sie Ihre eigene stichprobenbasierte Verarbeitung definieren, die "SimpleRNNCell" entspricht, und diese in "RNN ()" einschließen.

Bisher haben wir den zu Beginn erwähnten "Unterschied zwischen" RNN "und" SimpleRNN "und" SimpleRNNCell "verstanden.

Werfen wir einen Blick auf den Inhalt von SimpleRNNCell

Lassen Sie uns zur Vorbereitung der Erstellung Ihrer eigenen RNN zunächst sehen, was die vorhandene "SimpleRNNCell" tut. Wenn Sie selbst schreiben, sollte es eine Abkürzung sein, um den vorhandenen Prozess zuerst nachzuahmen.

(Ich denke auch, dass Sie sich auf das Beispiel von [tf.keras.layers.RNN | TensorFlow Core v2.1.0] beziehen können (https://www.tensorflow.org/api_docs/python/tf/keras/layers/RNN))

Der Quellcode für SimpleRNNCell ist unten zu finden. tensorflow/recurrent.py at v2.1.0 · tensorflow/tensorflow · GitHub

Werfen wir einen Blick auf den Inhalt, indem wir einen Auszug daraus ziehen.

Erben Sie zuerst Layer in der Klassendefinition. DropoutRNNCellMixin scheint vererbt zu sein, um Dropout zu unterstützen, aber es kommt nicht in Frage und wird hier nicht erwähnt.

recurrent.py


class SimpleRNNCell(DropoutRNNCellMixin, Layer):

In "build ()" wird das für die Ebene erforderliche Gewicht durch "add_weight" definiert. Nicht nur RNN, sondern auch Dense () usw. machen dasselbe. Entsprechend Gleichung (1) ist "Kernel" $ U $, "wiederkehrender Kernel" ist $ W $ und "Bias" ist $ b $.

Verwenden Sie dann call (), um die eigentliche Verarbeitung zu definieren. Das ist das Wichtigste.

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]

Die Eingabe $ x_t $ wird in Eingaben eingegeben, und der Status $ s_ {t-1} $ (zum vorherigen Zeitpunkt generiert) wird in Zustände eingegeben. States wird als Liste jeder Variablen übergeben, so dass sie mehrere Zustände haben kann. Daher werden zuerst nur die Zustände [0] extrahiert. Lassen wir die Dropout-bezogene Verarbeitung beiseite und versuchen, den Hauptteil zu extrahieren

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]

Das ist es. In TensorFlow wird jede Eingabe- / Ausgabestichprobe durch einen Zeilenvektor dargestellt, sodass die Reihenfolge des Matrixprodukts umgekehrt wird. Sie können jedoch sehen, dass sie Gleichung (1) entsprechen kann. Die endgültige return gibt die Layer-Ausgabe $ o_t $ und den Status $ s_t $ zurück, den Sie zum nächsten Mal bringen möchten. Der hier übergebene Status kann beim nächsten Mal von call () empfangen werden. Der Status wird als Liste sowie als Argument zurückgegeben. Wenn Sie hier mehrere Status zurückgeben, erhalten Sie beim nächsten Mal mehrere Status.

Werfen wir einen Blick auf LSTM

Als nächstes betrachten wir die LSTM-Ebene als etwas komplizierteres Beispiel. Versuchen Sie zunächst, LSTM anstelle von SimpleRNN mit den gleichen Problemeinstellungen wie zuvor zu verwenden.

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)

Wenn Sie es nur verwenden möchten, habe ich es im vorherigen Artikel versucht (die Problemeinstellung ist dieselbe ...). Versuchen Sie return_sequences = True auf Keras 'RNN (LSTM) -Qiita Lassen Sie uns diesmal etwas tiefer in den Implementierungsteil eintauchen.

Ähnlich wie "SimpleRNN" kann dies auch in Zellen von "RNN" getrennt werden, um eine äquivalente Verarbeitung zu erreichen [^ 2].

[^ 2]: Tatsächlich bietet die Verwendung von LSTM (manchmal) eine schnelle CuDNN-Implementierung. Wenn Sie also nur LSTM verwenden möchten, macht es keinen Sinn, es zu trennen.

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

Danach konzentrieren wir uns auf die Verarbeitung des Zellteils "LSTMCell".

LSTM-Formel

Bevor wir uns die Implementierung ansehen, überprüfen wir zunächst die Verarbeitung von LSTM. Die theoretische Bedeutung jedes Tores wird hier nicht erwähnt. (Formeln und Zahlen stammen aus [^ 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}

$ \ Otimes $ ist jedoch das Produkt jedes Elements, $ \ sigma $ ist die Sigmoidfunktion und $ \ tanh $ ist die Zweikurventangente (hyperbolische Tangente).

Dekodierung von LSTMCell

Schauen wir uns die Implementierung von LSTMCell basierend auf den Gleichungen (2.1) bis (2.6) an. tensorflow/recurrent.py at v2.1.0 · tensorflow/tensorflow · GitHub

Das Exportieren einer Klasse entspricht "SimpleRNN".

recurrent.py


class LSTMCell(DropoutRNNCellMixin, Layer):

Das Gewicht wird durch build () definiert.

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

Abgesehen von den Details hier beachten Sie bitte, dass es mehrere Beschreibungen von self.units * 4 gibt. Tatsächlich enthält "Kernel" eine Verkettung von vier Matrizen $ W_o, W_f, W_i, W_z $ [^ 4]. In ähnlicher Weise hat "wiederkehrender Kernel" vier von $ R_o, R_f, R_i, R_z $ zusammen und "Bias" hat vier von "b_o, b_f, b_i, b_z". Ich habe sie alle zusammen. Natürlich ist es kein Fehler, jede von ihnen in 4 Variablen zu halten (insgesamt 12). Wie üblich sind die Zeilen und Spalten im Vergleich zum Schreiben in einer Formel umgekehrt. Wenn man sich also die Formel ansieht, scheint sich die Anzahl der Zeilen zu vervierfachen, aber im Code wird die Anzahl der Spalten vervierfacht.

[^ 4]: Zusätzlich zum Effekt der Reduzierung der Anzahl von Variablen besteht der Vorteil, dass der Inhalt der Aktivierungsfunktionen in den Gleichungen (2.1) bis (2.4) zusammen berechnet werden kann. Wenn Sie beim Erstellen von LSTMCell () Implementation = 2 angeben, wird anscheinend die Implementierung verwendet, die alle zusammen berechnet.

call () ist der Hauptteil. Der Einfachheit halber wird nur die Verarbeitung für "Implementierung = 1" gezeigt.

recurrent.py


  def call(self, inputs, states, training=None):
    h_tm1 = states[0]  # previous memory state
    c_tm1 = states[1]  # previous carry state
    (Abkürzung)
      if 0 < self.dropout < 1.:
        (Abkürzung)
      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.:
        (Abkürzung)
      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)
    (Abkürzung)
    h = o * self.activation(c)
    return h, [h, c]

In den Gleichungen (2.1) bis (2.6) werden $ h_ {t-1} und c_ {t-1} $ als Informationen zur vorherigen Zeit verwendet. Daher müssen beide als Staat haben. ** Wie oben erwähnt, können Sie mehr als einen Status verarbeiten, indem Sie die Status in einer Liste übergeben.

In der ersten Hälfte berechnen wir vier Werte: $ W_ox_t + b_o, W_fx_t + b_f, W_ix_t + b_i, W_zx_t + b_z $. (Beachten Sie, dass sich $ W_z und b_z $ in Formel (2.4) von k_c und b_c im Code unterscheiden.) In der zweiten Hälfte verwenden wir _compute_carry_and_output (), um die Werte für $ c_t und o_t $ zu berechnen. Berechnen Sie schließlich $ h_t $. Hier wird $ h_t $ so ausgegeben, wie es ist, und $ h_t und c_t $ werden als Zustände zur Verwendung in der Berechnung beim nächsten Mal zurückgegeben. Der Standardwert für "Aktivierung" ist "tanh", wie in Gleichung (2.6).

_compute_carry_and_output () ist wie folgt definiert.

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

In jedem Fall wird das Matrixprodukt nur mit einem Teil von "wiederkehrendem Kernel" berechnet. Der Teil des Matrixprodukts ist im Grunde $ R_ih_ {t-1}, R_fh_ {t-1}, R_zh_ {t-1}, R_oh_ {t-1} $. x_i, x_f, x_c, x_o enthält die berechneten Werte von $ W_ix_t + b_i, W_fx_t + b_f, W_zx_t + b_z, W_ox_t + b_o $ Jetzt können Sie den Inhalt der Aktivierungsfunktion berechnen. Die Aktivierungsfunktion Wiederkehrende_Aktivierung entspricht der Sigmoidfunktion in den Gleichungen (2.1) bis (2.3), aber der Standardwert scheint hard_sigmoid [^ 5] zu sein. Der Rest ist wie in der Formel definiert.

[^ 5]: Keras 'hard_sigmoid ist max (0, min (1, (0,2 * x) + 0,5)) - Qiita

Versuchen Sie, RNN selbst zusammenzubauen

Aufgrund der bisherigen Inhalte möchte ich die im Papier vorgeschlagene abgeleitete Form von LSTM usw. selbst implementieren und ausprobieren! Ich werde ein Beispiel für den Fluss vorstellen, wenn ich darüber nachdachte.

Ich möchte ein einfaches Beispiel, also werde ich das in Wu (2016) [^ 6] vorgeschlagene vereinfachte LSTM (S-LSTM) ausprobieren.

Zunächst zitiere ich die Formel des Originalpapiers. Um die Notation mit anderen Ausdrücken auf dieser Seite abzugleichen, wurde die Position des Index geändert und die verallgemeinerte Version von $ \ delta, g $ wurde in $ \ sigma, \ tanh $ geändert. Ich mache.

\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}

Welcher Zustand ist das?

Lassen Sie uns zunächst anhand der Formel herausfinden, was Sie als Status haben sollten. Sie müssen eine Variable als Status haben, die die Informationen aus der vorherigen Zeit verwendet, dh auf den ** Index in $ t-1 $ verweist. Daher haben wir diesmal $ h_t und c_t $ als Zustände. ** **.

Welches ist das Gewicht?

Die Gewichte (Parameter, die Sie lernen möchten) sind $ W_f, R_f, b_f, W_c, R_c, b_c $. Im Vergleich zu gewöhnlichem LSTM halbiert sich die Anzahl der Gewichte.

Implementierung

LSTMCell erbt Layer, aber wenn Sie es selbst machen, scheint es besser zu sein, tf.keras.layers.AbstractRNNCell zu erben. tf.keras.layers.AbstractRNNCell | TensorFlow Core v2.1.0

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

Würde es in Bezug auf "build ()" so aussehen, wenn es basierend auf der Implementierung von LSTM geändert würde? * 4 wird in * 2 geändert, um Teile auszuschließen, die nicht direkt mit Dropout zusammenhängen.

  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

Implementieren Sie für call () nur die Verarbeitung, die Implementation = 1 entspricht. Vielleicht so etwas. Beachten Sie, dass Eingaben und Zustände tf.Tensor sind. Verwenden Sie daher keine Verarbeitung für ndarray wie np.dot. , tf.math, tf.linalg, tf.keras.backend usw. Bitte verwenden Sie die Funktion, die Tensor behandelt. Ich möchte mich an das Tensor-Objekt von TensorFlow - Qiita gewöhnen

  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]

Ganzer Code

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)

Ich bin mir nicht sicher, ob die Implementierung korrekt ist, aber es funktioniert so. Sagen wir also OK.

Wie ist der Ausgangszustand?

Tatsächlich ist der Zustand (Anfangszustand) am Anfang der Eingabe $ h_0 = c_0 = 0 $. Es ist in "AbstractRNNCell" definiert.

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)

Das ist in Ordnung für dieses Problem, aber wenn Sie möchten, dass der Anfangszustand ein anderer Wert ist, können Sie ihn ändern, indem Sie get_initial_state () im Vererbungsziel überschreiben. Wenn Sie beispielsweise mit $ h_0 = 1 $ beginnen möchten, ist dies wie folgt.

  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]

Versuchen Sie, die Bezeichnung der Trainingsdaten so zu ändern, dass sie auch "Teilsumme +1" ist.

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)

Es scheint eine Menge Fehler zu geben, aber irgendwie habe ich das Ergebnis bekommen.

Zusammenfassung

Ich habe geschrieben, wie man RNN in TensorFlow + Keras verwendet und wie man RNN für die Überprüfung von Papieren anpasst. Es ist nicht schwierig, wenn Sie nur RNN und LSTM als Black Box verwenden, aber wenn Sie versuchen, die interne Verarbeitung zu verstehen, sind die Referenzmaterialien (insbesondere auf Japanisch) überraschend. Ich hoffe, dieser Artikel senkt die Messlatte für den Umgang mit RNNs und LSTMs.

Recommended Posts

[TensorFlow / Keras] Der Weg zum Zusammenstellen eines RNN Ihrer Lieblingsstruktur
Der Weg zum Kompilieren zu Python 3 mit Thrift
Der Weg zur Aktualisierung von Splunkbase mit Ihrer eigenen Splunk-App, die mit Python v2 / v3 kompatibel ist
Fügen Sie unter Beibehaltung der Modellstruktur der Bildklassifizierung (mnist) einen Autoencoder hinzu, um die Genauigkeit von Ende zu Ende zu verbessern. [Tensorflow, Keras, Mnist, Autoencder]
Identifizieren Sie den Namen aus dem Blumenbild mit Keras (Tensorfluss)
[Neo4J] ④ Versuchen Sie, die Diagrammstruktur mit Cypher zu handhaben
Ich habe versucht, Grad-CAM mit Keras und Tensorflow zu implementieren
Ich habe versucht, die alternative Klasse mit Tensorflow zu finden
Der Weg nach Pythonista
Der Weg nach Djangoist
2020/02 Python 3.7 + TensorFlow 2.1 + Keras 2.3.1 + YOLOv3 Objekterkennung mit der neuesten Version
[TensorFlow 2 / Keras] Ausführen des Lernens mit CTC Loss in Keras
Ich habe versucht, mit TensorFlow den Durchschnitt mehrerer Spalten zu ermitteln
Verwenden Sie Errbot, um die Teilnahme am Adventskalender Ihres Unternehmens zu fördern
So manipulieren Sie das DOM im Iframe mit Selen
Der Weg zum Herunterladen von Matplotlib
Versuchen Sie TensorFlow MNIST mit RNN
Wie Sie die interne Struktur eines Objekts in Python kennen
Erstellen Sie mit der AWS-API einen Alias für Route53 zu CloudFront
[Python] Erklärt anhand eines Beispiels, wie die Formatierungsfunktion verwendet wird