Multi-feature causal CNN - Keras implementation

2024/9/28 9:30:07

I'm currently using a basic LSTM to make regression predictions and I would like to implement a causal CNN as it should be computationally more efficient.

I'm struggling to figure out how to reshape my current data to fit the causal CNN cell and represent the same data/timestep relationship as well as what the dilation rate should be set at.

My current data is of this shape: (number of examples, lookback, features) and here's a basic example of the LSTM NN I'm using right now.

lookback = 20   #  height -- timeseries
n_features = 5  #  width  -- features at each timestep# Build an LSTM to perform regression on time series input/output data
model = Sequential()
model.add(LSTM(units=256, return_sequences=True, input_shape=(lookback, n_features)))
model.add(Activation('elu'))model.add(LSTM(units=256, return_sequences=True))
model.add(Activation('elu'))model.add(LSTM(units=256))
model.add(Activation('elu'))model.add(Dense(units=1, activation='linear'))model.compile(optimizer='adam', loss='mean_squared_error')model.fit(X_train, y_train,epochs=50, batch_size=64,validation_data=(X_val, y_val),verbose=1, shuffle=True)prediction = model.predict(X_test)

I then created a new CNN model (although not causal as the 'causal' padding is only an option for Conv1D and not Conv2D, per Keras documentation. If I understand correctly, by having multiple features, I need to use Conv2D, rather than Conv1D but then if I set Conv2D(padding='causal'), I get the following error - Invalid padding: causal)

Anyways, I was also able to fit the data with a new shape (number of examples, lookback, features, 1) and run the following model using the Conv2D Layer:

lookback = 20   #  height -- timeseries
n_features = 5  #  width  -- features at each timestepmodel = Sequential()model.add(Conv2D(128, 3, activation='elu', input_shape=(lookback, n_features, 1)))
model.add(MaxPool2D())
model.add(Conv2D(128, 3, activation='elu'))
model.add(MaxPool2D())
model.add(Flatten())
model.add(Dense(1, activation='linear'))model.compile(optimizer='adam', loss='mean_squared_error')model.fit(X_train, y_train,epochs=50, batch_size=64,validation_data=(X_val, y_val),verbose=1, shuffle=True)prediction = model.predict(X_test)

However, from my understanding, this does not propagate the data as causal, rather just the entire set (lookback, features, 1) as an image.

Is there any way to either reshape my data to fit into a Conv1D(padding='causal') Layer, with multiple features or somehow run the same data and input shape as Conv2D with 'causal' padding?

Answer

I believe that you can have causal padding with dilation for any number of input features. Here is the solution I would propose.

The TimeDistributed layer is key to this.

From Keras Documentation: "This wrapper applies a layer to every temporal slice of an input. The input should be at least 3D, and the dimension of index one will be considered to be the temporal dimension."

For our purposes, we want this layer to apply "something" to each feature, so we move the features to the temporal index, which is 1.

Also relevant is the Conv1D documentation.

Specifically about channels: "The ordering of the dimensions in the inputs. "channels_last" corresponds to inputs with shape (batch, steps, channels) (default format for temporal data in Keras)"

from tensorflow.python.keras import Sequential, backend
from tensorflow.python.keras.layers import GlobalMaxPool1D, Activation, MaxPool1D, Flatten, Conv1D, Reshape, TimeDistributed, InputLayerbackend.clear_session()
lookback = 20
n_features = 5filters = 128model = Sequential()
model.add(InputLayer(input_shape=(lookback, n_features, 1)))
# Causal layers are first applied to the features independently
model.add(Permute(dims=(2, 1)))  # UPDATE must permute prior to adding new dim and reshap
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 mapsmodel.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(Permute(dims=(2, 1, 3)))  # UPDATED to fix issue with reshape
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()

Final 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:

"why you need to reshape and use n_features as the temporal layer"

The reason why n_features needs to be at the temporal layer initially is because Conv1D with dilation and causal padding only works with one feature at a time, and because of how the TimeDistributed layer is implemented.

From their documentation "Consider a batch of 32 samples, where each sample is a sequence of 10 vectors of 16 dimensions. The batch input shape of the layer is then (32, 10, 16), and the input_shape, not including the samples dimension, is (10, 16).

You can then use TimeDistributed to apply a Dense layer to each of the 10 timesteps, independently:"

By applying the TimeDistributed layer independently to each feature, it reduces the dimension of the problem as if there was only one feature (which would easily allow for dilation and causal padding). With 5 features, they need to each be handled separately at first.

  • After your edits this recommendation still applies.

  • There shouldn't be a difference in terms of the network whether InputLayer is included in the first layer or separate so you can definitely put it in the first CNN if that resolves the issue.

https://en.xdnf.cn/q/71351.html

Related Q&A

Adding a join to an SQL Alchemy expression that already has a select_from()

Note: this is a question about SQL Alchemys expression language not the ORMSQL Alchemy is fine for adding WHERE or HAVING clauses to an existing query:q = select([bmt_gene.c.id]).select_from(bmt_gene) …

How should I move blobs from BlobStore over to Google Cloud Storage?

Our application has been running on App Engine using the Blobstore for years. We would like to move our video files over to Google Cloud Storage. What is the best practice for migrating large blobs f…

Python: Find `sys.argv` before the `sys` module is loaded

I want to find the command line arguments that my program was called with, i.e. sys.argv, but I want to do that before Python makes sys.argv available. This is because Im running code in usercustomize.…

Dotted lines instead of a missing value in matplotlib

I have an array of some data, where some of the values are missingy = np.array([np.NAN, 45, 23, np.NAN, 5, 14, 22, np.NAN, np.NAN, 18, 23])When I plot it, I have these NANs missing (which is expected)f…

How to change the creation date of file using python on a mac?

I need to update the creation time of a .mp4 file so that it will appear at the top of a list of media files sorted by creation date. I am able to easily update both the accessed and modified date of …

Classification tree in sklearn giving inconsistent answers

I am using a classification tree from sklearn and when I have the the model train twice using the same data, and predict with the same test data, I am getting different results. I tried reproducing on…

Modifying binary file with Python

i am trying to patch a hex file. i have two patch files (hex) named "patch 1" and "patch 2"the file to be patched is a 16 MB file named "file.bin".i have tried many differ…

python error : module object has no attribute AF_UNIX

this is my python code :if __name__ == __main__: import socket sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect((0.0.0.0, 4000)) import time time.sleep(2) #sock.send(1)print …

How to speed up pandas string function?

I am using the pandas vectorized str.split() method to extract the first element returned from a split on "~". I also have also tried using df.apply() with a lambda and str.split() to produc…

sqlalchemy autoloaded orm persistence

We are using sqlalchemys autoload feature to do column mapping to prevent hardcoding in our code.class users(Base):__tablename__ = users__table_args__ = {autoload: True,mysql_engine: InnoDB,mysql_chars…