使用Keras在滑动窗口中评估功能

时间:2018-06-22 03:11:03

标签: python tensorflow keras conv-neural-network sliding-window

我正在尝试在序列中扩展匹配的匹配算法。我的比赛有20个单位,每个时间点有4个频道。我建立了一个封装匹配的模型,只是无法弄清楚如何在滑动窗口中使用它来将其应用到更长的序列中以找到序列中的匹配项。

我有2个(20, 4)输入张量(querytarget),它们可以连接,添加,展平然后应用简单的密集层。我现阶段有数据可以训练10万个查询目标对。

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)

这很好用。现在,我想使用这种经过预训练的模型来搜索具有变化的target序列的更长的query序列。

似乎应该是这样的:

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。我缺少包装纸吗?我会以错误的方式处理吗?我是否需要以某种方式将其重新表述为卷积问题?

继续实验: 在用键盘敲打我的头一天之后,很明显TimeDistributedbackend.rnn都只允许您将模型/图层应用于数据的单个时间片。似乎没有办法做到这一点。看来唯一可以“跨越”时间维度的多个层面的东西是Conv1D

因此,我将问题重新定义为卷积,但效果也不佳。我能够构建一个与特定Conv1D匹配的query过滤器。这工作得相当好,并且确实允许我扫描更长的序列并获得匹配。但是每个滤波器对于每个query张量都是唯一的,并且似乎没有一种方法可以在不训练整个query层的情况下,从新颖的Conv1D到合适的滤波器权重。由于我的目标是找到最符合目标的新query,所以并没有太大帮助。

由于我的“匹配”需要目标与每个窗口的查询进行交互,因此似乎没有一种方法可以使整个窗口上的每个窗口获得20长度query张量的交互通过target的100个长度的Conv1D张量。

在Keras / tensorflow中是否有任何方法可以进行此滑动窗口类型评估?似乎很简单,却又遥不可及。有什么方法可以解决我找不到的吗?

响应和进一步的实验。

@today和@nuric的解决方案都可以工作,但是最终它们以切片类型的方式复制了输入target数据。因此,对于长度为m的查询,图中的输入数据的m副本下会少一点。我希望找到一种解决方案,该解决方案实际上可以使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所需的方式堆叠过滤器。

此解决方案解决了数据重复问题,但以其他方式给我带来麻烦。一种是基于数据的...我的数据包含许多querytarget对,但是由于许多target都更容易生成,所以它们往往是相同的query许多query对。在这种情况下的真实数据。因此,这样做会使训练变得困难。其次,这假设每个query都以独立的方式工作,而实际上,我知道targetConv1D的配对才是真正重要的。因此,使用可以查看配对对的许多示例而非个人的示例的模型是有意义的。

有没有一种方法可以将两种方法结合起来?有没有一种方法可以使target沿其序列沿着query长张量case与常数OR组合在一起?

2 个答案:

答案 0 :(得分:9)

注意:请查看@ Yu-Yang的解决方案。更好。


好吧,正如我在评论中提到的那样,您可以使用tf.exctract_image_patches()(如果文档看起来有点含糊,请阅读this answer,以便提取补丁)(编辑:我只添加了两个变量win_lenfeat_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.]]]]

答案 1 :(得分:9)

仅提供使用Keras后端功能的替代解决方案。

您还可以使用K.arangeK.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

但是,由于匹配targetquery的逻辑是在Concatenate之后的各层中实现的,因此您可以将这些层收集到Model中并应用{{1 }}:

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) 中相同的方式处理和合并sliding_windows的输出:

match_model

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应用于滑动窗口来验证model的输出确实是匹配分数的最大值:

match_model