【问题标题】:CNTK: How do I initialize LSTM hidden state?CNTK:如何初始化 LSTM 隐藏状态?
【发布时间】:2017-12-07 06:20:11
【问题描述】:

我正在尝试将工作图像字幕 CNN-LSTM 网络从 TensorFlow 转换为 CNTK,并拥有我认为是经过正确训练的模型,但在弄清楚如何从最终训练的 CNTK 模型中提取预测时遇到了麻烦。

这是我正在使用的一般架构: 这是我的 CNTK 模型:

def create_lstm_model(image_features, text_features):
    embedding_dim = 512
    hidden_dim = 512
    cell_dim = 512
    vocab_dim = 77

    image_embedding = Embedding(embedding_dim)
    text_embedding = Embedding(embedding_dim)
    lstm_classifier = Sequential([Stabilizer(),
                                  Recurrence(LSTM(hidden_dim)),
                                  Recurrence(LSTM(hidden_dim)),
                                  Stabilizer(),
                                  Dense(vocab_dim)]) 

    embedded_images = BatchNormalization()(image_embedding(image_features))
    embedded_text = text_embedding(text_features)
    lstm_input = C.plus(embedded_images, embedded_text)
    lstm_input = C.dropout(lstm_input, 0.5)
    output = lstm_classifier(lstm_input)    

    return output

我以 CTF 格式提供我的数据,固定字幕序列大小为 40,使用以下结构:

def create_reader(path, is_training):
    return MinibatchSource(CTFDeserializer(path, StreamDefs(
        target_tokens = StreamDef(field='target_tokens', shape=vocab_len, is_sparse=True),
        input_tokens = StreamDef(field='input_tokens', shape=vocab_len, is_sparse=True),
        image_features = StreamDef(field='image_features', shape=image_features_dim, is_sparse=False)
    )), randomize = is_training, max_sweeps = INFINITELY_REPEAT if is_training else 1)

除此之外:三个数据流的原因 - 我有一个输入图像特征向量(预训练的 ResNet 的最后一个 2048-dim 层)、一个输入文本标记序列和一个输出文本标记序列。所以基本上我的 CTF 文件,就序列而言,看起来像:

0 | target_token_0  | input_token_0 | input_image_feature_vector (2048-dim)
0 | target_token_1  | input_token_1 | empty array of 2048 zeros
0 | target_token_2  | input_token_2 | empty array of 2048 zeros
...
0 | target_token_40 | input_token_40 | empty array of 2048 zeros
1 | target_token_0  | input_token_0 | input_image_feature_vector (2048-dim)
1 | target_token_1  | input_token_1 | empty array of 2048 zeros
1 | target_token_2  | input_token_2 | empty array of 2048 zeros
...
1 | target_token_40 | input_token_40 | empty array of 2048 zeros

基本上,我不知道如何在 CNTK 中将两个序列切片和拼接在一起(即使您可以轻松拼接两个张量),所以我通过仅提供序列中的第一个元素来绕过它输入 2048-dim 图像特征向量,以及序列中的剩余元素,其中包含一个空的 2048-dim 零向量 - 设置用于:

C.plus(embedded_images, embedded_text)

在上面的模型中 - 目标本质上是采用 40 个[2048]->[512] 图像嵌入序列的第一个元素,并在最后 39 个元素之前 hack-splice(TM) 40 个[vocab_dim]->[512] 词嵌入的序列。我指望为空图像向量(2048 个零)学习相当空的[2048]->[512] 图像嵌入,所以我将我的嵌入图像序列和元素方式添加到我的嵌入文本序列中,然后再进入 LSTM .基本上是这样的:

