Я считаю, что вы можете иметь причинно-следственную связь с расширением для любого количества входных функций. Вот решение, которое я бы предложил.
Слой TimeDistributed является ключевым для этого.
Из документации Keras: "Эта обертка применяет слой к каждому временному срезу входа. Вход должен быть не менее 3D, и измерение индекса один будет считаться временным измерением."
Для наших целей мы хотим, чтобы этот слой применял «что-то» к каждому объекту, поэтому мы перемещаем объекты во временный индекс, который равен 1.
Также актуальна документация Conv1D .
В частности, о каналах: "Упорядочение размеров на входах." Channel_last "соответствует входам с формой (партия, шаги, каналы) (формат по умолчанию для временных данных в кератах)"
from tensorflow.python.keras import Sequential, backend
from tensorflow.python.keras.layers import GlobalMaxPool1D, Activation, MaxPool1D, Flatten, Conv1D, Reshape, TimeDistributed, InputLayer
backend.clear_session()
lookback = 20
n_features = 5
filters = 128
model = Sequential()
model.add(InputLayer(input_shape=(lookback, n_features, 1)))
# Causal layers are first applied to the features independently
model.add(Reshape(target_shape=(n_features, lookback, 1)))
# After reshape 5 input features are now treated as the temporal layer
# for the TimeDistributed layer
# When Conv1D is applied to each input feature, it thinks the shape of the layer is (20, 1)
# with the default "channels_last", therefore...
# 20 times steps is the temporal dimension
# 1 is the "channel", the new location for the feature maps
model.add(TimeDistributed(Conv1D(filters, 3, activation="elu", padding="causal", dilation_rate=2**0)))
# You could add pooling here if you want.
# If you want interaction between features AND causal/dilation, then apply later
model.add(TimeDistributed(Conv1D(filters, 3, activation="elu", padding="causal", dilation_rate=2**1)))
model.add(TimeDistributed(Conv1D(filters, 3, activation="elu", padding="causal", dilation_rate=2**2)))
# Stack feature maps on top of each other so each time step can look at
# all features produce earlier
model.add(Reshape(target_shape=(lookback, n_features * filters))) # (20 time steps, 5 features * 128 filters)
# Causal layers are applied to the 5 input features dependently
model.add(Conv1D(filters, 3, activation="elu", padding="causal", dilation_rate=2**0))
model.add(MaxPool1D())
model.add(Conv1D(filters, 3, activation="elu", padding="causal", dilation_rate=2**1))
model.add(MaxPool1D())
model.add(Conv1D(filters, 3, activation="elu", padding="causal", dilation_rate=2**2))
model.add(GlobalMaxPool1D())
model.add(Dense(units=1, activation='linear'))
model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()
Окончательная сводная модель
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
reshape (Reshape) (None, 5, 20, 1) 0
_________________________________________________________________
time_distributed (TimeDistri (None, 5, 20, 128) 512
_________________________________________________________________
time_distributed_1 (TimeDist (None, 5, 20, 128) 49280
_________________________________________________________________
time_distributed_2 (TimeDist (None, 5, 20, 128) 49280
_________________________________________________________________
reshape_1 (Reshape) (None, 20, 640) 0
_________________________________________________________________
conv1d_3 (Conv1D) (None, 20, 128) 245888
_________________________________________________________________
max_pooling1d (MaxPooling1D) (None, 10, 128) 0
_________________________________________________________________
conv1d_4 (Conv1D) (None, 10, 128) 49280
_________________________________________________________________
max_pooling1d_1 (MaxPooling1 (None, 5, 128) 0
_________________________________________________________________
conv1d_5 (Conv1D) (None, 5, 128) 49280
_________________________________________________________________
global_max_pooling1d (Global (None, 128) 0
_________________________________________________________________
dense (Dense) (None, 1) 129
=================================================================
Total params: 443,649
Trainable params: 443,649
Non-trainable params: 0
_________________________________________________________________
Edit:
«почему вам нужно изменить форму и использовать n_features в качестве временного слоя»
Причина, по которой n_features изначально должна находиться на временном слое, заключается в том, что Conv1D с расширением и причинным заполнением работает только с одним объектом за раз, и из-за того, как реализован слой TimeDistributed.
Из их документации "Рассмотрим партию из 32 сэмплов, где каждый сэмпл представляет собой последовательность из 10 векторов 16 измерений. Тогда форма пакетного ввода слоя равна (32, 10, 16), а input_shape без учета размера образцов составляет (10, 16).
Затем вы можете использовать TimeDistributed для нанесения плотного слоя на каждый из 10 временных шагов независимо: "
Применение слоя TimeDistributed независимо к каждому объекту уменьшает размер проблемы, как если бы существовал только один объект (который легко учитывал бы расширение и причинное заполнение). С 5 функциями, они должны сначала обрабатываться отдельно.
После ваших изменений эта рекомендация остается в силе.
Не должно быть различий с точки зрения сети, независимо от того, включен ли InputLayer в первый слой или отдельно, так что вы определенно можете поместить его в первый CNN, если это решит проблему.