【问题标题】:Cascade multiple RNN models for N-dimensional output级联多个RNN模型用于N维输出
【发布时间】:2020-07-29 15:45:55
【问题描述】:

我在以不寻常的方式将两个模型链接在一起时遇到了一些困难。

我正在尝试复制以下流程图:

为了清楚起见,在Model[0] 的每个时间步,我尝试使用Model[1]IR[i](中间表示)生成整个时间序列作为重复输入。该方案的目的是允许从一维输入生成参差不齐的二维时间序列(同时允许在不需要该时间步的输出时省略第二个模型,并且不需要Model[0]在接受输入和生成输出之间不断“切换模式”)。

我假设需要一个自定义训练循环,并且我已经有一个自定义训练循环来处理第一个模型中的状态(以前的版本在每个时间步只有一个输出)。如图所示,第二个模型应该有相当短的输出(能够被限制在少于 10 个时间步长)。

但归根结底,虽然我可以围绕自己想要做的事情展开思考,但我对 Keras 和/或 Tensorflow 的实际实施还不够熟练。 (事实上​​,这是我第一个使用该库的非玩具项目。)

我没有成功地在文献中搜索类似鹦鹉的方案,或摆弄的示例代码。而且我什至不知道这个想法是否可以在 TF/Keras 中实现。

我已经让这两个模型独立工作。 (正如我已经计算出维度,并使用虚拟数据进行了一些训练以获得第二个模型的垃圾输出,第一个模型基于该问题的先前迭代并且已经完全训练。)如果我将Model[0]Model[1] 作为python 变量(我们称它们为model_amodel_b),那么我将如何将它们链接在一起来做到这一点?

编辑添加:

如果这一切都不清楚,也许每个输入和输出的维度会有所帮助:

每个输入和输出的维度是:

输入:(batch_size, model_a_timesteps, input_size)
红外:(batch_size, model_a_timesteps, ir_size)

IR[i](复制后):(batch_size, model_b_timesteps, ir_size)
出[i]:(batch_size, model_b_timesteps, output_size)
出:(batch_size, model_a_timesteps, model_b_timesteps, output_size)

【问题讨论】:

  • 根据您要实现的目标,解决方案要么简单,要么相当复杂;期望的 gradient 行为是什么?即,水平箭头是否表示有状态梯度?如果是这样,那么您必须通过几个完整的前向传播进行反向传播,这超出了一个问题的范围。
  • 期望的行为是Out[n][i] 依赖于IR[n]Model[1] 上一次迭代的隐藏状态。依此类推,就像一个普通的 RNN。 IR[n] 又依赖于Input[n]H[n],就像一个普通的RNN。因此,对任何特定输出有贡献的总梯度基本上是输出(逻辑)左侧和(逻辑)输出下方的所有内容。话虽这么说,虽然Model[0] 是有状态的,但对于我的特定用例而言,没有必要在特定时间批次的开始之后进行反向传播以获得良好的结果。 (H[0] 可以视为不可变输入)
  • 那么对于H[4],你会通过H[3], H[2], H[1], H[0] 反向传播吗?对于Out[2][2],通过Out[2][1], Out[2][0]?然后你寻找批次到批次的反向传播,这不是在 tf.keras 中原生实现的; RNN 的stateful 不会在批次之间反向传播,并且学会使用各种初始化而不是真正更长的序列。如果这种 RNN 行为是您所寻求的,那么任务很简单 - 否则,相当复杂,但可以通过调用 TF ops 在 tf.keras 中实现。
  • 我不确定我是否完全理解你所说的界限。作为一个具体的例子:Out[1][1](即停止)取决于IR[1]和上一次RNN迭代的隐藏状态(取决于IR[1]),而IR[1]又取决于@987654352 @、H[0]Input[1]Input[0]。如果可能的话,我希望从Out[1][1] 一直反向传播到输入。否则,我看不到训练两个网络的方法(因为我对 IR 可以是什么没有限制)。
  • 所以所有Input 都是单列步骤,对吗? IE。在消耗 Input[-1] 之前,权重不会更新。

标签: python tensorflow keras recurrent-neural-network tf.keras


【解决方案1】:

由于这个问题包含多个主要部分,因此我专门针对核心挑战进行了问答:stateful backpropagation。这个答案侧重于实现可变输出步长。


说明

  • 正如案例 5 中所验证的,我们可以采用自下而上的优先方法。首先,我们将完整的输入提供给 model_a (A) - 然后,将其输出作为输入提供给 model_b (B),但这一次一步一步
  • 请注意,我们必须将 B 的输出步骤 每个 A 的输入步骤链接,而不是 A 的输入步骤之间;即,在您的图表中,梯度是在Out[0][1]Out[0][0] 之间流动,而不是在Out[2][0]Out[0][1] 之间流动。
  • 对于计算损失,我们使用不规则张量还是填充张量无关紧要;然而,我们必须使用填充张量来写入 TensorArray。
  • 下面代码中的循环逻辑是通用的;然而,为了简单起见,特定的属性处理和隐藏状态传递是硬编码的,但为了通用性可以重写。

代码:在底部。


