CNTK:如何初始化 LSTM 隐藏状态?
CNTK: How do I initialize LSTM hidden state?
我正在尝试将工作图像字幕 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)
旁白:三个数据流的原因——我有一个输入图像特征向量(pre-trained ResNet 的最后一个 2048 暗层)、一个输入文本标记序列和一个输出序列文本标记。所以基本上我的 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 维图像特征向量,序列中的其余元素具有空的 2048 维零向量 - 设置为:
C.plus(embedded_images, embedded_text)
在上面的模型中——目标是从本质上获取 40 个 [2048]->[512]
图像嵌入和 hack-splice(TM ) 它位于 40 [vocab_dim]->[512]
个词嵌入序列的最后 39 个元素之前。我指望为空图像向量(2048 个零)学习非常空的 [2048]->[512]
图像嵌入,所以我将我的嵌入图像序列 element-wise 添加到我的嵌入文本序列之前进入 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()
问题是,我无法计算出从 LSTM 中获取隐藏状态并在下一个时间步将其抽回的 CNTK 等价物:
hiddens, states = self.lstm(inputs, states)
这在 CNTK 中如何工作?
我认为您要查找的函数是 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)
我最近遇到了同样的问题。下面我提供了我构建的示例代码,以了解如何使用 Recurrence
和 RNNStep
对象来获取预测。请注意,预测只是下一个周期状态,因为它没有以任何方式转换。我使用了 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)
所以因为在Recurrence
里面使用了RNNStep
,它会根据你提供给模型函数的输入数量自动调用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 部分,我对此了解有限,我也发布了一个尚未回答的问题。
我正在尝试将工作图像字幕 CNN-LSTM 网络从 TensorFlow 转换为 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)
旁白:三个数据流的原因——我有一个输入图像特征向量(pre-trained ResNet 的最后一个 2048 暗层)、一个输入文本标记序列和一个输出序列文本标记。所以基本上我的 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 维图像特征向量,序列中的其余元素具有空的 2048 维零向量 - 设置为:
C.plus(embedded_images, embedded_text)
在上面的模型中——目标是从本质上获取 40 个 [2048]->[512]
图像嵌入和 hack-splice(TM ) 它位于 40 [vocab_dim]->[512]
个词嵌入序列的最后 39 个元素之前。我指望为空图像向量(2048 个零)学习非常空的 [2048]->[512]
图像嵌入,所以我将我的嵌入图像序列 element-wise 添加到我的嵌入文本序列之前进入 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()
问题是,我无法计算出从 LSTM 中获取隐藏状态并在下一个时间步将其抽回的 CNTK 等价物:
hiddens, states = self.lstm(inputs, states)
这在 CNTK 中如何工作?
我认为您要查找的函数是 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)
我最近遇到了同样的问题。下面我提供了我构建的示例代码,以了解如何使用 Recurrence
和 RNNStep
对象来获取预测。请注意,预测只是下一个周期状态,因为它没有以任何方式转换。我使用了 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)
所以因为在Recurrence
里面使用了RNNStep
,它会根据你提供给模型函数的输入数量自动调用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 部分,我对此了解有限,我也发布了一个尚未回答的问题。