[Python] Is it valid to combine lagged and topological (TDA) features in LSTM to predict the...

Discussão em 'Python' iniciado por Stack, Junho 10, 2025.

  1. Stack

    Stack Membro Participativo

    I am working on a time series forecasting problem using an LSTM model to predict the next travel time value at each time step. The dataset contains 5-minute aggregated travel time values for a specific trip.

    To enhance the model's performance, I combined:

    10 lagged travel time values (i.e., t-1 to t-10) using sliding window method

    9 topological features extracted from the same 10-value window using persistent homology (e.g., Betti numbers, entropy, L1/L2 norms)

    At each time step, I:


    1. Take a rolling window of 10 past values.


    2. Extract 9 TDA-based features from that window (they do not change in a specific window and are generated once in a window).


    3. Retain the same 10 values as lag features.


    4. Concatenate both to form a 19-length feature vector.


    5. Use this to predict the next travel time (t+1).

    Then I reshape the data as:

    samples = number of windows

    timesteps = 1

    features = 19 (9 TDA + 10 lags)

    My question: Is this approach valid for LSTM-based time series forecasting?

    Specifically:

    Is it correct to use both lagged values and summary features (TDA) from the same window?

    Does this structure maintain temporal integrity?

    Are there alternative/better architectures when mixing static + sequential features?

    Any insights from those with experience in hybrid input LSTM models would be appreciated.

    Here’s a sample of my feature engineering and reshaping pipeline in google colab:

    def extract_all_features(series, win=10, dim=3, delay=1):
    data = []
    for i in range(len(series) - win - 10):
    segment = series[i:i+win]
    cloud = time_delay_embedding(segment, delay, dim)
    rips = gd.RipsComplex(points=cloud).create_simplex_tree(max_dimension=2)
    rips.persistence()
    h0 = rips.persistence_intervals_in_dimension(0)
    h1 = rips.persistence_intervals_in_dimension(1)
    h0_vals = replace_inf(h0)
    h1_vals = replace_inf(h1)
    row = [
    len(h0_vals), np.mean(h0_vals) if h0_vals else 0,
    persistent_entropy([(0, p) for p in h0_vals]),
    l_norm([(0, p) for p in h0_vals], p=1),
    l_norm([(0, p) for p in h0_vals], p=2),
    len(h1_vals), np.mean(h1_vals) if h1_vals else 0,
    l_norm([(0, p) for p in h1_vals], p=1),
    l_norm([(0, p) for p in h1_vals], p=2)
    ]
    lags = [series[i + win - j] for j in range(1, 11)]
    data.append(row + lags + [series[i + win]])
    return np.array(data)

    # Reshape for LSTM
    X = features[:, :-1]
    y = features[:, -1]
    X_scaled = MinMaxScaler().fit_transform(X)
    y_scaled = MinMaxScaler().fit_transform(y.reshape(-1, 1))

    X_train = X_scaled[train_mask]
    X_val = X_scaled[val_mask]
    X_train = X_train.reshape((X_train.shape[0], 1, X_train.shape[1]))
    X_val = X_val.reshape((X_val.shape[0], 1, X_val.shape[1]))

    # LSTM model
    model = Sequential([
    LSTM(64, return_sequences=True, input_shape=(1, X_train.shape[2])),
    Dropout(0.2),
    LSTM(32),
    Dropout(0.2),
    Dense(1)
    ])
    model.compile(optimizer='adam', loss='mse')
    model.fit(X_train, y_train, validation_data=(X_val, y_val),
    epochs=100, batch_size=16, callbacks=[EarlyStopping(patience=10)])```

    Continue reading...

Compartilhe esta Página