How do I predict a timeseries which has dynamic values in a double[] array as input, and changing with time in an ML.NET context?

28 views Asked by At

Below is a class of my code which I will be using on the main program of a Windows Forms Application in Visual Studio.

My input values are in double[] vs = new double[120]; and the values are updated each second. So there is an incrementing value int t=0; which increments with time. But these two values are not in this class. They are on the main program.

This class Edited on 26th for the latest update

using Microsoft.ML;
using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using Tensorflow;

class MLNET
{
private MLContext mlContext; // Declare mlContext at the class level
private ITransformer trainedModel; // Separate model for training
private PredictionEngine<TimeSeriesData, TimeSeriesPrediction> predictionEngine; // Separate prediction engine
int windowSize = 30; // Set the desired window size

public class TimeSeriesData
{
    [LoadColumn(0, 30)]
    public double[] Features;

    [LoadColumn(1)]
    public double[] Label;

    
}
public class TimeSeriesPrediction
{
    [ColumnName("Score")]
    public double[] Value { get; set; } = new double[10]; // Adjust the size
}

public ITransformer TrainModel(IEnumerable<TimeSeriesData> historicalData)
{
    try
    {
        // Initialize mlContext
        mlContext = new MLContext();

        // Check Label column data type in training data
        var labelColumnDataType = historicalData.First().Label.GetType();
        Console.WriteLine($"Label Column Data Type in Training Data: {labelColumnDataType}");

        // Load and prepare your time series data
        var data = mlContext.Data.LoadFromEnumerable(historicalData);

        // Define the pipeline
        /*var pipeline = mlContext.Transforms.Conversion.MapValueToKey("Label", "Label")
         .Append(mlContext.Transforms.Conversion.MapValueToKey("Features")) // Adjust based on your needs
         .Append(mlContext.Transforms.Conversion.MapKeyToValue("Label"));*/// working code
                                                                           
        // Define the pipeline
        var pipeline = mlContext.Transforms.Conversion.MapValueToKey("Label")
            .Append(mlContext.Transforms.Conversion.MapKeyToValue("Label"));

        // Train the model
        trainedModel = pipeline.Fit(data);
        // Log some information about the training process
        Console.WriteLine("Model trained successfully.");
       
        //log some addtional information about the output schema
        var originalSchema = data.Schema;
        var transformedData = pipeline.Fit(data).Transform(data);
        var transformedSchema = transformedData.Schema;

        Console.WriteLine("Original Schema:");
        foreach (var column in originalSchema)
        {
            Console.WriteLine($"Column Name: {column.Name}, Data Type: {column.Type}");
        }

        Console.WriteLine("\nTransformed Schema:");
        foreach (var column in transformedSchema)
        {
            Console.WriteLine($"Column Name: {column.Name}, Data Type: {column.Type}");
        }
        return trainedModel;
    }
    catch (Exception ex)
    {
        // Log the full exception details
        Console.WriteLine($"Error during training: {ex}");

        // Propagate the exception to the caller
        throw;
    }
}

public double[] Predict(double[] inputValues)
{
    // Check Label column data type in prediction data
    var labelDataTypeInInput = inputValues.GetType();
    Console.WriteLine($"Label Column Data Type in Prediction Input: {labelDataTypeInInput}");

    // Check if the trained model is null and retrain if necessary
    if (trainedModel == null)
    {
        // Log a warning or handle this differently based on your application
        Console.WriteLine("Warning: Model is null. Retraining the model.");
        trainedModel = TrainModelFromDoubleArray(inputValues, windowSize);
    }
    else 
    {
        // Log a warning or handle this differently based on your application
        Console.WriteLine("Warning: Model is not null. Retraining anyway.");
        trainedModel = TrainModelFromDoubleArray(inputValues, windowSize);
    }
    // Debug: Output the model status
    Console.WriteLine($"Model Status: {(trainedModel != null ? "Trained" : "Not Trained")}");

    // Check if the prediction engine is null and create a new one if necessary
    if (predictionEngine == null)
    {
        // Log a warning or handle this differently based on your application
        Console.WriteLine("Warning: Prediction engine is null. Creating a new one.");
        predictionEngine = mlContext.Model.CreatePredictionEngine<TimeSeriesData, TimeSeriesPrediction>(trainedModel);
    }
    // Add debug output
    Console.WriteLine($"Input Values Length (Before Adjustment): {inputValues.Length}");

    // Adjust the input array to match the expected window size
    inputValues = inputValues.Skip(inputValues.Length - windowSize).ToArray();

    // Add debug output
    Console.WriteLine($"Input Values Length (After Adjustment): {inputValues.Length}");

    // Create a new TimeSeriesData instance with the adjusted input values
    var newData = new TimeSeriesData { Features = inputValues, Label = new double[10] };

    //check if the features passed to the prediction engine are not null
    Console.WriteLine($"Input Features: {string.Join(", ", inputValues)}");

    // Make predictions
    var prediction = predictionEngine.Predict(newData);
    Console.WriteLine($"Prediction: {prediction.GetDataType()}");

    // Check if prediction.Value is not null before attempting to join
    if (prediction.Value != null)
    {
        // Log the predicted value
        Console.WriteLine($"Predicted Value: {prediction.Value[9]}");
        // Extract the predicted value
        return prediction.Value;
    }
    else
    {
        // Handle the case where prediction.Value is null (log a warning or handle it based on your application's logic)
        Console.WriteLine("Warning: Predicted values are null.");
        return new double[0]; // or return another default value
    }
}

public ITransformer TrainModelFromDoubleArray(double[] historicalData, int windowSize)
{
    // Convert the double[] to a list of TimeSeriesData
    var timeSeriesDataList = new List<TimeSeriesData>();

    // Use values from vs[0] to vs[vs.Length - 31] for training
    for (int i = 0; i < historicalData.Length - windowSize - 1; i++)
    {
        // Use the specified sliding window size
        var windowValues = historicalData.Skip(i).Take(windowSize).ToArray();

        // Adjusted code
        var timeSeriesData = new TimeSeriesData
        {
            Features = windowValues,
            Label = new double[] { historicalData[i + windowSize + 1] }
        };
        timeSeriesDataList.Add(timeSeriesData);
    }

    // Return the trained model with the specified window size
    return TrainModel(timeSeriesDataList);
}
}

and this is what is logged in the console

Transformed Schema:
Column Name: Features, Data Type: Vector<Double>
Column Name: Label, Data Type: Vector<Double>
Column Name: Label, Data Type: Vector<Key<UInt32, 0-77>>
Column Name: Label, Data Type: Vector<Double>

Model Status: Trained

Input Values Length (Before Adjustment): 120

Input Values Length (After Adjustment): 30

Input Features: 1679.94, 1679.88, 1680.32, 1680.23, 1680.53, 1680.36, 1680.05, 1680.12, 1680.01, 1679.56, 1679.41, 1679.07, 1679.39, 1679.16, 1679.58, 1679.39, 1679.31, 1679.24, 1679.51, 1679.69, 1679.78, 1679.53, 1679.12, 1679, 1678.92, 1679.3, 1678.74, 1678.99, 1679.4, 1679.18

Prediction: DtInvalid

Predicted Value: 0

Although I tried my best, I couldn't get the predicted value to anything other than zero.

What am I doing wrong, and how can I make sure that the predicted values are not zero?

0

There are 0 answers