Оценка функции в скользящем окне с помощью Keras

я пытаюсь расширить алгоритм сопоставления соответствия через последовательность. Мои матчи имеют длину 20 единиц и имеют 4 канала в каждый момент времени. Я построил модель, которая инкапсулирует соответствие, я просто не могу понять, как использовать это в скользящем окне, чтобы применить его в более длинной последовательности, чтобы найти совпадения в последовательности.

у меня 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)

это отлично работает. Теперь я хочу использовать эту предварительно обученную модель для поиска более длинного 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. Есть ли обертка, которую я потерял? Я делаю что-то не так? Нужно ли переформулировать это как проблему свертки почему?

продолжение экспериментов: После целого дня битья головой о клавиатуру становится ясно, что оба TimeDistributed и backend.rnn позволяет применять модель / слой только к одному временному срезу данных. Не похоже, что есть способ сделать это. Похоже, единственное, что может "ходить" по нескольким срезам измерения времени, - это Conv1D.

Итак, я переформулировал свою проблему как свертку, но это тоже не работает. Я смог строительство Conv1D фильтр, который будет соответствовать определенному query. Это работало достаточно хорошо, и это позволило мне сканировать более длинные последовательности и получать совпадения. Но каждый фильтр уникален для каждого query Тензор и, похоже, нет способа уйти от Романа query к соответствующим Весам фильтра без обучения совершенно новый Conv1D слой. Так как моя цель-найти новое querys, которые соответствуют большинству целей, это не очень помогает.

так как мое "соответствие" требует взаимодействие цели и запроса в каждом окне, похоже, не существует способа получить взаимодействие 20-длины query тензор в каждом окне через 100-length target тензор через 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 хочет.

это решение устраняет проблему дублирования данных, но оно винтами меня другими способами. Одна основана данных ... мои данные содержат много query, target пары, но это, как правило, то же самое target много querys, так как легче генерировать реальные данные в этой ориентации. Таким образом, это затрудняет тренировку. Во-вторых, это предполагает, что каждый query работает в независимом кстати, когда на самом деле, я знаю, что query, target сопряжение-это то, что действительно важно. Поэтому имеет смысл использовать модель, которая может смотреть на многие примеры пар, а не отдельных лиц.

есть ли способ объединить оба метода? Есть ли способ сделать это так Conv1D берет как долго target тензор объединить его с постоянной query как он идет по последовательности?

2 ответов


как раз обеспечить альтернативное решение используя функции backend 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])

учитывая ваш pretrained 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)

теперь вам просто нужно обработать и объединить выходы 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 на раздвижные окна:

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

Примечание: посмотрите на решение @Yu-Yang. Так гораздо лучше.


Ну, как я уже упоминал в своем комментарии, Вы можете использовать tf.exctract_image_patches() (если документация кажется немного расплывчатой, прочитайте ответ on SO) для извлечения патчей (редактировать: я только что добавил две переменные win_len и feat_len и изменен 100 to None и 81 to -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.]]]]