LSTMに異なる入力配列サイズを供給する方法は?


18

LSTMネットワークを作成し、さまざまな入力配列サイズでフィードしたい場合、どうすれば可能ですか?

たとえば、音声メッセージやテキストメッセージを別の言語で取得して翻訳したいと考えています。したがって、最初の入力は「こんにちは」かもしれませんが、2番目の入力は「元気ですか」です。LSTMさまざまな入力配列サイズを処理できるをどのように設計できますか?

Keras実装を使用していますLSTM

回答:


24

最も簡単な方法は、パディングとマスキングを使用することです。

可変長シーケンスを処理するには、3つの一般的な方法があります。

  1. パディングとマスキング((3)に使用できます)、
  2. バッチサイズ= 1、および
  3. バッチサイズ> 1、各バッチのサンプルは等長。

パディングとマスキング

このアプローチでは、短いマスクを特別な値で埋め、後でマスク(スキップ)します。たとえば、各タイムスタンプに次元2 -10があり、特別な値であるとすると、

X = [

  [[1,    1.1],
   [0.9, 0.95]],  # sequence 1 (2 timestamps)

  [[2,    2.2],
   [1.9, 1.95],
   [1.8, 1.85]],  # sequence 2 (3 timestamps)

]

に変換されます

X2 = [

  [[1,    1.1],
   [0.9, 0.95],
   [-10, -10]], # padded sequence 1 (3 timestamps)

  [[2,    2.2],
   [1.9, 1.95],
   [1.8, 1.85]], # sequence 2 (3 timestamps)
]

このように、すべてのシーケンスは同じ長さになります。次に、Maskingこれらの特別なタイムスタンプが存在しないようにスキップするレイヤーを使用します。最後に完全な例を示します。

(2)と(3)の場合seq_len、LSTMのをに設定する必要がありますNone。例:

model.add(LSTM(units, input_shape=(None, dimension)))

このように、LSTMは異なる長さのバッチを受け入れます。ただし、各バッチ内のサンプルは同じ長さでなければなりません。次に、カスタムバッチジェネレータmodel.fit_generator(の代わりにmodel.fit)にフィードする必要があります。

最後に、単純なケース(2)(バッチサイズ= 1)の完全な例を示しました。この例とリンクに基づいて、ケース(3)のジェネレーターを作成できるはずです(バッチサイズ> 1)。具体的には、(a)batch_size同じ長さのシーケンスを返すか、(b)ほぼ同じ長さのシーケンスを選択し、ケース(1)と同じように短い方をMaskingパディングし、LSTMレイヤーの前のレイヤーを使用してパディングを無視しますタイムスタンプ、例えば

model.add(Masking(mask_value=special_value, input_shape=(None, dimension)))
model.add(LSTM(lstm_units))

ここでも、input_shapeinの最初の次元は、異なる長さのバッチを許可するためのものです。MaskingNone

ケース(1)と(2)のコードは次のとおりです。

from keras import Sequential
from keras.utils import Sequence
from keras.layers import LSTM, Dense, Masking
import numpy as np


class MyBatchGenerator(Sequence):
    'Generates data for Keras'
    def __init__(self, X, y, batch_size=1, shuffle=True):
        'Initialization'
        self.X = X
        self.y = y
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.on_epoch_end()

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.y)/self.batch_size))

    def __getitem__(self, index):
        return self.__data_generation(index)

    def on_epoch_end(self):
        'Shuffles indexes after each epoch'
        self.indexes = np.arange(len(self.y))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)

    def __data_generation(self, index):
        Xb = np.empty((self.batch_size, *X[index].shape))
        yb = np.empty((self.batch_size, *y[index].shape))
        # naively use the same sample over and over again
        for s in range(0, self.batch_size):
            Xb[s] = X[index]
            yb[s] = y[index]
        return Xb, yb


# Parameters
N = 1000
halfN = int(N/2)
dimension = 2
lstm_units = 3

