How to build an ML Model.
Edge AI with ML.NET Anomaly Detection Tutorial
Scripts → Tutorial | Concept | How-to Guide | Reference
This tutorial demonstrates
using ML.NET 4.0how to use machine learning for real-time anomaly detection on sensor data using FrameworX Script Classes
and displays.
Table of Contents maxLevel 2 minLevel 2 indent 10px exclude Steps style none
Tag.MixerVibration
(Double) - Simulated sensor valueTag.Anomalies
(String) - Will hold the anomaly listTag.SimulatorSeed
(Integer) - For simulation consistencyWith these tags created: Pressure (Integer) and AnomalyBuffer (Text Array 9 position)
In Devices → Protocols, select the Value Simulator and click the "New Channel..." button.
In Devices → Points, create points to generate simulated data.
TagName | Node | Address | DataType | AccessType |
---|---|---|---|---|
Tag.Pressure | Node.ValueSimulator1Node1 | INTEGER:0,100,1 | Native | AccessType.Read |
For more information about the Value Simulator, see: Value Simulator Connector
Navigate to Scripts → Classes
Click
the
ML_Anomaly
csharp
// HOW TO USE
// Create an expression or Task to run periodically with the code:
// @Script.Class.ML_Anomaly.Check(@Tag.YourSensorValue);
// To retrieve anomalies: var list = @Script.Class.ML_Anomaly.GetAnomalies();
// Check if value is anomalous
public double Check(double currentValue) {
// Initialize ML engine if needed
if (_engine == null)
Initialize();
// Create data point
var data = new SensorData {
Timestamp = DateTime.Now,
SensorValue = currentValue };
// Get prediction from ML model
AnomalyPrediction prediction = _engine.Predict(data);
// Store anomaly if detected
if (prediction.Prediction[0] >= 1) { // >= 1 means anomaly detected
if (_anomalyBuffer.Count >= 10)
_anomalyBuffer.Dequeue();
_anomalyBuffer.Enqueue($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}|{currentValue:F3}|{prediction.Prediction[1]:F2}");
}
return Prediction[0];
}
// Internal buffer to store last 10 anomalies
private static Queue<string> _anomalyBuffer = new Queue<string>(10);
// Method to retrieve anomalies and clear buffer
public string GetAnomalies() {
if (_anomalyBuffer.Count == 0)
return "No new anomalies detected";
var anomalies = string.Join(Environment.NewLine, _anomalyBuffer);
_anomalyBuffer.Clear();
return anomalies;
}
//The following code is standard - no changes needed
public class SensorData {
public DateTime Timestamp { get; set; }
public double SensorValue { get; set; }
}
public class AnomalyPrediction {
// SR-CNN returns an array with 3 doubles:
// [0] = flag (0 or 1) - Binary indicator if anomaly detected
// [1] = score (0.0 to 1.0) - Confidence/severity of the anomaly
// [2] = baseline - Expected normal value at this point
// Note: This tutorial uses [0] for simplicity. Advanced users can leverage [1] and [2] for custom thresholds.
[VectorType(3)]
public double[] Prediction { get; set; }
}
private ITransformer _model;
private TimeSeriesPredictionEngine<SensorData, AnomalyPrediction> _engine;
private MLContext _mlContext;
List<SensorData> dataTrain;
private void Initialize() {
_mlContext = new MLContext();
if (dataTrain == null) {
DateTime now = DateTime.Now;
// Training data with clear normal pattern (wave-like)
var trainingData = new List<(int minutesAgo, double value)> {
(15, 0.20), (14, 0.25), (13, 0.30), (12, 0.25), // Rising
(11, 0.20), (10, 0.15), (9, 0.20), (8, 0.25), // Valley to peak
(7, 0.30), (6, 0.25), (5, 0.20), (4, 0.15), // Falling
(3, 0.20), (2, 0.25), (1, 0.30), (0, 0.25) // Rising
};
dataTrain = new List<SensorData>();
foreach (var item in trainingData) {
dataTrain.Add(new SensorData {
Timestamp = now.AddMinutes(-item.minutesAgo),
SensorValue = item.value
});
}
}
// SR-CNN in ML.NET - use these default values for most applications
SrCnnAnomalyEstimator pipeline = _mlContext.Transforms
.DetectAnomalyBySrCnn(
outputColumnName: nameof(AnomalyPrediction.Prediction),
inputColumnName: nameof(SensorData.SensorValue),
windowSize: 32,
backAddWindowSize: 32,
lookaheadWindowSize: 16,
averagingWindowSize: 16,
judgementWindowSize: 16,
threshold: 0.15);
IDataView dataView = _mlContext.Data.LoadFromEnumerable(dataTrain);
_model = pipeline.Fit(dataView);
_engine = _model.CreateTimeSeriesEngine<SensorData, AnomalyPrediction>(_mlContext);
// Train the model with initial data
foreach (var row in dataTrain) {
_engine.Predict(row);
}
}
SimulateVibration
csharp
// Simulate normal sensor data with occasional anomalies
if (@Tag.SimulatorSeed == 0)
@Tag.SimulatorSeed = Environment.TickCount;
Random rand = new Random(@Tag.SimulatorSeed + DateTime.Now.Millisecond);
// Base vibration level with sine wave pattern
double time = DateTime.Now.Ticks / 10000000.0;
double baseValue = 0.25 + 0.05 * Math.Sin(time);
double noise = (rand.NextDouble() - 0.5) * 0.02;
// 5% chance of anomaly
if (rand.NextDouble() < 0.05) {
@Tag.MixerVibration = baseValue + (rand.NextDouble() * 0.5 + 0.3); // Spike
} else {
@Tag.MixerVibration = baseValue + noise; // Normal
}
MonitorVibration
csharp
// Call the ML anomaly detection"Create a New Class" button
In "Import code from Library:", select AnomalyML
Open the script and uncomment the line that returns the detection to the AnomalyBuffer tag in Check() method.
This expression will check for anomalies each time the tag value changes.
Go to Scripts → Expressions
Create the following expression:
ObjectName | Expression | Execution |
---|---|---|
Script.Class.AnomalyML.Check(<DesiredTag>) | OnChange |
Where:
<DesiredTag> is the tag you want to monitor for anomalies
Example:
ObjectName | Expression | Execution |
---|---|---|
Script.Class.AnomalyML.Check(Tag.Pressure) | OnChange |
Go in Runtime → “Run Startup”
Wait a couple minutes to have some data in the model.
Open the PropertyWatch
See the values in the AnomalyBuffer, to see the predictions.
Alternatively:
Last 10 anomalies
Tag.Anomalies
OutputOnly
Refresh
MouseLeftButtonDown
RunExpressions
Tag.Anomalies
Script.Class.ML_Anomaly.GetAnomalies()
timestamp|value|severity
Example output:
2025-01-15 10:23:45|0.895|0.82
2025-01-15 10:24:12|0.756|0.91
2025-01-15 10:25:33|0.923|0.75
? ML.NET Integration: Must enable ML.NET namespaces when creating script class
? Simple API: Single method call Check()
for detection
? Self-contained: No external tags needed for ML state
? Production Ready: Can replace simulator with real sensor tags
Page Tree | ||
---|---|---|
|