Predict a continuous value from multiple input tags using ML.NET FastTree Regression. The AI generates the full C# Script Class with multi-feature training pipeline, connects it to live tags, creates output tags, and configures model persistence.
| Table of Contents |
|---|
| Table of Contents |
|---|
Prerequisites
Category | Items |
|---|---|
Tools |
|
Tables |
|
| Code Block |
|---|
get_table_schema('UnsTags')
|
| Code Block |
|---|
{
"table_type": "UnsTags",
"data": [
{ "Name": "<AssetPath>/ML/PredictedValue", "DataType": "Double", "Description": "Model predicted value" },
{ "Name": "<AssetPath>/ML/LastPrediction", "DataType": "DateTime", "Description": "Timestamp of last prediction" }
]
}
|
Replace <AssetPath> with the actual asset folder path (e.g., Plant/Reactor1).
| Warning |
|---|
Critical: The field |
| Code Block |
|---|
"NamespaceDeclarations": "Microsoft.ML;Microsoft.ML.Data;Microsoft.ML.Transforms;Microsoft.ML.Transforms.TimeSeries;Microsoft.ML.Transforms.Text;Microsoft.ML.Trainers;Microsoft.ML.TimeSeries"
|
Always use @Tag. prefix. ML.NET expects float but FrameworX tags use double — always cast with (float) when feeding ML.NET and (double) when writing to tags.
| Code Block | ||
|---|---|---|
| ||
var pipeline = mlContext.Transforms.Concatenate("Features", "Feature1", "Feature2", "Feature3")
.Append(mlContext.Regression.Trainers.FastTree(
labelColumnName: "Label",
featureColumnName: "Features",
numberOfLeaves: 20,
numberOfTrees: 100,
minimumExampleCountPerLeaf: 10,
learningRate: 0.2));
|
Output: float Score (predicted continuous value)
| Code Block | ||
|---|---|---|
| ||
public class ProcessData
{
public float Feature1 { get; set; } // e.g., Temperature
public float Feature2 { get; set; } // e.g., Pressure
public float Feature3 { get; set; } // e.g., Flow
public float Label { get; set; } // e.g., EnergyConsumption (what we predict)
}
public class RegressionPrediction
{
public float Score { get; set; }
}
private static MLContext mlContext = new MLContext(seed: 0);
private static ITransformer model;
private static IDataView lastTrainingDataView;
private static PredictionEngine<ProcessData, RegressionPrediction> predictionEngine;
private static bool modelTrained = false;
private static List<ProcessData> trainingBuffer = new List<ProcessData>();
private const int MinTrainingSize = 200;
private static readonly string ModelPath = Path.Combine(@Info.GetExecutionPath(), "<ClassName>.mlnet");
public void Predict(double input1, double input2, double input3, double label)
{
trainingBuffer.Add(new ProcessData
{
Feature1 = (float)input1,
Feature2 = (float)input2,
Feature3 = (float)input3,
Label = (float)label
});
if (!modelTrained && trainingBuffer.Count >= MinTrainingSize)
TrainModel();
if (modelTrained)
RunPrediction(input1, input2, input3);
}
public void LoadModel()
{
if (File.Exists(ModelPath))
{
model = mlContext.Model.Load(ModelPath, out _);
predictionEngine = mlContext.Model.CreatePredictionEngine<ProcessData, RegressionPrediction>(model);
modelTrained = true;
}
}
private void TrainModel()
{
lastTrainingDataView = mlContext.Data.LoadFromEnumerable(trainingBuffer);
var pipeline = mlContext.Transforms.Concatenate("Features",
nameof(ProcessData.Feature1),
nameof(ProcessData.Feature2),
nameof(ProcessData.Feature3))
.Append(mlContext.Regression.Trainers.FastTree(
labelColumnName: "Label",
featureColumnName: "Features",
numberOfLeaves: 20,
numberOfTrees: 100,
minimumExampleCountPerLeaf: 10,
learningRate: 0.2));
model = pipeline.Fit(lastTrainingDataView);
predictionEngine = mlContext.Model.CreatePredictionEngine<ProcessData, RegressionPrediction>(model);
modelTrained = true;
SaveModel();
}
private void SaveModel()
{
mlContext.Model.Save(model, lastTrainingDataView.Schema, ModelPath);
}
private void RunPrediction(double input1, double input2, double input3)
{
var input = new ProcessData
{
Feature1 = (float)input1,
Feature2 = (float)input2,
Feature3 = (float)input3
};
var result = predictionEngine.Predict(input);
@Tag.<AssetPath>/ML/PredictedValue.Value = (double)result.Score;
@Tag.<AssetPath>/ML/LastPrediction.Value = DateTime.Now;
}
|
Note on training data: The Predict() method accepts a label parameter during training. This is the known actual value the model learns to predict. After training, the label is not needed for prediction-only calls. The AI should adapt the method signature based on whether the user has a label tag or trains from historical data.
| Code Block |
|---|
get_table_schema('ScriptsClasses')
|
| Code Block |
|---|
{
"table_type": "ScriptsClasses",
"data": [
{
"Name": "<ClassName>",
"Code": "CSharp",
"Domain": "Server",
"ClassContent": "Methods",
"NamespaceDeclarations": "Microsoft.ML;Microsoft.ML.Data;Microsoft.ML.Transforms;Microsoft.ML.Transforms.TimeSeries;Microsoft.ML.Transforms.Text;Microsoft.ML.Trainers;Microsoft.ML.TimeSeries",
"Contents": "<AI-generated C# code from full class example above>"
}
]
}
|
| Warning |
|---|
Field names matter: |
| Code Block |
|---|
get_table_schema('ScriptsTasks')
|
| Code Block |
|---|
{
"table_type": "ScriptsTasks",
"data": [
{
"Name": "ML_Predict_Periodic",
"Language": "CSharp",
"Execution": "Periodic",
"Period": 5000,
"Code": "@Script.Class.<ClassName>.Predict(\n @Tag.Plant/Reactor1/Temperature.Value,\n @Tag.Plant/Reactor1/Pressure.Value,\n @Tag.Plant/Reactor1/Flow.Value,\n @Tag.Plant/Reactor1/EnergyConsumption.Value);"
}
]
}
|
| Warning |
|---|
The |
Read the existing ServerStartup task first (document object — read-modify-write), then append:
| Code Block | ||
|---|---|---|
| ||
Script.Class.<ClassName>.LoadModel();
|
MinTrainingSize data points (default 200 for regression)LastPrediction updates and PredictedValue is reasonableMistake | Why It Happens | How to Avoid |
|---|---|---|
Missing ML.NET namespaces | Used | Always set |
| Missing | Always |
| Build order issue | Set |
| .NET 4.8 target | Switch to Multiplatform |
Wrong data types | ML.NET=float, tags=double | Cast |
Model lost on restart | SaveModel/LoadModel missing | Include both + ServerStartup |
PredictionEngine null | Forgot to create after loading | Call |
Feature count mismatch | Concatenate names don't match data class | Use |
| Children Display |
|---|