La documentation de tensorflow r0.12 pour tf.nn.rnn_cell.LSTMCell décrit cela comme l'init:
tf.nn.rnn_cell.LSTMCell.__call__(inputs, state, scope=None)
où state
est la suivante:
state: si state_is_Tuple est à False, il doit s'agir d'un état tensor, 2D, lot x taille_état. Si state_is_Tuple a la valeur True, il doit s'agir d'un tuple d'état, tous deux bidimensionnels, de tailles de colonne c_state et m_state.
Qu'est-ce qu'un c_state
et un m_state
et comment s'intègrent-ils dans les LSTM? Je ne trouve aucune référence à ces documents dans la documentation.
Je suis tombé sur la même question, voici comment je le comprends! Exemple de LSTM minimaliste:
import tensorflow as tf
sample_input = tf.constant([[1,2,3]],dtype=tf.float32)
LSTM_CELL_SIZE = 2
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(LSTM_CELL_SIZE, state_is_Tuple=True)
state = (tf.zeros([1,LSTM_CELL_SIZE]),)*2
output, state_new = lstm_cell(sample_input, state)
init_op = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init_op)
print sess.run(output)
Notez que state_is_Tuple=True
donc lorsque vous passez state
à cette cell
, il doit être sous la forme Tuple
. c_state
et m_state
sont probablement "Etat de la mémoire" et "Etat de la cellule", bien que je ne sois honnêtement pas sûr, car ces termes ne sont mentionnés que dans la documentation. Dans le code et les articles sur LSTM
- les lettres h
et c
sont couramment utilisées pour désigner "valeur de sortie" et "état de cellule" . http://colah.github.io/posts/2015-08-Understanding- LSTMs/ Ces tenseurs représentent l'état interne combiné de la cellule et doivent être transmis ensemble. L'ancienne façon de le faire était simplement de les concaténer, et une nouvelle façon consiste à utiliser des n-uplets.
VIEILLE VOIE:
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(LSTM_CELL_SIZE, state_is_Tuple=False)
state = tf.zeros([1,LSTM_CELL_SIZE*2])
output, state_new = lstm_cell(sample_input, state)
NOUVELLE FAÇON:
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(LSTM_CELL_SIZE, state_is_Tuple=True)
state = (tf.zeros([1,LSTM_CELL_SIZE]),)*2
output, state_new = lstm_cell(sample_input, state)
Donc, pratiquement tout ce que nous avons fait, la variable state
est remplacée par 1 tenseur de longueur 4
en deux tenseurs de longueur 2
. Le contenu est resté le même. [0,0,0,0]
devient ([0,0],[0,0])
. (Ceci est censé le rendre plus rapide)
Je conviens que la documentation n'est pas claire. En regardant tf.nn.rnn_cell.LSTMCell.__call__
clarifie (j'ai pris le code de TensorFlow 1.0.0):
def __call__(self, inputs, state, scope=None):
"""Run one step of LSTM.
Args:
inputs: input Tensor, 2D, batch x num_units.
state: if `state_is_Tuple` is False, this must be a state Tensor,
`2-D, batch x state_size`. If `state_is_Tuple` is True, this must be a
Tuple of state Tensors, both `2-D`, with column sizes `c_state` and
`m_state`.
scope: VariableScope for the created subgraph; defaults to "lstm_cell".
Returns:
A Tuple containing:
- A `2-D, [batch x output_dim]`, Tensor representing the output of the
LSTM after reading `inputs` when previous state was `state`.
Here output_dim is:
num_proj if num_proj was set,
num_units otherwise.
- Tensor(s) representing the new state of LSTM after reading `inputs` when
the previous state was `state`. Same type and shape(s) as `state`.
Raises:
ValueError: If input size cannot be inferred from inputs via
static shape inference.
"""
num_proj = self._num_units if self._num_proj is None else self._num_proj
if self._state_is_Tuple:
(c_prev, m_prev) = state
else:
c_prev = array_ops.slice(state, [0, 0], [-1, self._num_units])
m_prev = array_ops.slice(state, [0, self._num_units], [-1, num_proj])
dtype = inputs.dtype
input_size = inputs.get_shape().with_rank(2)[1]
if input_size.value is None:
raise ValueError("Could not infer input size from inputs.get_shape()[-1]")
with vs.variable_scope(scope or "lstm_cell",
initializer=self._initializer) as unit_scope:
if self._num_unit_shards is not None:
unit_scope.set_partitioner(
partitioned_variables.fixed_size_partitioner(
self._num_unit_shards))
# i = input_gate, j = new_input, f = forget_gate, o = output_gate
lstm_matrix = _linear([inputs, m_prev], 4 * self._num_units, bias=True,
scope=scope)
i, j, f, o = array_ops.split(
value=lstm_matrix, num_or_size_splits=4, axis=1)
# Diagonal connections
if self._use_peepholes:
with vs.variable_scope(unit_scope) as projection_scope:
if self._num_unit_shards is not None:
projection_scope.set_partitioner(None)
w_f_diag = vs.get_variable(
"w_f_diag", shape=[self._num_units], dtype=dtype)
w_i_diag = vs.get_variable(
"w_i_diag", shape=[self._num_units], dtype=dtype)
w_o_diag = vs.get_variable(
"w_o_diag", shape=[self._num_units], dtype=dtype)
if self._use_peepholes:
c = (sigmoid(f + self._forget_bias + w_f_diag * c_prev) * c_prev +
sigmoid(i + w_i_diag * c_prev) * self._activation(j))
else:
c = (sigmoid(f + self._forget_bias) * c_prev + sigmoid(i) *
self._activation(j))
if self._cell_clip is not None:
# pylint: disable=invalid-unary-operand-type
c = clip_ops.clip_by_value(c, -self._cell_clip, self._cell_clip)
# pylint: enable=invalid-unary-operand-type
if self._use_peepholes:
m = sigmoid(o + w_o_diag * c) * self._activation(c)
else:
m = sigmoid(o) * self._activation(c)
if self._num_proj is not None:
with vs.variable_scope("projection") as proj_scope:
if self._num_proj_shards is not None:
proj_scope.set_partitioner(
partitioned_variables.fixed_size_partitioner(
self._num_proj_shards))
m = _linear(m, self._num_proj, bias=False, scope=scope)
if self._proj_clip is not None:
# pylint: disable=invalid-unary-operand-type
m = clip_ops.clip_by_value(m, -self._proj_clip, self._proj_clip)
# pylint: enable=invalid-unary-operand-type
new_state = (LSTMStateTuple(c, m) if self._state_is_Tuple else
array_ops.concat([c, m], 1))
return m, new_state
Les lignes clés sont:
c = (sigmoid(f + self._forget_bias) * c_prev + sigmoid(i) *
self._activation(j))
et
m = sigmoid(o) * self._activation(c)
et
new_state = (LSTMStateTuple(c, m)
Si vous comparez le code pour calculer c
et m
avec les équations LSTM (voir ci-dessous), vous pouvez voir qu'il correspond à l'état de la cellule (généralement noté c
) et à l'état masqué (généralement noté h
), respectivement:
new_state = (LSTMStateTuple(c, m)
indique que le premier élément de l'état retourné Tuple est c
(état de la cellule a.k.a. c_state
) et que le deuxième élément de l'état renvoyé Tuple est m
(état caché a.k.a. m_state
).
Peut-être que cet extrait du code aidera
def __call__(self, inputs, state, scope=None):
"""Long short-term memory cell (LSTM)."""
with vs.variable_scope(scope or type(self).__name__): # "BasicLSTMCell"
# Parameters of gates are concatenated into one multiply for efficiency.
if self._state_is_Tuple:
c, h = state
else:
c, h = array_ops.split(1, 2, state)
concat = _linear([inputs, h], 4 * self._num_units, True)
# i = input_gate, j = new_input, f = forget_gate, o = output_gate
i, j, f, o = array_ops.split(1, 4, concat)
new_c = (c * sigmoid(f + self._forget_bias) + sigmoid(i) *
self._activation(j))
new_h = self._activation(new_c) * sigmoid(o)
if self._state_is_Tuple:
new_state = LSTMStateTuple(new_c, new_h)
else:
new_state = array_ops.concat(1, [new_c, new_h])
return new_h, new_state
https://github.com/tensorflow/tensorflow/blob/r1.2/tensorflow/python/ops/rnn_cell_impl.py
Ligne # 308 - 314
classe LSTMStateTuple (_LSTMStateTuple): "" "Tuple utilisé par les cellules LSTM pour state_size
, zero_state
et l'état de sortie . Stocke deux éléments: (c, h)
, dans cet ordre . Uniquement utilisé lorsque state_is_Tuple=True
." "