# Data
np.random.seed(123)  # to generate the same numbers
# create sequence lengths between 1 to 10
seq_lens = np.random.randint(1, 10, halfN)
X_zero = np.array([np.random.normal(0, 1, size=(seq_len, dimension)) for seq_len in seq_lens])
y_zero = np.zeros((halfN, 1))
X_one = np.array([np.random.normal(1, 1, size=(seq_len, dimension)) for seq_len in seq_lens])
y_one = np.ones((halfN, 1))
p = np.random.permutation(N)  # to shuffle zero and one classes
X = np.concatenate((X_zero, X_one))[p]
y = np.concatenate((y_zero, y_one))[p]

# Batch = 1
model = Sequential()
model.add(LSTM(lstm_units, input_shape=(None, dimension)))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
print(model.summary())
model.fit_generator(MyBatchGenerator(X, y, batch_size=1), epochs=2)

# Padding and Masking
special_value = -10.0
max_seq_len = max(seq_lens)
Xpad = np.full((N, max_seq_len, dimension), fill_value=special_value)
for s, x in enumerate(X):
    seq_len = x.shape[0]
    Xpad[s, 0:seq_len, :] = x
model2 = Sequential()
model2.add(Masking(mask_value=special_value, input_shape=(max_seq_len, dimension)))
model2.add(LSTM(lstm_units))
model2.add(Dense(1, activation='sigmoid'))
model2.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
print(model2.summary())
model2.fit(Xpad, y, epochs=50, batch_size=32)

追記

  1. マスキングなしでパディングすると、パディングされた値は実際の値と見なされ、データのノイズになります。たとえば、パディングされた温度シーケンス[20, 21, 22, -10, -10]は、最後に2つのノイズのある(間違った)測定値を持つセンサーレポートと同じです。モデルはこのノイズを完全にまたは少なくとも部分的に無視することを学習する可能性がありますが、最初にデータをクリーンアップする、つまりマスクを使用するのが妥当です。

Esmailianの完全な例をありがとうございました。ただ1つの質問:パディング+マスキングの使用とパディングのみの使用の違いは何ですか(他の回答が示唆するように)?最終結果に大きな影響を与えるでしょうか?
user145959

@ user145959どういたしまして!最後にメモを追加しました。
エスマイリアン

すばらしい答えです!それはバケツと呼ばれますよね?
Aditya

1
@Aditya Adityaに感謝!バケット化は大きなシーケンスを小さなチャンクに分割することだと思いますが、各バッチのシーケンスは必ずしも同じ(大きな)シーケンスのチャンクではなく、独立したデータポイントである場合があります。
エスマイリアン

1
@ flow2k重要ではありません。パッドは完全に無視されます。この質問を見てください。
エスマイリアン

3

複数の入力サイズのLSTMレイヤーを使用します。ただし、それらをLSTMに送る前に処理する必要があります。

シーケンスのパディング:

さまざまな長さのシーケンスを固定長に埋め込む必要があります。この前処理では、データセット内のシーケンスの最大長を決定する必要があります。

値は主に値0で埋め込まれます。これはKerasで次のように実行できます:

y = keras.preprocessing.sequence.pad_sequences( x , maxlen=10 )
  • シーケンスが最大長より短い場合は、最大長と同じ長さになるまでゼロが追加されます。

  • シーケンスが最大長よりも長い場合、シーケンスは最大長にトリミングされます。


3
すべてを固定長にパディングすると、スペースの無駄になります。
Aditya

@Adityaに同意し、計算コストもかかります。しかし、単純化されたパディングがまだ広く使用されているのはそうではありませんか?Kerasには、このための機能さえあります。おそらくこれは、他のより効率的で挑戦的なソリューションではモデルのパフォーマンスが大幅に向上しないためでしょうか?誰もが経験を持っているかの比較を行っている場合は、重量を量るしてください。
flow2k
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.