From 9b6e8a3abfbdcf5773cde3b2a9fa4c622d154f1f Mon Sep 17 00:00:00 2001 From: Yael Dekel Date: Sun, 3 May 2020 18:54:42 +0300 Subject: [PATCH 1/6] POC Batch transform --- .../DataView/BatchTransform.cs | 298 ++++++++++++++++++ .../DataPipe/TestDataPipe.cs | 25 ++ 2 files changed, 323 insertions(+) create mode 100644 src/Microsoft.ML.Data/DataView/BatchTransform.cs diff --git a/src/Microsoft.ML.Data/DataView/BatchTransform.cs b/src/Microsoft.ML.Data/DataView/BatchTransform.cs new file mode 100644 index 0000000000..698a4b265f --- /dev/null +++ b/src/Microsoft.ML.Data/DataView/BatchTransform.cs @@ -0,0 +1,298 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.ML.Numeric; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.Data.DataView +{ + public abstract class BatchTransformBase : IDataView + { + private protected sealed class Bindings : ColumnBindingsBase + { + private readonly DataViewType _outputColumnType; + private readonly int _inputColumnIndex; + + public Bindings(DataViewSchema input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) + : base(input, true, outputColumnName) + { + _outputColumnType = outputColumnType; + _inputColumnIndex = Input[inputColumnName].Index; + } + + protected override DataViewType GetColumnTypeCore(int iinfo) + { + Contracts.Check(iinfo == 0); + return _outputColumnType; + } + + // Get a predicate for the input columns. + public Func GetDependencies(Func predicate) + { + Contracts.AssertValue(predicate); + + var active = new bool[Input.Count]; + for (int col = 0; col < ColumnCount; col++) + { + if (!predicate(col)) + continue; + + bool isSrc; + int index = MapColumnIndex(out isSrc, col); + if (isSrc) + active[index] = true; + else + active[_inputColumnIndex] = true; + } + + return col => 0 <= col && col < active.Length && active[col]; + } + } + + public bool CanShuffle => false; + + public DataViewSchema Schema => SchemaBindings.AsSchema; + + private readonly IDataView _source; + private readonly IHost _host; + private protected readonly Bindings SchemaBindings; + protected readonly string InputCol; + + protected BatchTransformBase(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) + { + _host = env.Register("Batch"); + _source = input; + SchemaBindings = new Bindings(input.Schema, inputColumnName, outputColumnName, outputColumnType); + InputCol = inputColumnName; + } + + public long? GetRowCount() => _source.GetRowCount(); + + public DataViewRowCursor GetRowCursor(IEnumerable columnsNeeded, Random rand = null) + { + _host.CheckValue(columnsNeeded, nameof(columnsNeeded)); + _host.CheckValueOrNull(rand); + + var predicate = RowCursorUtils.FromColumnsToPredicate(columnsNeeded, SchemaBindings.AsSchema); + + // If we aren't selecting any of the output columns, don't construct our cursor. + // Note that because we cannot support random due to the inherently + // stratified nature, neither can we allow the base data to be shuffled, + // even if it supports shuffling. + if (!SchemaBindings.AnyNewColumnsActive(predicate)) + { + var activeInput = SchemaBindings.GetActiveInput(predicate); + var inputCursor = _source.GetRowCursor(_source.Schema.Where(c => activeInput[c.Index]), null); + return new BindingsWrappedRowCursor(_host, inputCursor, SchemaBindings); + } + var active = SchemaBindings.GetActive(predicate); + Contracts.Assert(active.Length == SchemaBindings.ColumnCount); + + // REVIEW: We can get a different input predicate for the input cursor and for the lookahead cursor. The lookahead + // cursor is only used for getting the values from the input column, so it only needs that column activated. The + // other cursor is used to get source columns, so it needs the rest of them activated. + var predInput = SchemaBindings.GetDependencies(predicate); + var inputCols = _source.Schema.Where(c => predInput(c.Index)); + return new Cursor(this, _source.GetRowCursor(inputCols), _source.GetRowCursor(inputCols), active); + } + + public DataViewRowCursor[] GetRowCursorSet(IEnumerable columnsNeeded, int n, Random rand = null) + { + return new[] { GetRowCursor(columnsNeeded, rand) }; + } + + protected abstract TBatch InitializeBatch(DataViewRowCursor input); + protected abstract void ProcessBatch(TBatch currentBatch); + protected abstract void ProcessExample(TBatch currentBatch, TInput currentInput); + protected abstract Func GetLastInBatchDelegate(DataViewRowCursor lookAheadCursor); + protected abstract Func GetIsNewBatchDelegate(DataViewRowCursor lookAheadCursor); + protected abstract Delegate[] CreateGetters(DataViewRowCursor input, TBatch currentBatch, bool[] active); + + private sealed class Cursor : RootCursorBase + { + private readonly BatchTransformBase _parent; + private readonly DataViewRowCursor _lookAheadCursor; + private readonly DataViewRowCursor _input; + + private readonly bool[] _active; + private readonly Delegate[] _getters; + + private TBatch _currentBatch; + private readonly Func _lastInBatchInLookAheadCursorDel; + private readonly Func _firstInBatchInInputCursorDel; + private readonly ValueGetter _inputGetterInLookAheadCursor; + private TInput _currentInput; + + public override long Batch => 0; + + public override DataViewSchema Schema => _parent.Schema; + + public Cursor(BatchTransformBase parent, DataViewRowCursor input, DataViewRowCursor lookAheadCursor, bool[] active) + : base(parent._host) + { + _parent = parent; + _input = input; + _lookAheadCursor = lookAheadCursor; + _active = active; + + _currentBatch = _parent.InitializeBatch(_input); + + _getters = _parent.CreateGetters(_input, _currentBatch, _active); + + _lastInBatchInLookAheadCursorDel = _parent.GetLastInBatchDelegate(_lookAheadCursor); + _firstInBatchInInputCursorDel = _parent.GetIsNewBatchDelegate(_input); + _inputGetterInLookAheadCursor = _lookAheadCursor.GetGetter(_lookAheadCursor.Schema[_parent.InputCol]); + } + + public override ValueGetter GetGetter(DataViewSchema.Column column) + { + Contracts.CheckParam(IsColumnActive(column), nameof(column), "requested column is not active"); + + var col = _parent.SchemaBindings.MapColumnIndex(out bool isSrc, column.Index); + if (isSrc) + { + Contracts.AssertValue(_input); + return _input.GetGetter(_input.Schema[col]); + } + + Ch.AssertValue(_getters); + var getter = _getters[col]; + Ch.Assert(getter != null); + var fn = getter as ValueGetter; + if (fn == null) + throw Ch.Except("Invalid TValue in GetGetter: '{0}'", typeof(TValue)); + return fn; + } + + public override ValueGetter GetIdGetter() + { + return + (ref DataViewRowId val) => + { + Ch.Check(IsGood, "Cannot call ID getter in current state"); + val = new DataViewRowId((ulong)Position, 0); + }; + } + + public override bool IsColumnActive(DataViewSchema.Column column) + { + Ch.Check(column.Index < _parent.SchemaBindings.AsSchema.Count); + return _active[column.Index]; + } + + protected override bool MoveNextCore() + { + if (!_input.MoveNext()) + return false; + if (!_firstInBatchInInputCursorDel()) + return true; + + // If we are here, this means that _input.MoveNext() has gotten us to the beginning of the next batch, + // so now we need to look ahead at the entire next batch in the _lookAheadCursor. + // The _lookAheadCursor's position should be on the last row of the previous batch (or -1). + Ch.Assert(_lastInBatchInLookAheadCursorDel()); + + var good = _lookAheadCursor.MoveNext(); + // The two cursors should have the same number of elements, so if _input.MoveNext() returned true, + // then it must return true here too. + Ch.Assert(good); + + do + { + _inputGetterInLookAheadCursor(ref _currentInput); + _parent.ProcessExample(_currentBatch, _currentInput); + } while (!_lastInBatchInLookAheadCursorDel() && _lookAheadCursor.MoveNext()); + + _parent.ProcessBatch(_currentBatch); + return true; + } + } + } + + // Sample class: look at the whole batch of float values , and return for each input x the + // product x*dotproduct(). + public sealed class BatchTransform : BatchTransformBase + { + private readonly int _batchSize; + + public BatchTransform(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, int batchSize) + : base(env, input, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, batchSize)) + { + _batchSize = batchSize; + } + + protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch currentBatch, bool[] active) + { + if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) + return new Delegate[1]; + return new[] { currentBatch.CreateGetter(input, InputCol) }; + } + + protected override Batch InitializeBatch(DataViewRowCursor input) => new Batch(_batchSize); + + protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) + { + return () => input.Position % _batchSize == 0; + } + + protected override Func GetLastInBatchDelegate(DataViewRowCursor input) + { + return () => (input.Position + 1) % _batchSize == 0; + } + + protected override void ProcessExample(Batch currentBatch, float currentInput) + { + currentBatch.AddValue(currentInput); + } + + protected override void ProcessBatch(Batch currentBatch) + { + // Here we would do any calculations that need to be done on the entire batch. + currentBatch.Process(); + currentBatch.Reset(); + } + + public sealed class Batch + { + private readonly List _batch; + private float _dotProductCur; + + public Batch(int batchSize) + { + _batch = new List(batchSize); + } + + public void AddValue(float value) + { + _batch.Add(value); + } + + public void Process() + { + _dotProductCur = VectorUtils.NormSquared(new ReadOnlySpan(_batch.ToArray())); + } + + public void Reset() + { + _batch.Clear(); + } + + public ValueGetter CreateGetter(DataViewRowCursor input, string inputCol) + { + ValueGetter srcGetter = input.GetGetter(input.Schema[inputCol]); + ValueGetter getter = + (ref float dst) => + { + float src = default; + srcGetter(ref src); + dst = src * _dotProductCur; + }; + return getter; + } + } + } +} diff --git a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs index 7cae9e81a5..435bdbaea4 100644 --- a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs +++ b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs @@ -7,6 +7,7 @@ using System.IO; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; +using Microsoft.ML.Data.DataView; using Microsoft.ML.Internal.Utilities; using Microsoft.ML.Runtime; using Microsoft.ML.TestFrameworkCommon; @@ -1300,6 +1301,30 @@ public void ArrayDataViewBuilderNoCols() Done(); } + [Fact] + public void TestBatchTransform() + { + var bldr = new ArrayDataViewBuilder(Env); + bldr.AddColumn("Input", NumberDataViewType.Single, new[] { 1f, 2f, 3f, 2f, 3f, 4f, 3f, 4f, 5f, 4f }); + var input = bldr.GetDataView(); + + var output = new BatchTransform(Env, input, "Input", "Output", 3); + using (var cursor = output.GetRowCursor(output.Schema)) + { + var inputGetter = cursor.GetGetter(cursor.Schema["Input"]); + var outputGetter = cursor.GetGetter(cursor.Schema["Output"]); + Log("Input\tOutput"); + while (cursor.MoveNext()) + { + var src = 0f; + var dst = 0f; + inputGetter(ref src); + outputGetter(ref dst); + Log($"{src}\t{dst}"); + } + } + } + [Fact] public void SavePipeExpr() { From 61dbe4264adbbee53cbbd9b9b1d092fd0346977a Mon Sep 17 00:00:00 2001 From: Klaus Marius Hansen Date: Tue, 12 May 2020 21:29:05 -0700 Subject: [PATCH 2/6] SrCnn batch interface --- ... => DetectAnomalyBySrCnnBatchTransform.cs} | 49 ++++++++++++++++--- .../ExtensionsCatalog.cs | 24 +++++++++ .../DataPipe/TestDataPipe.cs | 22 ++++++++- 3 files changed, 87 insertions(+), 8 deletions(-) rename src/Microsoft.ML.Data/DataView/{BatchTransform.cs => DetectAnomalyBySrCnnBatchTransform.cs} (86%) diff --git a/src/Microsoft.ML.Data/DataView/BatchTransform.cs b/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs similarity index 86% rename from src/Microsoft.ML.Data/DataView/BatchTransform.cs rename to src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs index 698a4b265f..a16610aa4e 100644 --- a/src/Microsoft.ML.Data/DataView/BatchTransform.cs +++ b/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs @@ -215,11 +215,11 @@ protected override bool MoveNextCore() // Sample class: look at the whole batch of float values , and return for each input x the // product x*dotproduct(). - public sealed class BatchTransform : BatchTransformBase + public sealed class DetectAnomalyBySrCnnBatchTransform : BatchTransformBase { private readonly int _batchSize; - public BatchTransform(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, int batchSize) + public DetectAnomalyBySrCnnBatchTransform(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, DetectMode detectMode) : base(env, input, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, batchSize)) { _batchSize = batchSize; @@ -251,18 +251,42 @@ protected override void ProcessExample(Batch currentBatch, float currentInput) protected override void ProcessBatch(Batch currentBatch) { - // Here we would do any calculations that need to be done on the entire batch. currentBatch.Process(); currentBatch.Reset(); } + /// + /// The detect modes of SrCnn models. + /// + public enum DetectMode + { + /// + /// In this mode, output (IsAnomaly, RawScore, Mag). + /// + AnomalyOnly = 0, + + /// + /// In this mode, output (IsAnomaly, AnomalyScore, Mag, ExpectedValue, BoundaryUnit, UpperBoundary, LowerBoundary). + /// + AnomalyAndMargin = 1, + + /// + /// In this mode, output (IsAnomaly, RawScore, Mag, ExpectedValue). + /// + AnomalyAndExpectedValue = 2 + } + public sealed class Batch { - private readonly List _batch; - private float _dotProductCur; + private List _previousBatch; + private List _batch; + private float _cursor; + private readonly int _batchSize; public Batch(int batchSize) { + _batchSize = batchSize; + _previousBatch = new List(batchSize); _batch = new List(batchSize); } @@ -271,13 +295,24 @@ public void AddValue(float value) _batch.Add(value); } + public int Count => _batch.Count; + public void Process() { - _dotProductCur = VectorUtils.NormSquared(new ReadOnlySpan(_batch.ToArray())); + // TODO: replace with SrCnn + _cursor = VectorUtils.NormSquared(new ReadOnlySpan(_batch.ToArray())); + if (_batch.Count < _batchSize) + { + _cursor += VectorUtils.NormSquared(new ReadOnlySpan( + _previousBatch.GetRange(_batchSize - _batch.Count - 1, _batchSize - _batch.Count).ToArray())); + } } public void Reset() { + var tempBatch = _previousBatch; + _previousBatch = _batch; + _batch = tempBatch; _batch.Clear(); } @@ -289,7 +324,7 @@ public ValueGetter CreateGetter(DataViewRowCursor input, string inputCol) { float src = default; srcGetter(ref src); - dst = src * _dotProductCur; + dst = src * _cursor; }; return getter; } diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 03a507195e..be4b0a6ca0 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.ML.Data; +using Microsoft.ML.Data.DataView; using Microsoft.ML.Transforms.TimeSeries; namespace Microsoft.ML @@ -146,6 +147,29 @@ public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog 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); + /// + /// 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 threshold to determine anomaly, score larger than the threshold is considered as anomaly. Should be in (0,1) + /// .Divide the input data into batches to fit SrCnn model. Must be -1 or a positive integer no less than 12. Default value is 1024. + /// The sensitivity of boundaries. Must be in the interval (0, 100). + /// The detect modes of SrCnn models. + /// + /// + /// + /// + /// + public static DetectAnomalyBySrCnnBatchTransform BatchDetectAnomalyBySrCnn(this TransformsCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, + double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, DetectAnomalyBySrCnnBatchTransform.DetectMode detectMode = DetectAnomalyBySrCnnBatchTransform.DetectMode.AnomalyAndMargin) + => new DetectAnomalyBySrCnnBatchTransform(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode); + /// /// Singular Spectrum Analysis (SSA) model for univariate time-series forecasting. /// For the details of the model, refer to http://arxiv.org/pdf/1206.6910.pdf. diff --git a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs index 435bdbaea4..efd4c1c0eb 100644 --- a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs +++ b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; @@ -1304,11 +1305,22 @@ public void ArrayDataViewBuilderNoCols() [Fact] public void TestBatchTransform() { + // TODO: delete var bldr = new ArrayDataViewBuilder(Env); bldr.AddColumn("Input", NumberDataViewType.Single, new[] { 1f, 2f, 3f, 2f, 3f, 4f, 3f, 4f, 5f, 4f }); var input = bldr.GetDataView(); - var output = new BatchTransform(Env, input, "Input", "Output", 3); + var output = new DetectAnomalyBySrCnnBatchTransform( + Env, + input, + "Input", + "Output", + 0.3, + 3, + 99, + DetectAnomalyBySrCnnBatchTransform.DetectMode.AnomalyAndExpectedValue); + // var batchTransformOutput = ML.Data.CreateEnumerable(output, reuseRowObject: false).ToList(); + using (var cursor = output.GetRowCursor(output.Schema)) { var inputGetter = cursor.GetGetter(cursor.Schema["Input"]); @@ -1325,6 +1337,14 @@ public void TestBatchTransform() } } + private class BatchTransformOutput + { + public float Input { get; set; } + + [VectorType] + public double[] Output { get; set; } + } + [Fact] public void SavePipeExpr() { From 0b42e7294da77a04ffe519b0510ebe77993c383d Mon Sep 17 00:00:00 2001 From: Klaus Marius Hansen Date: Tue, 12 May 2020 21:38:26 -0700 Subject: [PATCH 3/6] Removed comment --- .../DataView/DetectAnomalyBySrCnnBatchTransform.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs b/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs index a16610aa4e..c0753c16ae 100644 --- a/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs +++ b/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs @@ -213,8 +213,7 @@ protected override bool MoveNextCore() } } - // Sample class: look at the whole batch of float values , and return for each input x the - // product x*dotproduct(). + // TODO: SrCnn public sealed class DetectAnomalyBySrCnnBatchTransform : BatchTransformBase { private readonly int _batchSize; From 2811a98a426460f948280cd90770c7a162f7012d Mon Sep 17 00:00:00 2001 From: Klaus Marius Hansen Date: Wed, 13 May 2020 19:18:09 -0700 Subject: [PATCH 4/6] Handled some APIreview comments. --- ...ransform.cs => BatchDataViewMapperBase.cs} | 132 +----------------- .../ExtensionsCatalog.cs | 9 +- .../SrCnnBatchAnomalyDetection.cs | 132 ++++++++++++++++++ .../DataPipe/TestDataPipe.cs | 44 ------ .../TimeSeriesDirectApi.cs | 34 +++++ 5 files changed, 178 insertions(+), 173 deletions(-) rename src/Microsoft.ML.Data/DataView/{DetectAnomalyBySrCnnBatchTransform.cs => BatchDataViewMapperBase.cs} (64%) create mode 100644 src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs diff --git a/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs b/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs similarity index 64% rename from src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs rename to src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs index c0753c16ae..7b7887e2f8 100644 --- a/src/Microsoft.ML.Data/DataView/DetectAnomalyBySrCnnBatchTransform.cs +++ b/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs @@ -5,14 +5,13 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.ML.Numeric; using Microsoft.ML.Runtime; namespace Microsoft.ML.Data.DataView { - public abstract class BatchTransformBase : IDataView + internal abstract class BatchDataViewMapperBase : IDataView { - private protected sealed class Bindings : ColumnBindingsBase + protected sealed class Bindings : ColumnBindingsBase { private readonly DataViewType _outputColumnType; private readonly int _inputColumnIndex; @@ -59,12 +58,12 @@ public Func GetDependencies(Func predicate) private readonly IDataView _source; private readonly IHost _host; - private protected readonly Bindings SchemaBindings; + protected readonly Bindings SchemaBindings; protected readonly string InputCol; - protected BatchTransformBase(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) + protected BatchDataViewMapperBase(IHostEnvironment env, string registrationName, IDataView input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) { - _host = env.Register("Batch"); + _host = env.Register(registrationName); _source = input; SchemaBindings = new Bindings(input.Schema, inputColumnName, outputColumnName, outputColumnType); InputCol = inputColumnName; @@ -114,7 +113,7 @@ public DataViewRowCursor[] GetRowCursorSet(IEnumerable co private sealed class Cursor : RootCursorBase { - private readonly BatchTransformBase _parent; + private readonly BatchDataViewMapperBase _parent; private readonly DataViewRowCursor _lookAheadCursor; private readonly DataViewRowCursor _input; @@ -131,7 +130,7 @@ private sealed class Cursor : RootCursorBase public override DataViewSchema Schema => _parent.Schema; - public Cursor(BatchTransformBase parent, DataViewRowCursor input, DataViewRowCursor lookAheadCursor, bool[] active) + public Cursor(BatchDataViewMapperBase parent, DataViewRowCursor input, DataViewRowCursor lookAheadCursor, bool[] active) : base(parent._host) { _parent = parent; @@ -212,121 +211,4 @@ protected override bool MoveNextCore() } } } - - // TODO: SrCnn - public sealed class DetectAnomalyBySrCnnBatchTransform : BatchTransformBase - { - private readonly int _batchSize; - - public DetectAnomalyBySrCnnBatchTransform(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, DetectMode detectMode) - : base(env, input, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, batchSize)) - { - _batchSize = batchSize; - } - - protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch currentBatch, bool[] active) - { - if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) - return new Delegate[1]; - return new[] { currentBatch.CreateGetter(input, InputCol) }; - } - - protected override Batch InitializeBatch(DataViewRowCursor input) => new Batch(_batchSize); - - protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) - { - return () => input.Position % _batchSize == 0; - } - - protected override Func GetLastInBatchDelegate(DataViewRowCursor input) - { - return () => (input.Position + 1) % _batchSize == 0; - } - - protected override void ProcessExample(Batch currentBatch, float currentInput) - { - currentBatch.AddValue(currentInput); - } - - protected override void ProcessBatch(Batch currentBatch) - { - currentBatch.Process(); - currentBatch.Reset(); - } - - /// - /// The detect modes of SrCnn models. - /// - public enum DetectMode - { - /// - /// In this mode, output (IsAnomaly, RawScore, Mag). - /// - AnomalyOnly = 0, - - /// - /// In this mode, output (IsAnomaly, AnomalyScore, Mag, ExpectedValue, BoundaryUnit, UpperBoundary, LowerBoundary). - /// - AnomalyAndMargin = 1, - - /// - /// In this mode, output (IsAnomaly, RawScore, Mag, ExpectedValue). - /// - AnomalyAndExpectedValue = 2 - } - - public sealed class Batch - { - private List _previousBatch; - private List _batch; - private float _cursor; - private readonly int _batchSize; - - public Batch(int batchSize) - { - _batchSize = batchSize; - _previousBatch = new List(batchSize); - _batch = new List(batchSize); - } - - public void AddValue(float value) - { - _batch.Add(value); - } - - public int Count => _batch.Count; - - public void Process() - { - // TODO: replace with SrCnn - _cursor = VectorUtils.NormSquared(new ReadOnlySpan(_batch.ToArray())); - if (_batch.Count < _batchSize) - { - _cursor += VectorUtils.NormSquared(new ReadOnlySpan( - _previousBatch.GetRange(_batchSize - _batch.Count - 1, _batchSize - _batch.Count).ToArray())); - } - } - - public void Reset() - { - var tempBatch = _previousBatch; - _previousBatch = _batch; - _batch = tempBatch; - _batch.Clear(); - } - - public ValueGetter CreateGetter(DataViewRowCursor input, string inputCol) - { - ValueGetter srcGetter = input.GetGetter(input.Schema[inputCol]); - ValueGetter getter = - (ref float dst) => - { - float src = default; - srcGetter(ref src); - dst = src * _cursor; - }; - return getter; - } - } - } } diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index be4b0a6ca0..e2019d4892 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -4,6 +4,7 @@ using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; +using Microsoft.ML.TimeSeries; using Microsoft.ML.Transforms.TimeSeries; namespace Microsoft.ML @@ -158,7 +159,7 @@ public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog /// The threshold to determine anomaly, score larger than the threshold is considered as anomaly. Should be in (0,1) /// .Divide the input data into batches to fit SrCnn model. Must be -1 or a positive integer no less than 12. Default value is 1024. /// The sensitivity of boundaries. Must be in the interval (0, 100). - /// The detect modes of SrCnn models. + /// The detect mode of the SrCnn model. /// /// /// /// /// - public static DetectAnomalyBySrCnnBatchTransform BatchDetectAnomalyBySrCnn(this TransformsCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, - double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, DetectAnomalyBySrCnnBatchTransform.DetectMode detectMode = DetectAnomalyBySrCnnBatchTransform.DetectMode.AnomalyAndMargin) - => new DetectAnomalyBySrCnnBatchTransform(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode); + public static IDataView BatchDetectAnomalyBySrCnn(this TransformsCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, + double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyAndMargin) + => new SrCnnBatchAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode); /// /// Singular Spectrum Analysis (SSA) model for univariate time-series forecasting. diff --git a/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs b/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs new file mode 100644 index 0000000000..f10426e359 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs @@ -0,0 +1,132 @@ +// 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.Collections.Generic; +using Microsoft.ML.Data; +using Microsoft.ML.Data.DataView; +using Microsoft.ML.Numeric; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// The detect modes of SrCnn models. + /// + public enum SrCnnDetectMode + { + /// + /// In this mode, output (IsAnomaly, RawScore, Mag). + /// + AnomalyOnly = 0, + + /// + /// In this mode, output (IsAnomaly, AnomalyScore, Mag, ExpectedValue, BoundaryUnit, UpperBoundary, LowerBoundary). + /// + AnomalyAndMargin = 1, + + /// + /// In this mode, output (IsAnomaly, RawScore, Mag, ExpectedValue). + /// + AnomalyAndExpectedValue = 2 + } + + // TODO: SrCnn + internal sealed class SrCnnBatchAnomalyDetector : BatchDataViewMapperBase + { + private readonly int _batchSize; + private const int _minBatchSize = 12; + + public SrCnnBatchAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode) + : base(env, "SrCnnBatchAnomalyDetector", input, inputColumnName, outputColumnName, NumberDataViewType.Single) + { + Contracts.CheckParam(batchSize >= _minBatchSize, nameof(batchSize), "batch size is too small"); + _batchSize = batchSize; + } + + protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch currentBatch, bool[] active) + { + if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) + return new Delegate[1]; + return new[] { currentBatch.CreateGetter(input, InputCol) }; + } + + protected override Batch InitializeBatch(DataViewRowCursor input) => new Batch(_batchSize); + + protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) + { + return () => input.Position % _batchSize == 0; + } + + protected override Func GetLastInBatchDelegate(DataViewRowCursor input) + { + return () => (input.Position + 1) % _batchSize == 0; + } + + protected override void ProcessExample(Batch currentBatch, float currentInput) + { + currentBatch.AddValue(currentInput); + } + + protected override void ProcessBatch(Batch currentBatch) + { + currentBatch.Process(); + currentBatch.Reset(); + } + + public sealed class Batch + { + private List _previousBatch; + private List _batch; + private float _cursor; + private readonly int _batchSize; + + public Batch(int batchSize) + { + _batchSize = batchSize; + _previousBatch = new List(batchSize); + _batch = new List(batchSize); + } + + public void AddValue(float value) + { + _batch.Add(value); + } + + public int Count => _batch.Count; + + public void Process() + { + // TODO: replace with SrCnn + _cursor = VectorUtils.NormSquared(new ReadOnlySpan(_batch.ToArray())); + if (_batch.Count < _batchSize) + { + _cursor += VectorUtils.NormSquared(new ReadOnlySpan( + _previousBatch.GetRange(_batch.Count, _batchSize - _batch.Count).ToArray())); + } + } + + public void Reset() + { + var tempBatch = _previousBatch; + _previousBatch = _batch; + _batch = tempBatch; + _batch.Clear(); + } + + public ValueGetter CreateGetter(DataViewRowCursor input, string inputCol) + { + ValueGetter srcGetter = input.GetGetter(input.Schema[inputCol]); + ValueGetter getter = + (ref float dst) => + { + float src = default; + srcGetter(ref src); + dst = src * _cursor; + }; + return getter; + } + } + } +} diff --git a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs index efd4c1c0eb..6175deffb6 100644 --- a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs +++ b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs @@ -8,7 +8,6 @@ using System.Linq; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; -using Microsoft.ML.Data.DataView; using Microsoft.ML.Internal.Utilities; using Microsoft.ML.Runtime; using Microsoft.ML.TestFrameworkCommon; @@ -1302,49 +1301,6 @@ public void ArrayDataViewBuilderNoCols() Done(); } - [Fact] - public void TestBatchTransform() - { - // TODO: delete - var bldr = new ArrayDataViewBuilder(Env); - bldr.AddColumn("Input", NumberDataViewType.Single, new[] { 1f, 2f, 3f, 2f, 3f, 4f, 3f, 4f, 5f, 4f }); - var input = bldr.GetDataView(); - - var output = new DetectAnomalyBySrCnnBatchTransform( - Env, - input, - "Input", - "Output", - 0.3, - 3, - 99, - DetectAnomalyBySrCnnBatchTransform.DetectMode.AnomalyAndExpectedValue); - // var batchTransformOutput = ML.Data.CreateEnumerable(output, reuseRowObject: false).ToList(); - - using (var cursor = output.GetRowCursor(output.Schema)) - { - var inputGetter = cursor.GetGetter(cursor.Schema["Input"]); - var outputGetter = cursor.GetGetter(cursor.Schema["Output"]); - Log("Input\tOutput"); - while (cursor.MoveNext()) - { - var src = 0f; - var dst = 0f; - inputGetter(ref src); - outputGetter(ref dst); - Log($"{src}\t{dst}"); - } - } - } - - private class BatchTransformOutput - { - public float Input { get; set; } - - [VectorType] - public double[] Output { get; set; } - } - [Fact] public void SavePipeExpr() { diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index f734624f34..3287ffda3c 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.ML.Data; using Microsoft.ML.TestFramework; using Microsoft.ML.TestFramework.Attributes; +using Microsoft.ML.TimeSeries; using Microsoft.ML.Transforms.TimeSeries; using Xunit; using Xunit.Abstractions; @@ -564,5 +566,37 @@ public void AnomalyDetectionWithSrCnn(bool loadDataFromFile) k += 1; } } + + + [Fact] + public void TestSrCnnBatchAnomalyDetector() + { + // TODO: delete/replace with SrCnn tests + var ml = new MLContext(1); + var bldr = new ArrayDataViewBuilder(ml); + bldr.AddColumn("Input", NumberDataViewType.Single, new[] { 1f, 2f, 3f, 2f, 3f, 4f, 3f, 4f, 5f, 4f, 6f, 7f, 1f, }); + var input = bldr.GetDataView(); + + var output = new SrCnnBatchAnomalyDetector( + ml, + input, + "Input", + "Output", + 0.3, + 12, + 99, + SrCnnDetectMode.AnomalyAndExpectedValue); + var batchTransformOutput = ml.Data.CreateEnumerable(output, reuseRowObject: false).ToList(); + + var inputs = batchTransformOutput.Select(e => e.Input); + var outputs = batchTransformOutput.Select(e => e.Output); + } + + private class BatchTransformOutput + { + public float Input { get; set; } + + public float Output { get; set; } + } } } From e09f1535dfcbe886b36bfbf1328394acf0d421f1 Mon Sep 17 00:00:00 2001 From: Klaus Marius Hansen Date: Wed, 13 May 2020 19:44:37 -0700 Subject: [PATCH 5/6] Handled other review comments. --- .../DataView/BatchDataViewMapperBase.cs | 55 +++--------------- .../SrCnnBatchAnomalyDetection.cs | 58 ++++++++++++++++++- 2 files changed, 63 insertions(+), 50 deletions(-) diff --git a/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs b/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs index 7b7887e2f8..43aa4bb1c3 100644 --- a/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs +++ b/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs @@ -11,62 +11,19 @@ namespace Microsoft.ML.Data.DataView { internal abstract class BatchDataViewMapperBase : IDataView { - protected sealed class Bindings : ColumnBindingsBase - { - private readonly DataViewType _outputColumnType; - private readonly int _inputColumnIndex; - - public Bindings(DataViewSchema input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) - : base(input, true, outputColumnName) - { - _outputColumnType = outputColumnType; - _inputColumnIndex = Input[inputColumnName].Index; - } - - protected override DataViewType GetColumnTypeCore(int iinfo) - { - Contracts.Check(iinfo == 0); - return _outputColumnType; - } - - // Get a predicate for the input columns. - public Func GetDependencies(Func predicate) - { - Contracts.AssertValue(predicate); - - var active = new bool[Input.Count]; - for (int col = 0; col < ColumnCount; col++) - { - if (!predicate(col)) - continue; - - bool isSrc; - int index = MapColumnIndex(out isSrc, col); - if (isSrc) - active[index] = true; - else - active[_inputColumnIndex] = true; - } - - return col => 0 <= col && col < active.Length && active[col]; - } - } - public bool CanShuffle => false; public DataViewSchema Schema => SchemaBindings.AsSchema; private readonly IDataView _source; private readonly IHost _host; - protected readonly Bindings SchemaBindings; - protected readonly string InputCol; + protected readonly ColumnBindingsBase SchemaBindings; - protected BatchDataViewMapperBase(IHostEnvironment env, string registrationName, IDataView input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) + protected BatchDataViewMapperBase(IHostEnvironment env, string registrationName, IDataView input, ColumnBindingsBase schemaBindings) { _host = env.Register(registrationName); _source = input; - SchemaBindings = new Bindings(input.Schema, inputColumnName, outputColumnName, outputColumnType); - InputCol = inputColumnName; + SchemaBindings = schemaBindings; } public long? GetRowCount() => _source.GetRowCount(); @@ -94,7 +51,7 @@ public DataViewRowCursor GetRowCursor(IEnumerable columns // REVIEW: We can get a different input predicate for the input cursor and for the lookahead cursor. The lookahead // cursor is only used for getting the values from the input column, so it only needs that column activated. The // other cursor is used to get source columns, so it needs the rest of them activated. - var predInput = SchemaBindings.GetDependencies(predicate); + var predInput = GetSchemaBindingDependencies(predicate); var inputCols = _source.Schema.Where(c => predInput(c.Index)); return new Cursor(this, _source.GetRowCursor(inputCols), _source.GetRowCursor(inputCols), active); } @@ -109,7 +66,9 @@ public DataViewRowCursor[] GetRowCursorSet(IEnumerable co protected abstract void ProcessExample(TBatch currentBatch, TInput currentInput); protected abstract Func GetLastInBatchDelegate(DataViewRowCursor lookAheadCursor); protected abstract Func GetIsNewBatchDelegate(DataViewRowCursor lookAheadCursor); + protected abstract ValueGetter GetLookAheadGetter(DataViewRowCursor lookAheadCursor); protected abstract Delegate[] CreateGetters(DataViewRowCursor input, TBatch currentBatch, bool[] active); + protected abstract Func GetSchemaBindingDependencies(Func predicate); private sealed class Cursor : RootCursorBase { @@ -144,7 +103,7 @@ public Cursor(BatchDataViewMapperBase parent, DataViewRowCursor _lastInBatchInLookAheadCursorDel = _parent.GetLastInBatchDelegate(_lookAheadCursor); _firstInBatchInInputCursorDel = _parent.GetIsNewBatchDelegate(_input); - _inputGetterInLookAheadCursor = _lookAheadCursor.GetGetter(_lookAheadCursor.Schema[_parent.InputCol]); + _inputGetterInLookAheadCursor = _parent.GetLookAheadGetter(_lookAheadCursor); } public override ValueGetter GetGetter(DataViewSchema.Column column) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs b/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs index f10426e359..c1dc7f0e9c 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; +using Microsoft.ML.Internal.Utilities; using Microsoft.ML.Numeric; using Microsoft.ML.Runtime; @@ -37,19 +38,62 @@ internal sealed class SrCnnBatchAnomalyDetector : BatchDataViewMapperBase GetDependencies(Func predicate) + { + Contracts.AssertValue(predicate); + + var active = new bool[Input.Count]; + for (int col = 0; col < ColumnCount; col++) + { + if (!predicate(col)) + continue; + + bool isSrc; + int index = MapColumnIndex(out isSrc, col); + if (isSrc) + active[index] = true; + else + active[_inputColumnIndex] = true; + } + + return col => 0 <= col && col < active.Length && active[col]; + } + } public SrCnnBatchAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode) - : base(env, "SrCnnBatchAnomalyDetector", input, inputColumnName, outputColumnName, NumberDataViewType.Single) + : base(env, "SrCnnBatchAnomalyDetector", input, new Bindings(input.Schema, inputColumnName, outputColumnName, NumberDataViewType.Single)) { Contracts.CheckParam(batchSize >= _minBatchSize, nameof(batchSize), "batch size is too small"); _batchSize = batchSize; + _inputColumnName = inputColumnName; } protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch currentBatch, bool[] active) { if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) return new Delegate[1]; - return new[] { currentBatch.CreateGetter(input, InputCol) }; + return new[] { currentBatch.CreateGetter(input, _inputColumnName) }; } protected override Batch InitializeBatch(DataViewRowCursor input) => new Batch(_batchSize); @@ -64,6 +108,16 @@ protected override Func GetLastInBatchDelegate(DataViewRowCursor input) return () => (input.Position + 1) % _batchSize == 0; } + protected override ValueGetter GetLookAheadGetter(DataViewRowCursor input) + { + return input.GetGetter(input.Schema[_inputColumnName]); + } + + protected override Func GetSchemaBindingDependencies(Func predicate) + { + return (SchemaBindings as Bindings).GetDependencies(predicate); + } + protected override void ProcessExample(Batch currentBatch, float currentInput) { currentBatch.AddValue(currentInput); From 9c1b9ab98155228d9fcdab593e87e15b1e8a34f2 Mon Sep 17 00:00:00 2001 From: Klaus Marius Hansen Date: Thu, 14 May 2020 17:27:58 -0700 Subject: [PATCH 6/6] Resolved review comments. Added sample. --- .../DetectAnomalyBySrCnnBatchPrediction.cs | 56 ++++---------- .../DataView/BatchDataViewMapperBase.cs | 7 +- .../ExtensionsCatalog.cs | 5 +- .../SrCnnBatchAnomalyDetection.cs | 76 +++++++++++-------- .../DataPipe/TestDataPipe.cs | 1 - .../TimeSeriesDirectApi.cs | 10 +-- 6 files changed, 68 insertions(+), 87 deletions(-) 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 5ef334bc76..493673e2af 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/Transforms/TimeSeries/DetectAnomalyBySrCnnBatchPrediction.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Microsoft.ML; using Microsoft.ML.Data; +using Microsoft.ML.TimeSeries; namespace Samples.Dynamic { @@ -29,69 +30,44 @@ public static void Example() // Convert data to IDataView. var dataView = ml.Data.LoadFromEnumerable(data); - // Setup the estimator arguments + + // Setup the detection arguments string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); string inputColumnName = nameof(TimeSeriesData.Value); - // The transformed data. - var transformedData = ml.Transforms.DetectAnomalyBySrCnn( - outputColumnName, inputColumnName, 16, 5, 5, 3, 8, 0.35).Fit( - dataView).Transform(dataView); + // Do batch anomaly detection + var outputDataView = ml.Data.BatchDetectAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, batchSize: 512, sensitivity: 70, detectMode: SrCnnDetectMode.AnomalyAndMargin); // Getting the data of the newly created column as an IEnumerable of // SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable( - transformedData, reuseRowObject: false); + outputDataView, reuseRowObject: false); Console.WriteLine($"{outputColumnName} column obtained post-" + $"transformation."); - Console.WriteLine("Data\tAlert\tScore\tMag"); + Console.WriteLine("Data\tAlert\tScore\tMag\tExpectedValue\tBoundaryUnit\tUpperBoundary\tLowerBoundary"); 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 + //Data Alert Score Mag ExpectedValue BoundaryUnit UpperBoundary LowerBoundary + // TODO: update with actual output from SrCnn } - private static void PrintPrediction(float value, SrCnnAnomalyDetection + private static void PrintPrediction(double 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]); + Console.WriteLine("{0}\t{1}\t{2:0.00}\t{3:0.00}\t{4:0.00}\t{5:0.00}\t{6:0.00}", value, + prediction.Prediction[0], prediction.Prediction[1], prediction.Prediction[2], + prediction.Prediction[3], prediction.Prediction[4], prediction.Prediction[5]); private class TimeSeriesData { - public float Value; + public double Value; - public TimeSeriesData(float value) + public TimeSeriesData(double value) { Value = value; } @@ -99,7 +75,7 @@ public TimeSeriesData(float value) private class SrCnnAnomalyDetection { - [VectorType(3)] + [VectorType(6)] public double[] Prediction { get; set; } } } diff --git a/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs b/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs index 43aa4bb1c3..e2d49abcbb 100644 --- a/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs +++ b/src/Microsoft.ML.Data/DataView/BatchDataViewMapperBase.cs @@ -17,13 +17,11 @@ internal abstract class BatchDataViewMapperBase : IDataView private readonly IDataView _source; private readonly IHost _host; - protected readonly ColumnBindingsBase SchemaBindings; - protected BatchDataViewMapperBase(IHostEnvironment env, string registrationName, IDataView input, ColumnBindingsBase schemaBindings) + protected BatchDataViewMapperBase(IHostEnvironment env, string registrationName, IDataView input) { _host = env.Register(registrationName); _source = input; - SchemaBindings = schemaBindings; } public long? GetRowCount() => _source.GetRowCount(); @@ -61,6 +59,7 @@ public DataViewRowCursor[] GetRowCursorSet(IEnumerable co return new[] { GetRowCursor(columnsNeeded, rand) }; } + protected abstract ColumnBindingsBase SchemaBindings { get; } protected abstract TBatch InitializeBatch(DataViewRowCursor input); protected abstract void ProcessBatch(TBatch currentBatch); protected abstract void ProcessExample(TBatch currentBatch, TInput currentInput); @@ -79,7 +78,7 @@ private sealed class Cursor : RootCursorBase private readonly bool[] _active; private readonly Delegate[] _getters; - private TBatch _currentBatch; + private readonly TBatch _currentBatch; private readonly Func _lastInBatchInLookAheadCursorDel; private readonly Func _firstInBatchInInputCursorDel; private readonly ValueGetter _inputGetterInLookAheadCursor; diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index e2019d4892..853c8bcc64 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Microsoft.ML.Data; -using Microsoft.ML.Data.DataView; using Microsoft.ML.TimeSeries; using Microsoft.ML.Transforms.TimeSeries; @@ -163,11 +162,11 @@ public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog /// /// /// /// /// - public static IDataView BatchDetectAnomalyBySrCnn(this TransformsCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, + public static IDataView BatchDetectAnomalyBySrCnn(this DataOperationsCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyAndMargin) => new SrCnnBatchAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode); diff --git a/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs b/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs index c1dc7f0e9c..e724a9990e 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnBatchAnomalyDetection.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; -using Microsoft.ML.Internal.Utilities; using Microsoft.ML.Numeric; using Microsoft.ML.Runtime; @@ -34,18 +34,19 @@ public enum SrCnnDetectMode } // TODO: SrCnn - internal sealed class SrCnnBatchAnomalyDetector : BatchDataViewMapperBase + internal sealed class SrCnnBatchAnomalyDetector : BatchDataViewMapperBase { + private const int MinBatchSize = 12; private readonly int _batchSize; - private const int _minBatchSize = 12; private readonly string _inputColumnName; + private readonly SrCnnDetectMode _detectMode; private class Bindings : ColumnBindingsBase { - private readonly DataViewType _outputColumnType; + private readonly VectorDataViewType _outputColumnType; private readonly int _inputColumnIndex; - public Bindings(DataViewSchema input, string inputColumnName, string outputColumnName, DataViewType outputColumnType) + public Bindings(DataViewSchema input, string inputColumnName, string outputColumnName, VectorDataViewType outputColumnType) : base(input, true, outputColumnName) { _outputColumnType = outputColumnType; @@ -82,13 +83,19 @@ public Func GetDependencies(Func predicate) } public SrCnnBatchAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode) - : base(env, "SrCnnBatchAnomalyDetector", input, new Bindings(input.Schema, inputColumnName, outputColumnName, NumberDataViewType.Single)) + : base(env, "SrCnnBatchAnomalyDetector", input) { - Contracts.CheckParam(batchSize >= _minBatchSize, nameof(batchSize), "batch size is too small"); + + Contracts.CheckParam(batchSize >= MinBatchSize, nameof(batchSize), "batch size is too small"); + _detectMode = detectMode; + int outputSize = 6; // TODO: determine based on detectMode + SchemaBindings = new Bindings(input.Schema, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, outputSize)); _batchSize = batchSize; _inputColumnName = inputColumnName; } + protected override ColumnBindingsBase SchemaBindings { get; } + protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch currentBatch, bool[] active) { if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) @@ -96,7 +103,7 @@ protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch curre return new[] { currentBatch.CreateGetter(input, _inputColumnName) }; } - protected override Batch InitializeBatch(DataViewRowCursor input) => new Batch(_batchSize); + protected override Batch InitializeBatch(DataViewRowCursor input) => new Batch(_batchSize, _detectMode); protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) { @@ -108,9 +115,9 @@ protected override Func GetLastInBatchDelegate(DataViewRowCursor input) return () => (input.Position + 1) % _batchSize == 0; } - protected override ValueGetter GetLookAheadGetter(DataViewRowCursor input) + protected override ValueGetter GetLookAheadGetter(DataViewRowCursor input) { - return input.GetGetter(input.Schema[_inputColumnName]); + return input.GetGetter(input.Schema[_inputColumnName]); } protected override Func GetSchemaBindingDependencies(Func predicate) @@ -118,7 +125,7 @@ protected override Func GetSchemaBindingDependencies(Func return (SchemaBindings as Bindings).GetDependencies(predicate); } - protected override void ProcessExample(Batch currentBatch, float currentInput) + protected override void ProcessExample(Batch currentBatch, double currentInput) { currentBatch.AddValue(currentInput); } @@ -131,19 +138,19 @@ protected override void ProcessBatch(Batch currentBatch) public sealed class Batch { - private List _previousBatch; - private List _batch; - private float _cursor; - private readonly int _batchSize; + private List _previousBatch; + private List _batch; + private readonly SrCnnDetectMode _detectMode; + private double _cursor; - public Batch(int batchSize) + public Batch(int batchSize, SrCnnDetectMode detectMode) { - _batchSize = batchSize; - _previousBatch = new List(batchSize); - _batch = new List(batchSize); + _detectMode = detectMode; + _previousBatch = new List(batchSize); + _batch = new List(batchSize); } - public void AddValue(float value) + public void AddValue(double value) { _batch.Add(value); } @@ -152,13 +159,8 @@ public void AddValue(float value) public void Process() { - // TODO: replace with SrCnn - _cursor = VectorUtils.NormSquared(new ReadOnlySpan(_batch.ToArray())); - if (_batch.Count < _batchSize) - { - _cursor += VectorUtils.NormSquared(new ReadOnlySpan( - _previousBatch.GetRange(_batch.Count, _batchSize - _batch.Count).ToArray())); - } + // TODO: replace with run of SrCnn + _cursor = _batch.Sum(); } public void Reset() @@ -169,15 +171,23 @@ public void Reset() _batch.Clear(); } - public ValueGetter CreateGetter(DataViewRowCursor input, string inputCol) + public ValueGetter> CreateGetter(DataViewRowCursor input, string inputCol) { - ValueGetter srcGetter = input.GetGetter(input.Schema[inputCol]); - ValueGetter getter = - (ref float dst) => + ValueGetter srcGetter = input.GetGetter(input.Schema[inputCol]); + ValueGetter> getter = + (ref VBuffer dst) => { - float src = default; + double src = default; srcGetter(ref src); - dst = src * _cursor; + // TODO: replace with SrCnn result + dst = new VBuffer(6, new[] { + src * _cursor, + (src + 1) * _cursor, + (src + 2) * _cursor, + (src + 3) * _cursor, + (src + 4) * _cursor, + (src + 5) * _cursor, + }); }; return getter; } diff --git a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs index 6175deffb6..7cae9e81a5 100644 --- a/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs +++ b/test/Microsoft.ML.TestFramework/DataPipe/TestDataPipe.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; using Microsoft.ML.Internal.Utilities; diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 3287ffda3c..709d7bbc3b 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -7,7 +7,6 @@ using System.Linq; using Microsoft.ML.Data; using Microsoft.ML.TestFramework; -using Microsoft.ML.TestFramework.Attributes; using Microsoft.ML.TimeSeries; using Microsoft.ML.Transforms.TimeSeries; using Xunit; @@ -567,16 +566,14 @@ public void AnomalyDetectionWithSrCnn(bool loadDataFromFile) } } - [Fact] public void TestSrCnnBatchAnomalyDetector() { // TODO: delete/replace with SrCnn tests var ml = new MLContext(1); var bldr = new ArrayDataViewBuilder(ml); - bldr.AddColumn("Input", NumberDataViewType.Single, new[] { 1f, 2f, 3f, 2f, 3f, 4f, 3f, 4f, 5f, 4f, 6f, 7f, 1f, }); + bldr.AddColumn("Input", NumberDataViewType.Double, new[] { 1.0, 2.0, 3.0, 2.0, 3.0, 4.0, 3.0, 4.0, 5.0, 4.0, 6.0, 7.0, 1.0, }); var input = bldr.GetDataView(); - var output = new SrCnnBatchAnomalyDetector( ml, input, @@ -594,9 +591,10 @@ public void TestSrCnnBatchAnomalyDetector() private class BatchTransformOutput { - public float Input { get; set; } + public double Input { get; set; } - public float Output { get; set; } + [VectorType] + public double[] Output { get; set; } } } }