使用 Keras 在滑动 window 中评估函数
Evaluate a function in a sliding window with Keras
我正在尝试跨序列扩展匹配匹配算法。我的比赛有 20 个单位长,每个时间点有 4 个通道。我已经建立了一个封装匹配的模型,我只是不知道如何在滑动 window 中使用它来将它应用于更长的序列以找到序列中的匹配项。
我有 2 个 (20, 4)
输入张量(query
和 target
),我将它们连接、添加、展平,然后应用一个简单的密集层。我现阶段有数据可以使用 100K 查询、目标对进行训练。
def sum_seqs(seqs):
return K.sum(seqs, axis=3)
def pad_dims(seq):
return K.expand_dims(seq, axis=3)
def pad_outshape(in_shape):
return (in_shape[0], in_shape[1], in_shape[2], 1)
query = Input((20, 4))
query_pad = Lambda(pad_dims, output_shape=pad_outshape, name='gpad')(query)
target = Input((20,4))
target_pad = Lambda(pad_dims, output_shape=pad_outshape)(target)
matching = Concatenate(axis = 3)([query_pad, target_pad])
matching = Lambda(sum_seqs)(matching)
matching = Flatten()(matching)
matching = Dropout(0.1)(matching)
matching = Dense(1, activation = 'sigmoid')(matching)
match_model = Model([query, target], matching)
这非常有效。现在我想使用这个预训练模型来搜索具有不同 query
序列的更长 target
序列。
好像应该是这样的:
long_target = Input((100, 4))
short_target = Input((20, 4))
choose_query = Input((20, 4))
spec_match = match_model([choose_query, short_target])
mdl = TimeDistributed(spec_match)(long_target)
但是 TimeDistributed
需要 Layer
而不是 Tensor
。我缺少包装纸吗?我会以错误的方式解决这个问题吗?我是否需要以某种方式将其重新表述为卷积问题?
继续实验:
在敲击键盘一天后,很明显 TimeDistributed
和 backend.rnn
都只允许您将 model/layer 应用于数据的单个时间片。似乎没有办法做到这一点。看起来唯一可以 "walk" 跨越时间维度的多个切片的是 Conv1D
.
因此,我将我的问题重新定义为卷积,但效果也不佳。我能够构建一个 Conv1D
过滤器来匹配特定的 query
。这工作得相当好,它确实允许我扫描更长的序列并获得匹配项。但是每个过滤器对于每个 query
张量都是唯一的,并且似乎没有办法在不训练全新的 Conv1D
层的情况下从新颖的 query
到适当的过滤器权重。因为我的目标是找到匹配最多目标的新 query
s,所以这没什么帮助。
因为我的 "matching" 需要目标和查询在每个 window 处的交互,所以似乎没有办法获得 20 长度的交互 query
张量在每个 window 跨越 100 长度 target
张量到 Conv1D
。
有什么方法可以在 Keras/tensorflow 中执行这种滑动 window 类型的评估吗?这似乎是一件如此简单却又如此遥远的事情。有没有我找不到的方法可以做到这一点?
回应和进一步的实验。
来自@today 和@nuric 的解决方案有效,但它们最终以平铺类型方式复制输入 target
数据。因此,对于长度为 m
的查询,图表中的输入数据副本将少于 m
个。我希望找到一个解决方案,实际上 "slide" 整个 target
的评估没有重复。
这是我想出的 Conv1D
几乎解决方案的一个版本。
query_weights = []
for query, (targets, scores) in query_target_gen():
single_query_model = Sequential()
single_query_model.add(Conv1D(1, 20, input_shape = (20, 4)))
single_query_model.add(Flatten())
single_query_model.fit(targets, scores)
query_weights.append(single_query_model.layers[0].get_weights())
multi_query_model_long_targets = Sequential()
multi_query_model_long_targets.add(Conv1D(len(query_weights), 20, input_shape = (100, 4)))
multi_query_model_long_targets.layers[0].set_weights(combine_weights(query_weights))
multi_query_model_long_targets.summary()
combine_weights
函数只是进行一些解包和矩阵重新排列,以 Conv1D
想要的方式堆叠过滤器。
此解决方案解决了数据重复问题,但在其他方面却让我很烦恼。一种是基于数据的……我的数据包含许多 query
、target
对,但它往往是相同的 target
许多 query
,因为它更容易生成那个方向的真实世界数据。所以,这样做会使训练变得困难。其次,这假设每个 query
都以独立的方式工作,而实际上,我知道 query
、target
配对才是真正重要的。因此,使用可以查看许多成对示例而不是单个示例的模型是有意义的。
有没有办法结合这两种方法?有没有一种方法可以使 Conv1D
在沿着序列行走时同时采用长 target
张量并将其与常量 query
相结合?
注:看@Yu-Yang的解决方法。好多了。
好吧,正如我在评论中提到的,您可以在 SO 上使用 tf.exctract_image_patches()
(if the documentation seems a bit vague read 来提取补丁(编辑:我刚刚添加了两个变量 win_len
和 feat_len
并将 100
更改为 None
并将 81
更改为 -1
以使其适用于任意长度的目标序列):
import tensorflow as tf
from keras import layers, models
import keras.backend as K
win_len = 20 # window length
feat_len = 4 # features length
def extract_patches(data):
data = K.expand_dims(data, axis=3)
patches = tf.extract_image_patches(data, ksizes=[1, win_len, feat_len, 1], strides=[1, 1, 1, 1], rates=[1, 1, 1, 1], padding='VALID')
return patches
target = layers.Input((None, feat_len))
patches = layers.Lambda(extract_patches)(target)
patches = layers.Reshape((-1, win_len, feat_len))(patches)
model = models.Model([target], [patches])
model.summary()
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) (None, None, 4) 0
_________________________________________________________________
lambda_2 (Lambda) (None, None, None, 80) 0
_________________________________________________________________
reshape_2 (Reshape) (None, None, 20, 4) 0
=================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
_________________________________________________________________
例如,如果输入目标的形状为 (100, 4)
,则输出形状为 (81, 20, 4)
。
这是一个测试:
import numpy as np
# an array consisting of numbers 0 to 399 with shape (100, 4)
target = np.arange(1*100*4*1).reshape(1, 100, 4)
print(model.predict(a))
这是输出:
[[[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]
...
[ 68. 69. 70. 71.]
[ 72. 73. 74. 75.]
[ 76. 77. 78. 79.]]
[[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]
[ 12. 13. 14. 15.]
...
[ 72. 73. 74. 75.]
[ 76. 77. 78. 79.]
[ 80. 81. 82. 83.]]
[[ 8. 9. 10. 11.]
[ 12. 13. 14. 15.]
[ 16. 17. 18. 19.]
...
[ 76. 77. 78. 79.]
[ 80. 81. 82. 83.]
[ 84. 85. 86. 87.]]
...
[[312. 313. 314. 315.]
[316. 317. 318. 319.]
[320. 321. 322. 323.]
...
[380. 381. 382. 383.]
[384. 385. 386. 387.]
[388. 389. 390. 391.]]
[[316. 317. 318. 319.]
[320. 321. 322. 323.]
[324. 325. 326. 327.]
...
[384. 385. 386. 387.]
[388. 389. 390. 391.]
[392. 393. 394. 395.]]
[[320. 321. 322. 323.]
[324. 325. 326. 327.]
[328. 329. 330. 331.]
...
[388. 389. 390. 391.]
[392. 393. 394. 395.]
[396. 397. 398. 399.]]]]
只是为了提供一个使用 Keras 后端函数的替代解决方案。
您还可以使用 K.arange
和 K.map_fn
:
生成滑动 windows
def sliding_windows(inputs):
target, query = inputs
target_length = K.shape(target)[1] # variable-length sequence, shape is a TF tensor
query_length = K.int_shape(query)[1]
num_windows = target_length - query_length + 1 # number of windows is also variable
# slice the target into consecutive windows
start_indices = K.arange(num_windows)
windows = K.map_fn(lambda t: target[:, t:(t + query_length), :],
start_indices,
dtype=K.floatx())
# `windows` is a tensor of shape (num_windows, batch_size, query_length, ...)
# so we need to change the batch axis back to axis 0
windows = K.permute_dimensions(windows, (1, 0, 2, 3))
# repeat query for `num_windows` times so that it could be merged with `windows` later
query = K.expand_dims(query, 1)
query = K.tile(query, [1, num_windows, 1, 1])
# just a hack to force the dimensions 2 to be known (required by Flatten layer)
windows = K.reshape(windows, shape=K.shape(query))
return [windows, query]
使用方法:
long_target = Input((None, 4))
choose_query = Input((20, 4))
windows, query = Lambda(sliding_windows)([long_target, choose_query])
考虑到您的预训练 match_model
,TimeDistributed
的问题是它无法将 Keras Model
包装成具有多个输入。
但是,由于匹配target
和query
的逻辑是在Concatenate
之后的图层中实现的,所以可以将这些图层集合成一个Model
,然后应用TimeDistributed
到它:
submodel_input = Input((20, 4, 2))
x = submodel_input
for layer in match_model.layers[-4:]: # the `Lambda(sum_seqs)` layer
x = layer(x)
submodel = Model(submodel_input, x)
现在你只需要像 match_model
一样处理和合并 sliding_windows
的输出:
long_target = Input((None, 4))
choose_query = Input((20, 4))
windows, query = Lambda(sliding_windows)([long_target, choose_query])
windows_pad = Lambda(lambda x: K.expand_dims(x))(windows)
query_pad = Lambda(lambda x: K.expand_dims(x))(query)
merged = Concatenate()([windows_pad, query_pad])
match_scores = TimeDistributed(submodel)(merged)
max_score = GlobalMaxPooling1D()(match_scores)
model = Model([long_target, choose_query], max_score)
model
然后可以端到端的方式用于匹配长目标。
您还可以通过将 match_model
应用于滑动 windows:
来验证 model
的输出确实是匹配分数的最大值
target_arr = np.random.rand(32, 100, 4)
query_arr = np.random.rand(32, 20, 4)
match_model_scores = np.array([
match_model.predict([target_arr[:, t:t + 20, :], query_arr])
for t in range(81)
])
scores = model.predict([target_arr, query_arr])
print(np.allclose(scores, match_model_scores.max(axis=0)))
True
我正在尝试跨序列扩展匹配匹配算法。我的比赛有 20 个单位长,每个时间点有 4 个通道。我已经建立了一个封装匹配的模型,我只是不知道如何在滑动 window 中使用它来将它应用于更长的序列以找到序列中的匹配项。
我有 2 个 (20, 4)
输入张量(query
和 target
),我将它们连接、添加、展平,然后应用一个简单的密集层。我现阶段有数据可以使用 100K 查询、目标对进行训练。
def sum_seqs(seqs):
return K.sum(seqs, axis=3)
def pad_dims(seq):
return K.expand_dims(seq, axis=3)
def pad_outshape(in_shape):
return (in_shape[0], in_shape[1], in_shape[2], 1)
query = Input((20, 4))
query_pad = Lambda(pad_dims, output_shape=pad_outshape, name='gpad')(query)
target = Input((20,4))
target_pad = Lambda(pad_dims, output_shape=pad_outshape)(target)
matching = Concatenate(axis = 3)([query_pad, target_pad])
matching = Lambda(sum_seqs)(matching)
matching = Flatten()(matching)
matching = Dropout(0.1)(matching)
matching = Dense(1, activation = 'sigmoid')(matching)
match_model = Model([query, target], matching)
这非常有效。现在我想使用这个预训练模型来搜索具有不同 query
序列的更长 target
序列。
好像应该是这样的:
long_target = Input((100, 4))
short_target = Input((20, 4))
choose_query = Input((20, 4))
spec_match = match_model([choose_query, short_target])
mdl = TimeDistributed(spec_match)(long_target)
但是 TimeDistributed
需要 Layer
而不是 Tensor
。我缺少包装纸吗?我会以错误的方式解决这个问题吗?我是否需要以某种方式将其重新表述为卷积问题?
继续实验:
在敲击键盘一天后,很明显 TimeDistributed
和 backend.rnn
都只允许您将 model/layer 应用于数据的单个时间片。似乎没有办法做到这一点。看起来唯一可以 "walk" 跨越时间维度的多个切片的是 Conv1D
.
因此,我将我的问题重新定义为卷积,但效果也不佳。我能够构建一个 Conv1D
过滤器来匹配特定的 query
。这工作得相当好,它确实允许我扫描更长的序列并获得匹配项。但是每个过滤器对于每个 query
张量都是唯一的,并且似乎没有办法在不训练全新的 Conv1D
层的情况下从新颖的 query
到适当的过滤器权重。因为我的目标是找到匹配最多目标的新 query
s,所以这没什么帮助。
因为我的 "matching" 需要目标和查询在每个 window 处的交互,所以似乎没有办法获得 20 长度的交互 query
张量在每个 window 跨越 100 长度 target
张量到 Conv1D
。
有什么方法可以在 Keras/tensorflow 中执行这种滑动 window 类型的评估吗?这似乎是一件如此简单却又如此遥远的事情。有没有我找不到的方法可以做到这一点?
回应和进一步的实验。
来自@today 和@nuric 的解决方案有效,但它们最终以平铺类型方式复制输入 target
数据。因此,对于长度为 m
的查询,图表中的输入数据副本将少于 m
个。我希望找到一个解决方案,实际上 "slide" 整个 target
的评估没有重复。
这是我想出的 Conv1D
几乎解决方案的一个版本。
query_weights = []
for query, (targets, scores) in query_target_gen():
single_query_model = Sequential()
single_query_model.add(Conv1D(1, 20, input_shape = (20, 4)))
single_query_model.add(Flatten())
single_query_model.fit(targets, scores)
query_weights.append(single_query_model.layers[0].get_weights())
multi_query_model_long_targets = Sequential()
multi_query_model_long_targets.add(Conv1D(len(query_weights), 20, input_shape = (100, 4)))
multi_query_model_long_targets.layers[0].set_weights(combine_weights(query_weights))
multi_query_model_long_targets.summary()
combine_weights
函数只是进行一些解包和矩阵重新排列,以 Conv1D
想要的方式堆叠过滤器。
此解决方案解决了数据重复问题,但在其他方面却让我很烦恼。一种是基于数据的……我的数据包含许多 query
、target
对,但它往往是相同的 target
许多 query
,因为它更容易生成那个方向的真实世界数据。所以,这样做会使训练变得困难。其次,这假设每个 query
都以独立的方式工作,而实际上,我知道 query
、target
配对才是真正重要的。因此,使用可以查看许多成对示例而不是单个示例的模型是有意义的。
有没有办法结合这两种方法?有没有一种方法可以使 Conv1D
在沿着序列行走时同时采用长 target
张量并将其与常量 query
相结合?
注:看@Yu-Yang的解决方法。好多了。
好吧,正如我在评论中提到的,您可以在 SO 上使用 tf.exctract_image_patches()
(if the documentation seems a bit vague read win_len
和 feat_len
并将 100
更改为 None
并将 81
更改为 -1
以使其适用于任意长度的目标序列):
import tensorflow as tf
from keras import layers, models
import keras.backend as K
win_len = 20 # window length
feat_len = 4 # features length
def extract_patches(data):
data = K.expand_dims(data, axis=3)
patches = tf.extract_image_patches(data, ksizes=[1, win_len, feat_len, 1], strides=[1, 1, 1, 1], rates=[1, 1, 1, 1], padding='VALID')
return patches
target = layers.Input((None, feat_len))
patches = layers.Lambda(extract_patches)(target)
patches = layers.Reshape((-1, win_len, feat_len))(patches)
model = models.Model([target], [patches])
model.summary()
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) (None, None, 4) 0
_________________________________________________________________
lambda_2 (Lambda) (None, None, None, 80) 0
_________________________________________________________________
reshape_2 (Reshape) (None, None, 20, 4) 0
=================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
_________________________________________________________________
例如,如果输入目标的形状为 (100, 4)
,则输出形状为 (81, 20, 4)
。
这是一个测试:
import numpy as np
# an array consisting of numbers 0 to 399 with shape (100, 4)
target = np.arange(1*100*4*1).reshape(1, 100, 4)
print(model.predict(a))
这是输出:
[[[[ 0. 1. 2. 3.]
[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]
...
[ 68. 69. 70. 71.]
[ 72. 73. 74. 75.]
[ 76. 77. 78. 79.]]
[[ 4. 5. 6. 7.]
[ 8. 9. 10. 11.]
[ 12. 13. 14. 15.]
...
[ 72. 73. 74. 75.]
[ 76. 77. 78. 79.]
[ 80. 81. 82. 83.]]
[[ 8. 9. 10. 11.]
[ 12. 13. 14. 15.]
[ 16. 17. 18. 19.]
...
[ 76. 77. 78. 79.]
[ 80. 81. 82. 83.]
[ 84. 85. 86. 87.]]
...
[[312. 313. 314. 315.]
[316. 317. 318. 319.]
[320. 321. 322. 323.]
...
[380. 381. 382. 383.]
[384. 385. 386. 387.]
[388. 389. 390. 391.]]
[[316. 317. 318. 319.]
[320. 321. 322. 323.]
[324. 325. 326. 327.]
...
[384. 385. 386. 387.]
[388. 389. 390. 391.]
[392. 393. 394. 395.]]
[[320. 321. 322. 323.]
[324. 325. 326. 327.]
[328. 329. 330. 331.]
...
[388. 389. 390. 391.]
[392. 393. 394. 395.]
[396. 397. 398. 399.]]]]
只是为了提供一个使用 Keras 后端函数的替代解决方案。
您还可以使用 K.arange
和 K.map_fn
:
def sliding_windows(inputs):
target, query = inputs
target_length = K.shape(target)[1] # variable-length sequence, shape is a TF tensor
query_length = K.int_shape(query)[1]
num_windows = target_length - query_length + 1 # number of windows is also variable
# slice the target into consecutive windows
start_indices = K.arange(num_windows)
windows = K.map_fn(lambda t: target[:, t:(t + query_length), :],
start_indices,
dtype=K.floatx())
# `windows` is a tensor of shape (num_windows, batch_size, query_length, ...)
# so we need to change the batch axis back to axis 0
windows = K.permute_dimensions(windows, (1, 0, 2, 3))
# repeat query for `num_windows` times so that it could be merged with `windows` later
query = K.expand_dims(query, 1)
query = K.tile(query, [1, num_windows, 1, 1])
# just a hack to force the dimensions 2 to be known (required by Flatten layer)
windows = K.reshape(windows, shape=K.shape(query))
return [windows, query]
使用方法:
long_target = Input((None, 4))
choose_query = Input((20, 4))
windows, query = Lambda(sliding_windows)([long_target, choose_query])
考虑到您的预训练 match_model
,TimeDistributed
的问题是它无法将 Keras Model
包装成具有多个输入。
但是,由于匹配target
和query
的逻辑是在Concatenate
之后的图层中实现的,所以可以将这些图层集合成一个Model
,然后应用TimeDistributed
到它:
submodel_input = Input((20, 4, 2))
x = submodel_input
for layer in match_model.layers[-4:]: # the `Lambda(sum_seqs)` layer
x = layer(x)
submodel = Model(submodel_input, x)
现在你只需要像 match_model
一样处理和合并 sliding_windows
的输出:
long_target = Input((None, 4))
choose_query = Input((20, 4))
windows, query = Lambda(sliding_windows)([long_target, choose_query])
windows_pad = Lambda(lambda x: K.expand_dims(x))(windows)
query_pad = Lambda(lambda x: K.expand_dims(x))(query)
merged = Concatenate()([windows_pad, query_pad])
match_scores = TimeDistributed(submodel)(merged)
max_score = GlobalMaxPooling1D()(match_scores)
model = Model([long_target, choose_query], max_score)
model
然后可以端到端的方式用于匹配长目标。
您还可以通过将 match_model
应用于滑动 windows:
model
的输出确实是匹配分数的最大值
target_arr = np.random.rand(32, 100, 4)
query_arr = np.random.rand(32, 20, 4)
match_model_scores = np.array([
match_model.predict([target_arr[:, t:t + 20, :], query_arr])
for t in range(81)
])
scores = model.predict([target_arr, query_arr])
print(np.allclose(scores, match_model_scores.max(axis=0)))
True