** 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.
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.
return_state
oder stateful
verwendetDie 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]).
--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.
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.
1 | 2 | 3 | 4 | 5 | 6 | 7 | ... | |
---|---|---|---|---|---|---|---|---|
1 | 3 | 2 | 4 | 1 | 0 | 1 | ||
1 | 4 | 6 | 10 | 11 | 11 | 12 | ||
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.
SimpleRNN(1, activation=None, input_shape=(None, 1), return_sequences=True)
Aktivierung
entspricht $ f $ in Gleichung (1). Diesmal ist es eine gleiche Funktion, also habe ich "Keine" gewählt. Im Fall von Dense
usw. ist der Standard eine gleiche Funktion, aber bitte beachten Sie, dass der Standard im RNN-System tanh
ist.
-- input_shape
hat die Form (None, dimension)
. Das erste "Keine" entspricht der Länge jeder Eingabespalte (es ist "Keine", um Eingaben mit variabler Länge zu akzeptieren). Die zweite ist die Anzahl der Dimensionen von $ x_t $ (diesmal 1).
-- return_sequences = True
gibt an, dass die Ausgabe jedes Mal als Ausgabe der Ebene zurückgegeben werden soll. Mit dieser Spezifikation lautet die Ausgabeform für eine Spalte mit der Länge 30 beispielsweise "(batch_size, 30, 1)". Wenn dies "False" ist, ist die Ausgabe der Ebene nur die Ausgabe des letzten Males (in diesem Fall $ o_ {30} $), und die Form der Ausgabe ist "(batch_size, 1)". Bitte verwenden Sie es entsprechend der Frageneinstellung und dem Format der Trainingsdaten.Einzelheiten finden Sie in der offiziellen Dokumentation. tf.keras.layers.SimpleRNN | TensorFlow Core v2.1.0
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.
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.
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".
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])
\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).
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
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}
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. ** **.
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.
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]
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.
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.
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