Edge AI with ML.NET Anomaly Detection Tutorial
Scripts → Tutorial | Concept | How-to Guide | Reference
This tutorial demonstrates using ML.NET 4.0 for real-time anomaly detection on sensor data using FrameworX Script Classes and displays.
Tag.MixerVibration
(Double) - Simulated sensor valueTag.Anomalies
(String) - Will hold the anomaly listTag.SimulatorSeed
(Integer) - For simulation consistencyML_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 void 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}");
}
}
// 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
@Script.Class.ML_Anomaly.Check(@Tag.MixerVibration);
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
This completes the ML.NET anomaly detection implementation in FrameworX!