Back to Documentations

Signature Description Parameters
#include <DataFrame/DataFrameMLVisitors.h>

template<arithmetic T, typename I = unsigned long>
struct  LSTMForecastVisitor;

// -------------------------------------

template<typename T, typename I = unsigned long>
using lstm_v = LSTMForecastVisitor<T, I>;
This is a "single action visitor", meaning it is passed the whole data vector in one call and you must use the single_act_visit() interface.

Long Short-Term Memory (LSTM) forecasting is a technique that uses a type of recurrent neural network to predict future values in a time series, excelling at capturing long-term dependencies in sequential data. LSTMs are effective for forecasting because their architecture, featuring a memory cell and three gates, allows them to remember and process information from past time steps, overcoming the vanishing gradient problem that affects traditional recurrent neural networks. This makes them well-suited for predicting future trends in fields like stock prices, streamflow, and groundwater levels.

This visitor has the following methods to get result:
get_result(): Retruns a vector of forecasted datapoints for the next periods periods ahead.
explicit
LSTMForecastVisitor(long input_size = 1,
                    long hidden_size = 32,
                    long seq_len = 10,
                    long batch_size = 1,
                    long epochs = 20,
                    value_type learning_rate = 0.001,
                    long periods = 3,
                    unsigned int seed = static_cast(-1));

input_size: Number of features per time step. Each input vector Xt has one feature. For example, if you’re
            forecasting a univariate time series (like daily temperature), each step is a single number.
hidden_size: Number of LSTM hidden units (neurons). Controls how much "memory" or capacity the LSTM has.
             Larger values -> can learn more complex temporal patterns, but need more data.
seq_len: Length of input sequence (T). Number of time steps fed to the LSTM before forecasting the next one.
         Example: using 10 days of data to predict day 11.
batch_size: Number of sequences processed together. 1 means one sequence at a time (simple for demonstration).
epochs: Number of training passes. How many times the model sees the full dataset.
        More epochs is more training iterations.
learning_rate: Step size for gradient descent. Controls how much weights are updated during training.
               Too high is unstable; too low is slow learning.
periods: Number of periods to forecast.
seed: Seed for random number generator.
        
T: Column data type.
I: Index type.
static void test_LSTMForecastVisitor()  {

    std::cout << "\nTesting LSTMForecastVisitor{ } ..." << std::endl;

    std::vector<unsigned long>  idxvec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
    std::vector<double>         col1 = { 266.0, 145.9, 183.1, 119.3, 180.3, 168.5, 231.8, 224.5, 192.8, 122.9, 336.5, 185.9, 194.3 };
    std::vector<double>         oscil = { 1.5, 1.8, 1.62, 1.78, 1.5, 1.68, 1.6, 1.8, 1.71, 1.9, 1.78, 1.84, 1.69 };
    std::vector<double>         constant = { 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56, 10.56 };
    std::vector<double>         increasing = { 10.56, 10.68, 10.78, 10.90, 11.01, 11.45, 11.99, 12.01, 12.21, 12.35, 12.67, 13.89, 14.01 };
    std::vector<double>         decreasing = { 10.56, 10.30, 10.12, 10.01, 9.80, 9.74, 9.41, 9.03, 9.0, 8.20, 8.01, 7.9, 7.55 };
    MyDataFrame                 df;

    df.load_data(std::move(idxvec),
                 std::make_pair("col1", col1),
                 std::make_pair("oscil", oscil),
                 std::make_pair("constant", constant),
                 std::make_pair("increasing", increasing),
                 std::make_pair("decreasing", decreasing));

    lstm_v<double>  lstm { 1, 40, 4, 1, 100, 0.001, 3, 123 };

    df.single_act_visit<double>("col1", lstm);

    const auto  result1 = lstm.get_result();

    assert(result1.size() == 3);
    assert(std::fabs(result1[0] - 177.075) < 0.001);
    assert(std::fabs(result1[1] - 181.576) < 0.001);
    assert(std::fabs(result1[2] - 185.103) < 0.001);

    lstm_v<double>  lstm2 { 1, 40, 4, 1, 1000, 0.001, 3, 123 };

    df.single_act_visit<double>("oscil", lstm2);

    const auto  result2 = lstm2.get_result();

    assert(result2.size() == 3);
    assert(std::fabs(result2[0] - 1.7698) < 0.00001);
    assert(std::fabs(result2[1] - 1.64903) < 0.00001);
    assert(std::fabs(result2[2] - 1.74927) < 0.00001);

    df.single_act_visit<double>("constant", lstm);

    const auto  result3 = lstm.get_result();

    assert(result3.size() == 3);
    assert(std::fabs(result3[0] - 10.56) < 0.00001);
    assert(std::fabs(result3[1] - 10.56) < 0.00001);
    assert(std::fabs(result3[2] - 10.56) < 0.00001);

    lstm_v<double>  lstm3 { 1, 100, 4, 1, 100, 0.001, 3, 123 };

    df.single_act_visit<double>("increasing", lstm3);

    const auto  result4 = lstm3.get_result();

    // It doesn't see the pattern here
    //
    assert(result4.size() == 3);
    assert(std::fabs(result4[0] - 13.4408) < 0.0001);
    assert(std::fabs(result4[1] - 13.4358) < 0.0001);
    assert(std::fabs(result4[2] - 13.4193) < 0.0001);

    df.single_act_visit<double>("decreasing", lstm3);

    const auto  result5 = lstm3.get_result();

    // Strangely, it sees the pattern here
    //
    assert(result5.size() == 3);
    assert(std::fabs(result5[0] - 7.23448) < 0.00001);
    assert(std::fabs(result5[1] - 7.14929) < 0.00001);
    assert(std::fabs(result5[2] - 7.10167) < 0.00001);

    // Now some real data
    //
    StrDataFrame    df2;

    try  {
        df2.read("IBM.csv", io_format::csv2);
    }
    catch (const DataFrameError &ex)  {
        std::cout << ex.what() << std::endl;
        ::exit(-1);
    }

    lstm_v<double>  lstm4 { 1, 50, 4, 1, 100, 0.001, 4, 123 };

    df2.single_act_visit<double>("IBM_Close", lstm4);

    const auto  result6 = lstm4.get_result();

    assert(result6.size() == 4);
    assert(std::fabs(result6[0] - 175.419) < 0.001);
    assert(std::fabs(result6[1] - 100.078) < 0.001);
    assert(std::fabs(result6[2] - 186.713) < 0.001);
    assert(std::fabs(result6[3] - 99.2631) < 0.0001);
}

C++ DataFrame