示例

  • 这里我们预定义了每个来自 A 的输入的 B 的迭代次数,但我们可以实现任意停止逻辑。例如,我们可以将 B 的 Dense 层的输出作为隐藏状态,并检查其 L2 范数是否超过阈值。
  • 如上所述,如果我们不知道longest_step,我们可以简单地设置它,这对于 NLP 和其他带有 STOP 令牌的任务很常见。
    • 或者,我们可以在每个 A 的输入上用dynamic_size=True 分隔TensorArrays;请参阅下面的“不确定性点”。
  • 一个有效的问题是,我们如何知道梯度流动正确?请注意,我们已经在链接的问答中验证了它们的垂直和水平,但它没有涵盖每个输入步骤的多个输出步骤,用于多个输入步骤。见下文。

不确定性点:我不完全确定梯度之间是否相互作用,例如Out[0][1]Out[2][0]。但是,我确实验证了如果我们为每个 A 的输入的 B 的输出分别写入 TensorArrays(案例 2),则梯度 不会 水平流动;对于案例 4 和 5 的重新实现,两个模型的毕业率会有所不同,包括具有完整单一水平通道的较低模型。

因此我们必须写一个统一的TensorArray。对于这样的,因为没有来自例如的操作。 IR[1]Out[0][1],我看不出 TF 会如何追踪它——所以看来我们是安全的。但是请注意,在下面的示例中,使用 steps_at_t=[1]*6 会使两个模型中的梯度流水平方向,因为我们正在写入单个 TensorArray 并传递隐藏状态。

然而,被检查的案例是混淆的,因为 B 在所有步骤中都是有状态的;取消这个要求,我们可能不需要需要为所有Out[0]Out[1] 等写一个统一的TensorArray,但我们仍然必须针对我们知道有效的东西进行测试,这不再是直截了当。


示例[代码]

import numpy as np
import tensorflow as tf

#%%# Make data & models, then fit ###########################################
x0 = y0 = tf.constant(np.random.randn(2, 3, 4))
msn = MultiStatefulNetwork(batch_shape=(2, 3, 4), steps_at_t=[3, 4, 2])

#%%#############################################
with tf.GradientTape(persistent=True) as tape:
    outputs = msn(x0)
    # shape: (3, 4, 2, 4), 0-padded
    # We can pad labels accordingly.
    # Note the (2, 4) model_b's output shape, which is a timestep slice;
    # model_b is a *slice model*. Careful in implementing various logics
    # which are and aren't intended to be stateful.

方法

不是最干净,也不是最优化的代码,但它可以工作;有改进的余地。

更重要的是:我在 Eager 中实现了这一点,但不知道它在 Graph 中将如何工作,并且使其适用于两者可能非常棘手。如果需要,只需在 Graph 中运行并比较“案例”中所做的所有值。

# ideally we won't `import tensorflow` at all; kept for code simplicity
import tensorflow as tf
from tensorflow.python.util import nest
from tensorflow.python.ops import array_ops, tensor_array_ops
from tensorflow.python.framework import ops

from tensorflow.keras.layers import Input, SimpleRNN, SimpleRNNCell
from tensorflow.keras.models import Model

#######################################################################
class MultiStatefulNetwork():
    def __init__(self, batch_shape=(2, 6, 4), steps_at_t=[]):
        self.batch_shape=batch_shape
        self.steps_at_t=steps_at_t

        self.batch_size = batch_shape[0]
        self.units = batch_shape[-1]
        self._build_models()

    def __call__(self, inputs):
        outputs = self._forward_pass_a(inputs)
        outputs = self._forward_pass_b(outputs)
        return outputs

    def _forward_pass_a(self, inputs):
        return self.model_a(inputs, training=True)

    def _forward_pass_b(self, inputs):
        return model_rnn_outer(self.model_b, inputs, self.steps_at_t)

    def _build_models(self):
        ipt = Input(batch_shape=self.batch_shape)
        out = SimpleRNN(self.units, return_sequences=True)(ipt)
        self.model_a = Model(ipt, out)

        ipt  = Input(batch_shape=(self.batch_size, self.units))
        sipt = Input(batch_shape=(self.batch_size, self.units))
        out, state = SimpleRNNCell(4)(ipt, sipt)
        self.model_b = Model([ipt, sipt], [out, state])

        self.model_a.compile('sgd', 'mse')
        self.model_b.compile('sgd', 'mse')


def inner_pass(model, inputs, states):
    return model_rnn(model, inputs, states)


