web-dev-qa-db-ja.com

Kerasを使用してスライディングウィンドウで関数を評価する

マッチングマッチングアルゴリズムをシーケンス全体に拡張しようとしています。私の試合は20ユニット長で、各タイムポイントに4つのチャネルがあります。マッチングをカプセル化するモデルを構築しましたが、それをスライディングウィンドウで使用して、長いシーケンスに適用し、シーケンス内の一致を見つける方法を理解できません。

2つの(20, 4)入力テンソル(queryおよびtarget)が2つあり、それらを連結、追加、平坦化してから、単純な高密度レイヤーを適用します。この段階で、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)

ただし、TimeDistributedLayerではなくTensorを取ります。行方不明のラッパーはありますか?私はこれを間違った方法で行っていますか?どういうわけかこれを畳み込み問題として再定式化する必要がありますか?

継続的な実験:キーボードに頭をぶつけて1日した後、TimeDistributedbackend.rnnの両方でモデル/レイヤーをデータの単一のタイムスライスにのみ適用できることは明らかです。これを行う方法はないようです。時間ディメンションの複数のスライスを「ウォーク」できるのはConv1Dだけです。

それで、問題を畳み込みとして再構成しましたが、それもうまくいきません。特定のqueryに一致するConv1Dフィルターを作成できました。これはかなりうまく機能し、長いシーケンスをスキャンして一致を取得することができました。しかし、各フィルターは各queryテンソルに固有であり、まったく新しいConv1Dレイヤーをトレーニングせずに、新しいqueryから適切なフィルターの重みに移行する方法はないようです。私の目標は、ほとんどのターゲットに一致する新しいquerysを見つけることなので、これはあまり役に立ちません。

「マッチング」にはターゲットと各ウィンドウでのクエリの相互作用が必要なため、長さ100のqueryテンソルからConv1Dまでの各ウィンドウで、長さ20のtargetテンソルの相互作用を取得する方法はないようです。 。

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が望む方法でフィルターをスタックするために、いくつかのアンパッキングと行列の再配置を実行します。

このソリューションはデータ複製の問題を修正しますが、他の方法で私を台無しにします。 1つはデータベースです...私のデータには多くのquerytargetペアが含まれていますが、同じtarget多くのquerysである傾向があります。その方向で実際のデータを生成する方が簡単だからです。したがって、この方法で行うと、トレーニングが難しくなります。次に、これは各queryが独立して機能することを前提としています。実際には、querytargetの組み合わせが実際に重要であることがわかっています。したがって、個人ではなくペアの多くの例を見ることができるモデルを使用することは理にかなっています。

両方の方法を組み合わせる方法はありますか? Conv1Dが長いtargetテンソルを定数queryと組み合わせてシーケンスに沿って歩くようにする方法はありますか?

14
JudoWill

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 to it:

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をエンドツーエンドで使用して、長いターゲットを照合できます。

スライディングウィンドウにmatch_modelを適用することにより、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
9
Yu-Yang

注:@ Yu-Yangのソリューションを見てください。それははるかに優れています。


まあ、私が私のコメントで述べたように、パッチを抽出するために tf.exctract_image_patches() (ドキュメントが少し曖昧に見える場合は この答え を使用)を使用できます(Edit:2つの変数を追加したばかりです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.]]]]
10
today