image embedding sequence: [-1, 40, 512]  (e.g., [-1, 0, 512])
text embedding sequence:  [-1, 40, 512]  (e.g., [-1, 1:40, 512)
+
---------------------------------------
lstm input sequence:      [-1, 40, 512]

这让我想到了我的实际问题。现在我有一个训练得很好的模型,我想从模型中提取字幕预测,基本上是做这样的事情(来自PyTorch image captioning tutorial):

def sample(self, features, states=None):
    """Samples captions for given image features (Greedy search)."""
    sampled_ids = []
    inputs = features.unsqueeze(1)
    for i in range(20):                                      # maximum sampling length
        hiddens, states = self.lstm(inputs, states)          # (batch_size, 1, hidden_size), 
        outputs = self.linear(hiddens.squeeze(1))            # (batch_size, vocab_size)
        predicted = outputs.max(1)[1]
        sampled_ids.append(predicted)
        inputs = self.embed(predicted)
        inputs = inputs.unsqueeze(1)                         # (batch_size, 1, embed_size)
    sampled_ids = torch.cat(sampled_ids, 1)                  # (batch_size, 20)
    return sampled_ids.squeeze()

问题是,我无法找出 CNTK 等效项,用于从 LSTM 中获取隐藏状态并在下一个时间步将其抽回:

hiddens, states = self.lstm(inputs, states)

这在 CNTK 中是如何工作的?

【问题讨论】:

    标签: cntk


    【解决方案1】:

    我认为您正在寻找的功能是RecurrenceFrom()。其文档包含以下示例:

    Example:
     >>> from cntk.layers import *
     >>> from cntk.layers.typing import *
    
     >>> # a plain sequence-to-sequence model in training (where label length is known)
     >>> en = C.input_variable(**SequenceOver[Axis('m')][SparseTensor[20000]])  # English input sentence
     >>> fr = C.input_variable(**SequenceOver[Axis('n')][SparseTensor[30000]])  # French target sentence
    
     >>> embed = Embedding(300)
     >>> encoder = Recurrence(LSTM(500), return_full_state=True)
     >>> decoder = RecurrenceFrom(LSTM(500))       # decoder starts from a data-dependent initial state, hence -From()
     >>> emit = Dense(30000)
     >>> h, c = encoder(embed(en)).outputs         # LSTM encoder has two outputs (h, c)
     >>> z = emit(decoder(h, c, sequence.past_value(fr)))   # decoder takes encoder outputs as initial state
     >>> loss = C.cross_entropy_with_softmax(z, fr)
    

    【讨论】:

      【解决方案2】:

      我最近遇到了同样的问题。下面我提供了一个示例代码,用于了解如何使用RecurrenceRNNStep 对象来获取预测。请注意,预测只是下一个期间的状态,因为它没有以任何方式转换。我使用了RNNStep,因为它包含的参数更少,可以轻松构建一个简单的示例。同样的逻辑也适用于 LSTM。

      示例的第一部分构建了一个白噪声进程(可以是任何进程):

      process_dim = 1
      n_periods = 100
      np.random.seed(0)
      x_data = np.random.multivariate_normal(mean=np.array([0.0], dtype=np.float32),
                                             cov=np.matrix([[0.1]], dtype=np.float32),
                                             size=n_periods).astype(np.float32)
      

      然后我构建 RNN 并以易于理解其内部计算的方式设置其参数。

      x = cntk.sequence.input_variable(shape=process_dim)
      initial_state = 10
      W = 1
      H = 1
      b = 1
      with cntk.layers.default_options(initial_state=initial_state):
          model = cntk.layers.Recurrence(cntk.layers.RNNStep(shape=process_dim,
                                                             activation=lambda x_: x_,
                                                             init=W,
                                                             init_bias=b))(x)
      

      注意 1:参数 init 同时设置 H 和 W。 注意 2:如果您不使用 with 语句,则初始状态设置为 0 也可以。

      让我们先来看看如何提前一步评估RNN,即结合上一周期状态(在我们的例子中为初始状态)和当前周期外部输入(x_data的条目)来获得下一个状态。下面的脚本通过在一些给定的输入上调用model.eval() 来实现这一点,然后手动进行实际的 RNN 计算以验证输出。

      x0 = np.array(x_data[0]).reshape((1, 1))
      x1 = np.array(x_data[1]).reshape((1, 1))
      x2 = np.array(x_data[2]).reshape((1, 1))
      
      h00 = model.eval({x: x0})
      h01 = model.eval({x: x1})
      h02 = model.eval({x: x2})
      
      print("x0: {}, h00: {}".format(x0, h00))
      print("x1: {}, h01: {}".format(x1, h01))
      print("x2: {}, h02: {}".format(x2, h02))
      
      # validation
      h00_m = initial_state * H + x0[0, 0] * W + b
      h01_m = initial_state * H + x1[0, 0] * W + b
      h02_m = initial_state * H + x2[0, 0] * W + b
      
      print("validation of state computed manually")
      print("x0: {}, h00_m: {}".format(x0[0, 0], h00_m))
      print("x1: {}, h01_m: {}".format(x1[0, 0], h01_m))
      print("x2: {}, h02_m: {}".format(x2[0, 0], h02_m))
      

      现在让我们看看如何使用model.eval() 获得多个提前期预测。同样,下面的脚本会执行此操作并验证输出。

      # x0 now contains multiple entries
      x0 = np.array(x_data[0:4]).reshape((4, 1))
      h1234 = model.eval({x: x0})
      print("forecasts obtained by model automatically")
      print(h1234)
      
      h01_m = initial_state * H + x0[0, 0] * W + b
      h12_m = h01_m * H + x0[1, 0] * W + b
      h23_m = h12_m * H + x0[2, 0] * W + b
      h34_m = h23_m * H + x0[3, 0] * W + b
      
      print("forecasts computed manually")
      print(h01_m)
      print(h12_m)
      print(h23_m)
      print(h34_m)
      

      所以因为RNNStepRecurrence 内部使用,它会根据您提供给模型函数的输入数量自动调用RNNStep。每次调用RNNStep 时,它都会接收上一个周期状态和给model.eval() 的数组条目。这与训练时发生的情况一致。

      以下是一些关于 LSTM 和 RNN 的有用文档: https://docs.microsoft.com/en-us/python/api/cntk.layers.blocks?view=cntk-py-2.2 它的内容与https://cntk.ai/pythondocs/index.html不完全相同,在这种情况下它更有用。

      抱歉,我对您问题的 ctf 部分无能为力,我对此知之甚少,并且我还发布了一个尚未回答的问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-05-05
        • 1970-01-01
        • 2016-12-13
        • 2020-04-30
        • 1970-01-01
        • 2018-09-21
        • 2021-08-23
        • 1970-01-01
        相关资源
        最近更新 更多