From 74da250463cf6e8ec6edad13084a410fd5ddf53d Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Thu, 9 May 2019 07:51:24 -0700 Subject: [PATCH 1/9] Add SrCnn Anomaly Detector --- .../ExtensionsCatalog.cs | 4 + .../SRCNNAnomalyDetector.cs | 203 +++++++++++++++++ .../SrCnnAnomalyDetectionBase.cs | 143 ++++++++++++ .../SrCnnTransformBase.cs | 212 ++++++++++++++++++ 4 files changed, 562 insertions(+) create mode 100644 src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs create mode 100644 src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs create mode 100644 src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index e4a3da6761..140193c3b2 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -121,5 +121,9 @@ public static SsaChangePointEstimator DetectChangePointBySsa(this TransformsCata public static SsaSpikeEstimator DetectSpikeBySsa(this TransformsCatalog catalog, string outputColumnName, string inputColumnName, int confidence, int pvalueHistoryLength, int trainingWindowSize, int seasonalityWindowSize, AnomalySide side = AnomalySide.TwoSided, ErrorFunction errorFunction = ErrorFunction.SignedDifference) => new SsaSpikeEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, confidence, pvalueHistoryLength, trainingWindowSize, seasonalityWindowSize, inputColumnName, side, errorFunction); + + public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog catalog, string outputColumnName, string inputColumnName, + int windowSize, int backAddWindowSize, int lookaheadWindowSize, double threshold) + => new SrCnnAnomalyEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, windowSize, backAddWindowSize, lookaheadWindowSize, threshold, inputColumnName); } } diff --git a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs new file mode 100644 index 0000000000..6eda8375cb --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs @@ -0,0 +1,203 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.ML; +using Microsoft.ML.CommandLine; +using Microsoft.ML.Data; +using Microsoft.ML.Runtime; +using Microsoft.ML.Transforms.TimeSeries; + +[assembly: LoadableClass(SrCnnAnomalyDetector.Summary, typeof(IDataTransform), typeof(SrCnnAnomalyDetector), typeof(SrCnnAnomalyDetector.Options), typeof(SignatureDataTransform), + SrCnnAnomalyDetector.UserName, SrCnnAnomalyDetector.LoaderSignature, SrCnnAnomalyDetector.ShortName)] + +[assembly: LoadableClass(SrCnnAnomalyDetector.Summary, typeof(IDataTransform), typeof(SrCnnAnomalyDetector), null, typeof(SignatureLoadDataTransform), + SrCnnAnomalyDetector.UserName, SrCnnAnomalyDetector.LoaderSignature)] + +[assembly: LoadableClass(SrCnnAnomalyDetector.Summary, typeof(SrCnnAnomalyDetector), null, typeof(SignatureLoadModel), + SrCnnAnomalyDetector.UserName, SrCnnAnomalyDetector.LoaderSignature)] + +[assembly: LoadableClass(typeof(IRowMapper), typeof(SrCnnAnomalyDetector), null, typeof(SignatureLoadRowMapper), + SrCnnAnomalyDetector.UserName, SrCnnAnomalyDetector.LoaderSignature)] + +namespace Microsoft.ML.Transforms.TimeSeries +{ + public sealed class SrCnnAnomalyDetector : SrCnnAnomalyDetectionBaseWrapper, IStatefulTransformer + { + internal const string Summary = "This transform detects the anomalies in a time-series using SRCNN."; + internal const string LoaderSignature = "SrCnnAnomalyDetector"; + internal const string UserName = "SrCnn Anomaly Detection"; + internal const string ShortName = "srcnn"; + + internal sealed class Options : TransformInputBase + { + [Argument(ArgumentType.Required, HelpText = "The name of the source column.", ShortName = "src", + SortOrder = 1, Purpose = SpecialPurpose.ColumnName)] + public string Source; + + [Argument(ArgumentType.Required, HelpText = "The name of the new column.", + SortOrder = 2)] + public string Name; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing spectral residual", ShortName = "wnd", + SortOrder = 101)] + public int WindowSize = 24; + + [Argument(ArgumentType.Required, HelpText = "The number of points to the back of training window.", + ShortName = "bwnd", SortOrder = 102)] + public int BackAddWindowSize = 5; + + [Argument(ArgumentType.Required, HelpText = "The number of pervious points used in prediction.", + ShortName = "awnd", SortOrder = 103)] + public int LookaheadWindowSize = 5; + + [Argument(ArgumentType.Required, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", + ShortName = "thre", SortOrder = 104)] + public double Threshold = 0.3; + } + + private sealed class SrCnnArgument : SrCnnArgumentBase + { + public SrCnnArgument(Options options) + { + Source = options.Source; + Name = options.Name; + WindowSize = options.WindowSize; + InitialWindowSize = 0; + BackAddWindowSize = options.BackAddWindowSize; + LookaheadWindowSize = options.LookaheadWindowSize; + Threshold = options.Threshold; + } + + public SrCnnArgument(SrCnnAnomalyDetector transform) + { + Source = transform.InternalTransform.InputColumnName; + Name = transform.InternalTransform.OutputColumnName; + WindowSize = transform.InternalTransform.WindowSize; + InitialWindowSize = 0; + BackAddWindowSize = transform.InternalTransform.BackAddWindowSize; + LookaheadWindowSize = transform.InternalTransform.LookaheadWindowSize; + Threshold = transform.InternalTransform.AlertThreshold; + } + } + + private static VersionInfo GetVersionInfo() + { + return new VersionInfo( + modelSignature: "SRCNNTRNS", + verWrittenCur: 0x00010001, // Initial + verReadableCur: 0x00010001, + verWeCanReadBack: 0x00010001, + loaderSignature: LoaderSignature, + loaderAssemblyName: typeof(SrCnnAnomalyDetector).Assembly.FullName); + } + + private static IDataTransform Create(IHostEnvironment env, Options options, IDataView input) + { + Contracts.CheckValue(env, nameof(env)); + env.CheckValue(options, nameof(options)); + env.CheckValue(input, nameof(input)); + + return new SrCnnAnomalyDetector(env, options).MakeDataTransform(input); + } + + private static IDataTransform Create(IHostEnvironment env, ModelLoadContext ctx, IDataView input) + { + Contracts.CheckValue(env, nameof(env)); + env.CheckValue(ctx, nameof(ctx)); + env.CheckValue(input, nameof(input)); + + return new SrCnnAnomalyDetector(env, ctx).MakeDataTransform(input); + } + + private static SrCnnAnomalyDetector Create(IHostEnvironment env, ModelLoadContext ctx) + { + Contracts.CheckValue(env, nameof(env)); + env.CheckValue(ctx, nameof(ctx)); + ctx.CheckAtModel(GetVersionInfo()); + + return new SrCnnAnomalyDetector(env, ctx); + } + + private static IRowMapper Create(IHostEnvironment env, ModelLoadContext ctx, DataViewSchema inputSchema) + => Create(env, ctx).MakeRowMapper(inputSchema); + + IStatefulTransformer IStatefulTransformer.Clone() + { + var clone = (SrCnnAnomalyDetector)MemberwiseClone(); + clone.InternalTransform.StateRef = (SrCnnAnomalyDetectionBase.State)clone.InternalTransform.StateRef.Clone(); + clone.InternalTransform.StateRef.InitState(clone.InternalTransform, InternalTransform.Host); + return clone; + } + + internal SrCnnAnomalyDetector(IHostEnvironment env, Options options) + :base(new SrCnnArgument(options), LoaderSignature, env) + { + } + + internal SrCnnAnomalyDetector(IHostEnvironment env, ModelLoadContext ctx) + : base(env, ctx, LoaderSignature) + { + //TODO: + } + + private SrCnnAnomalyDetector(IHostEnvironment env, SrCnnAnomalyDetector transform) + : base(new SrCnnArgument(transform), LoaderSignature, env) + { + } + + private protected override void SaveModel(ModelSaveContext ctx) + { + //TODO: + } + } + + /// + /// Detect anomalies in time series using Spectral Residual + /// + public sealed class SrCnnAnomalyEstimator : TrivialEstimator + { + /// + /// Create a new instance of + /// + /// + /// + /// + /// + /// + /// + /// + internal SrCnnAnomalyEstimator(IHostEnvironment env, + string outputColumnName, + int windowSize, + int backAddWindowSize, + int lookaheadWindowSize, + double threshold = 0.3, + string inputColumnName = null) + : base(Contracts.CheckRef(env, nameof(env)).Register(nameof(SrCnnAnomalyEstimator)), + new SrCnnAnomalyDetector(env, new SrCnnAnomalyDetector.Options + { + Source = inputColumnName ?? outputColumnName, + Name = outputColumnName, + WindowSize = windowSize, + BackAddWindowSize = backAddWindowSize, + LookaheadWindowSize = lookaheadWindowSize, + Threshold = threshold + })) + { + } + + internal SrCnnAnomalyEstimator(IHostEnvironment env, SrCnnAnomalyDetector.Options options) + : base(Contracts.CheckRef(env, nameof(env)).Register(nameof(SrCnnAnomalyEstimator)), new SrCnnAnomalyDetector(env, options)) + { + } + + public override SchemaShape GetOutputSchema(SchemaShape inputSchema) + { + //TODO: + throw new NotImplementedException(); + } + + } +} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs new file mode 100644 index 0000000000..0b6f277255 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Microsoft.ML.Data; +using Microsoft.ML.Internal.Utilities; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.Transforms.TimeSeries +{ + public class SrCnnAnomalyDetectionBaseWrapper : IStatefulTransformer, ICanSaveModel + { + /// + /// Whether a call to should succeed, on an + /// appropriate schema. + /// + bool ITransformer.IsRowToRowMapper => ((ITransformer)InternalTransform).IsRowToRowMapper; + + /// + /// Create a clone of the transformer. Used for taking the snapshot of the state. + /// + /// + IStatefulTransformer IStatefulTransformer.Clone() => InternalTransform.Clone(); + + /// + /// Schema propagation for transformers. + /// Returns the output schema of the data, if the input schema is like the one provided. + /// + public DataViewSchema GetOutputSchema(DataViewSchema inputSchema) => InternalTransform.GetOutputSchema(inputSchema); + + /// + /// Constructs a row-to-row mapper based on an input schema. If + /// is false, then an exception should be thrown. If the input schema is in any way + /// unsuitable for constructing the mapper, an exception should likewise be thrown. + /// + /// The input schema for which we should get the mapper. + /// The row to row mapper. + IRowToRowMapper ITransformer.GetRowToRowMapper(DataViewSchema inputSchema) + => ((ITransformer)InternalTransform).GetRowToRowMapper(inputSchema); + + /// + /// Same as but also supports mechanism to save the state. + /// + /// The input schema for which we should get the mapper. + /// The row to row mapper. + public IRowToRowMapper GetStatefulRowToRowMapper(DataViewSchema inputSchema) + => ((IStatefulTransformer)InternalTransform).GetStatefulRowToRowMapper(inputSchema); + + /// + /// Take the data in, make transformations, output the data. + /// Note that 's are lazy, so no actual transformations happen here, just schema validation. + /// + public IDataView Transform(IDataView input) => InternalTransform.Transform(input); + + /// + /// For saving a model into a repository. + /// + void ICanSaveModel.Save(ModelSaveContext ctx) => SaveModel(ctx); + + private protected virtual void SaveModel(ModelSaveContext ctx) + { + //TODO: + } + + /// + /// Creates a row mapper from Schema. + /// + internal IStatefulRowMapper MakeRowMapper(DataViewSchema schema) => InternalTransform.MakeRowMapper(schema); + + /// + /// Creates an IDataTransform from an IDataView. + /// + internal IDataTransform MakeDataTransform(IDataView input) => InternalTransform.MakeDataTransform(input); + + internal SrCnnAnomalyDetectionBase InternalTransform; + + internal SrCnnAnomalyDetectionBaseWrapper(SrCnnArgumentBase args, string name, IHostEnvironment env) + { + InternalTransform = new SrCnnAnomalyDetectionBase(args, name, env, this); + } + + internal SrCnnAnomalyDetectionBaseWrapper(IHostEnvironment env, ModelLoadContext ctx, string name) + { + InternalTransform = new SrCnnAnomalyDetectionBase(env, ctx, name, this); + } + + internal sealed class SrCnnAnomalyDetectionBase : SrCnnTransformBase + { + internal SrCnnAnomalyDetectionBaseWrapper Parent; + + public SrCnnAnomalyDetectionBase(SrCnnArgumentBase args, string name, IHostEnvironment env, SrCnnAnomalyDetectionBaseWrapper parent) + : base(args, name, env) + { + //TODO: + } + + public SrCnnAnomalyDetectionBase(IHostEnvironment env, ModelLoadContext ctx, string name, SrCnnAnomalyDetectionBaseWrapper parent) + : base(env, ctx, name) + { + //TODO: + } + + public override DataViewSchema GetOutputSchema(DataViewSchema inputSchema) + { + //TODO: + throw new NotImplementedException(); + } + + private protected override void SaveModel(ModelSaveContext ctx) + { + //TODO: + } + + internal sealed class State : SrCnnStateBase + { + public State() + { + } + + internal State(BinaryReader reader) + { + //TODO: + } + + internal override void Save(BinaryWriter writer) + { + //TODO: + } + + private protected override void CloneCore(State state) + { + //TODO: + } + + private protected override void LearnStateFromDataCore(FixedSizeQueue data) + { + } + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs new file mode 100644 index 0000000000..ce5de8d0d2 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -0,0 +1,212 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Microsoft.ML.CommandLine; +using Microsoft.ML.Data; +using Microsoft.ML.Internal.Utilities; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.Transforms.TimeSeries +{ + internal abstract class SrCnnArgumentBase + { + [Argument(ArgumentType.Required, HelpText = "The name of the source column.", ShortName = "src", + SortOrder = 1, Purpose = SpecialPurpose.ColumnName)] + public string Source; + + [Argument(ArgumentType.Required, HelpText = "The name of the new column.", + SortOrder = 2)] + public string Name; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the sliding window for computing spectral residual", ShortName = "wnd", + SortOrder = 3)] + public int WindowSize = 24; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the initial window for computingd. The default value is set to 0, which means there is no initial window considered.", ShortName = "iwnd", + SortOrder = 4)] + public int InitialWindowSize = 0; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The number of points to the back of training window.", + ShortName = "bwnd", SortOrder = 5)] + public int BackAddWindowSize = 5; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The number of pervious points used in prediction.", + ShortName = "awnd", SortOrder = 6)] + public int LookaheadWindowSize = 5; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", + ShortName = "thre", SortOrder = 7)] + public double Threshold = 0.3; + } + + internal abstract class SrCnnTransformBase : SequentialTransformerBase, TState> + where TState : SrCnnTransformBase.SrCnnStateBase, new() + { + internal int BackAddWindowSize; + + internal int LookaheadWindowSize; + + internal Double AlertThreshold; + + internal int OutputLength; + + private protected SrCnnTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName, string name, IHostEnvironment env, + int backAddWindowSize, int lookaheadWindowSize, Double alertThreshold) + : base(Contracts.CheckRef(env, nameof(env)).Register(name), windowSize, initialWindowSize, outputColumnName, inputColumnName, new VectorDataViewType(NumberDataViewType.Double, 3)) + { + //TODO: + + BackAddWindowSize = backAddWindowSize; + LookaheadWindowSize = lookaheadWindowSize; + AlertThreshold = alertThreshold; + } + + private protected SrCnnTransformBase(IHostEnvironment env, ModelLoadContext ctx, string name) + : base(Contracts.CheckRef(env, nameof(env)).Register(name), ctx) + { + //TODO: + } + + private protected SrCnnTransformBase(SrCnnArgumentBase args, string name, IHostEnvironment env) + : this(args.WindowSize, args.InitialWindowSize, args.Source, args.Name, + name, env, args.BackAddWindowSize, args.LookaheadWindowSize, args.Threshold) + { + } + + private protected override void SaveModel(ModelSaveContext ctx) + { + //TODO: + } + + internal override IStatefulRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(Host, this, schema); + + internal sealed class Mapper : IStatefulRowMapper + { + private readonly IHost _host; + private readonly SrCnnTransformBase _parent; + private readonly DataViewSchema _parentSchema; + private readonly int _inputColumnIndex; + private readonly VBuffer> _slotNames; + private SrCnnStateBase State { get; set; } + + public Mapper(IHostEnvironment env, SrCnnTransformBase parent, DataViewSchema inputSchema) + { + Contracts.CheckValue(env, nameof(env)); + _host = env.Register(nameof(Mapper)); + _host.CheckValue(inputSchema, nameof(inputSchema)); + _host.CheckValue(parent, nameof(parent)); + + if (!inputSchema.TryGetColumnIndex(parent.InputColumnName, out _inputColumnIndex)) + throw _host.ExceptSchemaMismatch(nameof(inputSchema), "input", parent.InputColumnName); + + var colType = inputSchema[_inputColumnIndex].Type; + if (colType != NumberDataViewType.Single) + throw _host.ExceptSchemaMismatch(nameof(inputSchema), "input", parent.InputColumnName, "Single", colType.ToString()); + + _parent = parent; + _parentSchema = inputSchema; + _slotNames = new VBuffer>(2, new[] { "Alert".AsMemory(), "Raw Score".AsMemory(), + "Mag".AsMemory()}); + + State = (SrCnnStateBase)_parent.StateRef; + } + + public DataViewSchema.DetachedColumn[] GetOutputColumns() + { + //TODO: + throw new NotImplementedException(); + } + + public void GetSlotNames(ref VBuffer> dst) + { + //TODO: + throw new NotImplementedException(); + } + + public Func GetDependencies(Func activeOutput) + { + //TODO: + throw new NotImplementedException(); + } + + void ICanSaveModel.Save(ModelSaveContext ctx) => _parent.SaveModel(ctx); + + public Delegate[] CreateGetters(DataViewRow input, Func activeOutput, out Action disposer) + { + //TODO: + throw new NotImplementedException(); + } + + private delegate void ProcessData(ref TInput src, ref VBuffer dst); + + private Delegate MakeGetter(DataViewRow input, SrCnnStateBase state) + { + //TODO: + throw new NotImplementedException(); + } + + public Action CreatePinger(DataViewRow input, Func activeOutput, out Action disposer) + { + //TODO: + throw new NotImplementedException(); + } + + private Action MakePinger(DataViewRow input, SrCnnStateBase state) + { + //TODO: + throw new NotImplementedException(); + } + + public void CloneState() + { + //TODO: + } + + public ITransformer GetTransformer() + { + //TODO: + throw new NotImplementedException(); + } + } + + internal abstract class SrCnnStateBase : SequentialTransformerBase, TState>.StateBase + { + protected SrCnnTransformBase Parent; + + private protected SrCnnStateBase() { } + + private protected override void CloneCore(TState state) + { + //TODO: + } + + private protected SrCnnStateBase(BinaryReader reader) : base(reader) + { + //TODO: + } + + internal override void Save(BinaryWriter writer) + { + //TODO: + } + + private protected override void SetNaOutput(ref VBuffer dst) + { + //TODO: + } + + private protected sealed override void TransformCore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration, ref VBuffer dst) + { + //TODO: + } + + private protected sealed override void InitializeStateCore(bool disk = false) + { + //TODO: + } + } + } +} From fa6520ad32ab8732da4495a0119682be8990069f Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Wed, 15 May 2019 12:53:19 -0700 Subject: [PATCH 2/9] Add core calculation code to SrCnn --- .../TimeSeries/DetectAnomalyBySrCnn.cs | 117 ++++++++++++ .../ExtensionsCatalog.cs | 4 +- .../SRCNNAnomalyDetector.cs | 59 ++++-- .../SrCnnAnomalyDetectionBase.cs | 173 +++++++++++++++++- .../SrCnnTransformBase.cs | 129 ++++++++++--- 5 files changed, 429 insertions(+), 53 deletions(-) create mode 100644 docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs new file mode 100644 index 0000000000..1274131baf --- /dev/null +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.ML; +using Microsoft.ML.Data; +using Microsoft.ML.Transforms.TimeSeries; + +namespace Samples.Dynamic +{ + public static class DetectAnomalyBySrCnn + { + // This example creates a time series (list of Data with the i-th element corresponding to the i-th time slot). + // The estimator is applied then to identify spiking points in the series. + public static void Example() + { + // Create a new ML context, for ML.NET operations. It can be used for exception tracking and logging, + // as well as the source of randomness. + var ml = new MLContext(); + + // Generate sample series data with a spike + const int Size = 10; + var data = new List(Size + 1) + { + new TimeSeriesData(5), + new TimeSeriesData(5), + new TimeSeriesData(5), + new TimeSeriesData(5), + new TimeSeriesData(5), + + // This is a spike. + new TimeSeriesData(10), + + new TimeSeriesData(5), + new TimeSeriesData(5), + new TimeSeriesData(5), + new TimeSeriesData(5), + new TimeSeriesData(5), + }; + + // Convert data to IDataView. + var dataView = ml.Data.LoadFromEnumerable(data); + + // Setup IidSpikeDetector arguments + string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); + string inputColumnName = nameof(TimeSeriesData.Value); + + // The transformed model. + //ITransformer model = ml.Transforms.DetectIidSpike(outputColumnName, inputColumnName, 95, Size).Fit(dataView); + ITransformer model = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 8, 5, 5, 3, 6, 0.3).Fit(dataView); + + // Create a time series prediction engine from the model. + var engine = model.CreateTimeSeriesPredictionFunction(ml); + + Console.WriteLine($"{outputColumnName} column obtained post-transformation."); + + + // Create non-anomalous data and check for anomaly. + for (int index = 0; index < 5; index++) + { + // Anomaly spike detection. + PrintPrediction(5, engine.Predict(new TimeSeriesData(5))); + } + + // 5 0 5.00 0.50 + // 5 0 5.00 0.50 + // 5 0 5.00 0.50 + // 5 0 5.00 0.50 + // 5 0 5.00 0.50 + + // Spike. + PrintPrediction(10, engine.Predict(new TimeSeriesData(10))); + + // 10 1 10.00 0.00 <-- alert is on, predicted spike (check-point model) + + // Checkpoint the model. + var modelPath = "temp.zip"; + engine.CheckPoint(ml, modelPath); + + // Load the model. + using (var file = File.OpenRead(modelPath)) + model = ml.Model.Load(file, out DataViewSchema schema); + + for (int index = 0; index < 5; index++) + { + // Anomaly spike detection. + PrintPrediction(5, engine.Predict(new TimeSeriesData(5))); + } + + // 5 0 5.00 0.26 <-- load model from disk. + // 5 0 5.00 0.26 + // 5 0 5.00 0.50 + // 5 0 5.00 0.50 + // 5 0 5.00 0.50 + + } + + private static void PrintPrediction(float value, SrCnnAnomalyDetection prediction) => + Console.WriteLine("{0}\t{1}\t{2:0.00}\t{3:0.00}", value, prediction.Prediction[0], + prediction.Prediction[1], prediction.Prediction[2]); + + class TimeSeriesData + { + public float Value; + + public TimeSeriesData(float value) + { + Value = value; + } + } + + class SrCnnAnomalyDetection + { + [VectorType(3)] + public double[] Prediction { get; set; } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 140193c3b2..3d5409e40f 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -123,7 +123,7 @@ public static SsaSpikeEstimator DetectSpikeBySsa(this TransformsCatalog catalog, => new SsaSpikeEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, confidence, pvalueHistoryLength, trainingWindowSize, seasonalityWindowSize, inputColumnName, side, errorFunction); public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog catalog, string outputColumnName, string inputColumnName, - int windowSize, int backAddWindowSize, int lookaheadWindowSize, double threshold) - => new SrCnnAnomalyEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, windowSize, backAddWindowSize, lookaheadWindowSize, threshold, inputColumnName); + int windowSize, int backAddWindowSize, int lookaheadWindowSize, int averageingWindowSize, int judgementWindowSize, double threshold) + => new SrCnnAnomalyEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, windowSize, backAddWindowSize, lookaheadWindowSize, averageingWindowSize, judgementWindowSize, threshold, inputColumnName); } } diff --git a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs index 6eda8375cb..b813730849 100644 --- a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.ML; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; @@ -45,15 +46,23 @@ internal sealed class Options : TransformInputBase public int WindowSize = 24; [Argument(ArgumentType.Required, HelpText = "The number of points to the back of training window.", - ShortName = "bwnd", SortOrder = 102)] + ShortName = "backwnd", SortOrder = 102)] public int BackAddWindowSize = 5; [Argument(ArgumentType.Required, HelpText = "The number of pervious points used in prediction.", - ShortName = "awnd", SortOrder = 103)] + ShortName = "aheadwnd", SortOrder = 103)] public int LookaheadWindowSize = 5; + [Argument(ArgumentType.Required, HelpText = "The size of sliding window to generate a saliency map for the series.", + ShortName = "avgwnd", SortOrder = 104)] + public int AvergingWindowSize = 3; + + [Argument(ArgumentType.Required, HelpText = "The size of sliding window to generate a saliency map for the series.", + ShortName = "jdgwnd", SortOrder = 105)] + public int JudgementWindowSize = 21; + [Argument(ArgumentType.Required, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", - ShortName = "thre", SortOrder = 104)] + ShortName = "thre", SortOrder = 106)] public double Threshold = 0.3; } @@ -67,6 +76,8 @@ public SrCnnArgument(Options options) InitialWindowSize = 0; BackAddWindowSize = options.BackAddWindowSize; LookaheadWindowSize = options.LookaheadWindowSize; + AvergingWindowSize = options.AvergingWindowSize; + JudgementWindowSize = options.JudgementWindowSize; Threshold = options.Threshold; } @@ -78,6 +89,8 @@ public SrCnnArgument(SrCnnAnomalyDetector transform) InitialWindowSize = 0; BackAddWindowSize = transform.InternalTransform.BackAddWindowSize; LookaheadWindowSize = transform.InternalTransform.LookaheadWindowSize; + AvergingWindowSize = transform.InternalTransform.AvergingWindowSize; + JudgementWindowSize = transform.InternalTransform.JudgementWindowSize; Threshold = transform.InternalTransform.AlertThreshold; } } @@ -132,14 +145,14 @@ IStatefulTransformer IStatefulTransformer.Clone() } internal SrCnnAnomalyDetector(IHostEnvironment env, Options options) - :base(new SrCnnArgument(options), LoaderSignature, env) + : base(new SrCnnArgument(options), LoaderSignature, env) { } internal SrCnnAnomalyDetector(IHostEnvironment env, ModelLoadContext ctx) : base(env, ctx, LoaderSignature) { - //TODO: + //TODO: Some data check here } private SrCnnAnomalyDetector(IHostEnvironment env, SrCnnAnomalyDetector transform) @@ -149,7 +162,13 @@ private SrCnnAnomalyDetector(IHostEnvironment env, SrCnnAnomalyDetector transfor private protected override void SaveModel(ModelSaveContext ctx) { - //TODO: + InternalTransform.Host.CheckValue(ctx, nameof(ctx)); + ctx.CheckAtModel(); + ctx.SetVersionInfo(GetVersionInfo()); + + // *** Binary format *** + // + base.SaveModel(ctx); } } @@ -158,14 +177,13 @@ private protected override void SaveModel(ModelSaveContext ctx) /// public sealed class SrCnnAnomalyEstimator : TrivialEstimator { - /// - /// Create a new instance of - /// /// /// /// /// /// + /// + /// /// /// internal SrCnnAnomalyEstimator(IHostEnvironment env, @@ -173,6 +191,8 @@ internal SrCnnAnomalyEstimator(IHostEnvironment env, int windowSize, int backAddWindowSize, int lookaheadWindowSize, + int averagingWindowSize, + int judgementWindowSize, double threshold = 0.3, string inputColumnName = null) : base(Contracts.CheckRef(env, nameof(env)).Register(nameof(SrCnnAnomalyEstimator)), @@ -183,6 +203,8 @@ internal SrCnnAnomalyEstimator(IHostEnvironment env, WindowSize = windowSize, BackAddWindowSize = backAddWindowSize, LookaheadWindowSize = lookaheadWindowSize, + AvergingWindowSize = averagingWindowSize, + JudgementWindowSize = judgementWindowSize, Threshold = threshold })) { @@ -195,8 +217,21 @@ internal SrCnnAnomalyEstimator(IHostEnvironment env, SrCnnAnomalyDetector.Option public override SchemaShape GetOutputSchema(SchemaShape inputSchema) { - //TODO: - throw new NotImplementedException(); + Host.CheckValue(inputSchema, nameof(inputSchema)); + + if (!inputSchema.TryFindColumn(Transformer.InternalTransform.InputColumnName, out var col)) + throw Host.ExceptSchemaMismatch(nameof(inputSchema), "input", Transformer.InternalTransform.InputColumnName); + if (col.ItemType != NumberDataViewType.Single) + throw Host.ExceptSchemaMismatch(nameof(inputSchema), "input", Transformer.InternalTransform.InputColumnName, "Single", col.GetTypeString()); + + var metadata = new List() { + new SchemaShape.Column(AnnotationUtils.Kinds.SlotNames, SchemaShape.Column.VectorKind.Vector, TextDataViewType.Instance, false) + }; + var resultDic = inputSchema.ToDictionary(x => x.Name); + resultDic[Transformer.InternalTransform.OutputColumnName] = new SchemaShape.Column( + Transformer.InternalTransform.OutputColumnName, SchemaShape.Column.VectorKind.Vector, NumberDataViewType.Double, false, new SchemaShape(metadata)); + + return new SchemaShape(resultDic.Values); } } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 0b6f277255..4ae82b6bcd 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.ML.Data; using Microsoft.ML.Internal.Utilities; using Microsoft.ML.Runtime; @@ -61,7 +63,7 @@ public IRowToRowMapper GetStatefulRowToRowMapper(DataViewSchema inputSchema) private protected virtual void SaveModel(ModelSaveContext ctx) { - //TODO: + InternalTransform.SaveThis(ctx); } /// @@ -93,24 +95,50 @@ internal sealed class SrCnnAnomalyDetectionBase : SrCnnTransformBase + // State: StateRef + StateRef.Save(ctx.Writer); } internal sealed class State : SrCnnStateBase @@ -121,22 +149,149 @@ public State() internal State(BinaryReader reader) { - //TODO: + WindowedBuffer = TimeSeriesUtils.DeserializeFixedSizeQueueSingle(reader, Host); + InitialWindowedBuffer = TimeSeriesUtils.DeserializeFixedSizeQueueSingle(reader, Host); } internal override void Save(BinaryWriter writer) { - //TODO: + base.Save(writer); + TimeSeriesUtils.SerializeFixedSizeQueue(WindowedBuffer, writer); + TimeSeriesUtils.SerializeFixedSizeQueue(InitialWindowedBuffer, writer); } private protected override void CloneCore(State state) { - //TODO: + base.CloneCore(state); + Contracts.Assert(state is State); + var stateLocal = state as State; + stateLocal.WindowedBuffer = WindowedBuffer.Clone(); + stateLocal.InitialWindowedBuffer = InitialWindowedBuffer.Clone(); } private protected override void LearnStateFromDataCore(FixedSizeQueue data) { } + + private protected override sealed void SpectralResidual(Single input, FixedSizeQueue data, ref VBufferEditor result) + { + // Step 1: Get backadd wave + List backAddList = BackAdd(input, data); + + // Step 2: FFT transformation + int length = backAddList.Count; + float[] fftRe = new float[length]; + float[] fftIm = new float[length]; + FftUtils.ComputeForwardFft(backAddList.ToArray(), Enumerable.Repeat(0.0f, length).ToArray(), fftRe, fftIm, length); + + // Step 3: Calculate mags of FFT + List magList = new List(); + for (int i = 0; i < length; ++i) + { + magList.Add(MathUtils.Sqrt((fftRe[i] * fftRe[i] + fftIm[i] * fftIm[i]))); + } + + // Step 4: Calculate spectral + List magLogList = magList.Select(x => x != 0 ? MathUtils.Log(x) : 0).ToList(); + List filteredLogList = AverageFilter(magLogList, Parent.AvergingWindowSize); + List spectralList = new List(); + for (int i = 0; i < magLogList.Count; ++i) + { + spectralList.Add(magLogList[i] - filteredLogList[i]); + } + + // Step 5: IFFT transformation + float[] transRe = new float[length]; + float[] transIm = new float[length]; + for (int i = 0; i < length; ++i) + { + if (magLogList[i] != 0) + { + transRe[i] = fftRe[i] * spectralList[i] / magList[i]; + transIm[i] = fftIm[i] * spectralList[i] / magList[i]; + } + else + { + transRe[i] = 0; + transIm[i] = 0; + } + } + + float[] ifftRe = new float[length]; + float[] ifftIm = new float[length]; + FftUtils.ComputeBackwardFft(transRe, transIm, ifftRe, ifftIm, length); + + // Step 6: Calculate mag and ave_mag of IFFT + List ifftMagList = new List(); + for (int i = 0; i < length; ++i) + { + ifftMagList.Add(MathUtils.Sqrt((ifftRe[i] * ifftRe[i] + ifftIm[i] * ifftIm[i]))); + } + List filteredIfftMagList = AverageFilter(ifftMagList, Parent.AvergingWindowSize); + + // Step 7: Calculate score + var score = CalculateSocre(ifftMagList[data.Count-1], filteredIfftMagList[data.Count-1]); + var detres = score > Parent.AlertThreshold ? 1 : 0; + var mag = ifftMagList[data.Count-1]; + + //Step 8: Set result + result.Values[0] = detres; + result.Values[1] = score; + result.Values[2] = mag; + } + + private List BackAdd(Single input, FixedSizeQueue data) + { + List predictArray = new List(); + for (int i = data.Count-Parent.LookaheadWindowSize-2; i < data.Count-1; ++i) + { + predictArray.Add(data[i]); + } + var predictedValue = PredictNext(input, predictArray); + List backAddArray = new List(); + for (int i = 0; i < data.Count; ++i) + { + backAddArray.Add(data[i]); + } + backAddArray.AddRange(Enumerable.Repeat(predictedValue, Parent.BackAddWindowSize)); + return backAddArray; + } + + private Single PredictNext(Single input, List data) + { + var n = data.Count; + Single slopeSum = 0.0f; + for (int i = 0; i < n-1; ++i) + { + slopeSum += (input - data[i]) / (n - 1 - i); + } + return (input + slopeSum); + } + + private List AverageFilter(List data, int n) + { + Single cumsum = 0.0f; + List cumSumList = data.Select(x => cumsum += x).ToList(); + for (int i = n; i < cumSumList.Count; ++i) + { + cumSumList[i] -= cumSumList[i - n]; + } + for (int i = 1; i < n; ++i) + { + cumSumList[i] /= (i + 1); + } + return cumSumList; + } + + private Single CalculateSocre(Single mag, Single avgMag) + { + double safeDivisor = avgMag; + if (safeDivisor < 1e-8) + { + safeDivisor = 1e-8; + } + return (float)(Math.Abs(mag - avgMag) / safeDivisor); + } } } } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index ce5de8d0d2..68ea08d5c4 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Threading; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; using Microsoft.ML.Internal.Utilities; @@ -30,15 +31,23 @@ internal abstract class SrCnnArgumentBase public int InitialWindowSize = 0; [Argument(ArgumentType.AtMostOnce, HelpText = "The number of points to the back of training window.", - ShortName = "bwnd", SortOrder = 5)] + ShortName = "backwnd", SortOrder = 5)] public int BackAddWindowSize = 5; [Argument(ArgumentType.AtMostOnce, HelpText = "The number of pervious points used in prediction.", - ShortName = "awnd", SortOrder = 6)] + ShortName = "aheadwnd", SortOrder = 6)] public int LookaheadWindowSize = 5; + [Argument(ArgumentType.Required, HelpText = "The size of sliding window to generate a saliency map for the series.", + ShortName = "avgwnd", SortOrder = 7)] + public int AvergingWindowSize = 3; + + [Argument(ArgumentType.Required, HelpText = "The size of sliding window to generate a saliency map for the series.", + ShortName = "jdgwnd", SortOrder = 8)] + public int JudgementWindowSize = 21; + [Argument(ArgumentType.AtMostOnce, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", - ShortName = "thre", SortOrder = 7)] + ShortName = "thre", SortOrder = 9)] public double Threshold = 0.3; } @@ -49,36 +58,44 @@ internal abstract class SrCnnTransformBase : SequentialTransform internal int LookaheadWindowSize; + internal int AvergingWindowSize; + + internal int JudgementWindowSize; + internal Double AlertThreshold; internal int OutputLength; private protected SrCnnTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName, string name, IHostEnvironment env, - int backAddWindowSize, int lookaheadWindowSize, Double alertThreshold) + int backAddWindowSize, int lookaheadWindowSize, int averagingWindowSize, int judgementWindowSize, Double alertThreshold) : base(Contracts.CheckRef(env, nameof(env)).Register(name), windowSize, initialWindowSize, outputColumnName, inputColumnName, new VectorDataViewType(NumberDataViewType.Double, 3)) { - //TODO: + //TODO: Check argument BackAddWindowSize = backAddWindowSize; LookaheadWindowSize = lookaheadWindowSize; + AvergingWindowSize = averagingWindowSize; + JudgementWindowSize = judgementWindowSize; AlertThreshold = alertThreshold; + + OutputLength = 3; } private protected SrCnnTransformBase(IHostEnvironment env, ModelLoadContext ctx, string name) : base(Contracts.CheckRef(env, nameof(env)).Register(name), ctx) { - //TODO: + //TODO: Read from binary format } private protected SrCnnTransformBase(SrCnnArgumentBase args, string name, IHostEnvironment env) : this(args.WindowSize, args.InitialWindowSize, args.Source, args.Name, - name, env, args.BackAddWindowSize, args.LookaheadWindowSize, args.Threshold) + name, env, args.BackAddWindowSize, args.LookaheadWindowSize, args.AvergingWindowSize, args.JudgementWindowSize, args.Threshold) { } private protected override void SaveModel(ModelSaveContext ctx) { - //TODO: + //TODO: save to ctx and write to file } internal override IStatefulRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(Host, this, schema); @@ -116,59 +133,87 @@ public Mapper(IHostEnvironment env, SrCnnTransformBase parent, D public DataViewSchema.DetachedColumn[] GetOutputColumns() { - //TODO: - throw new NotImplementedException(); + var meta = new DataViewSchema.Annotations.Builder(); + meta.AddSlotNames(_parent.OutputLength, GetSlotNames); + var info = new DataViewSchema.DetachedColumn[1]; + info[0] = new DataViewSchema.DetachedColumn(_parent.OutputColumnName, new VectorDataViewType(NumberDataViewType.Double, _parent.OutputLength), meta.ToAnnotations()); + return info; } - public void GetSlotNames(ref VBuffer> dst) - { - //TODO: - throw new NotImplementedException(); - } + public void GetSlotNames(ref VBuffer> dst) => _slotNames.CopyTo(ref dst, 0, _parent.OutputLength); public Func GetDependencies(Func activeOutput) { - //TODO: - throw new NotImplementedException(); + if (activeOutput(0)) + return col => col == _inputColumnIndex; + else + return col => false; } void ICanSaveModel.Save(ModelSaveContext ctx) => _parent.SaveModel(ctx); public Delegate[] CreateGetters(DataViewRow input, Func activeOutput, out Action disposer) { - //TODO: - throw new NotImplementedException(); + disposer = null; + var getters = new Delegate[1]; + if (activeOutput(0)) + getters[0] = MakeGetter(input, State); + + return getters; } private delegate void ProcessData(ref TInput src, ref VBuffer dst); private Delegate MakeGetter(DataViewRow input, SrCnnStateBase state) { - //TODO: - throw new NotImplementedException(); + _host.AssertValue(input); + var srcGetter = input.GetGetter(input.Schema[_inputColumnIndex]); + ProcessData processData = _parent.WindowSize > 0 ? + (ProcessData)state.Process : state.ProcessWithoutBuffer; + + ValueGetter> valueGetter = (ref VBuffer dst) => + { + TInput src = default; + srcGetter(ref src); + processData(ref src, ref dst); + }; + return valueGetter; } public Action CreatePinger(DataViewRow input, Func activeOutput, out Action disposer) { - //TODO: - throw new NotImplementedException(); + disposer = null; + Action pinger = null; + if (activeOutput(0)) + pinger = MakePinger(input, State); + + return pinger; } private Action MakePinger(DataViewRow input, SrCnnStateBase state) { - //TODO: - throw new NotImplementedException(); + _host.AssertValue(input); + var srcGetter = input.GetGetter(input.Schema[_inputColumnIndex]); + Action pinger = (long rowPosition) => + { + TInput src = default; + srcGetter(ref src); + state.UpdateState(ref src, rowPosition, _parent.WindowSize > 0); + }; + return pinger; } public void CloneState() { - //TODO: + if (Interlocked.Increment(ref _parent.StateRefCount) > 1) + { + State = (SrCnnStateBase)_parent.StateRef.Clone(); + } } public ITransformer GetTransformer() { - //TODO: - throw new NotImplementedException(); + return _parent; } } @@ -195,18 +240,42 @@ internal override void Save(BinaryWriter writer) private protected override void SetNaOutput(ref VBuffer dst) { - //TODO: + var outputLength = Parent.OutputLength; + var editor = VBufferEditor.Create(ref dst, outputLength); + + for (int i = 0; i < outputLength; ++i) + editor.Values[i] = Double.NaN; + + dst = editor.Commit(); } private protected sealed override void TransformCore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration, ref VBuffer dst) { - //TODO: + var outputLength = Parent.OutputLength; + Host.Assert(outputLength >= 2); + + var result = VBufferEditor.Create(ref dst, outputLength); + for (int i = 0; i < outputLength; ++i) + result.Values[i] = Double.NaN; + + SpectralResidual(input, windowedBuffer, ref result); + + dst = result.Commit(); } private protected sealed override void InitializeStateCore(bool disk = false) { //TODO: } + + private protected override void LearnStateFromDataCore(FixedSizeQueue data) + { + //TODO: + } + + private protected virtual void SpectralResidual(TInput input, FixedSizeQueue data, ref VBufferEditor result) + { + } } } } From c48869306ff50464d9f3940376f7cabae849ca2d Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Sun, 19 May 2019 16:33:59 -0700 Subject: [PATCH 3/9] Fix implementation bugs --- src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs | 2 +- .../SrCnnAnomalyDetectionBase.cs | 10 +++++++--- src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs index b813730849..d46ba31b17 100644 --- a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs @@ -98,7 +98,7 @@ public SrCnnArgument(SrCnnAnomalyDetector transform) private static VersionInfo GetVersionInfo() { return new VersionInfo( - modelSignature: "SRCNNTRNS", + modelSignature: "SRCNTRNS", verWrittenCur: 0x00010001, // Initial verReadableCur: 0x00010001, verWeCanReadBack: 0x00010001, diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 4ae82b6bcd..ee62f31353 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -197,7 +197,7 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ List spectralList = new List(); for (int i = 0; i < magLogList.Count; ++i) { - spectralList.Add(magLogList[i] - filteredLogList[i]); + spectralList.Add(MathUtils.ExpSlow(magLogList[i] - filteredLogList[i])); } // Step 5: IFFT transformation @@ -227,10 +227,13 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ { ifftMagList.Add(MathUtils.Sqrt((ifftRe[i] * ifftRe[i] + ifftIm[i] * ifftIm[i]))); } - List filteredIfftMagList = AverageFilter(ifftMagList, Parent.AvergingWindowSize); + List filteredIfftMagList = AverageFilter(ifftMagList, Parent.JudgementWindowSize); // Step 7: Calculate score var score = CalculateSocre(ifftMagList[data.Count-1], filteredIfftMagList[data.Count-1]); + score = (score < 1) ? 0 : score; + score = (score > 10) ? 10 : score; + score /= 10.0f; var detres = score > Parent.AlertThreshold ? 1 : 0; var mag = ifftMagList[data.Count-1]; @@ -272,9 +275,10 @@ private List AverageFilter(List data, int n) { Single cumsum = 0.0f; List cumSumList = data.Select(x => cumsum += x).ToList(); + List cumSumShift = new List(cumSumList); for (int i = n; i < cumSumList.Count; ++i) { - cumSumList[i] -= cumSumList[i - n]; + cumSumList[i] = (cumSumList[i] - cumSumShift[i - n]) / n; } for (int i = 1; i < n; ++i) { diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index 68ea08d5c4..cffb9aede1 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -265,7 +265,8 @@ private protected sealed override void TransformCore(ref TInput input, FixedSize private protected sealed override void InitializeStateCore(bool disk = false) { - //TODO: + Parent = (SrCnnTransformBase)ParentTransform; + //TODO: assert for value threshold } private protected override void LearnStateFromDataCore(FixedSizeQueue data) From 406c664d60840f203a261454bfd4907fda9f9b3b Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Mon, 20 May 2019 10:12:34 -0700 Subject: [PATCH 4/9] Add test and sample. --- .../TimeSeries/DetectAnomalyBySrCnn.cs | 26 ++----- .../DetectAnomalyBySrCnnBatchPrediction.cs | 73 +++++++++++++++++++ .../SRCNNAnomalyDetector.cs | 1 - .../SrCnnAnomalyDetectionBase.cs | 20 ++--- .../SrCnnTransformBase.cs | 49 +++++++++++-- .../TimeSeriesDirectApi.cs | 61 ++++++++++++++++ 6 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs index 1274131baf..97c5265b4e 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs @@ -45,32 +45,25 @@ public static void Example() string inputColumnName = nameof(TimeSeriesData.Value); // The transformed model. - //ITransformer model = ml.Transforms.DetectIidSpike(outputColumnName, inputColumnName, 95, Size).Fit(dataView); - ITransformer model = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 8, 5, 5, 3, 6, 0.3).Fit(dataView); + ITransformer model = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 64, 5, 5, 3, 21, 0.25).Fit(dataView); // Create a time series prediction engine from the model. var engine = model.CreateTimeSeriesPredictionFunction(ml); Console.WriteLine($"{outputColumnName} column obtained post-transformation."); - // Create non-anomalous data and check for anomaly. - for (int index = 0; index < 5; index++) + for (int index = 0; index < 100; index++) { // Anomaly spike detection. PrintPrediction(5, engine.Predict(new TimeSeriesData(5))); } - // 5 0 5.00 0.50 - // 5 0 5.00 0.50 - // 5 0 5.00 0.50 - // 5 0 5.00 0.50 - // 5 0 5.00 0.50 - // Spike. - PrintPrediction(10, engine.Predict(new TimeSeriesData(10))); - - // 10 1 10.00 0.00 <-- alert is on, predicted spike (check-point model) + for (int index = 0; index < 5; index++) + { + PrintPrediction(15, engine.Predict(new TimeSeriesData(10))); + } // Checkpoint the model. var modelPath = "temp.zip"; @@ -85,13 +78,6 @@ public static void Example() // Anomaly spike detection. PrintPrediction(5, engine.Predict(new TimeSeriesData(5))); } - - // 5 0 5.00 0.26 <-- load model from disk. - // 5 0 5.00 0.26 - // 5 0 5.00 0.50 - // 5 0 5.00 0.50 - // 5 0 5.00 0.50 - } private static void PrintPrediction(float value, SrCnnAnomalyDetection prediction) => diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs new file mode 100644 index 0000000000..a401bb9be3 --- /dev/null +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Microsoft.ML; +using Microsoft.ML.Data; + +namespace Samples.Dynamic +{ + public static class DetectAnomalyBySrCnnBatchPrediction + { + public static void Example() + { + // Create a new ML context, for ML.NET operations. It can be used for exception tracking and logging, + // as well as the source of randomness. + var ml = new MLContext(); + + // Generate sample series data with a spike + var data = new List(); + for (int index = 0; index < 100; index++) + { + data.Add(new TimeSeriesData(5)); + } + for (int index = 0; index < 5; index++) + { + data.Add(new TimeSeriesData(15)); + } + for (int index = 0; index < 5; index++) + { + data.Add(new TimeSeriesData(5)); + } + + // Convert data to IDataView. + var dataView = ml.Data.LoadFromEnumerable(data); + + // Setup the estimator arguments + string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); + string inputColumnName = nameof(TimeSeriesData.Value); + + // The transformed data. + var transformedData = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 64, 5, 5, 3, 21, 0.25).Fit(dataView).Transform(dataView); + + // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. + var predictionColumn = ml.Data.CreateEnumerable(transformedData, reuseRowObject: false); + + Console.WriteLine($"{outputColumnName} column obtained post-transformation."); + Console.WriteLine("Data\tAlert\tScore\tP-Value"); + + int k = 0; + foreach (var prediction in predictionColumn) + PrintPrediction(data[k++].Value, prediction); + + } + + private static void PrintPrediction(float value, SrCnnAnomalyDetection prediction) => + Console.WriteLine("{0}\t{1}\t{2:0.00}\t{3:0.00}", value, prediction.Prediction[0], + prediction.Prediction[1], prediction.Prediction[2]); + + class TimeSeriesData + { + public float Value; + + public TimeSeriesData(float value) + { + Value = value; + } + } + + class SrCnnAnomalyDetection + { + [VectorType(3)] + public double[] Prediction { get; set; } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs index d46ba31b17..4164a4f743 100644 --- a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs @@ -152,7 +152,6 @@ internal SrCnnAnomalyDetector(IHostEnvironment env, Options options) internal SrCnnAnomalyDetector(IHostEnvironment env, ModelLoadContext ctx) : base(env, ctx, LoaderSignature) { - //TODO: Some data check here } private SrCnnAnomalyDetector(IHostEnvironment env, SrCnnAnomalyDetector transform) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index ee62f31353..178f60d3ef 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -104,7 +104,7 @@ public SrCnnAnomalyDetectionBase(SrCnnArgumentBase args, string name, IHostEnvir public SrCnnAnomalyDetectionBase(IHostEnvironment env, ModelLoadContext ctx, string name, SrCnnAnomalyDetectionBaseWrapper parent) : base(env, ctx, name) { - Host.CheckDecode(InitialWindowSize == 0); + //Host.CheckDecode(InitialWindowSize == 0); StateRef = new State(ctx.Reader); StateRef.InitState(this, Host); Parent = parent; @@ -132,7 +132,7 @@ private protected override void SaveModel(ModelSaveContext ctx) internal void SaveThis(ModelSaveContext ctx) { ctx.CheckAtModel(); - Host.Assert(InitialWindowSize == 0); + //Host.Assert(InitialWindowSize == 0); base.SaveModel(ctx); // *** Binary format *** @@ -147,7 +147,7 @@ public State() { } - internal State(BinaryReader reader) + internal State(BinaryReader reader) : base(reader) { WindowedBuffer = TimeSeriesUtils.DeserializeFixedSizeQueueSingle(reader, Host); InitialWindowedBuffer = TimeSeriesUtils.DeserializeFixedSizeQueueSingle(reader, Host); @@ -229,17 +229,17 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ } List filteredIfftMagList = AverageFilter(ifftMagList, Parent.JudgementWindowSize); - // Step 7: Calculate score + // Step 7: Calculate score and set result var score = CalculateSocre(ifftMagList[data.Count-1], filteredIfftMagList[data.Count-1]); - score = (score < 1) ? 0 : score; - score = (score > 10) ? 10 : score; score /= 10.0f; - var detres = score > Parent.AlertThreshold ? 1 : 0; - var mag = ifftMagList[data.Count-1]; + result.Values[1] = score; - //Step 8: Set result + score = Math.Min(score, 1); + score = Math.Max(score, 0); + var detres = score > Parent.AlertThreshold ? 1 : 0; result.Values[0] = detres; - result.Values[1] = score; + + var mag = ifftMagList[data.Count-1]; result.Values[2] = mag; } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index cffb9aede1..d10fdd6f30 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -84,7 +84,28 @@ private protected SrCnnTransformBase(int windowSize, int initialWindowSize, stri private protected SrCnnTransformBase(IHostEnvironment env, ModelLoadContext ctx, string name) : base(Contracts.CheckRef(env, nameof(env)).Register(name), ctx) { - //TODO: Read from binary format + OutputLength = 3; + + byte temp; + temp = ctx.Reader.ReadByte(); + BackAddWindowSize = (int)temp; + Host.CheckDecode(BackAddWindowSize > 0); + + temp = ctx.Reader.ReadByte(); + LookaheadWindowSize = (int)temp; + Host.CheckDecode(LookaheadWindowSize > 0); + + temp = ctx.Reader.ReadByte(); + AvergingWindowSize = (int)temp; + Host.CheckDecode(AvergingWindowSize > 0); + + temp = ctx.Reader.ReadByte(); + JudgementWindowSize = (int)temp; + Host.CheckDecode(JudgementWindowSize > 0); + + temp = ctx.Reader.ReadByte(); + AlertThreshold = (double)temp; + Host.CheckDecode(AlertThreshold >= 0 && AlertThreshold <= 1); } private protected SrCnnTransformBase(SrCnnArgumentBase args, string name, IHostEnvironment env) @@ -95,7 +116,23 @@ private protected SrCnnTransformBase(SrCnnArgumentBase args, string name, IHostE private protected override void SaveModel(ModelSaveContext ctx) { - //TODO: save to ctx and write to file + Host.CheckValue(ctx, nameof(ctx)); + ctx.CheckAtModel(); + + Host.Assert(WindowSize > 0); + Host.Assert(InitialWindowSize == WindowSize); + Host.Assert(BackAddWindowSize > 0); + Host.Assert(LookaheadWindowSize > 0); + Host.Assert(AvergingWindowSize > 0); + Host.Assert(JudgementWindowSize > 0); + Host.Assert(AlertThreshold >= 0 && AlertThreshold <= 1); + + base.SaveModel(ctx); + ctx.Writer.Write((byte)BackAddWindowSize); + ctx.Writer.Write((byte)LookaheadWindowSize); + ctx.Writer.Write((byte)AvergingWindowSize); + ctx.Writer.Write((byte)JudgementWindowSize); + ctx.Writer.Write((byte)AlertThreshold); } internal override IStatefulRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(Host, this, schema); @@ -225,17 +262,17 @@ private protected SrCnnStateBase() { } private protected override void CloneCore(TState state) { - //TODO: + base.CloneCore(state); + Contracts.Assert(state is SrCnnStateBase); } private protected SrCnnStateBase(BinaryReader reader) : base(reader) { - //TODO: } internal override void Save(BinaryWriter writer) { - //TODO: + base.Save(writer); } private protected override void SetNaOutput(ref VBuffer dst) @@ -244,7 +281,7 @@ private protected override void SetNaOutput(ref VBuffer dst) var editor = VBufferEditor.Create(ref dst, outputLength); for (int i = 0; i < outputLength; ++i) - editor.Values[i] = Double.NaN; + editor.Values[i] = 0; dst = editor.Commit(); } diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 05055b5613..073a284a62 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.IO; using Microsoft.ML.Data; @@ -41,6 +42,22 @@ public Data(float value) } } + private sealed class TimeSeriesData + { + public float Value; + + public TimeSeriesData(float value) + { + Value = value; + } + } + + private sealed class SrCnnAnomalyDetection + { + [VectorType(3)] + public double[] Prediction { get; set; } + } + [Fact] public void ChangeDetection() { @@ -276,5 +293,49 @@ public void ChangePointDetectionWithSeasonalityPredictionEngine() Assert.Equal(0.14823824685192111, prediction.Change[2], precision: 5); // P-Value score Assert.Equal(1.5292508189989167E-07, prediction.Change[3], precision: 5); // Martingale score } + + [Fact] + public void AnomalyDetectionWithSrCnn() + { + var ml = new MLContext(); + + // Generate sample series data with a spike + var data = new List(); + for (int index = 0; index < 100; index++) + { + data.Add(new TimeSeriesData(5)); + } + for (int index = 0; index < 5; index++) + { + data.Add(new TimeSeriesData(15)); + } + for (int index = 0; index < 5; index++) + { + data.Add(new TimeSeriesData(5)); + } + + // Convert data to IDataView. + var dataView = ml.Data.LoadFromEnumerable(data); + + // Setup the estimator arguments + string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); + string inputColumnName = nameof(TimeSeriesData.Value); + + // The transformed data. + var transformedData = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 64, 5, 5, 3, 21, 0.25).Fit(dataView).Transform(dataView); + + // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. + var predictionColumn = ml.Data.CreateEnumerable(transformedData, reuseRowObject: false); + + int k = 0; + foreach (var prediction in predictionColumn) + { + if (k == 101 || k == 106) + Assert.Equal(1, prediction.Prediction[0]); + else + Assert.Equal(0, prediction.Prediction[0]); + k += 1; + } + } } } From c56d4d4b09a54fc891a1a7c5d521f513cb5c2881 Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Mon, 27 May 2019 08:29:56 -0700 Subject: [PATCH 5/9] Fix commented problems: 1.Add xml to DetectAnomalyBySrCnn; 2.Add default value to DetectAnomalyBySrCnn; 3.Change class name of SrCnnAnomalyDetectionBaseWrapper; 4.fix AlertThreshold write bug; 5.Add check argument part and remove redundent TODOs 6.Fix slot name filling bug. --- .../ExtensionsCatalog.cs | 16 +++++++++- .../SRCNNAnomalyDetector.cs | 24 +++++++-------- .../SrCnnAnomalyDetectionBase.cs | 30 +++++++++---------- .../SrCnnTransformBase.cs | 18 +++++------ 4 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 3d5409e40f..64891b7e58 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -122,8 +122,22 @@ public static SsaSpikeEstimator DetectSpikeBySsa(this TransformsCatalog catalog, int trainingWindowSize, int seasonalityWindowSize, AnomalySide side = AnomalySide.TwoSided, ErrorFunction errorFunction = ErrorFunction.SignedDifference) => new SsaSpikeEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, confidence, pvalueHistoryLength, trainingWindowSize, seasonalityWindowSize, inputColumnName, side, errorFunction); + /// + /// Create , which detects timeseries anomalies using SRCNN algorithm. + /// + /// The transform's catalog. + /// Name of the column resulting from the transformation of . + /// The column data is a vector of . The vector contains 3 elements: alert (1 means anomaly while 0 means normal), raw score, and magnitude of spectual residual. + /// Name of column to transform. The column data must be . + /// The size of the sliding window for computing spectral residual. + /// The number of points to add back of training window. + /// The number of pervious points used in prediction. + /// The size of sliding window to generate a saliency map for the series. + /// The size of sliding window to calculate the anomaly score for each data point. + /// The threshold to determine anomaly, score larger than the threshold is considered as anomaly. + /// public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog catalog, string outputColumnName, string inputColumnName, - int windowSize, int backAddWindowSize, int lookaheadWindowSize, int averageingWindowSize, int judgementWindowSize, double threshold) + int windowSize=64, int backAddWindowSize=5, int lookaheadWindowSize=5, int averageingWindowSize=3, int judgementWindowSize=21, double threshold=0.3) => new SrCnnAnomalyEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, windowSize, backAddWindowSize, lookaheadWindowSize, averageingWindowSize, judgementWindowSize, threshold, inputColumnName); } } diff --git a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs index 4164a4f743..80687d0123 100644 --- a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs @@ -24,7 +24,7 @@ namespace Microsoft.ML.Transforms.TimeSeries { - public sealed class SrCnnAnomalyDetector : SrCnnAnomalyDetectionBaseWrapper, IStatefulTransformer + public sealed class SrCnnAnomalyDetector : SrCnnAnomalyDetectionBase, IStatefulTransformer { internal const string Summary = "This transform detects the anomalies in a time-series using SRCNN."; internal const string LoaderSignature = "SrCnnAnomalyDetector"; @@ -57,7 +57,7 @@ internal sealed class Options : TransformInputBase ShortName = "avgwnd", SortOrder = 104)] public int AvergingWindowSize = 3; - [Argument(ArgumentType.Required, HelpText = "The size of sliding window to generate a saliency map for the series.", + [Argument(ArgumentType.Required, HelpText = "The size of sliding window to calculate the anomaly score for each data point.", ShortName = "jdgwnd", SortOrder = 105)] public int JudgementWindowSize = 21; @@ -139,7 +139,7 @@ private static IRowMapper Create(IHostEnvironment env, ModelLoadContext ctx, Dat IStatefulTransformer IStatefulTransformer.Clone() { var clone = (SrCnnAnomalyDetector)MemberwiseClone(); - clone.InternalTransform.StateRef = (SrCnnAnomalyDetectionBase.State)clone.InternalTransform.StateRef.Clone(); + clone.InternalTransform.StateRef = (SrCnnAnomalyDetectionBaseCore.State)clone.InternalTransform.StateRef.Clone(); clone.InternalTransform.StateRef.InitState(clone.InternalTransform, InternalTransform.Host); return clone; } @@ -176,15 +176,15 @@ private protected override void SaveModel(ModelSaveContext ctx) /// public sealed class SrCnnAnomalyEstimator : TrivialEstimator { - /// - /// - /// - /// - /// - /// - /// - /// - /// + /// Host environment. + /// Name of the column resulting from the transformation of . + /// The size of the sliding window for computing spectral residual. + /// The size of the sliding window for computing spectral residual. + /// The number of pervious points used in prediction. + /// The size of sliding window to generate a saliency map for the series. + /// The size of sliding window to calculate the anomaly score for each data point. + /// The threshold to determine anomaly, score larger than the threshold is considered as anomaly. + /// Name of column to transform. The column data must be . internal SrCnnAnomalyEstimator(IHostEnvironment env, string outputColumnName, int windowSize, diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 178f60d3ef..439a0e5ca4 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -12,7 +12,7 @@ namespace Microsoft.ML.Transforms.TimeSeries { - public class SrCnnAnomalyDetectionBaseWrapper : IStatefulTransformer, ICanSaveModel + public class SrCnnAnomalyDetectionBase : IStatefulTransformer, ICanSaveModel { /// /// Whether a call to should succeed, on an @@ -76,23 +76,23 @@ private protected virtual void SaveModel(ModelSaveContext ctx) /// internal IDataTransform MakeDataTransform(IDataView input) => InternalTransform.MakeDataTransform(input); - internal SrCnnAnomalyDetectionBase InternalTransform; + internal SrCnnAnomalyDetectionBaseCore InternalTransform; - internal SrCnnAnomalyDetectionBaseWrapper(SrCnnArgumentBase args, string name, IHostEnvironment env) + internal SrCnnAnomalyDetectionBase(SrCnnArgumentBase args, string name, IHostEnvironment env) { - InternalTransform = new SrCnnAnomalyDetectionBase(args, name, env, this); + InternalTransform = new SrCnnAnomalyDetectionBaseCore(args, name, env, this); } - internal SrCnnAnomalyDetectionBaseWrapper(IHostEnvironment env, ModelLoadContext ctx, string name) + internal SrCnnAnomalyDetectionBase(IHostEnvironment env, ModelLoadContext ctx, string name) { - InternalTransform = new SrCnnAnomalyDetectionBase(env, ctx, name, this); + InternalTransform = new SrCnnAnomalyDetectionBaseCore(env, ctx, name, this); } - internal sealed class SrCnnAnomalyDetectionBase : SrCnnTransformBase + internal sealed class SrCnnAnomalyDetectionBaseCore : SrCnnTransformBase { - internal SrCnnAnomalyDetectionBaseWrapper Parent; + internal SrCnnAnomalyDetectionBase Parent; - public SrCnnAnomalyDetectionBase(SrCnnArgumentBase args, string name, IHostEnvironment env, SrCnnAnomalyDetectionBaseWrapper parent) + public SrCnnAnomalyDetectionBaseCore(SrCnnArgumentBase args, string name, IHostEnvironment env, SrCnnAnomalyDetectionBase parent) : base(args, name, env) { InitialWindowSize = WindowSize; @@ -101,10 +101,9 @@ public SrCnnAnomalyDetectionBase(SrCnnArgumentBase args, string name, IHostEnvir Parent = parent; } - public SrCnnAnomalyDetectionBase(IHostEnvironment env, ModelLoadContext ctx, string name, SrCnnAnomalyDetectionBaseWrapper parent) + public SrCnnAnomalyDetectionBaseCore(IHostEnvironment env, ModelLoadContext ctx, string name, SrCnnAnomalyDetectionBase parent) : base(env, ctx, name) { - //Host.CheckDecode(InitialWindowSize == 0); StateRef = new State(ctx.Reader); StateRef.InitState(this, Host); Parent = parent; @@ -132,7 +131,6 @@ private protected override void SaveModel(ModelSaveContext ctx) internal void SaveThis(ModelSaveContext ctx) { ctx.CheckAtModel(); - //Host.Assert(InitialWindowSize == 0); base.SaveModel(ctx); // *** Binary format *** @@ -230,7 +228,7 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ List filteredIfftMagList = AverageFilter(ifftMagList, Parent.JudgementWindowSize); // Step 7: Calculate score and set result - var score = CalculateSocre(ifftMagList[data.Count-1], filteredIfftMagList[data.Count-1]); + var score = CalculateSocre(ifftMagList[data.Count - 1], filteredIfftMagList[data.Count - 1]); score /= 10.0f; result.Values[1] = score; @@ -239,14 +237,14 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ var detres = score > Parent.AlertThreshold ? 1 : 0; result.Values[0] = detres; - var mag = ifftMagList[data.Count-1]; + var mag = ifftMagList[data.Count - 1]; result.Values[2] = mag; } private List BackAdd(Single input, FixedSizeQueue data) { List predictArray = new List(); - for (int i = data.Count-Parent.LookaheadWindowSize-2; i < data.Count-1; ++i) + for (int i = data.Count - Parent.LookaheadWindowSize - 2; i < data.Count - 1; ++i) { predictArray.Add(data[i]); } @@ -264,7 +262,7 @@ private Single PredictNext(Single input, List data) { var n = data.Count; Single slopeSum = 0.0f; - for (int i = 0; i < n-1; ++i) + for (int i = 0; i < n - 1; ++i) { slopeSum += (input - data[i]) / (n - 1 - i); } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index d10fdd6f30..24b3a6e722 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -70,7 +70,11 @@ private protected SrCnnTransformBase(int windowSize, int initialWindowSize, stri int backAddWindowSize, int lookaheadWindowSize, int averagingWindowSize, int judgementWindowSize, Double alertThreshold) : base(Contracts.CheckRef(env, nameof(env)).Register(name), windowSize, initialWindowSize, outputColumnName, inputColumnName, new VectorDataViewType(NumberDataViewType.Double, 3)) { - //TODO: Check argument + Host.CheckUserArg(backAddWindowSize > 0, nameof(SrCnnArgumentBase.BackAddWindowSize), "Must be non-negative"); + Host.CheckUserArg(lookaheadWindowSize > 0 && lookaheadWindowSize < windowSize, nameof(SrCnnArgumentBase.LookaheadWindowSize), "Must be non-negative and not larger than window size"); + Host.CheckUserArg(averagingWindowSize > 0 && averagingWindowSize < windowSize, nameof(SrCnnArgumentBase.AvergingWindowSize), "Must be non-negative and not larger than window size"); + Host.CheckUserArg(judgementWindowSize > 0 && judgementWindowSize < windowSize, nameof(SrCnnArgumentBase.JudgementWindowSize), "Must be non-negative and not larger than window size"); + Host.CheckUserArg(alertThreshold > 0 && alertThreshold < 1, nameof(SrCnnArgumentBase.Threshold), "Must be in (0,1)"); BackAddWindowSize = backAddWindowSize; LookaheadWindowSize = lookaheadWindowSize; @@ -103,8 +107,7 @@ private protected SrCnnTransformBase(IHostEnvironment env, ModelLoadContext ctx, JudgementWindowSize = (int)temp; Host.CheckDecode(JudgementWindowSize > 0); - temp = ctx.Reader.ReadByte(); - AlertThreshold = (double)temp; + AlertThreshold = ctx.Reader.ReadDouble(); Host.CheckDecode(AlertThreshold >= 0 && AlertThreshold <= 1); } @@ -132,7 +135,7 @@ private protected override void SaveModel(ModelSaveContext ctx) ctx.Writer.Write((byte)LookaheadWindowSize); ctx.Writer.Write((byte)AvergingWindowSize); ctx.Writer.Write((byte)JudgementWindowSize); - ctx.Writer.Write((byte)AlertThreshold); + ctx.Writer.Write(AlertThreshold); } internal override IStatefulRowMapper MakeRowMapper(DataViewSchema schema) => new Mapper(Host, this, schema); @@ -162,7 +165,7 @@ public Mapper(IHostEnvironment env, SrCnnTransformBase parent, D _parent = parent; _parentSchema = inputSchema; - _slotNames = new VBuffer>(2, new[] { "Alert".AsMemory(), "Raw Score".AsMemory(), + _slotNames = new VBuffer>(_parent.OutputLength, new[] { "Alert".AsMemory(), "Raw Score".AsMemory(), "Mag".AsMemory()}); State = (SrCnnStateBase)_parent.StateRef; @@ -292,8 +295,7 @@ private protected sealed override void TransformCore(ref TInput input, FixedSize Host.Assert(outputLength >= 2); var result = VBufferEditor.Create(ref dst, outputLength); - for (int i = 0; i < outputLength; ++i) - result.Values[i] = Double.NaN; + result.Values.Fill(Double.NaN); SpectralResidual(input, windowedBuffer, ref result); @@ -303,12 +305,10 @@ private protected sealed override void TransformCore(ref TInput input, FixedSize private protected sealed override void InitializeStateCore(bool disk = false) { Parent = (SrCnnTransformBase)ParentTransform; - //TODO: assert for value threshold } private protected override void LearnStateFromDataCore(FixedSizeQueue data) { - //TODO: } private protected virtual void SpectralResidual(TInput input, FixedSizeQueue data, ref VBufferEditor result) From be277ba10c7602bf04d70b59cc5a080aad71b53d Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Thu, 30 May 2019 09:20:13 -0700 Subject: [PATCH 6/9] Fix a predict bug --- .../SrCnnAnomalyDetectionBase.cs | 12 ++++++------ src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 439a0e5ca4..748563000d 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -174,7 +174,7 @@ private protected override void LearnStateFromDataCore(FixedSizeQueue dat private protected override sealed void SpectralResidual(Single input, FixedSizeQueue data, ref VBufferEditor result) { // Step 1: Get backadd wave - List backAddList = BackAdd(input, data); + List backAddList = BackAdd(data); // Step 2: FFT transformation int length = backAddList.Count; @@ -241,14 +241,14 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ result.Values[2] = mag; } - private List BackAdd(Single input, FixedSizeQueue data) + private List BackAdd(FixedSizeQueue data) { List predictArray = new List(); for (int i = data.Count - Parent.LookaheadWindowSize - 2; i < data.Count - 1; ++i) { predictArray.Add(data[i]); } - var predictedValue = PredictNext(input, predictArray); + var predictedValue = PredictNext(predictArray); List backAddArray = new List(); for (int i = 0; i < data.Count; ++i) { @@ -258,15 +258,15 @@ private List BackAdd(Single input, FixedSizeQueue data) return backAddArray; } - private Single PredictNext(Single input, List data) + private Single PredictNext(List data) { var n = data.Count; Single slopeSum = 0.0f; for (int i = 0; i < n - 1; ++i) { - slopeSum += (input - data[i]) / (n - 1 - i); + slopeSum += (data[n-1] - data[i]) / (n - 1 - i); } - return (input + slopeSum); + return (data[1] + slopeSum); } private List AverageFilter(List data, int n) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index 24b3a6e722..2d931b185d 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -292,7 +292,6 @@ private protected override void SetNaOutput(ref VBuffer dst) private protected sealed override void TransformCore(ref TInput input, FixedSizeQueue windowedBuffer, long iteration, ref VBuffer dst) { var outputLength = Parent.OutputLength; - Host.Assert(outputLength >= 2); var result = VBufferEditor.Create(ref dst, outputLength); result.Values.Fill(Double.NaN); From 7e41da86510d82aa5fda499a2bb230f12f6be1b7 Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Fri, 31 May 2019 00:26:05 -0700 Subject: [PATCH 7/9] 1. Fix build fail problem; 2. Improve samples; 3. Minor change to docs --- .../TimeSeries/DetectAnomalyBySrCnn.cs | 82 ++++++++++++------- .../DetectAnomalyBySrCnnBatchPrediction.cs | 45 +++++++--- .../ExtensionsCatalog.cs | 8 +- .../SrCnnAnomalyDetectionBase.cs | 4 +- .../SrCnnTransformBase.cs | 12 +-- .../TimeSeriesDirectApi.cs | 13 ++- 6 files changed, 106 insertions(+), 58 deletions(-) diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs index 97c5265b4e..9b17e86244 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnn.cs @@ -17,53 +17,69 @@ public static void Example() // as well as the source of randomness. var ml = new MLContext(); - // Generate sample series data with a spike - const int Size = 10; - var data = new List(Size + 1) + // Generate sample series data with an anomaly + var data = new List(); + for (int index = 0; index < 20; index++) { - new TimeSeriesData(5), - new TimeSeriesData(5), - new TimeSeriesData(5), - new TimeSeriesData(5), - new TimeSeriesData(5), - - // This is a spike. - new TimeSeriesData(10), - - new TimeSeriesData(5), - new TimeSeriesData(5), - new TimeSeriesData(5), - new TimeSeriesData(5), - new TimeSeriesData(5), - }; + data.Add(new TimeSeriesData(5)); + } + data.Add(new TimeSeriesData(10)); + for (int index = 0; index < 5; index++) + { + data.Add(new TimeSeriesData(5)); + } // Convert data to IDataView. var dataView = ml.Data.LoadFromEnumerable(data); - // Setup IidSpikeDetector arguments + // Setup the estimator arguments string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); string inputColumnName = nameof(TimeSeriesData.Value); // The transformed model. - ITransformer model = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 64, 5, 5, 3, 21, 0.25).Fit(dataView); + ITransformer model = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 16, 5, 5, 3, 8, 0.35).Fit(dataView); // Create a time series prediction engine from the model. var engine = model.CreateTimeSeriesPredictionFunction(ml); Console.WriteLine($"{outputColumnName} column obtained post-transformation."); + Console.WriteLine("Data\tAlert\tScore\tMag"); + + // Prediction column obtained post-transformation. + // Data Alert Score Mag // Create non-anomalous data and check for anomaly. - for (int index = 0; index < 100; index++) + for (int index = 0; index < 20; index++) { - // Anomaly spike detection. + // Anomaly detection. PrintPrediction(5, engine.Predict(new TimeSeriesData(5))); } - // Spike. - for (int index = 0; index < 5; index++) - { - PrintPrediction(15, engine.Predict(new TimeSeriesData(10))); - } + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + + // Anomaly. + PrintPrediction(10, engine.Predict(new TimeSeriesData(10))); + + //10 1 0.47 0.93 <-- alert is on, predicted anomaly // Checkpoint the model. var modelPath = "temp.zip"; @@ -75,16 +91,22 @@ public static void Example() for (int index = 0; index < 5; index++) { - // Anomaly spike detection. + // Anomaly detection. PrintPrediction(5, engine.Predict(new TimeSeriesData(5))); } + + //5 0 0.31 0.50 + //5 0 0.05 0.30 + //5 0 0.01 0.23 + //5 0 0.00 0.21 + //5 0 0.01 0.25 } private static void PrintPrediction(float value, SrCnnAnomalyDetection prediction) => Console.WriteLine("{0}\t{1}\t{2:0.00}\t{3:0.00}", value, prediction.Prediction[0], prediction.Prediction[1], prediction.Prediction[2]); - class TimeSeriesData + private class TimeSeriesData { public float Value; @@ -94,7 +116,7 @@ public TimeSeriesData(float value) } } - class SrCnnAnomalyDetection + private class SrCnnAnomalyDetection { [VectorType(3)] public double[] Prediction { get; set; } diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs index a401bb9be3..ef1d2a0de9 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs @@ -13,16 +13,13 @@ public static void Example() // as well as the source of randomness. var ml = new MLContext(); - // Generate sample series data with a spike + // Generate sample series data with an anomaly var data = new List(); - for (int index = 0; index < 100; index++) + for (int index = 0; index < 20; index++) { data.Add(new TimeSeriesData(5)); } - for (int index = 0; index < 5; index++) - { - data.Add(new TimeSeriesData(15)); - } + data.Add(new TimeSeriesData(10)); for (int index = 0; index < 5; index++) { data.Add(new TimeSeriesData(5)); @@ -36,25 +33,53 @@ public static void Example() string inputColumnName = nameof(TimeSeriesData.Value); // The transformed data. - var transformedData = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 64, 5, 5, 3, 21, 0.25).Fit(dataView).Transform(dataView); + var transformedData = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 16, 5, 5, 3, 8, 0.35).Fit(dataView).Transform(dataView); // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable(transformedData, reuseRowObject: false); Console.WriteLine($"{outputColumnName} column obtained post-transformation."); - Console.WriteLine("Data\tAlert\tScore\tP-Value"); + Console.WriteLine("Data\tAlert\tScore\tMag"); int k = 0; foreach (var prediction in predictionColumn) PrintPrediction(data[k++].Value, prediction); + //Prediction column obtained post-transformation. + //Data Alert Score Mag + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.00 0.00 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //5 0 0.03 0.18 + //10 1 0.47 0.93 + //5 0 0.31 0.50 + //5 0 0.05 0.30 + //5 0 0.01 0.23 + //5 0 0.00 0.21 + //5 0 0.01 0.25 } private static void PrintPrediction(float value, SrCnnAnomalyDetection prediction) => Console.WriteLine("{0}\t{1}\t{2:0.00}\t{3:0.00}", value, prediction.Prediction[0], prediction.Prediction[1], prediction.Prediction[2]); - class TimeSeriesData + private class TimeSeriesData { public float Value; @@ -64,7 +89,7 @@ public TimeSeriesData(float value) } } - class SrCnnAnomalyDetection + private class SrCnnAnomalyDetection { [VectorType(3)] public double[] Prediction { get; set; } diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 64891b7e58..baee5321a5 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -135,7 +135,13 @@ public static SsaSpikeEstimator DetectSpikeBySsa(this TransformsCatalog catalog, /// The size of sliding window to generate a saliency map for the series. /// The size of sliding window to calculate the anomaly score for each data point. /// The threshold to determine anomaly, score larger than the threshold is considered as anomaly. - /// + /// + /// + /// + /// + /// public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog catalog, string outputColumnName, string inputColumnName, int windowSize=64, int backAddWindowSize=5, int lookaheadWindowSize=5, int averageingWindowSize=3, int judgementWindowSize=21, double threshold=0.3) => new SrCnnAnomalyEstimator(CatalogUtils.GetEnvironment(catalog), outputColumnName, windowSize, backAddWindowSize, lookaheadWindowSize, averageingWindowSize, judgementWindowSize, threshold, inputColumnName); diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 748563000d..6f95309c94 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -23,7 +23,6 @@ public class SrCnnAnomalyDetectionBase : IStatefulTransformer, ICanSaveModel /// /// Create a clone of the transformer. Used for taking the snapshot of the state. /// - /// IStatefulTransformer IStatefulTransformer.Clone() => InternalTransform.Clone(); /// @@ -51,8 +50,7 @@ public IRowToRowMapper GetStatefulRowToRowMapper(DataViewSchema inputSchema) => ((IStatefulTransformer)InternalTransform).GetStatefulRowToRowMapper(inputSchema); /// - /// Take the data in, make transformations, output the data. - /// Note that 's are lazy, so no actual transformations happen here, just schema validation. + /// Initialize a transformer which will do lambda transfrom on input data in prediction engine. No actual transformations happen here, just schema validation. /// public IDataView Transform(IDataView input) => InternalTransform.Transform(input); diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index 2d931b185d..d1d43b2e5e 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -54,17 +54,17 @@ internal abstract class SrCnnArgumentBase internal abstract class SrCnnTransformBase : SequentialTransformerBase, TState> where TState : SrCnnTransformBase.SrCnnStateBase, new() { - internal int BackAddWindowSize; + internal int BackAddWindowSize { get; } - internal int LookaheadWindowSize; + internal int LookaheadWindowSize { get; } - internal int AvergingWindowSize; + internal int AvergingWindowSize { get; } - internal int JudgementWindowSize; + internal int JudgementWindowSize { get; } - internal Double AlertThreshold; + internal Double AlertThreshold { get; } - internal int OutputLength; + internal int OutputLength { get; } private protected SrCnnTransformBase(int windowSize, int initialWindowSize, string inputColumnName, string outputColumnName, string name, IHostEnvironment env, int backAddWindowSize, int lookaheadWindowSize, int averagingWindowSize, int judgementWindowSize, Double alertThreshold) diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 073a284a62..281fbe96c2 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -299,16 +299,13 @@ public void AnomalyDetectionWithSrCnn() { var ml = new MLContext(); - // Generate sample series data with a spike + // Generate sample series data with an anomaly var data = new List(); - for (int index = 0; index < 100; index++) + for (int index = 0; index < 20; index++) { data.Add(new TimeSeriesData(5)); } - for (int index = 0; index < 5; index++) - { - data.Add(new TimeSeriesData(15)); - } + data.Add(new TimeSeriesData(10)); for (int index = 0; index < 5; index++) { data.Add(new TimeSeriesData(5)); @@ -322,7 +319,7 @@ public void AnomalyDetectionWithSrCnn() string inputColumnName = nameof(TimeSeriesData.Value); // The transformed data. - var transformedData = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 64, 5, 5, 3, 21, 0.25).Fit(dataView).Transform(dataView); + var transformedData = ml.Transforms.DetectAnomalyBySrCnn(outputColumnName, inputColumnName, 16, 5, 5, 3, 8, 0.35).Fit(dataView).Transform(dataView); // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable(transformedData, reuseRowObject: false); @@ -330,7 +327,7 @@ public void AnomalyDetectionWithSrCnn() int k = 0; foreach (var prediction in predictionColumn) { - if (k == 101 || k == 106) + if (k == 20) Assert.Equal(1, prediction.Prediction[0]); else Assert.Equal(0, prediction.Prediction[0]); From dcb271b8a458d96ddd26dee07cb9f0ac8c936db3 Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Fri, 31 May 2019 10:28:46 -0700 Subject: [PATCH 8/9] Add document. --- .../ExtensionsCatalog.cs | 10 +++--- .../SRCNNAnomalyDetector.cs | 34 ++++++++++++++++++- .../SrCnnTransformBase.cs | 6 ++-- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index baee5321a5..f5261db8cc 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -130,11 +130,11 @@ public static SsaSpikeEstimator DetectSpikeBySsa(this TransformsCatalog catalog, /// The column data is a vector of . The vector contains 3 elements: alert (1 means anomaly while 0 means normal), raw score, and magnitude of spectual residual. /// Name of column to transform. The column data must be . /// The size of the sliding window for computing spectral residual. - /// The number of points to add back of training window. - /// The number of pervious points used in prediction. - /// The size of sliding window to generate a saliency map for the series. - /// The size of sliding window to calculate the anomaly score for each data point. - /// The threshold to determine anomaly, score larger than the threshold is considered as anomaly. + /// The number of points to add back of training window. No more than windowSize, usually keep default value. + /// The number of pervious points used in prediction. No more than windowSize, usually keep default value. + /// The size of sliding window to generate a saliency map for the series. No more than windowSize, usually keep default value. + /// The size of sliding window to calculate the anomaly score for each data point. No more than windowSize. + /// The threshold to determine anomaly, score larger than the threshold is considered as anomaly. Should be in (0,1) /// /// /// + /// resulting from fitting a . + /// public sealed class SrCnnAnomalyDetector : SrCnnAnomalyDetectionBase, IStatefulTransformer { internal const string Summary = "This transform detects the anomalies in a time-series using SRCNN."; @@ -172,8 +175,37 @@ private protected override void SaveModel(ModelSaveContext ctx) } /// - /// Detect anomalies in time series using Spectral Residual + /// Detect anomalies in time series using Spectral Residual(SR) algorithm /// + /// + /// | + ///| Output column data type | 3-element vector of | + ///### Background + ///At Microsoft, we develop a time-series anomaly detection service + ///which helps customers to monitor the time-series continuously + ///and alert for potential incidents on time. To tackle the problem + ///of time-series anomaly detection, we propose a novel algorithm + ///based on Spectral Residual (SR) and Convolutional Neural Network + ///(CNN). The SR model is borrowed from visual saliency detection domain to time-series anomaly detection. And here we onboarded this SR algorithm firstly. + /// + ///The Spectral Residual (SR) algorithm is unsupervised, which means training step is not needed while using SR. It consists of three major steps: + ///(1) Fourier Transform to get the log amplitude spectrum; + ///(2) calculation of spectral residual; + ///(3) Inverse Fourier Transform that transforms the sequence back to spatial domain. + /// + ///There are several parameters for SR algorithm. To obtain a model with good performance, we suggest to tune windowSize and threshold at first, these are the most important parameters to SR. Then you could search for an appropriate judgementWindowSize which is no larger than windowSize. And for the remaining parameters, you could use the default value directly. + /// + ///* Link to the KDD 2019 paper will be updated after it goes public. + /// ]]> + /// + /// + /// public sealed class SrCnnAnomalyEstimator : TrivialEstimator { /// Host environment. diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index d1d43b2e5e..821b62de5e 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -71,9 +71,9 @@ private protected SrCnnTransformBase(int windowSize, int initialWindowSize, stri : base(Contracts.CheckRef(env, nameof(env)).Register(name), windowSize, initialWindowSize, outputColumnName, inputColumnName, new VectorDataViewType(NumberDataViewType.Double, 3)) { Host.CheckUserArg(backAddWindowSize > 0, nameof(SrCnnArgumentBase.BackAddWindowSize), "Must be non-negative"); - Host.CheckUserArg(lookaheadWindowSize > 0 && lookaheadWindowSize < windowSize, nameof(SrCnnArgumentBase.LookaheadWindowSize), "Must be non-negative and not larger than window size"); - Host.CheckUserArg(averagingWindowSize > 0 && averagingWindowSize < windowSize, nameof(SrCnnArgumentBase.AvergingWindowSize), "Must be non-negative and not larger than window size"); - Host.CheckUserArg(judgementWindowSize > 0 && judgementWindowSize < windowSize, nameof(SrCnnArgumentBase.JudgementWindowSize), "Must be non-negative and not larger than window size"); + Host.CheckUserArg(lookaheadWindowSize > 0 && lookaheadWindowSize <= windowSize, nameof(SrCnnArgumentBase.LookaheadWindowSize), "Must be non-negative and not larger than window size"); + Host.CheckUserArg(averagingWindowSize > 0 && averagingWindowSize <= windowSize, nameof(SrCnnArgumentBase.AvergingWindowSize), "Must be non-negative and not larger than window size"); + Host.CheckUserArg(judgementWindowSize > 0 && judgementWindowSize <= windowSize, nameof(SrCnnArgumentBase.JudgementWindowSize), "Must be non-negative and not larger than window size"); Host.CheckUserArg(alertThreshold > 0 && alertThreshold < 1, nameof(SrCnnArgumentBase.Threshold), "Must be in (0,1)"); BackAddWindowSize = backAddWindowSize; From a4f8b24444aef3ffd07f9de11f0fa87d510a839a Mon Sep 17 00:00:00 2001 From: Meng Ai Date: Fri, 31 May 2019 14:10:08 -0700 Subject: [PATCH 9/9] Add equations to doc --- .../SRCNNAnomalyDetector.cs | 58 ++++++++++++------- .../SrCnnAnomalyDetectionBase.cs | 2 +- .../SrCnnTransformBase.cs | 2 +- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs index b2cb01c699..31f00c10d1 100644 --- a/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SRCNNAnomalyDetector.cs @@ -179,29 +179,47 @@ private protected override void SaveModel(ModelSaveContext ctx) /// /// /// | - ///| Output column data type | 3-element vector of | - ///### Background - ///At Microsoft, we develop a time-series anomaly detection service - ///which helps customers to monitor the time-series continuously - ///and alert for potential incidents on time. To tackle the problem - ///of time-series anomaly detection, we propose a novel algorithm - ///based on Spectral Residual (SR) and Convolutional Neural Network - ///(CNN). The SR model is borrowed from visual saliency detection domain to time-series anomaly detection. And here we onboarded this SR algorithm firstly. + /// To create this estimator, use + /// [DetectAnomalyBySrCnn](xref:Microsoft.ML.TimeSeriesCatalog.DetectAnomalyBySrCnn(Microsoft.ML.TransformsCatalog,System.String,System.String,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Double)) + /// ### Estimator Characteristics + /// | | | + /// | -- | -- | + /// | Does this estimator need to look at the data to train its parameters? | No | + /// | Input column data type | | + /// | Output column data type | 3-element vector of | + /// ### Background + /// At Microsoft, we develop a time-series anomaly detection service which helps customers to monitor the time-series continuously + /// and alert for potential incidents on time. To tackle the problem of time-series anomaly detection, + /// we propose a novel algorithm based on Spectral Residual (SR) and Convolutional Neural Network + /// (CNN). The SR model is borrowed from visual saliency detection domain to time-series anomaly detection. + /// And here we onboarded this SR algorithm firstly. /// - ///The Spectral Residual (SR) algorithm is unsupervised, which means training step is not needed while using SR. It consists of three major steps: - ///(1) Fourier Transform to get the log amplitude spectrum; - ///(2) calculation of spectral residual; - ///(3) Inverse Fourier Transform that transforms the sequence back to spatial domain. + /// The Spectral Residual (SR) algorithm is unsupervised, which means training step is not needed while using SR. It consists of three major steps: + /// (1) Fourier Transform to get the log amplitude spectrum; + /// (2) calculation of spectral residual; + /// (3) Inverse Fourier Transform that transforms the sequence back to spatial domain. + /// Mathematically, given a sequence $\mathbf{x}$, we have + /// $$A(f) = Amplitude(\mathfrak{F}(\mathbf{x}))\\P(f) = Phrase(\mathfrak{F}(\mathbf{x}))\\L(f) = log(A(f))\\AL(f) = h_n(f) \cdot L(f)\\R(f) = L(f) - AL(f)\\S(\mathbf{x}) = \mathfrak{F}^{-1}(exp(R(f) + P(f))^{2})$$ + /// where $\mathfrak{F}$ and $\mathfrak{F}^{-1}$ denote Fourier Transform and Inverse Fourier Transform respectively. + /// $\mathbf{x}$ is the input sequence with shape $n × 1$; $A(f)$ is the amplitude spectrum of sequence $\mathbf{x}$; + /// $P(f)$ is the corresponding phase spectrum of sequence $\mathbf{x}$; $L(f)$ is the log representation of $A(f)$; + /// and $AL(f)$ is the average spectrum of $L(f)$ which can be approximated by convoluting the input sequence by $h_n(f)$, + /// where $h_n(f)$ is an $n × n$ matrix defined as: + /// $$n_f(f) = \begin{bmatrix}1&1&1&\cdots&1\\1&1&1&\cdots&1\\\vdots&\vdots&\vdots&\ddots&\vdots\\1&1&1&\cdots&1\end{bmatrix}$$ + /// $R(f)$ is the spectral residual, i.e., the log spectrum $L(f)$ subtracting the averaged log spectrum $AL(f)$. + /// The spectral residual serves as a compressed representation of the sequence while the innovation part of the original sequence becomes more significant. + /// At last, we transfer the sequence back to spatial domain via Inverse Fourier Transform. The result sequence $S(\mathbf{x})$ is called the saliency map. + /// Given the saliency map $S(\mathbf{x})$, the output sequence $O(\mathbf{x})$ is computed by: + /// $$O(x_i) = \begin{cases}1, if \frac{S(x_i)-\overline{S(x_i)}}{S(x_i)} > \tau\\0,otherwise,\end{cases}$$ + /// where $x_i$ represents an arbitrary point in sequence $\mathbf{x}$; $S(x_i)$is the corresponding point in the saliency map; + /// and $\overline{S(x_i)}$ is the local average of the preceding points of $S(x_i)$. /// - ///There are several parameters for SR algorithm. To obtain a model with good performance, we suggest to tune windowSize and threshold at first, these are the most important parameters to SR. Then you could search for an appropriate judgementWindowSize which is no larger than windowSize. And for the remaining parameters, you could use the default value directly. + /// There are several parameters for SR algorithm. To obtain a model with good performance, + /// we suggest to tune windowSize and threshold at first, + /// these are the most important parameters to SR. Then you could search for an appropriate judgementWindowSize + /// which is no larger than windowSize. And for the remaining parameters, you could use the default value directly. /// - ///* Link to the KDD 2019 paper will be updated after it goes public. + /// * Link to the KDD 2019 paper will be updated after it goes public. /// ]]> /// /// diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 6f95309c94..566b5b6cd2 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -74,7 +74,7 @@ private protected virtual void SaveModel(ModelSaveContext ctx) /// internal IDataTransform MakeDataTransform(IDataView input) => InternalTransform.MakeDataTransform(input); - internal SrCnnAnomalyDetectionBaseCore InternalTransform; + internal SrCnnAnomalyDetectionBaseCore InternalTransform { get; } internal SrCnnAnomalyDetectionBase(SrCnnArgumentBase args, string name, IHostEnvironment env) { diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index 821b62de5e..9f64fa2167 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -62,7 +62,7 @@ internal abstract class SrCnnTransformBase : SequentialTransform internal int JudgementWindowSize { get; } - internal Double AlertThreshold { get; } + internal double AlertThreshold { get; } internal int OutputLength { get; }