Edge AI with ML.NET Anomaly Detection Tutorial

Scripts → Tutorial | Concept | How-to Guide | Reference


Overview

This tutorial demonstrates using ML.NET 4.0 for real-time anomaly detection on sensor data using FrameworX Script Classes and displays.

Prerequisites

  • FrameworX with Scripts module
  • ML.NET 4.0 references (included in FrameworX)
  • Basic understanding of tags and displays


Step 1: Create Monitoring Tags

  1. Navigate to Unified Namespace → Tags
  2. Create the following tags:
    • Tag.MixerVibration (Double) - Simulated sensor value
    • Tag.Anomalies (String) - Will hold the anomaly list
    • Tag.SimulatorSeed (Integer) - For simulation consistency

Step 2: Create ML Anomaly Detection Script Class

  1. Navigate to Scripts → Classes
  2. Click "Create new script"
  3. Configure the new script:
    • Name: ML_Anomaly
    • Language: CSharp
    • Domain: Server
    • Select "Create new Code"Methods (Default)
    • ? Check "Add ML.NET namespaces" (Required for ML functionality)
  4. Click OK
  5. Replace the default code with:

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); } }

Step 3: Create Value Simulator

  1. Go to Scripts → Tasks
  2. Create task: SimulateVibration
  3. Set trigger: Period = 500ms
  4. Add code:

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
}

Step 4: Create Monitoring Task

  1. Go to Scripts → Tasks
  2. Create task: MonitorVibration
  3. Set trigger: Period = 1000ms (1 second)
  4. Add code:

csharp

// Call the ML anomaly detection
@Script.Class.ML_Anomaly.Check(@Tag.MixerVibration);

Alternatively:

  1. Go to Scripts → Expressions
  2. Add the same expression and trigger

Step 5: Create Display Components

  1. Open Displays → Create New Display
  2. Add a TextBox for Results:
    • Add a TextBox control
    • Configure in Settings tab:
      • Label Text: Last 10 anomalies
      • Linked Value: Tag.Anomalies
      • Binding: OutputOnly
      • ? Enable Multiline
  3. Add a Refresh Button:
    • Add a Button control
    • Label: Refresh
    • In the Dynamics tab, add an Action:
      • Event: MouseLeftButtonDown
      • Action: RunExpressions
      • Result (optional): Tag.Anomalies
      • Expression: Script.Class.ML_Anomaly.GetAnomalies()

Step 6: Test the System

  1. Start Runtime (F5)
  2. Open your display
  3. Observe the simulated vibration values
  4. Click "Refresh" button periodically
  5. The TextBox will display:
    • "No new anomalies detected" when buffer is empty
    • List of anomalies with format: 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

How It Works

  1. SimulateVibration generates realistic sensor data with 5% anomalies
  2. MonitorVibration checks each value using ML model
  3. ML_Anomaly class uses SR-CNN algorithm to detect spikes
  4. Anomalies are buffered internally (max 10)
  5. Refresh button retrieves and clears the buffer

Key Learning Points

? 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


Next Steps

  • Connect real sensor data instead of simulator
  • Adjust SR-CNN threshold (0.15) for sensitivity
  • Crate alarms for critical anomalies
  • Store anomaly history in Historian
  • Create trending displays for pattern analysis

This completes the ML.NET anomaly detection implementation in FrameworX!


In this section...