def model_rnn_outer(model, inputs, steps_at_t=[2, 2, 4, 3]):
    def outer_step_function(inputs, states):
        x, steps = inputs
        x = array_ops.expand_dims(x, 0)
        x = array_ops.tile(x, [steps, *[1] * (x.ndim - 1)])  # repeat steps times
        output, new_states = inner_pass(model, x, states)
        return output, new_states

    (outer_steps, steps_at_t, longest_step, outer_t, initial_states,
     output_ta, input_ta) = _process_args_outer(model, inputs, steps_at_t)

    def _outer_step(outer_t, output_ta_t, *states):
        current_input = [input_ta.read(outer_t), steps_at_t.read(outer_t)]
        output, new_states = outer_step_function(current_input, tuple(states))

        # pad if shorter than longest_step.
        # model_b may output twice, but longest in `steps_at_t` is 4; then we need
        # output.shape == (2, *model_b.output_shape) -> (4, *...)
        # checking directly on `output` is more reliable than from `steps_at_t`
        output = tf.cond(
            tf.math.less(output.shape[0], longest_step),
            lambda: tf.pad(output, [[0, longest_step - output.shape[0]],
                                    *[[0, 0]] * (output.ndim - 1)]),
            lambda: output)

        output_ta_t = output_ta_t.write(outer_t, output)
        return (outer_t + 1, output_ta_t) + tuple(new_states)

    final_outputs = tf.while_loop(
        body=_outer_step,
        loop_vars=(outer_t, output_ta) + initial_states,
        cond=lambda outer_t, *_: tf.math.less(outer_t, outer_steps))

    output_ta = final_outputs[1]
    outputs = output_ta.stack()
    return outputs


def _process_args_outer(model, inputs, steps_at_t):
    def swap_batch_timestep(input_t):
        # Swap the batch and timestep dim for the incoming tensor.
        # (samples, timesteps, channels) -> (timesteps, samples, channels)
        # iterating dim0 to feed (samples, channels) slices expected by RNN
        axes = list(range(len(input_t.shape)))
        axes[0], axes[1] = 1, 0
        return array_ops.transpose(input_t, axes)

    inputs = nest.map_structure(swap_batch_timestep, inputs)

    assert inputs.shape[0] == len(steps_at_t)
    outer_steps = array_ops.shape(inputs)[0]  # model_a_steps
    longest_step = max(steps_at_t)
    steps_at_t = tensor_array_ops.TensorArray(
        dtype=tf.int32, size=len(steps_at_t)).unstack(steps_at_t)

    # assume single-input network, excluding states which are handled separately
    input_ta = tensor_array_ops.TensorArray(
        dtype=inputs.dtype,
        size=outer_steps,
        element_shape=tf.TensorShape(model.input_shape[0]),
        tensor_array_name='outer_input_ta_0').unstack(inputs)

    # TensorArray is used to write outputs at every timestep, but does not
    # support RaggedTensor; thus we must make TensorArray such that column length
    # is that of the longest outer step, # and pad model_b's outputs accordingly
    element_shape = tf.TensorShape((longest_step, *model.output_shape[0]))

    # overall shape: (outer_steps, longest_step, *model_b.output_shape)
    # for every input / at each step we write in dim0 (outer_steps)
    output_ta = tensor_array_ops.TensorArray(
        dtype=model.output[0].dtype,
        size=outer_steps,
        element_shape=element_shape,
        tensor_array_name='outer_output_ta_0')

    outer_t = tf.constant(0, dtype='int32')
    initial_states = (tf.zeros(model.input_shape[0], dtype='float32'),)

    return (outer_steps, steps_at_t, longest_step, outer_t, initial_states,
            output_ta, input_ta)


def model_rnn(model, inputs, states):
    def step_function(inputs, states):
        output, new_states = model([inputs, *states], training=True)
        return output, new_states

    initial_states = states
    input_ta, output_ta, time, time_steps_t = _process_args(model, inputs)

    def _step(time, output_ta_t, *states):
        current_input = input_ta.read(time)
        output, new_states = step_function(current_input, tuple(states))

        flat_state = nest.flatten(states)
        flat_new_state = nest.flatten(new_states)
        for state, new_state in zip(flat_state, flat_new_state):
            if isinstance(new_state, ops.Tensor):
                new_state.set_shape(state.shape)

        output_ta_t = output_ta_t.write(time, output)
        new_states = nest.pack_sequence_as(initial_states, flat_new_state)
        return (time + 1, output_ta_t) + tuple(new_states)

    final_outputs = tf.while_loop(
        body=_step,
        loop_vars=(time, output_ta) + tuple(initial_states),
        cond=lambda time, *_: tf.math.less(time, time_steps_t))

    new_states = final_outputs[2:]
    output_ta = final_outputs[1]
    outputs = output_ta.stack()
    return outputs, new_states


def _process_args(model, inputs):
    time_steps_t = tf.constant(inputs.shape[0], dtype='int32')

    # assume single-input network (excluding states)
    input_ta = tensor_array_ops.TensorArray(
        dtype=inputs.dtype,
        size=time_steps_t,
        tensor_array_name='input_ta_0').unstack(inputs)

    # assume single-output network (excluding states)
    output_ta = tensor_array_ops.TensorArray(
        dtype=model.output[0].dtype,
        size=time_steps_t,
        element_shape=tf.TensorShape(model.output_shape[0]),
        tensor_array_name='output_ta_0')

    time = tf.constant(0, dtype='int32', name='time')
    return input_ta, output_ta, time, time_steps_t

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-10
    • 1970-01-01
    • 2010-10-16
    • 1970-01-01
    • 2019-07-23
    • 2019-06-14
    • 2014-09-28
    • 2018-08-08
    相关资源
    最近更新 更多