From 78961c708c6eb12d3340b9e46cf07598ad8ce2dd Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Mon, 1 Jun 2020 16:51:11 +0800 Subject: [PATCH 01/49] add seasonality detect and stl decompose in srcnn --- .../BackendStructureBase.cs | 111 ++ .../BasicParameters.cs | 30 + src/Microsoft.ML.TimeSeries/Cyclic.cs | 30 + src/Microsoft.ML.TimeSeries/FastLoess.cs | 98 ++ src/Microsoft.ML.TimeSeries/FftTransform.cs | 148 ++ src/Microsoft.ML.TimeSeries/InnerStl.cs | 298 ++++ src/Microsoft.ML.TimeSeries/LeastSquares.cs | 556 ++++++ .../LocalRegression.cs | 261 +++ src/Microsoft.ML.TimeSeries/Loess.cs | 173 ++ .../LoessConfiguration.cs | 53 + src/Microsoft.ML.TimeSeries/MathsTools.cs | 1556 +++++++++++++++++ src/Microsoft.ML.TimeSeries/MatrixEx.cs | 172 ++ .../Microsoft.ML.TimeSeries.csproj | 10 + src/Microsoft.ML.TimeSeries/MovingAverage.cs | 75 + src/Microsoft.ML.TimeSeries/SeasonalInfo.cs | 83 + .../SerialCorrelation.cs | 400 +++++ .../SrCnnAnomalyDetectionBase.cs | 4 +- .../SrCnnEntireAnomalyDetector.cs | 147 +- .../StlConfiguration.cs | 72 + .../TemporalOutlierInfo.cs | 88 + .../TrajectoryMatrix.cs | 18 +- src/Microsoft.ML.TimeSeries/TrendInfo.cs | 228 +++ src/Microsoft.ML.TimeSeries/TuningParams.cs | 106 ++ 23 files changed, 4702 insertions(+), 15 deletions(-) create mode 100644 src/Microsoft.ML.TimeSeries/BackendStructureBase.cs create mode 100644 src/Microsoft.ML.TimeSeries/BasicParameters.cs create mode 100644 src/Microsoft.ML.TimeSeries/Cyclic.cs create mode 100644 src/Microsoft.ML.TimeSeries/FastLoess.cs create mode 100644 src/Microsoft.ML.TimeSeries/FftTransform.cs create mode 100644 src/Microsoft.ML.TimeSeries/InnerStl.cs create mode 100644 src/Microsoft.ML.TimeSeries/LeastSquares.cs create mode 100644 src/Microsoft.ML.TimeSeries/LocalRegression.cs create mode 100644 src/Microsoft.ML.TimeSeries/Loess.cs create mode 100644 src/Microsoft.ML.TimeSeries/LoessConfiguration.cs create mode 100644 src/Microsoft.ML.TimeSeries/MathsTools.cs create mode 100644 src/Microsoft.ML.TimeSeries/MatrixEx.cs create mode 100644 src/Microsoft.ML.TimeSeries/MovingAverage.cs create mode 100644 src/Microsoft.ML.TimeSeries/SeasonalInfo.cs create mode 100644 src/Microsoft.ML.TimeSeries/SerialCorrelation.cs create mode 100644 src/Microsoft.ML.TimeSeries/StlConfiguration.cs create mode 100644 src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs create mode 100644 src/Microsoft.ML.TimeSeries/TrendInfo.cs create mode 100644 src/Microsoft.ML.TimeSeries/TuningParams.cs diff --git a/src/Microsoft.ML.TimeSeries/BackendStructureBase.cs b/src/Microsoft.ML.TimeSeries/BackendStructureBase.cs new file mode 100644 index 0000000000..773ff9e601 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/BackendStructureBase.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public enum TimeSeriesInfoKind + { + /// + /// (single-time series) the outliers of a single time series + /// + Outlier, + + /// + /// (single-time series) the seasonal signal of a single time series + /// + Seasonal, + + /// + /// (single-time series) the trend curve of a single time series + /// + Trend, + + /// + /// (two-time series) the lead/lag correlation between two time series + /// + CrossCorrelation, + + /// + /// (two-time series) the correlation of outliers from two time series + /// + OutlierCorrelation, + + /// + /// (two-time series) the correlation of trends from two time series + /// + TrendCorrelation, + } + + public abstract class TimeSeriesInfoBase + { + /// + /// each insight should be ranked, so that it can be compared with other insights. + /// + public abstract double Rank { get; set; } + + /// + /// the description of this particular insight + /// + public abstract string Description { get; protected set; } + + /// + /// indicate the kind of insight. + /// + public abstract TimeSeriesInfoKind Kind { get; protected set; } + + /// + /// basic comparison function, used for quick sort. + /// + /// the left element + /// the right element + public static int Compare(TimeSeriesInfoBase left, TimeSeriesInfoBase right) + { + if (object.ReferenceEquals(left, right)) + return 0; + if (left == null) + return -1; + if (right == null) + return 1; + return left.Rank.CompareTo(right.Rank); + } + } + + /// + /// the characteristic of single time series. + /// + public abstract class SingleSeriesInfo : TimeSeriesInfoBase + { + /// + /// x-axis values of original curve + /// + public IReadOnlyList X { get; protected set; } + + /// + /// y-axis values of original curve + /// + public IReadOnlyList Y { get; protected set; } + } + + /// + /// the characteristic between two time series, such as the lead/lag correlation, + /// and the correlation of the outliers. + /// + public abstract class TwoSeriesInfo : TimeSeriesInfoBase + { + /// + /// x-axis values of two time series. they should be aligned. + /// + public IReadOnlyList X { get; protected set; } + + /// + /// y-axis values of first series + /// + public IReadOnlyList Y1 { get; protected set; } + + /// + /// y-axis values of second series + /// + public IReadOnlyList Y2 { get; protected set; } + } +} diff --git a/src/Microsoft.ML.TimeSeries/BasicParameters.cs b/src/Microsoft.ML.TimeSeries/BasicParameters.cs new file mode 100644 index 0000000000..bffde50693 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/BasicParameters.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class BasicParameters + { + /// + /// the minimum length of a valid time series. a time series with length equals 2 is so trivial. when less than 2, meaningless. + /// + public const int MinTimeSeriesLength = 3; + + /// + /// the maximum length of a valid time series. when there are too many data points, the chart will look so dense that details are lost. + /// this number is tuned so that the bird strike data can still preserve results. + /// + public const int MaxTimeSeriesLength = 4000; + + /// + /// the minimum count of repeated periods. this is used for determining a noticeable seasonal signal. + /// + public const int MinPeriodRepeatCount = 3; + + /// + /// the minimum count of regular gaps. when there are too few gaps, the time series will look odd, which will impact the seasonality analysis + /// + public const int MinRegularGap = 5; + } +} diff --git a/src/Microsoft.ML.TimeSeries/Cyclic.cs b/src/Microsoft.ML.TimeSeries/Cyclic.cs new file mode 100644 index 0000000000..a8cf4c123e --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/Cyclic.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class Cyclic + { + private readonly IReadOnlyList _y; + + public Cyclic(IReadOnlyList yValues) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + + if (yValues.Count < BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: cyclic"); + + _y = yValues; + } + + /// + /// detect the cyclic length by given the input time series. + /// if not exist (the cyclic pattern is not significant), then return -1 + /// + public int DetectCyclic(out double confidence) + { + return SerialCorrelation.Period(_y, out confidence); + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/FastLoess.cs b/src/Microsoft.ML.TimeSeries/FastLoess.cs new file mode 100644 index 0000000000..cc03d463f1 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/FastLoess.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// this is the fast version of Loess. there are several alternatives to improve the performance. this one is an approximation approach. + /// the smoothing is conducted on a sample set, and then the values on the left points are assigned directly. + /// + public class FastLoess + { + /// + /// this class is a sampling based method, so here specifies the sample size. + /// + private static int _sampleSize = 100; + + private readonly IReadOnlyList _x; + private readonly IReadOnlyList _y; + private readonly int _length; + + private readonly Loess _smoother; + + /// + /// Initializes a new instance of the class. + /// the fast version of the Loess method. when the time series is too long, the sampling will be conducted first + /// to improve the performance. + /// + /// the input x-axis values + /// the input y-axis values + /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + /// this method will provide default smoothing ratio if user did not specify + public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal = true, int r = -1) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(xValues, nameof(xValues)); + //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + Y = new List(); + + if (yValues.Count < BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: lowess"); + + _x = xValues; + _y = yValues; + _length = _y.Count; + + // the sampling is not neccessary + if (_length <= FastLoess._sampleSize) + { + if (r == -1) + _smoother = new Loess(_x, _y, isTemporal); + else + _smoother = new Loess(_x, _y, r, isTemporal); + } + else + { + // conduct sampling based strategy, to boost the performance. + double step = _length * 1.0 / FastLoess._sampleSize; + var sampleX = new double[FastLoess._sampleSize]; + var sampleY = new double[FastLoess._sampleSize]; + for (int i = 0; i < FastLoess._sampleSize; i++) + { + int index = (int)(i * step); + sampleX[i] = _x[index]; + sampleY[i] = _y[index]; + } + if (r == -1) + _smoother = new Loess(sampleX, sampleY, isTemporal); + else + _smoother = new Loess(sampleX, sampleY, r, isTemporal); + } + } + + /// + /// the estimated y values. this is the very cool smoothing method. + /// + public List Y { get; } + + /// + /// assign the smoothing values to all the data points, not only on the sample size. + /// + public void Estimate() + { + for (int i = 0; i < _length; i++) + { + double yValue = _smoother.EstimateY(_x[i]); + Y.Add(yValue); + } + } + + /// + /// estimate any y value by given any x value, event the x value is not one of the input points. + /// + public double EstimateY(double xValue) + { + return _smoother.EstimateY(xValue); + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/FftTransform.cs b/src/Microsoft.ML.TimeSeries/FftTransform.cs new file mode 100644 index 0000000000..187e059581 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/FftTransform.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class FftTransform + { + /// + /// calculate the fast fourier transform. + /// + public static Complex[] Fft(Complex[] array) + { + int n = array.Length; + int newN = Get2Power(n); + if (newN > n) + { + Complex[] newArray = new Complex[newN]; + for (int i = 0; i < newN; i++) + { + if (i < n) + newArray[i] = array[i]; + else + newArray[i] = Complex.Zero; + } + return RecursiveFft(newArray); + } + else + { + return RecursiveFft(array); + } + } + + /// + /// calculate the fast fourier transform. + /// + public static Complex[] RevertFft(Complex[] array) + { + int n = array.Length; + int newN = Get2Power(n); + if (newN > n) + { + Complex[] newArray = new Complex[newN]; + for (int i = 0; i < newN; i++) + { + if (i < n) + newArray[i] = array[i]; + else + newArray[i] = Complex.Zero; + } + Complex[] result = RevertRecursiveFft(newArray); + for (int i = 0; i < result.Length; i++) + result[i] /= newN; + return result; + } + else + { + Complex[] result = RevertRecursiveFft(array); + for (int i = 0; i < result.Length; i++) + result[i] /= n; + return result; + } + } + + /// + /// this method is the recursive FftTransform, which use the divide and conquer to achieve nlogn complexity. + /// the key trick is the selection of n unit roots in the complex space. the length of the input array MUST be 2^k + /// + /// the input polynomial coefficients (or the dual if reverse FftTransform is called) + /// return the dual coefficients + private static Complex[] RecursiveFft(Complex[] array) + { + int n = array.Length; + if (n == 1) + return array; + Complex wn = new Complex(Math.Cos(2 * Math.PI / n), Math.Sin(2 * Math.PI / n)); + Complex w = Complex.One; + Complex[] array0 = new Complex[n / 2]; + Complex[] array1 = new Complex[n / 2]; + for (int i = 0; i < n / 2; i++) + { + array0[i] = array[i * 2]; + array1[i] = array[i * 2 + 1]; + } + Complex[] y0 = RecursiveFft(array0); + Complex[] y1 = RecursiveFft(array1); + Complex[] y = new Complex[n]; + for (int i = 0; i < n / 2; i++) + { + y[i] = y0[i] + w * y1[i]; + y[i + n / 2] = y0[i] - w * y1[i]; + w *= wn; + } + return y; + } + + /// + /// this method is the revert recursive FftTransform, + /// + /// the input polynomial coefficients (or the dual if reverse FftTransform is called) + /// return the dual coefficients + private static Complex[] RevertRecursiveFft(Complex[] array) + { + int n = array.Length; + if (n == 1) + return array; + Complex wn = new Complex(Math.Cos(2 * Math.PI / n), -Math.Sin(2 * Math.PI / n)); + Complex w = Complex.One; + Complex[] array0 = new Complex[n / 2]; + Complex[] array1 = new Complex[n / 2]; + for (int i = 0; i < n / 2; i++) + { + array0[i] = array[i * 2]; + array1[i] = array[i * 2 + 1]; + } + Complex[] y0 = RevertRecursiveFft(array0); + Complex[] y1 = RevertRecursiveFft(array1); + Complex[] y = new Complex[n]; + for (int i = 0; i < n / 2; i++) + { + y[i] = y0[i] + w * y1[i]; + y[i + n / 2] = y0[i] - w * y1[i]; + w *= wn; + } + return y; + } + + /// + /// get the smallest 2^k which is equal or greater than n + /// + private static int Get2Power(int n) + { + int result = 1; + bool meet1 = false; // check is n is just equals to 2^k for some k + while (n > 1) + { + if ((n & 1) != 0) + meet1 = true; + result = result << 1; + n = n >> 1; + } + if (meet1) + result = result << 1; + return result; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/InnerStl.cs b/src/Microsoft.ML.TimeSeries/InnerStl.cs new file mode 100644 index 0000000000..344ef3b528 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/InnerStl.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + internal class InnerStl + { + private readonly IReadOnlyList _x; + private readonly IReadOnlyList _y; + private readonly int _length; + private readonly bool _isTemporal; + private readonly StlConfiguration _config; + + private readonly double[] _seasonalComponent; + private readonly double[] _trendComponent; + private readonly double[] _residual; + private readonly int[] _outlierIndexes; + private readonly double[] _outlierSeverity; + + /// + /// Initializes a new instance of the class. + /// for a time series, only with y values. assume the x-values are 0, 1, 2, ... + /// since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. + /// otherwise, the smoothing on seasonal component will be very complicated. + /// + /// the y-axis values + /// the configuration for applying regression + /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + public InnerStl(IReadOnlyList yValues, StlConfiguration config, bool isTemporal) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + //ExtendedDiagnostics.EnsureArgumentNotNull(config, nameof(config)); + + if (yValues.Count == 0) + throw new Exception("input data structure cannot be 0-length: innerSTL"); + + _y = yValues; + _length = _y.Count; + _isTemporal = isTemporal; + _x = VirtualXValuesProvider.GetXValues(_length); + _config = config; + + _seasonalComponent = new double[_length]; + _trendComponent = new double[_length]; + _residual = new double[_length]; + _outlierIndexes = new int[_length]; + _outlierSeverity = new double[_length]; + } + + /// + /// the seasonal component + /// + public IReadOnlyList SeasonalComponent + { + get { return _seasonalComponent; } + } + + /// + /// the trend component + /// + public IReadOnlyList TrendComponent + { + get { return _trendComponent; } + } + + /// + /// the left component after seasonal and trend are eliminated. + /// + public IReadOnlyList Residual + { + get { return _residual; } + } + + /// + /// this field is used to indicate which data point is an outlier. + /// the definition is outlier is, not contribute for the local regression. + /// + public IReadOnlyList OutlierIndexeses + { + get { return _outlierIndexes; } + } + + /// + /// [0, infinite] indicate the severity of each outlier + /// + public IReadOnlyList OutlierSeverity + { + get { return _outlierSeverity; } + } + + /// + /// calculate the slope of the trend component + /// + public double Slope + { + get; + private set; + } + + /// + /// the mean residual squares. here the outliers are not taken into account. + /// + public double Mrs + { + get; + private set; + } + + /// + /// the core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, + /// see section 2 and 3. especially section 2. + /// + /// return true if the process goes successfully. otherwise, return false. + public bool Decomposition() + { + double[] s = new double[_length]; + double[] t = new double[_length]; + for (int iter = 0; iter < StlConfiguration.Ni; iter++) + { + // step1: detrending + double[] detrendedY = new double[_length]; + for (int i = 0; i < _length; i++) + detrendedY[i] = _y[i] - t[i]; + + // step2: cycle-subseries smoothing + List[] cycleSubSeries = new List[_config.Np]; + List[] smoothedSubseries = new List[_config.Np]; + for (int i = 0; i < _config.Np; i++) + { + cycleSubSeries[i] = new List(); + smoothedSubseries[i] = new List(); + } + + // obtain all the subseries + for (int i = 0; i < _length; i++) + { + int cycleIndex = i % _config.Np; + cycleSubSeries[cycleIndex].Add(detrendedY[i]); + } + + // smoothing on each subseries + for (int i = 0; i < cycleSubSeries.Length; i++) + { + List virtualXValues = VirtualXValuesProvider.GetXValues(cycleSubSeries[i].Count); + + FastLoess model = new FastLoess(virtualXValues, cycleSubSeries[i], _isTemporal, StlConfiguration.Ns); + model.Estimate(); + + // add a prior point + smoothedSubseries[i].Add(model.EstimateY(-1.0)); + smoothedSubseries[i].AddRange(model.Y); + + // add a after point + smoothedSubseries[i].Add(model.EstimateY(cycleSubSeries[i].Count * 1.0)); + } + + // c is the smoothed series, with _length+2Np points. + List c = new List(); + for (int i = 0; i < smoothedSubseries[0].Count; i++) + { + for (int j = 0; j < smoothedSubseries.Length; j++) + { + if (smoothedSubseries[j].Count <= i) + break; + if (smoothedSubseries[j][i].Equals(double.NaN)) + { + return false; + } + c.Add(smoothedSubseries[j][i]); + } + } + + // step3: low-pass filtering of smoothed cycle-subseries + List c1 = MovingAverage.MA(c, _config.Np); + List c2 = MovingAverage.MA(c1, _config.Np); + List c3 = MovingAverage.MA(c2, 3); + List virtualC3XValues = VirtualXValuesProvider.GetXValues(c3.Count); + FastLoess lowPass = new FastLoess(virtualC3XValues, c3, _isTemporal, _config.Nl); + lowPass.Estimate(); + + // step4: detrending of smoothed cycle-subseries + for (int i = 0; i < _length; i++) + { + s[i] = c[i] - lowPass.Y[i]; + } + + // step5: deseasonalizing + List deseasonSeries = new List(); + for (int i = 0; i < _length; i++) + { + deseasonSeries.Add(_y[i] - s[i]); + } + + // step6: trend smoothing + List virtualDeseasonSeries = VirtualXValuesProvider.GetXValues(deseasonSeries.Count); + FastLoess trender = new FastLoess(virtualDeseasonSeries, deseasonSeries, _isTemporal, _config.Nt); + trender.Estimate(); + for (int i = 0; i < _length; i++) + { + t[i] = trender.Y[i]; + } + } + + for (int i = 0; i < s.Length; i++) + { + _seasonalComponent[i] = s[i]; + _trendComponent[i] = t[i]; + } + + // the slope is still based on the regression models. + Slope = (_trendComponent[_length - 1] - _seasonalComponent[0]) / (_length - 1); + + var absResiduals = new List(_residual); + for (int i = 0; i < _y.Count; i++) + { + _residual[i] = _y[i] - _seasonalComponent[i] - _trendComponent[i]; + absResiduals.Add(Math.Abs(_y[i] - _seasonalComponent[i] - _trendComponent[i])); + } + + // identify the outliers and corresponding mean residual squares (Mrs) + //double median = MathUtility.QuickSelect(absResiduals, absResiduals.Count / 2); + double median = 0; + + // when median is very close to 0, which means the regularity of the serial is strong, so that no data points is outlier. + Mrs = 0; + int nonOutlierCount = 0; + if (median < 0.0001) + { + // the curve fitting is perfect, so Mrs remains 0. no update. + for (int i = 0; i < _length; i++) + { + _outlierIndexes[i] = 0; + _outlierSeverity[i] = 0; + } + } + else + { + for (int i = 0; i < _length; i++) + { + double severity = Math.Abs(_residual[i]) / median; + + // this is the key criteria + if (severity > 6) + { + _outlierIndexes[i] = 1; + _outlierSeverity[i] = severity; + } + else + { + nonOutlierCount++; + Mrs += _residual[i] * _residual[i]; + _outlierIndexes[i] = 0; + _outlierSeverity[i] = 0; + } + } + Mrs /= nonOutlierCount; + } + return true; + } + + /// + /// this class provides the virtual x values for multi object usage. + /// the cache mechanism is used for performance consideration. + /// + internal class VirtualXValuesProvider + { + private static Dictionary> _xValuesPool; + + static VirtualXValuesProvider() + { + _xValuesPool = new Dictionary>(); + } + + /// + /// get a list of virtual x-axis values. the values are from 0 to length - 1. + /// + /// specify the length you want to create the x values. + /// if this is cached, return directly. otherwise, create a new list and return + internal static List GetXValues(int length) + { + lock (_xValuesPool) + { + List xValues; + if (_xValuesPool.TryGetValue(length, out xValues)) + return xValues; + + var newXValues = new List(length); + for (int i = 0; i < length; i++) + newXValues.Add(i); + + _xValuesPool.Add(length, newXValues); + return newXValues; + } + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/LeastSquares.cs new file mode 100644 index 0000000000..beca9b33db --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/LeastSquares.cs @@ -0,0 +1,556 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// this class is used to calculate the least squares of the scatterplots. + /// please check http://en.wikipedia.org/wiki/Least_squares for more details. + /// + public class LeastSquares + { + private readonly List _x; + private readonly List _y; + private readonly int _length; + + /// + /// Initializes a new instance of the class. + /// constructing the least square algorithm. the input will be consumed directly without any copy, due to memory usage concern. + /// + /// the corresponding x-axis value + /// the corresponding y-axis value + public LeastSquares(List x, List y) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(x, nameof(x)); + //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + + if (x.Count == 0 || y.Count == 0) + throw new Exception("input data structure cannot be 0-length"); + if (x.Count != y.Count) + throw new Exception("the x-axis length should be equal to y-axis length!"); + _x = x; + _y = y; + _length = _x.Count; + } + + /// + /// y=b0+b1x, but the penalty is weighted + /// + /// the weighted least squares. note that the weight should be non-negative, and equal length to data + public PolynomialModel RegressionDegreeOneWeighted(List weights) + { + return new PolynomialModel(RegressionDegreeOneWeightedFast(weights)); + //return RegressionDegreeOneWeightedOld(weights); + } + + public PolynomialModel RegressionDegreeOneWeightedOld(List weights) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); + + if (weights.Count != _length) + throw new Exception("the weight vector is not equal length to the data points"); + + foreach (double value in weights) + { + if (value < 0) + throw new Exception("the value in weights should be non-negative!"); + } + + double[] w = new double[_length]; + for (int i = 0; i < _length; i++) + { + w[i] = Math.Sqrt(weights[i]); + } + + double[,] kernelMatrix = new double[_length, 2]; + for (int i = 0; i < _length; i++) + { + kernelMatrix[i, 0] = 1; + kernelMatrix[i, 1] = _x[i]; + } + double[,] kernelMatrix1 = new double[_length, 2]; + for (int i = 0; i < _length; i++) + { + for (int j = 0; j < 2; j++) + { + kernelMatrix1[i, j] = w[i] * kernelMatrix[i, j]; + } + } + double[] y1 = new double[_length]; + for (int i = 0; i < _length; i++) + y1[i] = w[i] * _y[i]; + + double[,] s = new double[2, 2]; + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 2; j++) + { + double sum = 0; + for (int k = 0; k < _length; k++) + { + sum += kernelMatrix1[k, i] * kernelMatrix1[k, j]; + } + s[i, j] = sum; + } + } + + /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is + * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. + */ + double a = s[0, 0]; + double b = s[0, 1]; + double c = s[1, 0]; + double d = s[1, 1]; + double divider = a * d - b * c; + double[,] reverseS = new double[2, 2]; + reverseS[0, 0] = d / divider; + reverseS[0, 1] = -c / divider; + reverseS[1, 0] = -b / divider; + reverseS[1, 1] = a / divider; + + // double[,] reverseS = MatrixEx.ReverseMatrix(S); + + double fy0 = 0; + double fy1 = 0; + for (int i = 0; i < _length; i++) + { + fy0 += kernelMatrix1[i, 0] * y1[i]; + fy1 += kernelMatrix1[i, 1] * y1[i]; + } + + double b0 = reverseS[0, 0] * fy0 + reverseS[0, 1] * fy1; + double b1 = reverseS[1, 0] * fy0 + reverseS[1, 1] * fy1; + + List results = new List(); + results.Add(b0); + results.Add(b1); + + //List results2 = RegressionDegreeOneWeightedFast(weights); + //Trace.Assert(results[0] == results2[0]); + //Trace.Assert(results[1] == results2[1]); + + return new PolynomialModel(results); + } + + public List RegressionDegreeOneWeightedFast(List weights) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); + + if (weights.Count != _length) + throw new Exception("the weight vector is not equal length to the data points"); + + foreach (double value in weights) + { + if (value < 0) + throw new Exception("the value in weights should be non-negative!"); + } + + double[] buffer1 = new double[_length]; + double[] buffer2 = new double[_length]; + double[] buffer3 = new double[_length]; + + double[] w = buffer1; + for (int i = 0; i < _length; i++) + { + w[i] = Math.Sqrt(weights[i]); + } + + double[] kernelMatrixR0 = buffer2; + double[] kernelMatrixR1 = buffer3; + for (int i = 0; i < _length; i++) + { + kernelMatrixR0[i] = 1; + kernelMatrixR1[i] = _x[i]; + } + double[] kernelMatrix1R0 = buffer2; + double[] kernelMatrix1R1 = buffer3; + for (int i = 0; i < _length; i++) + { + kernelMatrix1R0[i] = w[i] * kernelMatrixR0[i]; + kernelMatrix1R1[i] = w[i] * kernelMatrixR1[i]; + } + double[] y1 = buffer1; + for (int i = 0; i < _length; i++) + y1[i] = w[i] * _y[i]; + + double sum00 = 0; + double sum01 = 0; + double sum10 = 0; + double sum11 = 0; + for (int k = 0; k < _length; k++) + { + sum00 += kernelMatrix1R0[k] * kernelMatrix1R0[k]; + sum01 += kernelMatrix1R0[k] * kernelMatrix1R1[k]; + sum10 += kernelMatrix1R1[k] * kernelMatrix1R0[k]; + sum11 += kernelMatrix1R1[k] * kernelMatrix1R1[k]; + } + + /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is + * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. + */ + double a = sum00; + double b = sum01; + double c = sum10; + double d = sum11; + double divider = a * d - b * c; + double[,] reverseS = new double[2, 2]; + reverseS[0, 0] = d / divider; + reverseS[0, 1] = -c / divider; + reverseS[1, 0] = -b / divider; + reverseS[1, 1] = a / divider; + + // double[,] reverseS = MatrixEx.ReverseMatrix(S); + + double fy0 = 0; + double fy1 = 0; + for (int i = 0; i < _length; i++) + { + fy0 += kernelMatrix1R0[i] * y1[i]; + fy1 += kernelMatrix1R1[i] * y1[i]; + } + + double b0 = reverseS[0, 0] * fy0 + reverseS[0, 1] * fy1; + double b1 = reverseS[1, 0] * fy0 + reverseS[1, 1] * fy1; + + List results = new List(); + results.Add(b0); + results.Add(b1); + + return results; + } + + // The result is incorrect, as the data can only be partially loaded into the vector + public List RegressionDegreeOneWeightedSimd(List weights) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); + + if (weights.Count != _length) + throw new Exception("the weight vector is not equal length to the data points"); + + foreach (double value in weights) + { + if (value < 0) + throw new Exception("the value in weights should be non-negative!"); + } + + int vectorSize = Vector.Count; + int bufferLength = ((weights.Count / vectorSize) + 1) * vectorSize; + double[] buffer1 = new double[bufferLength]; + double[] buffer2 = new double[bufferLength]; + double[] buffer3 = new double[bufferLength]; + double[] buffer4 = new double[vectorSize]; + double[] w = buffer1; + double[] kernelMatrixR0 = buffer2; + double[] kernelMatrixR1 = buffer3; + double[] kernelMatrix1R0 = buffer2; + double[] kernelMatrix1R1 = buffer3; + double[] y1; + double[,] s = new double[2, 2]; + double[,] reverseS = new double[2, 2]; + + for (int i = 0; i < _length; ++i) + { + w[i] = Math.Sqrt(weights[i]); + } + + for (int i = 0; i < _length; ++i) + { + kernelMatrixR0[i] = 1; + kernelMatrixR1[i] = _x[i]; + } + + for (int i = 0; i <= bufferLength - bufferLength / 2; i += bufferLength/2) + { + var vw = new Vector(w, i); + var vkm0 = new Vector(kernelMatrixR0, i); + var vkm1 = new Vector(kernelMatrixR1, i); + (vw * vkm0).CopyTo(buffer4); + for (int j = 0; j < vectorSize / 2; ++j) + { + kernelMatrix1R0[i + j] = buffer4[j]; + } + (vw * vkm1).CopyTo(buffer4); + for (int j = 0; j < vectorSize / 2; ++j) + { + kernelMatrix1R1[i + j] = buffer4[j]; + } + } + + y1 = buffer1; + for (int i = 0; i < _length; ++i) + y1[i] = w[i] * _y[i]; + + double sum00 = 0; + double sum01 = 0; + double sum10 = 0; + double sum11 = 0; + + for (int k = 0; k < _length; ++k) + { + sum00 += kernelMatrix1R0[k] * kernelMatrix1R0[k]; + sum01 += kernelMatrix1R0[k] * kernelMatrix1R1[k]; + sum10 += kernelMatrix1R1[k] * kernelMatrix1R0[k]; + sum11 += kernelMatrix1R1[k] * kernelMatrix1R1[k]; + } + + /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is + * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. + */ + double a = s[0, 0]; + double b = s[0, 1]; + double c = s[1, 0]; + double d = s[1, 1]; + double divider = a * d - b * c; + reverseS[0, 0] = d / divider; + reverseS[0, 1] = -c / divider; + reverseS[1, 0] = -b / divider; + reverseS[1, 1] = a / divider; + + // double[,] reverseS = MatrixEx.ReverseMatrix(S); + + double fy0 = 0; + double fy1 = 0; + for (int i = 0; i < _length; ++i) + { + fy0 += kernelMatrix1R0[i] * y1[i]; + fy1 += kernelMatrix1R1[i] * y1[i]; + } + + double b0 = reverseS[0, 0] * fy0 + reverseS[0, 1] * fy1; + double b1 = reverseS[1, 0] * fy0 + reverseS[1, 1] * fy1; + + List results = new List(); + results.Add(b0); + results.Add(b1); + + return results; + } + + /// + /// y=b0+b1x, with equal weights for each data points. + /// caution: this method should not be removed since it has common usage for other scenarios. + /// + public PolynomialModel RegressionDegreeOne() + { + double[,] kernelMatrix = new double[_length, 2]; + for (int i = 0; i < _length; i++) + { + kernelMatrix[i, 0] = 1; + kernelMatrix[i, 1] = _x[i]; + } + double[,] s = new double[2, 2]; + for (int i = 0; i < 2; i++) + { + for (int j = 0; j < 2; j++) + { + double sum = 0; + for (int k = 0; k < _length; k++) + { + sum += kernelMatrix[k, i] * kernelMatrix[k, j]; + } + s[i, j] = sum; + } + } + + double[,] reverseS = MatrixEx.ReverseMatrix(s); + + double y0 = 0; + double y1 = 0; + for (int i = 0; i < _length; i++) + { + y0 += kernelMatrix[i, 0] * _y[i]; + y1 += kernelMatrix[i, 1] * _y[i]; + } + + double b0 = reverseS[0, 0] * y0 + reverseS[0, 1] * y1; + double b1 = reverseS[1, 0] * y0 + reverseS[1, 1] * y1; + + List results = new List(); + results.Add(b0); + results.Add(b1); + return new PolynomialModel(results); + } + + /// + /// y=b0+b1x+b2x^2, but the penalty is weighted + /// + public PolynomialModel RegressionDegreeTwoWeighted(List weights) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); + + if (weights.Count != _length) + throw new Exception("the weight vector is not equal length to the data points"); + + foreach (double value in weights) + { + if (value < 0) + throw new Exception("the value in weights should be non-negative!"); + } + + double[,] w = new double[_length, _length]; + for (int i = 0; i < _length; i++) + { + for (int j = 0; j < _length; j++) + { + if (i == j) + w[i, j] = Math.Sqrt(weights[i]); + } + } + + double[,] kernelMatrix = new double[_length, 3]; + for (int i = 0; i < _length; i++) + { + kernelMatrix[i, 0] = 1; + kernelMatrix[i, 1] = _x[i]; + kernelMatrix[i, 2] = _x[i] * _x[i]; + } + double[,] kernelMatrix1 = new double[_length, 3]; + for (int i = 0; i < _length; i++) + { + for (int j = 0; j < 3; j++) + { + kernelMatrix1[i, j] = w[i, i] * kernelMatrix[i, j]; + } + } + double[] ty1 = new double[_length]; + for (int i = 0; i < _length; i++) + ty1[i] = w[i, i] * _y[i]; + + double[,] s = new double[3, 3]; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + double sum = 0; + for (int k = 0; k < _length; k++) + { + sum += kernelMatrix1[k, i] * kernelMatrix1[k, j]; + } + s[i, j] = sum; + } + } + + double[,] reverseS = MatrixEx.ReverseMatrix(s); + + double y0 = 0; + double y1 = 0; + double y2 = 0; + for (int i = 0; i < _length; i++) + { + y0 += kernelMatrix1[i, 0] * ty1[i]; + y1 += kernelMatrix1[i, 1] * ty1[i]; + y2 += kernelMatrix1[i, 2] * ty1[i]; + } + + double b0 = reverseS[0, 0] * y0 + reverseS[0, 1] * y1 + reverseS[0, 2] * y2; + double b1 = reverseS[1, 0] * y0 + reverseS[1, 1] * y1 + reverseS[1, 2] * y2; + double b2 = reverseS[2, 0] * y0 + reverseS[2, 1] * y1 + reverseS[2, 2] * y2; + + List results = new List(); + results.Add(b0); + results.Add(b1); + results.Add(b2); + return new PolynomialModel(results); + } + + /// + /// y=b0+b1x+b2x^2, with equal weights for each data points + /// caution: this method should not be removed since it has common usage for other scenarios. + /// + public PolynomialModel RegressionDegreeTwo() + { + double[,] kernelMatrix = new double[_length, 3]; + for (int i = 0; i < _length; i++) + { + kernelMatrix[i, 0] = 1; + kernelMatrix[i, 1] = _x[i]; + kernelMatrix[i, 2] = _x[i] * _x[i]; + } + double[,] s = new double[3, 3]; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + double sum = 0; + for (int k = 0; k < _length; k++) + { + sum += kernelMatrix[k, i] * kernelMatrix[k, j]; + } + s[i, j] = sum; + } + } + + double[,] reverseS = MatrixEx.ReverseMatrix(s); + + double y0 = 0; + double y1 = 0; + double y2 = 0; + for (int i = 0; i < _length; i++) + { + y0 += kernelMatrix[i, 0] * _y[i]; + y1 += kernelMatrix[i, 1] * _y[i]; + y2 += kernelMatrix[i, 2] * _y[i]; + } + + double b0 = reverseS[0, 0] * y0 + reverseS[0, 1] * y1 + reverseS[0, 2] * y2; + double b1 = reverseS[1, 0] * y0 + reverseS[1, 1] * y1 + reverseS[1, 2] * y2; + double b2 = reverseS[2, 0] * y0 + reverseS[2, 1] * y1 + reverseS[2, 2] * y2; + + List results = new List(); + results.Add(b0); + results.Add(b1); + results.Add(b2); + return new PolynomialModel(results); + } + } + + /// + /// indicate a specific polynomial model + /// + public class PolynomialModel + { + private readonly List _coeffs; + + public PolynomialModel(ICollection coeffs) + { + //ExtendedDiagnostics.EnsureCollectionNotNullOrEmpty(coeffs, nameof(coeffs)); + + _coeffs = new List(coeffs); + } + + /// + /// calculate the y value by given the x value, under this model + /// + /// the specific x value + public double Y(double x) + { + return YNew(x); + } + + public double YOld(double x) + { + double result = _coeffs[0]; + for (int i = 1; i < _coeffs.Count; i++) + { + result += _coeffs[i] * Math.Pow(x, i); + } + + //Trace.Assert(YNew(x) == result); + return result; + } + + public double YNew(double x) + { + double result = _coeffs[0]; + double p = 1.0; + for (int i = 1; i < _coeffs.Count; i++) + { + p = p * x; + result += _coeffs[i] * p; + } + return result; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/LocalRegression.cs new file mode 100644 index 0000000000..183d93acfd --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/LocalRegression.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// this class is used to maintain the neighbors of a given particular point. + /// + internal class LocalRegression + { + private const double NumericalThreshold = 1.0e-10; + private readonly IReadOnlyList _x; + private readonly IReadOnlyList _y; + private int _length; + + /// + /// the model is learned by several iterations of local weighted regression. + /// + private PolynomialModel _model = null; + + /// + /// Initializes a new instance of the class. + /// construct the neighborhood information of a given point. note that the input series will not be copies again, due to + /// memory usage concern. + /// + /// the complete values of x-axis + /// the complete values of y-axis + /// the index of the current point + /// number of neighbors, usually should be less then n. if it is equal/larger than n, the weight has slight change. + /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + internal LocalRegression(IReadOnlyList x, IReadOnlyList y, int selfIndex, int r, bool isTemporal = true) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(x, nameof(x)); + //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + + if (x.Count <= 1 || x.Count != y.Count) + throw new Exception("cannot accomplish neighbors obtaining"); + + _x = x; + _y = y; + _length = _x.Count; + SelfIndex = selfIndex; + + NeighborsX = new List(); + NeighborsY = new List(); + Weights = new List(); + + int startIndex = selfIndex; + int endIndex = selfIndex; + double selfValue = _x[SelfIndex]; + + // the normal case, the farthest neighbor is contained in the list. + if (r < _length) + { + int left = r; + while (left > 0) + { + if (startIndex == 0) + { + endIndex += left; + break; + } + if (endIndex == _length - 1) + { + startIndex -= left; + break; + } + double startV = _x[startIndex]; + double endV = _x[endIndex]; + + // the left point is closer to the current + // bug fix: avoid potential inconsistent index assignment due to numerical precision. + double distanceDiff = (selfValue - startV) - (endV - selfValue); + + if (distanceDiff < NumericalThreshold) + { + startIndex--; + } + else + { + endIndex++; + } + left--; + } + StartIndex = startIndex; + EndIndex = endIndex; + + double leftRange = selfValue - _x[startIndex]; + double rightRange = _x[endIndex] - selfValue; + double range = Math.Max(leftRange, rightRange); + + if (isTemporal) + { + for (int i = StartIndex; i <= EndIndex; i++) + { + NeighborsX.Add(_x[i]); + NeighborsY.Add(_y[i]); + Weights.Add(WeightMethod.Tricube((_x[i] - selfValue) / range)); + } + } + else + { + for (int i = StartIndex; i <= EndIndex; i++) + { + NeighborsX.Add(_x[i]); + NeighborsY.Add(_y[i]); + + // since we do not consider the local/temporal information, all the neighbors share same weight for further weighted regression + Weights.Add(1.0); + } + } + } + else + { + // when the r is equal/larger than n + StartIndex = 0; + EndIndex = _length - 1; + + double leftRange = selfValue - _x[StartIndex]; + double rightRange = _x[EndIndex] - selfValue; + double range = Math.Max(leftRange, rightRange); + + // this is the slight modification of the weighting calculation + range = range * r / (_length - 1); + + if (isTemporal) + { + for (int i = StartIndex; i <= EndIndex; i++) + { + NeighborsX.Add(_x[i]); + NeighborsY.Add(_y[i]); + Weights.Add(WeightMethod.Tricube((_x[i] - selfValue) / range)); + } + } + else + { + for (int i = StartIndex; i <= EndIndex; i++) + { + NeighborsX.Add(_x[i]); + NeighborsY.Add(_y[i]); + + // since we do not consider the local/temporal information, all the neighbors share same weight for further weighted regression + Weights.Add(1.0); + } + } + } + } + + /// + /// the values of the y-axis of the neighbors (include the self point) + /// + public List NeighborsY { get; private set; } + + /// + /// the values of the x-axis of the neighbors (include the self point) + /// + public List NeighborsX { get; private set; } + + /// + /// the weights for each neighbor. this is used for weighted least squares. + /// + public List Weights { get; private set; } + + /// + /// the start index of the neighbors (inclusive) + /// + public int StartIndex { get; private set; } + + /// + /// the end index of the neighbors (inclusive) + /// + public int EndIndex { get; private set; } + + /// + /// the index of the self point. the index is on the complete series, not only on the neighbor series. + /// + public int SelfIndex { get; private set; } + + private void Estimate() + { + for (int iter = 0; iter < LoessConfiguration.T; iter++) + { + _model = Regression(); + + // calculate the errors + var errors = new double[NeighborsX.Count]; + var absErrors = new double[NeighborsX.Count]; + for (int i = 0; i < NeighborsX.Count; i++) + { + double error = NeighborsY[i] - _model.Y(NeighborsX[i]); + errors[i] = error; + absErrors[i] = Math.Abs(error); + } + + Array.Sort(absErrors); + + double median = absErrors[absErrors.Length / 2]; + if (median == 0) // a very subtle bug! sometimes, when the input data is very clean, so that the median could be 0! + median = double.Epsilon; + + // calculate the gain for new weights. the outliers will get much less weight + var deltas = new double[errors.Length]; + for (int i = 0; i < errors.Length; i++) + { + deltas[i] = WeightMethod.BisquareWeight(errors[i] / 6.0 / median); + } + + // update new weights. + for (int i = 0; i < Weights.Count; i++) + { + Weights[i] *= deltas[i]; + } + } + } + + /// + /// get the best estimated y for the current value. + /// + public double Y() + { + if (_model == null) + { + Estimate(); + } + return _model.Y(_x[SelfIndex]); + } + + /// + /// get the best estimated y for any given x-value, event not one of the observed point + /// + /// any given x value + public double Y(double xValue) + { + if (_model == null) + { + Estimate(); + } + return _model.Y(xValue); + } + + private PolynomialModel Regression() + { + PolynomialModel result = null; + LeastSquares ls = new LeastSquares(NeighborsX, NeighborsY); + switch (LoessConfiguration.ModelType) + { + case RegressionModelType.One: + result = ls.RegressionDegreeOneWeighted(Weights); + break; + case RegressionModelType.Two: + result = ls.RegressionDegreeTwoWeighted(Weights); + break; + default: + result = ls.RegressionDegreeOneWeighted(Weights); + break; + } + return result; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/Loess.cs b/src/Microsoft.ML.TimeSeries/Loess.cs new file mode 100644 index 0000000000..28f6bb70aa --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/Loess.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// Loess is short for Robust Locally Weighted Regression and Smoothing Scatterplots. + /// + public class Loess + { + + private const double NumericalThreshold = 1.0e-10; + + /// + /// the ratio to determine the local region + /// + private readonly int _r; + + private readonly bool _isTemporal; + + /// + /// key is the index of the given point, value is the corresponding neighbors of the given point. + /// + private readonly Dictionary _neighbors = new Dictionary(); + + private IReadOnlyList _x; + private IReadOnlyList _y; + private int _length; + + /// + /// Initializes a new instance of the class. + /// constructing the least square algorithm + /// + /// the corresponding x-axis value + /// the corresponding y-axis value + /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(xValues, nameof(xValues)); + //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + + if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: lowess"); + + if (xValues.Count != yValues.Count) + throw new Exception("the x-axis length should be equal to y-axis length!: lowess"); + + _length = xValues.Count; + _isTemporal = isTemporal; + + _r = (int)(_length * LoessConfiguration.F); + + // r cannot be equal to length. + if (_r >= _length) + _r = _length - 1; + else if (_r < LoessConfiguration.MinimumNeighborCount) // the neighbors should be at least 2, or the matrix operations would encounter issues. + _r = LoessConfiguration.MinimumNeighborCount; + + // DEBUG + // control the performance + if (_r >= LoessConfiguration.MaximumNeighborCount) + { + _r = LoessConfiguration.MaximumNeighborCount; + } + Init(xValues, yValues); + } + + /// + /// Initializes a new instance of the class. + /// constructing the least square algorithm. specified with the # of neighbors + /// + /// the corresponding x-axis value + /// the corresponding y-axis value + /// the smoothing range is not determined by the ratio, but be specified externally. (which can exceed the length of the list) + /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + public Loess(IReadOnlyList xValues, IReadOnlyList yValues, int r, bool isTemporal) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(xValues, nameof(xValues)); + //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + + if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: lowess"); + + if (xValues.Count != yValues.Count) + throw new Exception("the x-axis length should be equal to y-axis length!: lowess"); + + _length = xValues.Count; + _isTemporal = isTemporal; + + _r = r; + if (_r < LoessConfiguration.MinimumNeighborCount) // the neighbors should be at least 2, or the matrix operations would encounter issues. + _r = LoessConfiguration.MinimumNeighborCount; + Init(xValues, yValues); + } + + /// + /// initialize the signal with basic checking + /// + /// the input x-axis values + /// the input y-axis values + private void Init(IReadOnlyList xValues, IReadOnlyList yValues) + { + _x = xValues; + _y = yValues; + for (int i = 0; i < _length; i++) + { + LocalRegression neighbor = new LocalRegression(_x, _y, i, _r, _isTemporal); + _neighbors.Add(i, neighbor); + } + } + + /// + /// estimate any y value by given any x value, even the x value is not one of the input points. + /// when the x value is not one of the input points, find the closed one from input points, and use its model. + /// + /// find the index with value closest to the input x value. + public double EstimateY(double xValue) + { + // find the closest point in x to the xValue + int start = 0; + int end = _length - 1; + while (end - start > 1) + { + int mid = (start + end) / 2; + if (_x[mid] > xValue) + { + end = mid; + } + else + { + start = mid; + } + } + double distanceDiff = (_x[end] - xValue) - (xValue - _x[start]); + + int index = distanceDiff > -NumericalThreshold ? start : end; + return _neighbors[index].Y(xValue); + } + } + + /// + /// this class is used to define a set of weight functions. these functions are useful for various purposes for smoothing. + /// i.e., the weighted least squares. + /// + public class WeightMethod + { + /// + /// this is used for robust weight, it is one iteration step of loess. + /// + public static double BisquareWeight(double value) + { + double abs = Math.Abs(value); + if (abs >= 1) + return 0; + double temp = 1 - abs * abs; + return temp * temp; + } + + /// + /// a famous weight function, since it enhances a chi-squared distributional approximation of an estimated of the error variance. + /// tricube should provide an adequate smooth in almost all situations. /ref + /// + public static double Tricube(double value) + { + double abs = Math.Abs(value); + if (abs >= 1) + return 0; + double temp = 1 - abs * abs * abs; + return temp * temp * temp; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/LoessConfiguration.cs b/src/Microsoft.ML.TimeSeries/LoessConfiguration.cs new file mode 100644 index 0000000000..0b4585ee08 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/LoessConfiguration.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public enum RegressionModelType + { + /// + /// the 1-order model, i.e., linear model + /// + One, + + /// + /// the 2-order model, i.e., square polynomial model. + /// + Two, + } + + /// + /// this class is used to store the parameters which are needed for lowess algorithm. + /// the name of these constansts are compliant with the original terms in paper. + /// + public class LoessConfiguration + { + /// + /// this value is used for performance concern. when the length of the series goes large, a ratio of neighbors will be significant, + /// which leads to unsatisfied slow. so this value is used to bound the maximum # of neighbors one epoch can have. + /// + public const int MaximumNeighborCount = 100; + + /// + /// minumum number of neighbor counts, to apply underlying regression analysis. + /// this number should be even, so that neighbors on left/right side of a given data point is balanced. unbalanced neighbors would make the local-weighted regression biased noticeably at corner cases. + /// + public const int MinimumNeighborCount = 4; + + /// + /// (0, 1], a smooth range ratio. let fn be the number of neighbors of a specific point. + /// + public static readonly double F = 0.3; + + /// + /// this is used to indicate which regression model is used. + /// + public static readonly RegressionModelType ModelType = RegressionModelType.One; + + /// + /// the number of iterations for robust regression. + /// + public static readonly int T = 2; + } +} diff --git a/src/Microsoft.ML.TimeSeries/MathsTools.cs b/src/Microsoft.ML.TimeSeries/MathsTools.cs new file mode 100644 index 0000000000..862f51f60d --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/MathsTools.cs @@ -0,0 +1,1556 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class MathUtility + { + private const int PearsonCoeffMinLength = 5; + + private const double SquareRootOfTwo = 1.414213562373095; + + /// + /// Returns the reciprocal of , guarding against division by zero. + /// If the input value is less than the given , this method does not perform + /// the division and simply returns zero. + /// + public static double ReciprocalOrZero(double value, double precision) + { + if (Math.Abs(value) < precision) + return 0.0; + + return 1.0 / value; + } + + /// + /// efficient method (O(m) complexity) for randomly sample m data points from total n data points. with complexity O(m) + /// these n data points are indexed from 0 to n-1. + /// + /// total number of data points + /// number of points be sampled + /// return the indexes of the m randomly sampled points + public static int[] RandomSampling(int n, int m) + { + if (n <= 0 || m > n || m <= 0) + return null; + int[] result = new int[m]; + for (int i = 0; i < m; i++) + result[i] = i; + if (m == n) + { + return result; + } + + // we use fixed seed, to make the results stable. + Random rd = new Random(0); + for (int i = m + 1; i < n; i++) + { + var value = rd.Next(i); + + // probability m/i is hit, will use i to replace + if (value < m) + { + var chosenIndex = rd.Next(m); + result[chosenIndex] = i; + } + } + return result; + } + + /// + /// calculate the cosine of two input 2-dimensional vectors. return false if either vector is a zero vector, where + /// cosine is undefined there. + /// + public static bool Cosine(double vector1X, double vector1Y, double vector2X, double vector2Y, out double cosine) + { + cosine = double.NaN; + double norm = Math.Sqrt((vector1X * vector1X + vector1Y * vector1Y) * (vector2X * vector2X + vector2Y * vector2Y)); + if (norm == 0.0) + return false; + cosine = (vector1X * vector2X + vector1Y * vector2Y) / norm; + return true; + } + + /// + /// calculate the cosine of two input vectors. return false if either vector is a zero vector, where + /// cosine is undefined there. + /// + public static bool Cosine(double[] vector1, double[] vector2, out double cosine) + { + cosine = double.NaN; + if (vector1 == null || vector2 == null || vector1.Length == vector2.Length || vector1.Length == 0) + return false; + double norm1 = 0.0; + double norm2 = 0.0; + double innerProduct = 0.0; + for (int i = 0; i < vector1.Length; i++) + { + norm1 += vector1[i] * vector1[i]; + norm2 += vector2[i] * vector2[i]; + innerProduct += vector1[i] * vector2[i]; + } + if (norm1 == 0.0 || norm2 == 0.0) + return false; + cosine = innerProduct / Math.Sqrt(norm1 * norm2); + return true; + } + + /// + /// error function. + /// + /// given the input x + public static double Erf(double x) + { + // handle either positive or negative x. because error function is negatively symmetric of x + double a = 0.140012; + double b = x * x; + double item = -b * (4 / Math.PI + a * b) / (1 + a * b); + double result = Math.Sqrt(1 - Math.Exp(item)); + if (x >= 0) + return result; + return -result; + } + + /// + /// calculate the standard cumulative distribution function F(x) = P(Z less or equal than x), where Z follows + /// a standard normal distribution. + /// + public static double StandardCdf(double x) + { + return (1.0 + Erf(x / SquareRootOfTwo)) / 2; + } + + /// + /// given a confidence level as input, we calculate the Z such that P(Z greater than alpha) = alpha + /// + public static double ReverseAlpha(double alpha) + { + var p = 1.0 - alpha; + + // for a standard normal distribution, the probability that x is smaller than lower or x is larger than upper is almost zero. + // we can set a larger value but already has no gain. + double lower = -5.0; + double upper = 5.0; + double middle; + while (true) + { + middle = (lower + upper) / 2; + var estimate = StandardCdf(middle); + if (Math.Abs(estimate - p) < 0.00000001) + break; + + // because standard CDF is monotonic, thus we can use binary search + if (estimate > p) + { + upper = middle; + } + else + { + lower = middle; + } + } + return middle; + } + + /// + /// calculate the statistical significance for a gaussian distribution. + /// + /// the observed x value + /// mean value + /// the standard deviation + public static double GaussianSignificance(double x, double u, double sigma) + { + double x1 = Math.Abs(x - u); + + // 1.414213562373095 is sqrt(2) + double cdf = 0.5 + 0.5 * Erf(x1 / sigma / 1.414213562373095); + return 2 * cdf - 1; + } + + /// + /// calculate the standard sigmoid function + /// + /// the input value + public static double Sigmoid(double x) + { + return 1.0 / (1.0 + Math.Exp(-x)); + } + + /// + /// use quick-sort like method to obtain the median value. + /// the complexity in expectation is O(n), which is faster than using quickSort. + /// + /// the input list of values. note that this list will be modified after calling this method + /// returns the median value + public static double QuickMedian(List values) + { + if (values == null || values.Count == 0) + return double.NaN; + + // here the third parameter is start from 1. so we need to plus 1 to compliant. + return QuickSelect(values, 0, values.Count - 1, values.Count / 2 + 1); + } + + private static double QuickSelect(List values, int start, int end, int k) + { + if (start == end) + return values[start]; + int q = Partition(values, start, end); + int index = q - start + 1; + if (index == k) + return values[q]; + else if (k < index) + return QuickSelect(values, start, q - 1, k); + return QuickSelect(values, q + 1, end, k - index); + } + + /// + /// This version of QuickSelect follows a similar idea as the one above, except that this method + /// does not use the Partition() method, and, therefore, does not modify the original values. + /// On average, this version is faster (~40% faster) and has better memory allocation (~60% less). + /// + /// The list of values + /// The k smallest value in the list + public static double QuickSelect(IReadOnlyList values, int k) + { + var nums = values; + double[] left = new double[values.Count]; + double[] right = new double[values.Count]; + int numsCount = nums.Count; + + while (true) + { + if (numsCount == 1) + return nums[0]; + + int idx = FindMedianIndex(nums, 0, numsCount - 1); + double key = nums[idx]; + + int leftIdx = 0; + int rightIdx = 0; + for (int i = 0; i < numsCount; i++) + { + if (i == idx) + continue; + + if (nums[i] < key) + left[leftIdx++] = nums[i]; + else + right[rightIdx++] = nums[i]; + } + + if (leftIdx == k - 1) + return key; + + if (leftIdx >= k) + { + nums = left; + numsCount = leftIdx; + } + else + { + nums = right; + k = k - leftIdx - 1; + numsCount = rightIdx; + } + } + } + + private static int Partition(List values, int start, int end) + { + int medianIndex = FindMedianIndex(values, start, end); + if (medianIndex != end) + { + double temp = values[end]; + values[end] = values[medianIndex]; + values[medianIndex] = temp; + } + double pivot = values[end]; + int startIndex = start; + int endIndex = end - 1; + while (startIndex < endIndex) + { + while (values[startIndex] < pivot && startIndex < endIndex) + { + startIndex++; + } + while (values[endIndex] > pivot && endIndex > startIndex) + { + endIndex--; + } + if (startIndex == endIndex) + break; + double temp = values[startIndex]; + values[startIndex] = values[endIndex]; + values[endIndex] = temp; + startIndex++; + endIndex--; + } + if (values[startIndex] > pivot) + { + double temp = values[startIndex]; + values[startIndex] = pivot; + values[end] = temp; + } + else + { + startIndex++; + double temp = values[startIndex]; + values[startIndex] = pivot; + values[end] = temp; + } + return startIndex; + } + + public static int FindMedianIndex(IReadOnlyList values, int start, int end) + { + // use the middle value among first/middle/end as the guard value, to make sure the average performance good. + // according to unit test, this fix will improve the average performance 10%. and works normally when input list is ordered. + double first = values[start]; + double last = values[end]; + int midIndex = (start + end) / 2; + int medianIndex = -1; + double middleValue = values[midIndex]; + if (first < last) + { + if (middleValue > last) + { + // last is the middle value + medianIndex = end; + } + else if (middleValue > first) + { + // middleValue is the middle value + medianIndex = midIndex; + } + else + { + // first is the middle value + medianIndex = start; + } + } + else + { + if (middleValue > first) + { + // first is the middle value + medianIndex = start; + } + else if (middleValue < last) + { + // last is the middle value + medianIndex = end; + } + else + { + // middleValue is the middle value + medianIndex = midIndex; + } + } + return medianIndex; + } + + /// + /// Calculate the Pearson correlation of two series of real numbers with filter conditions. + /// The Pearson score is in [-1, 1]. + /// NOTE: We have 2 versions of this function in order to avoid allocating extra memory. This method should be + /// removed once we change everything to k-dimension. In the mean time, any changes made here should also be made + /// in the other version of this method. + /// + /// The first numerical vector + /// The second numerical vector + /// The indexes that are accountable for the pearson correlation calculation + public static double PearsonCoeff( + IReadOnlyList vector1, + IReadOnlyList vector2, + IReadOnlyList validIndexes = null) + { + // Conduct pearson correlation only when the two series are equal length, and also with at least 5 data points. + // Otherwise, the correlation may not be statistically significant + if (vector1.Count <= PearsonCoeffMinLength || + vector1.Count != vector2.Count || + (validIndexes != null && validIndexes.Count <= PearsonCoeffMinLength)) + return 0; + + bool hasIndexes = validIndexes != null && validIndexes.Count > 0; + int length = hasIndexes ? validIndexes.Count : vector1.Count; + + double averageX = 0; + double averageY = 0; + if (hasIndexes) + { + for (int i = 0; i < length; i++) + { + int idx = validIndexes[i]; + averageX += vector1[idx]; + averageY += vector2[idx]; + } + } + else + { + for (int i = 0; i < length; i++) + { + averageX += vector1[i]; + averageY += vector2[i]; + } + } + averageX /= length; + averageY /= length; + + double stdvX = 0; + double stdvY = 0; + if (hasIndexes) + { + for (int i = 0; i < length; i++) + { + int idx = validIndexes[i]; + double deltaX = vector1[idx] - averageX; + double deltaY = vector2[idx] - averageY; + stdvX += deltaX * deltaX; + stdvY += deltaY * deltaY; + } + } + else + { + for (int i = 0; i < length; i++) + { + double deltaX = vector1[i] - averageX; + double deltaY = vector2[i] - averageY; + stdvX += deltaX * deltaX; + stdvY += deltaY * deltaY; + } + } + + // This happens when one vector with identical values. + if (stdvX == 0.0 || stdvY == 0.0) + return 0; + + stdvX = Math.Sqrt(stdvX / (length - 1)); + stdvY = Math.Sqrt(stdvY / (length - 1)); + + double coeff = 0; + if (hasIndexes) + { + for (int i = 0; i < length; i++) + { + int idx = validIndexes[i]; + double item1 = (vector1[idx] - averageX) / stdvX; + double item2 = (vector2[idx] - averageY) / stdvY; + coeff += item1 * item2; + } + } + else + { + for (int i = 0; i < length; i++) + { + double item1 = (vector1[i] - averageX) / stdvX; + double item2 = (vector2[i] - averageY) / stdvY; + coeff += item1 * item2; + } + } + + return coeff / (length - 1); + } + + public static double PearsonCoeff( + IReadOnlyList values, + IReadOnlyList validIndexes = null) + { + // Conduct pearson correlation only when the two series are equal length, and also with at least 5 data points. + // Otherwise, the correlation may not be statistically significant + if (values.Count <= PearsonCoeffMinLength || + (validIndexes != null && validIndexes.Count <= PearsonCoeffMinLength)) + { + return 0; + } + + bool hasIndexes = validIndexes != null && validIndexes.Count > 0; + int length = hasIndexes ? validIndexes.Count : values.Count; + + double averageX = 0; + double averageY = 0; + if (hasIndexes) + { + for (int i = 0; i < length; i++) + { + int idx = validIndexes[i]; + averageX += values[idx][0]; + averageY += values[idx][1]; + } + } + else + { + for (int i = 0; i < length; i++) + { + averageX += values[i][0]; + averageY += values[i][1]; + } + } + averageX /= length; + averageY /= length; + + double stdvX = 0; + double stdvY = 0; + if (hasIndexes) + { + for (int i = 0; i < length; i++) + { + int idx = validIndexes[i]; + double deltaX = values[idx][0] - averageX; + double deltaY = values[idx][1] - averageY; + stdvX += deltaX * deltaX; + stdvY += deltaY * deltaY; + } + } + else + { + for (int i = 0; i < length; i++) + { + double deltaX = values[i][0] - averageX; + double deltaY = values[i][1] - averageY; + stdvX += deltaX * deltaX; + stdvY += deltaY * deltaY; + } + } + + // This happens when one vector with identical values. + if (stdvX == 0.0 || stdvY == 0.0) + return 0; + + stdvX = Math.Sqrt(stdvX / (length - 1)); + stdvY = Math.Sqrt(stdvY / (length - 1)); + + double coeff = 0; + if (hasIndexes) + { + for (int i = 0; i < length; i++) + { + int idx = validIndexes[i]; + double item1 = (values[idx][0] - averageX) / stdvX; + double item2 = (values[idx][1] - averageY) / stdvY; + coeff += item1 * item2; + } + } + else + { + for (int i = 0; i < length; i++) + { + double item1 = (values[i][0] - averageX) / stdvX; + double item2 = (values[i][1] - averageY) / stdvY; + coeff += item1 * item2; + } + } + + return coeff / (length - 1); + } + + /// + /// Compute the square of the euclidean distance between 2 k-dimensional points. + /// + /// The first point + /// The second point + public static double SquareDistance(double[] point1, double[] point2) + { + if (point1.Length == 0 || point1.Length != point2.Length) + return double.NaN; + + double result = 0; + for (int i = 0; i < point1.Length; i++) + { + var delta = point1[i] - point2[i]; + result += delta * delta; + } + + return result; + } + } + + /// + /// this class is used to calculate the common mathematical properties of time series. + /// + public class TimeSeriesProperty + { + /// + /// 0.1 ~ 0.3 * 0.3, which means, when the fluctuation is more than 30% of the base signal, we think it is noisy. + /// + private const double NoisyLevelThreshold = 0.1; + + /// + /// when the different between the typical distance (represented by median) and max/min distance is smaller than this threshold, + /// we think the equal distance property is approximately hold. + /// [6821022]: parameter is tuned to fix this bug. + /// + private const double AlmostEqualDistanceThreshold = 0.1; + + /// + /// when LargestGap/Median exceeds this threshold, we think the non-equal distance becomes illness. + /// this threshold is a magnitude-level threshold + /// + private const double MaxNonEqualDistanceThreshold = 6.0; + + /// + /// one data point is defined as spatial outlier is its gap: + /// gap/median exceeds this threshold. + /// + private const double SpatialOutlierThreshold = 1.5; + + /// + /// when the population rate of spatial outliers exceeds this threshold, we think the non-equal distance becomes illness. + /// + private const double SpatialOutlierRateThreshold = 0.1; + + /// + /// we are using Gaussian kernel smoothing function. here this value refers to the weight of the nearest neighbor of a given point. + /// NOTE: this parameter effects the smoothing radius, which relates to the overall smoothing complexity. we should carefully tune this parameter. + /// + private const double KernelSmoothingParameter = 0.6; + + /// + /// when the weight is smaller than this threshold, it will not be taken into account for weighted smoothing. + /// + private const double MinSmoothEffectedWeight = 0.01; + + /// + /// this is the threshold of the angle of the corner point, to check if it is significant or not. + /// this threshold is carefully tuned. since we are using Gaussian kernel for smoothing, the smoothing window size is smaller than previous method, + /// which cause the corner point threshold should be changed as well. + /// + private const double CornerPointThreshold = 121; + + /// + /// for performance optimization. + /// the smoothing radius can be pre-calculated, so that we do not need to calculate it on-the-fly. + /// NOTE: the overall complexity of smoothing a whole time series is O(n * smoothing radius) + /// + private static readonly int _smoothingRadius; + + /// + /// the smoothing weights for the data points near the given data point (with the range of radius) + /// + private static readonly double[] _smoothingWeights; + + /// + /// this is used to boost the normalization. this is a cumulative array of the weights within the range of [-radius, radius]. specifically, this is with + /// length equals to 2*radius + 2, i.e., c0=0, c1, c2, ... c(r), c(r+1), c(r+2), ... c(2r+1), where c(r) is the center. + /// + private static readonly double[] _cumulativeDenominator; + + static TimeSeriesProperty() + { + _smoothingRadius = (int)Math.Sqrt(Math.Log(MinSmoothEffectedWeight) / Math.Log(KernelSmoothingParameter)); + if (_smoothingRadius >= 0) + { + _smoothingWeights = new double[_smoothingRadius + 1]; + _smoothingWeights[0] = 1.0; + for (int i = 1; i < _smoothingWeights.Length; i++) + { + _smoothingWeights[i] = Math.Pow(KernelSmoothingParameter, i * i); + } + + _cumulativeDenominator = new double[2 * _smoothingRadius + 2]; + _cumulativeDenominator[0] = 0.0; + for (int i = 1; i < _cumulativeDenominator.Length; i++) + { + int dis = Math.Abs(_smoothingRadius + 1 - i); + _cumulativeDenominator[i] = _cumulativeDenominator[i - 1] + _smoothingWeights[dis]; + } + } + } + + /// + /// given a pure seasonal series, calculate the average amplitude. + /// + /// the input time series, which should be pure seasonal + /// the period of the seasonal time series + public static double Amplitude(IReadOnlyList seasonalSeries, int period) + { + double result = 0; + int numberOfPeriods = 0; + for (int i = 0; i + period <= seasonalSeries.Count; i += period) + { + numberOfPeriods++; + double max = double.MinValue; + double min = double.MaxValue; + for (int j = i; j < i + period; j++) + { + max = Math.Max(max, seasonalSeries[j]); + min = Math.Min(min, seasonalSeries[j]); + } + result += max - min; + } + + // divde by 2, is by definition of amplitude. + return result / numberOfPeriods / 2; + } + + /// + /// obtain the outliers by using 6-sigma methods. + /// + /// the residual that the regression curve is eliminated + /// outlier Severity + /// the mean residual squares is an additional output + /// significance + /// measure the strictness of the outlier. default value is 6, which is very strict for selecting outliers. + public static int[] DetermineOutliers(IReadOnlyList residual, out double[] outlierSeverity, out double mrs, out double significance, double severityThreshold = 6) + { + int length = residual.Count; + + // epsilon is used to represents a very small positive real value. + const double epsilon = 0.0001; + mrs = 0; + significance = 0; + outlierSeverity = new double[length]; + var outlierIndexes = new int[length]; + + var absResiduals = new double[length]; + double nonzeroMin = double.MaxValue; + for (int i = 0; i < length; i++) + { + absResiduals[i] = Math.Abs(residual[i]); + if (absResiduals[i] > epsilon && nonzeroMin > absResiduals[i]) + nonzeroMin = absResiduals[i]; + } + + double median = MathUtility.QuickSelect(absResiduals, length / 2); + + // When the median is 0, this is possible when there are more than half 0s. + // In such case, it is not perfect to mark all the non-zero points as outliers. + // Therefore, we need to find the first value that is not zero. + if (median < epsilon) + { + median = nonzeroMin; + } + + // this implies that all the values are smaller than epsilon, therefore, there's no outliers at all. + if (median < epsilon) + { + for (int i = 0; i < length; i++) + { + outlierIndexes[i] = 0; + outlierSeverity[i] = 0; + } + } + else + { + // when median is very close to 0, which means the regularity of the serial is strong, so that no data points is outlier. + for (int i = 0; i < length; i++) + { + double severity = Math.Abs(residual[i]) / median; + + // this is the key criteria + if (severity > severityThreshold) + { + double newSignificance = MathUtility.GaussianSignificance(residual[i], median, 3 * median); + if (newSignificance > significance) + significance = newSignificance; + outlierIndexes[i] = 1; + outlierSeverity[i] = severity; + } + else + { + outlierIndexes[i] = 0; + outlierSeverity[i] = 0; + } + + // Here, we estimate the Mrs by adding all the residuals, rather than the residuals of non-outliers. + // Such compromise from robust statistic is for user friendly consideration. + mrs += residual[i] * residual[i]; + } + mrs /= length; + } + return outlierIndexes; + } + + /// + /// this is a method to calculate the CCF lower bound of two time series, by given the first time series, and partial information of the second + /// time series, which is indicated by alpha. + /// specifically, the second time series is the child of the first one from subspace perspective. + /// both should be non-negative, and the first one is larger than the second one for each time epoch. let sum1 is the sum of all values from series1, + /// and sum2 is the sum of all values from series2, then sum2=sum1*(1-alpha). when alpha is a small value, the two time series naturally be correlated, which is not interesting. + /// + /// represents the first time series + /// represents the overall different from the second time series. which should be between 0 and 1. this method works well only when alpha largely smaller than 1 + /// returns the strict lower bound of the CCF of the two time series. + public static double CcfLowerBound(List series, double alpha) + { + int n = series.Count; + double sum = 0; + double max = 0; + double min = double.MaxValue; + foreach (double item in series) + { + sum += item; + max = Math.Max(max, item); + min = Math.Min(min, item); + } + double mean = sum / n; + double var = 0; + foreach (double item in series) + { + var += (item - mean) * (item - mean); + } + double numerator = var + n * alpha * mean * (mean - max); + double denominator = Math.Sqrt(var * (var + alpha * (n * n * alpha * mean * mean - n * alpha * mean * mean + 2 * n * mean * mean - 2 * n * mean * min))); + return numerator / denominator; + } + + /// + /// obtain the outliers by inspecting the residual distribution. + /// this is a more sophisticated method rather than simply set the 6-median, or 3-sigma to determine outliers. + /// + /// the input residuals after trending + /// in the problem of why-analysis, the suitable threshold of corner point is difference than default, thus need to make this as a parameter rather than constant + /// returns the index of the potential outlier candidates. the index is aligned with the input residual + public static List DetermineOutliersEx(IReadOnlyList residual, double cornerPointThreshold = CornerPointThreshold) + { + List result = new List(); + int length = residual.Count; + if (length < 3) + return result; + + List> absResiduals = new List>(); + for (int i = 0; i < residual.Count; i++) + { + double absValue = Math.Abs(residual[i]); + absResiduals.Add(new KeyValuePair(absValue, i)); + } + absResiduals.Sort(ReverseComparer); + + // smoothing. the reason is after we find the corner point, we still eliminate the extra points which are close to the corner point. + var rawY = new double[absResiduals.Count]; + for (int i = 0; i < absResiduals.Count; i++) + { + rawY[i] = absResiduals[i].Key; + } + var smoothedY = KernelSmooth(rawY); + + // normalization. because we use geometric way to identify the corner point. + double yMax = smoothedY[0]; + double yMin = smoothedY[length - 1]; + double xMax = length - 1; + double xMin = 0.0; + List normX = new List(); + List normY = new List(); + for (int i = 0; i < length; i++) + { + normX.Add((i - xMin) / (xMax - xMin)); + normY.Add((smoothedY[i] - yMin) / (yMax - yMin)); + } + + // inspecting the inner product and the angle of each check point + int cornerPointIndex = -1; + double bestAngle = double.MaxValue; + for (int i = 1; i < length - 1; i++) + { + double angle; + double x = normX[i]; + double y = normY[i]; + + if (!EvaluateCornerPoint(x, y, out angle)) + continue; + + if (angle < bestAngle) + { + bestAngle = angle; + cornerPointIndex = i; + } + } + if (cornerPointIndex == -1) + return result; + double normRawX = cornerPointIndex * 1.0 / (length - 1); + double normRawY = (rawY[cornerPointIndex] - rawY[length - 1]) / (rawY[0] - rawY[length - 1]); + double bestRawAngle; + + // we use smoothing curve to obtain the reasonable corner point, then we use the raw (also normalized) curve to obtain the true angle + // of the corner point. this makes best sense: smoothing makes sure robust corner point identification, while angle on the raw curve + // reflects how the original residuals behave. + if (!EvaluateCornerPoint(normRawX, normRawY, out bestRawAngle)) + return result; + + if (bestRawAngle > cornerPointThreshold) + return result; + + // further absort the data points close to the corner point, that doesnt look like outliers + double slope = (1 - normY[cornerPointIndex]) / normX[cornerPointIndex]; + int absorbIndex = -1; + for (absorbIndex = cornerPointIndex - 1; absorbIndex >= 0; absorbIndex--) + { + double currentSlope = (normY[absorbIndex] - normY[absorbIndex + 1]) / (normX[absorbIndex + 1] - normX[absorbIndex]); + if (currentSlope > slope) + break; + } + + if (absorbIndex != -1) + cornerPointIndex = absorbIndex + 1; + + for (int i = 0; i < cornerPointIndex; i++) + { + result.Add(absResiduals[i].Value); + } + + return result; + } + + /// + /// check if the (almost) equal distance property is hold + /// + /// the x-axis values + /// the typical gap/distance between two adjacent data points + /// return true if the x-axis values are equal or almost equal distance + public static bool IsEqualDistance(IReadOnlyList xValues, out double typicalGap) + { + typicalGap = double.NaN; + int length = xValues.Count; + + // equal distance is undefined under such condition + if (xValues == null || length < 2) + { + return false; + } + else if (length == 2) + { + typicalGap = xValues[1] - xValues[0]; + return true; + } + + // if this is indeed a regular distance (i.e., equal-distance segments divided by regular gaps) + List gaps; + double regularGap; + double minGap; + double maxGap; + if (IsRegularEqualDistance(xValues, out typicalGap, out regularGap, out gaps, out minGap, out maxGap)) + return true; + + // will inspect the x values only when the gaps is null after checking regular equal-distance. + if (gaps == null) + { + gaps = new List(xValues.Count - 1); + for (int i = 1; i < length; i++) + { + double gap = xValues[i] - xValues[i - 1]; + maxGap = Math.Max(maxGap, gap); + minGap = Math.Min(minGap, gap); + gaps.Add(gap); + } + } + typicalGap = MathUtility.QuickMedian(gaps); + + // the min gap should not be too small + if (typicalGap - minGap > AlmostEqualDistanceThreshold * typicalGap) + return false; + + // the max gap should not be too large + if (maxGap - typicalGap > AlmostEqualDistanceThreshold * typicalGap) + return false; + return true; + } + + /// + /// considering there would be the case that final visual effect is confusing, although underlying algorithm could deal with non-equal distance. + /// Therefore, we need to identify the strong non-equal distance datasets, and avoid analyze or output it. + /// Here I propose several rules to define what is a strong non-equal distance behavior: + /// 1 - a point is an outlier (spatial outlier) if and only if its gap is SpatialOutlierThreshold larger than typical distance (represented by median gap) + /// 2 - an outlier is illness if its gap is MaxNonEqualDistanceThreshold larger from typical distance. when there exist illness outlier, we bypass time series analysis + /// 3 - the ratio of the outliers cannot exceed SpatialOutlierRateThreshold of total number of points + /// 4 - any two outliers must NOT be adjacent (otherwise, the analysis results may differ from user intuition! think about it) + /// + /// the input x-axis values + /// returns true if the input series is an illness/strongly non-equal distance series. + public static bool IsStrongNonEqualDistance(List xValues) + { + if (xValues == null) + return false; + + int length = xValues.Count; + if (length <= 2) + return false; + + // if it is a regular equal-distance, then strong non-equal distance is false. + double typicalGap; + double regularGap; + List gaps; + double minGap; + double maxGap; + if (IsRegularEqualDistance(xValues, out typicalGap, out regularGap, out gaps, out minGap, out maxGap)) + return false; + + if (gaps == null) + { + gaps = new List(xValues.Count - 1); + maxGap = double.MinValue; + for (int i = 1; i < length; i++) + { + double gap = xValues[i] - xValues[i - 1]; + gaps.Add(gap); + maxGap = Math.Max(maxGap, gap); + } + } + + // since this method will modify the input list. we need to copy one in order to preserve the original order + double median = MathUtility.QuickMedian(new List(gaps)); + double threshold = SpatialOutlierThreshold * median; + + // illness checking. + if (maxGap > median * MaxNonEqualDistanceThreshold) + return true; + + int outlierCount = 0; + for (int i = 0; i < gaps.Count; i++) + { + double gap = gaps[i]; + if (gap > threshold) + { + outlierCount++; + + // if two spatial outliers are adjacent, then the left point will look like an isolated data point. isolated data points are not addressed in any type of insights so far. + if (i != 0 && gaps[i - 1] > threshold) + { + return true; + } + + // if more than SpatialOutlierRateThreshold of total points are outliers, we think this is a very strong non-equal distance series + if (outlierCount > SpatialOutlierRateThreshold * gaps.Count) + return true; + } + } + return false; + } + + /// + /// check if the (almost) equal distance property is hold by considering an additional regular gap. + /// the motivation is from the stock time series, which has stock records on weekdays, but no records at weekends, regularly. + /// in order to support this, we should first identify the consistency gap (~2 days), then the left gaps must qualify the equal-distance gap. + /// [remark]: in order to be regular equal-distance x-values, the segments (with typical gap) must be with equal number of data points (the + /// first and the last segment can contain fewer data points), and strictly separated by the regular gap. this method does not handle the + /// noise (a few violation). a suitable place to address the noise condition is a logic layer after data query, and before all time series + /// analysis, where we can detect these spatial noise points, and eliminate them properly before feeding into time series analysis modules. + /// + /// the x-axis values + /// the typical gap-distance between two adjacent data points + /// the regular gap between two segments (e.g., two days corresponds to the weekend in stock dataset) + /// performance: the list of gaps may be used further in other places. + /// performance: caller can directly use this value: the maximum gap along the x values + /// performance: caller can directly use this value: the minimum gap along the x values + /// return true if the x-axis values are equal distance by considering the regular gaps + public static bool IsRegularEqualDistance( + IReadOnlyList xValues, + out double typicalGap, + out double regularGap, + out List gaps, + out double minGap, + out double maxGap) + { + typicalGap = double.NaN; + regularGap = double.NaN; + minGap = double.MaxValue; + maxGap = double.MinValue; + gaps = null; + + int length = xValues.Count; + + /* when time series is with length smaller than 9, any regular gaps between equal-distance regions still + * make the overall time series 'looks' messy; the seasonal component will be weak since the recurrence of + * seasonal component is too few. + */ + if (xValues == null || length < 9) + return false; + gaps = new List(xValues.Count - 1); + for (int i = 1; i < length; i++) + { + double gap = xValues[i] - xValues[i - 1]; + maxGap = Math.Max(gap, maxGap); + minGap = Math.Min(gap, minGap); + gaps.Add(gap); + } + + // we assume the regular gap must be significantly larger than the typical gap. + if (maxGap - minGap < minGap) + return false; + + // records the typical/regular gaps, which is used to estimate a robust typical/regular gap to return. + List typicalGaps = new List(); + List regularGaps = new List(); + + /* if the regular equal-distance property is satisfied, then the middle inter-segment length must be equal, and the + length of the first or last segment must be equal or smaller than the middle length*/ + int firstSegLength = -1; + int firstMiddleSegLength = -1; + int currMiddleSegLength = -1; + for (int i = 0; i < gaps.Count; i++) + { + var currentGap = gaps[i]; + + if (currentGap - minGap <= AlmostEqualDistanceThreshold * minGap) + { + // this is a typical gap + typicalGaps.Add(currentGap); + + if (firstSegLength == -1) + { + // the first segment length is not set yet + firstSegLength = 1; + } + else if (firstMiddleSegLength == -1) + { + // the first middle segment length is not set yet + firstSegLength++; + } + else if (currMiddleSegLength == -1) + { + // the first middle segment length is just started + firstMiddleSegLength++; + } + else + { + // the current middle segment length is just started + currMiddleSegLength++; + + // current segment length is already longer than previous segment length, this is a violation. + if (currMiddleSegLength > firstMiddleSegLength) + return false; + } + } + else if (maxGap - currentGap <= AlmostEqualDistanceThreshold * maxGap) + { + // this is a regular gap + regularGaps.Add(currentGap); + + // the first middle segment length is not set yet + if (firstMiddleSegLength == -1) + { + // start counting the length of the first middle segment + firstMiddleSegLength = 0; + } + else if (currMiddleSegLength == -1) + { + // start counting the length of the next segment + currMiddleSegLength = 0; + + // the first segment cannot be longer than middle ones + if (firstSegLength > firstMiddleSegLength) + return false; + } + else + { + // the length of the middle segments must be identical + if (currMiddleSegLength != firstMiddleSegLength) + return false; + currMiddleSegLength = 0; + } + } + else + { + // the current gap is neither similar to typical gap nor similar to regular gap, this is a violation. + return false; + } + } + + // the check of the last segment + if (currMiddleSegLength > firstMiddleSegLength) + return false; + + // number of regular gaps indicate the segments, or number of repeating. this should not be too few to indicate reasonable repeating. + if (regularGaps.Count < BasicParameters.MinRegularGap) + return false; + + // use the median value to robustly represent the true typical/regular gaps + typicalGap = MathUtility.QuickMedian(typicalGaps); + regularGap = MathUtility.QuickMedian(regularGaps); + return true; + } + + /// + /// determine if a given time series is too noisy or not by considering its normalized residual squares + /// + /// normalized residual squares + /// true if the normalized residual squares exceeds the given threshold + public static bool IsTooNoiseSignal(double mrs) + { + return mrs > NoisyLevelThreshold; + } + + /// + /// calculate the angle of a given data point within a sorted residual curve. return true if the angle is valid. + /// + public static bool EvaluateCornerPoint(double x, double y, out double angle, bool inRadians = false) + { + angle = -1.0; + + // use outer product to check if the corner point is valid. if it is minus, it means a very rare thing, that this point is actually an anti-corner point + double outerProduct = (1 - x) * (1 - y) - x * y; + if (outerProduct <= 0) + return false; + + double innerProduct = -(x * (1 - x) + y * (1 - y)); + + // considering 3 points p1, p2 and p3: (0, 1), (x, y), (1, 0), they form up two vectors v1 = (-x, 1-y), v2 = (1-x, -y) + // and here we calculate the angle between the two vectors. considering the normalized sorted residual curve, the left-most + // point is p1, and the right most point is p3, here corner point is defined as p2 which has smallest angle. + // intuitively, the data points on the left side of corner point are with significant higher residuals than the others, + // which are most likely to be outliers. + double cosine = innerProduct / Math.Sqrt((x * x + (1 - y) * (1 - y)) * ((1 - x) * (1 - x) + y * y)); + angle = Math.Acos(cosine); + + // convert to 360-degree unit, which is intuitive for tuning + if (!inRadians) + angle = angle * 180 / Math.PI; + + return true; + } + + /// + /// use Gaussian kernel smoothing function to smooth a time series with equal x-axis distance + /// + /// the specified time series + /// returns the smoothed time series + internal static IReadOnlyList KernelSmooth(IReadOnlyList values) + { + if (_smoothingRadius <= 0) + return values; + + var results = new double[values.Count]; + for (int i = 0; i < values.Count; i++) + results[i] = KernelSmooth(values, i); + return results; + } + + /// + /// Gaussian kernel based smoothing. i.e., K(x1, x2) = EXP[-(x-x0)^2/2b^2]. + /// this is a simplified version, which assumes the x-axis values are equal-distance. hence given a fixed point x0, the weight K(x, x0) will look like + /// 1.0, EXP(-1/2b^2), EXP(-4/2b^2), EXP(-9/2b^2), ... etc. let EXP(-1/2b^2) = lambda, then the weights can be re-formulated as + /// 1.0, lambda, lambda^4, lambda^9, lambda^16, ... etc. + /// + /// the original one-dimensional time series + /// the specified position that needs to be smoothed + /// return the smoothed value + private static double KernelSmooth(IReadOnlyList values, int index) + { + int lowIndex = index - _smoothingRadius; + if (lowIndex < 0) + lowIndex = 0; + int upperIndex = index + _smoothingRadius; + if (upperIndex >= values.Count) + upperIndex = values.Count - 1; + double weightedSum = 0; + for (int i = lowIndex; i <= upperIndex; i++) + { + int distance = Math.Abs(i - index); + double currentWeight = _smoothingWeights[distance]; + weightedSum += values[i] * currentWeight; + } + double totalWeight = _cumulativeDenominator[_smoothingRadius + 1 + upperIndex - index] - _cumulativeDenominator[_smoothingRadius - index + lowIndex]; + return weightedSum / totalWeight; + } + + private static int ReverseComparer(KeyValuePair left, KeyValuePair right) + { + int compare1 = left.Key.CompareTo(right.Key); + if (compare1 != 0) + return -compare1; + return -left.Value.CompareTo(right.Value); + } + } + + /// + /// this class is used to calculate Combinatorics related mathematics. + /// + public class Combinatorics + { + /// + /// calculate the logarithm of factorial. the reeason for using logarithm is to control the data value. + /// or the value can easily exceed the int.MaxValue. see the ref on + /// http://en.wikipedia.org/wiki/Stirling%27s_approximation + /// + /// the input integer. + public static double LogFactorial(int n) + { + double value = 0.5 * Math.Log(2 * Math.PI * n) + n * Math.Log(n) - n + Math.Log(1 + 1.0 / 12 / n); + return value; + } + + /// + /// calculate the logarithm of combinations. n!/m!/(n-m)! + /// + /// the base number + /// the numbers should select out. m is smaller than n + public static double LogCombination(int n, int m) + { + if (n < 0 || m < 0 || m > n) + throw new Exception("combination calculation input invalid"); + if (n == 0) + return 0; + if (m == 0 || m == n) // note that here is LOG! + return 0; + return LogFactorial(n) - LogFactorial(m) - LogFactorial(n - m); + } + } + + /// + /// this class is used for density estimation for spatial data analysis. + /// currently, it is used to accept/reject if a proper scatter plot can well represent the cross-measure correlation. + /// if there exists a high density, small region, then the scatter plot will distort the true underlying correlation, we prefer rather not + /// show it to end user. + /// + public class DensityIdentifier + { + /// + /// when too few data points, the further density estimation algorithm will not be effective. + /// + private const int MinimumDataPointCount = 5; + + /// + /// number of segments on x axis, and y axis. + /// this value should be relatively large, must NOT be smaller than 4. actually, this constant is related with the high-density ratio threshold. + /// take these two values (20, 0.3) as an example, suppose all the data points form a linear line, then these data points are occupied by 20 + /// diagonal grids, each one typically with 1/20~5% data points. then 30% points is certainly a much higher ratio. + /// + private const int SegmentCount = 20; + + /// + /// when a grid with data points more than this ratio, it is viewed as a high density region. + /// + private const double HighDensityRatio = 0.3; + + /// + /// when a gap is larger than typical gap times this value, we think it is a very large gap. + /// this can be used to avoid some regular insight analysis such as cross measure correlation. + /// + private const double MaxGapThreshold = 8.0; + + /// + /// grid-based density estimation to check whether there exists at least one grid with high ratio of data points. + /// + /// the x-axis values of the data points + /// the y-axis values of the data points + /// false if there does not exist any high density region + public static bool HasHighDensityRegions(double[] xValues, double[] yValues) + { + if (xValues == null || yValues == null || xValues.Length != yValues.Length || xValues.Length < MinimumDataPointCount) + return false; + double xMin = double.MaxValue; + double xMax = double.MinValue; + double yMin = double.MaxValue; + double yMax = double.MinValue; + foreach (double value in xValues) + { + xMin = Math.Min(xMin, value); + xMax = Math.Max(xMax, value); + } + foreach (double value in yValues) + { + yMin = Math.Min(yMin, value); + yMax = Math.Max(yMax, value); + } + + double xGridSize = (xMax - xMin) / SegmentCount; + double yGridSize = (yMax - yMin) / SegmentCount; + + // if all the x or y values are identical, then trivial + if (xGridSize <= double.Epsilon || yGridSize <= double.Epsilon) + return false; + + // key indicates the location of the grid, value is number of data points within this grid + Dictionary densityPerGrid = new Dictionary(); + + int count = xValues.Length; + for (int i = 0; i < count; i++) + { + int xGridIndex = (int)((xValues[i] - xMin) / xGridSize); + int yGridIndex = (int)((yValues[i] - yMin) / yGridSize); + long hashId = GetHashId(xGridIndex, yGridIndex); + if (!densityPerGrid.ContainsKey(hashId)) + { + densityPerGrid.Add(hashId, 1); + } + else + { + densityPerGrid[hashId]++; + + // if more than 30% data points are within one small grid, which means a high portion of data points located in a small region. + if (densityPerGrid[hashId] > count * HighDensityRatio) + { + return true; + } + } + } + return false; + } + + /// + /// check if there exists a large gap along a set of 2-dimensional data points. + /// + /// the x-axis values of the data points + /// the y-axis values of the data points + /// true indicates there indeed exists a large gap; false otherwise + public static bool HasLargeGap(double[] xValues, double[] yValues) + { + if (HasLargeGap(xValues)) + return true; + if (HasLargeGap(yValues)) + return true; + return false; + } + + private static long GetHashId(int xGridIndex, int yGridIndex) + { + long xLong = (long)xGridIndex; + long yLong = (long)yGridIndex; + return (xLong << 32) + yLong; + } + + /// + /// check if there exists a large gap along a set of one dimensional data points. + /// + /// a list of one dimensional data points + /// true indicates there indeed exists a large gap; false otherwise + private static bool HasLargeGap(double[] values) + { + if (values == null || values.Length <= 3) + return false; + List sortedValues = new List(values); + sortedValues.Sort(); + + List gaps = new List(sortedValues.Count - 1); + double maxGap = double.MinValue; + for (int i = 1; i < sortedValues.Count; i++) + { + var gap = sortedValues[i] - sortedValues[i - 1]; + + // when the gap is almost zero, which means they are overlapped, we ignore this case, since it has no effect on chart. + if (gap < double.Epsilon) + continue; + maxGap = Math.Max(maxGap, gap); + gaps.Add(gap); + } + double typicalGap = MathUtility.QuickMedian(gaps); + if (typicalGap * MaxGapThreshold < maxGap) + return true; + return false; + } + } + + /// + /// move this from point insight evaluation to numerical library, because in small number analysis for why-analysis, + /// we need to use these methods to check if extreme points exist + /// + public class CalculateRankSignificance + { + public static double OutstandingRankMinusOne(double waitValue, ICollection otherValues, ref bool isSignificant) + { + if (waitValue >= 0 || otherValues.Count == 0) + { + // when less or equal to 2 points, not so meaningful + isSignificant = false; + return 0; + } + + List minusValues = new List(); + foreach (double value in otherValues) + { + if (value < 0) + { + minusValues.Add(-value); + } + } + return OutstandingRankOne(-waitValue, minusValues, ref isSignificant); + } + + public static double OutstandingRankOne(double waitValue, ICollection otherValues, ref bool isSignificant) + { + if (waitValue < 0 || otherValues.Count == 0) + { + isSignificant = false; + return 0; + } + List orderedValues = new List(); + foreach (double value in otherValues) + { + if (value > 0) + orderedValues.Add(value); + } + + orderedValues.Sort((x, y) => y.CompareTo(x)); + if (orderedValues.Count >= 2) + { + // train model + double alpha = -1; + double innerProduct = 0; + double var = 0; + for (int i = 0; i < orderedValues.Count; i++) + { + double x = i + 2; + double y = orderedValues[i]; + double phy = KernelFunc(x); + var += phy * phy; + innerProduct += phy * y; + } + alpha = innerProduct / var; + + // get error collection + double meanError = 0; + double varError = 0; + for (int i = 0; i < orderedValues.Count; i++) + { + double currentError = Math.Abs(orderedValues[i] - (alpha * KernelFunc(i + 2))); + meanError += currentError; + varError += currentError * currentError; + } + meanError /= orderedValues.Count; + varError /= orderedValues.Count; + double stdv = Math.Sqrt(varError); + + // calcualte 3-sigma significance + double estimatedError = waitValue - (alpha * KernelFunc(1)); + + // this is the consideration of the temporal information. the outstanding no. 1 must has higher error then the second + double secondError = Math.Abs(orderedValues[0] - (alpha * KernelFunc(2))); + + // My proposed solution to detect high-quality outstanding #1 (i.e., it is also not an outstanding top-k insight, for example, not a top-two, or top-three, etc.) is to + // add such additional checking: v1-α≥k(v2-α/2^β ), Basically, I only check the error of the 1st and 2nd values. + // Next, I will first show what is a proper k, and then, I will illustrate why checking these two values are enough (i.e., we don’t need to check the left values) + // In the typical outstanding top-two case, when v2≫ vi,where i>2, the estimated α* is + // α*=argmax(α) ⁡∑[i from 2 to ∞] (vi-α/i^β)^2 -> α*=(∑vi/i^β)/(∑i^2β) ≈ (v2/2^β)/(∑1/i^2β ) ≈ 0.294v2, when β=0.7 + // According to the empirical study, under such circumstance, the first value should be at least TWO times larger than the second, user would think this is a valid outstanding no. 1, + // therefore, we have 2v2-α≥k(v2-α/2^β ), Put α* and β into, we have k=2.083 as the minimum value. + // So when we set k≥2.083, when outstanding no.1 insight is output, the first value will be 2 times higher than the second, + // which is aligned with user’s feeling. Above we only consider the first and second values. Here, let’s consider + // v2≈v3≫ vi,i>3: because we don’t want to wrongly make an top-three insight as a top-1 insight. according to the empirical study, + // the first value should be at least 2 times larger than the second, user would think this is a valid outstanding no. 1. + // According to the calculation, we found that the k=2.083 still valid for this case ((as v3 grows, the error of v1 decreased faster than the error of v2)). + // It’s not difficult to verify that (by a simple qualitative analysis), in order to avoid wrongly make outstanding top-k as outstanding no.1, this threshold is valid. + + double k = 2.083; + + // based on the intuition, v1 should be at least 5 times larger than v2 when v2 is one magnitude larger than v3. in order to achieve this, k should be set to 5.64 accordingly. + if (orderedValues[0] / orderedValues[1] > 10) + { + k = 5.64; + } + if (estimatedError > k * secondError && estimatedError >= meanError + (1 * stdv)) + { + isSignificant = true; + return MathUtility.GaussianSignificance(estimatedError, meanError, stdv); + } + else + { + isSignificant = false; + return 0; + } + } + else + { + isSignificant = false; + return 0; + } + } + + /// + /// the kernel function used for evaluating significant rank #1 + /// + private static double KernelFunc(double x) + { + return 1.0 / Math.Pow(x, 0.7); + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/MatrixEx.cs b/src/Microsoft.ML.TimeSeries/MatrixEx.cs new file mode 100644 index 0000000000..f8800b1ea4 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/MatrixEx.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + internal static class MatrixEx + { + /// + /// Calculate the inverse of a matrix + /// Please make sure that: + /// 1. the input matrix is a square matrix; + /// 2. the input matrix is not singular. + /// + /// the input matrix + /// the inverse of a matrix + public static double[,] ReverseMatrix(this double[,] matrix) + { + if (matrix.GetLength(0) != matrix.GetLength(1)) + return null; + int level = matrix.GetLength(0); + + // Calculate the determinant value of the matrix + double determinantMatrixValue = MatrixValue(matrix, level); + if (determinantMatrixValue == 0) + return null; + + double[,] reverseMatrix = new double[level, 2 * level]; + double x; + double c; + + // Init Reverse matrix + for (int i = 0; i < level; i++) + { + for (int j = 0; j < 2 * level; j++) + { + if (j < level) + reverseMatrix[i, j] = matrix[i, j]; + else + reverseMatrix[i, j] = 0; + } + + reverseMatrix[i, level + i] = 1; + } + + for (int i = 0, j = 0; i < level && j < level; i++, j++) + { + if (reverseMatrix[i, j] == 0) + { + int m = i; + for (; matrix[m, j] == 0; m++) + { + } + + if (m == level) + { + return null; + } + else + { + // Add i-row with m-row + for (int n = j; n < 2 * level; n++) + reverseMatrix[i, n] += reverseMatrix[m, n]; + } + } + + // Format the i-row with "1" start + x = reverseMatrix[i, j]; + if (x != 1) + { + for (int n = j; n < 2 * level; n++) + { + if (reverseMatrix[i, n] != 0) + reverseMatrix[i, n] /= x; + } + } + + // Set 0 to the current column in the rows after current row + for (int s = level - 1; s > i; s--) + { + x = reverseMatrix[s, j]; + for (int t = j; t < 2 * level; t++) + reverseMatrix[s, t] -= reverseMatrix[i, t] * x; + } + } + + // Format the first matrix into unit-matrix + for (int i = level - 2; i >= 0; i--) + { + for (int j = i + 1; j < level; j++) + { + if (reverseMatrix[i, j] != 0) + { + c = reverseMatrix[i, j]; + for (int n = j; n < 2 * level; n++) + reverseMatrix[i, n] -= c * reverseMatrix[j, n]; + } + } + } + + double[,] result = new double[level, level]; + for (int i = 0; i < level; i++) + { + for (int j = 0; j < level; j++) + result[i, j] = reverseMatrix[i, j + level]; + } + return result; + } + + /// + /// Calculate the determinant value of a matrix + /// + public static double MatrixValue(double[,] matrixList, int level) + { + double[,] matrix = new double[level, level]; + for (int i = 0; i < level; i++) + { + for (int j = 0; j < level; j++) + matrix[i, j] = matrixList[i, j]; + } + double c; + double x; + int k = 1; + for (int i = 0, j = 0; i < level && j < level; i++, j++) + { + if (matrix[i, j] == 0) + { + int m = i; + for (; matrix[m, j] == 0; m++) + { + } + + if (m == level) + { + return 0; + } + else + { + // Row change between i-row and m-row + for (int n = j; n < level; n++) + { + c = matrix[i, n]; + matrix[i, n] = matrix[m, n]; + matrix[m, n] = c; + } + + // Change value pre-value + k *= -1; + } + } + + // Set 0 to the current column in the rows after current row + for (int s = level - 1; s > i; s--) + { + x = matrix[s, j]; + for (int t = j; t < level; t++) + matrix[s, t] -= matrix[i, t] * (x / matrix[i, j]); + } + } + + double sn = 1; + for (int i = 0; i < level; i++) + { + if (matrix[i, i] != 0) + sn *= matrix[i, i]; + else + return 0; + } + return k * sn; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj index c5b4550c51..fc02b0bd5c 100644 --- a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj +++ b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj @@ -3,8 +3,18 @@ netstandard2.0 Microsoft.ML.TimeSeries + Library + + + + + + + + + diff --git a/src/Microsoft.ML.TimeSeries/MovingAverage.cs b/src/Microsoft.ML.TimeSeries/MovingAverage.cs new file mode 100644 index 0000000000..cd468fd1bb --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/MovingAverage.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class MovingAverage + { + /// + /// calculate the moving average of a given series. + /// + /// the input series + /// the length of the moving average window + public static List MA(IReadOnlyList s, int length) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(s, nameof(s)); + if (s.Count <= 1 || length <= 1 || length >= s.Count) + throw new Exception("the input information for moving average is invalid!"); + //var result1 = MaFast(s, length); + //var result2 = MaOld(s, length); + + //Trace.Assert(result1.Count == result2.Count); + + //for (int i=0; i MaOld(IReadOnlyList s, int length) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(s, nameof(s)); + + List series = new List(s); + int left = length / 2; + int right = length - left - 1; + List result = new List(); + for (int i = left; i < series.Count - right; i++) + { + int startIndex = i - left; + int endIndex = i + right; + double sum = 0; + for (int j = startIndex; j <= endIndex; j++) + { + sum += series[j]; + } + result.Add(sum / length); + } + return result; + } + + public static List MaFast(IReadOnlyList s, int length) + { + List results = new List(s.Count); + double partialSum = 0; + for (int i = 0; i < length; ++i) + { + partialSum += s[i]; + } + + for (int i = length; i < s.Count; ++i) + { + results.Add(partialSum / length); + partialSum = partialSum - s[i - length] + s[i]; + } + results.Add(partialSum / length); + + return results; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/SeasonalInfo.cs b/src/Microsoft.ML.TimeSeries/SeasonalInfo.cs new file mode 100644 index 0000000000..a70df98fc4 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/SeasonalInfo.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class SeasonalInfo : SingleSeriesInfo + { + /// + /// Initializes a new instance of the class. + /// the corresponding properties of seasonal component + /// + /// x + /// y + /// the seasonal component + /// period of seasonal component + /// the average amplitude of the seasonal component + /// the statistical confidence + public SeasonalInfo( + IReadOnlyList x, + IReadOnlyList y, + IReadOnlyList seasonal, + int period, + double amplitude, + double confidence) + { + X = x; + Y = y; + SeasonalSignal = seasonal; + Period = period; + Amplitude = amplitude; + + // since seasonal component has sound statistical modeling and tests, so its rank is just the statistical confidence + Rank = confidence; + + Description = String.Format( + "this is a seasonal component, with period = {0}, amplitude = {1}. rank:{2}", + Period, + Amplitude, + Rank); + + Kind = TimeSeriesInfoKind.Seasonal; + } + + /// + /// the curve of seasonal + /// + public IReadOnlyList SeasonalSignal { get; private set; } + + /// + /// the period of the seasonal component. + /// + public int Period { get; private set; } + + /// + /// the average amplitude of the seasonal component + /// + public double Amplitude { get; private set; } + + public override string Description + { + get; + protected set; + } + + public override double Rank + { + get; + set; + } + + public override TimeSeriesInfoKind Kind + { + get; + protected set; + } + + public override string ToString() + { + return Description; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs b/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs new file mode 100644 index 0000000000..0e2bc23947 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs @@ -0,0 +1,400 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// this class is used to detect the periodicity automatically + /// + internal class SerialCorrelation + { + /// + /// the ratio of all potential lags. this value should not be greater than 0.2, otherwise, over-fit. + /// + private const double LagRatio = 0.25; + + /// + /// in practice, the max lag very rarely exceed 365, which lacks of strong interpretation, and which also brings performance overhead. + /// + private const int MaxLag = 400; + + /// + /// suppose the length of time series is 651, now we found an period is 128, then 651/128 = 5, which means there are at most 5 recurrent period. this is too small, the significance build upon this is not trustable. + /// + private const int MinRecurrentCount = 8; + + /// + /// when input time series is with very close values (i.e., different is smaller than E-20), the accuracy of double could distort the + /// final trend signal. any seasonal signal under such circumstance becomes unreliable. + /// so use this threshold to eliminate such kind of time series. here set to 1e-10 is for conservative consideration. + /// + private const double MinEnergyThreshold = 1e-10; + + /// + /// this is a slow version of calculating Acf, with complexity is O(n^2). + /// the assumptions needed for applying serial correlation is, the series should be generated by a second-order stationary process. + /// therefore, one should conduct de-trend process before running serial correlation. + /// + internal static List Acf(ICollection y) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + + if (y.Count <= BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: serial correlation"); + + List values = new List(y); + double mean = 0; + foreach (double value in values) + mean += value; + mean /= values.Count; + double var = 0; + for (int i = 0; i < values.Count; i++) + { + values[i] -= mean; + var += values[i] * values[i]; + } + + List energies = new List(); + int maxLag = Math.Min((int)(y.Count * SerialCorrelation.LagRatio), SerialCorrelation.MaxLag); + for (int lag = 0; lag <= maxLag; lag++) + { + double corr = 0; + for (int i = 0; i + lag < values.Count; i++) + { + corr += values[i] * values[i + lag]; + } + energies.Add(corr / var); + } + return energies; + } + + /// + /// Obtain the period by adopting techniques of spectral analysis. which is founded by + /// the fourier analysis. returns -1 means there's no significant period. otherwise, a period + /// is returned. + /// + /// the input time series + /// confidence + public static int Period(IReadOnlyList y, out double confidence) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + + if (y.Count < BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: serial correlation"); + + confidence = 0; + + List values = new List(y); + + double mean = 0; + foreach (double value in values) + mean += value; + mean /= values.Count; + for (int i = 0; i < values.Count; i++) + { + values[i] -= mean; + } + + // caution1: need to extend to power of 2 explicitly, by adding 0. it is clear that adding any number of 0 will not affect the Acf results. + int newLength = Get2Power(y.Count); + for (int i = y.Count; i < newLength; i++) + { + values.Add(0); + } + + // caution2: need to double the length. this is used to make the "Summation lemma" works. you can check it out by proving the WK theorem. + Complex[] x = new Complex[values.Count * 2]; + for (int i = 0; i < values.Count; i++) + { + x[i] = new Complex(values[i], 0); + x[i + values.Count] = Complex.Zero; + } + + Complex[] z = FftTransform.Fft(x); + + // Y is the key data source for analyzing period patterns. + /* here w is the periodogram, which indicates the square of "energy" on the frequency domain. specifically, + * w[j] = a[j]^2+b[j]^2, where a and b are Fourier Coefficients for cosine and sine, specifically, + * x(t) = a0+sum(a[j]cos(2Pi * f[j]t)+b[j]sin(2Pi * f[j]t) + */ + Complex[] w = new Complex[x.Length]; + for (int i = 0; i < x.Length; i++) + { + w[i] = z[i] * Complex.Conjugate(z[i]); + } + + int bestFreq; + int secondFreq; + + FindBestTwoFrequencies(w, y.Count, out bestFreq, out secondFreq); + + // when the time series is very short, and there exist no seasonal, the loop above may not access + if (bestFreq == -1) + return -1; + + // next check if the period is statistical significant + /* r is an auto-correlation function of input y, but without normalization. specifically, + * r[j] = sum(x[i]x[i+j]). + */ + Complex[] r = FftTransform.RevertFft(w); + + /* pick the best frequency by inspecting the auto-correlation energy (pick the highest) in time-domain. + * in the normal case, usually, when the time series is with period T, then the best frequency is N/T, while the second frequency would be N/2T, + * because period = T implies period = nT, where n is an integer. + * in such a case, smaller period will win out on the autu-correlation energy list, due to the property of auto-correlation. + */ + int firstPeriod = -1; + int secondPeriod = -1; + double firstTimeDomainEnergy = -1; + double secondTimeDomainEnergy = -1; + firstPeriod = FindBestPeriod(r, bestFreq, y.Count, out firstTimeDomainEnergy); + if (secondFreq != -1) + { + secondPeriod = FindBestPeriod(r, secondFreq, y.Count, out secondTimeDomainEnergy); + } + if (firstPeriod == -1 && secondPeriod == -1) + return -1; + int truePeriod; + double trueTimeDomainEnergy; + if (firstPeriod == -1) + { + truePeriod = secondPeriod; + trueTimeDomainEnergy = secondTimeDomainEnergy; + } + else if (secondPeriod == -1) + { + truePeriod = firstPeriod; + trueTimeDomainEnergy = firstTimeDomainEnergy; + } + else + { + if (firstPeriod == secondPeriod) + { + truePeriod = firstPeriod; + trueTimeDomainEnergy = firstTimeDomainEnergy; + } + else + { + // hueristic: if the second frequency is with somewhat higher energy in time domain, we think it is a better candidate + if (secondTimeDomainEnergy > firstTimeDomainEnergy * 1.05) + { + truePeriod = secondPeriod; + trueTimeDomainEnergy = secondTimeDomainEnergy; + } + else + { + truePeriod = firstPeriod; + trueTimeDomainEnergy = firstTimeDomainEnergy; + } + } + } + trueTimeDomainEnergy /= r[0].Real; + + // this is a key equation, which is named the "testing for randomness with the correlogram". /ref: http://www.ltrr.arizona.edu/~dmeko/notes_3.pdf + // actually, 1.96 is for the 2-sigma, which has 95% statistical confidence. 2.58 is for 99% confidence, 2.85 for 99.5% confidence + /* increasing the threshold aims to mitigate the fake seasonal component caused by outliers. in practice, if there exist true seasonal component, + * such as BirdStrike/Appdownloads, the energy is far larger than threshold, hence change threshold from 2.85 to 4.0 have no impact (tested); + */ + double threshold = 4.0 / Math.Sqrt(y.Count); + + if (trueTimeDomainEnergy < threshold || r[truePeriod].Real < MinEnergyThreshold) + return -1; + + confidence = MathUtility.GaussianSignificance(trueTimeDomainEnergy, 0, 1.0 / Math.Sqrt(y.Count)); + return truePeriod; + } + + /// + /// in order to pick up a proper frequency robustly (this is useful especially for large frequency, or small period, e.g., period = 2), + /// this method aims to pick up the top two frequencies for further evaluation. + /// of course, the energy of the second frequency (in frequency domain) must be at similar magnitude compared with the energy of the first + /// frequency. + /// + /// the energy list in the frequency domain, the index is the frequency. + /// the original time series length + /// the frequency with highest energy + /// the frequency with second highest energy + private static void FindBestTwoFrequencies(Complex[] w, int timeSeriesLength, out int bestFreq, out int secondFreq) + { + bestFreq = -1; + double bestEnergy = -1.0; + secondFreq = -1; + double secondEnergy = -1.0; + + if (w.Length < 2) + return; + + List energies = new List(); + + /* length of time series divided by frequency is period. it is obvious that the period should be larger than 1 and smaller than the total length, and is an integer */ + for (int i = w.Length / timeSeriesLength; i < w.Length / 2 + 1; i++) + { + double nextWeight = w[i].Magnitude; + energies.Add(nextWeight); + + if (nextWeight > bestEnergy) + { + bestEnergy = nextWeight; + bestFreq = i; + } + } + + // once we found a best frequency, the region formed by lower bound to upper bound corresponding to this frequency will not be inspected anymore. because they all share the same period. + int period = w.Length / bestFreq; + double lowerBound = w.Length * 1.0 / (period + 1); + double upperBound = w.Length * 1.0 / (period - 1); + + for (int i = w.Length / timeSeriesLength; i < w.Length / 2 + 1; i++) + { + if (i > lowerBound && i < upperBound) + continue; + double weight = w[i].Magnitude; + if (weight > secondEnergy) + { + double prevWeight = 0; + if (i > 0) + prevWeight = w[i - 1].Magnitude; + double nextWeight = 0; + if (i < w.Length - 1) + nextWeight = w[i + 1].Magnitude; + + // should be a local maximum + if (weight >= prevWeight && weight >= nextWeight) + { + secondEnergy = nextWeight; + secondFreq = i; + } + } + } + double typycalEnergy = MathUtility.QuickMedian(energies); + + // the second energy must be at least significantly large enough than typical energies, and also similar to best energy at magnitude level. + if (typycalEnergy * 6.0 < secondEnergy && secondEnergy * 10.0 > bestEnergy) + return; + + // set the second frequency to -1, since it is obviously not strong enought to compete with the best energy. + secondFreq = -1; + } + + /// + /// given a frequency F represented by an integer, we aim to find the best period by inspecting the auto-correlation function in time domain. + /// since either frequency or the period is an integer, so the possible period located within + /// [N/(F+1), N/(F-1)], we need to check this domain, and pick the best one. where N is the length of the augmented time series + /// + /// the auto-correlation function of the augmented time series + /// the input frequency candidate + /// the length of the original time series, this is used for post processing to reduce false positive + /// output the energy on the auto-correlation function + /// return the best period estimated + private static int FindBestPeriod(Complex[] r, int frequency, int timeSeriesLength, out double energy) + { + energy = -1; + + // this will never make sense of a seasonal signal + if (frequency <= 1) + return -1; + + int lowerBound = r.Length / (frequency + 1); + int upperBound = r.Length / (frequency - 1); + int bestPeriod = -1; + for (int i = lowerBound; i <= upperBound && i < r.Length; i++) + { + var currentEnergy = r[i].Real; + if (currentEnergy > energy) + { + energy = currentEnergy; + bestPeriod = i; + } + } + + /* condition1: period does not make sense, since the corresponding zone in the auto-correlation energy list are all negative. + * condition2: for real dataset, we do not think there will exist such long period. this is used to reduce false-positive + * condition3: the number of repeats under this period is too few. this is used to reduce false-positive + */ + if (bestPeriod <= 1 || bestPeriod > SerialCorrelation.MaxLag || timeSeriesLength < SerialCorrelation.MinRecurrentCount * bestPeriod) + { + energy = -1; + return -1; + } + return bestPeriod; + } + + /// + /// this is a fast version of calculating Acf, by using FftTransform. the complexity is O(n log n). Basically + /// 1) Z = FftTransform (X) + /// 2) Y = ZZ* + /// 3) R = IFFT(Y) + /// the series R is just the Acf. + /// + public static List FastAcf(ICollection y) + { + //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + + if (y.Count <= BasicParameters.MinTimeSeriesLength) + throw new Exception("input data structure cannot be 0-length: serial correlation"); + + List values = new List(y); + + double mean = 0; + foreach (double value in values) + mean += value; + mean /= values.Count; + for (int i = 0; i < values.Count; i++) + { + values[i] -= mean; + } + + // caution1: need to extend to power of 2 explicitly, by adding 0. it is clear that adding any number of 0 will not affect the Acf results. + int newLength = Get2Power(y.Count); + for (int i = y.Count; i < newLength; i++) + { + values.Add(0); + } + + // caution2: need to double the length. this is used to make the "Summation lemma" works. you can check it out by proving the WK theorem. + Complex[] x = new Complex[values.Count * 2]; + for (int i = 0; i < values.Count; i++) + { + x[i] = new Complex(values[i], 0); + x[i + values.Count] = Complex.Zero; + } + Complex[] z = FftTransform.Fft(x); + Complex[] w = new Complex[x.Length]; + for (int i = 0; i < x.Length; i++) + { + w[i] = z[i] * Complex.Conjugate(z[i]); + } + + Complex[] r = FftTransform.RevertFft(w); + double var = r[0].Real; + List energies = new List(); + int maxLag = Math.Min((int)(y.Count * SerialCorrelation.LagRatio), SerialCorrelation.MaxLag); + for (int lag = 0; lag <= maxLag; lag++) + { + energies.Add(r[lag].Real / var); + } + return energies; + } + + /// + /// get the smallest 2^k which is equal or greater than n + /// + private static int Get2Power(int n) + { + int result = 1; + bool meet1 = false; // check is n is just equals to 2^k for some k + while (n > 1) + { + if ((n & 1) != 0) + meet1 = true; + result = result << 1; + n = n >> 1; + } + if (meet1) + result = result << 1; + return result; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs index 566b5b6cd2..c18a309539 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnAnomalyDetectionBase.cs @@ -174,13 +174,13 @@ private protected override sealed void SpectralResidual(Single input, FixedSizeQ // Step 1: Get backadd wave List backAddList = BackAdd(data); - // Step 2: FFT transformation + // Step 2: FftTransform 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 + // Step 3: Calculate mags of FftTransform List magList = new List(); for (int i = 0; i < length; ++i) { diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index e146ff1ebf..28e4de8991 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; using Microsoft.ML.Runtime; @@ -337,6 +338,8 @@ internal sealed class SrCnnEntireModeler private double[] _trends; private double[] _curWindow; + private int _period; + public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode) { _threshold = threshold; @@ -359,11 +362,53 @@ public void Train(double[] values, ref double[][] results) { Array.Resize(ref results, values.Length); } - SpectralResidual(values, results, _threshold); + + Cyclic seasonalDetector = new Cyclic(values); + int minPeriodRepeatCount = 4; + int rawPeriod = seasonalDetector.DetectCyclic(out double seasonalConfidence); + + if (rawPeriod != -1 && rawPeriod * minPeriodRepeatCount <= values.Length) + { + _period = rawPeriod; + } + + bool isTemporal = true; + double[] seriesToDetect = values.ToArray(); + double[] trends = new double[values.Length]; + double[] seasonal = new double[values.Length]; + double[] loss = new double[values.Length]; + + if (_period > 0) + { + StlConfiguration config = new StlConfiguration(_period); + InnerStl stl = new InnerStl(values, config, isTemporal); + bool success = stl.Decomposition(); + + if (success) + { + seriesToDetect = stl.Residual.ToArray(); + //trends = stl.TrendComponent.ToArray(); + //seasonal = stl.SeasonalComponent.ToArray(); + } + } + + SpectralResidual(seriesToDetect, results, _threshold); //Optional Steps if (_detectMode == SrCnnDetectMode.AnomalyAndMargin) { - GetMargin(values, results, _sensitivity); + if (_period > 0) + { + GetMarginPeriod(values, results, seriesToDetect, _sensitivity); + //for (int i=0; i residual, double sensitivity) + { + //Step 8: Calculated Expected Value + for (int i = 0; i < values.Length; ++i) + { + results[i][3] = values[i] - residual[i]; + } + + //Step 9: Calculate Boundary Unit + CalculateBoundaryUnit(values, results.Select(x => x[0] > 0).ToArray()); + + for (int i = 0; i < results.Length; ++i) + { + //Step 10: Calculate UpperBound and LowerBound + var margin = CalculateMargin(_units[i], sensitivity); + results[i][4] = _units[i]; + results[i][5] = results[i][3] + margin; + results[i][6] = results[i][3] - margin; + + // update anomaly result according to the boundary + results[i][0] = results[i][0] > 0 && (values[i] < results[i][6] || results[i][5] < values[i]) ? 1 : 0; + } + + List> segments = new List>(); + int start = -1; + int cursor = -1; + for(int i = 0; i < values.Length; ++i) + { + // this is a outlier + if (results[i][6] > values[i] || values[i] > results[i][5]) + { + if (cursor + 1 == i) + { + cursor = i; + } + else + { + if (start > -1) + { + segments.Add(new Tuple(start, cursor)); + } + start = i; + cursor = i; + } + } + } + + if (start > -1) + { + segments.Add(new Tuple(start, Math.Max(start, cursor))); + } + + List anomalyIndex = new List(); + for (int i = 0; i < values.Length; ++i) + { + if(results[i][0] > 0) + { + anomalyIndex.Add(i); + } + } + + // more than one anomaly, update anomaly results + if (anomalyIndex.Count > 1) + { + cursor = 0; + for(int i = 0; i < anomalyIndex.Count - 1; ++i) + { + while (cursor < segments.Count && anomalyIndex[i] >= segments[cursor].Item2) + { + ++cursor; + } + + if (cursor < segments.Count && segments[cursor].Item1 <= anomalyIndex[i] && anomalyIndex[i+1] <= segments[cursor].Item2) + { + for (int j = anomalyIndex[i]; j < anomalyIndex[i+1]; ++j) + { + results[j][0] = 1; + } + } + } + } + + //Step 11: Update Anomaly Score + for (int i = 0; i < results.Length; ++i) + { + results[i][1] = CalculateAnomalyScore(values[i], _ifftRe[i], _units[i], results[i][0] > 0); + } + + } + private void GetMargin(double[] values, double[][] results, double sensitivity) { //Step 8: Calculate Expected Value @@ -558,8 +693,12 @@ private void GetMargin(double[] values, double[][] results, double sensitivity) results[i][4] = _units[i]; results[i][5] = _ifftRe[i] + margin; results[i][6] = _ifftRe[i] - margin; + //Step 11: Update Anomaly Score results[i][1] = CalculateAnomalyScore(values[i], _ifftRe[i], _units[i], results[i][0] > 0); + + // update anomaly result according to the boundary + results[i][0] = results[i][0] > 0 && (values[i] < results[i][6] || results[i][5] < values[i]) ? 1 : 0; } } diff --git a/src/Microsoft.ML.TimeSeries/StlConfiguration.cs b/src/Microsoft.ML.TimeSeries/StlConfiguration.cs new file mode 100644 index 0000000000..530384f673 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/StlConfiguration.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + internal class StlConfiguration + { + /// + /// the smoothing parameter for the seasonal component. + /// should be odd, and at least 7. + /// + public const int Ns = 9; + + /// + /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases + /// + public const int Ni = 2; + + /// + /// the number of robustness iterations of the outer loop + /// + public const int No = 10; + + public StlConfiguration() + { + Np = -1; + } + + public StlConfiguration(int np) + { + Np = np; + } + + /// + /// the number of observations in each cycle of the seasonal component + /// + public int Np { get; } + + /// + /// the smoothing parameter for the low-pass filter. + /// /ref: should be the least odd integer greater than or equal to np. + /// it will preventing the trend and seasonal components from competing for the same variation in the data. + /// + public int Nl + { + get + { + if (Np % 2 == 0) + return Np + 1; + return Np; + } + } + + /// + /// the smoothing parameter for the trend component. + /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen + /// s.t., satisty the following inequality. + /// + public int Nt + { + get + { + double value = 1.5 * Np / (1.0 - 1.5 / StlConfiguration.Ns); + int result = (int)value + 1; + if (result % 2 == 0) + result++; + return result; + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs b/src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs new file mode 100644 index 0000000000..8916b6aa7e --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// the outliers of time series. A default rank is available when this instance is constructed. + /// + public class TemporalOutlierInfo : SingleSeriesInfo + { + /// + /// Initializes a new instance of the class. + /// the corresponding properties of outlier component + /// + /// x axis values + /// y axis values + /// the residual of the curve. which is the data source for outlier identification + /// the indication for outliers + /// severity + /// confidence + public TemporalOutlierInfo( + IReadOnlyList x, + IReadOnlyList y, + IReadOnlyList residual, + IReadOnlyList index, + IReadOnlyList severity, + double confidence) + { + X = x; + Y = y; + Residual = residual; + OutlierIndex = index; + OutlierSeverity = severity; + Rank = confidence; + + Count = 0; + foreach (int indicator in index) + { + if (indicator == 1) + Count++; + } + + Description = String.Format("there exists {0} outliers. Rank:{1}", Count, Rank); + + Kind = TimeSeriesInfoKind.Outlier; + } + + /// + /// 0/1 value for each data point, to indicate whether it is an outlier or not. + /// 1 means outlier, 0 means not. + /// + public IReadOnlyList OutlierIndex { get; private set; } + + /// + /// equal length with OutlierIndex, indicate the severity of each outlier. 0 for non-outlier epochs. + /// + public IReadOnlyList OutlierSeverity { get; private set; } + + /// + /// the residual values, which is the signal after decompose trend and seasonal signal. + /// + public IReadOnlyList Residual { get; private set; } + + /// + /// total count of outliers + /// + public int Count { get; private set; } + + public override string Description + { + get; + protected set; + } + + public override double Rank + { + get; + set; + } + + public override TimeSeriesInfoKind Kind + { + get; + protected set; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs b/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs index 62d81cf6ec..4667ffb455 100644 --- a/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs +++ b/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs @@ -298,11 +298,11 @@ private void FftMultiply(Single[] vector, Single[] result, bool add = false, int int i; - // Computing the FFT of the trajectory matrix + // Computing the FftTransform of the trajectory matrix if (!_isSeriesFftCached) CacheInputSeriesFft(); - // Computing the FFT of the input vector + // Computing the FftTransform of the input vector for (i = 0; i < _k; ++i) _inputRe[i] = vector[_k - i - 1 + srcIndex]; @@ -323,7 +323,7 @@ private void FftMultiply(Single[] vector, Single[] result, bool add = false, int _outputIm[i] = _cachedSeriesFftRe[i] * im + _cachedSeriesFftIm[i] * re; } - // Computing the inverse FFT of the result + // Computing the inverse FftTransform of the result FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, _inputRe.Length); // Generating the output @@ -402,11 +402,11 @@ private void FftMultiplyTranspose(Single[] vector, Single[] result, bool add = f int i; - // Computing the FFT of the trajectory matrix + // Computing the FftTransform of the trajectory matrix if (!_isSeriesFftCached) CacheInputSeriesFft(); - // Computing the FFT of the input vector + // Computing the FftTransform of the input vector for (i = 0; i < _k - 1; ++i) _inputRe[i] = 0; @@ -427,7 +427,7 @@ private void FftMultiplyTranspose(Single[] vector, Single[] result, bool add = f _outputIm[i] = _cachedSeriesFftRe[i] * im + _cachedSeriesFftIm[i] * re; } - // Computing the inverse FFT of the result + // Computing the inverse FftTransform of the result FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, _inputRe.Length); // Generating the output @@ -578,7 +578,7 @@ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Singl if (!_isSeriesFftCached) CacheInputSeriesFft(); - // Computing the FFT of u + // Computing the FftTransform of u for (i = us; i <= ue; ++i) _inputRe[i - us] = u[i + uIndex]; @@ -587,7 +587,7 @@ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Singl FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _outputRe, _outputIm, len); - // Computing the FFT of v + // Computing the FftTransform of v for (i = vs; i <= ve; ++i) _inputRe[i - vs] = v[i + vIndex]; @@ -612,7 +612,7 @@ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Singl for (i = 0; i < _seriesLength; ++i) _allZerosIm[i] = 0; - // Computing the inverse FFT of the result + // Computing the inverse FftTransform of the result FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, len); // Generating the output diff --git a/src/Microsoft.ML.TimeSeries/TrendInfo.cs b/src/Microsoft.ML.TimeSeries/TrendInfo.cs new file mode 100644 index 0000000000..f9d137e4ba --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/TrendInfo.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// the trend component of time series. and corresponding mathematical properties of the trend. A default rank is available when this instance is constructed. + /// + public class TrendInfo : SingleSeriesInfo + { + private const double _slopeThreshold = 0.25; + private const double _msrThreshold = 0.1; + + /// + /// Initializes a new instance of the class. + /// insight item for trend component + /// + /// x-axis values of original curve + /// y-axis values of original curve + /// the curve of trend + /// mean residual squares. which measure the quality of regression model fitting to original curve. + public TrendInfo(IReadOnlyList x, IReadOnlyList y, IReadOnlyList trend, double mrs) + { + X = x; + Y = y; + Trend = trend; + + int length = Trend.Count; + + // double slope = (this.Trend[length - 1] - this.Trend[0]) / (length - 1); + double increase = Trend[length - 1] - Trend[0]; + + // get relative slope, which is unit-invariant + double y0 = Y[0]; + if (Math.Abs(y0) < 0.01) + y0 = 0.01; + + // this is so called relative slope. + Slope = increase / y0; + IsIncrease = Slope > 0; + + // get relative mrs, which is unit-invariant. now the mrs is caclulated directly from original signal rather than trend. which is compliant with user perception. + double average = 0; + foreach (double value in Y) + average += value; + average /= Y.Count; + if (Math.Abs(average) < 0.01) + average = 0.01; + + // conducted normalization + Mrs = mrs / average / average; + IncreaseRatio = CalculateIncreasingRatio(Trend); + + // 0.15 is a magic number + double slopeSignificance = MathUtility.Sigmoid(50 * (Math.Abs(Slope) - 0.15)); + + // the lower the variance, the better the trend. 0.10 is a magic number. + double mesSignificance = 1 - MathUtility.Sigmoid(50 * (mrs - 0.10)); + double consistencySignificance = 1.0; + if (IsIncrease) + { + if (IncreaseRatio <= 0.5) + consistencySignificance = 0; + + // 0.667 is a magic number + consistencySignificance = MathUtility.Sigmoid(50 * (IncreaseRatio - 0.667)); + } + else + { + if (IncreaseRatio >= 0.5) + consistencySignificance = 0; + + // 0.667 is a magic number + consistencySignificance = MathUtility.Sigmoid(50 * (1.0 - IncreaseRatio - 0.667)); + } + Rank = slopeSignificance * mesSignificance * consistencySignificance; + + Description = String.Format("this is a trend, with slope = {0}, Mrs:{1}. rank:{2}", Slope, Mrs, Rank); + + Kind = TimeSeriesInfoKind.Trend; + } + + /// + /// the curve of trend + /// + public IReadOnlyList Trend { get; private set; } + + /// + /// the estimated relative slope of the trend. + /// + public double Slope { get; private set; } + + /// + /// relative mean residual squares. which measure the quality of regression model fitting to original curve. + /// this is already normalized + /// + public double Mrs { get; private set; } + + /// + /// indicate whether the curve is overall increasing or not. + /// this is directly derived from "Slope". if slope is positive, this is true. + /// + public bool IsIncrease { get; set; } + + /// + /// a percentage to indicate in the trend, what's the ratio of local increasing, i.e., delta is positive means increasing. + /// + public double IncreaseRatio { get; set; } + + public override string Description + { + get; + protected set; + } + + public override double Rank + { + get; + set; + } + + public override TimeSeriesInfoKind Kind + { + get; + protected set; + } + + public static double CalcSignificance( + IReadOnlyList trend, + IReadOnlyList rawSignal, + double mrs, + out double slope, + out double increaseRatio, + out double decreaseRatio) + { + // we must assume the length of x, and y are same. + int length = trend.Count; + + // calculate the slope. which is unit-invariant + double increase = trend[length - 1] - trend[0]; + + // let's try to use this value + double y0 = rawSignal[0]; + if (Math.Abs(y0) <= 0.01) + y0 = 0.01; + slope = increase / y0; + bool isIncrease = slope > 0; + + // when the time series is short, we should apply the consistency checking on the original signal, so that the result will be compliant with user perception. + if (trend.Count < TuningParams.ShortTimeseriesLength) + { + CalculateRatio(rawSignal, out increaseRatio, out decreaseRatio); + } + else + { + CalculateRatio(trend, out increaseRatio, out decreaseRatio); + } + + double slopeSignificance = MathUtility.Sigmoid(50 * (Math.Abs(slope) - _slopeThreshold)); + + double mesSignificance = 1 - MathUtility.Sigmoid(50 * (mrs - _msrThreshold)); + double consistencySignificance = 1.0; + double consistencyThreshold = ConsistencyThreshold(trend.Count); + if (isIncrease) + { + if (increaseRatio <= 0.5) + consistencySignificance = 0; + else + consistencySignificance = MathUtility.Sigmoid(50 * (increaseRatio - consistencyThreshold)); + } + else + { + if (decreaseRatio <= 0.5) + consistencySignificance = 0; + else + consistencySignificance = MathUtility.Sigmoid(50 * (decreaseRatio - consistencyThreshold)); + } + return slopeSignificance * mesSignificance * consistencySignificance; + } + + /// + /// calculate the increasing ratio. total number of positive deltas divided by total deltas + /// + private static double CalculateIncreasingRatio(IReadOnlyList trend) + { + if (trend.Count <= 1) + return 0; + int count = 0; + for (int i = 0; i < trend.Count - 1; i++) + { + if (trend[i + 1] - trend[i] > 0) + count++; + } + return count * 1.0 / (trend.Count - 1); + } + + /// + /// calculate the increasing/decreasing ratio. + /// + private static void CalculateRatio(IReadOnlyList signal, out double increaseRatio, out double decreaseRatio) + { + increaseRatio = 0; + decreaseRatio = 0; + if (signal.Count <= 1) + return; + int increaseCount = 0; + int decreaseCount = 0; + for (int i = 0; i < signal.Count - 1; i++) + { + if (signal[i + 1] - signal[i] > 0) + increaseCount++; + else if (signal[i + 1] - signal[i] < 0) + decreaseCount++; + } + increaseRatio = increaseCount * 1.0 / (signal.Count - 1); + decreaseRatio = decreaseCount * 1.0 / (signal.Count - 1); + } + + private static double ConsistencyThreshold(int length) + { + if (length < TuningParams.ShortTimeseriesLength) + return 0.8; + else + return 0.7; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/TuningParams.cs b/src/Microsoft.ML.TimeSeries/TuningParams.cs new file mode 100644 index 0000000000..d6fb9ed7c7 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/TuningParams.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + public class TuningParams + { + public const int ShortTimeseriesLength = 40; + private const double MagnitudeLower = 0.7; + private const double MagnitudeUpper = 1.3; + + /// + /// dynamically identify a set of outlier categorization params by checking the input time series length + /// + /// the input time series length + /// when we determine if an outlier is dense or not, we use the outliers in its neighbors. this value indicates the window size of the neighbors + /// the threshold of the total outlier count in the window size + /// finally, we glue the outliers near each other into a group. this is the threshold to determine "nearby" + public static void ParamsForCategorizeOutliers( + int length, + out int outlierConnectWindowSize, + out int denseOutlierThreshold, + out int glueGapThreshold) + { + // for typical long time series, we use a set of parameters which are propotional to the entire time series length + if (length > ShortTimeseriesLength) + { + int windowSize = ChangeDetectionUtility.GetWindowSize(length); + + // at least 5% of data points are outliers, which forms reasonable change region. this is one-side window size. + outlierConnectWindowSize = Math.Max(1, (int)(windowSize * 0.05)); + + // the total length is 2 times of outlierConnectWindowSize plus the checking point itself + int checkSize = (2 * outlierConnectWindowSize) + 1; + + // in the checkSize region, at least 70% points are outliers, then these points formed a dense outlier region + denseOutlierThreshold = (int)(checkSize * 0.7); + + // the maximum gap between two outliers that we accept gluing them together. + glueGapThreshold = Math.Max(1, checkSize - denseOutlierThreshold); + } + else + { + outlierConnectWindowSize = 0; + denseOutlierThreshold = 1; + glueGapThreshold = 1; + } + } + + /// + /// especially for short time series, we don't necessarily output two many outliers. + /// since there usually exist only few outliers for short time series. + /// + public static int OutputOutlierMaxCount(int length) + { + /*all these numbers are tunable magic numbers*/ + return 5; + } + + /// + /// check whether two positive numbers are with similar magnitude or not. + /// + /// the first positive number + /// the second positive number + /// return true if they are with similar magnitude. otherwise, return false + public static bool IsSimilarMagnitude(double absValue1, double absValue2) + { + if (absValue1 > absValue2) + { + // using smaller or equal to handle when both input values are 0 + return absValue1 <= MagnitudeUpper * absValue2; + } + else + { + // using smaller or equal to handle when both input values are 0 + return absValue1 >= MagnitudeLower * absValue2; + } + } + } + internal class ChangeDetectionUtility + { + /// + /// outlier detection for change points. we use this value instead of 6 (recommended by paper) to reduce false-negative + /// + internal const double Severity = 3.5; + + /// + /// according to the theory, the window size A should be + /// lim A/n ->0, lim (logn)^2/A -> 0 + /// + private const double Power = 0.6; + + /// + /// calculate the proper window size for change point detection, given the total time series length. + /// stay tuned + /// + /// the total length of time series + public static int GetWindowSize(int n) + { + // at least 0.1n as the window size, make sure at most 5 change points. + double value = Math.Max(Math.Pow(n, Power), n * 0.1); + return (int)value; + } + } +} From 468d444c9d69ad83928177374f3898a16921b3d5 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 11:16:54 +0800 Subject: [PATCH 02/49] optimizations in performance --- src/Microsoft.ML.TimeSeries/InnerStl.cs | 27 ++++ src/Microsoft.ML.TimeSeries/LeastSquares.cs | 1 + src/Microsoft.ML.TimeSeries/MovingAverage.cs | 1 + .../SerialCorrelation.cs | 129 +++++++++++++++++- .../SrCnnEntireAnomalyDetector.cs | 34 ++++- 5 files changed, 185 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/InnerStl.cs b/src/Microsoft.ML.TimeSeries/InnerStl.cs index 344ef3b528..b39a2c4866 100644 --- a/src/Microsoft.ML.TimeSeries/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/InnerStl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace Microsoft.ML.TimeSeries @@ -259,6 +260,32 @@ public bool Decomposition() return true; } + public bool DecompositionSimple() + { + if (_config.Np <= 0) + { + for (int i = 0; i < _y.Count; ++i) + { + _residual[i] = _y[i]; + } + } else + { + double[] sum = new double[_config.Np]; + for (int i = 0; i < _y.Count; i++) + { + var indexInPeriod = i % _config.Np; + sum[indexInPeriod] += _y[i]; + } + double[] averages = sum.Select((s, i) => s / (_y.Count / _config.Np)).ToArray(); + for (int i = 0; i < _y.Count; ++i) + { + _residual[i] = _y[i] - averages[i % _config.Np]; + } + } + + return true; + } + /// /// this class provides the virtual x values for multi object usage. /// the cache mechanism is used for performance consideration. diff --git a/src/Microsoft.ML.TimeSeries/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/LeastSquares.cs index beca9b33db..6f81adf146 100644 --- a/src/Microsoft.ML.TimeSeries/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/LeastSquares.cs @@ -526,6 +526,7 @@ public PolynomialModel(ICollection coeffs) /// the specific x value public double Y(double x) { + //return YOld(x); return YNew(x); } diff --git a/src/Microsoft.ML.TimeSeries/MovingAverage.cs b/src/Microsoft.ML.TimeSeries/MovingAverage.cs index cd468fd1bb..6710ea4ff5 100644 --- a/src/Microsoft.ML.TimeSeries/MovingAverage.cs +++ b/src/Microsoft.ML.TimeSeries/MovingAverage.cs @@ -29,6 +29,7 @@ public static List MA(IReadOnlyList s, int length) //} return MaFast(s, length); + //return MaOld(s, length); } public static List MaOld(IReadOnlyList s, int length) diff --git a/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs b/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs index 0e2bc23947..c0830e4c7f 100644 --- a/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs +++ b/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs @@ -1,15 +1,22 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using System.Text; +using Microsoft.ML.Transforms.TimeSeries; namespace Microsoft.ML.TimeSeries { /// /// this class is used to detect the periodicity automatically /// - internal class SerialCorrelation + public class SerialCorrelation { + /// + /// the minimum period allowed. + /// + private const int MinPeriod = 4; + /// /// the ratio of all potential lags. this value should not be greater than 0.2, otherwise, over-fit. /// @@ -70,6 +77,126 @@ internal static List Acf(ICollection y) return energies; } + public static int DetectSeasonality(double[] y) + { + //Cyclic seasonalDetector = new Cyclic(Y); + //var period = seasonalDetector.DetectCyclic(out var seasonalConfidence); + // simulate SerialCorrelation.Period + //var watch = Stopwatch.StartNew(); + int length = y.Length; + + double mean = 0; + double std = 0; + foreach (double value in y) + mean += value; + mean /= length; + + int newLength = Get2Power(y.Length); + double[] fftRe = new double[newLength]; + double[] fftIm = new double[newLength]; + double[] inputRe = new double[newLength]; + for (int i = 0; i < length; ++i) + { + inputRe[i] = y[i] - mean; + std = std + inputRe[i] * inputRe[i]; + } + if (std / length < 1e-8) + { + return -1; + } + + for (int i = length; i < newLength; ++i) + { + inputRe[i] = 0; + } + + FftUtils.ComputeForwardFft(inputRe, Enumerable.Repeat(0.0, newLength).ToArray(), fftRe, fftIm, newLength); + + var z = fftRe.Select((m, i) => new Complex(m, fftIm[i])).ToArray(); + var w = z.Select((t, i) => t * Complex.Conjugate(t)).ToArray(); + SerialCorrelation.FindBestTwoFrequencies(w, length, out var bestFreq, out var secondFreq); + + double[] ifftRe = new double[newLength]; + double[] ifftIm = new double[newLength]; + FftUtils.ComputeBackwardFft( + w.Select(t => (double)t.Real).ToArray(), + w.Select(t => (double)t.Imaginary).ToArray(), ifftRe, ifftIm, newLength); + var r = ifftRe.Select((t, i) => new Complex(t, ifftIm[i])).ToArray(); + int period = FindTruePeriod(r, bestFreq, secondFreq, newLength); + + if (period < MinPeriod) + { + period = -1; + } + //watch.Stop(); + //Console.WriteLine(watch.ElapsedMilliseconds); + return period; + } + + // copy from SerialCorrelation.Period + private static int FindTruePeriod(Complex[] r, int bestFreq, int secondFreq, int timeSeriesLength) + { + int firstPeriod = -1; + int secondPeriod = -1; + double firstTimeDomainEnergy = -1; + double secondTimeDomainEnergy = -1; + firstPeriod = SerialCorrelation.FindBestPeriod(r, bestFreq, timeSeriesLength, out firstTimeDomainEnergy); + if (secondFreq != -1) + { + secondPeriod = SerialCorrelation.FindBestPeriod(r, secondFreq, timeSeriesLength, out secondTimeDomainEnergy); + } + if (firstPeriod == -1 && secondPeriod == -1) + return -1; + int truePeriod; + double trueTimeDomainEnergy; + if (firstPeriod == -1) + { + truePeriod = secondPeriod; + trueTimeDomainEnergy = secondTimeDomainEnergy; + } + else if (secondPeriod == -1) + { + truePeriod = firstPeriod; + trueTimeDomainEnergy = firstTimeDomainEnergy; + } + else + { + if (firstPeriod == secondPeriod) + { + truePeriod = firstPeriod; + trueTimeDomainEnergy = firstTimeDomainEnergy; + } + else + { + // hueristic: if the second frequency is with somewhat higher energy in time domain, we think it is a better candidate + if (secondTimeDomainEnergy > firstTimeDomainEnergy * 1.05) + { + truePeriod = secondPeriod; + trueTimeDomainEnergy = secondTimeDomainEnergy; + } + else + { + truePeriod = firstPeriod; + trueTimeDomainEnergy = firstTimeDomainEnergy; + } + } + } + trueTimeDomainEnergy /= r[0].Real; + + // this is a key equation, which is named the "testing for randomness with the correlogram". /ref: http://www.ltrr.arizona.edu/~dmeko/notes_3.pdf + // actually, 1.96 is for the 2-sigma, which has 95% statistical confidence. 2.58 is for 99% confidence, 2.85 for 99.5% confidence + /* increasing the threshold aims to mitigate the fake seasonal component caused by outliers. in practice, if there exist true seasonal component, + * such as BirdStrike/Appdownloads, the energy is far larger than threshold, hence change threshold from 2.85 to 4.0 have no impact (tested); + */ + + double threshold = 4 / Math.Sqrt(timeSeriesLength); + + if (trueTimeDomainEnergy < threshold || r[truePeriod].Real < MinEnergyThreshold) + return -1; + + return truePeriod; + } + /// /// Obtain the period by adopting techniques of spectral analysis. which is founded by /// the fourier analysis. returns -1 means there's no significant period. otherwise, a period diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 28e4de8991..e5316e5d46 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using Microsoft.ML.Data; @@ -363,9 +364,13 @@ public void Train(double[] values, ref double[][] results) Array.Resize(ref results, values.Length); } - Cyclic seasonalDetector = new Cyclic(values); int minPeriodRepeatCount = 4; - int rawPeriod = seasonalDetector.DetectCyclic(out double seasonalConfidence); + Cyclic seasonalDetector = new Cyclic(values); + int rawPeriod1 = seasonalDetector.DetectCyclic(out double seasonalConfidence); + int rawPeriod2 = SerialCorrelation.DetectSeasonality(values); + //Trace.Assert(rawPeriod == rawPeriod2); + int rawPeriod = rawPeriod2; + Console.WriteLine("{0}, {1}, {2}", rawPeriod1, rawPeriod2, seasonalConfidence); if (rawPeriod != -1 && rawPeriod * minPeriodRepeatCount <= values.Length) { @@ -374,15 +379,16 @@ public void Train(double[] values, ref double[][] results) bool isTemporal = true; double[] seriesToDetect = values.ToArray(); - double[] trends = new double[values.Length]; - double[] seasonal = new double[values.Length]; - double[] loss = new double[values.Length]; + //double[] trends = new double[values.Length]; + //double[] seasonal = new double[values.Length]; + //double[] loss = new double[values.Length]; if (_period > 0) { StlConfiguration config = new StlConfiguration(_period); InnerStl stl = new InnerStl(values, config, isTemporal); bool success = stl.Decomposition(); + //bool success = stl.DecompositionSimple(); if (success) { @@ -412,7 +418,14 @@ public void Train(double[] values, ref double[][] results) } else if (_detectMode == SrCnnDetectMode.AnomalyAndExpectedValue) { - GetExpectedValue(values, results); + if (_period > 0) + { + GetExpectedValuePeriod(values, results, seriesToDetect); + } + else + { + GetExpectedValue(values, results); + } } } @@ -586,6 +599,15 @@ private void GetExpectedValue(double[] values, double[][] results) } } + private void GetExpectedValuePeriod(double[] values, double[][] results, IReadOnlyList residual) + { + //Step 8: Calculate Expected Value + for (int i = 0; i < values.Length; ++i) + { + results[i][3] = values[i] - residual[i]; + } + } + private void GetMarginPeriod(double[] values, double[][] results, IReadOnlyList residual, double sensitivity) { //Step 8: Calculated Expected Value From 45b69716e0520ea2324fb1bf82d2db1f3821a347 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 16:11:10 +0800 Subject: [PATCH 03/49] Add parameter period in SrCnn interface, remove inner period detect logic --- .../ExtensionsCatalog.cs | 5 ++- .../SrCnnEntireAnomalyDetector.cs | 43 ++++++------------- .../TimeSeriesDirectApi.cs | 6 +++ 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 161f8bc27b..262d598045 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -167,6 +167,7 @@ public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog /// When set to AnomalyAndMargin, the output vector would be a 7-element Double vector of (IsAnomaly, AnomalyScore, Mag, ExpectedValue, BoundaryUnit, UpperBoundary, LowerBoundary). /// The RawScore is output by SR to determine whether a point is an anomaly or not, under AnomalyAndMargin mode, when a point is an anomaly, an AnomalyScore will be calculated according to sensitivity setting. /// Default value is AnomalyOnly. + /// The period of the data to be detected. If the data has a circular pattern, set this parameter to the length of one period, otherwise 0 which indicates that there is no circular pattern. Default value is 0. /// /// /// /// public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, - double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyOnly) - => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode); + double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyOnly, int period = 0) + => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode, period); /// /// Create , which localizes root causes using decision tree algorithm. diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index e5316e5d46..a17b0d7f9b 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -74,7 +74,7 @@ public enum SrCnnDetectMode /// ]]> /// /// - /// + /// internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase { private const int MinBatchSize = 12; @@ -87,6 +87,7 @@ internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase GetDependencies(Func predicate) } } - public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode) + public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode, int period) : base(env, nameof(SrCnnEntireAnomalyDetector), input) { Host.CheckValue(inputColumnName, nameof(inputColumnName)); _inputColumnName = inputColumnName; + Host.CheckUserArg(period >= 0, nameof(period), "Must be integer equal or greater than 0."); + _period = period; + Host.CheckUserArg(batchSize == -1 || batchSize >= MinBatchSize, nameof(batchSize), "BatchSize must be -1 or no less than 12."); + Host.CheckUserArg(batchSize >= 4 * period || batchSize == -1 || period == 0, nameof(batchSize), "BatchSize must be at least four times the length of one period."); _batchSize = batchSize; Host.CheckUserArg(threshold >= 0 && threshold <= 1, nameof(threshold), "Must be in [0,1]."); @@ -144,6 +149,7 @@ public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string || detectMode == SrCnnDetectMode.AnomalyAndMargin, nameof(detectMode), "Invalid detectMode"); Host.CheckUserArg(sensitivity >= 0 && sensitivity <= 100, nameof(sensitivity), "Must be in [0,100]."); + _outputLength = _outputLengthArray[(int)detectMode]; _threshold = threshold; _sensitivity = sensitivity; @@ -161,7 +167,7 @@ protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch curre return new[] { currentBatch.CreateGetter(input, _inputColumnName) }; } - protected override Batch CreateBatch(DataViewRowCursor input) => new Batch(_batchSize, _outputLength, _threshold, _sensitivity, _detectMode); + protected override Batch CreateBatch(DataViewRowCursor input) => new Batch(_batchSize, _outputLength, _threshold, _sensitivity, _detectMode, _period); protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) { @@ -204,7 +210,7 @@ internal sealed class Batch private double[][] _results; private int _bLen; - public Batch(int batchSize, int outputLength, double threshold, double sensitivity, SrCnnDetectMode detectMode) + public Batch(int batchSize, int outputLength, double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) { _batchSize = batchSize; _outputLength = outputLength; @@ -218,7 +224,7 @@ public Batch(int batchSize, int outputLength, double threshold, double sensitivi _previousBatch = new List(batchSize); _batch = new List(batchSize); } - _modeler = new SrCnnEntireModeler(threshold, sensitivity, detectMode); + _modeler = new SrCnnEntireModeler(threshold, sensitivity, detectMode, period); } public void AddValue(double value) @@ -341,11 +347,12 @@ internal sealed class SrCnnEntireModeler private int _period; - public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode) + public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) { _threshold = threshold; _sensitivity = sensitivity; _detectMode = detectMode; + _period = period; _predictArray = new double[_lookaheadWindowSize + 1]; } @@ -364,37 +371,18 @@ public void Train(double[] values, ref double[][] results) Array.Resize(ref results, values.Length); } - int minPeriodRepeatCount = 4; - Cyclic seasonalDetector = new Cyclic(values); - int rawPeriod1 = seasonalDetector.DetectCyclic(out double seasonalConfidence); - int rawPeriod2 = SerialCorrelation.DetectSeasonality(values); - //Trace.Assert(rawPeriod == rawPeriod2); - int rawPeriod = rawPeriod2; - Console.WriteLine("{0}, {1}, {2}", rawPeriod1, rawPeriod2, seasonalConfidence); - - if (rawPeriod != -1 && rawPeriod * minPeriodRepeatCount <= values.Length) - { - _period = rawPeriod; - } - bool isTemporal = true; double[] seriesToDetect = values.ToArray(); - //double[] trends = new double[values.Length]; - //double[] seasonal = new double[values.Length]; - //double[] loss = new double[values.Length]; if (_period > 0) { StlConfiguration config = new StlConfiguration(_period); InnerStl stl = new InnerStl(values, config, isTemporal); bool success = stl.Decomposition(); - //bool success = stl.DecompositionSimple(); if (success) { seriesToDetect = stl.Residual.ToArray(); - //trends = stl.TrendComponent.ToArray(); - //seasonal = stl.SeasonalComponent.ToArray(); } } @@ -405,11 +393,6 @@ public void Train(double[] values, ref double[][] results) if (_period > 0) { GetMarginPeriod(values, results, seriesToDetect, _sensitivity); - //for (int i=0; i Date: Wed, 3 Jun 2020 16:12:34 +0800 Subject: [PATCH 04/49] add periodical data --- test/data/Timeseries/period_anomaly.csv | 4033 ++++++++++++++++++++ test/data/Timeseries/period_no_anomaly.csv | 4033 ++++++++++++++++++++ 2 files changed, 8066 insertions(+) create mode 100644 test/data/Timeseries/period_anomaly.csv create mode 100644 test/data/Timeseries/period_no_anomaly.csv diff --git a/test/data/Timeseries/period_anomaly.csv b/test/data/Timeseries/period_anomaly.csv new file mode 100644 index 0000000000..52183ce307 --- /dev/null +++ b/test/data/Timeseries/period_anomaly.csv @@ -0,0 +1,4033 @@ +Value +18.09048623 +20.35984259 +21.10546985 +21.15158525 +18.13714054 +21.62566053 +21.67380669 +20.24556149 +19.9283328 +18.17946985 +20.24781657 +19.63751633 +21.09122563 +19.61553427 +20.73432333 +18.40563259 +18.53180908 +18.55884076 +18.182102 +20.16531544 +21.1758709 +20.44164481 +20.29981954 +20.72249571 +19.35858129 +19.44781629 +19.25708039 +20.84843573 +18.64656134 +18.05210112 +20.38478579 +21.76952546 +20.43763167 +21.13168661 +20.69116284 +19.98974661 +20.87356961 +21.2964165 +18.89892658 +19.34452192 +18.89490293 +19.80869182 +21.5402582 +19.3808284 +19.49786756 +21.44146894 +20.06149732 +20.141796 +21.44320481 +19.96210695 +20.72623014 +18.34257866 +18.71452685 +19.6175048 +19.97935627 +21.8629348 +21.88260653 +21.40478835 +19.26239534 +21.85611316 +20.75736346 +19.57176051 +20.80669967 +18.06609092 +18.58461873 +21.31874285 +20.58229455 +19.45677881 +20.00266656 +21.6205686 +20.52631244 +19.59259714 +18.48130663 +19.17498721 +21.91336093 +19.74945225 +19.88735786 +19.99439213 +21.53641609 +20.41571314 +20.77470815 +19.55765417 +18.11635047 +21.72389166 +21.79651072 +21.79661221 +21.77474835 +21.26096776 +20.7383842 +21.37803967 +20.40218496 +18.53747846 +19.56487266 +21.88517965 +19.40455125 +21.47131165 +18.37284588 +19.33673537 +19.4038133 +18.20641017 +18.47637732 +18.77266084 +21.35126236 +18.25360796 +19.19480361 +18.04070001 +20.78251098 +20.48121236 +69.97175876 +66.29160587 +64.03244237 +70.9963549 +72.23120307 +63.08498037 +67.82088022 +64.58253327 +63.48625331 +71.18682426 +68.51811832 +64.14012052 +74.65120017 +84.84246498 +83.01150684 +83.49677345 +76.97938775 +79.52148874 +75.68743533 +71.04608986 +76.0645957 +79.05858129 +74.29647908 +83.00019292 +73.97109693 +77.40682104 +73.55447171 +78.00645026 +72.19877339 +75.90262558 +77.9412935 +79.17274194 +80.21494689 +75.94145893 +79.88644939 +79.6632095 +76.9663782 +85.18086544 +80.63338172 +77.22353592 +87.05282729 +85.41917245 +87.6161393 +82.9760511 +77.95790461 +76.40197814 +82.5139498 +87.58783364 +82.0077547 +86.08517365 +77.24711114 +78.49241782 +86.47964374 +84.08968086 +87.68151651 +84.37597226 +79.15724902 +80.47776902 +86.3875099 +80.80694945 +76.08270584 +78.96612645 +80.25720329 +73.54398706 +83.89617558 +73.74612295 +78.47491644 +84.86988511 +79.47731069 +85.46814565 +77.46638259 +87.31798924 +75.35077153 +72.61152924 +85.95799948 +84.03443672 +75.71101456 +87.75502326 +80.03571372 +87.21620976 +80.07969157 +79.54922971 +79.18545641 +77.35726178 +80.53906932 +79.24072245 +72.58781066 +76.16681749 +75.40982001 +85.87880475 +81.581763 +84.21876791 +87.30904897 +82.30447034 +87.72159144 +76.98807701 +74.98024612 +78.45292808 +79.5327601 +86.32575154 +80.95901594 +76.67486655 +83.88211632 +80.09328024 +74.59408248 +81.22908541 +86.58227934 +85.48192905 +32.55557757 +31.21332632 +29.35665837 +31.805747 +33.61330195 +31.10574583 +30.30685386 +34.76082353 +34.05794994 +32.88495765 +29.6754077 +29.72741087 +20.62242264 +22.92100781 +20.67772019 +23.93496854 +24.60848186 +21.80123867 +22.1924397 +23.34826767 +24.35844413 +24.16553878 +20.41801027 +20.91744053 +21.52282987 +21.25370592 +18.97766313 +20.48845628 +21.63021647 +19.32812544 +22.00079389 +18.64897217 +21.63587081 +20.95439396 +19.72305629 +21.38493946 +21.23088455 +21.83992669 +19.56455991 +20.84363841 +20.50843529 +20.19730819 +21.66516184 +18.09913002 +19.22026292 +20.24984383 +18.80299623 +21.73529712 +20.61014723 +19.93064704 +21.27921548 +19.82255616 +20.25403727 +18.02829454 +19.4814864 +18.57297484 +20.77868312 +19.93522729 +19.22546897 +19.89040582 +18.84213655 +21.70451299 +18.21938798 +21.46578878 +20.04928388 +20.47971733 +19.21411088 +21.87098889 +19.13107629 +20.35942024 +21.97788576 +21.52851026 +20.1453193 +21.96149809 +20.80547945 +18.83480166 +19.62238484 +19.33797334 +19.37605483 +21.04182576 +20.33705171 +18.15237085 +21.50979393 +19.18718456 +21.98829951 +21.81676292 +21.76212287 +21.24451042 +18.88245333 +20.13053098 +18.89721051 +19.03870182 +20.44380708 +18.16333418 +21.87783427 +21.67469269 +19.52307029 +20.06168979 +19.86447768 +21.52879911 +20.75440751 +19.11244499 +19.78736502 +19.7158639 +21.83734067 +18.70124364 +18.35830535 +21.52247533 +21.58674396 +21.48695635 +20.04390766 +20.63897771 +20.25345219 +18.2777913 +20.38333959 +18.05445659 +19.29994964 +21.7291703 +19.21618108 +21.75930657 +19.00053583 +19.25824896 +20.51362999 +21.04650789 +20.00555107 +21.52846275 +18.16096169 +21.68877935 +18.7852312 +21.53695498 +19.60345272 +21.33102822 +20.6866622 +18.70376653 +21.67219028 +18.98206956 +18.97656047 +21.25671559 +18.42349302 +18.88814363 +20.01471029 +21.32190811 +18.5899359 +20.19067294 +20.84496237 +21.4423809 +21.74226224 +21.13855966 +20.61131359 +19.73448034 +18.44844062 +20.83894877 +21.97094922 +20.94704238 +18.06684438 +19.98503991 +20.45858653 +20.7586968 +20.87890334 +20.23243376 +18.89344133 +21.76880265 +20.78666587 +18.94736015 +19.28928733 +21.68771068 +21.54994075 +19.62058575 +18.15322889 +18.19398948 +19.13567316 +20.51648888 +20.33677854 +20.53531115 +20.86913189 +18.07612901 +18.42034238 +19.43793381 +18.61943999 +18.30128017 +69.91711579 +72.28857609 +71.43572254 +72.31174099 +70.255649 +71.20069638 +64.89619133 +74.70490929 +63.6910529 +64.11711669 +65.12933691 +74.46452105 +85.17823891 +82.176751 +79.60297393 +77.91137117 +84.90403553 +80.6360659 +84.50221159 +74.75286841 +75.02018478 +83.1053257 +72.16574837 +84.52080041 +79.63887214 +86.30168633 +76.92435251 +82.95461041 +79.24764594 +81.53668494 +79.58954013 +86.94900678 +82.57524425 +75.56497688 +86.03550177 +76.24424896 +77.85214677 +73.54561839 +86.29578496 +76.48589902 +76.05131067 +86.62013253 +84.78656368 +74.16815403 +84.20802103 +86.50459933 +73.16827208 +85.10119579 +80.73933874 +82.68730947 +79.86649686 +83.14488979 +72.52815485 +84.18309878 +72.51609113 +79.26576921 +74.39758018 +85.15674591 +77.00009009 +79.24647299 +78.85622658 +84.35598217 +79.69448424 +84.61927144 +73.17261997 +75.06190034 +80.82066842 +87.14922983 +73.19261213 +72.68585837 +74.50123457 +82.25450166 +78.38313418 +85.20802007 +73.15022817 +75.14190402 +76.7996852 +82.02689392 +82.9619666 +73.98654243 +87.10003918 +77.45596258 +83.40989947 +80.38655178 +75.56398473 +85.53948893 +85.09164507 +78.33699926 +81.41756505 +76.12506497 +83.03746663 +76.94428127 +85.50509558 +75.47801675 +72.23671607 +76.29598218 +75.05681096 +80.13255226 +75.25922377 +84.21100559 +75.86190108 +83.31145889 +81.25550357 +87.779964 +87.99838184 +85.91238434 +84.15970159 +78.1832726 +33.6209646 +34.92271673 +28.94961092 +32.24264789 +30.19518335 +32.37040221 +31.78525527 +32.48491268 +32.32615201 +34.94467834 +32.33398489 +34.21587879 +21.5927937 +22.12268987 +22.26745115 +21.40870046 +21.86145917 +22.84133734 +22.75923993 +23.01122908 +21.73614307 +21.57492391 +22.90004686 +20.75521949 +19.46841697 +18.60455499 +18.72941383 +19.42889768 +18.51199171 +19.43720155 +21.11478277 +18.68229437 +20.22087319 +22.17209333 +21.6731024 +19.68307177 +21.80337813 +19.41131515 +18.37785424 +22.08170965 +21.58319449 +19.95907699 +20.76470048 +20.78419984 +18.86806294 +18.89043542 +19.97373871 +21.75019471 +18.05729347 +21.15030736 +18.24760803 +19.93200482 +20.57122075 +20.76695421 +18.25142161 +19.6933019 +20.35487097 +18.28171576 +18.55817698 +20.67442436 +20.0904094 +18.65089493 +20.14482441 +21.65492644 +18.06581831 +20.57448463 +18.2981143 +18.02925872 +18.60618966 +19.69540732 +21.21344819 +19.1308144 +20.50269285 +19.64462655 +18.93732717 +20.05698846 +21.18985796 +21.32996948 +21.38607813 +19.35423125 +21.17382426 +18.84871382 +20.95370041 +21.79353771 +18.05497534 +19.76145572 +20.59108395 +20.80710308 +18.70435103 +21.8887331 +20.46106147 +18.6573624 +18.08209005 +21.1330568 +18.31881896 +18.56420368 +20.29932011 +20.41402201 +20.74754847 +19.57405605 +21.09559468 +21.8974344 +19.08437977 +20.81211447 +21.71387461 +20.74514705 +20.72443799 +18.89509028 +20.1405208 +20.25009137 +20.80051825 +21.9872858 +18.13457529 +18.26093065 +18.31180028 +20.97191654 +18.54541518 +20.4898075 +19.29867551 +19.82927982 +18.86265264 +21.46442625 +20.3446013 +21.77825305 +20.298385 +18.49563212 +18.68348174 +21.95629591 +20.90226079 +18.197115 +20.81512402 +19.64869219 +19.22883355 +18.61624317 +21.35961582 +18.25536552 +19.15934185 +19.22553158 +18.64362114 +20.03273953 +18.04681051 +19.20390206 +21.73451778 +19.24211828 +19.44183755 +19.89266019 +20.2467735 +21.23483458 +18.76128489 +21.48698551 +21.03019743 +20.89060134 +18.34721829 +18.08656276 +20.3760508 +21.45253585 +20.33776586 +18.85293946 +18.36207959 +20.79838581 +20.88708111 +18.6901268 +18.11276126 +19.63313405 +20.33087481 +20.82400964 +21.23978004 +20.44214971 +18.44352243 +20.73032669 +21.93670657 +21.62233834 +21.48667564 +19.20868061 +21.00315271 +21.40441378 +20.71330482 +19.00224703 +21.81533589 +19.80327765 +62.9653556 +62.69297937 +70.00625823 +67.15285119 +62.4849807 +71.26895818 +72.07082227 +71.06565258 +63.95074794 +70.5809462 +61.34583012 +65.3495251 +76.02971017 +82.80303265 +75.89892067 +70.0149018 +75.30841631 +77.58330201 +84.56855429 +84.44485239 +72.64716507 +72.71347433 +72.3756802 +82.08406839 +71.66377453 +73.59062616 +80.12370998 +75.50522711 +73.03683711 +77.29149991 +75.92899775 +79.69660289 +87.14173108 +73.54494094 +77.49584551 +86.39028972 +85.92673273 +78.21436655 +83.08343233 +73.31762536 +83.3005761 +87.4606448 +80.50309067 +79.87987262 +74.9071014 +82.04204245 +81.3492601 +82.33201184 +72.39995243 +80.76018872 +81.49939295 +75.30240525 +75.31524572 +76.40115002 +83.36753246 +81.68064895 +85.51167461 +76.71212182 +86.57230662 +83.87916276 +78.31642735 +73.52066389 +76.10808524 +87.30410068 +76.96211966 +77.274706 +75.02982129 +84.90553396 +76.40663999 +77.28042234 +78.62579552 +78.93586337 +78.58069826 +80.05913055 +85.03138976 +85.10774361 +84.77219632 +73.61729069 +84.79404682 +73.92331871 +73.99486996 +87.16496755 +85.10954846 +80.83779142 +85.99935815 +87.82966565 +81.77011607 +81.80621792 +84.07067105 +75.71407787 +74.67385441 +84.15313043 +77.51832788 +74.16098331 +77.0378957 +77.73804063 +87.44360897 +83.16732344 +80.49367017 +87.50193662 +86.45020265 +80.19198291 +86.49084095 +78.4688444 +75.68670158 +75.72282175 +82.59701667 +80.0170944 +33.01727908 +33.1908798 +28.94286083 +29.1458478 +32.20913127 +30.02262129 +32.84820981 +32.33398252 +32.54886004 +30.73570853 +32.89499212 +34.10874632 +24.63135786 +23.91622954 +22.6575932 +21.02656342 +23.21287625 +24.36068762 +21.10916094 +21.30685921 +20.82755713 +23.46051168 +21.98139433 +20.44087534 +22.0157949 +20.71622387 +19.78816198 +19.84095403 +19.62086876 +20.29764654 +19.81252252 +20.67638781 +21.60032641 +21.19459316 +19.6380952 +20.59772294 +21.46303501 +18.36053445 +18.40109233 +20.32175869 +20.65513147 +18.93611115 +21.08158868 +20.68386171 +21.79728417 +18.76546068 +21.1063234 +18.42257826 +20.28062035 +20.22697983 +19.57231157 +21.39880773 +18.03477935 +20.09999509 +18.88672806 +20.9941295 +18.18971508 +21.34669191 +21.37286717 +21.35194224 +18.61384728 +20.52556622 +18.06093647 +21.3959576 +20.89591202 +20.12847885 +20.92638282 +18.38082483 +20.11848533 +19.16878567 +21.89468082 +21.43258607 +20.93310278 +21.73499054 +19.38737322 +21.6035196 +19.30095293 +20.93118688 +20.94566765 +18.07339951 +19.44423879 +18.07773069 +21.98836807 +18.48878705 +20.17124358 +21.16222496 +18.80505441 +19.94021067 +20.42971504 +18.64081902 +21.11422184 +19.19734583 +18.84409231 +20.23315499 +19.64125451 +21.68114378 +21.52025079 +19.93340265 +19.4370551 +19.30207193 +19.07025449 +20.17819141 +21.21991442 +19.24302643 +18.76037636 +19.90193522 +18.27613767 +20.80511602 +20.07343307 +19.98549923 +19.20178152 +21.61869375 +18.36194517 +21.47929604 +19.50682081 +21.52249406 +20.91504583 +18.41668291 +21.32614515 +20.72870733 +18.12920765 +18.53481073 +20.54251222 +20.65040201 +20.98660573 +20.8809816 +20.63807982 +21.06495561 +20.30034282 +19.04316554 +21.64950645 +18.07285612 +19.46624688 +20.91548117 +19.70862416 +18.09933763 +18.26392361 +20.59454948 +21.8594584 +20.20745454 +20.02703127 +19.27330085 +19.0267627 +18.08393993 +19.09181947 +19.79644176 +18.79179913 +18.34939195 +20.87091147 +21.57848108 +21.74708828 +20.47199491 +18.21109833 +20.96345052 +18.44038122 +20.67899247 +21.11746334 +20.90345884 +21.80753522 +20.95258514 +20.77283911 +19.04697269 +21.69907475 +18.71817548 +21.61958645 +19.25065017 +18.52065438 +21.30673211 +18.00998181 +21.32835327 +19.04924737 +21.53888509 +20.76591673 +20.66473333 +19.46127825 +18.2376119 +21.32029239 +18.68361518 +19.48903457 +20.40141874 +67.75865084 +63.90178464 +63.11238245 +62.8761493 +72.93690719 +61.62920987 +74.48739393 +63.89999019 +62.70038324 +69.04681451 +62.28780903 +69.39336438 +83.04172553 +70.58337729 +82.25396074 +75.96228988 +72.31529464 +72.57301737 +84.09922572 +71.55660127 +83.29246886 +78.28667652 +70.4461519 +77.04866978 +84.56871708 +83.11752913 +79.34159557 +85.24623329 +73.48228117 +86.03970251 +85.02980098 +82.83243464 +84.57568665 +80.21684182 +82.30578327 +80.48375172 +77.59363226 +81.00445063 +76.02810199 +78.94961337 +76.40061563 +73.87744519 +77.05720995 +85.52903734 +78.00357558 +83.86729671 +74.71340187 +78.6961577 +73.81997606 +74.20012471 +81.85933112 +81.70009635 +75.39585117 +79.57166932 +85.79230358 +74.76927521 +72.29001476 +82.41213859 +72.8384859 +87.65800085 +81.75000035 +79.52252438 +78.87609441 +81.16523442 +73.44646539 +86.87613469 +82.0063366 +74.94005867 +80.9974088 +74.12854575 +72.277374 +82.08433153 +82.37494266 +81.33836447 +74.93501189 +81.65409385 +72.90347517 +81.74702891 +80.86349601 +82.48328545 +86.32678095 +85.60490787 +87.68962524 +87.61901538 +84.35695313 +87.30254633 +85.87637729 +83.64115861 +73.20600752 +72.78465847 +85.40142911 +79.83434044 +85.03354369 +77.00654448 +78.36224728 +83.20282779 +86.64519452 +73.4041554 +85.78361737 +84.68817108 +77.04859875 +77.683436 +72.7433557 +72.25182203 +79.63206744 +73.52860609 +75.10400019 +85.54824234 +32.45457547 +32.85524129 +29.99986196 +34.103429 +34.35506395 +32.14929043 +34.64036309 +34.20689663 +33.4804683 +33.49807943 +29.47451257 +30.53334478 +20.45947442 +20.19889071 +20.86922556 +24.10458126 +21.23463462 +24.50628841 +22.83586243 +21.22291686 +22.38714477 +21.14154365 +24.56820019 +23.95574581 +18.53947386 +22.31409036 +21.38548242 +18.76943338 +21.17893195 +21.0818115 +19.32815847 +21.53859742 +18.7886168 +20.43676722 +18.47264523 +22.39994849 +21.1070845 +18.72485822 +19.45965625 +18.73173226 +21.16758886 +18.32311607 +21.46416238 +21.87327547 +20.80590298 +18.94235069 +19.53367407 +20.00452246 +20.43810084 +20.69450002 +20.23626704 +19.42388472 +21.58437023 +18.34316077 +19.88409463 +21.12251751 +18.2610044 +20.84132934 +21.29927417 +20.66314278 +18.0552135 +18.72414245 +19.37217106 +18.93839561 +21.85480388 +20.61033208 +19.1915679 +21.72296 +20.96640157 +19.14210342 +19.51927326 +18.80132459 +21.88429599 +20.06918598 +21.58422675 +19.38599908 +21.44336641 +18.21554739 +21.92836127 +21.64813747 +19.45281178 +21.09888866 +21.00903171 +21.60501919 +19.22901992 +19.86208002 +21.45999845 +21.11431782 +19.57386688 +21.38290093 +18.18781616 +21.12818521 +18.82706344 +19.14448363 +19.97285937 +19.798938 +20.08228043 +20.17910238 +19.32611076 +18.70091005 +21.07392492 +19.5999046 +20.02336345 +20.57878687 +21.63855169 +20.83021132 +18.1818369 +21.35424145 +21.95742006 +19.8899401 +21.87836135 +20.18931497 +19.83846398 +19.32561906 +20.6302195 +18.91928168 +18.41973687 +18.10894841 +19.16952045 +19.58308333 +20.20830679 +19.1709993 +21.93623829 +19.16698067 +19.76390155 +18.41310466 +19.04250605 +18.19332856 +20.23803453 +21.00425873 +18.78283926 +19.47164375 +18.06646491 +21.35808822 +21.1198541 +18.25301612 +19.45947044 +21.82114646 +19.56810822 +20.10483471 +19.42097144 +20.38452555 +21.9126275 +21.99001001 +21.09885991 +19.61724439 +19.9777288 +19.42692531 +21.94229852 +20.83104122 +19.79572578 +18.04846108 +20.08193366 +21.93571281 +20.09224364 +18.72970674 +18.08837499 +19.41881503 +21.67291222 +21.98621643 +21.7814719 +20.91767997 +20.23257091 +20.36274459 +20.15596804 +20.82819722 +19.67549291 +20.67681602 +18.48644846 +19.06278123 +21.34401216 +21.61499572 +20.15603295 +18.12654247 +21.70789428 +20.80825703 +19.6561117 +20.39351577 +19.71909934 +19.86135691 +73.26061417 +63.39074576 +62.60990024 +61.23711093 +65.47040511 +63.20482722 +64.19180273 +61.35285016 +64.28985764 +65.72487751 +61.62931795 +69.46749609 +78.66778141 +71.34599528 +79.07455175 +85.33910882 +84.49815815 +69.88145382 +82.18427139 +75.32795389 +70.82786927 +74.43365948 +74.83335073 +77.96075059 +82.08187623 +83.48098046 +75.47884199 +76.68543915 +76.47250267 +77.33696715 +87.46653394 +83.94041029 +87.07424836 +82.59123829 +78.02932924 +77.7664915 +79.72151756 +78.57551263 +85.15091607 +79.34470417 +76.63775397 +84.29648698 +85.56792168 +78.63239106 +82.58571523 +72.8533435 +80.37487191 +72.63243457 +72.44966136 +81.78716398 +76.83492131 +83.86046326 +86.31660001 +74.21244352 +83.57230325 +80.21306422 +73.77619746 +82.40569959 +81.7318485 +80.23100981 +86.81937174 +80.33341895 +87.84402513 +80.14381393 +73.85008764 +79.5237227 +74.91366417 +81.55022117 +83.08612648 +79.4911487 +73.55971645 +79.49444646 +73.30256372 +82.91321931 +84.645079 +87.39724621 +74.38011966 +80.51795239 +81.07981951 +86.75879165 +82.0356387 +74.32744078 +78.98086296 +82.47540868 +83.20439116 +85.80901922 +74.88797013 +87.22450688 +72.94609054 +87.74002569 +85.7241089 +82.564206 +83.75976719 +73.16293162 +72.66353829 +73.62811138 +73.39772885 +80.75806872 +75.44707482 +85.76325726 +79.13194758 +83.1281357 +82.44593783 +77.11761901 +75.89065203 +77.47927081 +84.74650144 +81.38039832 +30.4226416 +34.0342107 +29.15045447 +33.27476721 +31.61023909 +33.48025204 +34.73574552 +33.60021178 +31.6285722 +29.76623865 +33.96063972 +31.74850928 +20.49739808 +23.3813669 +21.46749563 +20.37901815 +24.20432322 +22.32429167 +21.41870434 +23.3694105 +24.12884133 +20.16137459 +23.24157651 +23.59621109 +19.29186251 +20.43749897 +18.79771482 +21.49068726 +21.90317959 +18.87513232 +21.77622094 +21.59235116 +19.85295672 +21.25942566 +18.43219689 +22.24864293 +20.29850025 +20.57966591 +20.75779906 +20.3426543 +21.52990536 +20.53691355 +21.7348689 +20.42690721 +18.77417519 +21.75819057 +20.82599719 +20.89678132 +21.96299124 +18.31835496 +19.73329621 +18.97457969 +20.54140669 +21.24762488 +21.88623346 +19.83741007 +21.16517199 +21.68900784 +20.75320569 +18.98914229 +20.43533943 +21.88400911 +21.95442975 +20.57338863 +21.55208824 +18.80500732 +18.98795941 +21.81573827 +18.54450136 +18.76802852 +19.68597338 +20.36224595 +20.41526888 +19.16790403 +21.68245559 +21.66039963 +19.56827221 +19.73716708 +20.80998847 +20.65198359 +19.52918806 +19.65697701 +19.39215577 +18.94154003 +19.83255345 +20.74573653 +20.96786682 +21.11549459 +19.49892805 +21.05854398 +18.02606526 +19.90819975 +21.1756176 +20.52017888 +19.8962525 +20.57856803 +21.62778401 +20.2655272 +21.19914477 +21.9229709 +20.82227281 +20.29330306 +18.14994379 +19.49791159 +20.26177093 +21.18882629 +19.75241916 +21.8402331 +19.08717501 +20.97826878 +19.67612567 +19.94395398 +21.21000814 +19.18486105 +18.38724823 +20.08102234 +20.11517845 +21.18867109 +21.87327682 +18.22120275 +20.3667579 +20.72511412 +21.9432054 +21.77036086 +20.84980306 +20.08715765 +18.64609569 +19.53670622 +20.3404893 +20.90080159 +20.54159823 +20.85649121 +19.71661002 +20.91050916 +18.57496502 +18.65034525 +18.65132858 +21.56345983 +18.30413625 +21.20603349 +18.93428359 +19.87371976 +21.66382638 +20.34374892 +18.84047074 +19.13636024 +21.6817865 +19.7085757 +20.34794193 +19.83972879 +19.51375235 +18.89311651 +20.744766 +20.87296811 +19.09954037 +21.15604337 +21.4181408 +21.68270039 +18.64484966 +19.21569473 +21.44141339 +18.79416081 +18.01387384 +21.48330924 +19.30579777 +19.6579901 +19.7853361 +21.3930984 +19.35156858 +21.56911954 +20.46775655 +20.1091469 +19.30245803 +18.97484679 +20.49623377 +19.78757664 +21.64206404 +19.96655486 +20.04717967 +19.66689312 +71.93397676 +66.97781309 +64.8630512 +72.92361989 +69.31686381 +69.24453899 +69.50718202 +70.97622058 +65.45772465 +61.66028344 +72.22406699 +62.95310873 +81.04264232 +71.11753343 +75.66477771 +70.8181646 +77.94475631 +79.97666719 +72.50064235 +75.60766064 +84.2773169 +76.7058791 +82.29547619 +84.22990724 +87.063996 +85.8549134 +76.61601435 +86.87675918 +84.48833392 +84.88244048 +71.80931114 +86.74421488 +83.94274692 +83.78175506 +72.86456932 +75.26677008 +87.29131122 +79.9148202 +78.27168942 +85.33652621 +73.30936969 +78.57666697 +77.91759894 +77.50450505 +76.83076821 +75.27076587 +74.67685296 +82.14780207 +86.68950698 +80.12239233 +76.74916862 +74.52867199 +85.26182609 +87.52286237 +77.56786567 +76.63680956 +81.60429751 +84.38500643 +79.26741611 +82.06147092 +83.46077878 +87.36528287 +74.63701454 +87.95389791 +78.27074931 +83.56719087 +81.15473443 +79.78286811 +83.16521378 +78.32830166 +74.01420685 +79.81846266 +80.34988256 +75.22971224 +86.96907479 +75.63888421 +78.54688745 +74.47815999 +80.57197284 +76.92146328 +79.82759397 +75.59314496 +72.85970259 +84.46681553 +82.25722261 +77.45168098 +72.7597679 +85.84903617 +74.71657662 +86.89170866 +79.65737312 +77.44603866 +83.8679906 +86.02926532 +77.93126739 +78.51734928 +78.9305012 +86.75713567 +83.08099196 +75.9623087 +74.95996638 +75.68585355 +76.21699296 +75.6829068 +82.53607146 +73.85490781 +78.19770581 +82.00082568 +30.66149795 +29.21775045 +29.32366748 +29.60618168 +34.1493888 +33.0103201 +29.21293782 +31.30283482 +30.58293923 +32.49945047 +32.14595191 +31.87918763 +21.56901424 +20.77734184 +23.48584491 +24.101757 +24.38979474 +20.42261187 +23.47478974 +23.41473421 +24.40304973 +21.29305622 +23.4849608 +20.40472465 +21.01758222 +19.86907245 +21.58145281 +21.96809409 +19.92630675 +20.58480039 +18.91288681 +20.13965069 +19.90983817 +19.18207346 +22.09578418 +19.03856756 +20.64156358 +21.66406126 +21.75178406 +21.27458895 +18.7943464 +21.37182027 +19.19796925 +21.7937021 +20.93636114 +21.24254733 +18.42816239 +20.42864949 +21.30500168 +21.46694991 +18.13191676 +21.77752329 +18.19080135 +20.47771446 +20.79349271 +18.44707336 +18.20951182 +21.15994267 +19.55743962 +18.44614136 +21.05471299 +21.40076044 +20.35345187 +18.64852706 +18.06435758 +20.80720414 +20.2044372 +20.60714438 +21.2568192 +19.74070059 +18.5016135 +21.76411105 +20.18104851 +20.46117648 +21.73364572 +21.72633463 +20.28837843 +19.1904457 +21.98845454 +20.72326836 +19.63374671 +20.23934721 +19.6633362 +21.55322061 +19.9299975 +21.2941135 +21.36070519 +21.29732935 +20.88875445 +21.41951087 +19.54891832 +18.45865282 +21.36981682 +19.99267366 +18.50950413 +18.46125009 +18.63696958 +21.00389304 +20.84342669 +18.81527472 +21.38769367 +19.12517918 +21.17194233 +19.95522363 +20.92619473 +19.52555065 +19.38856239 +21.3654384 +18.14120902 +21.89894779 +20.59271823 +19.72993355 +20.51221812 +18.49096379 +20.05620982 +21.94038209 +18.94107775 +20.8891124 +19.7398855 +20.60983259 +18.81954891 +19.88185993 +21.92037086 +18.7811486 +20.54578011 +20.302391 +20.26020635 +20.46049316 +20.00895189 +18.067723 +21.60038725 +21.30791531 +18.69667072 +19.65309271 +21.08219592 +21.92024926 +21.80454819 +21.5974368 +18.63349559 +21.65790042 +21.51377965 +19.50779964 +19.57809038 +19.83963569 +20.62470252 +19.04334246 +18.09179372 +18.5649181 +19.76046363 +19.12801028 +21.05806624 +18.42676028 +21.6698609 +21.58130872 +21.63502727 +20.95843999 +18.42799989 +19.75255029 +18.52562149 +21.95079677 +19.33215369 +20.34132217 +21.11757222 +21.68246434 +21.0221909 +20.69801512 +19.86392713 +20.42160054 +18.37665524 +21.53569017 +20.17510924 +18.1605402 +21.6796085 +19.9572079 +21.47435657 +21.42755182 +21.26300635 +18.24561208 +18.98826658 +21.50219149 +73.42627836 +67.42973653 +67.18456396 +63.58191262 +74.37038673 +64.09868326 +63.79674027 +70.18591399 +64.50268252 +73.96729391 +61.34833514 +61.27824437 +77.67568796 +77.41472938 +72.37540962 +77.51494362 +76.22898035 +83.65791423 +69.98362084 +84.99170737 +79.5305757 +72.19661028 +70.56571541 +78.19139982 +75.3036932 +76.3577367 +75.46658195 +81.90834048 +80.37132461 +72.80810523 +79.94187798 +84.15108203 +72.85931877 +85.71840868 +80.76538895 +86.68673156 +74.28101517 +87.79156354 +82.20959582 +80.9826113 +80.14818062 +72.29161581 +86.92978599 +82.20514687 +73.37786452 +72.24843244 +72.72516318 +81.65223182 +81.80460882 +79.97547795 +76.88979515 +80.48856181 +80.24156654 +72.2932944 +76.501143 +72.83348123 +86.38173943 +83.58350575 +87.91673764 +76.81629988 +86.05164289 +81.64494914 +79.24620251 +75.55754284 +83.81025156 +86.4621016 +75.76129765 +74.80265466 +83.13555994 +87.62736429 +83.99524965 +76.54961727 +78.71853108 +78.14833194 +85.63269821 +84.34112278 +81.42282529 +76.39861233 +87.00698117 +82.45225205 +87.52678608 +81.28109222 +85.1382668 +85.00886998 +77.39711183 +87.42416371 +72.35092457 +84.29921269 +87.44874547 +77.4223826 +82.36961125 +80.99541649 +84.18395532 +85.8586819 +81.64855634 +85.05431008 +76.99653965 +75.65736389 +78.89065274 +82.84375378 +74.99592238 +82.88560325 +78.30546831 +84.72917185 +72.5893452 +82.68769045 +82.39712575 +87.05493419 +33.36554594 +33.89632563 +34.58937586 +33.12032592 +30.65697738 +34.36654572 +33.58449352 +31.51639461 +33.3473803 +34.35320222 +35.04416691 +34.89504293 +21.61746748 +22.04918663 +20.65072286 +21.26899724 +24.11324569 +21.77660648 +22.94007465 +23.1435247 +23.66259385 +20.97894298 +21.52556166 +22.16854559 +19.92638875 +22.45797956 +21.85191727 +20.3967967 +22.03348403 +19.85598624 +21.65075255 +19.34817502 +21.59543084 +20.3016329 +18.87231699 +19.55128278 +19.70322212 +21.09795802 +18.37973208 +20.73725123 +20.96420197 +20.71464072 +21.14368336 +18.80249526 +20.44608575 +21.86904972 +20.54126409 +20.543483 +20.96586649 +19.35137642 +20.82187342 +21.68573366 +18.06090372 +21.50107339 +18.89259281 +19.84272383 +21.42994421 +21.31609232 +21.5884149 +21.78255836 +19.79242724 +20.62941533 +19.56566965 +19.40659222 +19.73016911 +20.97633122 +20.00960409 +21.26031989 +20.98490223 +18.08538557 +20.52489265 +19.75007578 +19.82651596 +21.27349222 +20.19043194 +21.54248729 +20.85122237 +20.21136004 +21.0181519 +18.1533962 +18.10833462 +18.33339731 +19.7502613 +18.94180058 +21.93704518 +20.16916056 +20.64966298 +19.11528055 +19.28718763 +19.34184766 +20.79494282 +19.89482483 +20.07073461 +20.36801193 +21.07524663 +18.36147899 +21.10749565 +20.25294028 +20.67638178 +19.31020706 +19.63754735 +18.92466328 +21.91719999 +19.98801189 +21.98430766 +19.95914391 +18.87900506 +20.02980959 +18.15838229 +21.61907921 +21.274201 +20.90328208 +20.43930686 +20.31933693 +21.98691286 +19.11198974 +21.11873746 +20.32561908 +21.90352218 +21.31121273 +21.99410045 +20.35329998 +18.81817583 +20.01474814 +20.84444454 +21.09744154 +18.00203996 +20.02010835 +18.94093832 +21.49964733 +18.61162178 +20.665825 +19.06424876 +19.42616857 +21.76223704 +18.9617925 +18.38001105 +18.27713961 +20.01846044 +20.56684593 +19.30917457 +20.76103897 +18.24413487 +21.42933858 +21.26626884 +21.94198509 +21.74601992 +19.42478866 +19.59710154 +20.98385939 +18.53633109 +19.25605884 +18.47416375 +19.8195458 +20.20801712 +18.84464188 +18.10652675 +20.87742333 +18.08002066 +21.89361044 +18.88642903 +20.65560037 +19.73900691 +18.23869544 +19.88873443 +20.06704254 +21.41873283 +20.92939628 +18.3172099 +18.83665073 +19.24755074 +18.46563075 +19.82759628 +21.93723676 +20.05845117 +20.96752867 +20.73175119 +21.39964574 +18.86289396 +21.01530505 +73.18643127 +63.53253359 +65.91651019 +74.39338399 +71.81871471 +65.04323532 +68.86960807 +66.69883527 +67.8635264 +66.0302602 +64.99890185 +70.37677736 +81.92296897 +83.40381455 +72.25072109 +82.61071773 +73.7945088 +76.47226077 +83.830406 +76.89389589 +72.89842605 +72.32116153 +77.18070806 +81.67667602 +87.42755061 +84.09094683 +85.5159982 +83.46152587 +85.80899984 +72.78137251 +86.35763215 +77.87244347 +86.22535261 +72.77245505 +82.94322399 +83.26839386 +87.84503354 +84.20052231 +78.02743388 +80.81887765 +77.77137723 +75.04613477 +75.884866 +75.46150595 +80.68410803 +86.39477187 +86.49970167 +72.78876898 +73.2608036 +75.90368366 +76.06269181 +73.50058243 +78.19664176 +81.38406942 +81.23109507 +80.67550813 +77.4001703 +83.28329818 +85.20328936 +77.52177857 +73.36504406 +76.73760044 +74.19433697 +79.4739247 +74.49138666 +79.40170466 +75.53242055 +83.81567434 +72.43761468 +80.87205882 +74.70822623 +72.01241307 +85.9063767 +82.74365026 +73.58237876 +82.62436468 +81.78557217 +79.57294309 +78.70737398 +86.19228875 +75.92163946 +78.37220853 +77.26510485 +84.33840681 +86.66327851 +85.59634283 +84.76310014 +87.78847057 +83.15415315 +84.01669935 +87.7246722 +72.11171411 +82.14461631 +83.82338357 +86.68506642 +86.14134348 +85.47133808 +87.13262689 +80.59803606 +72.11069384 +72.17684266 +80.7141939 +78.9643255 +79.21989043 +78.40644569 +86.68357966 +77.74669576 +85.69959816 +34.57832804 +31.99680518 +31.66039736 +33.6705254 +33.33101125 +30.90261881 +29.45630235 +30.49504951 +29.63857675 +30.68628067 +30.47565217 +32.8813468 +24.43614087 +23.23213065 +24.31138801 +23.67291122 +23.42089046 +22.01499628 +21.61507819 +23.95132131 +24.21616036 +22.18443235 +20.20060157 +23.71053784 +19.59964174 +18.47250047 +21.72923288 +20.55132827 +21.23862296 +22.17856924 +20.64864776 +20.49308268 +19.62255181 +19.68207371 +21.69463604 +20.27122355 +18.74364449 +18.46632996 +19.98716048 +19.95514718 +20.71277946 +19.97971436 +18.6630575 +20.12736131 +21.73096792 +18.72722688 +20.8586618 +20.47323911 +21.01449747 +18.62025939 +21.7873171 +20.50353021 +21.40881729 +19.00465402 +20.97777089 +21.14440196 +19.60854723 +19.55456818 +20.65099099 +18.3912694 +20.94651756 +18.17503597 +18.76596358 +20.66054109 +18.39682618 +21.89157074 +21.47770776 +20.50914633 +21.28031486 +21.55445785 +19.04155787 +21.4017547 +18.77917513 +19.65336906 +20.23303853 +20.85556385 +18.12056527 +19.16203765 +20.43407639 +19.42947472 +21.10010022 +20.60660864 +18.93760581 +18.58371592 +20.51618797 +19.80186931 +18.67582816 +19.30549682 +19.21218699 +18.43252789 +20.18763608 +21.05459454 +20.30186756 +21.30516546 +18.51027419 +19.74244743 +20.43766041 +20.70400624 +20.78314523 +20.91586171 +20.60571925 +18.37691236 +20.78122271 +20.89913063 +21.97391974 +20.77344975 +20.74732158 +20.05925316 +21.47169163 +21.98733905 +21.11617948 +20.45000151 +18.31581931 +20.90695653 +20.83424627 +21.10227259 +18.49822155 +20.61493 +19.18289562 +21.04108497 +18.96149582 +20.73591471 +19.17997179 +18.03597717 +20.53343165 +18.45307633 +20.63716589 +21.70243779 +21.87698374 +18.94369235 +21.23701291 +19.41421311 +21.60182829 +21.14995827 +18.29568687 +19.62854798 +19.3492713 +21.14072408 +19.64114284 +20.73835359 +20.40974102 +21.90255488 +21.75113983 +19.71079806 +20.89431285 +21.31784038 +21.59554302 +18.27253706 +19.2083152 +19.28092016 +19.7377838 +18.677059 +19.98482352 +20.65285731 +19.98945568 +20.66317238 +18.26576653 +21.50394933 +21.75627677 +20.4716297 +21.52556399 +18.52578407 +21.18979568 +18.47659277 +19.63204093 +19.29527414 +20.3423673 +19.20605814 +20.54925452 +19.46954916 +21.44316152 +20.53553536 +18.86119319 +19.34451769 +21.38968355 +19.545919 +20.14319626 +20.59576774 +19.3912386 +19.17591452 +67.66346668 +64.12238122 +65.06526398 +62.04426738 +65.33399575 +65.56157567 +74.28093106 +69.22031203 +71.90840099 +61.81713064 +62.64889903 +72.27770267 +79.51748901 +72.80404111 +76.1224247 +78.2001972 +82.93282455 +70.77759411 +79.10112993 +80.9683975 +85.01688163 +82.36712442 +74.12704791 +80.85266909 +83.70717148 +83.17371339 +84.61157126 +85.94092154 +73.13342864 +73.23416439 +84.23394542 +75.58173187 +72.49904277 +77.94914889 +84.60732241 +73.58089014 +82.65017218 +71.97138436 +73.6672922 +77.52046921 +73.24963133 +85.43178537 +85.7221379 +83.98822429 +75.78861849 +80.27550686 +74.85707999 +76.40865465 +80.23682321 +83.74011848 +73.49682661 +76.72872785 +86.80510335 +78.84704525 +87.00102952 +87.84132169 +80.40725484 +76.59031807 +84.63211235 +74.53108002 +76.02910018 +84.55401976 +78.93454576 +75.22099621 +82.41405585 +83.72017102 +73.99758778 +76.42801544 +75.53680215 +78.07392633 +84.211076 +83.41532551 +82.5995896 +72.09987657 +74.27357998 +75.57203529 +76.35056056 +74.34211098 +78.5642946 +73.20218661 +83.99954864 +84.6027204 +81.18748763 +73.43457305 +86.24116513 +87.45000536 +75.23767251 +83.27407722 +85.47085399 +80.63734154 +77.17302142 +79.6567865 +80.19614604 +78.41004216 +86.87731309 +83.90196868 +75.86079715 +81.36888375 +83.01899454 +74.24247424 +77.73478203 +86.48873929 +81.07473847 +75.47193496 +73.96452837 +80.29159946 +80.76294056 +77.35060152 +32.83465628 +29.23957638 +29.72888833 +32.29614625 +29.19561484 +34.43780948 +29.70973107 +33.11308084 +31.30369602 +31.35863556 +33.69528651 +31.99655152 +23.41093519 +22.42325547 +22.81621468 +23.56800024 +22.88074191 +22.91795443 +23.69570247 +24.4119076 +21.84768717 +22.74548362 +20.54650262 +21.76744019 +20.11975339 +21.85313179 +20.53485503 +21.42644761 +18.50985409 +20.33456173 +22.12379689 +19.35078954 +18.56111465 +20.20724193 +21.31735248 +20.29348228 +19.93514479 +20.77848037 +19.10883945 +21.1083954 +18.70179386 +19.67700078 +20.72302774 +21.41578066 +21.26772163 +20.98699871 +19.52968014 +18.57796546 +18.8854495 +21.47993621 +21.44989762 +21.82717739 +18.3215264 +20.16657768 +20.61450311 +20.86263842 +20.59520536 +21.10028239 +19.45654667 +18.7357001 +18.82194661 +18.05137996 +19.40501253 +18.37050266 +19.28965767 +20.51677877 +21.58205518 +21.36252262 +19.0143469 +18.53989154 +18.77589337 +21.88814832 +20.19789792 +18.73000889 +21.36298462 +19.58076498 +21.59688861 +19.04270851 +19.57288704 +20.01979105 +21.57046134 +21.47502973 +20.29742028 +18.88710204 +19.61342789 +21.90253226 +20.4310929 +21.12439102 +19.79945878 +20.1280291 +19.71581645 +20.81168399 +21.40588406 +20.02945417 +19.92346983 +19.36114587 +18.70838678 +21.75699089 +19.26431887 +21.20276585 +20.86138809 +21.1636704 +21.74707253 +20.49348304 +21.59180137 +18.67078929 +20.5068503 +21.08910245 +19.08656452 +20.96301521 +21.11390732 +21.32475182 +19.25153165 +20.72642636 +19.21701313 +20.95616155 +19.85189466 +20.3901775 +19.66335993 +18.64793171 +18.71211812 +18.31027563 +20.47229072 +21.39455359 +18.08334731 +18.26495801 +19.82986797 +20.95088294 +18.63083058 +19.08660528 +18.92802481 +19.59620487 +18.89999471 +19.41039471 +20.91417039 +20.44403699 +18.32472682 +20.55098267 +19.90830869 +21.7382623 +19.15721727 +20.90466568 +20.92344329 +18.88600312 +21.6128945 +19.9647328 +20.71553347 +18.58342224 +19.03121567 +20.34442847 +19.27610907 +19.99877167 +19.4960208 +20.56221519 +19.94858145 +18.01843974 +20.59872335 +19.68848623 +19.36679336 +18.37658701 +18.88147876 +18.89508917 +21.741478 +19.7300519 +18.16826339 +18.55526673 +18.3161307 +20.35547756 +18.74648597 +19.99423056 +19.48024825 +21.21550551 +19.28195855 +19.83306193 +20.88514219 +19.73340483 +21.17286214 +20.64673742 +19.64211646 +20.0159277 +74.353967 +70.19791635 +73.50793507 +63.63111565 +67.77201172 +73.55779871 +65.58752404 +74.36023309 +62.52025387 +73.47632792 +67.25488486 +62.20292065 +83.49017512 +73.7501121 +70.18532195 +71.94772628 +80.06946079 +85.01563431 +71.56989241 +77.00943371 +72.64347927 +81.40296701 +71.67227412 +73.56234394 +74.69164051 +80.35527459 +85.4551198 +79.69637856 +72.49430387 +80.52813755 +84.25177397 +73.66981216 +72.72439915 +74.55489489 +83.43375688 +80.97808539 +80.55273505 +75.9004883 +77.22300658 +87.34921521 +75.75898362 +86.32261518 +81.19095787 +73.88397617 +79.38162565 +87.07216734 +78.60261311 +85.71120801 +76.08459443 +81.11528209 +85.48111254 +86.71019962 +79.75402831 +82.21527323 +72.03039065 +76.03332054 +76.44104341 +73.47824917 +76.45676618 +77.11943942 +87.49605662 +82.6936686 +78.8122981 +87.80465655 +74.44814344 +79.6129975 +82.34904237 +72.88579147 +83.46797507 +73.59434495 +74.2409532 +74.55145356 +86.86140335 +82.93097258 +82.48604814 +80.88711942 +76.6841598 +81.9334487 +78.56164132 +74.32594487 +83.03275952 +74.07260358 +75.04997681 +85.08237219 +82.56171502 +82.09570084 +78.69109346 +78.34033788 +83.0440423 +83.83992764 +80.16325732 +87.50813989 +87.29102534 +78.59872093 +85.12979262 +80.69213054 +80.18806341 +72.879397 +81.18356419 +86.40921371 +84.48210915 +84.58104184 +74.55883599 +77.7351613 +75.73124959 +77.05461493 +81.27259829 +77.83651112 +32.38845981 +31.60640192 +30.63433369 +29.41057633 +34.5077595 +28.872784 +28.81057039 +34.09149899 +35.17958326 +33.45345167 +28.89774702 +30.25081164 +24.51376435 +23.78254575 +22.69926206 +21.22113481 +20.22437741 +20.25345829 +22.90167301 +20.25780569 +24.61146169 +20.98155842 +23.3439677 +20.85466641 +20.64422241 +18.76218361 +21.57272417 +20.82783655 +18.78493855 +21.90780877 +20.672588 +22.20684184 +22.18648907 +22.20239964 +18.75060796 +18.92744277 +22.05618714 +20.04616517 +19.04382987 +21.17983546 +19.14861511 +21.00922524 +21.45161895 +21.23851131 +19.1878529 +19.70058532 +19.74733838 +20.06092406 +18.68741411 +19.82889768 +20.06237336 +20.14615774 +21.30003013 +20.08860455 +18.94130189 +20.67234136 +21.20959825 +19.29599304 +20.60098168 +18.60559622 +19.37142382 +21.64105 +21.64938854 +20.30519616 +20.81340125 +21.42017354 +21.83840491 +19.96337799 +20.25019023 +19.66634148 +18.29143903 +19.6949946 +21.16250936 +21.21954199 +21.56950846 +18.20586682 +20.10861836 +18.42969795 +18.80296873 +21.60086601 +19.98716503 +21.03417968 +18.25753642 +19.66730602 +20.69696112 +18.53362593 +19.30327152 +18.25819122 +20.60535976 +21.88356504 +21.30818196 +21.89849247 +21.38241918 +21.42078322 +20.30870878 +20.07300925 +19.62501707 +21.27237958 +18.72827045 +18.22489966 +21.79157529 +18.01511274 +21.90731729 +20.91541348 +19.31399568 +20.79127937 +21.27150063 +18.98144822 +21.08358273 +21.2174785 +18.99741852 +18.16770542 +18.81648914 +18.80258564 +18.26096182 +20.90402954 +19.5198997 +21.53754467 +19.00868442 +19.81292866 +20.55606972 +21.5098598 +20.09848441 +19.49409851 +21.46806031 +19.2409857 +18.3176948 +19.78016843 +21.14037323 +18.12312333 +20.34352898 +20.37785619 +21.71416705 +19.14585927 +19.11534645 +19.76193461 +19.42765961 +20.94538345 +20.56700831 +21.03568415 +21.28016716 +21.69192453 +19.72374693 +21.93878956 +18.86264325 +20.85746065 +20.13603727 +21.26760238 +19.90708208 +21.13575454 +21.72067486 +20.92917706 +21.10437714 +18.26870173 +18.81983065 +21.89721496 +20.77274802 +20.24579607 +19.46790549 +18.177556 +19.47654363 +19.99565149 +18.84583804 +18.69254717 +20.03366621 +20.34361694 +18.95967168 +20.3495579 +21.60135986 +19.89269303 +21.42956068 +21.70908578 +18.36957612 +18.38324632 +19.90417673 +20.35240978 +19.70353386 +19.30580308 +20.56397646 +18.00832605 +39.37012762 +34.07096944 +39.00311113 +33.18490616 +34.11422771 +34.09109065 +39.57230091 +32.43312393 +37.00539511 +38.66174338 +37.24037031 +35.01168349 +42.7931454 +42.28520272 +38.68220657 +39.77489538 +42.86535043 +42.98327324 +38.80661448 +41.92322422 +37.64752498 +38.19501292 +36.57220356 +36.92000721 +41.08996647 +39.92590793 +37.69765867 +39.06638382 +38.82952581 +43.72328611 +42.65721259 +38.94731574 +43.1380873 +38.5861188 +41.17783925 +43.55826717 +39.09093313 +41.40592846 +38.33669658 +38.09999413 +40.56077873 +43.79904949 +39.59337073 +43.46629489 +43.28025624 +37.99967682 +39.18318361 +41.11350269 +40.13794398 +36.76178541 +43.94255231 +42.01450861 +39.49523206 +40.93879714 +36.69471044 +39.95601924 +43.43970786 +43.16590002 +38.69285492 +40.24450276 +41.57773766 +38.71883509 +36.51786514 +40.04225186 +40.81177987 +36.04557931 +36.76342652 +37.65712008 +38.78317719 +40.32711927 +38.34951613 +38.20824781 +42.95451984 +37.1601673 +39.9634253 +43.62231855 +40.99984359 +41.73737672 +39.44964345 +36.15922592 +41.16877947 +41.0551315 +36.88318504 +39.69702365 +43.3225761 +37.64063048 +39.68873924 +39.68877468 +42.48603736 +42.59655244 +42.0741015 +38.92358345 +36.57624858 +38.79581416 +41.43827657 +41.62191932 +43.71999048 +39.62911971 +41.05947415 +40.31351665 +37.20493896 +39.68960962 +40.35867091 +43.60598569 +38.93230247 +43.8407816 +40.33839027 +37.93974423 +22.51998714 +22.2801337 +23.77407575 +21.70524501 +23.73621533 +26.34434027 +24.8165105 +25.24697123 +22.17122398 +23.8708821 +22.97386892 +25.89166592 +19.4741763 +20.62833023 +21.16627809 +20.85841053 +22.17381264 +19.68284481 +20.28994662 +22.01507024 +20.91677666 +20.39280792 +18.84248049 +21.85408293 +22.06383805 +20.64499714 +21.59757411 +20.22804291 +19.07813758 +18.20223049 +20.77850173 +20.13221479 +21.29741964 +20.94495942 +22.12742386 +20.20158025 +19.74023306 +19.17759388 +19.93929706 +21.52587227 +18.1988305 +21.94665873 +22.00838593 +20.42391155 +19.35709868 +18.92242337 +19.13653111 +18.22954237 +19.90681197 +18.23513046 +20.8805765 +19.49395389 +18.80347411 +20.18587164 +21.90204364 +20.78717396 +19.97337683 +21.11441164 +20.95660985 +21.57384326 +18.81988019 +21.43688218 +19.20533341 +21.76538861 +19.12548455 +21.70878345 +21.85656159 +18.58315468 +21.899401 +18.34406582 +18.8754791 +19.77794083 +21.97438946 +19.9542522 +20.20163777 +20.7776998 +21.87317423 +21.1334056 +18.89661602 +19.21196372 +18.39051296 +20.57780946 +18.72296123 +19.02961955 +21.55245462 +19.3070227 +20.26658687 +19.59026404 +18.29612515 +19.45145532 +19.63227281 +21.58280441 +19.29780261 +19.51696448 +21.13158707 +21.0358266 +21.19314111 +21.81508296 +21.18222999 +19.63043281 +21.28603407 +20.03469853 +19.11085613 +19.21166827 +18.89536955 +18.57997183 +18.73796165 +18.2527122 +21.55380346 +21.72403124 +20.34708157 +21.36195823 +18.73428441 +21.6066953 +20.02544624 +18.26865997 +18.56525098 +19.23590584 +18.17423032 +19.23717351 +18.23972173 +18.94411042 +19.06267902 +21.97747591 +18.9421867 +18.9567458 +20.74715587 +21.43742991 +19.54822165 +21.50247208 +19.36056808 +18.78806352 +19.3185553 +20.4374255 +19.46857264 +20.69157522 +18.94774352 +20.80397158 +19.38975345 +19.82347349 +20.23098056 +20.74490001 +18.95485477 +21.34378215 +20.887371 +18.93639168 +21.89989825 +20.65918796 +19.80970755 +18.6301093 +20.13288401 +18.65910355 +19.71796671 +19.63485009 +20.87975858 +18.76953272 +21.28748542 +19.30452931 +19.77595451 +21.0893639 +20.5015696 +19.23210141 +19.5448117 +19.22314726 +20.37908067 +19.33049813 +20.90279078 +20.31914863 +18.58282645 +19.24107447 +18.88578977 +19.56125285 +20.06393634 +20.02705143 +21.01809471 +18.75668745 +21.78589132 +19.37138043 +19.20099539 +20.74071431 +72.15502615 +66.347216 +71.35711886 +73.218809 +71.62852985 +61.6650722 +72.45221719 +71.73294104 +70.30234724 +71.5363049 +74.52018538 +67.06313527 +70.88330461 +76.99301233 +70.56065033 +74.40676633 +78.94837437 +74.6453473 +80.47733422 +82.59722781 +76.36521606 +83.810141 +71.07910021 +82.87855631 +84.74562731 +76.68899093 +76.31876634 +76.42023058 +84.4724056 +84.79461707 +76.81190599 +80.99255689 +73.77759408 +79.02345705 +87.4528015 +74.09417781 +87.05640904 +85.08584986 +87.45036789 +74.78341226 +75.32579629 +72.18315106 +83.49491473 +82.20674848 +74.75922816 +84.55672947 +76.69498354 +74.07093719 +78.7421 +73.43788825 +76.19360764 +77.92262781 +85.60927563 +84.66045647 +79.0650884 +83.57313856 +81.07893056 +77.43488085 +83.59392214 +82.91608183 +81.90157961 +81.88335775 +82.92373135 +78.14897687 +85.90108959 +74.59334488 +86.13803259 +77.47279062 +73.99695383 +81.59652162 +78.01204195 +85.04391333 +78.89181013 +82.69620652 +76.93138928 +75.35051463 +78.20803369 +81.70814095 +82.51035572 +81.25052663 +87.80804853 +83.05664563 +86.90347976 +73.8862218 +72.98182675 +76.97576744 +74.18528058 +80.41374206 +80.30354587 +81.20059876 +83.92348496 +87.54110712 +86.05197384 +73.89373396 +77.41607038 +76.0930223 +74.82904899 +80.71556703 +80.85219939 +75.6358882 +83.39032398 +83.72659664 +72.33565214 +78.33209318 +86.24221714 +80.32506689 +77.56201504 +76.05878922 +30.3574631 +29.43850976 +34.03024255 +29.35460176 +30.67068061 +29.09231914 +33.80086756 +29.05140999 +30.77312116 +32.46909693 +31.42118667 +30.60652895 +21.39284587 +23.73409927 +24.52342052 +21.7192228 +20.33537079 +20.32381357 +23.56127481 +22.63461556 +23.67049381 +23.28478533 +24.22562712 +24.1284915 +21.66098875 +20.37837456 +19.69408553 +20.50878148 +20.48848506 +20.9316884 +19.19425541 +21.11741878 +20.30223714 +20.97235874 +19.41342112 +20.21128124 +18.93124067 +21.60442643 +20.04761298 +18.99064526 +18.25202595 +20.80232879 +18.94909999 +22.06372869 +20.23144849 +19.2478563 +20.72973709 +19.19256387 +21.30152702 +19.25140782 +21.97406579 +20.44018289 +21.39816712 +18.30882148 +19.41181513 +20.97006028 +20.64003149 +18.79499837 +19.44345953 +19.07206524 +20.45740775 +19.38199354 +19.23991032 +18.83312719 +19.63987861 +18.7856165 +21.8544736 +18.6215819 +21.65737628 +19.71767746 +19.51521017 +18.92891286 +20.54302534 +20.74211206 +19.87028385 +18.22282758 +21.6740406 +21.92131653 +19.72417366 +20.45464323 +19.80119451 +19.2875186 +18.30927517 +19.82427092 +21.27353823 +18.06812584 +20.32843073 +21.52308843 +18.87019772 +18.73302346 +21.0891065 +19.02034572 +18.76793707 +19.511957 +19.57000805 +21.49640425 +20.47377389 +19.62384583 +20.13755186 +21.81586093 +18.45285871 +18.83086209 +21.85011483 +21.82929939 +18.70866169 +18.58043527 +19.68434054 +18.19887317 +20.61961413 +18.30998409 +18.23211851 +19.94074774 +21.83114654 +19.70718374 +19.42592463 +21.99182686 +20.12787584 +19.50096563 +19.54344325 +18.77983798 +20.04889223 +19.75542011 +19.08318119 +18.64216035 +19.24495547 +20.03473331 +18.23333309 +18.61497611 +21.79062907 +20.1714214 +18.25379721 +20.77235104 +18.24721148 +20.95549691 +20.99985089 +20.6043159 +20.92881818 +18.35186312 +20.90712221 +18.54675328 +19.35802363 +21.77098101 +20.01150191 +21.86970573 +18.35568984 +19.9102264 +18.24960928 +20.52084105 +18.31482801 +18.70147121 +19.03532124 +20.99497541 +18.65584848 +21.38652821 +18.52761576 +18.1409778 +19.58018168 +21.91776657 +18.44136891 +18.40687378 +21.00466604 +19.24864678 +20.86912647 +18.80004236 +19.84028798 +19.06115951 +19.80611286 +21.07638837 +21.04803976 +20.06308915 +21.71348737 +19.05513641 +18.9612454 +20.4552256 +20.48518814 +18.18616016 +20.43111085 +19.49774463 +20.9897701 +21.23242266 +68.70929195 +61.22318843 +69.85457502 +71.3847854 +64.58617242 +67.51918562 +74.33388208 +65.25437153 +64.23488421 +64.06762515 +68.79969135 +74.11710458 +79.28687081 +74.54757483 +79.75060296 +79.53755246 +79.10427811 +72.31429473 +79.68847673 +76.5161523 +70.12854875 +78.21714484 +85.19341989 +75.26795845 +86.92425381 +73.09805612 +83.21688664 +73.38630424 +75.91888231 +80.64665265 +80.99556825 +83.89696079 +81.35391262 +81.00884826 +86.0780635 +78.7591646 +75.51352736 +74.49911102 +78.30583365 +83.30395923 +72.59347892 +73.97597636 +73.60937798 +81.26636565 +75.79477141 +74.57055867 +81.38655032 +84.0799415 +77.29592319 +81.23177339 +73.55233381 +86.94218535 +77.37176528 +87.93898067 +87.63636632 +84.44090451 +72.30371647 +80.67623178 +73.37176928 +86.30149109 +84.6301176 +87.55324327 +81.73020401 +80.35090784 +80.29972712 +78.1794212 +79.49522512 +82.16265733 +86.92298922 +81.53023577 +85.70974081 +85.80035997 +80.30055192 +77.55876969 +82.23239253 +79.13327778 +73.30235017 +78.04419638 +87.90431032 +72.77549127 +83.14587238 +85.45819887 +82.37882361 +81.11519299 +83.3207107 +78.86933237 +73.46164675 +87.39778026 +78.9166608 +82.64606911 +80.50081243 +72.87462923 +87.24377811 +84.68043303 +74.38542349 +87.70621362 +83.79038978 +79.55711586 +81.7818895 +73.70775861 +80.96655307 +73.17854905 +80.71765312 +80.84309998 +85.99098754 +83.6008879 +75.99280909 +86.21478517 +32.46874682 +32.68082239 +29.88147503 +34.28974443 +34.75817006 +33.75993367 +34.82080901 +30.73471349 +34.31115609 +31.92890998 +29.84906437 +33.08031748 +23.22485754 +21.44542065 +20.28316901 +21.05531457 +21.95100221 +20.91068069 +21.02925301 +21.91342897 +24.46880269 +23.58380966 +21.59247165 +24.42318489 +21.07656901 +20.2975215 +20.35015737 +20.1144091 +19.38633521 +20.01564522 +20.59426334 +20.59479978 +21.55207409 +19.90163578 +21.36800447 +20.79986392 +21.952249 +19.63493713 +21.70856916 +21.38472647 +20.64930024 +20.71702449 +18.42662497 +21.90554268 +18.51619738 +20.35014726 +19.41302051 +19.81406102 +20.46035514 +21.65617549 +19.84930554 +21.97199109 +20.39080465 +18.66011229 +18.77804681 +20.884474 +18.2190647 +19.94049081 +18.61072559 +19.75181809 +21.00370162 +19.83906008 +18.65738596 +19.01135096 +19.47389662 +18.28175416 +20.34125531 +20.43405775 +18.25211144 +19.61301026 +19.65977668 +19.33899065 +20.23095896 +19.38382996 +19.66563712 +21.19009419 +18.64843742 +20.47645878 +20.02237761 +19.22028655 +20.91013863 +20.20475344 +19.90944611 +20.67067726 +21.20916123 +21.89067516 +20.51740147 +21.49775994 +18.00436616 +20.12094277 +18.8585902 +18.37676983 +20.12788407 +20.98730602 +19.64909009 +20.6804298 +19.95887702 +19.74834346 +19.16102656 +19.53037406 +19.39918252 +20.58474874 +18.46407405 +18.37054547 +20.64005198 +20.18519228 +19.72080039 +18.60977154 +19.64410845 +21.15892705 +21.55848494 +20.81030628 +18.2396487 +20.68163911 +18.96394236 +19.26100497 +18.78349283 +21.10302316 +20.59440849 +20.44734022 +20.67189044 +18.94708905 +19.77857766 +20.53301489 +20.54637611 +21.16740745 +19.14841587 +21.08134449 +19.76073792 +20.68069077 +19.82287752 +21.79444068 +18.71720906 +20.70002035 +20.30614 +18.69092416 +18.26402534 +21.19657756 +18.62980533 +18.74602673 +21.9137369 +20.86904341 +19.00194737 +20.36379584 +20.13644735 +21.37789307 +20.91728299 +21.3268845 +18.84602635 +20.56087682 +18.16096343 +18.27576245 +20.85410813 +18.09768041 +20.31906004 +21.36162039 +19.51976723 +18.17936175 +20.8553898 +21.71874581 +20.86868557 +20.31378897 +18.92011532 +18.0615025 +20.77868843 +18.9241047 +21.43265071 +19.86545648 +20.12254233 +21.01574618 +21.11772243 +20.57060333 +21.75372726 +21.92919375 +21.78754178 +21.29671674 +20.82250088 +20.65330343 +18.41500071 +19.08593739 +61.27735984 +63.06877424 +67.17352089 +63.19289709 +73.46262244 +72.15750892 +62.173922 +64.58522653 +72.33937763 +72.12142732 +74.05812748 +74.76664956 +84.99171138 +76.20534154 +73.72509745 +81.61052616 +74.4847153 +82.35533359 +78.12697742 +82.16175131 +74.12165573 +77.72458394 +78.59364928 +84.47703721 +75.80796786 +86.46508976 +84.36847619 +83.19213904 +84.08238476 +81.19667993 +75.44984849 +74.58172798 +85.3462942 +83.10061706 +74.95880091 +81.35044489 +79.06575081 +74.44903654 +80.41310209 +76.41250031 +73.4516581 +79.37596718 +81.34400675 +72.66320196 +78.33936381 +76.03124361 +72.40342096 +73.92178807 +74.20673265 +74.90800573 +84.83367617 +79.01290221 +74.33553107 +73.58708633 +76.74862325 +84.74790392 +77.81267804 +74.54046376 +78.4470271 +80.86557252 +72.46986822 +82.03833079 +78.31785261 +72.15215053 +82.77825684 +83.39419906 +82.05749767 +77.46716488 +80.46981493 +78.19076996 +83.20595361 +81.33337879 +73.30808206 +82.28870695 +82.99501787 +77.93048675 +86.01174677 +85.48740899 +74.80194628 +86.46743084 +79.05319903 +83.57919207 +85.02473587 +80.08102995 +78.38548053 +86.64705554 +75.69351152 +76.11137511 +77.78577568 +85.7512878 +87.02044515 +75.52093844 +85.82175758 +76.13110118 +87.53009189 +81.55836968 +72.11335579 +72.54605061 +74.35972394 +75.00114172 +84.2818736 +75.56422501 +87.50623232 +82.17776136 +83.08024825 +75.48503532 +86.28326789 +82.68500141 +34.3321894 +34.92147232 +29.60409657 +29.41815263 +33.94180357 +34.34320018 +32.53606685 +31.14239753 +32.97682334 +30.75624416 +30.32566261 +31.92412321 +21.12836799 +22.32699094 +24.4455958 +23.92223117 +24.37850518 +23.7723525 +21.749283 +23.79796136 +24.2946612 +24.40271899 +23.2207251 +21.10692141 +18.59473039 +19.03493191 +22.38687991 +20.32474835 +21.93402245 +20.33682061 +21.13870857 +18.5308804 +19.45544657 +20.21895873 +21.01506452 +19.13455056 +21.15928519 +19.55101535 +21.37183578 +18.48015732 +20.81847763 +19.26534519 +21.283204 +19.70280333 +20.76057319 +21.84155968 +18.16930252 +18.13108053 +21.10135487 +20.60533435 +19.19032391 +19.99676528 +18.18518369 +18.7878014 +19.71689555 +21.35666039 +19.93779064 +20.36498164 +21.51565037 +19.25374151 +20.80679142 +21.45078679 +18.81407441 +19.42964243 +21.92344919 +21.93708537 +18.89654716 +18.26929038 +19.08735051 +19.59468929 +19.76781709 +20.47915647 diff --git a/test/data/Timeseries/period_no_anomaly.csv b/test/data/Timeseries/period_no_anomaly.csv new file mode 100644 index 0000000000..ef5a04acdd --- /dev/null +++ b/test/data/Timeseries/period_no_anomaly.csv @@ -0,0 +1,4033 @@ +Value +18.32491854 +21.97032718 +18.62480603 +21.95368398 +21.90911973 +21.17527242 +20.63769185 +20.3112282 +21.46440618 +19.15775809 +19.87072485 +20.47755988 +19.6447619 +19.70994582 +19.32113867 +20.25692727 +21.40229811 +18.80611351 +21.73773216 +20.75635062 +21.29309285 +20.22476277 +21.11806681 +18.06480159 +21.2735217 +18.16055544 +21.55965351 +21.96785099 +19.17626985 +18.84127423 +21.06145346 +19.01210536 +21.46224953 +18.41137037 +18.50382079 +21.91660622 +20.69535559 +21.3875489 +19.29731751 +20.705935 +20.37696731 +20.41260048 +20.73012984 +20.30143669 +19.71624948 +19.10369078 +21.07432532 +18.90508972 +20.76941722 +18.93333129 +20.50132068 +20.98831307 +18.87485556 +18.2397732 +18.52389497 +20.42398787 +21.39778562 +18.17998728 +20.93660504 +19.36527458 +19.91411348 +21.7154382 +19.32788602 +19.86134774 +18.05472966 +18.32639488 +19.03436636 +18.11140896 +20.5255013 +19.70559343 +20.19258081 +18.69858874 +19.18372812 +20.6552979 +21.86121089 +18.20166709 +21.56153728 +20.30752481 +20.25567827 +20.00111414 +18.27757634 +18.35950741 +20.40364802 +19.36383429 +21.66907227 +19.62636215 +18.57312583 +20.85887533 +19.17341369 +20.10188123 +20.79133114 +21.60105644 +21.16876478 +20.70544427 +20.71853273 +21.78369858 +19.18329639 +18.00444838 +19.08781824 +18.87156552 +20.64733834 +20.5348464 +20.37384553 +18.06497377 +20.91511222 +19.29412165 +20.66022396 +20.22631371 +65.86114059 +63.03023723 +62.48409701 +72.50864933 +73.69020657 +70.03711744 +62.60670396 +66.66757015 +71.11357312 +71.80556402 +62.80777326 +61.20041778 +80.89236695 +75.37437265 +73.78955027 +70.04016873 +78.22544679 +83.04985941 +84.70244315 +78.62036163 +77.82443981 +71.1600751 +78.35850588 +75.73727531 +81.22387951 +77.75030384 +75.38971726 +73.07832774 +76.57646294 +73.10503944 +74.38136911 +87.27059427 +78.63102297 +80.02688857 +85.46202908 +87.40152779 +81.22584307 +84.89758164 +77.13837912 +76.79963304 +78.34912762 +82.66080406 +82.81556895 +76.95137836 +74.04240116 +82.0197981 +73.96434994 +81.15837308 +83.07481131 +83.18981359 +84.03538044 +85.94511896 +79.98014403 +83.66390288 +81.88710614 +74.96824234 +72.39365567 +76.53428953 +79.06955805 +81.85404542 +85.59524052 +75.13632475 +74.00524422 +87.41148321 +73.72517397 +79.65203364 +81.36511395 +80.65811896 +73.37299244 +72.90893083 +73.68419783 +81.36549721 +80.70874339 +75.73912557 +82.20359711 +85.12026287 +72.66456577 +79.96931138 +83.02816952 +76.03626088 +76.92359601 +81.81742099 +86.36613557 +84.95797512 +81.32992735 +83.68338681 +77.83701008 +82.25529052 +79.4587529 +75.03883411 +83.22765258 +80.90327886 +77.73779605 +86.5812608 +72.334807 +77.05222248 +72.91007437 +84.2644047 +83.2336873 +77.30160582 +82.81057464 +78.33496554 +84.10002837 +79.26690817 +78.59644546 +86.95564526 +76.01403641 +73.92712905 +32.54106968 +35.00365288 +31.21816497 +29.19702968 +30.97154776 +32.03795644 +29.83630151 +33.01184786 +35.18558933 +31.69112656 +31.0702568 +31.36450065 +20.67585375 +24.11706441 +22.01841773 +21.89498816 +23.11541115 +22.14030851 +23.06408468 +20.43527826 +23.80547927 +24.37345637 +23.06646766 +24.43645402 +19.05226653 +20.38749647 +20.89700013 +20.81479306 +20.07638721 +19.8167935 +18.95147097 +20.03657684 +19.09825847 +21.43996155 +19.28737128 +18.67188327 +20.30646993 +20.47874729 +21.59079292 +20.40540445 +19.4749566 +21.3138653 +20.25788888 +18.40504328 +20.36066652 +21.73590808 +19.67925966 +19.27739262 +18.59170874 +18.62364363 +19.75002777 +20.4022867 +18.34005117 +21.77916229 +21.04071417 +20.3660283 +21.33421588 +18.33833494 +19.92646656 +20.54007206 +21.31870403 +21.13793594 +19.11194579 +21.76769927 +18.50093486 +21.50466258 +21.88796268 +18.71220615 +20.8922748 +18.16462901 +19.62545866 +20.0689857 +20.3225809 +21.74374899 +20.68900275 +19.9251118 +21.23870015 +21.80213128 +18.08826459 +21.93008509 +18.34452856 +21.25754712 +19.12133901 +18.52871863 +19.75942256 +20.57582632 +19.52380738 +18.21826163 +20.39784456 +21.6096131 +19.53286146 +18.86533111 +19.77030938 +18.19345084 +21.28195034 +21.30536324 +20.35200185 +19.41241156 +21.19913346 +20.2187481 +21.30372923 +20.52583517 +21.13762189 +20.39425197 +19.65656427 +21.83178344 +20.1644553 +20.42035807 +18.88195056 +20.50260316 +20.2864306 +18.73935359 +18.23834839 +20.41743728 +21.05476908 +20.09360548 +18.9070905 +20.66608791 +18.32042291 +19.76733482 +18.65346703 +18.7378362 +18.8091758 +19.54682823 +18.20460082 +19.59117376 +20.04682979 +19.93317714 +19.5311364 +21.35658643 +18.58130677 +20.02278698 +18.24816567 +18.28462272 +20.2954634 +20.2640737 +21.51213405 +20.2314947 +21.83916408 +18.19477939 +18.39203074 +18.17626345 +18.7411074 +20.16508044 +20.58228873 +18.18234063 +21.78388356 +21.36866151 +19.18644993 +18.30875012 +18.67954829 +18.50777766 +18.49313512 +20.07424675 +18.98338977 +19.43255887 +21.95802133 +20.737257 +21.79592267 +18.57022622 +19.5285579 +20.21892721 +18.30703225 +18.01674977 +20.68173734 +20.56727807 +19.64315524 +19.9585763 +19.661772 +18.09602018 +19.34148642 +18.70832902 +18.39369676 +21.82688174 +20.3974389 +20.91462514 +19.26145497 +19.57258852 +18.94769598 +18.38786268 +63.63483027 +72.04619102 +70.41462295 +68.63823271 +67.66122227 +73.74510626 +62.18919529 +65.01617106 +65.95915481 +71.84099105 +74.69819883 +64.473119 +83.40924983 +82.73126524 +73.32668281 +76.03764164 +76.19812293 +85.02198478 +72.64744231 +82.24293171 +75.02667092 +81.18310777 +76.35857079 +78.81614352 +84.80985764 +84.35744915 +85.34759431 +72.51615394 +82.574372 +73.79435924 +78.18117851 +80.29275015 +77.97316877 +79.83190409 +87.41675442 +73.71849616 +82.70792797 +78.24201224 +74.03920299 +74.45034047 +87.08374366 +85.9703356 +86.40885616 +87.75908698 +75.24264658 +77.59380496 +82.60825547 +79.64515163 +78.67534592 +77.70632599 +81.48092999 +81.19000458 +74.56542535 +79.52874592 +80.83614309 +81.10512112 +75.34362828 +83.85621195 +72.38580875 +77.65866713 +84.47934717 +81.01839767 +76.17494235 +83.11526019 +81.07436335 +84.73583024 +83.75638248 +81.75780219 +79.80701188 +74.12735573 +76.17767158 +78.7018137 +81.58800534 +80.21855558 +76.61021695 +72.10273753 +79.93906013 +76.56928074 +83.75292878 +72.38244537 +81.35646395 +87.06275199 +74.78983028 +79.54562821 +73.46261082 +82.01075314 +80.80835174 +78.51769763 +80.34187809 +86.34502875 +75.14056457 +72.36089325 +85.78647344 +81.23371735 +86.25036969 +81.550967 +84.96254481 +78.87474353 +83.99772665 +86.60863021 +81.15393626 +74.89253365 +76.30073254 +75.17828436 +75.94968328 +76.90261766 +80.88210953 +81.40783481 +31.52959233 +32.73868671 +29.32207389 +29.36183908 +29.89970281 +32.11568982 +30.16956015 +30.6130143 +31.36199613 +33.99593667 +28.88871212 +32.95601812 +23.15656559 +23.73797659 +24.33873183 +20.25021645 +20.84894986 +24.13004607 +22.21416194 +22.68936482 +23.1303606 +23.20221712 +24.33419567 +24.62633204 +21.3817647 +19.60845784 +22.03546558 +18.63360864 +19.37850694 +22.10769163 +19.48136021 +19.33578651 +18.99776097 +21.9509215 +20.48023838 +21.19636231 +19.04725556 +21.13311601 +21.14101423 +19.34279393 +19.73701559 +18.92918712 +22.01139401 +20.68120583 +21.84251871 +19.30576713 +21.22897591 +21.16453901 +20.78655856 +21.88354394 +19.59301259 +18.54065124 +20.70669479 +20.93731974 +20.31973052 +18.84534574 +20.03704087 +19.26723775 +19.72878757 +20.60063193 +20.98488729 +18.93253311 +19.62506873 +19.080309 +18.798135 +21.13760652 +21.64116508 +19.48466908 +21.00914868 +21.59212605 +21.38860266 +18.73484995 +21.82516753 +19.50670576 +19.35696264 +18.24972531 +19.94469462 +18.43605446 +18.99415928 +19.26777049 +21.23530529 +21.5019546 +19.20986527 +21.43479249 +21.42892375 +21.0586038 +18.58678046 +20.76129139 +21.78009626 +20.73016192 +19.3264674 +20.00231727 +20.58290017 +20.04228163 +18.47981177 +19.24825618 +21.20958412 +21.44567218 +18.54920013 +21.80954701 +19.31529047 +20.64925495 +21.00730058 +21.24778494 +21.78299881 +19.25167819 +21.43402582 +18.52674761 +20.82016363 +20.96791092 +20.71089296 +19.00925894 +21.49217259 +18.67543385 +18.21453917 +20.86985996 +19.90190286 +21.39639208 +19.53358856 +18.67311435 +21.34576877 +20.19563853 +18.75678317 +20.88153599 +20.04445455 +20.41725193 +19.84359335 +21.31483594 +21.31825086 +20.83755529 +18.42160551 +21.35541545 +20.68405192 +20.78809666 +19.45193136 +21.51711822 +20.9014864 +18.45036187 +19.63111214 +21.54552145 +18.18878681 +21.84566648 +20.71665027 +20.52550391 +19.08223703 +18.99516716 +21.76823288 +19.19308982 +21.10202119 +19.57248719 +18.55701628 +18.04286119 +20.17702935 +21.90311527 +19.47654478 +19.42876934 +19.37636647 +20.08217535 +19.76265953 +21.51965851 +20.02208317 +20.03103135 +18.55348638 +21.93095816 +18.99989156 +18.29345627 +20.652585 +20.38146548 +19.66058262 +19.68876894 +21.14025568 +18.78285056 +21.28101328 +20.59498846 +19.6758791 +19.83211447 +19.02658883 +21.00845733 +20.05149897 +19.28341817 +69.30628157 +74.74198855 +67.119567 +73.72140305 +65.89222902 +61.94436481 +63.97769265 +66.89425395 +64.18703503 +61.20308772 +73.64402091 +72.67675999 +81.36037543 +72.44448252 +71.7080427 +70.88922571 +81.07693301 +84.25509955 +76.52191594 +71.68906323 +77.79233658 +75.2538712 +75.65728283 +79.48305129 +82.99849287 +73.32703964 +72.13402703 +87.38182913 +75.26931166 +72.42412067 +74.58777723 +72.18758559 +77.79936047 +84.32939798 +76.99515173 +78.6322929 +82.71001711 +80.04613436 +85.68019122 +85.75354292 +72.07198084 +84.34740241 +79.27649152 +87.71606292 +79.63603516 +87.68498672 +83.97222205 +75.88650494 +80.59497223 +87.85308043 +85.69991681 +73.54021641 +83.28195948 +77.51066635 +72.09440938 +78.43686614 +75.73788929 +85.08148387 +77.37871439 +85.21992141 +72.00611056 +76.921414 +75.85500221 +75.7334424 +73.08747755 +74.99038862 +80.88491046 +76.6580974 +78.67453996 +78.02612465 +86.01035011 +86.48063271 +79.88654463 +77.28175329 +73.70810366 +87.25360843 +72.96293067 +76.76166752 +76.76832224 +77.12568016 +74.62787306 +84.5297714 +73.95925578 +82.15771782 +73.98139438 +73.12842979 +86.55070588 +75.04191618 +85.139839 +80.71393897 +73.24556368 +87.28700347 +75.95123285 +85.75416088 +74.70081667 +85.66292164 +72.21163328 +80.18845651 +84.3110489 +86.95669489 +86.54720095 +85.11396523 +86.21102849 +75.1874861 +76.54691934 +76.62346698 +78.03283293 +78.29310066 +32.28514788 +29.82322476 +33.22397227 +29.87242012 +30.78596837 +32.02140269 +33.89721309 +33.53259665 +32.93141356 +29.79694315 +32.30108401 +29.35965197 +21.85058583 +22.01255285 +20.33835413 +21.38746551 +24.44130827 +24.43227274 +21.73842401 +20.35308737 +20.96234418 +21.91713451 +24.40605171 +20.79257066 +21.55936064 +18.87716345 +22.45123077 +19.99510094 +21.60736041 +19.83525284 +21.00293147 +19.21849765 +19.03010187 +22.27431045 +22.19068298 +22.06707251 +20.60014207 +21.33499154 +18.36387321 +19.43198407 +19.34723555 +20.64118563 +19.51706544 +18.68327841 +20.48372042 +21.3595718 +19.6222307 +18.61801437 +18.80478806 +18.96812515 +19.21802197 +20.59092861 +20.42336956 +20.80427238 +19.46867994 +21.31643475 +18.83441433 +19.8952441 +21.22411048 +18.73860639 +18.44299283 +21.53887054 +19.37543802 +20.73946171 +18.10486988 +20.76417351 +19.26810401 +18.22646222 +20.44317538 +18.9668562 +19.55098203 +18.32584113 +18.588653 +19.19190894 +18.359038 +21.64330506 +18.02418817 +19.93544088 +18.49298983 +19.23071636 +21.70423824 +18.64677821 +21.86332928 +18.82317751 +20.96626443 +20.18670906 +21.53791491 +20.21597302 +20.13823688 +18.781832 +18.66491745 +21.6659566 +19.3113528 +19.48832902 +21.03929463 +19.35529393 +21.80824979 +20.95171108 +20.38971697 +21.52358064 +18.19842363 +19.02825077 +19.86014534 +18.90791416 +20.97562075 +21.67683212 +19.69508048 +19.3685304 +21.84620293 +19.77940973 +19.9561936 +19.40659949 +21.63819389 +20.27582193 +21.04869835 +21.24722687 +19.36739617 +19.73477733 +21.3938871 +19.45302141 +21.17574053 +21.76396929 +20.49284149 +20.96650915 +21.22038925 +21.3801586 +19.58021335 +18.20505384 +19.60750508 +20.69502492 +19.80140154 +20.9958526 +19.42792972 +20.53897656 +18.74567922 +20.99747775 +20.18278388 +18.81625755 +19.16764469 +20.99443483 +19.93040494 +18.86412576 +21.86908476 +20.86050008 +21.82853933 +19.70842018 +19.90040136 +21.59182086 +20.10019736 +20.82911565 +19.00869151 +19.78690587 +18.49379946 +18.82392014 +18.00377366 +18.15706021 +21.90913529 +18.96941478 +20.65324284 +21.3563988 +20.20264184 +18.612578 +20.91246575 +20.40100564 +20.92500236 +21.07898649 +21.90164147 +20.29425312 +19.36656445 +20.59385823 +18.27376284 +21.58958927 +18.47752405 +19.31137135 +21.26298152 +20.38924951 +19.57454208 +19.89374264 +21.41818957 +19.36001754 +73.02723571 +62.39862826 +71.76445875 +72.72664781 +63.67272008 +67.05271284 +63.45079694 +70.80981615 +68.48072965 +69.84039752 +63.87222275 +64.07974372 +70.48403849 +74.83848815 +78.52723714 +83.15514987 +80.19071398 +76.59735094 +84.63242418 +81.00268551 +84.27616395 +78.02816535 +73.85819434 +70.65987017 +83.11565348 +73.49720288 +76.38333564 +80.03641732 +80.54448141 +81.11967516 +74.21212477 +77.60734369 +81.38385699 +86.99199922 +83.14024561 +86.24109015 +84.09435846 +81.36971054 +74.98228706 +82.56777007 +81.87570417 +81.54153932 +79.74077 +80.36195546 +77.25825914 +80.21263228 +75.07070142 +84.78093101 +74.95443789 +73.34185437 +78.96098124 +82.51311113 +78.98450837 +76.40972338 +80.91131556 +77.53342488 +86.90502083 +86.74149928 +80.01874001 +77.22760483 +83.79009651 +72.5839229 +79.59248347 +77.37746001 +86.72441119 +72.18858322 +80.84115745 +83.84659247 +79.75645931 +73.36292689 +87.55517939 +80.28431307 +81.82619437 +75.78502898 +79.7352039 +78.86563475 +73.19763077 +73.69785815 +85.39874048 +75.8363912 +75.1186112 +80.07781447 +84.29914482 +72.99044358 +81.22877075 +73.90722518 +72.58323745 +72.84376337 +85.34646187 +73.88741787 +72.7222822 +79.0062465 +85.50373138 +76.19838772 +78.74725069 +72.63862105 +79.18522582 +81.24177809 +81.1344817 +77.31879738 +77.0485013 +73.7059496 +77.86874323 +73.59188644 +84.26550276 +87.46035518 +87.5211577 +85.84110463 +32.64506349 +33.28553407 +30.62531126 +32.83692438 +31.71615103 +31.88490485 +33.64976353 +30.6042716 +30.83544759 +34.7177008 +29.15982989 +31.90805338 +24.24934626 +22.89442502 +22.60790245 +21.27885276 +21.697873 +23.97717559 +22.66529599 +22.4660438 +20.60611599 +20.23180503 +24.4773083 +21.84889683 +20.90276045 +18.75851271 +21.23003586 +22.25179297 +20.78992463 +18.94856813 +20.81473113 +21.57095793 +20.09668189 +22.03581515 +21.08900512 +22.1153233 +20.5923925 +18.61407257 +19.33111288 +18.89745421 +21.73915541 +21.1425999 +20.80498027 +19.29604378 +18.8261127 +21.12672816 +19.99213557 +18.99389332 +20.48613573 +18.17877028 +19.3232135 +19.89330723 +18.60968149 +21.95967897 +18.85447238 +18.53919307 +18.83220106 +21.09647772 +19.72093657 +20.28111495 +18.08750876 +18.14120768 +19.78710059 +21.27070416 +21.54509138 +18.35034886 +20.15732187 +21.6570761 +19.55564498 +21.32235643 +18.53834129 +21.74575234 +21.00569327 +21.76533757 +20.70646893 +19.45361573 +21.75272469 +19.10226711 +19.32869009 +20.80323367 +21.0612652 +21.71761394 +18.81920964 +21.19199214 +20.95418421 +18.25517823 +19.54889256 +19.13106183 +19.2179617 +21.93178752 +20.57110965 +20.87070463 +21.90647055 +19.50880713 +21.20736067 +19.73812385 +21.47813469 +18.72263017 +21.79074471 +18.87508683 +19.30488203 +21.02476894 +19.57502057 +20.43284551 +19.778294 +20.96947975 +18.91332501 +18.23383195 +19.19971759 +19.89723466 +18.67208021 +19.41918916 +19.59993207 +18.22956737 +20.33322899 +21.53737371 +18.60697165 +20.39123205 +20.65895682 +19.67733872 +20.80439035 +19.64292617 +20.01854915 +18.02941218 +20.76688046 +20.64537072 +18.15360351 +19.47300043 +21.91612736 +19.68294625 +20.00778162 +21.63972063 +20.8690475 +20.32828052 +21.19121873 +21.45746507 +19.82755229 +19.90043346 +19.54420374 +21.86682456 +20.78866502 +18.33289226 +21.45183849 +19.92223976 +18.27501563 +20.19886902 +19.66865712 +21.51099762 +18.81597841 +21.30585844 +20.23075476 +18.21844121 +21.88738121 +20.05046098 +19.16350742 +19.87762737 +21.43143113 +19.2196125 +18.77070559 +18.51628499 +19.19143759 +21.12019235 +19.88650546 +18.90992641 +18.66474 +19.33091791 +21.75969749 +19.37210685 +21.9524963 +19.94092182 +18.71665567 +21.56456037 +19.84142149 +20.83868537 +21.93484633 +18.2007014 +18.38677824 +20.88182697 +19.12730001 +18.73720559 +61.73532157 +61.49711528 +71.7330042 +63.02829461 +67.56100038 +69.44019669 +67.02558449 +61.56865047 +71.85562044 +61.34297985 +71.56170256 +73.34894112 +76.55767557 +78.15161918 +77.24278225 +75.88051776 +83.61282879 +83.71384809 +75.05186998 +79.38588258 +84.22560536 +84.7711479 +77.73904431 +76.86784098 +76.0003544 +86.81789827 +73.8393187 +80.78960958 +76.06167945 +76.00534785 +78.00862041 +87.45280285 +72.44218414 +80.76912908 +72.8812276 +74.02549928 +78.93816746 +80.07116127 +76.04930972 +76.97083812 +73.30340256 +76.75929472 +87.35997711 +87.01077973 +77.26522298 +81.03630258 +86.44966203 +72.01242243 +84.9505441 +87.15299111 +73.44358106 +74.56526302 +87.35097158 +80.58129814 +72.20981164 +76.10384267 +79.180091 +83.06660181 +83.71047071 +87.90564179 +76.59650238 +80.60765228 +80.52931117 +78.48655662 +74.70300439 +87.63509832 +85.64896364 +79.96948973 +81.64800747 +75.63254632 +80.6967195 +83.11553239 +80.24190212 +87.84371942 +81.7806814 +77.93483122 +84.35362264 +74.7266828 +78.15150276 +77.55059871 +78.908156 +84.50766593 +79.74600295 +84.70341804 +81.2236665 +77.47296221 +84.95949678 +75.81248917 +75.58998254 +80.07189305 +76.17344168 +72.14525404 +82.37260228 +80.1773472 +79.12813311 +87.47132829 +81.13915579 +85.81487229 +74.23117527 +81.10727226 +85.88764105 +81.32966089 +83.48819479 +77.95266715 +73.47916406 +79.32637426 +78.31164018 +85.87292994 +30.36156411 +34.98709234 +34.93207726 +30.38483668 +31.02752656 +34.53155011 +34.9863755 +31.63800418 +30.85437517 +32.16699551 +33.20171655 +30.55023103 +23.45939854 +20.39142384 +20.38083821 +21.89056772 +21.74355871 +21.57403562 +22.35976731 +21.59688526 +21.02131432 +21.5464224 +24.5275698 +22.0017869 +20.25354329 +20.79352803 +22.45976352 +19.4215986 +21.22253127 +21.36467305 +20.91960056 +18.79481665 +21.20446859 +18.69453171 +20.87380004 +21.46132424 +18.93433014 +21.46904712 +18.20839797 +21.47785261 +19.93655447 +18.9609294 +18.62408107 +21.91793211 +18.37845748 +18.88659623 +19.48436753 +20.57924785 +20.19857637 +18.52033643 +19.17730448 +18.97320388 +18.87227141 +19.66343613 +20.83610928 +18.88792674 +19.48540356 +19.58245082 +21.9268557 +21.83961134 +21.20462083 +18.97027728 +18.27565751 +19.27131593 +19.07083909 +20.02754775 +21.11496493 +21.43740695 +21.3835612 +19.44243447 +20.75292117 +19.25503304 +18.24362217 +20.80626106 +18.03931021 +19.63673035 +19.31751708 +20.77351015 +21.06498189 +19.21637854 +21.24650806 +19.17566704 +19.78530544 +20.90807411 +20.77168092 +21.94947389 +18.9006441 +20.64927165 +20.43872763 +21.66039382 +18.62544857 +21.00719265 +20.27307447 +19.09866348 +19.89773966 +19.33510639 +20.31725361 +21.55600244 +20.27721994 +20.52688967 +20.07499139 +18.20466203 +18.93143423 +19.96800692 +19.24094114 +18.72705439 +20.01653137 +19.29123917 +19.03243057 +19.85594167 +18.53313703 +21.71098293 +21.47080839 +20.83048234 +18.42485531 +21.10350902 +21.23182931 +21.07105847 +18.45586355 +20.57563183 +21.33729918 +20.3094136 +20.5856295 +19.83345392 +20.11482928 +18.13750743 +20.55585209 +18.32578885 +19.74005838 +19.17062039 +20.1817118 +18.53366534 +20.76657361 +21.18004615 +18.6534178 +18.41725235 +18.58206193 +21.50851025 +20.11275705 +19.86904398 +18.42684728 +20.59822403 +21.47199837 +21.31274348 +20.50421884 +18.30111487 +18.44958811 +18.19723595 +19.02991687 +18.54264587 +19.04019216 +19.54023222 +21.77193114 +20.44150424 +21.33989514 +20.841406 +18.45001252 +20.94368163 +19.10039553 +20.55643139 +19.36384789 +21.99068905 +19.05420592 +19.11652188 +21.72268902 +18.09857762 +20.78675595 +20.90130556 +21.34551431 +21.89775724 +21.90240768 +18.26110703 +21.28735729 +20.58523973 +20.58936854 +18.51339971 +20.60074867 +21.92993032 +21.64727428 +19.75652264 +61.74329244 +66.12719146 +72.43890824 +70.74038778 +67.44707372 +61.94938088 +67.0057683 +67.57873918 +73.82835525 +69.91877101 +65.43847967 +68.3443284 +70.86517838 +84.93425902 +75.42812968 +77.25650135 +75.35400728 +80.43258205 +80.1178431 +80.94180226 +74.00158292 +74.73917483 +71.78389911 +80.01351476 +72.20093351 +81.07353986 +85.39964335 +85.73579449 +78.70949654 +78.87276586 +72.79211789 +82.23584696 +84.62930982 +75.45899399 +78.26836922 +84.90890081 +85.93068677 +75.79981063 +80.7121592 +74.98979454 +73.54170415 +86.47403451 +85.02596632 +84.05956576 +72.69292479 +85.39706109 +78.91389477 +77.38534799 +80.72424331 +73.44402109 +84.08615927 +81.29944468 +74.96744504 +74.99371238 +74.92999091 +72.89453931 +81.38523804 +85.4250639 +72.26262423 +72.76660614 +81.16042854 +85.10784559 +77.22411531 +77.59010588 +81.53762997 +76.38373963 +72.42225742 +83.75963563 +82.00646211 +81.28819396 +83.86292045 +82.79971882 +75.29338899 +77.63895141 +74.0170517 +74.07700107 +82.81833688 +85.73677863 +87.40288227 +82.05276617 +74.9449017 +73.6896981 +85.00479949 +81.25932352 +81.8924031 +84.50323117 +83.2522758 +85.35116817 +72.89928252 +83.96016898 +85.61347974 +76.57801365 +82.24699847 +77.02644513 +87.32736939 +72.53463789 +81.27665429 +74.66402808 +83.39549126 +74.81421581 +75.7661031 +79.90990229 +86.63732593 +75.38740101 +74.3078118 +84.143597 +80.00988179 +86.58852855 +29.73832746 +32.93393219 +30.29042072 +29.80326102 +33.17394189 +32.59957531 +33.63088928 +30.78055458 +31.66701252 +31.3950127 +35.09816583 +30.67038743 +21.48078005 +21.74837478 +22.68938024 +21.29040473 +24.2604976 +21.35027016 +24.17325315 +20.62356715 +23.10139964 +24.22329379 +20.91599634 +24.49810344 +20.28701 +22.30564882 +21.20425464 +19.66729958 +19.20328691 +19.67609851 +18.81299908 +19.02128296 +18.86068894 +20.16437651 +21.69068877 +19.01110681 +18.61018384 +20.67246958 +18.8789178 +18.47871422 +18.75699776 +21.79906117 +21.38805721 +19.50945624 +20.72227478 +20.24581834 +20.80789779 +20.83019185 +20.53463039 +19.08270729 +18.17345546 +19.84129649 +21.58459687 +20.29745589 +21.35004054 +21.9875852 +18.67237309 +20.78704697 +20.71704357 +21.94086481 +21.94661588 +20.75069781 +18.97810226 +21.19829264 +18.43165473 +21.12685279 +21.64493953 +19.53508359 +20.52648151 +19.72435728 +19.75485242 +19.24517792 +21.04851848 +21.74470626 +21.76300344 +18.69912514 +21.4989746 +20.94972577 +19.0113496 +18.26913845 +19.07198907 +21.64883295 +20.5327147 +20.88421864 +19.0286094 +20.87755262 +18.11248603 +19.50128538 +20.50701339 +18.58438626 +21.2920143 +18.9401252 +20.60912445 +19.62078857 +20.38417438 +18.42980221 +18.0676512 +21.63825771 +19.07852004 +21.27870801 +21.9974012 +18.84572503 +19.06068296 +20.65143915 +21.86179454 +21.66325739 +20.01150208 +21.18044113 +18.54298341 +21.90975274 +21.85741294 +18.65491583 +19.1976925 +18.40061999 +19.0868704 +21.26382316 +19.51326243 +18.35358787 +19.3689021 +21.92553334 +20.51873438 +19.66464379 +18.66615405 +21.94470719 +21.28339604 +19.12590921 +18.28027478 +18.22650223 +18.4815126 +20.32307433 +21.71505368 +19.98697371 +21.37178193 +19.78354648 +20.83433418 +20.92749095 +18.69585482 +19.44486424 +20.54920863 +18.28278643 +19.44616075 +20.13919787 +21.98489797 +19.89813425 +21.26422703 +19.72826914 +21.18556289 +20.38165921 +21.54638177 +19.64377021 +20.41721768 +20.52007472 +19.66953177 +18.57724817 +18.37423763 +18.0666862 +21.49413706 +18.94589965 +18.846027 +20.9441416 +19.28006445 +20.02417877 +18.65004426 +21.83898289 +21.4063256 +21.45515849 +19.62709499 +21.04944485 +18.57550462 +18.03447601 +18.07989173 +20.34640695 +19.82157962 +20.0720614 +18.02985256 +19.69074648 +20.87227264 +19.77077481 +20.92887683 +18.93207586 +72.25873126 +69.20266487 +64.06637815 +64.41939847 +69.74121432 +72.48717847 +66.85253784 +64.71288584 +72.99135785 +70.30204877 +67.29396263 +67.83544266 +73.01006038 +79.56797682 +75.40768376 +85.29241314 +77.29244816 +77.38094461 +78.62201974 +84.06805317 +82.26869691 +76.84153868 +80.99421261 +78.93837494 +79.39860554 +74.62659005 +78.94644233 +75.47749881 +86.11764955 +76.90230996 +86.71280689 +80.04571133 +81.6120818 +80.20292483 +73.62566933 +77.54506067 +73.49803419 +81.86890399 +79.05500714 +84.34642223 +75.43689466 +74.54349727 +78.40711412 +86.72117569 +83.26670894 +78.60833466 +80.18002309 +73.25939129 +87.73215357 +82.64267787 +76.54808914 +81.3534048 +73.73401744 +87.43239789 +74.77860863 +82.34871665 +75.90904886 +83.46094669 +84.40887888 +82.75484973 +81.82806343 +86.27536409 +73.25451099 +77.29092611 +75.96895299 +87.8040086 +78.22648142 +83.71778454 +82.95162075 +75.38968092 +75.82032333 +76.49688099 +78.5772821 +75.74847386 +72.31372951 +86.06946263 +87.44891778 +77.46438253 +77.41536688 +72.11277672 +82.88321533 +85.20622034 +74.34501909 +74.55394587 +84.64116949 +81.8604948 +72.7176293 +81.96059448 +76.94253584 +77.89945292 +84.31554081 +84.97479993 +78.40555549 +87.15427964 +78.50325276 +84.34921461 +79.74200561 +86.36048406 +86.52414667 +73.39864074 +86.9838599 +85.20852474 +84.66093892 +75.21455255 +84.88187475 +85.60209088 +76.62637593 +87.23612776 +29.11909935 +29.75941351 +32.24051309 +32.48506558 +32.92895421 +28.90859688 +34.94634065 +29.08535821 +29.71559079 +28.88968737 +32.42788775 +34.76650138 +23.14344203 +23.84550415 +20.21949868 +22.5882273 +22.21884092 +22.39537516 +20.48351395 +23.22626914 +22.41442623 +23.58786555 +22.33410082 +20.82922859 +21.08726023 +19.13809431 +22.00532787 +20.94332386 +19.07590688 +22.37373642 +20.55522469 +18.73059183 +20.99950732 +19.46910249 +21.72392812 +21.78179895 +22.02098516 +20.10351397 +19.91496264 +21.1147691 +18.61882602 +20.28456278 +20.2818783 +18.44601033 +19.76636159 +21.51592221 +21.52745258 +18.48525598 +18.38051189 +19.06907065 +21.51970334 +18.53338532 +20.90003199 +18.42233095 +20.51222425 +19.44291649 +21.17596527 +18.95293916 +20.57654184 +18.96091515 +20.85864522 +21.49883912 +18.51076058 +21.4705043 +20.37821288 +18.51199558 +19.71395177 +18.64702071 +20.80538542 +21.03951135 +18.42672333 +20.02840412 +21.25850022 +21.74465217 +20.05407147 +21.80111689 +20.14275588 +19.57676198 +21.39281251 +19.97193084 +19.88761004 +19.96124996 +21.38714001 +21.32776617 +19.61407997 +19.33319944 +19.9677121 +20.29109004 +18.95334884 +21.17582034 +19.94593106 +19.33618219 +18.42728397 +18.95538064 +21.79712469 +19.22039875 +18.67543716 +19.84269117 +19.22835097 +18.140637 +19.29258087 +20.1764345 +20.20684949 +19.42623568 +19.78921103 +21.86207167 +19.50939391 +19.03290725 +18.97386818 +19.83105845 +21.03808059 +18.82395274 +20.83718451 +18.08459929 +19.67573191 +21.10346026 +19.35493182 +20.25494841 +18.81991693 +20.96798156 +18.20911641 +21.41017058 +18.79278555 +21.49759213 +19.41011697 +20.8907343 +19.27690119 +21.62364668 +18.5065485 +18.71407916 +19.9721205 +18.32775376 +18.78392074 +21.77506553 +21.90595864 +19.50503732 +21.69314408 +20.1792987 +19.07896986 +20.80959488 +20.63560723 +21.95650128 +19.06018915 +20.68823801 +18.20809502 +18.45458415 +18.0156735 +21.99784272 +21.20909479 +21.14689206 +20.0389043 +19.20026833 +18.60505714 +19.13869896 +19.50388489 +21.02524039 +20.15864716 +18.30993451 +18.07211604 +18.40932872 +19.64199485 +21.73277167 +20.54960992 +21.2073036 +19.86549838 +19.44666433 +18.43360149 +21.50373209 +21.13264012 +18.67247239 +20.04904226 +19.94949923 +20.50036621 +21.78474758 +20.31908577 +20.16086907 +18.20129173 +18.2516299 +18.74938717 +18.12988309 +71.30959938 +71.03729213 +63.05036479 +72.57067305 +63.30509007 +65.29598798 +64.58608583 +66.26337654 +64.66342722 +67.8497326 +66.74074719 +74.61734507 +70.70797695 +72.04665496 +70.65985787 +79.55305535 +70.68598399 +82.82154472 +77.78528755 +78.01915833 +78.55594676 +72.49102872 +72.9081535 +74.78804485 +81.006967 +74.90971311 +73.41813979 +86.99673896 +71.91759878 +84.33409953 +84.10091102 +76.29489327 +73.7250057 +73.884078 +82.65995244 +81.79119602 +84.42250207 +86.89529912 +82.97115281 +74.40423224 +75.80400777 +85.37252957 +75.25847854 +73.07256464 +83.15257789 +78.34629318 +86.61657607 +82.30519624 +73.42907644 +87.14478531 +76.11864336 +77.37682495 +78.41734194 +72.80211204 +79.92506014 +73.95534231 +78.3967055 +77.59272988 +84.34519363 +84.83036401 +74.4510323 +77.86108149 +77.96990784 +74.41899482 +84.77118687 +83.3116541 +75.04250637 +77.8638129 +73.68934314 +82.04736709 +73.93840158 +87.7707335 +84.49010208 +78.24306565 +81.25624285 +86.58063788 +80.86481218 +74.79125778 +78.05850432 +85.48949713 +82.3066181 +82.410799 +85.86831943 +86.30274041 +86.71651465 +73.51744594 +80.70586108 +83.84799135 +84.11066803 +81.70730235 +74.668454 +84.75856613 +83.52162097 +73.37803146 +79.42080471 +74.78349481 +77.39960301 +72.28926972 +78.40524779 +80.79799971 +81.91329003 +82.6158138 +85.38062822 +75.44737754 +73.92768077 +85.62456187 +74.50577643 +87.59612766 +34.1922381 +32.00560413 +35.08109038 +29.42320542 +30.73961497 +32.88462994 +31.040038 +34.74157766 +29.22000135 +32.31622952 +32.58153786 +32.97745366 +21.30538351 +21.7932564 +23.93968746 +21.03334245 +23.86436665 +22.68069505 +23.57459876 +23.35695972 +21.08965034 +22.70827981 +24.04739841 +22.09587953 +22.39115553 +20.28427281 +19.01684974 +20.02712421 +22.33017279 +19.74157636 +21.9877298 +22.04160253 +19.86636377 +18.77302314 +21.39978894 +19.70795414 +21.23312654 +19.86551832 +20.03678404 +21.21297344 +19.62319465 +19.86741738 +19.59838468 +19.56281495 +20.20028443 +21.33712668 +19.54041915 +20.19348452 +20.70276488 +19.09607242 +19.1284369 +21.7545168 +20.91419666 +20.32824309 +20.61064745 +18.13459027 +20.49206537 +20.057272 +20.20237439 +19.45635549 +18.32941416 +18.77696142 +21.80716502 +19.63856813 +19.87054703 +18.21433564 +18.15374977 +20.87273258 +20.15624791 +20.03867062 +18.97614084 +20.98303089 +18.67294632 +18.75973897 +19.84284869 +19.14511945 +18.98985192 +20.58095058 +20.60388698 +21.29806828 +19.67083427 +18.29234036 +19.09466554 +21.92002185 +20.3850343 +20.83894834 +18.02943132 +20.07282293 +18.15011463 +20.27692085 +21.6830834 +21.87367652 +20.1622107 +19.83281922 +21.78006838 +20.69443933 +18.25959712 +19.86290231 +18.96098007 +19.87921729 +19.82186229 +20.32857921 +19.42708196 +18.6570818 +20.01487774 +21.32081154 +20.65604891 +19.35347146 +21.74209209 +21.64883029 +18.8373936 +18.15767034 +20.96523483 +21.31044105 +21.87145826 +21.55934558 +20.40213907 +20.12020936 +18.11136421 +18.98662579 +19.83845901 +20.5140156 +20.22900733 +20.97054396 +19.11906772 +20.25981043 +18.35256897 +19.65466209 +21.00064333 +20.77360341 +21.96257095 +19.1115645 +19.9927572 +19.31812166 +19.76241246 +21.42780052 +20.7368363 +21.52976477 +18.81404443 +21.49344447 +18.2390491 +21.55439712 +19.31629512 +19.25835053 +20.04835431 +21.76276075 +18.19112546 +19.4089239 +21.42628795 +19.58421791 +18.79662759 +19.69917673 +19.54221001 +21.66166481 +21.0477375 +20.02046222 +21.95348227 +19.86111013 +18.23295132 +19.04909729 +19.74428192 +18.6276105 +21.79026051 +21.06739559 +19.0731488 +20.36321813 +20.88579663 +21.76511572 +21.5265414 +20.0607689 +21.09861048 +20.24857902 +20.21081556 +18.45896783 +18.50455063 +20.11813446 +19.07544618 +18.08414761 +20.89135682 +18.12994639 +69.29576504 +68.5330356 +74.37587979 +67.7852987 +62.06497953 +68.70295636 +72.63890792 +74.75715027 +67.05266716 +65.5560629 +73.53048484 +63.80089296 +79.39145676 +76.13668786 +70.4775983 +75.22991712 +73.95634667 +80.48813471 +84.56522852 +74.00009235 +82.43395138 +70.69918436 +78.19163808 +70.93493699 +86.42286069 +83.75605077 +82.03203539 +84.67124805 +81.86615483 +86.97809169 +82.9227872 +86.27377053 +79.79246561 +73.42979242 +79.96813848 +73.58578546 +81.38211979 +79.6242251 +81.86285292 +72.07415879 +75.02730693 +79.76545337 +87.10340732 +78.04397935 +78.69540447 +83.33501526 +81.5626913 +76.69067902 +78.55420479 +75.46508081 +87.43535382 +74.07236847 +77.9306807 +72.39604433 +75.53034024 +84.4672612 +75.2007512 +73.263427 +79.57214898 +72.14218947 +73.35273737 +84.91793766 +84.33952503 +86.6380623 +85.66813471 +82.92682833 +72.83382953 +84.52152066 +85.52406762 +84.78124464 +80.13181805 +75.72279455 +85.52445857 +83.63380206 +87.69133695 +74.49036645 +75.60990747 +75.38804667 +84.67982335 +74.98979683 +73.06585584 +87.97612833 +72.39638037 +85.36308547 +77.84572 +85.46568326 +80.87377258 +86.42593373 +86.81892109 +72.14543238 +73.20645426 +76.00559374 +82.70888786 +86.21218962 +79.35891885 +81.71141297 +75.01596884 +84.87943675 +87.47721619 +76.29755363 +79.91718454 +73.37782959 +80.94615542 +80.04450433 +74.31095521 +79.07302905 +77.34564473 +80.34926315 +32.5481815 +28.85436616 +33.67043183 +33.36328506 +31.14422689 +29.13878439 +33.6315618 +34.91311432 +30.33696439 +30.8705516 +29.97639674 +32.49238495 +20.8536883 +21.75574767 +23.28261703 +22.66931836 +21.89135109 +22.04271701 +22.67825551 +20.18627287 +20.51172309 +24.10803482 +20.50889675 +23.32647883 +22.20047605 +19.54071981 +19.94953269 +20.76230183 +22.04347968 +22.29004502 +21.46125081 +18.78679432 +21.17285517 +20.1126038 +20.35999105 +18.63331158 +18.8543169 +21.26363224 +19.91095129 +19.29884083 +19.62984941 +21.52040247 +21.0967768 +19.18089 +18.55866335 +19.09923121 +21.78710427 +20.42392181 +20.15324939 +20.40514454 +19.4827986 +21.24880736 +20.11243441 +18.48471991 +18.5340361 +20.16677812 +21.76715729 +19.25187255 +21.4987837 +21.07659646 +18.99489609 +18.1488469 +18.41092328 +20.91799122 +18.8022883 +19.48882482 +20.01063797 +20.90588583 +18.45646785 +19.62923371 +19.94412017 +20.16717961 +21.27344257 +18.68671278 +21.23132581 +20.77151371 +20.22348326 +20.79811148 +19.64807096 +18.55359034 +20.66306096 +20.09868923 +20.5574992 +19.35199512 +21.62746631 +20.34947947 +18.34160994 +19.79921596 +21.02542422 +21.17653825 +19.97838821 +21.57899028 +19.07067375 +19.64239479 +21.59438764 +21.58502687 +20.74285108 +21.43592219 +18.35509307 +21.4599799 +19.28247963 +18.71332605 +18.95747935 +19.67187403 +19.00476802 +19.28227034 +18.53991394 +19.18470706 +20.28913526 +19.76741483 +19.38687504 +20.98580852 +21.57403989 +21.02715689 +19.32800281 +20.97776867 +18.60792073 +18.52127195 +19.38250815 +21.23080948 +18.98539299 +18.25301364 +18.52683814 +21.43497987 +20.53513314 +19.01280465 +20.2211833 +20.11031849 +20.62174391 +18.93275022 +21.45761195 +19.23747425 +20.77652662 +18.06449859 +19.49062852 +19.40683927 +20.17393771 +18.29094505 +21.16052549 +18.87112321 +20.70815126 +21.21284849 +20.24750783 +20.96353283 +20.64744125 +19.42211919 +21.23543416 +19.17880548 +20.28485167 +20.98159096 +19.51582364 +19.21107043 +19.83738404 +21.27185398 +21.71968705 +20.18033365 +20.64586482 +21.61309449 +18.39418741 +18.92831431 +20.83560003 +21.29497785 +21.42811611 +19.66042871 +18.35735961 +19.75112591 +18.92293836 +19.16478582 +21.08907023 +18.30210834 +21.31032487 +19.71067634 +20.38425572 +18.35860892 +19.34943631 +18.35880368 +21.84617489 +21.2290466 +19.83853102 +18.89441019 +73.89498943 +65.8865137 +63.38938827 +68.85903568 +74.03532804 +61.25209195 +64.43849787 +73.96288791 +63.52676915 +62.74011094 +68.95454613 +72.921076 +77.24474437 +85.27563726 +80.38074478 +70.98964256 +85.24875468 +76.17051855 +74.30048733 +78.49828493 +74.56285383 +71.18182051 +69.86216788 +76.81753364 +84.87747587 +80.67010071 +87.45939288 +76.13000905 +76.02835735 +74.10865542 +74.17726731 +76.10360762 +84.17732808 +85.973156 +87.07578635 +75.26233466 +85.99743982 +77.15937893 +73.71688217 +85.81729433 +82.07907693 +74.5461315 +78.78264736 +76.50420777 +83.68816664 +79.1595541 +87.72308495 +72.78846861 +77.661601 +79.69127218 +80.03092484 +77.03647996 +83.6477637 +85.97531415 +86.99862415 +84.86112247 +85.58873693 +80.78933072 +85.19376021 +76.83761709 +78.37189816 +77.06690883 +79.47438241 +76.56559853 +87.38158856 +77.00181778 +73.56518131 +80.42521406 +72.9100854 +73.76514976 +75.50660574 +79.42579138 +78.93125717 +84.79425576 +83.83078968 +78.82033602 +77.33852486 +77.06276074 +84.38311131 +72.8324593 +72.20445322 +84.72789688 +79.63357753 +74.09338691 +75.76890948 +86.91989636 +85.75188297 +83.35640076 +79.71545063 +80.32502379 +87.60374437 +87.45215919 +75.44415988 +84.9775099 +75.15573836 +75.41645485 +82.81745674 +78.47998744 +86.90467714 +73.42412756 +80.18437946 +86.6963112 +86.46578536 +74.5044388 +77.8661733 +81.90591095 +74.55028953 +87.48832116 +34.65438905 +29.86847541 +29.57538927 +29.34660866 +34.6204328 +30.03918662 +31.4642175 +31.67264266 +32.63366442 +29.25910058 +30.25124934 +34.7615318 +24.22967595 +23.6601808 +20.24691647 +21.62498914 +22.97325387 +21.39285098 +24.23494706 +22.99982353 +20.48214097 +24.23234371 +24.45079329 +20.50815051 +20.61254727 +19.67457986 +20.04106862 +19.16785116 +20.79978308 +22.35588316 +20.49258512 +18.7716179 +20.97152521 +19.82555315 +21.93786931 +20.21244145 +18.86146091 +18.85449167 +21.17621694 +19.15384963 +21.17006441 +18.91390223 +19.37932181 +19.95728519 +21.87729271 +21.83121489 +18.9178594 +19.52615392 +18.61545124 +20.05917865 +19.86137883 +21.62377956 +21.74087272 +19.55302245 +19.98612004 +21.60566653 +21.24720898 +18.03191928 +19.29874728 +19.70599657 +18.92249756 +18.80189573 +18.15462304 +20.57742901 +19.05369877 +18.21288491 +18.92755063 +21.21172768 +21.41006667 +19.93609197 +19.66805822 +21.91562176 +18.80225864 +18.86143713 +19.16746871 +18.69102927 +19.51746239 +21.64333691 +20.46139136 +19.64778619 +21.73764318 +18.19385693 +18.84110471 +20.26355442 +20.46221049 +18.15823003 +19.61215503 +20.59661586 +20.79228219 +20.55255961 +19.2822529 +18.37408049 +19.70844979 +18.51501775 +21.79049463 +19.90068346 +19.2690367 +21.06521628 +19.99179437 +19.2401361 +21.24787097 +21.4686086 +20.69203267 +18.18785769 +20.26529152 +21.05317965 +18.54601205 +18.91468301 +21.52998042 +18.08048689 +21.01090855 +19.89509317 +19.78708864 +18.02838613 +21.27759292 +19.4392996 +20.16306253 +20.70920275 +20.81051831 +21.82392024 +20.12535981 +19.5988782 +21.81481451 +20.56844094 +19.19260143 +18.50585698 +21.37021801 +19.44872641 +20.64187889 +19.88326568 +18.79571912 +20.40674643 +18.24273228 +19.65336984 +20.18645976 +19.7441879 +20.9946887 +21.31169982 +21.0849723 +18.15525179 +18.77651508 +20.14643004 +21.73928816 +21.3398271 +21.37013803 +19.21283278 +19.71779159 +19.88282379 +18.62948633 +18.12427252 +21.7876116 +18.96285034 +18.95979561 +18.33351622 +21.71403101 +20.31894846 +20.66814743 +21.64630755 +19.38248068 +20.48602924 +18.15519782 +18.74709011 +21.32498322 +21.06707343 +19.40257077 +19.50724251 +20.13421773 +18.00096402 +18.96497734 +18.83292723 +19.00193179 +21.22362337 +19.11869543 +18.76608529 +20.01705384 +21.46258015 +18.96539882 +18.31414497 +19.42561894 +20.9906073 +68.68939141 +72.30838983 +74.25045321 +71.98020968 +69.41172187 +67.9094456 +61.74493957 +74.58677891 +66.41534327 +63.27030371 +70.26724043 +65.06632073 +78.93127876 +78.94663793 +76.75772682 +82.81656091 +73.9522888 +76.80277782 +78.18498184 +80.13945567 +82.00724312 +75.96740903 +79.86664857 +72.17748199 +86.40083539 +77.66016726 +83.1703087 +71.89150457 +74.83129632 +79.31456078 +85.53572725 +79.9534659 +86.35666015 +79.26230584 +79.36810243 +73.47245292 +84.74068891 +75.86030486 +86.29012727 +86.4692997 +86.98845024 +80.2272994 +82.73410036 +84.35490658 +74.00731842 +81.51737674 +72.4617079 +79.86510407 +74.45960831 +74.90878439 +77.82207928 +78.90005411 +81.95108581 +73.17328889 +80.039854 +74.9040208 +81.16411874 +82.17874616 +80.88793034 +83.79261311 +77.36603416 +84.23766186 +78.92048494 +79.74312412 +82.36568029 +72.29026404 +82.45994928 +81.89343131 +76.95385991 +73.36918667 +73.45144136 +86.76741492 +83.02767207 +74.0329311 +77.70682472 +81.4789457 +85.36832774 +72.69787695 +75.78540256 +86.23515598 +72.1459562 +75.22166351 +87.34354831 +81.81364413 +76.18659559 +73.07171721 +72.45092234 +75.91577552 +86.10360779 +78.65446745 +83.52517354 +86.69239412 +78.52849661 +75.06029382 +80.27373277 +86.52917286 +84.86995804 +73.05324944 +83.0811329 +81.1503292 +86.5193776 +73.95967427 +80.54454688 +86.07137004 +78.56035042 +79.71188807 +73.12734276 +76.45026889 +30.87683663 +34.84877698 +30.60906523 +31.74955381 +33.09634196 +29.83796785 +31.91322515 +33.52664342 +34.34786677 +32.80805306 +33.08193615 +29.60820854 +24.39707737 +20.75393073 +23.63053346 +21.34637595 +24.34249595 +23.38541745 +23.53708414 +21.14138394 +22.14481788 +22.92780185 +23.60948051 +20.62573687 +22.37384807 +22.0783436 +19.53934759 +21.80202569 +20.55505454 +20.3235591 +19.71992186 +20.81164708 +21.42162557 +20.11518094 +21.00928156 +21.29638547 +20.94454076 +18.2952621 +21.54189833 +19.92821167 +19.14110267 +19.05809986 +20.02773027 +21.65075327 +19.60705455 +21.09969767 +18.4755338 +19.99976705 +21.98783263 +21.10198968 +18.86020237 +20.02497142 +18.95267616 +19.65709641 +21.14061161 +21.10514075 +18.93055741 +21.7097146 +20.38982818 +19.53843606 +20.01158381 +20.42420571 +20.24344812 +19.9494497 +20.023575 +18.70923912 +21.25501179 +20.17091335 +18.52787611 +21.06867281 +21.73394803 +21.79341113 +19.47607093 +18.54206749 +20.07359851 +18.45043692 +21.73594967 +18.73431851 +19.01106344 +19.62654611 +19.53265787 +21.17865157 +19.82526728 +18.68307304 +19.06883958 +20.03645354 +18.58981567 +20.4466227 +20.26298923 +20.85880927 +21.75081846 +21.64901824 +21.78430247 +18.97886239 +18.52626123 +19.2091068 +21.58034069 +21.8882139 +21.4372998 +18.44007088 +21.78971199 +19.69343211 +18.03724116 +19.76957496 +18.18427668 +20.17551168 +19.35421611 +19.89373973 +20.45268144 +21.47520438 +20.64760631 +19.73619547 +21.27771141 +21.62232855 +18.47903987 +20.12773191 +19.13804784 +18.18824479 +20.67418678 +21.45108311 +18.65161062 +21.24965463 +21.41358293 +21.65736177 +19.06114036 +21.61581226 +19.28265102 +20.20935205 +18.20202988 +18.17552435 +20.88149285 +19.77791991 +19.0251695 +18.76165398 +20.67981396 +18.00160618 +21.22356468 +18.31699842 +18.76595846 +19.54494558 +19.93943496 +19.41998046 +19.28284866 +21.85566405 +20.56631873 +18.09373182 +19.72070363 +21.5000348 +19.20582475 +18.37822538 +21.03353909 +20.42405533 +20.28125003 +18.21430624 +18.56199604 +20.49019758 +18.83129537 +20.01628032 +20.17952674 +19.13402876 +21.79370062 +21.36985643 +20.88949403 +18.31197547 +18.42452008 +19.97112538 +18.6428534 +21.91306185 +18.63408094 +19.94987134 +19.45648423 +20.55754569 +18.51614564 +19.72126571 +19.09882013 +21.5510061 +18.16234285 +21.65490998 +21.33197655 +19.19028287 +71.9343529 +61.62450442 +74.34937148 +68.36169122 +65.32462195 +66.13113169 +62.10311281 +74.65515339 +61.73437045 +70.10673687 +67.22120862 +74.10526057 +73.65791597 +78.09798751 +77.32616836 +80.74848721 +70.8955188 +72.8476073 +73.17004601 +81.29609972 +72.77543257 +78.74059218 +73.18058409 +83.21241923 +86.55251747 +79.81411227 +77.17112049 +75.29045485 +72.35971705 +75.83822521 +81.97296584 +80.05464368 +86.58203249 +77.87039358 +82.6032026 +79.35018898 +80.10497799 +76.19284777 +79.18407737 +79.43712088 +83.20631281 +86.9457797 +77.17641747 +72.58799725 +79.84479314 +80.61452766 +74.60541476 +84.06643278 +87.73780361 +87.1114837 +75.13565973 +85.98517044 +83.25191293 +79.24252225 +80.63041996 +85.57306034 +79.66303532 +77.95260479 +79.87278582 +81.24657301 +75.02164125 +86.41037579 +82.78301741 +82.12836033 +73.58840721 +81.66286301 +73.9318217 +73.25564636 +80.43311336 +78.44080321 +81.41705612 +73.42403017 +78.89497161 +74.79586939 +87.79275856 +80.97762601 +82.99819298 +77.20595131 +73.51511688 +87.61309271 +76.5794094 +78.78017467 +82.39703173 +72.39446177 +84.96398296 +80.58509632 +76.45132821 +72.98558763 +74.59983142 +86.32037601 +82.97622827 +75.99828163 +73.04947198 +83.04968554 +81.15836779 +78.47216172 +77.81775331 +75.46899943 +82.71374834 +87.53065483 +74.81141521 +81.5538346 +78.78693064 +79.14698916 +76.06632727 +85.20212529 +79.76405904 +80.69446265 +30.75046394 +29.96356843 +29.34982085 +33.37035609 +30.05589363 +33.21647589 +31.29840325 +31.46095269 +31.80272987 +31.15332847 +29.4439622 +30.78163767 +23.13352867 +23.14271285 +21.44228372 +22.22125855 +21.02664243 +21.10246577 +20.26324984 +24.17742322 +21.1039647 +21.17935061 +21.41261324 +20.73078702 +21.05042052 +18.73978531 +18.98169291 +21.0729233 +19.82324966 +21.33175404 +20.71521151 +19.48066249 +21.09779103 +18.90825677 +20.62913668 +20.2225584 +19.16303746 +20.51401684 +21.58846392 +22.10119772 +20.52912007 +19.7129938 +18.99155233 +19.11972056 +20.0134254 +19.56256249 +21.75920328 +19.5666531 +19.51249238 +19.55289081 +21.36697345 +20.6229323 +20.4763138 +19.35513519 +21.29203745 +18.17088275 +21.50381254 +20.07024855 +19.61181021 +20.00785824 +20.6736307 +18.05452751 +21.49033278 +19.7907572 +19.37558548 +18.55654443 +19.761655 +19.98912826 +19.61835278 +20.71861758 +19.68736536 +21.84986468 +20.39711007 +21.22991174 +18.43742361 +19.58916517 +19.34739081 +20.27792955 +21.44445408 +18.31335287 +18.29029919 +21.40127083 +18.85140034 +20.67501302 +20.17151253 +21.33157438 +18.50035564 +18.51913186 +20.22727344 +21.78551461 +19.65999103 +19.3077684 +20.42675729 +21.7217944 +21.34771133 +21.98193444 +18.09027433 +18.45143258 +20.43787105 +21.28230576 +19.14097737 +18.95684435 +21.1479032 +18.22552742 +19.47753059 +20.02323689 +20.2413947 +20.91446063 +19.41938289 +20.45073738 +20.34150169 +20.1904644 +20.56193317 +19.03779668 +18.33859501 +18.47708669 +18.89832953 +20.00079174 +21.361771 +20.80517337 +21.15616242 +19.4039025 +19.61861497 +19.57472754 +18.76749274 +18.24486663 +20.36366437 +21.95686673 +18.66731882 +18.18673928 +21.11812673 +19.01034403 +21.31857522 +20.81574667 +21.78269415 +19.63570894 +20.64787936 +19.85941912 +20.52235479 +19.79839562 +18.75452899 +20.03158966 +20.11433681 +21.24241539 +20.94208282 +20.6551665 +18.45354838 +20.88246516 +20.00122063 +21.57639814 +21.67948374 +18.22062636 +21.28256624 +18.15078956 +19.4686785 +20.1691999 +18.59240319 +20.98525525 +18.03341882 +18.67439619 +20.32318732 +20.85755025 +21.66657241 +20.82198661 +20.68711708 +19.57736459 +18.0051377 +18.48063703 +20.13821818 +20.70051954 +19.66979314 +20.92271605 +20.08744918 +18.29670478 +18.768065 +21.35999289 +20.89965099 +21.57904795 +18.470229 +21.96089014 +64.04793131 +61.36020632 +74.76495197 +65.02260484 +70.28506455 +63.60287947 +69.09110804 +73.49487582 +65.55446388 +70.31545407 +63.75548881 +69.69721742 +75.06170946 +73.11795217 +74.70058543 +72.61561338 +83.83773799 +76.00434048 +73.64673481 +76.79928969 +74.81472131 +81.39338682 +84.91333303 +76.63828235 +85.26187519 +80.56748632 +74.06867602 +82.55166409 +79.73366224 +81.91524615 +85.71758861 +78.57267141 +85.90032892 +83.50664 +79.33877274 +81.16164485 +77.11664696 +87.63227173 +82.57840547 +72.61430778 +77.13327935 +81.8746633 +72.81385085 +72.29528848 +79.10534737 +77.23025522 +78.9179624 +76.11164925 +74.61757143 +83.0063674 +80.21388191 +75.18441879 +77.0949236 +81.44937398 +73.13811221 +74.34140193 +73.1543684 +84.97667802 +84.01837848 +84.03283419 +83.45719936 +82.70619669 +79.62804832 +82.27250135 +72.61029941 +81.93949748 +84.44198988 +80.00189349 +77.71648268 +76.43515869 +82.28088313 +83.08083111 +85.93465662 +77.47660154 +84.31554748 +80.98091576 +86.12455705 +80.44422146 +80.28130655 +81.24525334 +83.98823939 +87.43420828 +73.17631117 +72.85425423 +87.00063836 +80.47785549 +81.8382911 +79.71862457 +73.49837812 +84.28926822 +83.95543313 +76.98710533 +76.89968028 +85.40529812 +73.15757566 +86.09751707 +76.10655781 +81.48772467 +86.97587347 +82.84992821 +78.63084681 +83.03251546 +77.50364442 +76.43610531 +87.94689868 +84.01261144 +82.31548803 +77.68460446 +29.11462372 +28.99875844 +32.34675382 +33.67700295 +32.23922063 +34.1810179 +29.01392424 +31.14720731 +30.31724468 +31.66254092 +29.2632033 +34.9064358 +21.16018304 +24.2919375 +22.06800012 +24.57514677 +22.64218032 +24.3301299 +20.18396236 +23.51210856 +24.2056656 +20.785947 +20.59064269 +22.06208407 +22.25634853 +20.6536151 +20.59246143 +19.01005235 +18.43523362 +21.75033355 +18.79070518 +21.02824861 +22.17826873 +19.286638 +21.15695728 +18.75434915 +20.51301262 +18.74527433 +21.0443445 +19.29710877 +19.94886109 +20.49563118 +18.77478205 +21.06719067 +19.30277201 +20.74860493 +18.72257119 +20.85208244 +18.53052452 +19.72033934 +19.00705356 +21.01365475 +19.06642622 +21.54753501 +21.58798542 +18.80379959 +20.47502094 +18.36848739 +19.58493189 +20.87330606 +20.64165542 +20.50273845 +19.13383644 +19.51584612 +18.41531964 +20.88009949 +20.01939817 +20.83906286 +18.99942129 +19.30145808 +19.34057402 +18.10644541 +19.36419149 +21.21321829 +21.58919398 +20.07287326 +19.82200678 +18.12329847 +21.51681735 +18.05204085 +21.77900032 +19.95686499 +19.36773797 +21.69687374 +18.09756397 +20.32569958 +20.1957301 +18.8772816 +19.71554973 +20.60905602 +20.90852479 +18.00286743 +21.74741372 +18.65810149 +18.93432174 +19.05389378 +18.15128055 +18.49220849 +21.94539969 +19.26086695 +18.46120436 +18.42796504 +20.87783987 +21.41827466 +19.0278092 +18.08363805 +18.89236007 +18.22606276 +18.41357975 +19.72603248 +18.91967348 +20.06378351 +19.01397501 +21.7621492 +21.62377587 +20.26722442 +19.41769597 +19.52458753 +19.64740913 +20.91031729 +19.80655598 +21.42691979 +21.77637914 +19.34378461 +20.24277793 +18.38022615 +18.80187619 +19.65386307 +18.82481086 +21.50624196 +18.63314502 +19.82264329 +20.42279696 +20.76745563 +19.53227748 +21.47639627 +20.2732766 +19.75803112 +18.26896254 +19.85875787 +21.28294336 +20.45816407 +21.32864981 +19.97604668 +18.47384682 +18.05455938 +19.21287819 +20.79196189 +21.16103962 +19.18301134 +19.98261568 +19.68447562 +19.64209114 +18.92539485 +18.54548442 +18.78675423 +18.22132955 +18.79252297 +18.03680359 +20.5090336 +19.26550163 +21.89257885 +18.7748682 +19.38331428 +19.43136463 +18.03380598 +21.35683181 +18.12797784 +21.76071934 +21.5682974 +20.14208766 +20.1743823 +20.61511169 +19.54820005 +20.58888913 +18.17907355 +18.21619051 +19.82892168 +20.1984892 +21.55127056 +62.62338462 +72.38801181 +64.05664379 +66.55953762 +65.50579311 +66.64211171 +63.24673877 +68.04855301 +72.10233922 +67.20486254 +67.87234463 +72.74323916 +80.79802194 +77.45185242 +73.83943646 +83.57406311 +78.79535663 +73.27221091 +74.01673856 +82.30462554 +70.52513912 +85.23727074 +71.86305807 +82.19498351 +77.84650664 +79.39620618 +85.57386678 +78.13493956 +80.11630066 +72.35206183 +71.60533422 +75.46129718 +82.39199351 +73.49659686 +73.37727019 +78.10391751 +82.26655415 +83.29308009 +74.76538453 +73.18548227 +86.94189692 +76.07925733 +85.55075971 +85.71288417 +84.25912903 +74.6116961 +85.04664476 +84.20683934 +81.52799643 +77.37665771 +76.18184964 +73.93247934 +80.1368756 +81.34464661 +77.73076399 +79.5866397 +77.79755542 +82.23635545 +86.39940649 +74.60481474 +78.09879204 +74.25033348 +81.99516437 +83.65791238 +86.90643201 +76.45828065 +87.19919353 +73.00869816 +81.6443027 +72.91062363 +86.29624459 +80.47308202 +82.7087796 +85.30436388 +74.40425567 +72.61676786 +86.06127214 +77.03819207 +74.06295152 +79.49461664 +81.57211411 +72.4948337 +76.39759711 +72.52410175 +78.72521507 +86.32143473 +87.72227016 +82.19260305 +74.5860599 +86.49342417 +80.25981107 +74.45984603 +86.74746016 +84.1193433 +83.70712365 +83.34673123 +75.20540826 +75.37607812 +83.0126561 +75.515535 +79.01871681 +84.51199676 +84.54320783 +79.25215793 +84.6750065 +85.41791594 +82.47638035 +75.65478459 +30.55366953 +30.81497748 +29.54754062 +30.54821571 +30.14091725 +31.71409791 +34.61399366 +29.46866837 +33.4097083 +33.19860485 +29.41282122 +34.70445673 +22.70675422 +21.78948914 +23.54929156 +21.31299608 +23.26692279 +20.33790509 +23.9205467 +22.16780418 +23.01238299 +21.15114427 +23.19328896 +23.61963514 +20.37028097 +20.96220923 +20.30111074 +22.09419798 +20.47127192 +21.88004821 +21.71433777 +20.11342751 +19.37442585 +20.09531217 +21.81618611 +19.36438263 +20.19421451 +21.326264 +19.50709662 +20.66756892 +19.07364238 +18.5256159 +21.27918903 +18.48417465 +19.17713606 +21.52325574 +19.47892931 +21.99726382 +21.93997951 +19.09141425 +18.18135024 +20.84024057 +19.82295779 +18.42067826 +20.85008815 +18.14209934 +21.50140831 +18.57242253 +18.52701638 +18.72064376 +21.84525892 +20.77666579 +21.20955759 +19.59430132 +19.3489652 +18.94157211 +19.92641725 +20.21119474 +20.53104058 +19.03212748 +21.79880089 +18.68262037 From 604084ac8ce48ade4be03c88363bdf3ce4f61535 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 17:53:01 +0800 Subject: [PATCH 05/49] add test --- .../TimeSeriesDirectApi.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index c27471245c..0a7e49e532 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -664,7 +664,74 @@ public void TestSrCnnBatchAnomalyDetector( [Fact] public void TestSrCnnAnomalyDetectorWithSeasonalData() { + var ml = new MLContext(1); + IDataView dataView; + var dataPath = GetDataPath("Timeseries", "period_no_anomaly.csv"); + + // Load data from file into the dataView + dataView = ml.Data.LoadFromTextFile(dataPath, hasHeader: true); + + // Setup the detection arguments + string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); + string inputColumnName = nameof(TimeSeriesDataDouble.Value); + + // Do batch anomaly detection + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, + threshold: 0.3, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288); + + // Getting the data of the newly created column as an IEnumerable of + // SrCnnAnomalyDetection. + var predictionColumn = ml.Data.CreateEnumerable( + outputDataView, reuseRowObject: false); + + foreach (var prediction in predictionColumn) + { + Assert.Equal(7, prediction.Prediction.Length); + Assert.Equal(0, prediction.Prediction[0]); + } + } + + [Fact] + public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData() + { + var ml = new MLContext(1); + IDataView dataView; + var dataPath = GetDataPath("Timeseries", "period_anomaly.csv"); + + // Load data from file into the dataView + dataView = ml.Data.LoadFromTextFile(dataPath, hasHeader: true); + // Setup the detection arguments + string outputColumnName = nameof(SrCnnAnomalyDetection.Prediction); + string inputColumnName = nameof(TimeSeriesDataDouble.Value); + + // Do batch anomaly detection + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, + threshold: 0.3, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288); + + // Getting the data of the newly created column as an IEnumerable of + // SrCnnAnomalyDetection. + var predictionColumn = ml.Data.CreateEnumerable( + outputDataView, reuseRowObject: false); + + var anomalyStartIndex = 2988; + var anomalyEndIndex = 3095; + + int k = 0; + foreach (var prediction in predictionColumn) + { + Assert.Equal(7, prediction.Prediction.Length); + if (anomalyStartIndex <= k && k <= anomalyEndIndex) + { + Assert.Equal(1, prediction.Prediction[0]); + } + else + { + Assert.Equal(0, prediction.Prediction[0]); + } + + ++k; + } } [Fact] From 7c6734fcdb7b8e2a2009bbf97a60b2356de246cf Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 18:09:41 +0800 Subject: [PATCH 06/49] Remove unused files --- src/Microsoft.ML.TimeSeries/Cyclic.cs | 30 - src/Microsoft.ML.TimeSeries/InnerStl.cs | 90 +-- src/Microsoft.ML.TimeSeries/LeastSquares.cs | 115 +--- src/Microsoft.ML.TimeSeries/MovingAverage.cs | 76 --- .../SerialCorrelation.cs | 527 ------------------ 5 files changed, 24 insertions(+), 814 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/Cyclic.cs delete mode 100644 src/Microsoft.ML.TimeSeries/MovingAverage.cs delete mode 100644 src/Microsoft.ML.TimeSeries/SerialCorrelation.cs diff --git a/src/Microsoft.ML.TimeSeries/Cyclic.cs b/src/Microsoft.ML.TimeSeries/Cyclic.cs deleted file mode 100644 index a8cf4c123e..0000000000 --- a/src/Microsoft.ML.TimeSeries/Cyclic.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - public class Cyclic - { - private readonly IReadOnlyList _y; - - public Cyclic(IReadOnlyList yValues) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); - - if (yValues.Count < BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: cyclic"); - - _y = yValues; - } - - /// - /// detect the cyclic length by given the input time series. - /// if not exist (the cyclic pattern is not significant), then return -1 - /// - public int DetectCyclic(out double confidence) - { - return SerialCorrelation.Period(_y, out confidence); - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/InnerStl.cs b/src/Microsoft.ML.TimeSeries/InnerStl.cs index b39a2c4866..f4d52de6fc 100644 --- a/src/Microsoft.ML.TimeSeries/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/InnerStl.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; namespace Microsoft.ML.TimeSeries @@ -173,9 +174,9 @@ public bool Decomposition() } // step3: low-pass filtering of smoothed cycle-subseries - List c1 = MovingAverage.MA(c, _config.Np); - List c2 = MovingAverage.MA(c1, _config.Np); - List c3 = MovingAverage.MA(c2, 3); + List c1 = MovingAverage(c, _config.Np); + List c2 = MovingAverage(c1, _config.Np); + List c3 = MovingAverage(c2, 3); List virtualC3XValues = VirtualXValuesProvider.GetXValues(c3.Count); FastLoess lowPass = new FastLoess(virtualC3XValues, c3, _isTemporal, _config.Nl); lowPass.Estimate(); @@ -219,70 +220,6 @@ public bool Decomposition() absResiduals.Add(Math.Abs(_y[i] - _seasonalComponent[i] - _trendComponent[i])); } - // identify the outliers and corresponding mean residual squares (Mrs) - //double median = MathUtility.QuickSelect(absResiduals, absResiduals.Count / 2); - double median = 0; - - // when median is very close to 0, which means the regularity of the serial is strong, so that no data points is outlier. - Mrs = 0; - int nonOutlierCount = 0; - if (median < 0.0001) - { - // the curve fitting is perfect, so Mrs remains 0. no update. - for (int i = 0; i < _length; i++) - { - _outlierIndexes[i] = 0; - _outlierSeverity[i] = 0; - } - } - else - { - for (int i = 0; i < _length; i++) - { - double severity = Math.Abs(_residual[i]) / median; - - // this is the key criteria - if (severity > 6) - { - _outlierIndexes[i] = 1; - _outlierSeverity[i] = severity; - } - else - { - nonOutlierCount++; - Mrs += _residual[i] * _residual[i]; - _outlierIndexes[i] = 0; - _outlierSeverity[i] = 0; - } - } - Mrs /= nonOutlierCount; - } - return true; - } - - public bool DecompositionSimple() - { - if (_config.Np <= 0) - { - for (int i = 0; i < _y.Count; ++i) - { - _residual[i] = _y[i]; - } - } else - { - double[] sum = new double[_config.Np]; - for (int i = 0; i < _y.Count; i++) - { - var indexInPeriod = i % _config.Np; - sum[indexInPeriod] += _y[i]; - } - double[] averages = sum.Select((s, i) => s / (_y.Count / _config.Np)).ToArray(); - for (int i = 0; i < _y.Count; ++i) - { - _residual[i] = _y[i] - averages[i % _config.Np]; - } - } - return true; } @@ -321,5 +258,24 @@ internal static List GetXValues(int length) } } } + + private static List MovingAverage(IReadOnlyList s, int length) + { + List results = new List(s.Count); + double partialSum = 0; + for (int i = 0; i < length; ++i) + { + partialSum += s[i]; + } + + for (int i = length; i < s.Count; ++i) + { + results.Add(partialSum / length); + partialSum = partialSum - s[i - length] + s[i]; + } + results.Add(partialSum / length); + + return results; + } } } diff --git a/src/Microsoft.ML.TimeSeries/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/LeastSquares.cs index 6f81adf146..32653e14a7 100644 --- a/src/Microsoft.ML.TimeSeries/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/LeastSquares.cs @@ -40,101 +40,6 @@ public LeastSquares(List x, List y) /// /// the weighted least squares. note that the weight should be non-negative, and equal length to data public PolynomialModel RegressionDegreeOneWeighted(List weights) - { - return new PolynomialModel(RegressionDegreeOneWeightedFast(weights)); - //return RegressionDegreeOneWeightedOld(weights); - } - - public PolynomialModel RegressionDegreeOneWeightedOld(List weights) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); - - if (weights.Count != _length) - throw new Exception("the weight vector is not equal length to the data points"); - - foreach (double value in weights) - { - if (value < 0) - throw new Exception("the value in weights should be non-negative!"); - } - - double[] w = new double[_length]; - for (int i = 0; i < _length; i++) - { - w[i] = Math.Sqrt(weights[i]); - } - - double[,] kernelMatrix = new double[_length, 2]; - for (int i = 0; i < _length; i++) - { - kernelMatrix[i, 0] = 1; - kernelMatrix[i, 1] = _x[i]; - } - double[,] kernelMatrix1 = new double[_length, 2]; - for (int i = 0; i < _length; i++) - { - for (int j = 0; j < 2; j++) - { - kernelMatrix1[i, j] = w[i] * kernelMatrix[i, j]; - } - } - double[] y1 = new double[_length]; - for (int i = 0; i < _length; i++) - y1[i] = w[i] * _y[i]; - - double[,] s = new double[2, 2]; - for (int i = 0; i < 2; i++) - { - for (int j = 0; j < 2; j++) - { - double sum = 0; - for (int k = 0; k < _length; k++) - { - sum += kernelMatrix1[k, i] * kernelMatrix1[k, j]; - } - s[i, j] = sum; - } - } - - /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is - * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. - */ - double a = s[0, 0]; - double b = s[0, 1]; - double c = s[1, 0]; - double d = s[1, 1]; - double divider = a * d - b * c; - double[,] reverseS = new double[2, 2]; - reverseS[0, 0] = d / divider; - reverseS[0, 1] = -c / divider; - reverseS[1, 0] = -b / divider; - reverseS[1, 1] = a / divider; - - // double[,] reverseS = MatrixEx.ReverseMatrix(S); - - double fy0 = 0; - double fy1 = 0; - for (int i = 0; i < _length; i++) - { - fy0 += kernelMatrix1[i, 0] * y1[i]; - fy1 += kernelMatrix1[i, 1] * y1[i]; - } - - double b0 = reverseS[0, 0] * fy0 + reverseS[0, 1] * fy1; - double b1 = reverseS[1, 0] * fy0 + reverseS[1, 1] * fy1; - - List results = new List(); - results.Add(b0); - results.Add(b1); - - //List results2 = RegressionDegreeOneWeightedFast(weights); - //Trace.Assert(results[0] == results2[0]); - //Trace.Assert(results[1] == results2[1]); - - return new PolynomialModel(results); - } - - public List RegressionDegreeOneWeightedFast(List weights) { //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); @@ -218,7 +123,7 @@ public List RegressionDegreeOneWeightedFast(List weights) results.Add(b0); results.Add(b1); - return results; + return new PolynomialModel(results); } // The result is incorrect, as the data can only be partially loaded into the vector @@ -525,24 +430,6 @@ public PolynomialModel(ICollection coeffs) /// /// the specific x value public double Y(double x) - { - //return YOld(x); - return YNew(x); - } - - public double YOld(double x) - { - double result = _coeffs[0]; - for (int i = 1; i < _coeffs.Count; i++) - { - result += _coeffs[i] * Math.Pow(x, i); - } - - //Trace.Assert(YNew(x) == result); - return result; - } - - public double YNew(double x) { double result = _coeffs[0]; double p = 1.0; diff --git a/src/Microsoft.ML.TimeSeries/MovingAverage.cs b/src/Microsoft.ML.TimeSeries/MovingAverage.cs deleted file mode 100644 index 6710ea4ff5..0000000000 --- a/src/Microsoft.ML.TimeSeries/MovingAverage.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - public class MovingAverage - { - /// - /// calculate the moving average of a given series. - /// - /// the input series - /// the length of the moving average window - public static List MA(IReadOnlyList s, int length) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(s, nameof(s)); - if (s.Count <= 1 || length <= 1 || length >= s.Count) - throw new Exception("the input information for moving average is invalid!"); - //var result1 = MaFast(s, length); - //var result2 = MaOld(s, length); - - //Trace.Assert(result1.Count == result2.Count); - - //for (int i=0; i MaOld(IReadOnlyList s, int length) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(s, nameof(s)); - - List series = new List(s); - int left = length / 2; - int right = length - left - 1; - List result = new List(); - for (int i = left; i < series.Count - right; i++) - { - int startIndex = i - left; - int endIndex = i + right; - double sum = 0; - for (int j = startIndex; j <= endIndex; j++) - { - sum += series[j]; - } - result.Add(sum / length); - } - return result; - } - - public static List MaFast(IReadOnlyList s, int length) - { - List results = new List(s.Count); - double partialSum = 0; - for (int i = 0; i < length; ++i) - { - partialSum += s[i]; - } - - for (int i = length; i < s.Count; ++i) - { - results.Add(partialSum / length); - partialSum = partialSum - s[i - length] + s[i]; - } - results.Add(partialSum / length); - - return results; - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs b/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs deleted file mode 100644 index c0830e4c7f..0000000000 --- a/src/Microsoft.ML.TimeSeries/SerialCorrelation.cs +++ /dev/null @@ -1,527 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; -using Microsoft.ML.Transforms.TimeSeries; - -namespace Microsoft.ML.TimeSeries -{ - /// - /// this class is used to detect the periodicity automatically - /// - public class SerialCorrelation - { - /// - /// the minimum period allowed. - /// - private const int MinPeriod = 4; - - /// - /// the ratio of all potential lags. this value should not be greater than 0.2, otherwise, over-fit. - /// - private const double LagRatio = 0.25; - - /// - /// in practice, the max lag very rarely exceed 365, which lacks of strong interpretation, and which also brings performance overhead. - /// - private const int MaxLag = 400; - - /// - /// suppose the length of time series is 651, now we found an period is 128, then 651/128 = 5, which means there are at most 5 recurrent period. this is too small, the significance build upon this is not trustable. - /// - private const int MinRecurrentCount = 8; - - /// - /// when input time series is with very close values (i.e., different is smaller than E-20), the accuracy of double could distort the - /// final trend signal. any seasonal signal under such circumstance becomes unreliable. - /// so use this threshold to eliminate such kind of time series. here set to 1e-10 is for conservative consideration. - /// - private const double MinEnergyThreshold = 1e-10; - - /// - /// this is a slow version of calculating Acf, with complexity is O(n^2). - /// the assumptions needed for applying serial correlation is, the series should be generated by a second-order stationary process. - /// therefore, one should conduct de-trend process before running serial correlation. - /// - internal static List Acf(ICollection y) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); - - if (y.Count <= BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: serial correlation"); - - List values = new List(y); - double mean = 0; - foreach (double value in values) - mean += value; - mean /= values.Count; - double var = 0; - for (int i = 0; i < values.Count; i++) - { - values[i] -= mean; - var += values[i] * values[i]; - } - - List energies = new List(); - int maxLag = Math.Min((int)(y.Count * SerialCorrelation.LagRatio), SerialCorrelation.MaxLag); - for (int lag = 0; lag <= maxLag; lag++) - { - double corr = 0; - for (int i = 0; i + lag < values.Count; i++) - { - corr += values[i] * values[i + lag]; - } - energies.Add(corr / var); - } - return energies; - } - - public static int DetectSeasonality(double[] y) - { - //Cyclic seasonalDetector = new Cyclic(Y); - //var period = seasonalDetector.DetectCyclic(out var seasonalConfidence); - // simulate SerialCorrelation.Period - //var watch = Stopwatch.StartNew(); - int length = y.Length; - - double mean = 0; - double std = 0; - foreach (double value in y) - mean += value; - mean /= length; - - int newLength = Get2Power(y.Length); - double[] fftRe = new double[newLength]; - double[] fftIm = new double[newLength]; - double[] inputRe = new double[newLength]; - for (int i = 0; i < length; ++i) - { - inputRe[i] = y[i] - mean; - std = std + inputRe[i] * inputRe[i]; - } - if (std / length < 1e-8) - { - return -1; - } - - for (int i = length; i < newLength; ++i) - { - inputRe[i] = 0; - } - - FftUtils.ComputeForwardFft(inputRe, Enumerable.Repeat(0.0, newLength).ToArray(), fftRe, fftIm, newLength); - - var z = fftRe.Select((m, i) => new Complex(m, fftIm[i])).ToArray(); - var w = z.Select((t, i) => t * Complex.Conjugate(t)).ToArray(); - SerialCorrelation.FindBestTwoFrequencies(w, length, out var bestFreq, out var secondFreq); - - double[] ifftRe = new double[newLength]; - double[] ifftIm = new double[newLength]; - FftUtils.ComputeBackwardFft( - w.Select(t => (double)t.Real).ToArray(), - w.Select(t => (double)t.Imaginary).ToArray(), ifftRe, ifftIm, newLength); - var r = ifftRe.Select((t, i) => new Complex(t, ifftIm[i])).ToArray(); - int period = FindTruePeriod(r, bestFreq, secondFreq, newLength); - - if (period < MinPeriod) - { - period = -1; - } - //watch.Stop(); - //Console.WriteLine(watch.ElapsedMilliseconds); - return period; - } - - // copy from SerialCorrelation.Period - private static int FindTruePeriod(Complex[] r, int bestFreq, int secondFreq, int timeSeriesLength) - { - int firstPeriod = -1; - int secondPeriod = -1; - double firstTimeDomainEnergy = -1; - double secondTimeDomainEnergy = -1; - firstPeriod = SerialCorrelation.FindBestPeriod(r, bestFreq, timeSeriesLength, out firstTimeDomainEnergy); - if (secondFreq != -1) - { - secondPeriod = SerialCorrelation.FindBestPeriod(r, secondFreq, timeSeriesLength, out secondTimeDomainEnergy); - } - if (firstPeriod == -1 && secondPeriod == -1) - return -1; - int truePeriod; - double trueTimeDomainEnergy; - if (firstPeriod == -1) - { - truePeriod = secondPeriod; - trueTimeDomainEnergy = secondTimeDomainEnergy; - } - else if (secondPeriod == -1) - { - truePeriod = firstPeriod; - trueTimeDomainEnergy = firstTimeDomainEnergy; - } - else - { - if (firstPeriod == secondPeriod) - { - truePeriod = firstPeriod; - trueTimeDomainEnergy = firstTimeDomainEnergy; - } - else - { - // hueristic: if the second frequency is with somewhat higher energy in time domain, we think it is a better candidate - if (secondTimeDomainEnergy > firstTimeDomainEnergy * 1.05) - { - truePeriod = secondPeriod; - trueTimeDomainEnergy = secondTimeDomainEnergy; - } - else - { - truePeriod = firstPeriod; - trueTimeDomainEnergy = firstTimeDomainEnergy; - } - } - } - trueTimeDomainEnergy /= r[0].Real; - - // this is a key equation, which is named the "testing for randomness with the correlogram". /ref: http://www.ltrr.arizona.edu/~dmeko/notes_3.pdf - // actually, 1.96 is for the 2-sigma, which has 95% statistical confidence. 2.58 is for 99% confidence, 2.85 for 99.5% confidence - /* increasing the threshold aims to mitigate the fake seasonal component caused by outliers. in practice, if there exist true seasonal component, - * such as BirdStrike/Appdownloads, the energy is far larger than threshold, hence change threshold from 2.85 to 4.0 have no impact (tested); - */ - - double threshold = 4 / Math.Sqrt(timeSeriesLength); - - if (trueTimeDomainEnergy < threshold || r[truePeriod].Real < MinEnergyThreshold) - return -1; - - return truePeriod; - } - - /// - /// Obtain the period by adopting techniques of spectral analysis. which is founded by - /// the fourier analysis. returns -1 means there's no significant period. otherwise, a period - /// is returned. - /// - /// the input time series - /// confidence - public static int Period(IReadOnlyList y, out double confidence) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); - - if (y.Count < BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: serial correlation"); - - confidence = 0; - - List values = new List(y); - - double mean = 0; - foreach (double value in values) - mean += value; - mean /= values.Count; - for (int i = 0; i < values.Count; i++) - { - values[i] -= mean; - } - - // caution1: need to extend to power of 2 explicitly, by adding 0. it is clear that adding any number of 0 will not affect the Acf results. - int newLength = Get2Power(y.Count); - for (int i = y.Count; i < newLength; i++) - { - values.Add(0); - } - - // caution2: need to double the length. this is used to make the "Summation lemma" works. you can check it out by proving the WK theorem. - Complex[] x = new Complex[values.Count * 2]; - for (int i = 0; i < values.Count; i++) - { - x[i] = new Complex(values[i], 0); - x[i + values.Count] = Complex.Zero; - } - - Complex[] z = FftTransform.Fft(x); - - // Y is the key data source for analyzing period patterns. - /* here w is the periodogram, which indicates the square of "energy" on the frequency domain. specifically, - * w[j] = a[j]^2+b[j]^2, where a and b are Fourier Coefficients for cosine and sine, specifically, - * x(t) = a0+sum(a[j]cos(2Pi * f[j]t)+b[j]sin(2Pi * f[j]t) - */ - Complex[] w = new Complex[x.Length]; - for (int i = 0; i < x.Length; i++) - { - w[i] = z[i] * Complex.Conjugate(z[i]); - } - - int bestFreq; - int secondFreq; - - FindBestTwoFrequencies(w, y.Count, out bestFreq, out secondFreq); - - // when the time series is very short, and there exist no seasonal, the loop above may not access - if (bestFreq == -1) - return -1; - - // next check if the period is statistical significant - /* r is an auto-correlation function of input y, but without normalization. specifically, - * r[j] = sum(x[i]x[i+j]). - */ - Complex[] r = FftTransform.RevertFft(w); - - /* pick the best frequency by inspecting the auto-correlation energy (pick the highest) in time-domain. - * in the normal case, usually, when the time series is with period T, then the best frequency is N/T, while the second frequency would be N/2T, - * because period = T implies period = nT, where n is an integer. - * in such a case, smaller period will win out on the autu-correlation energy list, due to the property of auto-correlation. - */ - int firstPeriod = -1; - int secondPeriod = -1; - double firstTimeDomainEnergy = -1; - double secondTimeDomainEnergy = -1; - firstPeriod = FindBestPeriod(r, bestFreq, y.Count, out firstTimeDomainEnergy); - if (secondFreq != -1) - { - secondPeriod = FindBestPeriod(r, secondFreq, y.Count, out secondTimeDomainEnergy); - } - if (firstPeriod == -1 && secondPeriod == -1) - return -1; - int truePeriod; - double trueTimeDomainEnergy; - if (firstPeriod == -1) - { - truePeriod = secondPeriod; - trueTimeDomainEnergy = secondTimeDomainEnergy; - } - else if (secondPeriod == -1) - { - truePeriod = firstPeriod; - trueTimeDomainEnergy = firstTimeDomainEnergy; - } - else - { - if (firstPeriod == secondPeriod) - { - truePeriod = firstPeriod; - trueTimeDomainEnergy = firstTimeDomainEnergy; - } - else - { - // hueristic: if the second frequency is with somewhat higher energy in time domain, we think it is a better candidate - if (secondTimeDomainEnergy > firstTimeDomainEnergy * 1.05) - { - truePeriod = secondPeriod; - trueTimeDomainEnergy = secondTimeDomainEnergy; - } - else - { - truePeriod = firstPeriod; - trueTimeDomainEnergy = firstTimeDomainEnergy; - } - } - } - trueTimeDomainEnergy /= r[0].Real; - - // this is a key equation, which is named the "testing for randomness with the correlogram". /ref: http://www.ltrr.arizona.edu/~dmeko/notes_3.pdf - // actually, 1.96 is for the 2-sigma, which has 95% statistical confidence. 2.58 is for 99% confidence, 2.85 for 99.5% confidence - /* increasing the threshold aims to mitigate the fake seasonal component caused by outliers. in practice, if there exist true seasonal component, - * such as BirdStrike/Appdownloads, the energy is far larger than threshold, hence change threshold from 2.85 to 4.0 have no impact (tested); - */ - double threshold = 4.0 / Math.Sqrt(y.Count); - - if (trueTimeDomainEnergy < threshold || r[truePeriod].Real < MinEnergyThreshold) - return -1; - - confidence = MathUtility.GaussianSignificance(trueTimeDomainEnergy, 0, 1.0 / Math.Sqrt(y.Count)); - return truePeriod; - } - - /// - /// in order to pick up a proper frequency robustly (this is useful especially for large frequency, or small period, e.g., period = 2), - /// this method aims to pick up the top two frequencies for further evaluation. - /// of course, the energy of the second frequency (in frequency domain) must be at similar magnitude compared with the energy of the first - /// frequency. - /// - /// the energy list in the frequency domain, the index is the frequency. - /// the original time series length - /// the frequency with highest energy - /// the frequency with second highest energy - private static void FindBestTwoFrequencies(Complex[] w, int timeSeriesLength, out int bestFreq, out int secondFreq) - { - bestFreq = -1; - double bestEnergy = -1.0; - secondFreq = -1; - double secondEnergy = -1.0; - - if (w.Length < 2) - return; - - List energies = new List(); - - /* length of time series divided by frequency is period. it is obvious that the period should be larger than 1 and smaller than the total length, and is an integer */ - for (int i = w.Length / timeSeriesLength; i < w.Length / 2 + 1; i++) - { - double nextWeight = w[i].Magnitude; - energies.Add(nextWeight); - - if (nextWeight > bestEnergy) - { - bestEnergy = nextWeight; - bestFreq = i; - } - } - - // once we found a best frequency, the region formed by lower bound to upper bound corresponding to this frequency will not be inspected anymore. because they all share the same period. - int period = w.Length / bestFreq; - double lowerBound = w.Length * 1.0 / (period + 1); - double upperBound = w.Length * 1.0 / (period - 1); - - for (int i = w.Length / timeSeriesLength; i < w.Length / 2 + 1; i++) - { - if (i > lowerBound && i < upperBound) - continue; - double weight = w[i].Magnitude; - if (weight > secondEnergy) - { - double prevWeight = 0; - if (i > 0) - prevWeight = w[i - 1].Magnitude; - double nextWeight = 0; - if (i < w.Length - 1) - nextWeight = w[i + 1].Magnitude; - - // should be a local maximum - if (weight >= prevWeight && weight >= nextWeight) - { - secondEnergy = nextWeight; - secondFreq = i; - } - } - } - double typycalEnergy = MathUtility.QuickMedian(energies); - - // the second energy must be at least significantly large enough than typical energies, and also similar to best energy at magnitude level. - if (typycalEnergy * 6.0 < secondEnergy && secondEnergy * 10.0 > bestEnergy) - return; - - // set the second frequency to -1, since it is obviously not strong enought to compete with the best energy. - secondFreq = -1; - } - - /// - /// given a frequency F represented by an integer, we aim to find the best period by inspecting the auto-correlation function in time domain. - /// since either frequency or the period is an integer, so the possible period located within - /// [N/(F+1), N/(F-1)], we need to check this domain, and pick the best one. where N is the length of the augmented time series - /// - /// the auto-correlation function of the augmented time series - /// the input frequency candidate - /// the length of the original time series, this is used for post processing to reduce false positive - /// output the energy on the auto-correlation function - /// return the best period estimated - private static int FindBestPeriod(Complex[] r, int frequency, int timeSeriesLength, out double energy) - { - energy = -1; - - // this will never make sense of a seasonal signal - if (frequency <= 1) - return -1; - - int lowerBound = r.Length / (frequency + 1); - int upperBound = r.Length / (frequency - 1); - int bestPeriod = -1; - for (int i = lowerBound; i <= upperBound && i < r.Length; i++) - { - var currentEnergy = r[i].Real; - if (currentEnergy > energy) - { - energy = currentEnergy; - bestPeriod = i; - } - } - - /* condition1: period does not make sense, since the corresponding zone in the auto-correlation energy list are all negative. - * condition2: for real dataset, we do not think there will exist such long period. this is used to reduce false-positive - * condition3: the number of repeats under this period is too few. this is used to reduce false-positive - */ - if (bestPeriod <= 1 || bestPeriod > SerialCorrelation.MaxLag || timeSeriesLength < SerialCorrelation.MinRecurrentCount * bestPeriod) - { - energy = -1; - return -1; - } - return bestPeriod; - } - - /// - /// this is a fast version of calculating Acf, by using FftTransform. the complexity is O(n log n). Basically - /// 1) Z = FftTransform (X) - /// 2) Y = ZZ* - /// 3) R = IFFT(Y) - /// the series R is just the Acf. - /// - public static List FastAcf(ICollection y) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); - - if (y.Count <= BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: serial correlation"); - - List values = new List(y); - - double mean = 0; - foreach (double value in values) - mean += value; - mean /= values.Count; - for (int i = 0; i < values.Count; i++) - { - values[i] -= mean; - } - - // caution1: need to extend to power of 2 explicitly, by adding 0. it is clear that adding any number of 0 will not affect the Acf results. - int newLength = Get2Power(y.Count); - for (int i = y.Count; i < newLength; i++) - { - values.Add(0); - } - - // caution2: need to double the length. this is used to make the "Summation lemma" works. you can check it out by proving the WK theorem. - Complex[] x = new Complex[values.Count * 2]; - for (int i = 0; i < values.Count; i++) - { - x[i] = new Complex(values[i], 0); - x[i + values.Count] = Complex.Zero; - } - Complex[] z = FftTransform.Fft(x); - Complex[] w = new Complex[x.Length]; - for (int i = 0; i < x.Length; i++) - { - w[i] = z[i] * Complex.Conjugate(z[i]); - } - - Complex[] r = FftTransform.RevertFft(w); - double var = r[0].Real; - List energies = new List(); - int maxLag = Math.Min((int)(y.Count * SerialCorrelation.LagRatio), SerialCorrelation.MaxLag); - for (int lag = 0; lag <= maxLag; lag++) - { - energies.Add(r[lag].Real / var); - } - return energies; - } - - /// - /// get the smallest 2^k which is equal or greater than n - /// - private static int Get2Power(int n) - { - int result = 1; - bool meet1 = false; // check is n is just equals to 2^k for some k - while (n > 1) - { - if ((n & 1) != 0) - meet1 = true; - result = result << 1; - n = n >> 1; - } - if (meet1) - result = result << 1; - return result; - } - } -} From 8ea07f6c001303da5902731f8b4f9c0eb45bbfd3 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 18:19:33 +0800 Subject: [PATCH 07/49] further remove unused codes --- src/Microsoft.ML.TimeSeries/LeastSquares.cs | 284 ------------------ .../LocalRegression.cs | 15 +- src/Microsoft.ML.TimeSeries/MatrixEx.cs | 172 ----------- 3 files changed, 1 insertion(+), 470 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/MatrixEx.cs diff --git a/src/Microsoft.ML.TimeSeries/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/LeastSquares.cs index 32653e14a7..c94408e701 100644 --- a/src/Microsoft.ML.TimeSeries/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/LeastSquares.cs @@ -125,290 +125,6 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) return new PolynomialModel(results); } - - // The result is incorrect, as the data can only be partially loaded into the vector - public List RegressionDegreeOneWeightedSimd(List weights) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); - - if (weights.Count != _length) - throw new Exception("the weight vector is not equal length to the data points"); - - foreach (double value in weights) - { - if (value < 0) - throw new Exception("the value in weights should be non-negative!"); - } - - int vectorSize = Vector.Count; - int bufferLength = ((weights.Count / vectorSize) + 1) * vectorSize; - double[] buffer1 = new double[bufferLength]; - double[] buffer2 = new double[bufferLength]; - double[] buffer3 = new double[bufferLength]; - double[] buffer4 = new double[vectorSize]; - double[] w = buffer1; - double[] kernelMatrixR0 = buffer2; - double[] kernelMatrixR1 = buffer3; - double[] kernelMatrix1R0 = buffer2; - double[] kernelMatrix1R1 = buffer3; - double[] y1; - double[,] s = new double[2, 2]; - double[,] reverseS = new double[2, 2]; - - for (int i = 0; i < _length; ++i) - { - w[i] = Math.Sqrt(weights[i]); - } - - for (int i = 0; i < _length; ++i) - { - kernelMatrixR0[i] = 1; - kernelMatrixR1[i] = _x[i]; - } - - for (int i = 0; i <= bufferLength - bufferLength / 2; i += bufferLength/2) - { - var vw = new Vector(w, i); - var vkm0 = new Vector(kernelMatrixR0, i); - var vkm1 = new Vector(kernelMatrixR1, i); - (vw * vkm0).CopyTo(buffer4); - for (int j = 0; j < vectorSize / 2; ++j) - { - kernelMatrix1R0[i + j] = buffer4[j]; - } - (vw * vkm1).CopyTo(buffer4); - for (int j = 0; j < vectorSize / 2; ++j) - { - kernelMatrix1R1[i + j] = buffer4[j]; - } - } - - y1 = buffer1; - for (int i = 0; i < _length; ++i) - y1[i] = w[i] * _y[i]; - - double sum00 = 0; - double sum01 = 0; - double sum10 = 0; - double sum11 = 0; - - for (int k = 0; k < _length; ++k) - { - sum00 += kernelMatrix1R0[k] * kernelMatrix1R0[k]; - sum01 += kernelMatrix1R0[k] * kernelMatrix1R1[k]; - sum10 += kernelMatrix1R1[k] * kernelMatrix1R0[k]; - sum11 += kernelMatrix1R1[k] * kernelMatrix1R1[k]; - } - - /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is - * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. - */ - double a = s[0, 0]; - double b = s[0, 1]; - double c = s[1, 0]; - double d = s[1, 1]; - double divider = a * d - b * c; - reverseS[0, 0] = d / divider; - reverseS[0, 1] = -c / divider; - reverseS[1, 0] = -b / divider; - reverseS[1, 1] = a / divider; - - // double[,] reverseS = MatrixEx.ReverseMatrix(S); - - double fy0 = 0; - double fy1 = 0; - for (int i = 0; i < _length; ++i) - { - fy0 += kernelMatrix1R0[i] * y1[i]; - fy1 += kernelMatrix1R1[i] * y1[i]; - } - - double b0 = reverseS[0, 0] * fy0 + reverseS[0, 1] * fy1; - double b1 = reverseS[1, 0] * fy0 + reverseS[1, 1] * fy1; - - List results = new List(); - results.Add(b0); - results.Add(b1); - - return results; - } - - /// - /// y=b0+b1x, with equal weights for each data points. - /// caution: this method should not be removed since it has common usage for other scenarios. - /// - public PolynomialModel RegressionDegreeOne() - { - double[,] kernelMatrix = new double[_length, 2]; - for (int i = 0; i < _length; i++) - { - kernelMatrix[i, 0] = 1; - kernelMatrix[i, 1] = _x[i]; - } - double[,] s = new double[2, 2]; - for (int i = 0; i < 2; i++) - { - for (int j = 0; j < 2; j++) - { - double sum = 0; - for (int k = 0; k < _length; k++) - { - sum += kernelMatrix[k, i] * kernelMatrix[k, j]; - } - s[i, j] = sum; - } - } - - double[,] reverseS = MatrixEx.ReverseMatrix(s); - - double y0 = 0; - double y1 = 0; - for (int i = 0; i < _length; i++) - { - y0 += kernelMatrix[i, 0] * _y[i]; - y1 += kernelMatrix[i, 1] * _y[i]; - } - - double b0 = reverseS[0, 0] * y0 + reverseS[0, 1] * y1; - double b1 = reverseS[1, 0] * y0 + reverseS[1, 1] * y1; - - List results = new List(); - results.Add(b0); - results.Add(b1); - return new PolynomialModel(results); - } - - /// - /// y=b0+b1x+b2x^2, but the penalty is weighted - /// - public PolynomialModel RegressionDegreeTwoWeighted(List weights) - { - //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); - - if (weights.Count != _length) - throw new Exception("the weight vector is not equal length to the data points"); - - foreach (double value in weights) - { - if (value < 0) - throw new Exception("the value in weights should be non-negative!"); - } - - double[,] w = new double[_length, _length]; - for (int i = 0; i < _length; i++) - { - for (int j = 0; j < _length; j++) - { - if (i == j) - w[i, j] = Math.Sqrt(weights[i]); - } - } - - double[,] kernelMatrix = new double[_length, 3]; - for (int i = 0; i < _length; i++) - { - kernelMatrix[i, 0] = 1; - kernelMatrix[i, 1] = _x[i]; - kernelMatrix[i, 2] = _x[i] * _x[i]; - } - double[,] kernelMatrix1 = new double[_length, 3]; - for (int i = 0; i < _length; i++) - { - for (int j = 0; j < 3; j++) - { - kernelMatrix1[i, j] = w[i, i] * kernelMatrix[i, j]; - } - } - double[] ty1 = new double[_length]; - for (int i = 0; i < _length; i++) - ty1[i] = w[i, i] * _y[i]; - - double[,] s = new double[3, 3]; - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - double sum = 0; - for (int k = 0; k < _length; k++) - { - sum += kernelMatrix1[k, i] * kernelMatrix1[k, j]; - } - s[i, j] = sum; - } - } - - double[,] reverseS = MatrixEx.ReverseMatrix(s); - - double y0 = 0; - double y1 = 0; - double y2 = 0; - for (int i = 0; i < _length; i++) - { - y0 += kernelMatrix1[i, 0] * ty1[i]; - y1 += kernelMatrix1[i, 1] * ty1[i]; - y2 += kernelMatrix1[i, 2] * ty1[i]; - } - - double b0 = reverseS[0, 0] * y0 + reverseS[0, 1] * y1 + reverseS[0, 2] * y2; - double b1 = reverseS[1, 0] * y0 + reverseS[1, 1] * y1 + reverseS[1, 2] * y2; - double b2 = reverseS[2, 0] * y0 + reverseS[2, 1] * y1 + reverseS[2, 2] * y2; - - List results = new List(); - results.Add(b0); - results.Add(b1); - results.Add(b2); - return new PolynomialModel(results); - } - - /// - /// y=b0+b1x+b2x^2, with equal weights for each data points - /// caution: this method should not be removed since it has common usage for other scenarios. - /// - public PolynomialModel RegressionDegreeTwo() - { - double[,] kernelMatrix = new double[_length, 3]; - for (int i = 0; i < _length; i++) - { - kernelMatrix[i, 0] = 1; - kernelMatrix[i, 1] = _x[i]; - kernelMatrix[i, 2] = _x[i] * _x[i]; - } - double[,] s = new double[3, 3]; - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - double sum = 0; - for (int k = 0; k < _length; k++) - { - sum += kernelMatrix[k, i] * kernelMatrix[k, j]; - } - s[i, j] = sum; - } - } - - double[,] reverseS = MatrixEx.ReverseMatrix(s); - - double y0 = 0; - double y1 = 0; - double y2 = 0; - for (int i = 0; i < _length; i++) - { - y0 += kernelMatrix[i, 0] * _y[i]; - y1 += kernelMatrix[i, 1] * _y[i]; - y2 += kernelMatrix[i, 2] * _y[i]; - } - - double b0 = reverseS[0, 0] * y0 + reverseS[0, 1] * y1 + reverseS[0, 2] * y2; - double b1 = reverseS[1, 0] * y0 + reverseS[1, 1] * y1 + reverseS[1, 2] * y2; - double b2 = reverseS[2, 0] * y0 + reverseS[2, 1] * y1 + reverseS[2, 2] * y2; - - List results = new List(); - results.Add(b0); - results.Add(b1); - results.Add(b2); - return new PolynomialModel(results); - } } /// diff --git a/src/Microsoft.ML.TimeSeries/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/LocalRegression.cs index 183d93acfd..9fd22818e1 100644 --- a/src/Microsoft.ML.TimeSeries/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/LocalRegression.cs @@ -241,21 +241,8 @@ public double Y(double xValue) private PolynomialModel Regression() { - PolynomialModel result = null; LeastSquares ls = new LeastSquares(NeighborsX, NeighborsY); - switch (LoessConfiguration.ModelType) - { - case RegressionModelType.One: - result = ls.RegressionDegreeOneWeighted(Weights); - break; - case RegressionModelType.Two: - result = ls.RegressionDegreeTwoWeighted(Weights); - break; - default: - result = ls.RegressionDegreeOneWeighted(Weights); - break; - } - return result; + return ls.RegressionDegreeOneWeighted(Weights); } } } diff --git a/src/Microsoft.ML.TimeSeries/MatrixEx.cs b/src/Microsoft.ML.TimeSeries/MatrixEx.cs deleted file mode 100644 index f8800b1ea4..0000000000 --- a/src/Microsoft.ML.TimeSeries/MatrixEx.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - internal static class MatrixEx - { - /// - /// Calculate the inverse of a matrix - /// Please make sure that: - /// 1. the input matrix is a square matrix; - /// 2. the input matrix is not singular. - /// - /// the input matrix - /// the inverse of a matrix - public static double[,] ReverseMatrix(this double[,] matrix) - { - if (matrix.GetLength(0) != matrix.GetLength(1)) - return null; - int level = matrix.GetLength(0); - - // Calculate the determinant value of the matrix - double determinantMatrixValue = MatrixValue(matrix, level); - if (determinantMatrixValue == 0) - return null; - - double[,] reverseMatrix = new double[level, 2 * level]; - double x; - double c; - - // Init Reverse matrix - for (int i = 0; i < level; i++) - { - for (int j = 0; j < 2 * level; j++) - { - if (j < level) - reverseMatrix[i, j] = matrix[i, j]; - else - reverseMatrix[i, j] = 0; - } - - reverseMatrix[i, level + i] = 1; - } - - for (int i = 0, j = 0; i < level && j < level; i++, j++) - { - if (reverseMatrix[i, j] == 0) - { - int m = i; - for (; matrix[m, j] == 0; m++) - { - } - - if (m == level) - { - return null; - } - else - { - // Add i-row with m-row - for (int n = j; n < 2 * level; n++) - reverseMatrix[i, n] += reverseMatrix[m, n]; - } - } - - // Format the i-row with "1" start - x = reverseMatrix[i, j]; - if (x != 1) - { - for (int n = j; n < 2 * level; n++) - { - if (reverseMatrix[i, n] != 0) - reverseMatrix[i, n] /= x; - } - } - - // Set 0 to the current column in the rows after current row - for (int s = level - 1; s > i; s--) - { - x = reverseMatrix[s, j]; - for (int t = j; t < 2 * level; t++) - reverseMatrix[s, t] -= reverseMatrix[i, t] * x; - } - } - - // Format the first matrix into unit-matrix - for (int i = level - 2; i >= 0; i--) - { - for (int j = i + 1; j < level; j++) - { - if (reverseMatrix[i, j] != 0) - { - c = reverseMatrix[i, j]; - for (int n = j; n < 2 * level; n++) - reverseMatrix[i, n] -= c * reverseMatrix[j, n]; - } - } - } - - double[,] result = new double[level, level]; - for (int i = 0; i < level; i++) - { - for (int j = 0; j < level; j++) - result[i, j] = reverseMatrix[i, j + level]; - } - return result; - } - - /// - /// Calculate the determinant value of a matrix - /// - public static double MatrixValue(double[,] matrixList, int level) - { - double[,] matrix = new double[level, level]; - for (int i = 0; i < level; i++) - { - for (int j = 0; j < level; j++) - matrix[i, j] = matrixList[i, j]; - } - double c; - double x; - int k = 1; - for (int i = 0, j = 0; i < level && j < level; i++, j++) - { - if (matrix[i, j] == 0) - { - int m = i; - for (; matrix[m, j] == 0; m++) - { - } - - if (m == level) - { - return 0; - } - else - { - // Row change between i-row and m-row - for (int n = j; n < level; n++) - { - c = matrix[i, n]; - matrix[i, n] = matrix[m, n]; - matrix[m, n] = c; - } - - // Change value pre-value - k *= -1; - } - } - - // Set 0 to the current column in the rows after current row - for (int s = level - 1; s > i; s--) - { - x = matrix[s, j]; - for (int t = j; t < level; t++) - matrix[s, t] -= matrix[i, t] * (x / matrix[i, j]); - } - } - - double sn = 1; - for (int i = 0; i < level; i++) - { - if (matrix[i, i] != 0) - sn *= matrix[i, i]; - else - return 0; - } - return k * sn; - } - } -} From b398fdbcb1fd01b026dc3436458ed7201be8fea3 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 18:23:10 +0800 Subject: [PATCH 08/49] remove unused functions --- src/Microsoft.ML.TimeSeries/MathsTools.cs | 1538 --------------------- 1 file changed, 1538 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/MathsTools.cs b/src/Microsoft.ML.TimeSeries/MathsTools.cs index 862f51f60d..91342f6173 100644 --- a/src/Microsoft.ML.TimeSeries/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/MathsTools.cs @@ -6,168 +6,6 @@ namespace Microsoft.ML.TimeSeries { public class MathUtility { - private const int PearsonCoeffMinLength = 5; - - private const double SquareRootOfTwo = 1.414213562373095; - - /// - /// Returns the reciprocal of , guarding against division by zero. - /// If the input value is less than the given , this method does not perform - /// the division and simply returns zero. - /// - public static double ReciprocalOrZero(double value, double precision) - { - if (Math.Abs(value) < precision) - return 0.0; - - return 1.0 / value; - } - - /// - /// efficient method (O(m) complexity) for randomly sample m data points from total n data points. with complexity O(m) - /// these n data points are indexed from 0 to n-1. - /// - /// total number of data points - /// number of points be sampled - /// return the indexes of the m randomly sampled points - public static int[] RandomSampling(int n, int m) - { - if (n <= 0 || m > n || m <= 0) - return null; - int[] result = new int[m]; - for (int i = 0; i < m; i++) - result[i] = i; - if (m == n) - { - return result; - } - - // we use fixed seed, to make the results stable. - Random rd = new Random(0); - for (int i = m + 1; i < n; i++) - { - var value = rd.Next(i); - - // probability m/i is hit, will use i to replace - if (value < m) - { - var chosenIndex = rd.Next(m); - result[chosenIndex] = i; - } - } - return result; - } - - /// - /// calculate the cosine of two input 2-dimensional vectors. return false if either vector is a zero vector, where - /// cosine is undefined there. - /// - public static bool Cosine(double vector1X, double vector1Y, double vector2X, double vector2Y, out double cosine) - { - cosine = double.NaN; - double norm = Math.Sqrt((vector1X * vector1X + vector1Y * vector1Y) * (vector2X * vector2X + vector2Y * vector2Y)); - if (norm == 0.0) - return false; - cosine = (vector1X * vector2X + vector1Y * vector2Y) / norm; - return true; - } - - /// - /// calculate the cosine of two input vectors. return false if either vector is a zero vector, where - /// cosine is undefined there. - /// - public static bool Cosine(double[] vector1, double[] vector2, out double cosine) - { - cosine = double.NaN; - if (vector1 == null || vector2 == null || vector1.Length == vector2.Length || vector1.Length == 0) - return false; - double norm1 = 0.0; - double norm2 = 0.0; - double innerProduct = 0.0; - for (int i = 0; i < vector1.Length; i++) - { - norm1 += vector1[i] * vector1[i]; - norm2 += vector2[i] * vector2[i]; - innerProduct += vector1[i] * vector2[i]; - } - if (norm1 == 0.0 || norm2 == 0.0) - return false; - cosine = innerProduct / Math.Sqrt(norm1 * norm2); - return true; - } - - /// - /// error function. - /// - /// given the input x - public static double Erf(double x) - { - // handle either positive or negative x. because error function is negatively symmetric of x - double a = 0.140012; - double b = x * x; - double item = -b * (4 / Math.PI + a * b) / (1 + a * b); - double result = Math.Sqrt(1 - Math.Exp(item)); - if (x >= 0) - return result; - return -result; - } - - /// - /// calculate the standard cumulative distribution function F(x) = P(Z less or equal than x), where Z follows - /// a standard normal distribution. - /// - public static double StandardCdf(double x) - { - return (1.0 + Erf(x / SquareRootOfTwo)) / 2; - } - - /// - /// given a confidence level as input, we calculate the Z such that P(Z greater than alpha) = alpha - /// - public static double ReverseAlpha(double alpha) - { - var p = 1.0 - alpha; - - // for a standard normal distribution, the probability that x is smaller than lower or x is larger than upper is almost zero. - // we can set a larger value but already has no gain. - double lower = -5.0; - double upper = 5.0; - double middle; - while (true) - { - middle = (lower + upper) / 2; - var estimate = StandardCdf(middle); - if (Math.Abs(estimate - p) < 0.00000001) - break; - - // because standard CDF is monotonic, thus we can use binary search - if (estimate > p) - { - upper = middle; - } - else - { - lower = middle; - } - } - return middle; - } - - /// - /// calculate the statistical significance for a gaussian distribution. - /// - /// the observed x value - /// mean value - /// the standard deviation - public static double GaussianSignificance(double x, double u, double sigma) - { - double x1 = Math.Abs(x - u); - - // 1.414213562373095 is sqrt(2) - double cdf = 0.5 + 0.5 * Erf(x1 / sigma / 1.414213562373095); - return 2 * cdf - 1; - } - /// /// calculate the standard sigmoid function /// @@ -176,1381 +14,5 @@ public static double Sigmoid(double x) { return 1.0 / (1.0 + Math.Exp(-x)); } - - /// - /// use quick-sort like method to obtain the median value. - /// the complexity in expectation is O(n), which is faster than using quickSort. - /// - /// the input list of values. note that this list will be modified after calling this method - /// returns the median value - public static double QuickMedian(List values) - { - if (values == null || values.Count == 0) - return double.NaN; - - // here the third parameter is start from 1. so we need to plus 1 to compliant. - return QuickSelect(values, 0, values.Count - 1, values.Count / 2 + 1); - } - - private static double QuickSelect(List values, int start, int end, int k) - { - if (start == end) - return values[start]; - int q = Partition(values, start, end); - int index = q - start + 1; - if (index == k) - return values[q]; - else if (k < index) - return QuickSelect(values, start, q - 1, k); - return QuickSelect(values, q + 1, end, k - index); - } - - /// - /// This version of QuickSelect follows a similar idea as the one above, except that this method - /// does not use the Partition() method, and, therefore, does not modify the original values. - /// On average, this version is faster (~40% faster) and has better memory allocation (~60% less). - /// - /// The list of values - /// The k smallest value in the list - public static double QuickSelect(IReadOnlyList values, int k) - { - var nums = values; - double[] left = new double[values.Count]; - double[] right = new double[values.Count]; - int numsCount = nums.Count; - - while (true) - { - if (numsCount == 1) - return nums[0]; - - int idx = FindMedianIndex(nums, 0, numsCount - 1); - double key = nums[idx]; - - int leftIdx = 0; - int rightIdx = 0; - for (int i = 0; i < numsCount; i++) - { - if (i == idx) - continue; - - if (nums[i] < key) - left[leftIdx++] = nums[i]; - else - right[rightIdx++] = nums[i]; - } - - if (leftIdx == k - 1) - return key; - - if (leftIdx >= k) - { - nums = left; - numsCount = leftIdx; - } - else - { - nums = right; - k = k - leftIdx - 1; - numsCount = rightIdx; - } - } - } - - private static int Partition(List values, int start, int end) - { - int medianIndex = FindMedianIndex(values, start, end); - if (medianIndex != end) - { - double temp = values[end]; - values[end] = values[medianIndex]; - values[medianIndex] = temp; - } - double pivot = values[end]; - int startIndex = start; - int endIndex = end - 1; - while (startIndex < endIndex) - { - while (values[startIndex] < pivot && startIndex < endIndex) - { - startIndex++; - } - while (values[endIndex] > pivot && endIndex > startIndex) - { - endIndex--; - } - if (startIndex == endIndex) - break; - double temp = values[startIndex]; - values[startIndex] = values[endIndex]; - values[endIndex] = temp; - startIndex++; - endIndex--; - } - if (values[startIndex] > pivot) - { - double temp = values[startIndex]; - values[startIndex] = pivot; - values[end] = temp; - } - else - { - startIndex++; - double temp = values[startIndex]; - values[startIndex] = pivot; - values[end] = temp; - } - return startIndex; - } - - public static int FindMedianIndex(IReadOnlyList values, int start, int end) - { - // use the middle value among first/middle/end as the guard value, to make sure the average performance good. - // according to unit test, this fix will improve the average performance 10%. and works normally when input list is ordered. - double first = values[start]; - double last = values[end]; - int midIndex = (start + end) / 2; - int medianIndex = -1; - double middleValue = values[midIndex]; - if (first < last) - { - if (middleValue > last) - { - // last is the middle value - medianIndex = end; - } - else if (middleValue > first) - { - // middleValue is the middle value - medianIndex = midIndex; - } - else - { - // first is the middle value - medianIndex = start; - } - } - else - { - if (middleValue > first) - { - // first is the middle value - medianIndex = start; - } - else if (middleValue < last) - { - // last is the middle value - medianIndex = end; - } - else - { - // middleValue is the middle value - medianIndex = midIndex; - } - } - return medianIndex; - } - - /// - /// Calculate the Pearson correlation of two series of real numbers with filter conditions. - /// The Pearson score is in [-1, 1]. - /// NOTE: We have 2 versions of this function in order to avoid allocating extra memory. This method should be - /// removed once we change everything to k-dimension. In the mean time, any changes made here should also be made - /// in the other version of this method. - /// - /// The first numerical vector - /// The second numerical vector - /// The indexes that are accountable for the pearson correlation calculation - public static double PearsonCoeff( - IReadOnlyList vector1, - IReadOnlyList vector2, - IReadOnlyList validIndexes = null) - { - // Conduct pearson correlation only when the two series are equal length, and also with at least 5 data points. - // Otherwise, the correlation may not be statistically significant - if (vector1.Count <= PearsonCoeffMinLength || - vector1.Count != vector2.Count || - (validIndexes != null && validIndexes.Count <= PearsonCoeffMinLength)) - return 0; - - bool hasIndexes = validIndexes != null && validIndexes.Count > 0; - int length = hasIndexes ? validIndexes.Count : vector1.Count; - - double averageX = 0; - double averageY = 0; - if (hasIndexes) - { - for (int i = 0; i < length; i++) - { - int idx = validIndexes[i]; - averageX += vector1[idx]; - averageY += vector2[idx]; - } - } - else - { - for (int i = 0; i < length; i++) - { - averageX += vector1[i]; - averageY += vector2[i]; - } - } - averageX /= length; - averageY /= length; - - double stdvX = 0; - double stdvY = 0; - if (hasIndexes) - { - for (int i = 0; i < length; i++) - { - int idx = validIndexes[i]; - double deltaX = vector1[idx] - averageX; - double deltaY = vector2[idx] - averageY; - stdvX += deltaX * deltaX; - stdvY += deltaY * deltaY; - } - } - else - { - for (int i = 0; i < length; i++) - { - double deltaX = vector1[i] - averageX; - double deltaY = vector2[i] - averageY; - stdvX += deltaX * deltaX; - stdvY += deltaY * deltaY; - } - } - - // This happens when one vector with identical values. - if (stdvX == 0.0 || stdvY == 0.0) - return 0; - - stdvX = Math.Sqrt(stdvX / (length - 1)); - stdvY = Math.Sqrt(stdvY / (length - 1)); - - double coeff = 0; - if (hasIndexes) - { - for (int i = 0; i < length; i++) - { - int idx = validIndexes[i]; - double item1 = (vector1[idx] - averageX) / stdvX; - double item2 = (vector2[idx] - averageY) / stdvY; - coeff += item1 * item2; - } - } - else - { - for (int i = 0; i < length; i++) - { - double item1 = (vector1[i] - averageX) / stdvX; - double item2 = (vector2[i] - averageY) / stdvY; - coeff += item1 * item2; - } - } - - return coeff / (length - 1); - } - - public static double PearsonCoeff( - IReadOnlyList values, - IReadOnlyList validIndexes = null) - { - // Conduct pearson correlation only when the two series are equal length, and also with at least 5 data points. - // Otherwise, the correlation may not be statistically significant - if (values.Count <= PearsonCoeffMinLength || - (validIndexes != null && validIndexes.Count <= PearsonCoeffMinLength)) - { - return 0; - } - - bool hasIndexes = validIndexes != null && validIndexes.Count > 0; - int length = hasIndexes ? validIndexes.Count : values.Count; - - double averageX = 0; - double averageY = 0; - if (hasIndexes) - { - for (int i = 0; i < length; i++) - { - int idx = validIndexes[i]; - averageX += values[idx][0]; - averageY += values[idx][1]; - } - } - else - { - for (int i = 0; i < length; i++) - { - averageX += values[i][0]; - averageY += values[i][1]; - } - } - averageX /= length; - averageY /= length; - - double stdvX = 0; - double stdvY = 0; - if (hasIndexes) - { - for (int i = 0; i < length; i++) - { - int idx = validIndexes[i]; - double deltaX = values[idx][0] - averageX; - double deltaY = values[idx][1] - averageY; - stdvX += deltaX * deltaX; - stdvY += deltaY * deltaY; - } - } - else - { - for (int i = 0; i < length; i++) - { - double deltaX = values[i][0] - averageX; - double deltaY = values[i][1] - averageY; - stdvX += deltaX * deltaX; - stdvY += deltaY * deltaY; - } - } - - // This happens when one vector with identical values. - if (stdvX == 0.0 || stdvY == 0.0) - return 0; - - stdvX = Math.Sqrt(stdvX / (length - 1)); - stdvY = Math.Sqrt(stdvY / (length - 1)); - - double coeff = 0; - if (hasIndexes) - { - for (int i = 0; i < length; i++) - { - int idx = validIndexes[i]; - double item1 = (values[idx][0] - averageX) / stdvX; - double item2 = (values[idx][1] - averageY) / stdvY; - coeff += item1 * item2; - } - } - else - { - for (int i = 0; i < length; i++) - { - double item1 = (values[i][0] - averageX) / stdvX; - double item2 = (values[i][1] - averageY) / stdvY; - coeff += item1 * item2; - } - } - - return coeff / (length - 1); - } - - /// - /// Compute the square of the euclidean distance between 2 k-dimensional points. - /// - /// The first point - /// The second point - public static double SquareDistance(double[] point1, double[] point2) - { - if (point1.Length == 0 || point1.Length != point2.Length) - return double.NaN; - - double result = 0; - for (int i = 0; i < point1.Length; i++) - { - var delta = point1[i] - point2[i]; - result += delta * delta; - } - - return result; - } - } - - /// - /// this class is used to calculate the common mathematical properties of time series. - /// - public class TimeSeriesProperty - { - /// - /// 0.1 ~ 0.3 * 0.3, which means, when the fluctuation is more than 30% of the base signal, we think it is noisy. - /// - private const double NoisyLevelThreshold = 0.1; - - /// - /// when the different between the typical distance (represented by median) and max/min distance is smaller than this threshold, - /// we think the equal distance property is approximately hold. - /// [6821022]: parameter is tuned to fix this bug. - /// - private const double AlmostEqualDistanceThreshold = 0.1; - - /// - /// when LargestGap/Median exceeds this threshold, we think the non-equal distance becomes illness. - /// this threshold is a magnitude-level threshold - /// - private const double MaxNonEqualDistanceThreshold = 6.0; - - /// - /// one data point is defined as spatial outlier is its gap: - /// gap/median exceeds this threshold. - /// - private const double SpatialOutlierThreshold = 1.5; - - /// - /// when the population rate of spatial outliers exceeds this threshold, we think the non-equal distance becomes illness. - /// - private const double SpatialOutlierRateThreshold = 0.1; - - /// - /// we are using Gaussian kernel smoothing function. here this value refers to the weight of the nearest neighbor of a given point. - /// NOTE: this parameter effects the smoothing radius, which relates to the overall smoothing complexity. we should carefully tune this parameter. - /// - private const double KernelSmoothingParameter = 0.6; - - /// - /// when the weight is smaller than this threshold, it will not be taken into account for weighted smoothing. - /// - private const double MinSmoothEffectedWeight = 0.01; - - /// - /// this is the threshold of the angle of the corner point, to check if it is significant or not. - /// this threshold is carefully tuned. since we are using Gaussian kernel for smoothing, the smoothing window size is smaller than previous method, - /// which cause the corner point threshold should be changed as well. - /// - private const double CornerPointThreshold = 121; - - /// - /// for performance optimization. - /// the smoothing radius can be pre-calculated, so that we do not need to calculate it on-the-fly. - /// NOTE: the overall complexity of smoothing a whole time series is O(n * smoothing radius) - /// - private static readonly int _smoothingRadius; - - /// - /// the smoothing weights for the data points near the given data point (with the range of radius) - /// - private static readonly double[] _smoothingWeights; - - /// - /// this is used to boost the normalization. this is a cumulative array of the weights within the range of [-radius, radius]. specifically, this is with - /// length equals to 2*radius + 2, i.e., c0=0, c1, c2, ... c(r), c(r+1), c(r+2), ... c(2r+1), where c(r) is the center. - /// - private static readonly double[] _cumulativeDenominator; - - static TimeSeriesProperty() - { - _smoothingRadius = (int)Math.Sqrt(Math.Log(MinSmoothEffectedWeight) / Math.Log(KernelSmoothingParameter)); - if (_smoothingRadius >= 0) - { - _smoothingWeights = new double[_smoothingRadius + 1]; - _smoothingWeights[0] = 1.0; - for (int i = 1; i < _smoothingWeights.Length; i++) - { - _smoothingWeights[i] = Math.Pow(KernelSmoothingParameter, i * i); - } - - _cumulativeDenominator = new double[2 * _smoothingRadius + 2]; - _cumulativeDenominator[0] = 0.0; - for (int i = 1; i < _cumulativeDenominator.Length; i++) - { - int dis = Math.Abs(_smoothingRadius + 1 - i); - _cumulativeDenominator[i] = _cumulativeDenominator[i - 1] + _smoothingWeights[dis]; - } - } - } - - /// - /// given a pure seasonal series, calculate the average amplitude. - /// - /// the input time series, which should be pure seasonal - /// the period of the seasonal time series - public static double Amplitude(IReadOnlyList seasonalSeries, int period) - { - double result = 0; - int numberOfPeriods = 0; - for (int i = 0; i + period <= seasonalSeries.Count; i += period) - { - numberOfPeriods++; - double max = double.MinValue; - double min = double.MaxValue; - for (int j = i; j < i + period; j++) - { - max = Math.Max(max, seasonalSeries[j]); - min = Math.Min(min, seasonalSeries[j]); - } - result += max - min; - } - - // divde by 2, is by definition of amplitude. - return result / numberOfPeriods / 2; - } - - /// - /// obtain the outliers by using 6-sigma methods. - /// - /// the residual that the regression curve is eliminated - /// outlier Severity - /// the mean residual squares is an additional output - /// significance - /// measure the strictness of the outlier. default value is 6, which is very strict for selecting outliers. - public static int[] DetermineOutliers(IReadOnlyList residual, out double[] outlierSeverity, out double mrs, out double significance, double severityThreshold = 6) - { - int length = residual.Count; - - // epsilon is used to represents a very small positive real value. - const double epsilon = 0.0001; - mrs = 0; - significance = 0; - outlierSeverity = new double[length]; - var outlierIndexes = new int[length]; - - var absResiduals = new double[length]; - double nonzeroMin = double.MaxValue; - for (int i = 0; i < length; i++) - { - absResiduals[i] = Math.Abs(residual[i]); - if (absResiduals[i] > epsilon && nonzeroMin > absResiduals[i]) - nonzeroMin = absResiduals[i]; - } - - double median = MathUtility.QuickSelect(absResiduals, length / 2); - - // When the median is 0, this is possible when there are more than half 0s. - // In such case, it is not perfect to mark all the non-zero points as outliers. - // Therefore, we need to find the first value that is not zero. - if (median < epsilon) - { - median = nonzeroMin; - } - - // this implies that all the values are smaller than epsilon, therefore, there's no outliers at all. - if (median < epsilon) - { - for (int i = 0; i < length; i++) - { - outlierIndexes[i] = 0; - outlierSeverity[i] = 0; - } - } - else - { - // when median is very close to 0, which means the regularity of the serial is strong, so that no data points is outlier. - for (int i = 0; i < length; i++) - { - double severity = Math.Abs(residual[i]) / median; - - // this is the key criteria - if (severity > severityThreshold) - { - double newSignificance = MathUtility.GaussianSignificance(residual[i], median, 3 * median); - if (newSignificance > significance) - significance = newSignificance; - outlierIndexes[i] = 1; - outlierSeverity[i] = severity; - } - else - { - outlierIndexes[i] = 0; - outlierSeverity[i] = 0; - } - - // Here, we estimate the Mrs by adding all the residuals, rather than the residuals of non-outliers. - // Such compromise from robust statistic is for user friendly consideration. - mrs += residual[i] * residual[i]; - } - mrs /= length; - } - return outlierIndexes; - } - - /// - /// this is a method to calculate the CCF lower bound of two time series, by given the first time series, and partial information of the second - /// time series, which is indicated by alpha. - /// specifically, the second time series is the child of the first one from subspace perspective. - /// both should be non-negative, and the first one is larger than the second one for each time epoch. let sum1 is the sum of all values from series1, - /// and sum2 is the sum of all values from series2, then sum2=sum1*(1-alpha). when alpha is a small value, the two time series naturally be correlated, which is not interesting. - /// - /// represents the first time series - /// represents the overall different from the second time series. which should be between 0 and 1. this method works well only when alpha largely smaller than 1 - /// returns the strict lower bound of the CCF of the two time series. - public static double CcfLowerBound(List series, double alpha) - { - int n = series.Count; - double sum = 0; - double max = 0; - double min = double.MaxValue; - foreach (double item in series) - { - sum += item; - max = Math.Max(max, item); - min = Math.Min(min, item); - } - double mean = sum / n; - double var = 0; - foreach (double item in series) - { - var += (item - mean) * (item - mean); - } - double numerator = var + n * alpha * mean * (mean - max); - double denominator = Math.Sqrt(var * (var + alpha * (n * n * alpha * mean * mean - n * alpha * mean * mean + 2 * n * mean * mean - 2 * n * mean * min))); - return numerator / denominator; - } - - /// - /// obtain the outliers by inspecting the residual distribution. - /// this is a more sophisticated method rather than simply set the 6-median, or 3-sigma to determine outliers. - /// - /// the input residuals after trending - /// in the problem of why-analysis, the suitable threshold of corner point is difference than default, thus need to make this as a parameter rather than constant - /// returns the index of the potential outlier candidates. the index is aligned with the input residual - public static List DetermineOutliersEx(IReadOnlyList residual, double cornerPointThreshold = CornerPointThreshold) - { - List result = new List(); - int length = residual.Count; - if (length < 3) - return result; - - List> absResiduals = new List>(); - for (int i = 0; i < residual.Count; i++) - { - double absValue = Math.Abs(residual[i]); - absResiduals.Add(new KeyValuePair(absValue, i)); - } - absResiduals.Sort(ReverseComparer); - - // smoothing. the reason is after we find the corner point, we still eliminate the extra points which are close to the corner point. - var rawY = new double[absResiduals.Count]; - for (int i = 0; i < absResiduals.Count; i++) - { - rawY[i] = absResiduals[i].Key; - } - var smoothedY = KernelSmooth(rawY); - - // normalization. because we use geometric way to identify the corner point. - double yMax = smoothedY[0]; - double yMin = smoothedY[length - 1]; - double xMax = length - 1; - double xMin = 0.0; - List normX = new List(); - List normY = new List(); - for (int i = 0; i < length; i++) - { - normX.Add((i - xMin) / (xMax - xMin)); - normY.Add((smoothedY[i] - yMin) / (yMax - yMin)); - } - - // inspecting the inner product and the angle of each check point - int cornerPointIndex = -1; - double bestAngle = double.MaxValue; - for (int i = 1; i < length - 1; i++) - { - double angle; - double x = normX[i]; - double y = normY[i]; - - if (!EvaluateCornerPoint(x, y, out angle)) - continue; - - if (angle < bestAngle) - { - bestAngle = angle; - cornerPointIndex = i; - } - } - if (cornerPointIndex == -1) - return result; - double normRawX = cornerPointIndex * 1.0 / (length - 1); - double normRawY = (rawY[cornerPointIndex] - rawY[length - 1]) / (rawY[0] - rawY[length - 1]); - double bestRawAngle; - - // we use smoothing curve to obtain the reasonable corner point, then we use the raw (also normalized) curve to obtain the true angle - // of the corner point. this makes best sense: smoothing makes sure robust corner point identification, while angle on the raw curve - // reflects how the original residuals behave. - if (!EvaluateCornerPoint(normRawX, normRawY, out bestRawAngle)) - return result; - - if (bestRawAngle > cornerPointThreshold) - return result; - - // further absort the data points close to the corner point, that doesnt look like outliers - double slope = (1 - normY[cornerPointIndex]) / normX[cornerPointIndex]; - int absorbIndex = -1; - for (absorbIndex = cornerPointIndex - 1; absorbIndex >= 0; absorbIndex--) - { - double currentSlope = (normY[absorbIndex] - normY[absorbIndex + 1]) / (normX[absorbIndex + 1] - normX[absorbIndex]); - if (currentSlope > slope) - break; - } - - if (absorbIndex != -1) - cornerPointIndex = absorbIndex + 1; - - for (int i = 0; i < cornerPointIndex; i++) - { - result.Add(absResiduals[i].Value); - } - - return result; - } - - /// - /// check if the (almost) equal distance property is hold - /// - /// the x-axis values - /// the typical gap/distance between two adjacent data points - /// return true if the x-axis values are equal or almost equal distance - public static bool IsEqualDistance(IReadOnlyList xValues, out double typicalGap) - { - typicalGap = double.NaN; - int length = xValues.Count; - - // equal distance is undefined under such condition - if (xValues == null || length < 2) - { - return false; - } - else if (length == 2) - { - typicalGap = xValues[1] - xValues[0]; - return true; - } - - // if this is indeed a regular distance (i.e., equal-distance segments divided by regular gaps) - List gaps; - double regularGap; - double minGap; - double maxGap; - if (IsRegularEqualDistance(xValues, out typicalGap, out regularGap, out gaps, out minGap, out maxGap)) - return true; - - // will inspect the x values only when the gaps is null after checking regular equal-distance. - if (gaps == null) - { - gaps = new List(xValues.Count - 1); - for (int i = 1; i < length; i++) - { - double gap = xValues[i] - xValues[i - 1]; - maxGap = Math.Max(maxGap, gap); - minGap = Math.Min(minGap, gap); - gaps.Add(gap); - } - } - typicalGap = MathUtility.QuickMedian(gaps); - - // the min gap should not be too small - if (typicalGap - minGap > AlmostEqualDistanceThreshold * typicalGap) - return false; - - // the max gap should not be too large - if (maxGap - typicalGap > AlmostEqualDistanceThreshold * typicalGap) - return false; - return true; - } - - /// - /// considering there would be the case that final visual effect is confusing, although underlying algorithm could deal with non-equal distance. - /// Therefore, we need to identify the strong non-equal distance datasets, and avoid analyze or output it. - /// Here I propose several rules to define what is a strong non-equal distance behavior: - /// 1 - a point is an outlier (spatial outlier) if and only if its gap is SpatialOutlierThreshold larger than typical distance (represented by median gap) - /// 2 - an outlier is illness if its gap is MaxNonEqualDistanceThreshold larger from typical distance. when there exist illness outlier, we bypass time series analysis - /// 3 - the ratio of the outliers cannot exceed SpatialOutlierRateThreshold of total number of points - /// 4 - any two outliers must NOT be adjacent (otherwise, the analysis results may differ from user intuition! think about it) - /// - /// the input x-axis values - /// returns true if the input series is an illness/strongly non-equal distance series. - public static bool IsStrongNonEqualDistance(List xValues) - { - if (xValues == null) - return false; - - int length = xValues.Count; - if (length <= 2) - return false; - - // if it is a regular equal-distance, then strong non-equal distance is false. - double typicalGap; - double regularGap; - List gaps; - double minGap; - double maxGap; - if (IsRegularEqualDistance(xValues, out typicalGap, out regularGap, out gaps, out minGap, out maxGap)) - return false; - - if (gaps == null) - { - gaps = new List(xValues.Count - 1); - maxGap = double.MinValue; - for (int i = 1; i < length; i++) - { - double gap = xValues[i] - xValues[i - 1]; - gaps.Add(gap); - maxGap = Math.Max(maxGap, gap); - } - } - - // since this method will modify the input list. we need to copy one in order to preserve the original order - double median = MathUtility.QuickMedian(new List(gaps)); - double threshold = SpatialOutlierThreshold * median; - - // illness checking. - if (maxGap > median * MaxNonEqualDistanceThreshold) - return true; - - int outlierCount = 0; - for (int i = 0; i < gaps.Count; i++) - { - double gap = gaps[i]; - if (gap > threshold) - { - outlierCount++; - - // if two spatial outliers are adjacent, then the left point will look like an isolated data point. isolated data points are not addressed in any type of insights so far. - if (i != 0 && gaps[i - 1] > threshold) - { - return true; - } - - // if more than SpatialOutlierRateThreshold of total points are outliers, we think this is a very strong non-equal distance series - if (outlierCount > SpatialOutlierRateThreshold * gaps.Count) - return true; - } - } - return false; - } - - /// - /// check if the (almost) equal distance property is hold by considering an additional regular gap. - /// the motivation is from the stock time series, which has stock records on weekdays, but no records at weekends, regularly. - /// in order to support this, we should first identify the consistency gap (~2 days), then the left gaps must qualify the equal-distance gap. - /// [remark]: in order to be regular equal-distance x-values, the segments (with typical gap) must be with equal number of data points (the - /// first and the last segment can contain fewer data points), and strictly separated by the regular gap. this method does not handle the - /// noise (a few violation). a suitable place to address the noise condition is a logic layer after data query, and before all time series - /// analysis, where we can detect these spatial noise points, and eliminate them properly before feeding into time series analysis modules. - /// - /// the x-axis values - /// the typical gap-distance between two adjacent data points - /// the regular gap between two segments (e.g., two days corresponds to the weekend in stock dataset) - /// performance: the list of gaps may be used further in other places. - /// performance: caller can directly use this value: the maximum gap along the x values - /// performance: caller can directly use this value: the minimum gap along the x values - /// return true if the x-axis values are equal distance by considering the regular gaps - public static bool IsRegularEqualDistance( - IReadOnlyList xValues, - out double typicalGap, - out double regularGap, - out List gaps, - out double minGap, - out double maxGap) - { - typicalGap = double.NaN; - regularGap = double.NaN; - minGap = double.MaxValue; - maxGap = double.MinValue; - gaps = null; - - int length = xValues.Count; - - /* when time series is with length smaller than 9, any regular gaps between equal-distance regions still - * make the overall time series 'looks' messy; the seasonal component will be weak since the recurrence of - * seasonal component is too few. - */ - if (xValues == null || length < 9) - return false; - gaps = new List(xValues.Count - 1); - for (int i = 1; i < length; i++) - { - double gap = xValues[i] - xValues[i - 1]; - maxGap = Math.Max(gap, maxGap); - minGap = Math.Min(gap, minGap); - gaps.Add(gap); - } - - // we assume the regular gap must be significantly larger than the typical gap. - if (maxGap - minGap < minGap) - return false; - - // records the typical/regular gaps, which is used to estimate a robust typical/regular gap to return. - List typicalGaps = new List(); - List regularGaps = new List(); - - /* if the regular equal-distance property is satisfied, then the middle inter-segment length must be equal, and the - length of the first or last segment must be equal or smaller than the middle length*/ - int firstSegLength = -1; - int firstMiddleSegLength = -1; - int currMiddleSegLength = -1; - for (int i = 0; i < gaps.Count; i++) - { - var currentGap = gaps[i]; - - if (currentGap - minGap <= AlmostEqualDistanceThreshold * minGap) - { - // this is a typical gap - typicalGaps.Add(currentGap); - - if (firstSegLength == -1) - { - // the first segment length is not set yet - firstSegLength = 1; - } - else if (firstMiddleSegLength == -1) - { - // the first middle segment length is not set yet - firstSegLength++; - } - else if (currMiddleSegLength == -1) - { - // the first middle segment length is just started - firstMiddleSegLength++; - } - else - { - // the current middle segment length is just started - currMiddleSegLength++; - - // current segment length is already longer than previous segment length, this is a violation. - if (currMiddleSegLength > firstMiddleSegLength) - return false; - } - } - else if (maxGap - currentGap <= AlmostEqualDistanceThreshold * maxGap) - { - // this is a regular gap - regularGaps.Add(currentGap); - - // the first middle segment length is not set yet - if (firstMiddleSegLength == -1) - { - // start counting the length of the first middle segment - firstMiddleSegLength = 0; - } - else if (currMiddleSegLength == -1) - { - // start counting the length of the next segment - currMiddleSegLength = 0; - - // the first segment cannot be longer than middle ones - if (firstSegLength > firstMiddleSegLength) - return false; - } - else - { - // the length of the middle segments must be identical - if (currMiddleSegLength != firstMiddleSegLength) - return false; - currMiddleSegLength = 0; - } - } - else - { - // the current gap is neither similar to typical gap nor similar to regular gap, this is a violation. - return false; - } - } - - // the check of the last segment - if (currMiddleSegLength > firstMiddleSegLength) - return false; - - // number of regular gaps indicate the segments, or number of repeating. this should not be too few to indicate reasonable repeating. - if (regularGaps.Count < BasicParameters.MinRegularGap) - return false; - - // use the median value to robustly represent the true typical/regular gaps - typicalGap = MathUtility.QuickMedian(typicalGaps); - regularGap = MathUtility.QuickMedian(regularGaps); - return true; - } - - /// - /// determine if a given time series is too noisy or not by considering its normalized residual squares - /// - /// normalized residual squares - /// true if the normalized residual squares exceeds the given threshold - public static bool IsTooNoiseSignal(double mrs) - { - return mrs > NoisyLevelThreshold; - } - - /// - /// calculate the angle of a given data point within a sorted residual curve. return true if the angle is valid. - /// - public static bool EvaluateCornerPoint(double x, double y, out double angle, bool inRadians = false) - { - angle = -1.0; - - // use outer product to check if the corner point is valid. if it is minus, it means a very rare thing, that this point is actually an anti-corner point - double outerProduct = (1 - x) * (1 - y) - x * y; - if (outerProduct <= 0) - return false; - - double innerProduct = -(x * (1 - x) + y * (1 - y)); - - // considering 3 points p1, p2 and p3: (0, 1), (x, y), (1, 0), they form up two vectors v1 = (-x, 1-y), v2 = (1-x, -y) - // and here we calculate the angle between the two vectors. considering the normalized sorted residual curve, the left-most - // point is p1, and the right most point is p3, here corner point is defined as p2 which has smallest angle. - // intuitively, the data points on the left side of corner point are with significant higher residuals than the others, - // which are most likely to be outliers. - double cosine = innerProduct / Math.Sqrt((x * x + (1 - y) * (1 - y)) * ((1 - x) * (1 - x) + y * y)); - angle = Math.Acos(cosine); - - // convert to 360-degree unit, which is intuitive for tuning - if (!inRadians) - angle = angle * 180 / Math.PI; - - return true; - } - - /// - /// use Gaussian kernel smoothing function to smooth a time series with equal x-axis distance - /// - /// the specified time series - /// returns the smoothed time series - internal static IReadOnlyList KernelSmooth(IReadOnlyList values) - { - if (_smoothingRadius <= 0) - return values; - - var results = new double[values.Count]; - for (int i = 0; i < values.Count; i++) - results[i] = KernelSmooth(values, i); - return results; - } - - /// - /// Gaussian kernel based smoothing. i.e., K(x1, x2) = EXP[-(x-x0)^2/2b^2]. - /// this is a simplified version, which assumes the x-axis values are equal-distance. hence given a fixed point x0, the weight K(x, x0) will look like - /// 1.0, EXP(-1/2b^2), EXP(-4/2b^2), EXP(-9/2b^2), ... etc. let EXP(-1/2b^2) = lambda, then the weights can be re-formulated as - /// 1.0, lambda, lambda^4, lambda^9, lambda^16, ... etc. - /// - /// the original one-dimensional time series - /// the specified position that needs to be smoothed - /// return the smoothed value - private static double KernelSmooth(IReadOnlyList values, int index) - { - int lowIndex = index - _smoothingRadius; - if (lowIndex < 0) - lowIndex = 0; - int upperIndex = index + _smoothingRadius; - if (upperIndex >= values.Count) - upperIndex = values.Count - 1; - double weightedSum = 0; - for (int i = lowIndex; i <= upperIndex; i++) - { - int distance = Math.Abs(i - index); - double currentWeight = _smoothingWeights[distance]; - weightedSum += values[i] * currentWeight; - } - double totalWeight = _cumulativeDenominator[_smoothingRadius + 1 + upperIndex - index] - _cumulativeDenominator[_smoothingRadius - index + lowIndex]; - return weightedSum / totalWeight; - } - - private static int ReverseComparer(KeyValuePair left, KeyValuePair right) - { - int compare1 = left.Key.CompareTo(right.Key); - if (compare1 != 0) - return -compare1; - return -left.Value.CompareTo(right.Value); - } - } - - /// - /// this class is used to calculate Combinatorics related mathematics. - /// - public class Combinatorics - { - /// - /// calculate the logarithm of factorial. the reeason for using logarithm is to control the data value. - /// or the value can easily exceed the int.MaxValue. see the ref on - /// http://en.wikipedia.org/wiki/Stirling%27s_approximation - /// - /// the input integer. - public static double LogFactorial(int n) - { - double value = 0.5 * Math.Log(2 * Math.PI * n) + n * Math.Log(n) - n + Math.Log(1 + 1.0 / 12 / n); - return value; - } - - /// - /// calculate the logarithm of combinations. n!/m!/(n-m)! - /// - /// the base number - /// the numbers should select out. m is smaller than n - public static double LogCombination(int n, int m) - { - if (n < 0 || m < 0 || m > n) - throw new Exception("combination calculation input invalid"); - if (n == 0) - return 0; - if (m == 0 || m == n) // note that here is LOG! - return 0; - return LogFactorial(n) - LogFactorial(m) - LogFactorial(n - m); - } - } - - /// - /// this class is used for density estimation for spatial data analysis. - /// currently, it is used to accept/reject if a proper scatter plot can well represent the cross-measure correlation. - /// if there exists a high density, small region, then the scatter plot will distort the true underlying correlation, we prefer rather not - /// show it to end user. - /// - public class DensityIdentifier - { - /// - /// when too few data points, the further density estimation algorithm will not be effective. - /// - private const int MinimumDataPointCount = 5; - - /// - /// number of segments on x axis, and y axis. - /// this value should be relatively large, must NOT be smaller than 4. actually, this constant is related with the high-density ratio threshold. - /// take these two values (20, 0.3) as an example, suppose all the data points form a linear line, then these data points are occupied by 20 - /// diagonal grids, each one typically with 1/20~5% data points. then 30% points is certainly a much higher ratio. - /// - private const int SegmentCount = 20; - - /// - /// when a grid with data points more than this ratio, it is viewed as a high density region. - /// - private const double HighDensityRatio = 0.3; - - /// - /// when a gap is larger than typical gap times this value, we think it is a very large gap. - /// this can be used to avoid some regular insight analysis such as cross measure correlation. - /// - private const double MaxGapThreshold = 8.0; - - /// - /// grid-based density estimation to check whether there exists at least one grid with high ratio of data points. - /// - /// the x-axis values of the data points - /// the y-axis values of the data points - /// false if there does not exist any high density region - public static bool HasHighDensityRegions(double[] xValues, double[] yValues) - { - if (xValues == null || yValues == null || xValues.Length != yValues.Length || xValues.Length < MinimumDataPointCount) - return false; - double xMin = double.MaxValue; - double xMax = double.MinValue; - double yMin = double.MaxValue; - double yMax = double.MinValue; - foreach (double value in xValues) - { - xMin = Math.Min(xMin, value); - xMax = Math.Max(xMax, value); - } - foreach (double value in yValues) - { - yMin = Math.Min(yMin, value); - yMax = Math.Max(yMax, value); - } - - double xGridSize = (xMax - xMin) / SegmentCount; - double yGridSize = (yMax - yMin) / SegmentCount; - - // if all the x or y values are identical, then trivial - if (xGridSize <= double.Epsilon || yGridSize <= double.Epsilon) - return false; - - // key indicates the location of the grid, value is number of data points within this grid - Dictionary densityPerGrid = new Dictionary(); - - int count = xValues.Length; - for (int i = 0; i < count; i++) - { - int xGridIndex = (int)((xValues[i] - xMin) / xGridSize); - int yGridIndex = (int)((yValues[i] - yMin) / yGridSize); - long hashId = GetHashId(xGridIndex, yGridIndex); - if (!densityPerGrid.ContainsKey(hashId)) - { - densityPerGrid.Add(hashId, 1); - } - else - { - densityPerGrid[hashId]++; - - // if more than 30% data points are within one small grid, which means a high portion of data points located in a small region. - if (densityPerGrid[hashId] > count * HighDensityRatio) - { - return true; - } - } - } - return false; - } - - /// - /// check if there exists a large gap along a set of 2-dimensional data points. - /// - /// the x-axis values of the data points - /// the y-axis values of the data points - /// true indicates there indeed exists a large gap; false otherwise - public static bool HasLargeGap(double[] xValues, double[] yValues) - { - if (HasLargeGap(xValues)) - return true; - if (HasLargeGap(yValues)) - return true; - return false; - } - - private static long GetHashId(int xGridIndex, int yGridIndex) - { - long xLong = (long)xGridIndex; - long yLong = (long)yGridIndex; - return (xLong << 32) + yLong; - } - - /// - /// check if there exists a large gap along a set of one dimensional data points. - /// - /// a list of one dimensional data points - /// true indicates there indeed exists a large gap; false otherwise - private static bool HasLargeGap(double[] values) - { - if (values == null || values.Length <= 3) - return false; - List sortedValues = new List(values); - sortedValues.Sort(); - - List gaps = new List(sortedValues.Count - 1); - double maxGap = double.MinValue; - for (int i = 1; i < sortedValues.Count; i++) - { - var gap = sortedValues[i] - sortedValues[i - 1]; - - // when the gap is almost zero, which means they are overlapped, we ignore this case, since it has no effect on chart. - if (gap < double.Epsilon) - continue; - maxGap = Math.Max(maxGap, gap); - gaps.Add(gap); - } - double typicalGap = MathUtility.QuickMedian(gaps); - if (typicalGap * MaxGapThreshold < maxGap) - return true; - return false; - } - } - - /// - /// move this from point insight evaluation to numerical library, because in small number analysis for why-analysis, - /// we need to use these methods to check if extreme points exist - /// - public class CalculateRankSignificance - { - public static double OutstandingRankMinusOne(double waitValue, ICollection otherValues, ref bool isSignificant) - { - if (waitValue >= 0 || otherValues.Count == 0) - { - // when less or equal to 2 points, not so meaningful - isSignificant = false; - return 0; - } - - List minusValues = new List(); - foreach (double value in otherValues) - { - if (value < 0) - { - minusValues.Add(-value); - } - } - return OutstandingRankOne(-waitValue, minusValues, ref isSignificant); - } - - public static double OutstandingRankOne(double waitValue, ICollection otherValues, ref bool isSignificant) - { - if (waitValue < 0 || otherValues.Count == 0) - { - isSignificant = false; - return 0; - } - List orderedValues = new List(); - foreach (double value in otherValues) - { - if (value > 0) - orderedValues.Add(value); - } - - orderedValues.Sort((x, y) => y.CompareTo(x)); - if (orderedValues.Count >= 2) - { - // train model - double alpha = -1; - double innerProduct = 0; - double var = 0; - for (int i = 0; i < orderedValues.Count; i++) - { - double x = i + 2; - double y = orderedValues[i]; - double phy = KernelFunc(x); - var += phy * phy; - innerProduct += phy * y; - } - alpha = innerProduct / var; - - // get error collection - double meanError = 0; - double varError = 0; - for (int i = 0; i < orderedValues.Count; i++) - { - double currentError = Math.Abs(orderedValues[i] - (alpha * KernelFunc(i + 2))); - meanError += currentError; - varError += currentError * currentError; - } - meanError /= orderedValues.Count; - varError /= orderedValues.Count; - double stdv = Math.Sqrt(varError); - - // calcualte 3-sigma significance - double estimatedError = waitValue - (alpha * KernelFunc(1)); - - // this is the consideration of the temporal information. the outstanding no. 1 must has higher error then the second - double secondError = Math.Abs(orderedValues[0] - (alpha * KernelFunc(2))); - - // My proposed solution to detect high-quality outstanding #1 (i.e., it is also not an outstanding top-k insight, for example, not a top-two, or top-three, etc.) is to - // add such additional checking: v1-α≥k(v2-α/2^β ), Basically, I only check the error of the 1st and 2nd values. - // Next, I will first show what is a proper k, and then, I will illustrate why checking these two values are enough (i.e., we don’t need to check the left values) - // In the typical outstanding top-two case, when v2≫ vi,where i>2, the estimated α* is - // α*=argmax(α) ⁡∑[i from 2 to ∞] (vi-α/i^β)^2 -> α*=(∑vi/i^β)/(∑i^2β) ≈ (v2/2^β)/(∑1/i^2β ) ≈ 0.294v2, when β=0.7 - // According to the empirical study, under such circumstance, the first value should be at least TWO times larger than the second, user would think this is a valid outstanding no. 1, - // therefore, we have 2v2-α≥k(v2-α/2^β ), Put α* and β into, we have k=2.083 as the minimum value. - // So when we set k≥2.083, when outstanding no.1 insight is output, the first value will be 2 times higher than the second, - // which is aligned with user’s feeling. Above we only consider the first and second values. Here, let’s consider - // v2≈v3≫ vi,i>3: because we don’t want to wrongly make an top-three insight as a top-1 insight. according to the empirical study, - // the first value should be at least 2 times larger than the second, user would think this is a valid outstanding no. 1. - // According to the calculation, we found that the k=2.083 still valid for this case ((as v3 grows, the error of v1 decreased faster than the error of v2)). - // It’s not difficult to verify that (by a simple qualitative analysis), in order to avoid wrongly make outstanding top-k as outstanding no.1, this threshold is valid. - - double k = 2.083; - - // based on the intuition, v1 should be at least 5 times larger than v2 when v2 is one magnitude larger than v3. in order to achieve this, k should be set to 5.64 accordingly. - if (orderedValues[0] / orderedValues[1] > 10) - { - k = 5.64; - } - if (estimatedError > k * secondError && estimatedError >= meanError + (1 * stdv)) - { - isSignificant = true; - return MathUtility.GaussianSignificance(estimatedError, meanError, stdv); - } - else - { - isSignificant = false; - return 0; - } - } - else - { - isSignificant = false; - return 0; - } - } - - /// - /// the kernel function used for evaluating significant rank #1 - /// - private static double KernelFunc(double x) - { - return 1.0 / Math.Pow(x, 0.7); - } } } From cce849dc95fb2457a2d9b8bf0c20cbe21f17d7e3 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 3 Jun 2020 20:50:20 +0800 Subject: [PATCH 09/49] update --- Microsoft.ML.sln | 402 ++++++++++---------- src/Microsoft.ML.TimeSeries/FftTransform.cs | 148 ------- 2 files changed, 207 insertions(+), 343 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/FftTransform.cs diff --git a/Microsoft.ML.sln b/Microsoft.ML.sln index 8c6c84b14c..9884eda1a6 100644 --- a/Microsoft.ML.sln +++ b/Microsoft.ML.sln @@ -1,9 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.29209.152 +VisualStudioVersion = 16.0.30114.105 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core", "src\Microsoft.ML.Core\Microsoft.ML.Core.csproj", "{A6CA6CC6-5D7C-4D7F-A0F5-35E14B383B0A}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{09EADF06-BE25-4228-AB53-95AE3E15B530}" ProjectSection(SolutionItems) = preProject src\Source.ruleset = src\Source.ruleset @@ -11,38 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{09EADF06-BE2 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{AED9C836-31E3-4F3F-8ABC-929555D3F3C4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core.Tests", "test\Microsoft.ML.Core.Tests\Microsoft.ML.Core.Tests.csproj", "{EC743D1D-7691-43B7-B9B0-5F2F7018A8F6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath", "src\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.csproj", "{46F2F967-C23F-4076-858D-33F7DA9BD2DA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Data", "src\Microsoft.ML.Data\Microsoft.ML.Data.csproj", "{AD92D96B-0E96-4F22-8DCE-892E13B1F282}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxConverter", "src\Microsoft.ML.OnnxConverter\Microsoft.ML.OnnxConverter.csproj", "{65D0603E-B96C-4DFC-BDD1-705891B88C18}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StandardTrainers", "src\Microsoft.ML.StandardTrainers\Microsoft.ML.StandardTrainers.csproj", "{707BB22C-7E5F-497A-8C2F-74578F675705}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Transforms", "src\Microsoft.ML.Transforms\Microsoft.ML.Transforms.csproj", "{2911A286-ECA4-4730-97A9-DA1FEE2DED97}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.KMeansClustering", "src\Microsoft.ML.KMeansClustering\Microsoft.ML.KMeansClustering.csproj", "{F1CAE3AB-4F86-4BC0-BBA8-C4A58E7E8A4A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.PCA", "src\Microsoft.ML.PCA\Microsoft.ML.PCA.csproj", "{58E06735-1129-4DD5-86E0-6BBFF049AAD9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Tests", "test\Microsoft.ML.Tests\Microsoft.ML.Tests.csproj", "{64BC22D3-1E76-41EF-94D8-C79E471FF2DD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFramework", "test\Microsoft.ML.TestFramework\Microsoft.ML.TestFramework.csproj", "{B5989C06-4FFA-46C1-9D85-9366B34AB0A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Predictor.Tests", "test\Microsoft.ML.Predictor.Tests\Microsoft.ML.Predictor.Tests.csproj", "{6B047E09-39C9-4583-96F3-685D84CA4117}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Functional.Tests", "test\Microsoft.ML.Functional.Tests\Microsoft.ML.Functional.Tests.csproj", "{CFED9F0C-FF81-4C96-8D5E-0436264CA7B5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ResultProcessor", "src\Microsoft.ML.ResultProcessor\Microsoft.ML.ResultProcessor.csproj", "{3769FCC3-9AFF-4C37-97E9-6854324681DF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.FastTree", "src\Microsoft.ML.FastTree\Microsoft.ML.FastTree.csproj", "{B7B593C5-FB8C-4ADA-A638-5B53B47D087E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Parquet", "src\Microsoft.ML.Parquet\Microsoft.ML.Parquet.csproj", "{16BB1454-2108-40E5-B3A6-594654005303}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper", "src\Microsoft.ML.Sweeper\Microsoft.ML.Sweeper.csproj", "{55C8122D-79EA-48AB-85D0-EB551FC1C427}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pkg", "pkg", "{D3D38B03-B557-484D-8348-8BADEE4DF592}" ProjectSection(SolutionItems) = preProject pkg\Directory.Build.props = pkg\Directory.Build.props @@ -59,12 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Parquet", "Mic pkg\Microsoft.ML.Parquet\Microsoft.ML.Parquet.nupkgproj = pkg\Microsoft.ML.Parquet\Microsoft.ML.Parquet.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks", "test\Microsoft.ML.Benchmarks\Microsoft.ML.Benchmarks.csproj", "{7A9DB75F-2CA5-4184-9EF5-1F17EB39483F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Maml", "src\Microsoft.ML.Maml\Microsoft.ML.Maml.csproj", "{64F40A0D-D4C2-4AA7-8470-E9CC437827E4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Console", "src\Microsoft.ML.Console\Microsoft.ML.Console.csproj", "{362A98CF-FBF7-4EBB-A11B-990BBF845B15}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{487213C9-E8A9-4F94-85D7-28A05DBBFE3A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstandard2.0", "{9252A8EB-ABFB-440C-AB4D-1D562753CE0F}" @@ -72,12 +32,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstanda pkg\Microsoft.ML\build\netstandard2.0\Microsoft.ML.targets = pkg\Microsoft.ML\build\netstandard2.0\Microsoft.ML.targets EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper.Tests", "test\Microsoft.ML.Sweeper.Tests\Microsoft.ML.Sweeper.Tests.csproj", "{3DEB504D-7A07-48CE-91A2-8047461CB3D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.LightGbm", "src\Microsoft.ML.LightGbm\Microsoft.ML.LightGbm.csproj", "{001F3B4E-FBE4-4001-AFD2-A6A989CD1C25}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Ensemble", "src\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.csproj", "{DCF46B79-1FDB-4DBA-A263-D3D64E3AAA27}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.CpuMath", "Microsoft.ML.CpuMath", "{BF66A305-DF10-47E4-8D81-42049B149D2B}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.nupkgproj = pkg\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.nupkgproj @@ -86,52 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.CpuMath", "Mic EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools-local", "tools-local", "{7F13E156-3EBA-4021-84A5-CD56BA72F99E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.InternalCodeAnalyzer", "tools-local\Microsoft.ML.InternalCodeAnalyzer\Microsoft.ML.InternalCodeAnalyzer.csproj", "{B4E55B2D-2A92-46E7-B72F-E76D6FD83440}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeAnalyzer.Tests", "test\Microsoft.ML.CodeAnalyzer.Tests\Microsoft.ML.CodeAnalyzer.Tests.csproj", "{3E4ABF07-7970-4BE6-B45B-A13D3C397545}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.PerformanceTests", "test\Microsoft.ML.CpuMath.PerformanceTests\Microsoft.ML.CpuMath.PerformanceTests.csproj", "{7333EDEF-4144-405C-A5EC-6F42201857D8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.UnitTests", "test\Microsoft.ML.CpuMath.UnitTests\Microsoft.ML.CpuMath.UnitTests.csproj", "{E97D8B5A-3035-4D41-9B0D-77FF8FB8D132}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Microsoft.ML.FSharp.Tests", "test\Microsoft.ML.FSharp.Tests\Microsoft.ML.FSharp.Tests.fsproj", "{802233D6-8CC0-46AD-9F23-FEE1E9AED9B3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ImageAnalytics", "src\Microsoft.ML.ImageAnalytics\Microsoft.ML.ImageAnalytics.csproj", "{00E38F77-1E61-4CDF-8F97-1417D4E85053}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Mkl.Components", "src\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.csproj", "{A7222F41-1CF0-47D9-B80C-B4D77B027A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TensorFlow", "src\Microsoft.ML.TensorFlow\Microsoft.ML.TensorFlow.csproj", "{570A0B8A-5463-44D2-8521-54C0CA4CACA9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries", "src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj", "{5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformer", "src\Microsoft.ML.OnnxTransformer\Microsoft.ML.OnnxTransformer.csproj", "{8C05642D-C3AA-4972-B02C-93681161A6BC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnAnalyzer", "src\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer.csproj", "{73DAAC82-D308-48CC-8FFE-3B037F8BBCCA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformerTest", "test\Microsoft.ML.OnnxTransformerTest\Microsoft.ML.OnnxTransformerTest.csproj", "{49D03292-8AFE-4B82-823C-D047BF8420F7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks.Tests", "test\Microsoft.ML.Benchmarks.Tests\Microsoft.ML.Benchmarks.Tests.csproj", "{B6C83F04-A04B-4F00-9E68-1EC411F9317C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries.Tests", "test\Microsoft.ML.TimeSeries.Tests\Microsoft.ML.TimeSeries.Tests.csproj", "{4B101D58-E7E4-4877-A536-A9B41E2E82A3}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DA452A53-2E94-4433-B08C-041EDEC729E6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples", "docs\samples\Microsoft.ML.Samples\Microsoft.ML.Samples.csproj", "{ECB71297-9DF1-48CE-B93A-CD969221F9B6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.SamplesUtils", "src\Microsoft.ML.SamplesUtils\Microsoft.ML.SamplesUtils.csproj", "{11A5210E-2EA7-42F1-80DB-827762E9C781}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Recommender", "src\Microsoft.ML.Recommender\Microsoft.ML.Recommender.csproj", "{C8E1772B-DFD9-4A4D-830D-6AAB1C668BB3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet18", "src\Microsoft.ML.DnnImageFeaturizer.ResNet18\Microsoft.ML.DnnImageFeaturizer.ResNet18.csproj", "{9222FC9D-599A-49A5-B685-08CC9A5C81D7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.AlexNet", "src\Microsoft.ML.DnnImageFeaturizer.AlexNet\Microsoft.ML.DnnImageFeaturizer.AlexNet.csproj", "{6C29AA9B-054B-4762-BEA5-D305B932AA80}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet50", "src\Microsoft.ML.DnnImageFeaturizer.ResNet50\Microsoft.ML.DnnImageFeaturizer.ResNet50.csproj", "{4805129D-78C8-46D4-9519-0AD9B0574D6D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet101", "src\Microsoft.ML.DnnImageFeaturizer.ResNet101\Microsoft.ML.DnnImageFeaturizer.ResNet101.csproj", "{DB7CEB5E-8BE6-48A7-87BE-B91D9AE96F71}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.EntryPoints", "src\Microsoft.ML.EntryPoints\Microsoft.ML.EntryPoints.csproj", "{7504D46F-E4B3-43CB-9B1C-82F3131F1C99}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Mkl.Components", "Microsoft.ML.Mkl.Components", "{63006A14-B924-48C5-83C9-CFE9DA22B01F}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.nupkgproj = pkg\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.nupkgproj @@ -221,82 +131,48 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{A84717 pkg\common\DnnImageFeaturizer.props = pkg\common\DnnImageFeaturizer.props EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DataView", "src\Microsoft.ML.DataView\Microsoft.ML.DataView.csproj", "{85D0CAFD-2FE8-496A-88C7-585D35B94243}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.DataView", "Microsoft.ML.DataView", "{31D38B21-102B-41C0-9E0A-2FE0BF68D123}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.nupkgproj = pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.nupkgproj pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.symbols.nupkgproj = pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteExecutorConsoleApp", "test\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj", "{5E920CAC-5A28-42FB-936E-49C472130953}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Ensemble", "Microsoft.ML.Ensemble", "{AD7058C9-5608-49A8-BE23-58C33A74EE91}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.nupkgproj = pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.nupkgproj pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.symbols.nupkgproj = pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Experimental", "src\Microsoft.ML.Experimental\Microsoft.ML.Experimental.csproj", "{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.FastTree", "Microsoft.ML.FastTree", "{B1B3F284-FA3D-4D76-A712-FF04495D244B}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.nupkgproj = pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.nupkgproj pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.symbols.nupkgproj = pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML", "src\Microsoft.Extensions.ML\Microsoft.Extensions.ML.csproj", "{D6741C37-B5E6-4050-BCBA-9715809EA15B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML.Tests", "test\Microsoft.Extensions.ML.Tests\Microsoft.Extensions.ML.Tests.csproj", "{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Extensions.ML", "Microsoft.Extensions.ML", "{AE4F7569-26F3-4160-8A8B-7A57D0DA3350}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.nupkgproj = pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.nupkgproj pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.symbols.nupkgproj = pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StableApi", "tools-local\Microsoft.ML.StableApi\Microsoft.ML.StableApi.csproj", "{F308DC6B-7E59-40D7-A581-834E8CD99CFE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Tests", "test\Microsoft.ML.AutoML.Tests\Microsoft.ML.AutoML.Tests.csproj", "{C2652287-CD6D-40FB-B042-95FB56D09DB8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML", "src\Microsoft.ML.AutoML\Microsoft.ML.AutoML.csproj", "{E48285BF-F49A-4EA3-AED0-1BDDBF77EB80}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.AutoML", "Microsoft.ML.AutoML", "{F5D11F71-2D61-4AE9-99D7-0F0B54649B15}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.nupkgproj = pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.nupkgproj pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.symbols.nupkgproj = pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Samples", "docs\samples\Microsoft.ML.AutoML.Samples\Microsoft.ML.AutoML.Samples.csproj", "{A6924919-9E37-4023-8B7F-E85C8E3CC9B3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples.GPU", "docs\samples\Microsoft.ML.Samples.GPU\Microsoft.ML.Samples.GPU.csproj", "{3C8F910B-7F23-4D25-B521-6D5AC9570ADD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Featurizers", "src\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.csproj", "{E2DD0721-5B0F-4606-8182-4C7EFB834518}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Featurizers", "Microsoft.ML.Featurizers", "{1BA5C784-52E8-4A87-8525-26B2452F2882}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.nupkgproj = pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.nupkgproj pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.symbols.nupkgproj = pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator", "src\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.csproj", "{56CB0850-7341-4D71-9AE4-9EFC472D93DD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator.Tests", "test\Microsoft.ML.CodeGenerator.Tests\Microsoft.ML.CodeGenerator.Tests.csproj", "{46CC5637-3DDF-4100-93FC-44BB87B2DB81}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.CodeGenerator", "Microsoft.ML.CodeGenerator", "{3817A875-278C-4140-BF66-3C4A8CA55F0D}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.nupkgproj = pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.nupkgproj pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.symbols.nupkgproj = pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.symbols.nupkgproj EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Vision", "src\Microsoft.ML.Vision\Microsoft.ML.Vision.csproj", "{419F93D5-4135-4DA0-A76E-EFC23E04093D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFrameworkCommon", "test\Microsoft.ML.TestFrameworkCommon\Microsoft.ML.TestFrameworkCommon.csproj", "{A22FAD27-77E8-4460-8B92-EC7090B7173A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ML.NightlyBuild.Tests", "test\Microsoft.ML.NightlyBuild.Tests\Microsoft.ML.NightlyBuild.Tests.csproj", "{A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ML.NugetPackageVersionUpdater", "test\Microsoft.ML.NugetPackageVersionUpdater\Microsoft.ML.NugetPackageVersionUpdater.csproj", "{C8DB58DC-6434-4431-A81F-263D86E2A5F3}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{C91F81E3-B900-4968-A6DF-F53B515E97E1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstandard2.0", "{027DBA48-85B6-46F1-9487-0B49B5057FC0}" @@ -304,6 +180,130 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstanda pkg\Microsoft.ML.CpuMath\build\netstandard2.0\Microsoft.ML.CpuMath.props = pkg\Microsoft.ML.CpuMath\build\netstandard2.0\Microsoft.ML.CpuMath.props EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core", "src\Microsoft.ML.Core\Microsoft.ML.Core.csproj", "{A6CA6CC6-5D7C-4D7F-A0F5-35E14B383B0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core.Tests", "test\Microsoft.ML.Core.Tests\Microsoft.ML.Core.Tests.csproj", "{EC743D1D-7691-43B7-B9B0-5F2F7018A8F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath", "src\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.csproj", "{46F2F967-C23F-4076-858D-33F7DA9BD2DA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Data", "src\Microsoft.ML.Data\Microsoft.ML.Data.csproj", "{AD92D96B-0E96-4F22-8DCE-892E13B1F282}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxConverter", "src\Microsoft.ML.OnnxConverter\Microsoft.ML.OnnxConverter.csproj", "{65D0603E-B96C-4DFC-BDD1-705891B88C18}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StandardTrainers", "src\Microsoft.ML.StandardTrainers\Microsoft.ML.StandardTrainers.csproj", "{707BB22C-7E5F-497A-8C2F-74578F675705}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Transforms", "src\Microsoft.ML.Transforms\Microsoft.ML.Transforms.csproj", "{2911A286-ECA4-4730-97A9-DA1FEE2DED97}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.KMeansClustering", "src\Microsoft.ML.KMeansClustering\Microsoft.ML.KMeansClustering.csproj", "{F1CAE3AB-4F86-4BC0-BBA8-C4A58E7E8A4A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.PCA", "src\Microsoft.ML.PCA\Microsoft.ML.PCA.csproj", "{58E06735-1129-4DD5-86E0-6BBFF049AAD9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Tests", "test\Microsoft.ML.Tests\Microsoft.ML.Tests.csproj", "{64BC22D3-1E76-41EF-94D8-C79E471FF2DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFramework", "test\Microsoft.ML.TestFramework\Microsoft.ML.TestFramework.csproj", "{B5989C06-4FFA-46C1-9D85-9366B34AB0A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Predictor.Tests", "test\Microsoft.ML.Predictor.Tests\Microsoft.ML.Predictor.Tests.csproj", "{6B047E09-39C9-4583-96F3-685D84CA4117}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Functional.Tests", "test\Microsoft.ML.Functional.Tests\Microsoft.ML.Functional.Tests.csproj", "{CFED9F0C-FF81-4C96-8D5E-0436264CA7B5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ResultProcessor", "src\Microsoft.ML.ResultProcessor\Microsoft.ML.ResultProcessor.csproj", "{3769FCC3-9AFF-4C37-97E9-6854324681DF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.FastTree", "src\Microsoft.ML.FastTree\Microsoft.ML.FastTree.csproj", "{B7B593C5-FB8C-4ADA-A638-5B53B47D087E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Parquet", "src\Microsoft.ML.Parquet\Microsoft.ML.Parquet.csproj", "{16BB1454-2108-40E5-B3A6-594654005303}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper", "src\Microsoft.ML.Sweeper\Microsoft.ML.Sweeper.csproj", "{55C8122D-79EA-48AB-85D0-EB551FC1C427}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks", "test\Microsoft.ML.Benchmarks\Microsoft.ML.Benchmarks.csproj", "{7A9DB75F-2CA5-4184-9EF5-1F17EB39483F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Maml", "src\Microsoft.ML.Maml\Microsoft.ML.Maml.csproj", "{64F40A0D-D4C2-4AA7-8470-E9CC437827E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Console", "src\Microsoft.ML.Console\Microsoft.ML.Console.csproj", "{362A98CF-FBF7-4EBB-A11B-990BBF845B15}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper.Tests", "test\Microsoft.ML.Sweeper.Tests\Microsoft.ML.Sweeper.Tests.csproj", "{3DEB504D-7A07-48CE-91A2-8047461CB3D4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.LightGbm", "src\Microsoft.ML.LightGbm\Microsoft.ML.LightGbm.csproj", "{001F3B4E-FBE4-4001-AFD2-A6A989CD1C25}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Ensemble", "src\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.csproj", "{DCF46B79-1FDB-4DBA-A263-D3D64E3AAA27}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.InternalCodeAnalyzer", "tools-local\Microsoft.ML.InternalCodeAnalyzer\Microsoft.ML.InternalCodeAnalyzer.csproj", "{B4E55B2D-2A92-46E7-B72F-E76D6FD83440}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeAnalyzer.Tests", "test\Microsoft.ML.CodeAnalyzer.Tests\Microsoft.ML.CodeAnalyzer.Tests.csproj", "{3E4ABF07-7970-4BE6-B45B-A13D3C397545}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.PerformanceTests", "test\Microsoft.ML.CpuMath.PerformanceTests\Microsoft.ML.CpuMath.PerformanceTests.csproj", "{7333EDEF-4144-405C-A5EC-6F42201857D8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.UnitTests", "test\Microsoft.ML.CpuMath.UnitTests\Microsoft.ML.CpuMath.UnitTests.csproj", "{E97D8B5A-3035-4D41-9B0D-77FF8FB8D132}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Microsoft.ML.FSharp.Tests", "test\Microsoft.ML.FSharp.Tests\Microsoft.ML.FSharp.Tests.fsproj", "{802233D6-8CC0-46AD-9F23-FEE1E9AED9B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ImageAnalytics", "src\Microsoft.ML.ImageAnalytics\Microsoft.ML.ImageAnalytics.csproj", "{00E38F77-1E61-4CDF-8F97-1417D4E85053}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Mkl.Components", "src\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.csproj", "{A7222F41-1CF0-47D9-B80C-B4D77B027A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TensorFlow", "src\Microsoft.ML.TensorFlow\Microsoft.ML.TensorFlow.csproj", "{570A0B8A-5463-44D2-8521-54C0CA4CACA9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries", "src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj", "{5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformer", "src\Microsoft.ML.OnnxTransformer\Microsoft.ML.OnnxTransformer.csproj", "{8C05642D-C3AA-4972-B02C-93681161A6BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnAnalyzer", "src\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer.csproj", "{73DAAC82-D308-48CC-8FFE-3B037F8BBCCA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformerTest", "test\Microsoft.ML.OnnxTransformerTest\Microsoft.ML.OnnxTransformerTest.csproj", "{49D03292-8AFE-4B82-823C-D047BF8420F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks.Tests", "test\Microsoft.ML.Benchmarks.Tests\Microsoft.ML.Benchmarks.Tests.csproj", "{B6C83F04-A04B-4F00-9E68-1EC411F9317C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries.Tests", "test\Microsoft.ML.TimeSeries.Tests\Microsoft.ML.TimeSeries.Tests.csproj", "{4B101D58-E7E4-4877-A536-A9B41E2E82A3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples", "docs\samples\Microsoft.ML.Samples\Microsoft.ML.Samples.csproj", "{ECB71297-9DF1-48CE-B93A-CD969221F9B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.SamplesUtils", "src\Microsoft.ML.SamplesUtils\Microsoft.ML.SamplesUtils.csproj", "{11A5210E-2EA7-42F1-80DB-827762E9C781}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Recommender", "src\Microsoft.ML.Recommender\Microsoft.ML.Recommender.csproj", "{C8E1772B-DFD9-4A4D-830D-6AAB1C668BB3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet18", "src\Microsoft.ML.DnnImageFeaturizer.ResNet18\Microsoft.ML.DnnImageFeaturizer.ResNet18.csproj", "{9222FC9D-599A-49A5-B685-08CC9A5C81D7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.AlexNet", "src\Microsoft.ML.DnnImageFeaturizer.AlexNet\Microsoft.ML.DnnImageFeaturizer.AlexNet.csproj", "{6C29AA9B-054B-4762-BEA5-D305B932AA80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet50", "src\Microsoft.ML.DnnImageFeaturizer.ResNet50\Microsoft.ML.DnnImageFeaturizer.ResNet50.csproj", "{4805129D-78C8-46D4-9519-0AD9B0574D6D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet101", "src\Microsoft.ML.DnnImageFeaturizer.ResNet101\Microsoft.ML.DnnImageFeaturizer.ResNet101.csproj", "{DB7CEB5E-8BE6-48A7-87BE-B91D9AE96F71}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.EntryPoints", "src\Microsoft.ML.EntryPoints\Microsoft.ML.EntryPoints.csproj", "{7504D46F-E4B3-43CB-9B1C-82F3131F1C99}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DataView", "src\Microsoft.ML.DataView\Microsoft.ML.DataView.csproj", "{85D0CAFD-2FE8-496A-88C7-585D35B94243}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteExecutorConsoleApp", "test\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj", "{5E920CAC-5A28-42FB-936E-49C472130953}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Experimental", "src\Microsoft.ML.Experimental\Microsoft.ML.Experimental.csproj", "{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML", "src\Microsoft.Extensions.ML\Microsoft.Extensions.ML.csproj", "{D6741C37-B5E6-4050-BCBA-9715809EA15B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML.Tests", "test\Microsoft.Extensions.ML.Tests\Microsoft.Extensions.ML.Tests.csproj", "{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StableApi", "tools-local\Microsoft.ML.StableApi\Microsoft.ML.StableApi.csproj", "{F308DC6B-7E59-40D7-A581-834E8CD99CFE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Tests", "test\Microsoft.ML.AutoML.Tests\Microsoft.ML.AutoML.Tests.csproj", "{C2652287-CD6D-40FB-B042-95FB56D09DB8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML", "src\Microsoft.ML.AutoML\Microsoft.ML.AutoML.csproj", "{E48285BF-F49A-4EA3-AED0-1BDDBF77EB80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Samples", "docs\samples\Microsoft.ML.AutoML.Samples\Microsoft.ML.AutoML.Samples.csproj", "{A6924919-9E37-4023-8B7F-E85C8E3CC9B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples.GPU", "docs\samples\Microsoft.ML.Samples.GPU\Microsoft.ML.Samples.GPU.csproj", "{3C8F910B-7F23-4D25-B521-6D5AC9570ADD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Featurizers", "src\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.csproj", "{E2DD0721-5B0F-4606-8182-4C7EFB834518}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator", "src\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.csproj", "{56CB0850-7341-4D71-9AE4-9EFC472D93DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator.Tests", "test\Microsoft.ML.CodeGenerator.Tests\Microsoft.ML.CodeGenerator.Tests.csproj", "{46CC5637-3DDF-4100-93FC-44BB87B2DB81}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Vision", "src\Microsoft.ML.Vision\Microsoft.ML.Vision.csproj", "{419F93D5-4135-4DA0-A76E-EFC23E04093D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFrameworkCommon", "test\Microsoft.ML.TestFrameworkCommon\Microsoft.ML.TestFrameworkCommon.csproj", "{A22FAD27-77E8-4460-8B92-EC7090B7173A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.NightlyBuild.Tests", "test\Microsoft.ML.NightlyBuild.Tests\Microsoft.ML.NightlyBuild.Tests.csproj", "{A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.NugetPackageVersionUpdater", "test\Microsoft.ML.NugetPackageVersionUpdater\Microsoft.ML.NugetPackageVersionUpdater.csproj", "{C8DB58DC-6434-4431-A81F-263D86E2A5F3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1639,6 +1639,30 @@ Global {3C8F910B-7F23-4D25-B521-6D5AC9570ADD}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU {3C8F910B-7F23-4D25-B521-6D5AC9570ADD}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU {3C8F910B-7F23-4D25-B521-6D5AC9570ADD}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.ActiveCfg = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.Build.0 = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.Build.0 = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.ActiveCfg = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.Build.0 = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU {56CB0850-7341-4D71-9AE4-9EFC472D93DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56CB0850-7341-4D71-9AE4-9EFC472D93DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {56CB0850-7341-4D71-9AE4-9EFC472D93DD}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1736,70 +1760,87 @@ Global {A22FAD27-77E8-4460-8B92-EC7090B7173A}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU {A22FAD27-77E8-4460-8B92-EC7090B7173A}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|x64.Build.0 = Debug|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|Any CPU.Build.0 = Release|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|x64.ActiveCfg = Release|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|x64.Build.0 = Release|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU + {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|x64.ActiveCfg = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|x64.Build.0 = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.Build.0 = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.ActiveCfg = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.Build.0 = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.ActiveCfg = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.Build.0 = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|Any CPU.Build.0 = Release|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|x64.ActiveCfg = Release|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|x64.Build.0 = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.ActiveCfg = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.Build.0 = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.Build.0 = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.ActiveCfg = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.Build.0 = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.Build.0 = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.ActiveCfg = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.Build.0 = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.ActiveCfg = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.Build.0 = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.ActiveCfg = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution + {DEC8F776-49F7-4D87-836C-FE4DC057D08C} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {6C95FC87-F5F2-4EEF-BB97-567F2F5DD141} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} = {DEC8F776-49F7-4D87-836C-FE4DC057D08C} + {9252A8EB-ABFB-440C-AB4D-1D562753CE0F} = {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} + {BF66A305-DF10-47E4-8D81-42049B149D2B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {63006A14-B924-48C5-83C9-CFE9DA22B01F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {1229F799-37F0-4282-B9F0-74BFA97CC362} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {DE95FE65-9FF7-4233-93DF-7A8F2805624A} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {4CF8095E-B4A3-4326-A550-43098E447288} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {19AC192B-75FE-45D5-B219-898E401D5904} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {93FF16AA-635E-421D-96C1-008818C143A2} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {320AF46A-4809-486E-8F9E-A00C8AE47751} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {11894B4A-78B4-4523-A6DD-4495722E244F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {B836F712-7FB6-4B75-A3EB-FB05F8E0D15E} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {B00098E4-771E-41DF-A3AA-A606AAB334B7} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {BD93C0F3-3CED-4BE8-9389-4234250FBFB1} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {8EDFB7E5-7E7E-411D-99C5-7A4895D0F9CB} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {9E689AD4-F908-493C-B882-B1B33E8F7696} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {8D8CC016-0020-40EC-BD8E-73F1CE0F9662} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {A84717CB-F11A-41C5-A74D-C0F1D47B7431} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {31D38B21-102B-41C0-9E0A-2FE0BF68D123} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {AD7058C9-5608-49A8-BE23-58C33A74EE91} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {B1B3F284-FA3D-4D76-A712-FF04495D244B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {AE4F7569-26F3-4160-8A8B-7A57D0DA3350} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {F5D11F71-2D61-4AE9-99D7-0F0B54649B15} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {1BA5C784-52E8-4A87-8525-26B2452F2882} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {3817A875-278C-4140-BF66-3C4A8CA55F0D} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {C91F81E3-B900-4968-A6DF-F53B515E97E1} = {BF66A305-DF10-47E4-8D81-42049B149D2B} + {027DBA48-85B6-46F1-9487-0B49B5057FC0} = {C91F81E3-B900-4968-A6DF-F53B515E97E1} {A6CA6CC6-5D7C-4D7F-A0F5-35E14B383B0A} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {EC743D1D-7691-43B7-B9B0-5F2F7018A8F6} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {46F2F967-C23F-4076-858D-33F7DA9BD2DA} = {09EADF06-BE25-4228-AB53-95AE3E15B530} @@ -1817,17 +1858,12 @@ Global {B7B593C5-FB8C-4ADA-A638-5B53B47D087E} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {16BB1454-2108-40E5-B3A6-594654005303} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {55C8122D-79EA-48AB-85D0-EB551FC1C427} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {DEC8F776-49F7-4D87-836C-FE4DC057D08C} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {6C95FC87-F5F2-4EEF-BB97-567F2F5DD141} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {7A9DB75F-2CA5-4184-9EF5-1F17EB39483F} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {64F40A0D-D4C2-4AA7-8470-E9CC437827E4} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {362A98CF-FBF7-4EBB-A11B-990BBF845B15} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} = {DEC8F776-49F7-4D87-836C-FE4DC057D08C} - {9252A8EB-ABFB-440C-AB4D-1D562753CE0F} = {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} {3DEB504D-7A07-48CE-91A2-8047461CB3D4} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {001F3B4E-FBE4-4001-AFD2-A6A989CD1C25} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {DCF46B79-1FDB-4DBA-A263-D3D64E3AAA27} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {BF66A305-DF10-47E4-8D81-42049B149D2B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {B4E55B2D-2A92-46E7-B72F-E76D6FD83440} = {7F13E156-3EBA-4021-84A5-CD56BA72F99E} {3E4ABF07-7970-4BE6-B45B-A13D3C397545} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {7333EDEF-4144-405C-A5EC-6F42201857D8} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} @@ -1850,47 +1886,23 @@ Global {4805129D-78C8-46D4-9519-0AD9B0574D6D} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {DB7CEB5E-8BE6-48A7-87BE-B91D9AE96F71} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {7504D46F-E4B3-43CB-9B1C-82F3131F1C99} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {63006A14-B924-48C5-83C9-CFE9DA22B01F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {1229F799-37F0-4282-B9F0-74BFA97CC362} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {DE95FE65-9FF7-4233-93DF-7A8F2805624A} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {4CF8095E-B4A3-4326-A550-43098E447288} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {19AC192B-75FE-45D5-B219-898E401D5904} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {93FF16AA-635E-421D-96C1-008818C143A2} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {320AF46A-4809-486E-8F9E-A00C8AE47751} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {11894B4A-78B4-4523-A6DD-4495722E244F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {B836F712-7FB6-4B75-A3EB-FB05F8E0D15E} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {B00098E4-771E-41DF-A3AA-A606AAB334B7} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {BD93C0F3-3CED-4BE8-9389-4234250FBFB1} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {8EDFB7E5-7E7E-411D-99C5-7A4895D0F9CB} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {9E689AD4-F908-493C-B882-B1B33E8F7696} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {8D8CC016-0020-40EC-BD8E-73F1CE0F9662} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {A84717CB-F11A-41C5-A74D-C0F1D47B7431} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {85D0CAFD-2FE8-496A-88C7-585D35B94243} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {31D38B21-102B-41C0-9E0A-2FE0BF68D123} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {5E920CAC-5A28-42FB-936E-49C472130953} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} - {AD7058C9-5608-49A8-BE23-58C33A74EE91} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {B1B3F284-FA3D-4D76-A712-FF04495D244B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {D6741C37-B5E6-4050-BCBA-9715809EA15B} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {21CAD3A1-5E1F-42C1-BB73-46B6E67F4206} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} - {AE4F7569-26F3-4160-8A8B-7A57D0DA3350} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {F308DC6B-7E59-40D7-A581-834E8CD99CFE} = {7F13E156-3EBA-4021-84A5-CD56BA72F99E} {C2652287-CD6D-40FB-B042-95FB56D09DB8} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {E48285BF-F49A-4EA3-AED0-1BDDBF77EB80} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {F5D11F71-2D61-4AE9-99D7-0F0B54649B15} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {A6924919-9E37-4023-8B7F-E85C8E3CC9B3} = {DA452A53-2E94-4433-B08C-041EDEC729E6} {3C8F910B-7F23-4D25-B521-6D5AC9570ADD} = {DA452A53-2E94-4433-B08C-041EDEC729E6} + {E2DD0721-5B0F-4606-8182-4C7EFB834518} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {56CB0850-7341-4D71-9AE4-9EFC472D93DD} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {46CC5637-3DDF-4100-93FC-44BB87B2DB81} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} - {3817A875-278C-4140-BF66-3C4A8CA55F0D} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {419F93D5-4135-4DA0-A76E-EFC23E04093D} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {A22FAD27-77E8-4460-8B92-EC7090B7173A} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {C8DB58DC-6434-4431-A81F-263D86E2A5F3} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} - {C91F81E3-B900-4968-A6DF-F53B515E97E1} = {BF66A305-DF10-47E4-8D81-42049B149D2B} - {027DBA48-85B6-46F1-9487-0B49B5057FC0} = {C91F81E3-B900-4968-A6DF-F53B515E97E1} - {E2DD0721-5B0F-4606-8182-4C7EFB834518} = {09EADF06-BE25-4228-AB53-95AE3E15B530} - {1BA5C784-52E8-4A87-8525-26B2452F2882} = {D3D38B03-B557-484D-8348-8BADEE4DF592} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {41165AF1-35BB-4832-A189-73060F82B01D} diff --git a/src/Microsoft.ML.TimeSeries/FftTransform.cs b/src/Microsoft.ML.TimeSeries/FftTransform.cs deleted file mode 100644 index 187e059581..0000000000 --- a/src/Microsoft.ML.TimeSeries/FftTransform.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - public class FftTransform - { - /// - /// calculate the fast fourier transform. - /// - public static Complex[] Fft(Complex[] array) - { - int n = array.Length; - int newN = Get2Power(n); - if (newN > n) - { - Complex[] newArray = new Complex[newN]; - for (int i = 0; i < newN; i++) - { - if (i < n) - newArray[i] = array[i]; - else - newArray[i] = Complex.Zero; - } - return RecursiveFft(newArray); - } - else - { - return RecursiveFft(array); - } - } - - /// - /// calculate the fast fourier transform. - /// - public static Complex[] RevertFft(Complex[] array) - { - int n = array.Length; - int newN = Get2Power(n); - if (newN > n) - { - Complex[] newArray = new Complex[newN]; - for (int i = 0; i < newN; i++) - { - if (i < n) - newArray[i] = array[i]; - else - newArray[i] = Complex.Zero; - } - Complex[] result = RevertRecursiveFft(newArray); - for (int i = 0; i < result.Length; i++) - result[i] /= newN; - return result; - } - else - { - Complex[] result = RevertRecursiveFft(array); - for (int i = 0; i < result.Length; i++) - result[i] /= n; - return result; - } - } - - /// - /// this method is the recursive FftTransform, which use the divide and conquer to achieve nlogn complexity. - /// the key trick is the selection of n unit roots in the complex space. the length of the input array MUST be 2^k - /// - /// the input polynomial coefficients (or the dual if reverse FftTransform is called) - /// return the dual coefficients - private static Complex[] RecursiveFft(Complex[] array) - { - int n = array.Length; - if (n == 1) - return array; - Complex wn = new Complex(Math.Cos(2 * Math.PI / n), Math.Sin(2 * Math.PI / n)); - Complex w = Complex.One; - Complex[] array0 = new Complex[n / 2]; - Complex[] array1 = new Complex[n / 2]; - for (int i = 0; i < n / 2; i++) - { - array0[i] = array[i * 2]; - array1[i] = array[i * 2 + 1]; - } - Complex[] y0 = RecursiveFft(array0); - Complex[] y1 = RecursiveFft(array1); - Complex[] y = new Complex[n]; - for (int i = 0; i < n / 2; i++) - { - y[i] = y0[i] + w * y1[i]; - y[i + n / 2] = y0[i] - w * y1[i]; - w *= wn; - } - return y; - } - - /// - /// this method is the revert recursive FftTransform, - /// - /// the input polynomial coefficients (or the dual if reverse FftTransform is called) - /// return the dual coefficients - private static Complex[] RevertRecursiveFft(Complex[] array) - { - int n = array.Length; - if (n == 1) - return array; - Complex wn = new Complex(Math.Cos(2 * Math.PI / n), -Math.Sin(2 * Math.PI / n)); - Complex w = Complex.One; - Complex[] array0 = new Complex[n / 2]; - Complex[] array1 = new Complex[n / 2]; - for (int i = 0; i < n / 2; i++) - { - array0[i] = array[i * 2]; - array1[i] = array[i * 2 + 1]; - } - Complex[] y0 = RevertRecursiveFft(array0); - Complex[] y1 = RevertRecursiveFft(array1); - Complex[] y = new Complex[n]; - for (int i = 0; i < n / 2; i++) - { - y[i] = y0[i] + w * y1[i]; - y[i + n / 2] = y0[i] - w * y1[i]; - w *= wn; - } - return y; - } - - /// - /// get the smallest 2^k which is equal or greater than n - /// - private static int Get2Power(int n) - { - int result = 1; - bool meet1 = false; // check is n is just equals to 2^k for some k - while (n > 1) - { - if ((n & 1) != 0) - meet1 = true; - result = result << 1; - n = n >> 1; - } - if (meet1) - result = result << 1; - return result; - } - } -} From 10935c383b3df6c5bc00bbe1d7d6e2718fe72273 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 4 Jun 2020 15:51:31 +0800 Subject: [PATCH 10/49] optimize WeightedRegression; clean code; add null checks --- src/Microsoft.ML.TimeSeries/FastLoess.cs | 5 +- src/Microsoft.ML.TimeSeries/InnerStl.cs | 5 +- src/Microsoft.ML.TimeSeries/LeastSquares.cs | 76 ++++++---------- .../LocalRegression.cs | 5 +- src/Microsoft.ML.TimeSeries/Loess.cs | 39 +++++++- .../LoessConfiguration.cs | 53 ----------- .../TemporalOutlierInfo.cs | 88 ------------------- 7 files changed, 69 insertions(+), 202 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/LoessConfiguration.cs delete mode 100644 src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs diff --git a/src/Microsoft.ML.TimeSeries/FastLoess.cs b/src/Microsoft.ML.TimeSeries/FastLoess.cs index cc03d463f1..e2adc3dadc 100644 --- a/src/Microsoft.ML.TimeSeries/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/FastLoess.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries { @@ -32,8 +33,8 @@ public class FastLoess /// this method will provide default smoothing ratio if user did not specify public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal = true, int r = -1) { - //ExtendedDiagnostics.EnsureArgumentNotNull(xValues, nameof(xValues)); - //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + Contracts.CheckValue(xValues, nameof(xValues)); + Contracts.CheckValue(yValues, nameof(yValues)); Y = new List(); if (yValues.Count < BasicParameters.MinTimeSeriesLength) diff --git a/src/Microsoft.ML.TimeSeries/InnerStl.cs b/src/Microsoft.ML.TimeSeries/InnerStl.cs index f4d52de6fc..36b56c9c64 100644 --- a/src/Microsoft.ML.TimeSeries/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/InnerStl.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries { @@ -31,8 +32,8 @@ internal class InnerStl /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data public InnerStl(IReadOnlyList yValues, StlConfiguration config, bool isTemporal) { - //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); - //ExtendedDiagnostics.EnsureArgumentNotNull(config, nameof(config)); + Contracts.CheckValue(yValues, nameof(yValues)); + Contracts.CheckValue(config, nameof(config)); if (yValues.Count == 0) throw new Exception("input data structure cannot be 0-length: innerSTL"); diff --git a/src/Microsoft.ML.TimeSeries/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/LeastSquares.cs index c94408e701..d55fb9e6ce 100644 --- a/src/Microsoft.ML.TimeSeries/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/LeastSquares.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Numerics; +using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries { @@ -23,8 +24,8 @@ public class LeastSquares /// the corresponding y-axis value public LeastSquares(List x, List y) { - //ExtendedDiagnostics.EnsureArgumentNotNull(x, nameof(x)); - //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + Contracts.CheckValue(x, nameof(x)); + Contracts.CheckValue(y, nameof(y)); if (x.Count == 0 || y.Count == 0) throw new Exception("input data structure cannot be 0-length"); @@ -41,7 +42,7 @@ public LeastSquares(List x, List y) /// the weighted least squares. note that the weight should be non-negative, and equal length to data public PolynomialModel RegressionDegreeOneWeighted(List weights) { - //ExtendedDiagnostics.EnsureArgumentNotNull(weights, nameof(weights)); + Contracts.CheckValue(weights, nameof(weights)); if (weights.Count != _length) throw new Exception("the weight vector is not equal length to the data points"); @@ -52,44 +53,20 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) throw new Exception("the value in weights should be non-negative!"); } - double[] buffer1 = new double[_length]; - double[] buffer2 = new double[_length]; - double[] buffer3 = new double[_length]; - - double[] w = buffer1; - for (int i = 0; i < _length; i++) - { - w[i] = Math.Sqrt(weights[i]); - } - - double[] kernelMatrixR0 = buffer2; - double[] kernelMatrixR1 = buffer3; - for (int i = 0; i < _length; i++) - { - kernelMatrixR0[i] = 1; - kernelMatrixR1[i] = _x[i]; - } - double[] kernelMatrix1R0 = buffer2; - double[] kernelMatrix1R1 = buffer3; - for (int i = 0; i < _length; i++) - { - kernelMatrix1R0[i] = w[i] * kernelMatrixR0[i]; - kernelMatrix1R1[i] = w[i] * kernelMatrixR1[i]; - } - double[] y1 = buffer1; - for (int i = 0; i < _length; i++) - y1[i] = w[i] * _y[i]; - + // This part unfold the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W), sqrt(W) .* X] double sum00 = 0; double sum01 = 0; double sum10 = 0; double sum11 = 0; for (int k = 0; k < _length; k++) { - sum00 += kernelMatrix1R0[k] * kernelMatrix1R0[k]; - sum01 += kernelMatrix1R0[k] * kernelMatrix1R1[k]; - sum10 += kernelMatrix1R1[k] * kernelMatrix1R0[k]; - sum11 += kernelMatrix1R1[k] * kernelMatrix1R1[k]; + double temp = weights[k]; + sum00 += temp; + temp *= _x[k]; + sum01 += temp; + sum10 += temp; + temp *= _x[k]; + sum11 += temp; } /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is @@ -100,28 +77,25 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) double c = sum10; double d = sum11; double divider = a * d - b * c; - double[,] reverseS = new double[2, 2]; - reverseS[0, 0] = d / divider; - reverseS[0, 1] = -c / divider; - reverseS[1, 0] = -b / divider; - reverseS[1, 1] = a / divider; - - // double[,] reverseS = MatrixEx.ReverseMatrix(S); + double reverseS00 = d / divider; + double reverseS01 = -c / divider; + double reverseS10 = -b / divider; + double reverseS11 = a / divider; + // This part unfold the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W) .* Y] double fy0 = 0; double fy1 = 0; for (int i = 0; i < _length; i++) { - fy0 += kernelMatrix1R0[i] * y1[i]; - fy1 += kernelMatrix1R1[i] * y1[i]; + double temp = weights[i] * _y[i]; + fy0 += temp; + fy1 += temp * _x[i]; } - double b0 = reverseS[0, 0] * fy0 + reverseS[0, 1] * fy1; - double b1 = reverseS[1, 0] * fy0 + reverseS[1, 1] * fy1; + double b0 = reverseS00 * fy0 + reverseS01 * fy1; + double b1 = reverseS10 * fy0 + reverseS11 * fy1; - List results = new List(); - results.Add(b0); - results.Add(b1); + List results = new List(){ b0, b1 }; return new PolynomialModel(results); } @@ -136,7 +110,7 @@ public class PolynomialModel public PolynomialModel(ICollection coeffs) { - //ExtendedDiagnostics.EnsureCollectionNotNullOrEmpty(coeffs, nameof(coeffs)); + Contracts.CheckValue(coeffs, nameof(coeffs)); _coeffs = new List(coeffs); } @@ -151,7 +125,7 @@ public double Y(double x) double p = 1.0; for (int i = 1; i < _coeffs.Count; i++) { - p = p * x; + p *= x; result += _coeffs[i] * p; } return result; diff --git a/src/Microsoft.ML.TimeSeries/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/LocalRegression.cs index 9fd22818e1..8e8a485439 100644 --- a/src/Microsoft.ML.TimeSeries/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/LocalRegression.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using Microsoft.ML.Internal.CpuMath.Core; namespace Microsoft.ML.TimeSeries { @@ -31,8 +32,8 @@ internal class LocalRegression /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data internal LocalRegression(IReadOnlyList x, IReadOnlyList y, int selfIndex, int r, bool isTemporal = true) { - //ExtendedDiagnostics.EnsureArgumentNotNull(x, nameof(x)); - //ExtendedDiagnostics.EnsureArgumentNotNull(y, nameof(y)); + Contracts.CheckValue(x, nameof(x)); + Contracts.CheckValue(y, nameof(y)); if (x.Count <= 1 || x.Count != y.Count) throw new Exception("cannot accomplish neighbors obtaining"); diff --git a/src/Microsoft.ML.TimeSeries/Loess.cs b/src/Microsoft.ML.TimeSeries/Loess.cs index 28f6bb70aa..104b81eb30 100644 --- a/src/Microsoft.ML.TimeSeries/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/Loess.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Text; +using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries { @@ -37,8 +39,8 @@ public class Loess /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal) { - //ExtendedDiagnostics.EnsureArgumentNotNull(xValues, nameof(xValues)); - //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + Contracts.CheckValue(xValues, nameof(xValues)); + Contracts.CheckValue(yValues, nameof(yValues)); if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) throw new Exception("input data structure cannot be 0-length: lowess"); @@ -76,8 +78,8 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data public Loess(IReadOnlyList xValues, IReadOnlyList yValues, int r, bool isTemporal) { - //ExtendedDiagnostics.EnsureArgumentNotNull(xValues, nameof(xValues)); - //ExtendedDiagnostics.EnsureArgumentNotNull(yValues, nameof(yValues)); + Contracts.CheckValue(xValues, nameof(xValues)); + Contracts.CheckValue(yValues, nameof(yValues)); if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) throw new Exception("input data structure cannot be 0-length: lowess"); @@ -170,4 +172,33 @@ public static double Tricube(double value) return temp * temp * temp; } } + + /// + /// this class is used to store the parameters which are needed for lowess algorithm. + /// the name of these constansts are compliant with the original terms in paper. + /// + public class LoessConfiguration + { + /// + /// this value is used for performance concern. when the length of the series goes large, a ratio of neighbors will be significant, + /// which leads to unsatisfied slow. so this value is used to bound the maximum # of neighbors one epoch can have. + /// + public const int MaximumNeighborCount = 100; + + /// + /// minumum number of neighbor counts, to apply underlying regression analysis. + /// this number should be even, so that neighbors on left/right side of a given data point is balanced. unbalanced neighbors would make the local-weighted regression biased noticeably at corner cases. + /// + public const int MinimumNeighborCount = 4; + + /// + /// (0, 1], a smooth range ratio. let fn be the number of neighbors of a specific point. + /// + public static readonly double F = 0.3; + + /// + /// the number of iterations for robust regression. + /// + public static readonly int T = 2; + } } diff --git a/src/Microsoft.ML.TimeSeries/LoessConfiguration.cs b/src/Microsoft.ML.TimeSeries/LoessConfiguration.cs deleted file mode 100644 index 0b4585ee08..0000000000 --- a/src/Microsoft.ML.TimeSeries/LoessConfiguration.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - public enum RegressionModelType - { - /// - /// the 1-order model, i.e., linear model - /// - One, - - /// - /// the 2-order model, i.e., square polynomial model. - /// - Two, - } - - /// - /// this class is used to store the parameters which are needed for lowess algorithm. - /// the name of these constansts are compliant with the original terms in paper. - /// - public class LoessConfiguration - { - /// - /// this value is used for performance concern. when the length of the series goes large, a ratio of neighbors will be significant, - /// which leads to unsatisfied slow. so this value is used to bound the maximum # of neighbors one epoch can have. - /// - public const int MaximumNeighborCount = 100; - - /// - /// minumum number of neighbor counts, to apply underlying regression analysis. - /// this number should be even, so that neighbors on left/right side of a given data point is balanced. unbalanced neighbors would make the local-weighted regression biased noticeably at corner cases. - /// - public const int MinimumNeighborCount = 4; - - /// - /// (0, 1], a smooth range ratio. let fn be the number of neighbors of a specific point. - /// - public static readonly double F = 0.3; - - /// - /// this is used to indicate which regression model is used. - /// - public static readonly RegressionModelType ModelType = RegressionModelType.One; - - /// - /// the number of iterations for robust regression. - /// - public static readonly int T = 2; - } -} diff --git a/src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs b/src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs deleted file mode 100644 index 8916b6aa7e..0000000000 --- a/src/Microsoft.ML.TimeSeries/TemporalOutlierInfo.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - /// - /// the outliers of time series. A default rank is available when this instance is constructed. - /// - public class TemporalOutlierInfo : SingleSeriesInfo - { - /// - /// Initializes a new instance of the class. - /// the corresponding properties of outlier component - /// - /// x axis values - /// y axis values - /// the residual of the curve. which is the data source for outlier identification - /// the indication for outliers - /// severity - /// confidence - public TemporalOutlierInfo( - IReadOnlyList x, - IReadOnlyList y, - IReadOnlyList residual, - IReadOnlyList index, - IReadOnlyList severity, - double confidence) - { - X = x; - Y = y; - Residual = residual; - OutlierIndex = index; - OutlierSeverity = severity; - Rank = confidence; - - Count = 0; - foreach (int indicator in index) - { - if (indicator == 1) - Count++; - } - - Description = String.Format("there exists {0} outliers. Rank:{1}", Count, Rank); - - Kind = TimeSeriesInfoKind.Outlier; - } - - /// - /// 0/1 value for each data point, to indicate whether it is an outlier or not. - /// 1 means outlier, 0 means not. - /// - public IReadOnlyList OutlierIndex { get; private set; } - - /// - /// equal length with OutlierIndex, indicate the severity of each outlier. 0 for non-outlier epochs. - /// - public IReadOnlyList OutlierSeverity { get; private set; } - - /// - /// the residual values, which is the signal after decompose trend and seasonal signal. - /// - public IReadOnlyList Residual { get; private set; } - - /// - /// total count of outliers - /// - public int Count { get; private set; } - - public override string Description - { - get; - protected set; - } - - public override double Rank - { - get; - set; - } - - public override TimeSeriesInfoKind Kind - { - get; - protected set; - } - } -} From b524957af6ab29b216cea3e831ba547790b8f324 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 4 Jun 2020 15:59:27 +0800 Subject: [PATCH 11/49] recover --- .../SrCnnEntireAnomalyDetector.cs | 4 ++-- .../TrajectoryMatrix.cs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index a17b0d7f9b..24cb8dc97a 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -429,7 +429,7 @@ private void SpectralResidual(double[] values, double[][] results, double thresh // Step 1: Get backadd wave BackAdd(values); - // Step 2: FftTransform transformation + // Step 2: FFT transformation int length = _backAddArray.Length; AllocateDoubleArray(ref _fftRe, length); AllocateDoubleArray(ref _fftIm, length); @@ -437,7 +437,7 @@ private void SpectralResidual(double[] values, double[][] results, double thresh AllocateDoubleArray(ref _zeroArray, length); FftUtils.ComputeForwardFft(_backAddArray, _zeroArray, _fftRe, _fftIm, length); - // Step 3: Calculate mags of FftTransform + // Step 3: Calculate mags of FFT AllocateDoubleArray(ref _magList, length); AllocateDoubleArray(ref _magLogList, length); for (int i = 0; i < length; ++i) diff --git a/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs b/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs index 4667ffb455..62d81cf6ec 100644 --- a/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs +++ b/src/Microsoft.ML.TimeSeries/TrajectoryMatrix.cs @@ -298,11 +298,11 @@ private void FftMultiply(Single[] vector, Single[] result, bool add = false, int int i; - // Computing the FftTransform of the trajectory matrix + // Computing the FFT of the trajectory matrix if (!_isSeriesFftCached) CacheInputSeriesFft(); - // Computing the FftTransform of the input vector + // Computing the FFT of the input vector for (i = 0; i < _k; ++i) _inputRe[i] = vector[_k - i - 1 + srcIndex]; @@ -323,7 +323,7 @@ private void FftMultiply(Single[] vector, Single[] result, bool add = false, int _outputIm[i] = _cachedSeriesFftRe[i] * im + _cachedSeriesFftIm[i] * re; } - // Computing the inverse FftTransform of the result + // Computing the inverse FFT of the result FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, _inputRe.Length); // Generating the output @@ -402,11 +402,11 @@ private void FftMultiplyTranspose(Single[] vector, Single[] result, bool add = f int i; - // Computing the FftTransform of the trajectory matrix + // Computing the FFT of the trajectory matrix if (!_isSeriesFftCached) CacheInputSeriesFft(); - // Computing the FftTransform of the input vector + // Computing the FFT of the input vector for (i = 0; i < _k - 1; ++i) _inputRe[i] = 0; @@ -427,7 +427,7 @@ private void FftMultiplyTranspose(Single[] vector, Single[] result, bool add = f _outputIm[i] = _cachedSeriesFftRe[i] * im + _cachedSeriesFftIm[i] * re; } - // Computing the inverse FftTransform of the result + // Computing the inverse FFT of the result FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, _inputRe.Length); // Generating the output @@ -578,7 +578,7 @@ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Singl if (!_isSeriesFftCached) CacheInputSeriesFft(); - // Computing the FftTransform of u + // Computing the FFT of u for (i = us; i <= ue; ++i) _inputRe[i - us] = u[i + uIndex]; @@ -587,7 +587,7 @@ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Singl FftUtils.ComputeForwardFft(_inputRe, _allZerosIm, _outputRe, _outputIm, len); - // Computing the FftTransform of v + // Computing the FFT of v for (i = vs; i <= ve; ++i) _inputRe[i - vs] = v[i + vIndex]; @@ -612,7 +612,7 @@ private void FftRankOneHankelization(Single[] u, Single[] v, Single sigma, Singl for (i = 0; i < _seriesLength; ++i) _allZerosIm[i] = 0; - // Computing the inverse FftTransform of the result + // Computing the inverse FFT of the result FftUtils.ComputeBackwardFft(_outputRe, _outputIm, _outputRe, _outputIm, len); // Generating the output From d40440b2e4c2ee42e59fedc0a4d27d8fa5fa0f4a Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 4 Jun 2020 16:04:26 +0800 Subject: [PATCH 12/49] reduce file numbers --- src/Microsoft.ML.TimeSeries/TrendInfo.cs | 5 +- src/Microsoft.ML.TimeSeries/TuningParams.cs | 106 -------------------- 2 files changed, 3 insertions(+), 108 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/TuningParams.cs diff --git a/src/Microsoft.ML.TimeSeries/TrendInfo.cs b/src/Microsoft.ML.TimeSeries/TrendInfo.cs index f9d137e4ba..bea7145ee9 100644 --- a/src/Microsoft.ML.TimeSeries/TrendInfo.cs +++ b/src/Microsoft.ML.TimeSeries/TrendInfo.cs @@ -11,6 +11,7 @@ public class TrendInfo : SingleSeriesInfo { private const double _slopeThreshold = 0.25; private const double _msrThreshold = 0.1; + private const int _shortTimeseriesLength = 40; /// /// Initializes a new instance of the class. @@ -148,7 +149,7 @@ public static double CalcSignificance( bool isIncrease = slope > 0; // when the time series is short, we should apply the consistency checking on the original signal, so that the result will be compliant with user perception. - if (trend.Count < TuningParams.ShortTimeseriesLength) + if (trend.Count < _shortTimeseriesLength) { CalculateRatio(rawSignal, out increaseRatio, out decreaseRatio); } @@ -219,7 +220,7 @@ private static void CalculateRatio(IReadOnlyList signal, out double incr private static double ConsistencyThreshold(int length) { - if (length < TuningParams.ShortTimeseriesLength) + if (length < _shortTimeseriesLength) return 0.8; else return 0.7; diff --git a/src/Microsoft.ML.TimeSeries/TuningParams.cs b/src/Microsoft.ML.TimeSeries/TuningParams.cs deleted file mode 100644 index d6fb9ed7c7..0000000000 --- a/src/Microsoft.ML.TimeSeries/TuningParams.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - public class TuningParams - { - public const int ShortTimeseriesLength = 40; - private const double MagnitudeLower = 0.7; - private const double MagnitudeUpper = 1.3; - - /// - /// dynamically identify a set of outlier categorization params by checking the input time series length - /// - /// the input time series length - /// when we determine if an outlier is dense or not, we use the outliers in its neighbors. this value indicates the window size of the neighbors - /// the threshold of the total outlier count in the window size - /// finally, we glue the outliers near each other into a group. this is the threshold to determine "nearby" - public static void ParamsForCategorizeOutliers( - int length, - out int outlierConnectWindowSize, - out int denseOutlierThreshold, - out int glueGapThreshold) - { - // for typical long time series, we use a set of parameters which are propotional to the entire time series length - if (length > ShortTimeseriesLength) - { - int windowSize = ChangeDetectionUtility.GetWindowSize(length); - - // at least 5% of data points are outliers, which forms reasonable change region. this is one-side window size. - outlierConnectWindowSize = Math.Max(1, (int)(windowSize * 0.05)); - - // the total length is 2 times of outlierConnectWindowSize plus the checking point itself - int checkSize = (2 * outlierConnectWindowSize) + 1; - - // in the checkSize region, at least 70% points are outliers, then these points formed a dense outlier region - denseOutlierThreshold = (int)(checkSize * 0.7); - - // the maximum gap between two outliers that we accept gluing them together. - glueGapThreshold = Math.Max(1, checkSize - denseOutlierThreshold); - } - else - { - outlierConnectWindowSize = 0; - denseOutlierThreshold = 1; - glueGapThreshold = 1; - } - } - - /// - /// especially for short time series, we don't necessarily output two many outliers. - /// since there usually exist only few outliers for short time series. - /// - public static int OutputOutlierMaxCount(int length) - { - /*all these numbers are tunable magic numbers*/ - return 5; - } - - /// - /// check whether two positive numbers are with similar magnitude or not. - /// - /// the first positive number - /// the second positive number - /// return true if they are with similar magnitude. otherwise, return false - public static bool IsSimilarMagnitude(double absValue1, double absValue2) - { - if (absValue1 > absValue2) - { - // using smaller or equal to handle when both input values are 0 - return absValue1 <= MagnitudeUpper * absValue2; - } - else - { - // using smaller or equal to handle when both input values are 0 - return absValue1 >= MagnitudeLower * absValue2; - } - } - } - internal class ChangeDetectionUtility - { - /// - /// outlier detection for change points. we use this value instead of 6 (recommended by paper) to reduce false-negative - /// - internal const double Severity = 3.5; - - /// - /// according to the theory, the window size A should be - /// lim A/n ->0, lim (logn)^2/A -> 0 - /// - private const double Power = 0.6; - - /// - /// calculate the proper window size for change point detection, given the total time series length. - /// stay tuned - /// - /// the total length of time series - public static int GetWindowSize(int n) - { - // at least 0.1n as the window size, make sure at most 5 change points. - double value = Math.Max(Math.Pow(n, Power), n * 0.1); - return (int)value; - } - } -} From 92c6aabb8b29b4926d5c13b7178f4d7b39cfa55f Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 4 Jun 2020 16:08:29 +0800 Subject: [PATCH 13/49] restore --- .../Microsoft.ML.TimeSeries.csproj | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj index fc02b0bd5c..c5b4550c51 100644 --- a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj +++ b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj @@ -3,18 +3,8 @@ netstandard2.0 Microsoft.ML.TimeSeries - Library - - - - - - - - - From f6e85661a9fa8c080f7f0fd548675f6889d76977 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 4 Jun 2020 20:36:34 +0800 Subject: [PATCH 14/49] move stl related codes to a subfolder --- .../{ => STL}/BackendStructureBase.cs | 0 .../{ => STL}/BasicParameters.cs | 0 .../{ => STL}/FastLoess.cs | 0 .../{ => STL}/InnerStl.cs | 92 +++++++++++++------ .../{ => STL}/LeastSquares.cs | 0 .../{ => STL}/LocalRegression.cs | 0 .../{ => STL}/Loess.cs | 0 .../{ => STL}/MathsTools.cs | 0 .../{ => STL}/SeasonalInfo.cs | 0 .../StlConfiguration.cs | 72 --------------- 10 files changed, 66 insertions(+), 98 deletions(-) rename src/Microsoft.ML.TimeSeries/{ => STL}/BackendStructureBase.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/BasicParameters.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/FastLoess.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/InnerStl.cs (85%) rename src/Microsoft.ML.TimeSeries/{ => STL}/LeastSquares.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/LocalRegression.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/Loess.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/MathsTools.cs (100%) rename src/Microsoft.ML.TimeSeries/{ => STL}/SeasonalInfo.cs (100%) delete mode 100644 src/Microsoft.ML.TimeSeries/StlConfiguration.cs diff --git a/src/Microsoft.ML.TimeSeries/BackendStructureBase.cs b/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/BackendStructureBase.cs rename to src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs diff --git a/src/Microsoft.ML.TimeSeries/BasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/BasicParameters.cs rename to src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs diff --git a/src/Microsoft.ML.TimeSeries/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/FastLoess.cs rename to src/Microsoft.ML.TimeSeries/STL/FastLoess.cs diff --git a/src/Microsoft.ML.TimeSeries/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs similarity index 85% rename from src/Microsoft.ML.TimeSeries/InnerStl.cs rename to src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 36b56c9c64..cf8add650c 100644 --- a/src/Microsoft.ML.TimeSeries/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -7,6 +7,72 @@ namespace Microsoft.ML.TimeSeries { + internal class StlConfiguration + { + /// + /// the smoothing parameter for the seasonal component. + /// should be odd, and at least 7. + /// + public const int Ns = 9; + + /// + /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases + /// + public const int Ni = 2; + + /// + /// the number of robustness iterations of the outer loop + /// + public const int No = 10; + + public StlConfiguration() + { + Np = -1; + } + + public StlConfiguration(int np) + { + Np = np; + } + + /// + /// the number of observations in each cycle of the seasonal component + /// + public int Np { get; } + + /// + /// the smoothing parameter for the low-pass filter. + /// /ref: should be the least odd integer greater than or equal to np. + /// it will preventing the trend and seasonal components from competing for the same variation in the data. + /// + public int Nl + { + get + { + if (Np % 2 == 0) + return Np + 1; + return Np; + } + } + + /// + /// the smoothing parameter for the trend component. + /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen + /// s.t., satisty the following inequality. + /// + public int Nt + { + get + { + double value = 1.5 * Np / (1.0 - 1.5 / StlConfiguration.Ns); + int result = (int)value + 1; + if (result % 2 == 0) + result++; + return result; + } + } + } + internal class InnerStl { private readonly IReadOnlyList _x; @@ -75,23 +141,6 @@ public IReadOnlyList Residual get { return _residual; } } - /// - /// this field is used to indicate which data point is an outlier. - /// the definition is outlier is, not contribute for the local regression. - /// - public IReadOnlyList OutlierIndexeses - { - get { return _outlierIndexes; } - } - - /// - /// [0, infinite] indicate the severity of each outlier - /// - public IReadOnlyList OutlierSeverity - { - get { return _outlierSeverity; } - } - /// /// calculate the slope of the trend component /// @@ -101,15 +150,6 @@ public double Slope private set; } - /// - /// the mean residual squares. here the outliers are not taken into account. - /// - public double Mrs - { - get; - private set; - } - /// /// the core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, /// see section 2 and 3. especially section 2. diff --git a/src/Microsoft.ML.TimeSeries/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/LeastSquares.cs rename to src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs diff --git a/src/Microsoft.ML.TimeSeries/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/LocalRegression.cs rename to src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs diff --git a/src/Microsoft.ML.TimeSeries/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/Loess.cs rename to src/Microsoft.ML.TimeSeries/STL/Loess.cs diff --git a/src/Microsoft.ML.TimeSeries/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/MathsTools.cs rename to src/Microsoft.ML.TimeSeries/STL/MathsTools.cs diff --git a/src/Microsoft.ML.TimeSeries/SeasonalInfo.cs b/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/SeasonalInfo.cs rename to src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs diff --git a/src/Microsoft.ML.TimeSeries/StlConfiguration.cs b/src/Microsoft.ML.TimeSeries/StlConfiguration.cs deleted file mode 100644 index 530384f673..0000000000 --- a/src/Microsoft.ML.TimeSeries/StlConfiguration.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries -{ - internal class StlConfiguration - { - /// - /// the smoothing parameter for the seasonal component. - /// should be odd, and at least 7. - /// - public const int Ns = 9; - - /// - /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases - /// - public const int Ni = 2; - - /// - /// the number of robustness iterations of the outer loop - /// - public const int No = 10; - - public StlConfiguration() - { - Np = -1; - } - - public StlConfiguration(int np) - { - Np = np; - } - - /// - /// the number of observations in each cycle of the seasonal component - /// - public int Np { get; } - - /// - /// the smoothing parameter for the low-pass filter. - /// /ref: should be the least odd integer greater than or equal to np. - /// it will preventing the trend and seasonal components from competing for the same variation in the data. - /// - public int Nl - { - get - { - if (Np % 2 == 0) - return Np + 1; - return Np; - } - } - - /// - /// the smoothing parameter for the trend component. - /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen - /// s.t., satisty the following inequality. - /// - public int Nt - { - get - { - double value = 1.5 * Np / (1.0 - 1.5 / StlConfiguration.Ns); - int result = (int)value + 1; - if (result % 2 == 0) - result++; - return result; - } - } - } -} From d4515eefcc430402229ab7ced6e854c50fcea6f2 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Fri, 5 Jun 2020 10:49:20 +0800 Subject: [PATCH 15/49] fix sln file --- Microsoft.ML.sln | 402 +++++++++--------- .../{ => STL}/TrendInfo.cs | 0 2 files changed, 195 insertions(+), 207 deletions(-) rename src/Microsoft.ML.TimeSeries/{ => STL}/TrendInfo.cs (100%) diff --git a/Microsoft.ML.sln b/Microsoft.ML.sln index 9884eda1a6..8c6c84b14c 100644 --- a/Microsoft.ML.sln +++ b/Microsoft.ML.sln @@ -1,7 +1,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 +VisualStudioVersion = 16.0.29209.152 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core", "src\Microsoft.ML.Core\Microsoft.ML.Core.csproj", "{A6CA6CC6-5D7C-4D7F-A0F5-35E14B383B0A}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{09EADF06-BE25-4228-AB53-95AE3E15B530}" ProjectSection(SolutionItems) = preProject src\Source.ruleset = src\Source.ruleset @@ -9,6 +11,38 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{09EADF06-BE2 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{AED9C836-31E3-4F3F-8ABC-929555D3F3C4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core.Tests", "test\Microsoft.ML.Core.Tests\Microsoft.ML.Core.Tests.csproj", "{EC743D1D-7691-43B7-B9B0-5F2F7018A8F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath", "src\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.csproj", "{46F2F967-C23F-4076-858D-33F7DA9BD2DA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Data", "src\Microsoft.ML.Data\Microsoft.ML.Data.csproj", "{AD92D96B-0E96-4F22-8DCE-892E13B1F282}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxConverter", "src\Microsoft.ML.OnnxConverter\Microsoft.ML.OnnxConverter.csproj", "{65D0603E-B96C-4DFC-BDD1-705891B88C18}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StandardTrainers", "src\Microsoft.ML.StandardTrainers\Microsoft.ML.StandardTrainers.csproj", "{707BB22C-7E5F-497A-8C2F-74578F675705}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Transforms", "src\Microsoft.ML.Transforms\Microsoft.ML.Transforms.csproj", "{2911A286-ECA4-4730-97A9-DA1FEE2DED97}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.KMeansClustering", "src\Microsoft.ML.KMeansClustering\Microsoft.ML.KMeansClustering.csproj", "{F1CAE3AB-4F86-4BC0-BBA8-C4A58E7E8A4A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.PCA", "src\Microsoft.ML.PCA\Microsoft.ML.PCA.csproj", "{58E06735-1129-4DD5-86E0-6BBFF049AAD9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Tests", "test\Microsoft.ML.Tests\Microsoft.ML.Tests.csproj", "{64BC22D3-1E76-41EF-94D8-C79E471FF2DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFramework", "test\Microsoft.ML.TestFramework\Microsoft.ML.TestFramework.csproj", "{B5989C06-4FFA-46C1-9D85-9366B34AB0A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Predictor.Tests", "test\Microsoft.ML.Predictor.Tests\Microsoft.ML.Predictor.Tests.csproj", "{6B047E09-39C9-4583-96F3-685D84CA4117}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Functional.Tests", "test\Microsoft.ML.Functional.Tests\Microsoft.ML.Functional.Tests.csproj", "{CFED9F0C-FF81-4C96-8D5E-0436264CA7B5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ResultProcessor", "src\Microsoft.ML.ResultProcessor\Microsoft.ML.ResultProcessor.csproj", "{3769FCC3-9AFF-4C37-97E9-6854324681DF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.FastTree", "src\Microsoft.ML.FastTree\Microsoft.ML.FastTree.csproj", "{B7B593C5-FB8C-4ADA-A638-5B53B47D087E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Parquet", "src\Microsoft.ML.Parquet\Microsoft.ML.Parquet.csproj", "{16BB1454-2108-40E5-B3A6-594654005303}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper", "src\Microsoft.ML.Sweeper\Microsoft.ML.Sweeper.csproj", "{55C8122D-79EA-48AB-85D0-EB551FC1C427}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pkg", "pkg", "{D3D38B03-B557-484D-8348-8BADEE4DF592}" ProjectSection(SolutionItems) = preProject pkg\Directory.Build.props = pkg\Directory.Build.props @@ -25,6 +59,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Parquet", "Mic pkg\Microsoft.ML.Parquet\Microsoft.ML.Parquet.nupkgproj = pkg\Microsoft.ML.Parquet\Microsoft.ML.Parquet.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks", "test\Microsoft.ML.Benchmarks\Microsoft.ML.Benchmarks.csproj", "{7A9DB75F-2CA5-4184-9EF5-1F17EB39483F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Maml", "src\Microsoft.ML.Maml\Microsoft.ML.Maml.csproj", "{64F40A0D-D4C2-4AA7-8470-E9CC437827E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Console", "src\Microsoft.ML.Console\Microsoft.ML.Console.csproj", "{362A98CF-FBF7-4EBB-A11B-990BBF845B15}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{487213C9-E8A9-4F94-85D7-28A05DBBFE3A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstandard2.0", "{9252A8EB-ABFB-440C-AB4D-1D562753CE0F}" @@ -32,6 +72,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstanda pkg\Microsoft.ML\build\netstandard2.0\Microsoft.ML.targets = pkg\Microsoft.ML\build\netstandard2.0\Microsoft.ML.targets EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper.Tests", "test\Microsoft.ML.Sweeper.Tests\Microsoft.ML.Sweeper.Tests.csproj", "{3DEB504D-7A07-48CE-91A2-8047461CB3D4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.LightGbm", "src\Microsoft.ML.LightGbm\Microsoft.ML.LightGbm.csproj", "{001F3B4E-FBE4-4001-AFD2-A6A989CD1C25}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Ensemble", "src\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.csproj", "{DCF46B79-1FDB-4DBA-A263-D3D64E3AAA27}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.CpuMath", "Microsoft.ML.CpuMath", "{BF66A305-DF10-47E4-8D81-42049B149D2B}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.nupkgproj = pkg\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.nupkgproj @@ -40,8 +86,52 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.CpuMath", "Mic EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools-local", "tools-local", "{7F13E156-3EBA-4021-84A5-CD56BA72F99E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.InternalCodeAnalyzer", "tools-local\Microsoft.ML.InternalCodeAnalyzer\Microsoft.ML.InternalCodeAnalyzer.csproj", "{B4E55B2D-2A92-46E7-B72F-E76D6FD83440}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeAnalyzer.Tests", "test\Microsoft.ML.CodeAnalyzer.Tests\Microsoft.ML.CodeAnalyzer.Tests.csproj", "{3E4ABF07-7970-4BE6-B45B-A13D3C397545}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.PerformanceTests", "test\Microsoft.ML.CpuMath.PerformanceTests\Microsoft.ML.CpuMath.PerformanceTests.csproj", "{7333EDEF-4144-405C-A5EC-6F42201857D8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.UnitTests", "test\Microsoft.ML.CpuMath.UnitTests\Microsoft.ML.CpuMath.UnitTests.csproj", "{E97D8B5A-3035-4D41-9B0D-77FF8FB8D132}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Microsoft.ML.FSharp.Tests", "test\Microsoft.ML.FSharp.Tests\Microsoft.ML.FSharp.Tests.fsproj", "{802233D6-8CC0-46AD-9F23-FEE1E9AED9B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ImageAnalytics", "src\Microsoft.ML.ImageAnalytics\Microsoft.ML.ImageAnalytics.csproj", "{00E38F77-1E61-4CDF-8F97-1417D4E85053}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Mkl.Components", "src\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.csproj", "{A7222F41-1CF0-47D9-B80C-B4D77B027A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TensorFlow", "src\Microsoft.ML.TensorFlow\Microsoft.ML.TensorFlow.csproj", "{570A0B8A-5463-44D2-8521-54C0CA4CACA9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries", "src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj", "{5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformer", "src\Microsoft.ML.OnnxTransformer\Microsoft.ML.OnnxTransformer.csproj", "{8C05642D-C3AA-4972-B02C-93681161A6BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnAnalyzer", "src\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer.csproj", "{73DAAC82-D308-48CC-8FFE-3B037F8BBCCA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformerTest", "test\Microsoft.ML.OnnxTransformerTest\Microsoft.ML.OnnxTransformerTest.csproj", "{49D03292-8AFE-4B82-823C-D047BF8420F7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks.Tests", "test\Microsoft.ML.Benchmarks.Tests\Microsoft.ML.Benchmarks.Tests.csproj", "{B6C83F04-A04B-4F00-9E68-1EC411F9317C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries.Tests", "test\Microsoft.ML.TimeSeries.Tests\Microsoft.ML.TimeSeries.Tests.csproj", "{4B101D58-E7E4-4877-A536-A9B41E2E82A3}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DA452A53-2E94-4433-B08C-041EDEC729E6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples", "docs\samples\Microsoft.ML.Samples\Microsoft.ML.Samples.csproj", "{ECB71297-9DF1-48CE-B93A-CD969221F9B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.SamplesUtils", "src\Microsoft.ML.SamplesUtils\Microsoft.ML.SamplesUtils.csproj", "{11A5210E-2EA7-42F1-80DB-827762E9C781}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Recommender", "src\Microsoft.ML.Recommender\Microsoft.ML.Recommender.csproj", "{C8E1772B-DFD9-4A4D-830D-6AAB1C668BB3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet18", "src\Microsoft.ML.DnnImageFeaturizer.ResNet18\Microsoft.ML.DnnImageFeaturizer.ResNet18.csproj", "{9222FC9D-599A-49A5-B685-08CC9A5C81D7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.AlexNet", "src\Microsoft.ML.DnnImageFeaturizer.AlexNet\Microsoft.ML.DnnImageFeaturizer.AlexNet.csproj", "{6C29AA9B-054B-4762-BEA5-D305B932AA80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet50", "src\Microsoft.ML.DnnImageFeaturizer.ResNet50\Microsoft.ML.DnnImageFeaturizer.ResNet50.csproj", "{4805129D-78C8-46D4-9519-0AD9B0574D6D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet101", "src\Microsoft.ML.DnnImageFeaturizer.ResNet101\Microsoft.ML.DnnImageFeaturizer.ResNet101.csproj", "{DB7CEB5E-8BE6-48A7-87BE-B91D9AE96F71}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.EntryPoints", "src\Microsoft.ML.EntryPoints\Microsoft.ML.EntryPoints.csproj", "{7504D46F-E4B3-43CB-9B1C-82F3131F1C99}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Mkl.Components", "Microsoft.ML.Mkl.Components", "{63006A14-B924-48C5-83C9-CFE9DA22B01F}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.nupkgproj = pkg\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.nupkgproj @@ -131,48 +221,82 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{A84717 pkg\common\DnnImageFeaturizer.props = pkg\common\DnnImageFeaturizer.props EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DataView", "src\Microsoft.ML.DataView\Microsoft.ML.DataView.csproj", "{85D0CAFD-2FE8-496A-88C7-585D35B94243}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.DataView", "Microsoft.ML.DataView", "{31D38B21-102B-41C0-9E0A-2FE0BF68D123}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.nupkgproj = pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.nupkgproj pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.symbols.nupkgproj = pkg\Microsoft.ML.DataView\Microsoft.ML.DataView.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteExecutorConsoleApp", "test\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj", "{5E920CAC-5A28-42FB-936E-49C472130953}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Ensemble", "Microsoft.ML.Ensemble", "{AD7058C9-5608-49A8-BE23-58C33A74EE91}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.nupkgproj = pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.nupkgproj pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.symbols.nupkgproj = pkg\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Experimental", "src\Microsoft.ML.Experimental\Microsoft.ML.Experimental.csproj", "{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.FastTree", "Microsoft.ML.FastTree", "{B1B3F284-FA3D-4D76-A712-FF04495D244B}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.nupkgproj = pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.nupkgproj pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.symbols.nupkgproj = pkg\Microsoft.ML.FastTree\Microsoft.ML.FastTree.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML", "src\Microsoft.Extensions.ML\Microsoft.Extensions.ML.csproj", "{D6741C37-B5E6-4050-BCBA-9715809EA15B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML.Tests", "test\Microsoft.Extensions.ML.Tests\Microsoft.Extensions.ML.Tests.csproj", "{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Extensions.ML", "Microsoft.Extensions.ML", "{AE4F7569-26F3-4160-8A8B-7A57D0DA3350}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.nupkgproj = pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.nupkgproj pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.symbols.nupkgproj = pkg\Microsoft.Extensions.ML\Microsoft.Extensions.ML.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StableApi", "tools-local\Microsoft.ML.StableApi\Microsoft.ML.StableApi.csproj", "{F308DC6B-7E59-40D7-A581-834E8CD99CFE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Tests", "test\Microsoft.ML.AutoML.Tests\Microsoft.ML.AutoML.Tests.csproj", "{C2652287-CD6D-40FB-B042-95FB56D09DB8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML", "src\Microsoft.ML.AutoML\Microsoft.ML.AutoML.csproj", "{E48285BF-F49A-4EA3-AED0-1BDDBF77EB80}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.AutoML", "Microsoft.ML.AutoML", "{F5D11F71-2D61-4AE9-99D7-0F0B54649B15}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.nupkgproj = pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.nupkgproj pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.symbols.nupkgproj = pkg\Microsoft.ML.AutoML\Microsoft.ML.AutoML.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Samples", "docs\samples\Microsoft.ML.AutoML.Samples\Microsoft.ML.AutoML.Samples.csproj", "{A6924919-9E37-4023-8B7F-E85C8E3CC9B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples.GPU", "docs\samples\Microsoft.ML.Samples.GPU\Microsoft.ML.Samples.GPU.csproj", "{3C8F910B-7F23-4D25-B521-6D5AC9570ADD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Featurizers", "src\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.csproj", "{E2DD0721-5B0F-4606-8182-4C7EFB834518}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.Featurizers", "Microsoft.ML.Featurizers", "{1BA5C784-52E8-4A87-8525-26B2452F2882}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.nupkgproj = pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.nupkgproj pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.symbols.nupkgproj = pkg\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator", "src\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.csproj", "{56CB0850-7341-4D71-9AE4-9EFC472D93DD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator.Tests", "test\Microsoft.ML.CodeGenerator.Tests\Microsoft.ML.CodeGenerator.Tests.csproj", "{46CC5637-3DDF-4100-93FC-44BB87B2DB81}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.ML.CodeGenerator", "Microsoft.ML.CodeGenerator", "{3817A875-278C-4140-BF66-3C4A8CA55F0D}" ProjectSection(SolutionItems) = preProject pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.nupkgproj = pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.nupkgproj pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.symbols.nupkgproj = pkg\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.symbols.nupkgproj EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Vision", "src\Microsoft.ML.Vision\Microsoft.ML.Vision.csproj", "{419F93D5-4135-4DA0-A76E-EFC23E04093D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFrameworkCommon", "test\Microsoft.ML.TestFrameworkCommon\Microsoft.ML.TestFrameworkCommon.csproj", "{A22FAD27-77E8-4460-8B92-EC7090B7173A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ML.NightlyBuild.Tests", "test\Microsoft.ML.NightlyBuild.Tests\Microsoft.ML.NightlyBuild.Tests.csproj", "{A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.ML.NugetPackageVersionUpdater", "test\Microsoft.ML.NugetPackageVersionUpdater\Microsoft.ML.NugetPackageVersionUpdater.csproj", "{C8DB58DC-6434-4431-A81F-263D86E2A5F3}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{C91F81E3-B900-4968-A6DF-F53B515E97E1}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstandard2.0", "{027DBA48-85B6-46F1-9487-0B49B5057FC0}" @@ -180,130 +304,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netstandard2.0", "netstanda pkg\Microsoft.ML.CpuMath\build\netstandard2.0\Microsoft.ML.CpuMath.props = pkg\Microsoft.ML.CpuMath\build\netstandard2.0\Microsoft.ML.CpuMath.props EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core", "src\Microsoft.ML.Core\Microsoft.ML.Core.csproj", "{A6CA6CC6-5D7C-4D7F-A0F5-35E14B383B0A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Core.Tests", "test\Microsoft.ML.Core.Tests\Microsoft.ML.Core.Tests.csproj", "{EC743D1D-7691-43B7-B9B0-5F2F7018A8F6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath", "src\Microsoft.ML.CpuMath\Microsoft.ML.CpuMath.csproj", "{46F2F967-C23F-4076-858D-33F7DA9BD2DA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Data", "src\Microsoft.ML.Data\Microsoft.ML.Data.csproj", "{AD92D96B-0E96-4F22-8DCE-892E13B1F282}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxConverter", "src\Microsoft.ML.OnnxConverter\Microsoft.ML.OnnxConverter.csproj", "{65D0603E-B96C-4DFC-BDD1-705891B88C18}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StandardTrainers", "src\Microsoft.ML.StandardTrainers\Microsoft.ML.StandardTrainers.csproj", "{707BB22C-7E5F-497A-8C2F-74578F675705}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Transforms", "src\Microsoft.ML.Transforms\Microsoft.ML.Transforms.csproj", "{2911A286-ECA4-4730-97A9-DA1FEE2DED97}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.KMeansClustering", "src\Microsoft.ML.KMeansClustering\Microsoft.ML.KMeansClustering.csproj", "{F1CAE3AB-4F86-4BC0-BBA8-C4A58E7E8A4A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.PCA", "src\Microsoft.ML.PCA\Microsoft.ML.PCA.csproj", "{58E06735-1129-4DD5-86E0-6BBFF049AAD9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Tests", "test\Microsoft.ML.Tests\Microsoft.ML.Tests.csproj", "{64BC22D3-1E76-41EF-94D8-C79E471FF2DD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFramework", "test\Microsoft.ML.TestFramework\Microsoft.ML.TestFramework.csproj", "{B5989C06-4FFA-46C1-9D85-9366B34AB0A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Predictor.Tests", "test\Microsoft.ML.Predictor.Tests\Microsoft.ML.Predictor.Tests.csproj", "{6B047E09-39C9-4583-96F3-685D84CA4117}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Functional.Tests", "test\Microsoft.ML.Functional.Tests\Microsoft.ML.Functional.Tests.csproj", "{CFED9F0C-FF81-4C96-8D5E-0436264CA7B5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ResultProcessor", "src\Microsoft.ML.ResultProcessor\Microsoft.ML.ResultProcessor.csproj", "{3769FCC3-9AFF-4C37-97E9-6854324681DF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.FastTree", "src\Microsoft.ML.FastTree\Microsoft.ML.FastTree.csproj", "{B7B593C5-FB8C-4ADA-A638-5B53B47D087E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Parquet", "src\Microsoft.ML.Parquet\Microsoft.ML.Parquet.csproj", "{16BB1454-2108-40E5-B3A6-594654005303}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper", "src\Microsoft.ML.Sweeper\Microsoft.ML.Sweeper.csproj", "{55C8122D-79EA-48AB-85D0-EB551FC1C427}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks", "test\Microsoft.ML.Benchmarks\Microsoft.ML.Benchmarks.csproj", "{7A9DB75F-2CA5-4184-9EF5-1F17EB39483F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Maml", "src\Microsoft.ML.Maml\Microsoft.ML.Maml.csproj", "{64F40A0D-D4C2-4AA7-8470-E9CC437827E4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Console", "src\Microsoft.ML.Console\Microsoft.ML.Console.csproj", "{362A98CF-FBF7-4EBB-A11B-990BBF845B15}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Sweeper.Tests", "test\Microsoft.ML.Sweeper.Tests\Microsoft.ML.Sweeper.Tests.csproj", "{3DEB504D-7A07-48CE-91A2-8047461CB3D4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.LightGbm", "src\Microsoft.ML.LightGbm\Microsoft.ML.LightGbm.csproj", "{001F3B4E-FBE4-4001-AFD2-A6A989CD1C25}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Ensemble", "src\Microsoft.ML.Ensemble\Microsoft.ML.Ensemble.csproj", "{DCF46B79-1FDB-4DBA-A263-D3D64E3AAA27}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.InternalCodeAnalyzer", "tools-local\Microsoft.ML.InternalCodeAnalyzer\Microsoft.ML.InternalCodeAnalyzer.csproj", "{B4E55B2D-2A92-46E7-B72F-E76D6FD83440}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeAnalyzer.Tests", "test\Microsoft.ML.CodeAnalyzer.Tests\Microsoft.ML.CodeAnalyzer.Tests.csproj", "{3E4ABF07-7970-4BE6-B45B-A13D3C397545}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.PerformanceTests", "test\Microsoft.ML.CpuMath.PerformanceTests\Microsoft.ML.CpuMath.PerformanceTests.csproj", "{7333EDEF-4144-405C-A5EC-6F42201857D8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CpuMath.UnitTests", "test\Microsoft.ML.CpuMath.UnitTests\Microsoft.ML.CpuMath.UnitTests.csproj", "{E97D8B5A-3035-4D41-9B0D-77FF8FB8D132}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Microsoft.ML.FSharp.Tests", "test\Microsoft.ML.FSharp.Tests\Microsoft.ML.FSharp.Tests.fsproj", "{802233D6-8CC0-46AD-9F23-FEE1E9AED9B3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.ImageAnalytics", "src\Microsoft.ML.ImageAnalytics\Microsoft.ML.ImageAnalytics.csproj", "{00E38F77-1E61-4CDF-8F97-1417D4E85053}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Mkl.Components", "src\Microsoft.ML.Mkl.Components\Microsoft.ML.Mkl.Components.csproj", "{A7222F41-1CF0-47D9-B80C-B4D77B027A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TensorFlow", "src\Microsoft.ML.TensorFlow\Microsoft.ML.TensorFlow.csproj", "{570A0B8A-5463-44D2-8521-54C0CA4CACA9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries", "src\Microsoft.ML.TimeSeries\Microsoft.ML.TimeSeries.csproj", "{5A79C7F0-3D99-4123-B0DA-7C9FFCD13132}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformer", "src\Microsoft.ML.OnnxTransformer\Microsoft.ML.OnnxTransformer.csproj", "{8C05642D-C3AA-4972-B02C-93681161A6BC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnAnalyzer", "src\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer\Microsoft.ML.DnnAnalyzer.csproj", "{73DAAC82-D308-48CC-8FFE-3B037F8BBCCA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.OnnxTransformerTest", "test\Microsoft.ML.OnnxTransformerTest\Microsoft.ML.OnnxTransformerTest.csproj", "{49D03292-8AFE-4B82-823C-D047BF8420F7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Benchmarks.Tests", "test\Microsoft.ML.Benchmarks.Tests\Microsoft.ML.Benchmarks.Tests.csproj", "{B6C83F04-A04B-4F00-9E68-1EC411F9317C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TimeSeries.Tests", "test\Microsoft.ML.TimeSeries.Tests\Microsoft.ML.TimeSeries.Tests.csproj", "{4B101D58-E7E4-4877-A536-A9B41E2E82A3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples", "docs\samples\Microsoft.ML.Samples\Microsoft.ML.Samples.csproj", "{ECB71297-9DF1-48CE-B93A-CD969221F9B6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.SamplesUtils", "src\Microsoft.ML.SamplesUtils\Microsoft.ML.SamplesUtils.csproj", "{11A5210E-2EA7-42F1-80DB-827762E9C781}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Recommender", "src\Microsoft.ML.Recommender\Microsoft.ML.Recommender.csproj", "{C8E1772B-DFD9-4A4D-830D-6AAB1C668BB3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet18", "src\Microsoft.ML.DnnImageFeaturizer.ResNet18\Microsoft.ML.DnnImageFeaturizer.ResNet18.csproj", "{9222FC9D-599A-49A5-B685-08CC9A5C81D7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.AlexNet", "src\Microsoft.ML.DnnImageFeaturizer.AlexNet\Microsoft.ML.DnnImageFeaturizer.AlexNet.csproj", "{6C29AA9B-054B-4762-BEA5-D305B932AA80}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet50", "src\Microsoft.ML.DnnImageFeaturizer.ResNet50\Microsoft.ML.DnnImageFeaturizer.ResNet50.csproj", "{4805129D-78C8-46D4-9519-0AD9B0574D6D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DnnImageFeaturizer.ResNet101", "src\Microsoft.ML.DnnImageFeaturizer.ResNet101\Microsoft.ML.DnnImageFeaturizer.ResNet101.csproj", "{DB7CEB5E-8BE6-48A7-87BE-B91D9AE96F71}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.EntryPoints", "src\Microsoft.ML.EntryPoints\Microsoft.ML.EntryPoints.csproj", "{7504D46F-E4B3-43CB-9B1C-82F3131F1C99}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.DataView", "src\Microsoft.ML.DataView\Microsoft.ML.DataView.csproj", "{85D0CAFD-2FE8-496A-88C7-585D35B94243}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteExecutorConsoleApp", "test\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj", "{5E920CAC-5A28-42FB-936E-49C472130953}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Experimental", "src\Microsoft.ML.Experimental\Microsoft.ML.Experimental.csproj", "{E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML", "src\Microsoft.Extensions.ML\Microsoft.Extensions.ML.csproj", "{D6741C37-B5E6-4050-BCBA-9715809EA15B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ML.Tests", "test\Microsoft.Extensions.ML.Tests\Microsoft.Extensions.ML.Tests.csproj", "{21CAD3A1-5E1F-42C1-BB73-46B6E67F4206}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.StableApi", "tools-local\Microsoft.ML.StableApi\Microsoft.ML.StableApi.csproj", "{F308DC6B-7E59-40D7-A581-834E8CD99CFE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Tests", "test\Microsoft.ML.AutoML.Tests\Microsoft.ML.AutoML.Tests.csproj", "{C2652287-CD6D-40FB-B042-95FB56D09DB8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML", "src\Microsoft.ML.AutoML\Microsoft.ML.AutoML.csproj", "{E48285BF-F49A-4EA3-AED0-1BDDBF77EB80}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.AutoML.Samples", "docs\samples\Microsoft.ML.AutoML.Samples\Microsoft.ML.AutoML.Samples.csproj", "{A6924919-9E37-4023-8B7F-E85C8E3CC9B3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Samples.GPU", "docs\samples\Microsoft.ML.Samples.GPU\Microsoft.ML.Samples.GPU.csproj", "{3C8F910B-7F23-4D25-B521-6D5AC9570ADD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Featurizers", "src\Microsoft.ML.Featurizers\Microsoft.ML.Featurizers.csproj", "{E2DD0721-5B0F-4606-8182-4C7EFB834518}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator", "src\Microsoft.ML.CodeGenerator\Microsoft.ML.CodeGenerator.csproj", "{56CB0850-7341-4D71-9AE4-9EFC472D93DD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.CodeGenerator.Tests", "test\Microsoft.ML.CodeGenerator.Tests\Microsoft.ML.CodeGenerator.Tests.csproj", "{46CC5637-3DDF-4100-93FC-44BB87B2DB81}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.Vision", "src\Microsoft.ML.Vision\Microsoft.ML.Vision.csproj", "{419F93D5-4135-4DA0-A76E-EFC23E04093D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.TestFrameworkCommon", "test\Microsoft.ML.TestFrameworkCommon\Microsoft.ML.TestFrameworkCommon.csproj", "{A22FAD27-77E8-4460-8B92-EC7090B7173A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.NightlyBuild.Tests", "test\Microsoft.ML.NightlyBuild.Tests\Microsoft.ML.NightlyBuild.Tests.csproj", "{A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.ML.NugetPackageVersionUpdater", "test\Microsoft.ML.NugetPackageVersionUpdater\Microsoft.ML.NugetPackageVersionUpdater.csproj", "{C8DB58DC-6434-4431-A81F-263D86E2A5F3}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1639,30 +1639,6 @@ Global {3C8F910B-7F23-4D25-B521-6D5AC9570ADD}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU {3C8F910B-7F23-4D25-B521-6D5AC9570ADD}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU {3C8F910B-7F23-4D25-B521-6D5AC9570ADD}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.ActiveCfg = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.Build.0 = Debug|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.Build.0 = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.ActiveCfg = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.Build.0 = Release|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU - {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU {56CB0850-7341-4D71-9AE4-9EFC472D93DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56CB0850-7341-4D71-9AE4-9EFC472D93DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {56CB0850-7341-4D71-9AE4-9EFC472D93DD}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1760,87 +1736,70 @@ Global {A22FAD27-77E8-4460-8B92-EC7090B7173A}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU {A22FAD27-77E8-4460-8B92-EC7090B7173A}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|x64.ActiveCfg = Debug|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug|x64.Build.0 = Debug|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|Any CPU.Build.0 = Release|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|x64.ActiveCfg = Release|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release|x64.Build.0 = Release|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU - {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|x64.ActiveCfg = Debug|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug|x64.Build.0 = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.Build.0 = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.ActiveCfg = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.Build.0 = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.ActiveCfg = Debug|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.Build.0 = Debug|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|Any CPU.Build.0 = Release|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|x64.ActiveCfg = Release|Any CPU {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release|x64.Build.0 = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.Build.0 = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.ActiveCfg = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.Build.0 = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.ActiveCfg = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.Build.0 = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.ActiveCfg = Release|Any CPU - {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.Build.0 = Release|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU + {C8DB58DC-6434-4431-A81F-263D86E2A5F3}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.ActiveCfg = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug|x64.Build.0 = Debug|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|Any CPU.Build.0 = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.ActiveCfg = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netcoreapp3_1|x64.Build.0 = Debug-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.ActiveCfg = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|Any CPU.Build.0 = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.ActiveCfg = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Debug-netfx|x64.Build.0 = Debug-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|Any CPU.Build.0 = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.ActiveCfg = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release|x64.Build.0 = Release|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.ActiveCfg = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|Any CPU.Build.0 = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.ActiveCfg = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netcoreapp3_1|x64.Build.0 = Release-netcoreapp3_1|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.ActiveCfg = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|Any CPU.Build.0 = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.ActiveCfg = Release-netfx|Any CPU + {E2DD0721-5B0F-4606-8182-4C7EFB834518}.Release-netfx|x64.Build.0 = Release-netfx|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {DEC8F776-49F7-4D87-836C-FE4DC057D08C} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {6C95FC87-F5F2-4EEF-BB97-567F2F5DD141} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} = {DEC8F776-49F7-4D87-836C-FE4DC057D08C} - {9252A8EB-ABFB-440C-AB4D-1D562753CE0F} = {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} - {BF66A305-DF10-47E4-8D81-42049B149D2B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {63006A14-B924-48C5-83C9-CFE9DA22B01F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {1229F799-37F0-4282-B9F0-74BFA97CC362} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {DE95FE65-9FF7-4233-93DF-7A8F2805624A} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {4CF8095E-B4A3-4326-A550-43098E447288} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {19AC192B-75FE-45D5-B219-898E401D5904} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {93FF16AA-635E-421D-96C1-008818C143A2} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {320AF46A-4809-486E-8F9E-A00C8AE47751} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {11894B4A-78B4-4523-A6DD-4495722E244F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {B836F712-7FB6-4B75-A3EB-FB05F8E0D15E} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {B00098E4-771E-41DF-A3AA-A606AAB334B7} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {BD93C0F3-3CED-4BE8-9389-4234250FBFB1} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {8EDFB7E5-7E7E-411D-99C5-7A4895D0F9CB} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {9E689AD4-F908-493C-B882-B1B33E8F7696} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {8D8CC016-0020-40EC-BD8E-73F1CE0F9662} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {A84717CB-F11A-41C5-A74D-C0F1D47B7431} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {31D38B21-102B-41C0-9E0A-2FE0BF68D123} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {AD7058C9-5608-49A8-BE23-58C33A74EE91} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {B1B3F284-FA3D-4D76-A712-FF04495D244B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {AE4F7569-26F3-4160-8A8B-7A57D0DA3350} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {F5D11F71-2D61-4AE9-99D7-0F0B54649B15} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {1BA5C784-52E8-4A87-8525-26B2452F2882} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {3817A875-278C-4140-BF66-3C4A8CA55F0D} = {D3D38B03-B557-484D-8348-8BADEE4DF592} - {C91F81E3-B900-4968-A6DF-F53B515E97E1} = {BF66A305-DF10-47E4-8D81-42049B149D2B} - {027DBA48-85B6-46F1-9487-0B49B5057FC0} = {C91F81E3-B900-4968-A6DF-F53B515E97E1} {A6CA6CC6-5D7C-4D7F-A0F5-35E14B383B0A} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {EC743D1D-7691-43B7-B9B0-5F2F7018A8F6} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {46F2F967-C23F-4076-858D-33F7DA9BD2DA} = {09EADF06-BE25-4228-AB53-95AE3E15B530} @@ -1858,12 +1817,17 @@ Global {B7B593C5-FB8C-4ADA-A638-5B53B47D087E} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {16BB1454-2108-40E5-B3A6-594654005303} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {55C8122D-79EA-48AB-85D0-EB551FC1C427} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {DEC8F776-49F7-4D87-836C-FE4DC057D08C} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {6C95FC87-F5F2-4EEF-BB97-567F2F5DD141} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {7A9DB75F-2CA5-4184-9EF5-1F17EB39483F} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {64F40A0D-D4C2-4AA7-8470-E9CC437827E4} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {362A98CF-FBF7-4EBB-A11B-990BBF845B15} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} = {DEC8F776-49F7-4D87-836C-FE4DC057D08C} + {9252A8EB-ABFB-440C-AB4D-1D562753CE0F} = {487213C9-E8A9-4F94-85D7-28A05DBBFE3A} {3DEB504D-7A07-48CE-91A2-8047461CB3D4} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {001F3B4E-FBE4-4001-AFD2-A6A989CD1C25} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {DCF46B79-1FDB-4DBA-A263-D3D64E3AAA27} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {BF66A305-DF10-47E4-8D81-42049B149D2B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {B4E55B2D-2A92-46E7-B72F-E76D6FD83440} = {7F13E156-3EBA-4021-84A5-CD56BA72F99E} {3E4ABF07-7970-4BE6-B45B-A13D3C397545} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {7333EDEF-4144-405C-A5EC-6F42201857D8} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} @@ -1886,23 +1850,47 @@ Global {4805129D-78C8-46D4-9519-0AD9B0574D6D} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {DB7CEB5E-8BE6-48A7-87BE-B91D9AE96F71} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {7504D46F-E4B3-43CB-9B1C-82F3131F1C99} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {63006A14-B924-48C5-83C9-CFE9DA22B01F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {1229F799-37F0-4282-B9F0-74BFA97CC362} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {DE95FE65-9FF7-4233-93DF-7A8F2805624A} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {4CF8095E-B4A3-4326-A550-43098E447288} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {19AC192B-75FE-45D5-B219-898E401D5904} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {93FF16AA-635E-421D-96C1-008818C143A2} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {320AF46A-4809-486E-8F9E-A00C8AE47751} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {11894B4A-78B4-4523-A6DD-4495722E244F} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {B836F712-7FB6-4B75-A3EB-FB05F8E0D15E} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {B00098E4-771E-41DF-A3AA-A606AAB334B7} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {BD93C0F3-3CED-4BE8-9389-4234250FBFB1} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {8EDFB7E5-7E7E-411D-99C5-7A4895D0F9CB} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {9E689AD4-F908-493C-B882-B1B33E8F7696} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {8D8CC016-0020-40EC-BD8E-73F1CE0F9662} = {D3D38B03-B557-484D-8348-8BADEE4DF592} + {A84717CB-F11A-41C5-A74D-C0F1D47B7431} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {85D0CAFD-2FE8-496A-88C7-585D35B94243} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {31D38B21-102B-41C0-9E0A-2FE0BF68D123} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {5E920CAC-5A28-42FB-936E-49C472130953} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} + {AD7058C9-5608-49A8-BE23-58C33A74EE91} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {E02DA82D-3FEE-4C60-BD80-9EC3C3448DFC} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {B1B3F284-FA3D-4D76-A712-FF04495D244B} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {D6741C37-B5E6-4050-BCBA-9715809EA15B} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {21CAD3A1-5E1F-42C1-BB73-46B6E67F4206} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} + {AE4F7569-26F3-4160-8A8B-7A57D0DA3350} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {F308DC6B-7E59-40D7-A581-834E8CD99CFE} = {7F13E156-3EBA-4021-84A5-CD56BA72F99E} {C2652287-CD6D-40FB-B042-95FB56D09DB8} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {E48285BF-F49A-4EA3-AED0-1BDDBF77EB80} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {F5D11F71-2D61-4AE9-99D7-0F0B54649B15} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {A6924919-9E37-4023-8B7F-E85C8E3CC9B3} = {DA452A53-2E94-4433-B08C-041EDEC729E6} {3C8F910B-7F23-4D25-B521-6D5AC9570ADD} = {DA452A53-2E94-4433-B08C-041EDEC729E6} - {E2DD0721-5B0F-4606-8182-4C7EFB834518} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {56CB0850-7341-4D71-9AE4-9EFC472D93DD} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {46CC5637-3DDF-4100-93FC-44BB87B2DB81} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} + {3817A875-278C-4140-BF66-3C4A8CA55F0D} = {D3D38B03-B557-484D-8348-8BADEE4DF592} {419F93D5-4135-4DA0-A76E-EFC23E04093D} = {09EADF06-BE25-4228-AB53-95AE3E15B530} {A22FAD27-77E8-4460-8B92-EC7090B7173A} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {A1CAC86F-F4BB-4B6D-9D18-E9AE15B3C66E} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} {C8DB58DC-6434-4431-A81F-263D86E2A5F3} = {AED9C836-31E3-4F3F-8ABC-929555D3F3C4} + {C91F81E3-B900-4968-A6DF-F53B515E97E1} = {BF66A305-DF10-47E4-8D81-42049B149D2B} + {027DBA48-85B6-46F1-9487-0B49B5057FC0} = {C91F81E3-B900-4968-A6DF-F53B515E97E1} + {E2DD0721-5B0F-4606-8182-4C7EFB834518} = {09EADF06-BE25-4228-AB53-95AE3E15B530} + {1BA5C784-52E8-4A87-8525-26B2452F2882} = {D3D38B03-B557-484D-8348-8BADEE4DF592} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {41165AF1-35BB-4832-A189-73060F82B01D} diff --git a/src/Microsoft.ML.TimeSeries/TrendInfo.cs b/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/TrendInfo.cs rename to src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs From 8778e5adc7df141f63b5bb2ae5d52c09b36c93d2 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Fri, 5 Jun 2020 10:53:16 +0800 Subject: [PATCH 16/49] update code style --- .../SrCnnEntireAnomalyDetector.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 24cb8dc97a..e5c857fd2f 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -320,6 +320,7 @@ internal sealed class SrCnnEntireModeler private readonly double _threshold; private readonly double _sensitivity; private readonly SrCnnDetectMode _detectMode; + private readonly int _period; //used in all modes private readonly double[] _predictArray; @@ -337,6 +338,7 @@ internal sealed class SrCnnEntireModeler private double[] _cumSumList; private double[] _cumSumShift; private double[] _zeroArray; + private double[] _seriesToDetect; //used in AnomalyAndExpectedValue and AnomalyAndMargin private double[] _deAnomalyData; //used in AnomalyAndMargin mode @@ -345,8 +347,6 @@ internal sealed class SrCnnEntireModeler private double[] _trends; private double[] _curWindow; - private int _period; - public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) { _threshold = threshold; @@ -372,7 +372,11 @@ public void Train(double[] values, ref double[][] results) } bool isTemporal = true; - double[] seriesToDetect = values.ToArray(); + AllocateDoubleArray(ref _seriesToDetect, values.Length); + for (int i = 0; i < values.Length; ++i) + { + _seriesToDetect[i] = values[i]; + } if (_period > 0) { @@ -382,17 +386,17 @@ public void Train(double[] values, ref double[][] results) if (success) { - seriesToDetect = stl.Residual.ToArray(); + _seriesToDetect = stl.Residual.ToArray(); } } - SpectralResidual(seriesToDetect, results, _threshold); + SpectralResidual(_seriesToDetect, results, _threshold); //Optional Steps if (_detectMode == SrCnnDetectMode.AnomalyAndMargin) { if (_period > 0) { - GetMarginPeriod(values, results, seriesToDetect, _sensitivity); + GetMarginPeriod(values, results, _seriesToDetect, _sensitivity); } else { @@ -403,7 +407,7 @@ public void Train(double[] values, ref double[][] results) { if (_period > 0) { - GetExpectedValuePeriod(values, results, seriesToDetect); + GetExpectedValuePeriod(values, results, _seriesToDetect); } else { From 219decdf34a9ee2c623d4fe9f328d433f930c75e Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Fri, 5 Jun 2020 11:01:46 +0800 Subject: [PATCH 17/49] fix members initialization outside the constructor --- src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs | 4 +++- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index 8e8a485439..03d7b8fa8a 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -18,7 +18,7 @@ internal class LocalRegression /// /// the model is learned by several iterations of local weighted regression. /// - private PolynomialModel _model = null; + private PolynomialModel _model; /// /// Initializes a new instance of the class. @@ -38,6 +38,8 @@ internal LocalRegression(IReadOnlyList x, IReadOnlyList y, int s if (x.Count <= 1 || x.Count != y.Count) throw new Exception("cannot accomplish neighbors obtaining"); + _model = null; + _x = x; _y = y; _length = _x.Count; diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 104b81eb30..3c28f82dd0 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -24,7 +24,7 @@ public class Loess /// /// key is the index of the given point, value is the corresponding neighbors of the given point. /// - private readonly Dictionary _neighbors = new Dictionary(); + private Dictionary _neighbors; private IReadOnlyList _x; private IReadOnlyList _y; @@ -48,6 +48,8 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool if (xValues.Count != yValues.Count) throw new Exception("the x-axis length should be equal to y-axis length!: lowess"); + _neighbors = new Dictionary(); + _length = xValues.Count; _isTemporal = isTemporal; From e039cba797d758367a05cbef45d5b839725b529f Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Fri, 5 Jun 2020 11:46:42 +0800 Subject: [PATCH 18/49] remove unused using --- src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index e5c857fd2f..5bf1ce5cdb 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; using Microsoft.ML.Runtime; From 3553277c3b64a08cbeb477ce865d7e4b634147ea Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 9 Jun 2020 18:22:03 +0800 Subject: [PATCH 19/49] refactor InnerStl --- .../STL/InnerStl_new.cs | 353 ++++++++++++++++++ src/Microsoft.ML.TimeSeries/STL/Loess.cs | 2 + .../SrCnnEntireAnomalyDetector.cs | 9 +- 3 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs new file mode 100644 index 0000000000..f8d7154f6e --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.TimeSeries +{ + internal class InnerStlNew + { + private readonly bool _isTemporal; + private double[] _seasonalComponent; + private double[] _trendComponent; + private double[] _residual; + + private int _length; + + // arrays for intermediate results + private List[] _cycleSubSeries; + private List[] _smoothedSubseries; + + private double[] _s; + private double[] _t; + private double[] _detrendedY; + private double[] _c; + private double[] _deseasonSeries; + + /// + /// the smoothing parameter for the seasonal component. + /// should be odd, and at least 7. + /// + private const int Ns = 9; + + /// + /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases + /// + private const int Ni = 2; + + /// + /// the number of robustness iterations of the outer loop + /// + private const int No = 10; + + /// + /// the smoothing parameter for the low-pass filter. + /// /ref: should be the least odd integer greater than or equal to np. + /// it will preventing the trend and seasonal components from competing for the same variation in the data. + /// + private int Nl(int np) + { + if (np % 2 == 0) + return np + 1; + return np; + } + + /// + /// the smoothing parameter for the trend component. + /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen + /// s.t., satisty the following inequality. + /// + private int Nt(int np) + { + double value = 1.5 * np / (1.0 - 1.5 / Ns); + int result = (int)value + 1; + if (result % 2 == 0) + result++; + return result; + } + + /// + /// Initializes a new instance of the class. + /// for a time series, only with y values. assume the x-values are 0, 1, 2, ... + /// since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. + /// otherwise, the smoothing on seasonal component will be very complicated. + /// + /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + public InnerStlNew(bool isTemporal) + { + _isTemporal = isTemporal; + } + + /// + /// the seasonal component + /// + public IReadOnlyList SeasonalComponent + { + get { return _seasonalComponent; } + } + + /// + /// the trend component + /// + public IReadOnlyList TrendComponent + { + get { return _trendComponent; } + } + + /// + /// the left component after seasonal and trend are eliminated. + /// + public IReadOnlyList Residual + { + get { return _residual; } + } + + /// + /// the core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, + /// see section 2 and 3. especially section 2. + /// + /// return true if the process goes successfully. otherwise, return false. + public bool Decomposition(IReadOnlyList yValues, int np) + { + Contracts.CheckValue(yValues, nameof(yValues)); + Contracts.CheckParam(np > 0, nameof(np)); + + if (yValues.Count == 0) + throw new Exception("input data structure cannot be 0-length: innerSTL"); + + _length = yValues.Count; + AllocateDoubleArray(ref _seasonalComponent, _length); + AllocateDoubleArray(ref _trendComponent, _length); + AllocateDoubleArray(ref _residual, _length); + + AllocateDoubleArray(ref _s, _length); + AllocateDoubleArray(ref _t, _length); + AllocateDoubleArray(ref _detrendedY, _length); + AllocateDoubleArray(ref _c, _length + np * 2); + AllocateDoubleArray(ref _deseasonSeries, _length); + + AllocateListDoubleArray(ref _cycleSubSeries, np); + AllocateListDoubleArray(ref _smoothedSubseries, np); + + for (int i = 0; i < _length; ++i) + { + _t[i] = 0; + } + + for (int iter = 0; iter < Ni; iter++) + { + // step1: detrending + Detrending(yValues, _t, _detrendedY); + + // step2: cycle-subseries smoothing + bool success = CycleSubseriesSmooth(_detrendedY, np, _c); + if (!success) + { + return false; + } + + // step3: low-pass filtering of smoothed cycle-subseries + var lowPass = LowPassFiltering(_c, np); + + // step4: detrending of smoothed cycle-subseries + SmoothedCycleSubseriesDetrending(_c, lowPass, _s); + + // step5: deseasonalizing + Deseasonalizing(yValues, _s, _deseasonSeries); + + // step6: trend smoothing + TrendSmooth(_deseasonSeries, np, _t); + } + + for (int i = 0; i < _s.Length; i++) + { + _seasonalComponent[i] = _s[i]; + _trendComponent[i] = _t[i]; + } + + return true; + } + + private void Detrending(IReadOnlyList y, IReadOnlyList t, double[] detrendedY) + { + for (int i = 0; i < _length; i++) + detrendedY[i] = y[i] - t[i]; + } + + private bool CycleSubseriesSmooth(double[] detrendedY, int np, double[] c) + { + for (int i = 0; i < np; i++) + { + _cycleSubSeries[i] = new List(); + _smoothedSubseries[i] = new List(); + } + + // obtain all the subseries + for (int i = 0; i < _length; i++) + { + int cycleIndex = i % np; + _cycleSubSeries[cycleIndex].Add(detrendedY[i]); + } + + // smoothing on each subseries + for (int i = 0; i < _cycleSubSeries.Length; i++) + { + List virtualXValues = VirtualXValuesProvider.GetXValues(_cycleSubSeries[i].Count); + + FastLoess model = new FastLoess(virtualXValues, _cycleSubSeries[i], _isTemporal, Ns); + model.Estimate(); + + // add a prior point + _smoothedSubseries[i].Add(model.EstimateY(-1.0)); + _smoothedSubseries[i].AddRange(model.Y); + + // add a after point + _smoothedSubseries[i].Add(model.EstimateY(_cycleSubSeries[i].Count * 1.0)); + } + + // c is the smoothed series, with _length + 2Np points. + int index = 0; + for (int i = 0; i < _smoothedSubseries[0].Count; i++) + { + for (int j = 0; j < _smoothedSubseries.Length; j++) + { + if (_smoothedSubseries[j].Count <= i) + break; + if (_smoothedSubseries[j][i].Equals(double.NaN)) + { + return false; + } + c[index] = (_smoothedSubseries[j][i]); + ++index; + } + } + + return true; + } + + private FastLoess LowPassFiltering(double[] c, int np) + { + List c1 = MovingAverage(c, np); + List c2 = MovingAverage(c1, np); + List c3 = MovingAverage(c2, 3); + List virtualC3XValues = VirtualXValuesProvider.GetXValues(c3.Count); + FastLoess lowPass = new FastLoess(virtualC3XValues, c3, _isTemporal, Nl(np)); + lowPass.Estimate(); + + return lowPass; + } + + private void SmoothedCycleSubseriesDetrending(double[] c, FastLoess lowPass, double[] s) + { + for (int i = 0; i < _length; i++) + { + s[i] = c[i] - lowPass.Y[i]; + } + } + + private void Deseasonalizing(IReadOnlyList y, double[] s, double[] deseasonSeries) + { + for (int i = 0; i < _length; i++) + { + deseasonSeries[i] = y[i] - s[i]; + } + } + + private void TrendSmooth(double[] deseasonSeries, int np, double[] t) + { + List virtualDeseasonSeries = VirtualXValuesProvider.GetXValues(deseasonSeries.Length); + FastLoess trender = new FastLoess(virtualDeseasonSeries, deseasonSeries, _isTemporal, Nt(np)); + trender.Estimate(); + for (int i = 0; i < _length; i++) + { + t[i] = trender.Y[i]; + } + } + + /// + /// this class provides the virtual x values for multi object usage. + /// the cache mechanism is used for performance consideration. + /// + internal class VirtualXValuesProvider + { + private static Dictionary> _xValuesPool; + + static VirtualXValuesProvider() + { + _xValuesPool = new Dictionary>(); + } + + /// + /// get a list of virtual x-axis values. the values are from 0 to length - 1. + /// + /// specify the length you want to create the x values. + /// if this is cached, return directly. otherwise, create a new list and return + internal static List GetXValues(int length) + { + lock (_xValuesPool) + { + List xValues; + if (_xValuesPool.TryGetValue(length, out xValues)) + return xValues; + + var newXValues = new List(length); + for (int i = 0; i < length; i++) + newXValues.Add(i); + + _xValuesPool.Add(length, newXValues); + return newXValues; + } + } + } + + private static List MovingAverage(IReadOnlyList s, int length) + { + List results = new List(s.Count); + double partialSum = 0; + for (int i = 0; i < length; ++i) + { + partialSum += s[i]; + } + + for (int i = length; i < s.Count; ++i) + { + results.Add(partialSum / length); + partialSum = partialSum - s[i - length] + s[i]; + } + results.Add(partialSum / length); + + return results; + } + + private void AllocateDoubleArray(ref double[] arr, int length) + { + if (arr == null) + { + arr = new double[length]; + } + else if (arr.Length != length) + { + Array.Resize(ref arr, length); + } + } + + private void AllocateListDoubleArray(ref List[] arr, int length) + { + if (arr == null) + { + arr = new List[length]; + } + else if (arr.Length != length) + { + Array.Resize>(ref arr, length); + } + + for (int i = 0; i < length; ++i) + { + arr[i] = new List(); + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 3c28f82dd0..218b04ac5f 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -89,6 +89,8 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, int r if (xValues.Count != yValues.Count) throw new Exception("the x-axis length should be equal to y-axis length!: lowess"); + _neighbors = new Dictionary(); + _length = xValues.Count; _isTemporal = isTemporal; diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 5bf1ce5cdb..cbd087f4c4 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; @@ -380,11 +381,17 @@ public void Train(double[] values, ref double[][] results) { StlConfiguration config = new StlConfiguration(_period); InnerStl stl = new InnerStl(values, config, isTemporal); + InnerStlNew stlNew = new InnerStlNew(isTemporal); bool success = stl.Decomposition(); - + stlNew.Decomposition(values, _period); if (success) { _seriesToDetect = stl.Residual.ToArray(); + var residual2 = stl.Residual.ToArray(); + for (int i = 0; i<_seriesToDetect.Length; ++i) + { + Trace.Assert(_seriesToDetect[i] == residual2[i]); + } } } From 8c693c9369471715657acc79c618ca3e22017206 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 9 Jun 2020 20:15:34 +0800 Subject: [PATCH 20/49] use contract exception --- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs | 9 +++++---- src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 8 ++++---- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index e2adc3dadc..5d997b4391 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -38,7 +38,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b Y = new List(); if (yValues.Count < BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: lowess"); + throw Contracts.Except("input data structure cannot be 0-length: lowess"); _x = xValues; _y = yValues; diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index cf8add650c..1e6aadcb28 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -102,7 +102,7 @@ public InnerStl(IReadOnlyList yValues, StlConfiguration config, bool isT Contracts.CheckValue(config, nameof(config)); if (yValues.Count == 0) - throw new Exception("input data structure cannot be 0-length: innerSTL"); + throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); _y = yValues; _length = _y.Count; diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs index f8d7154f6e..5ecf8fb187 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs @@ -115,7 +115,7 @@ public bool Decomposition(IReadOnlyList yValues, int np) Contracts.CheckParam(np > 0, nameof(np)); if (yValues.Count == 0) - throw new Exception("input data structure cannot be 0-length: innerSTL"); + throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); _length = yValues.Count; AllocateDoubleArray(ref _seasonalComponent, _length); diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index d55fb9e6ce..adb8e71715 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -28,9 +28,9 @@ public LeastSquares(List x, List y) Contracts.CheckValue(y, nameof(y)); if (x.Count == 0 || y.Count == 0) - throw new Exception("input data structure cannot be 0-length"); + throw Contracts.Except("input data structure cannot be 0-length"); if (x.Count != y.Count) - throw new Exception("the x-axis length should be equal to y-axis length!"); + throw Contracts.Except("the x-axis length should be equal to y-axis length!"); _x = x; _y = y; _length = _x.Count; @@ -44,13 +44,14 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) { Contracts.CheckValue(weights, nameof(weights)); + Contracts.CheckParam(weights.Count == _length, nameof(weights)); if (weights.Count != _length) - throw new Exception("the weight vector is not equal length to the data points"); + throw Contracts.Except("the weight vector is not equal length to the data points"); foreach (double value in weights) { if (value < 0) - throw new Exception("the value in weights should be non-negative!"); + throw Contracts.Except("the value in weights should be non-negative!"); } // This part unfold the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W), sqrt(W) .* X] diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index 03d7b8fa8a..8d39efda0b 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -36,7 +36,7 @@ internal LocalRegression(IReadOnlyList x, IReadOnlyList y, int s Contracts.CheckValue(y, nameof(y)); if (x.Count <= 1 || x.Count != y.Count) - throw new Exception("cannot accomplish neighbors obtaining"); + throw Contracts.Except("cannot accomplish neighbors obtaining"); _model = null; diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 218b04ac5f..1467aaba6e 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -43,10 +43,10 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool Contracts.CheckValue(yValues, nameof(yValues)); if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: lowess"); + throw Contracts.Except("input data structure cannot be 0-length: lowess"); if (xValues.Count != yValues.Count) - throw new Exception("the x-axis length should be equal to y-axis length!: lowess"); + throw Contracts.Except("the x-axis length should be equal to y-axis length!: lowess"); _neighbors = new Dictionary(); @@ -84,10 +84,10 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, int r Contracts.CheckValue(yValues, nameof(yValues)); if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) - throw new Exception("input data structure cannot be 0-length: lowess"); + throw Contracts.Except("input data structure cannot be 0-length: lowess"); if (xValues.Count != yValues.Count) - throw new Exception("the x-axis length should be equal to y-axis length!: lowess"); + throw Contracts.Except("the x-axis length should be equal to y-axis length!: lowess"); _neighbors = new Dictionary(); From 37e7c6fd77b63a3fea9ad6a1561899a4a6a9c681 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 9 Jun 2020 20:34:10 +0800 Subject: [PATCH 21/49] remove unused class --- .../STL/BackendStructureBase.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs b/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs index 773ff9e601..23df126189 100644 --- a/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs +++ b/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs @@ -86,26 +86,4 @@ public abstract class SingleSeriesInfo : TimeSeriesInfoBase /// public IReadOnlyList Y { get; protected set; } } - - /// - /// the characteristic between two time series, such as the lead/lag correlation, - /// and the correlation of the outliers. - /// - public abstract class TwoSeriesInfo : TimeSeriesInfoBase - { - /// - /// x-axis values of two time series. they should be aligned. - /// - public IReadOnlyList X { get; protected set; } - - /// - /// y-axis values of first series - /// - public IReadOnlyList Y1 { get; protected set; } - - /// - /// y-axis values of second series - /// - public IReadOnlyList Y2 { get; protected set; } - } } From 65fb7a57df249d7a0d0d01e1ae07efdac21e3cc6 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 9 Jun 2020 20:44:20 +0800 Subject: [PATCH 22/49] update stl --- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 345 +++++++++-------- .../STL/InnerStl_new.cs | 353 ------------------ .../SrCnnEntireAnomalyDetector.cs | 16 +- 3 files changed, 192 insertions(+), 522 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 1e6aadcb28..1603f9f205 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -7,52 +7,51 @@ namespace Microsoft.ML.TimeSeries { - internal class StlConfiguration + internal class InnerStl { + private readonly bool _isTemporal; + private double[] _seasonalComponent; + private double[] _trendComponent; + private double[] _residual; + + private int _length; + + // arrays for intermediate results + private List[] _cycleSubSeries; + private List[] _smoothedSubseries; + + private double[] _s; + private double[] _t; + private double[] _detrendedY; + private double[] _c; + private double[] _deseasonSeries; + /// /// the smoothing parameter for the seasonal component. /// should be odd, and at least 7. /// - public const int Ns = 9; + private const int Ns = 9; /// /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases /// - public const int Ni = 2; + private const int Ni = 2; /// /// the number of robustness iterations of the outer loop /// - public const int No = 10; - - public StlConfiguration() - { - Np = -1; - } - - public StlConfiguration(int np) - { - Np = np; - } - - /// - /// the number of observations in each cycle of the seasonal component - /// - public int Np { get; } + private const int No = 10; /// /// the smoothing parameter for the low-pass filter. /// /ref: should be the least odd integer greater than or equal to np. /// it will preventing the trend and seasonal components from competing for the same variation in the data. /// - public int Nl + private int Nl(int np) { - get - { - if (Np % 2 == 0) - return Np + 1; - return Np; - } + if (np % 2 == 0) + return np + 1; + return np; } /// @@ -60,32 +59,14 @@ public int Nl /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen /// s.t., satisty the following inequality. /// - public int Nt + private int Nt(int np) { - get - { - double value = 1.5 * Np / (1.0 - 1.5 / StlConfiguration.Ns); - int result = (int)value + 1; - if (result % 2 == 0) - result++; - return result; - } + double value = 1.5 * np / (1.0 - 1.5 / Ns); + int result = (int)value + 1; + if (result % 2 == 0) + result++; + return result; } - } - - internal class InnerStl - { - private readonly IReadOnlyList _x; - private readonly IReadOnlyList _y; - private readonly int _length; - private readonly bool _isTemporal; - private readonly StlConfiguration _config; - - private readonly double[] _seasonalComponent; - private readonly double[] _trendComponent; - private readonly double[] _residual; - private readonly int[] _outlierIndexes; - private readonly double[] _outlierSeverity; /// /// Initializes a new instance of the class. @@ -93,28 +74,10 @@ internal class InnerStl /// since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. /// otherwise, the smoothing on seasonal component will be very complicated. /// - /// the y-axis values - /// the configuration for applying regression /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - public InnerStl(IReadOnlyList yValues, StlConfiguration config, bool isTemporal) + public InnerStl(bool isTemporal) { - Contracts.CheckValue(yValues, nameof(yValues)); - Contracts.CheckValue(config, nameof(config)); - - if (yValues.Count == 0) - throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); - - _y = yValues; - _length = _y.Count; _isTemporal = isTemporal; - _x = VirtualXValuesProvider.GetXValues(_length); - _config = config; - - _seasonalComponent = new double[_length]; - _trendComponent = new double[_length]; - _residual = new double[_length]; - _outlierIndexes = new int[_length]; - _outlierSeverity = new double[_length]; } /// @@ -141,129 +104,168 @@ public IReadOnlyList Residual get { return _residual; } } - /// - /// calculate the slope of the trend component - /// - public double Slope - { - get; - private set; - } - /// /// the core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, /// see section 2 and 3. especially section 2. /// /// return true if the process goes successfully. otherwise, return false. - public bool Decomposition() + public bool Decomposition(IReadOnlyList yValues, int np) { - double[] s = new double[_length]; - double[] t = new double[_length]; - for (int iter = 0; iter < StlConfiguration.Ni; iter++) - { - // step1: detrending - double[] detrendedY = new double[_length]; - for (int i = 0; i < _length; i++) - detrendedY[i] = _y[i] - t[i]; + Contracts.CheckValue(yValues, nameof(yValues)); + Contracts.CheckParam(np > 0, nameof(np)); - // step2: cycle-subseries smoothing - List[] cycleSubSeries = new List[_config.Np]; - List[] smoothedSubseries = new List[_config.Np]; - for (int i = 0; i < _config.Np; i++) - { - cycleSubSeries[i] = new List(); - smoothedSubseries[i] = new List(); - } + if (yValues.Count == 0) + throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); - // obtain all the subseries - for (int i = 0; i < _length; i++) - { - int cycleIndex = i % _config.Np; - cycleSubSeries[cycleIndex].Add(detrendedY[i]); - } + _length = yValues.Count; + AllocateDoubleArray(ref _seasonalComponent, _length); + AllocateDoubleArray(ref _trendComponent, _length); + AllocateDoubleArray(ref _residual, _length); - // smoothing on each subseries - for (int i = 0; i < cycleSubSeries.Length; i++) - { - List virtualXValues = VirtualXValuesProvider.GetXValues(cycleSubSeries[i].Count); + AllocateDoubleArray(ref _s, _length); + AllocateDoubleArray(ref _t, _length); + AllocateDoubleArray(ref _detrendedY, _length); + AllocateDoubleArray(ref _c, _length + np * 2); + AllocateDoubleArray(ref _deseasonSeries, _length); - FastLoess model = new FastLoess(virtualXValues, cycleSubSeries[i], _isTemporal, StlConfiguration.Ns); - model.Estimate(); + AllocateListDoubleArray(ref _cycleSubSeries, np); + AllocateListDoubleArray(ref _smoothedSubseries, np); - // add a prior point - smoothedSubseries[i].Add(model.EstimateY(-1.0)); - smoothedSubseries[i].AddRange(model.Y); + for (int i = 0; i < _length; ++i) + { + _t[i] = 0; + } - // add a after point - smoothedSubseries[i].Add(model.EstimateY(cycleSubSeries[i].Count * 1.0)); - } + for (int iter = 0; iter < Ni; iter++) + { + // step1: detrending + Detrending(yValues, _t, _detrendedY); - // c is the smoothed series, with _length+2Np points. - List c = new List(); - for (int i = 0; i < smoothedSubseries[0].Count; i++) + // step2: cycle-subseries smoothing + bool success = CycleSubseriesSmooth(_detrendedY, np, _c); + if (!success) { - for (int j = 0; j < smoothedSubseries.Length; j++) - { - if (smoothedSubseries[j].Count <= i) - break; - if (smoothedSubseries[j][i].Equals(double.NaN)) - { - return false; - } - c.Add(smoothedSubseries[j][i]); - } + return false; } // step3: low-pass filtering of smoothed cycle-subseries - List c1 = MovingAverage(c, _config.Np); - List c2 = MovingAverage(c1, _config.Np); - List c3 = MovingAverage(c2, 3); - List virtualC3XValues = VirtualXValuesProvider.GetXValues(c3.Count); - FastLoess lowPass = new FastLoess(virtualC3XValues, c3, _isTemporal, _config.Nl); - lowPass.Estimate(); + var lowPass = LowPassFiltering(_c, np); // step4: detrending of smoothed cycle-subseries - for (int i = 0; i < _length; i++) - { - s[i] = c[i] - lowPass.Y[i]; - } + SmoothedCycleSubseriesDetrending(_c, lowPass, _s); // step5: deseasonalizing - List deseasonSeries = new List(); - for (int i = 0; i < _length; i++) - { - deseasonSeries.Add(_y[i] - s[i]); - } + Deseasonalizing(yValues, _s, _deseasonSeries); // step6: trend smoothing - List virtualDeseasonSeries = VirtualXValuesProvider.GetXValues(deseasonSeries.Count); - FastLoess trender = new FastLoess(virtualDeseasonSeries, deseasonSeries, _isTemporal, _config.Nt); - trender.Estimate(); - for (int i = 0; i < _length; i++) - { - t[i] = trender.Y[i]; - } + TrendSmooth(_deseasonSeries, np, _t); } - for (int i = 0; i < s.Length; i++) + for (int i = 0; i < _s.Length; i++) { - _seasonalComponent[i] = s[i]; - _trendComponent[i] = t[i]; + _seasonalComponent[i] = _s[i]; + _trendComponent[i] = _t[i]; } - // the slope is still based on the regression models. - Slope = (_trendComponent[_length - 1] - _seasonalComponent[0]) / (_length - 1); + return true; + } + + private void Detrending(IReadOnlyList y, IReadOnlyList t, double[] detrendedY) + { + for (int i = 0; i < _length; i++) + detrendedY[i] = y[i] - t[i]; + } + + private bool CycleSubseriesSmooth(double[] detrendedY, int np, double[] c) + { + for (int i = 0; i < np; i++) + { + _cycleSubSeries[i] = new List(); + _smoothedSubseries[i] = new List(); + } + + // obtain all the subseries + for (int i = 0; i < _length; i++) + { + int cycleIndex = i % np; + _cycleSubSeries[cycleIndex].Add(detrendedY[i]); + } + + // smoothing on each subseries + for (int i = 0; i < _cycleSubSeries.Length; i++) + { + List virtualXValues = VirtualXValuesProvider.GetXValues(_cycleSubSeries[i].Count); + + FastLoess model = new FastLoess(virtualXValues, _cycleSubSeries[i], _isTemporal, Ns); + model.Estimate(); + + // add a prior point + _smoothedSubseries[i].Add(model.EstimateY(-1.0)); + _smoothedSubseries[i].AddRange(model.Y); + + // add a after point + _smoothedSubseries[i].Add(model.EstimateY(_cycleSubSeries[i].Count * 1.0)); + } - var absResiduals = new List(_residual); - for (int i = 0; i < _y.Count; i++) + // c is the smoothed series, with _length + 2Np points. + int index = 0; + for (int i = 0; i < _smoothedSubseries[0].Count; i++) { - _residual[i] = _y[i] - _seasonalComponent[i] - _trendComponent[i]; - absResiduals.Add(Math.Abs(_y[i] - _seasonalComponent[i] - _trendComponent[i])); + for (int j = 0; j < _smoothedSubseries.Length; j++) + { + if (_smoothedSubseries[j].Count <= i) + break; + if (_smoothedSubseries[j][i].Equals(double.NaN)) + { + return false; + } + c[index] = (_smoothedSubseries[j][i]); + ++index; + } } return true; } + private FastLoess LowPassFiltering(double[] c, int np) + { + List c1 = MovingAverage(c, np); + List c2 = MovingAverage(c1, np); + List c3 = MovingAverage(c2, 3); + List virtualC3XValues = VirtualXValuesProvider.GetXValues(c3.Count); + FastLoess lowPass = new FastLoess(virtualC3XValues, c3, _isTemporal, Nl(np)); + lowPass.Estimate(); + + return lowPass; + } + + private void SmoothedCycleSubseriesDetrending(double[] c, FastLoess lowPass, double[] s) + { + for (int i = 0; i < _length; i++) + { + s[i] = c[i] - lowPass.Y[i]; + } + } + + private void Deseasonalizing(IReadOnlyList y, double[] s, double[] deseasonSeries) + { + for (int i = 0; i < _length; i++) + { + deseasonSeries[i] = y[i] - s[i]; + } + } + + private void TrendSmooth(double[] deseasonSeries, int np, double[] t) + { + List virtualDeseasonSeries = VirtualXValuesProvider.GetXValues(deseasonSeries.Length); + FastLoess trender = new FastLoess(virtualDeseasonSeries, deseasonSeries, _isTemporal, Nt(np)); + trender.Estimate(); + for (int i = 0; i < _length; i++) + { + t[i] = trender.Y[i]; + } + } + /// /// this class provides the virtual x values for multi object usage. /// the cache mechanism is used for performance consideration. @@ -318,5 +320,34 @@ private static List MovingAverage(IReadOnlyList s, int length) return results; } + + private void AllocateDoubleArray(ref double[] arr, int length) + { + if (arr == null) + { + arr = new double[length]; + } + else if (arr.Length != length) + { + Array.Resize(ref arr, length); + } + } + + private void AllocateListDoubleArray(ref List[] arr, int length) + { + if (arr == null) + { + arr = new List[length]; + } + else if (arr.Length != length) + { + Array.Resize>(ref arr, length); + } + + for (int i = 0; i < length; ++i) + { + arr[i] = new List(); + } + } } } diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs deleted file mode 100644 index 5ecf8fb187..0000000000 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl_new.cs +++ /dev/null @@ -1,353 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using Microsoft.ML.Runtime; - -namespace Microsoft.ML.TimeSeries -{ - internal class InnerStlNew - { - private readonly bool _isTemporal; - private double[] _seasonalComponent; - private double[] _trendComponent; - private double[] _residual; - - private int _length; - - // arrays for intermediate results - private List[] _cycleSubSeries; - private List[] _smoothedSubseries; - - private double[] _s; - private double[] _t; - private double[] _detrendedY; - private double[] _c; - private double[] _deseasonSeries; - - /// - /// the smoothing parameter for the seasonal component. - /// should be odd, and at least 7. - /// - private const int Ns = 9; - - /// - /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases - /// - private const int Ni = 2; - - /// - /// the number of robustness iterations of the outer loop - /// - private const int No = 10; - - /// - /// the smoothing parameter for the low-pass filter. - /// /ref: should be the least odd integer greater than or equal to np. - /// it will preventing the trend and seasonal components from competing for the same variation in the data. - /// - private int Nl(int np) - { - if (np % 2 == 0) - return np + 1; - return np; - } - - /// - /// the smoothing parameter for the trend component. - /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen - /// s.t., satisty the following inequality. - /// - private int Nt(int np) - { - double value = 1.5 * np / (1.0 - 1.5 / Ns); - int result = (int)value + 1; - if (result % 2 == 0) - result++; - return result; - } - - /// - /// Initializes a new instance of the class. - /// for a time series, only with y values. assume the x-values are 0, 1, 2, ... - /// since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. - /// otherwise, the smoothing on seasonal component will be very complicated. - /// - /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - public InnerStlNew(bool isTemporal) - { - _isTemporal = isTemporal; - } - - /// - /// the seasonal component - /// - public IReadOnlyList SeasonalComponent - { - get { return _seasonalComponent; } - } - - /// - /// the trend component - /// - public IReadOnlyList TrendComponent - { - get { return _trendComponent; } - } - - /// - /// the left component after seasonal and trend are eliminated. - /// - public IReadOnlyList Residual - { - get { return _residual; } - } - - /// - /// the core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, - /// see section 2 and 3. especially section 2. - /// - /// return true if the process goes successfully. otherwise, return false. - public bool Decomposition(IReadOnlyList yValues, int np) - { - Contracts.CheckValue(yValues, nameof(yValues)); - Contracts.CheckParam(np > 0, nameof(np)); - - if (yValues.Count == 0) - throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); - - _length = yValues.Count; - AllocateDoubleArray(ref _seasonalComponent, _length); - AllocateDoubleArray(ref _trendComponent, _length); - AllocateDoubleArray(ref _residual, _length); - - AllocateDoubleArray(ref _s, _length); - AllocateDoubleArray(ref _t, _length); - AllocateDoubleArray(ref _detrendedY, _length); - AllocateDoubleArray(ref _c, _length + np * 2); - AllocateDoubleArray(ref _deseasonSeries, _length); - - AllocateListDoubleArray(ref _cycleSubSeries, np); - AllocateListDoubleArray(ref _smoothedSubseries, np); - - for (int i = 0; i < _length; ++i) - { - _t[i] = 0; - } - - for (int iter = 0; iter < Ni; iter++) - { - // step1: detrending - Detrending(yValues, _t, _detrendedY); - - // step2: cycle-subseries smoothing - bool success = CycleSubseriesSmooth(_detrendedY, np, _c); - if (!success) - { - return false; - } - - // step3: low-pass filtering of smoothed cycle-subseries - var lowPass = LowPassFiltering(_c, np); - - // step4: detrending of smoothed cycle-subseries - SmoothedCycleSubseriesDetrending(_c, lowPass, _s); - - // step5: deseasonalizing - Deseasonalizing(yValues, _s, _deseasonSeries); - - // step6: trend smoothing - TrendSmooth(_deseasonSeries, np, _t); - } - - for (int i = 0; i < _s.Length; i++) - { - _seasonalComponent[i] = _s[i]; - _trendComponent[i] = _t[i]; - } - - return true; - } - - private void Detrending(IReadOnlyList y, IReadOnlyList t, double[] detrendedY) - { - for (int i = 0; i < _length; i++) - detrendedY[i] = y[i] - t[i]; - } - - private bool CycleSubseriesSmooth(double[] detrendedY, int np, double[] c) - { - for (int i = 0; i < np; i++) - { - _cycleSubSeries[i] = new List(); - _smoothedSubseries[i] = new List(); - } - - // obtain all the subseries - for (int i = 0; i < _length; i++) - { - int cycleIndex = i % np; - _cycleSubSeries[cycleIndex].Add(detrendedY[i]); - } - - // smoothing on each subseries - for (int i = 0; i < _cycleSubSeries.Length; i++) - { - List virtualXValues = VirtualXValuesProvider.GetXValues(_cycleSubSeries[i].Count); - - FastLoess model = new FastLoess(virtualXValues, _cycleSubSeries[i], _isTemporal, Ns); - model.Estimate(); - - // add a prior point - _smoothedSubseries[i].Add(model.EstimateY(-1.0)); - _smoothedSubseries[i].AddRange(model.Y); - - // add a after point - _smoothedSubseries[i].Add(model.EstimateY(_cycleSubSeries[i].Count * 1.0)); - } - - // c is the smoothed series, with _length + 2Np points. - int index = 0; - for (int i = 0; i < _smoothedSubseries[0].Count; i++) - { - for (int j = 0; j < _smoothedSubseries.Length; j++) - { - if (_smoothedSubseries[j].Count <= i) - break; - if (_smoothedSubseries[j][i].Equals(double.NaN)) - { - return false; - } - c[index] = (_smoothedSubseries[j][i]); - ++index; - } - } - - return true; - } - - private FastLoess LowPassFiltering(double[] c, int np) - { - List c1 = MovingAverage(c, np); - List c2 = MovingAverage(c1, np); - List c3 = MovingAverage(c2, 3); - List virtualC3XValues = VirtualXValuesProvider.GetXValues(c3.Count); - FastLoess lowPass = new FastLoess(virtualC3XValues, c3, _isTemporal, Nl(np)); - lowPass.Estimate(); - - return lowPass; - } - - private void SmoothedCycleSubseriesDetrending(double[] c, FastLoess lowPass, double[] s) - { - for (int i = 0; i < _length; i++) - { - s[i] = c[i] - lowPass.Y[i]; - } - } - - private void Deseasonalizing(IReadOnlyList y, double[] s, double[] deseasonSeries) - { - for (int i = 0; i < _length; i++) - { - deseasonSeries[i] = y[i] - s[i]; - } - } - - private void TrendSmooth(double[] deseasonSeries, int np, double[] t) - { - List virtualDeseasonSeries = VirtualXValuesProvider.GetXValues(deseasonSeries.Length); - FastLoess trender = new FastLoess(virtualDeseasonSeries, deseasonSeries, _isTemporal, Nt(np)); - trender.Estimate(); - for (int i = 0; i < _length; i++) - { - t[i] = trender.Y[i]; - } - } - - /// - /// this class provides the virtual x values for multi object usage. - /// the cache mechanism is used for performance consideration. - /// - internal class VirtualXValuesProvider - { - private static Dictionary> _xValuesPool; - - static VirtualXValuesProvider() - { - _xValuesPool = new Dictionary>(); - } - - /// - /// get a list of virtual x-axis values. the values are from 0 to length - 1. - /// - /// specify the length you want to create the x values. - /// if this is cached, return directly. otherwise, create a new list and return - internal static List GetXValues(int length) - { - lock (_xValuesPool) - { - List xValues; - if (_xValuesPool.TryGetValue(length, out xValues)) - return xValues; - - var newXValues = new List(length); - for (int i = 0; i < length; i++) - newXValues.Add(i); - - _xValuesPool.Add(length, newXValues); - return newXValues; - } - } - } - - private static List MovingAverage(IReadOnlyList s, int length) - { - List results = new List(s.Count); - double partialSum = 0; - for (int i = 0; i < length; ++i) - { - partialSum += s[i]; - } - - for (int i = length; i < s.Count; ++i) - { - results.Add(partialSum / length); - partialSum = partialSum - s[i - length] + s[i]; - } - results.Add(partialSum / length); - - return results; - } - - private void AllocateDoubleArray(ref double[] arr, int length) - { - if (arr == null) - { - arr = new double[length]; - } - else if (arr.Length != length) - { - Array.Resize(ref arr, length); - } - } - - private void AllocateListDoubleArray(ref List[] arr, int length) - { - if (arr == null) - { - arr = new List[length]; - } - else if (arr.Length != length) - { - Array.Resize>(ref arr, length); - } - - for (int i = 0; i < length; ++i) - { - arr[i] = new List(); - } - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index cbd087f4c4..f5c4f00c02 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -345,6 +345,7 @@ internal sealed class SrCnnEntireModeler private double[] _val; private double[] _trends; private double[] _curWindow; + private InnerStl _stl; public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) { @@ -353,6 +354,7 @@ public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode _detectMode = detectMode; _period = period; _predictArray = new double[_lookaheadWindowSize + 1]; + _stl = new InnerStl(true); } public void Train(double[] values, ref double[][] results) @@ -370,7 +372,6 @@ public void Train(double[] values, ref double[][] results) Array.Resize(ref results, values.Length); } - bool isTemporal = true; AllocateDoubleArray(ref _seriesToDetect, values.Length); for (int i = 0; i < values.Length; ++i) { @@ -379,19 +380,10 @@ public void Train(double[] values, ref double[][] results) if (_period > 0) { - StlConfiguration config = new StlConfiguration(_period); - InnerStl stl = new InnerStl(values, config, isTemporal); - InnerStlNew stlNew = new InnerStlNew(isTemporal); - bool success = stl.Decomposition(); - stlNew.Decomposition(values, _period); + bool success = _stl.Decomposition(values, _period); if (success) { - _seriesToDetect = stl.Residual.ToArray(); - var residual2 = stl.Residual.ToArray(); - for (int i = 0; i<_seriesToDetect.Length; ++i) - { - Trace.Assert(_seriesToDetect[i] == residual2[i]); - } + _seriesToDetect = _stl.Residual.ToArray(); } } From d2e26532e0d760b2af9f5964a289f805ab081d21 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 9 Jun 2020 21:56:41 +0800 Subject: [PATCH 23/49] remove unused usings --- src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs | 4 +--- src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs | 6 +----- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 4 +--- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 3 --- src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs | 5 +---- src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs | 1 - src/Microsoft.ML.TimeSeries/STL/Loess.cs | 2 -- src/Microsoft.ML.TimeSeries/STL/MathsTools.cs | 2 -- src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs | 1 - src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs | 1 - src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs | 1 - 11 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs b/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs index 23df126189..d8e89a7d88 100644 --- a/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs +++ b/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace Microsoft.ML.TimeSeries { diff --git a/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs index bffde50693..da4af7cec2 100644 --- a/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs +++ b/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries +namespace Microsoft.ML.TimeSeries { public class BasicParameters { diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 5d997b4391..28f98d30e9 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 1603f9f205..fe7bcdaeb8 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index adb8e71715..f31c2f9fcf 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Numerics; +using System.Collections.Generic; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index 8d39efda0b..ea52f7af84 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; using Microsoft.ML.Internal.CpuMath.Core; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 1467aaba6e..9616bc0251 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Text; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs index 91342f6173..8dfe09746a 100644 --- a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.ML.TimeSeries { diff --git a/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs b/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs index a70df98fc4..2209a3aac2 100644 --- a/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs +++ b/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; namespace Microsoft.ML.TimeSeries { diff --git a/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs b/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs index bea7145ee9..f8bb3108bc 100644 --- a/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs +++ b/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; namespace Microsoft.ML.TimeSeries { diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index f5c4f00c02..a36e1bd6f7 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; From edc8a217dd3686e8486700791d97bafd70aba705 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 10 Jun 2020 10:22:05 +0800 Subject: [PATCH 24/49] add readonly --- src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index a36e1bd6f7..759c504de2 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -344,7 +344,7 @@ internal sealed class SrCnnEntireModeler private double[] _val; private double[] _trends; private double[] _curWindow; - private InnerStl _stl; + private readonly InnerStl _stl; public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) { From 7737a57cf597a56b0d62d53f287432aa78ce6147 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 11 Jun 2020 11:26:35 +0800 Subject: [PATCH 25/49] fix bug --- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index fe7bcdaeb8..2863f01f86 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -162,6 +162,7 @@ public bool Decomposition(IReadOnlyList yValues, int np) { _seasonalComponent[i] = _s[i]; _trendComponent[i] = _t[i]; + _residual[i] = yValues[i] - _s[i] - _t[i]; } return true; From 86f64f7d09d28ba769fc3a135dc0041cba918f9a Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 11 Jun 2020 17:10:12 +0800 Subject: [PATCH 26/49] add deseasonality --- .../Deseasonality/DeseasonalityBase.cs | 11 +++ .../Deseasonality/MeanDeseasonality.cs | 56 +++++++++++++ .../Deseasonality/MedianDeseasonality.cs | 68 ++++++++++++++++ .../Deseasonality/StlDeseasonality.cs | 31 ++++++++ .../ExtensionsCatalog.cs | 7 +- .../Microsoft.ML.TimeSeries.csproj | 4 + .../SrCnnEntireAnomalyDetector.cs | 79 ++++++++++++++++--- 7 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs create mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs create mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs create mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs new file mode 100644 index 0000000000..0759341c2b --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.ML.TimeSeries +{ + internal abstract class DeseasonalityBase + { + public abstract void Deseasonality(ref double[] values, int period, ref double[] results); + } +} diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs new file mode 100644 index 0000000000..6852d39fa1 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs @@ -0,0 +1,56 @@ +using System; + +namespace Microsoft.ML.TimeSeries.Deseasonality +{ + internal sealed class MeanDeseasonality : DeseasonalityBase + { + private double[] _circularComponent; + + public override void Deseasonality(ref double[] values, int period, ref double[] results) + { + AllocateDoubleArray(period); + var length = values.Length; + + // initialize the circurlar component to 0. + for (int i = 0; i < period; ++i) + { + _circularComponent[i] = 0; + } + + // sum up values that locates at the same position in one period. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + _circularComponent[indexInPeriod] += values[i]; + } + + // calculate the mean value as circular component. + var cnt = (length - 1) / period; + var rest = (length - 1) % period; + for (int i = 0; i < period; ++i) + { + var lastCircle = i <= rest ? 1 : 0; + _circularComponent[i] = _circularComponent[i] / (cnt + lastCircle); + } + + // substract the circular component from the original series. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + results[i] -= _circularComponent[indexInPeriod]; + } + } + + private void AllocateDoubleArray(int length) + { + if (_circularComponent == null) + { + _circularComponent = new double[length]; + } + else if (_circularComponent.Length != length) + { + Array.Resize(ref _circularComponent, length); + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs new file mode 100644 index 0000000000..753254d81c --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.ML.TimeSeries +{ + internal sealed class MedianDeseasonality : DeseasonalityBase + { + private List[] _subSeries; + private double[] _circularComponent; + + public override void Deseasonality(ref double[] values, int period, ref double[] results) + { + AllocateListDoubleArray(period); + + var length = values.Length; + + for (int i = 0; i < period; ++i) + { + _subSeries[i] = new List(); + } + + // split the original series into #period subseries. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + _subSeries[indexInPeriod].Add(values[i]); + } + + // calculate the median value as circular component. + AllocateDoubleArray(period); + for (int i = 0; i < period; ++i) + { + _circularComponent[i] = 1; + } + + // substract the circular component from the original series. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + results[i] -= _circularComponent[indexInPeriod]; + } + } + + private void AllocateListDoubleArray(int length) + { + if (_subSeries == null) + { + _subSeries = new List[length]; + } + else if (_subSeries.Length != length) + { + Array.Resize>(ref _subSeries, length); + } + } + + private void AllocateDoubleArray(int length) + { + if (_circularComponent == null) + { + _circularComponent = new double[length]; + } + else if (_circularComponent.Length != length) + { + Array.Resize(ref _circularComponent, length); + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs new file mode 100644 index 0000000000..655dbb7e04 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs @@ -0,0 +1,31 @@ +namespace Microsoft.ML.TimeSeries +{ + internal sealed class StlDeseasonality : DeseasonalityBase + { + private readonly InnerStl _stl; + + public StlDeseasonality() + { + _stl = new InnerStl(true); + } + + public override void Deseasonality(ref double[] values, int period, ref double[] results) + { + bool success = _stl.Decomposition(values, period); + if (success) + { + for (int i = 0; i < _stl.Residual.Count; ++i) + { + results[i] = _stl.Residual[i]; + } + } + else + { + for (int i = 0; i < values.Length; ++i) + { + results[i] = values[i]; + } + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 262d598045..cda59d6ca7 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -167,7 +167,8 @@ public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog /// When set to AnomalyAndMargin, the output vector would be a 7-element Double vector of (IsAnomaly, AnomalyScore, Mag, ExpectedValue, BoundaryUnit, UpperBoundary, LowerBoundary). /// The RawScore is output by SR to determine whether a point is an anomaly or not, under AnomalyAndMargin mode, when a point is an anomaly, an AnomalyScore will be calculated according to sensitivity setting. /// Default value is AnomalyOnly. - /// The period of the data to be detected. If the data has a circular pattern, set this parameter to the length of one period, otherwise 0 which indicates that there is no circular pattern. Default value is 0. + /// The period of the data to be detected. If the data has periodical pattern, set this parameter to the length of one period, otherwise 0 which indicates that there is no circular pattern. Default value is 0. + /// If the data has periodcal pattern, set this parameter to specify the method to perform deseasonality on the series. /// /// /// /// public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, - double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyOnly, int period = 0) - => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode, period); + double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyOnly, int period = 0, SrCnnDeseasonalityMode deseasonalityMode = SrCnnDeseasonalityMode.Stl) + => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode, period, deseasonalityMode); /// /// Create , which localizes root causes using decision tree algorithm. diff --git a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj index c5b4550c51..6473cc0b57 100644 --- a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj +++ b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj @@ -10,4 +10,8 @@ + + + + diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index ca52d3da39..a037e61f29 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -8,6 +8,7 @@ using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; using Microsoft.ML.Runtime; +using Microsoft.ML.TimeSeries.Deseasonality; using Microsoft.ML.Transforms.TimeSeries; namespace Microsoft.ML.TimeSeries @@ -33,6 +34,27 @@ public enum SrCnnDetectMode AnomalyAndExpectedValue = 2 } + /// + /// The Deseasonality modes of SrCnn models. The de-seasonality mode is envoked when the period of the series is greater than 0. + /// + public enum SrCnnDeseasonalityMode + { + /// + /// In this mode, the stl decompose algorithm is used to de-seasonality. + /// + Stl = 0, + + /// + /// In this mode, the mean value of points in the same position in a period is substracted to de-seasonality. + /// + Mean = 1, + + /// + /// In this mode, the median value of points in the same position in a period is substracted to de-seasonality. + /// + Median = 2 + } + /// /// Detect timeseries anomalies for entire input using Spectral Residual(SR) algorithm. /// @@ -72,7 +94,7 @@ public enum SrCnnDetectMode /// ]]> /// /// - /// + /// internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase { private const int MinBatchSize = 12; @@ -86,6 +108,7 @@ internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase GetDependencies(Func predicate) } } - public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode, int period) + public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode, int period, SrCnnDeseasonalityMode deseasonalityMode) : base(env, nameof(SrCnnEntireAnomalyDetector), input) { Host.CheckValue(inputColumnName, nameof(inputColumnName)); @@ -146,12 +169,17 @@ public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string || detectMode == SrCnnDetectMode.AnomalyAndExpectedValue || detectMode == SrCnnDetectMode.AnomalyAndMargin, nameof(detectMode), "Invalid detectMode"); + Host.CheckUserArg(deseasonalityMode == SrCnnDeseasonalityMode.Stl + || deseasonalityMode == SrCnnDeseasonalityMode.Mean + || deseasonalityMode == SrCnnDeseasonalityMode.Median, nameof(detectMode), "Invalid detectMode"); + Host.CheckUserArg(sensitivity >= 0 && sensitivity <= 100, nameof(sensitivity), "Must be in [0,100]."); _outputLength = _outputLengthArray[(int)detectMode]; _threshold = threshold; _sensitivity = sensitivity; _detectMode = detectMode; + _deseasonalityMode = deseasonalityMode; _bindings = new Bindings(input.Schema, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, _outputLength)); } @@ -165,7 +193,7 @@ protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch curre return new[] { currentBatch.CreateGetter(input, _inputColumnName) }; } - protected override Batch CreateBatch(DataViewRowCursor input) => new Batch(_batchSize, _outputLength, _threshold, _sensitivity, _detectMode, _period); + protected override Batch CreateBatch(DataViewRowCursor input) => new Batch(_batchSize, _outputLength, _threshold, _sensitivity, _detectMode, _period, _deseasonalityMode); protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) { @@ -208,7 +236,7 @@ internal sealed class Batch private double[][] _results; private int _bLen; - public Batch(int batchSize, int outputLength, double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) + public Batch(int batchSize, int outputLength, double threshold, double sensitivity, SrCnnDetectMode detectMode, int period, SrCnnDeseasonalityMode deseasonalityMode) { _batchSize = batchSize; _outputLength = outputLength; @@ -222,7 +250,7 @@ public Batch(int batchSize, int outputLength, double threshold, double sensitivi _previousBatch = new List(batchSize); _batch = new List(batchSize); } - _modeler = new SrCnnEntireModeler(threshold, sensitivity, detectMode, period); + _modeler = new SrCnnEntireModeler(threshold, sensitivity, detectMode, period, deseasonalityMode); } public void AddValue(double value) @@ -319,6 +347,7 @@ internal sealed class SrCnnEntireModeler private readonly double _sensitivity; private readonly SrCnnDetectMode _detectMode; private readonly int _period; + private readonly SrCnnDeseasonalityMode _deseasonalityMode; //used in all modes private readonly double[] _predictArray; @@ -339,14 +368,16 @@ internal sealed class SrCnnEntireModeler private double[] _seriesToDetect; //used in AnomalyAndExpectedValue and AnomalyAndMargin private double[] _deAnomalyData; + private double[] _circularComponent; //used in AnomalyAndMargin mode private double[] _units; private double[] _val; private double[] _trends; private double[] _curWindow; private readonly InnerStl _stl; + private readonly DeseasonalityBase _deseasonalityFunction; - public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period) + public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period, SrCnnDeseasonalityMode deseasonalityMode) { _threshold = threshold; _sensitivity = sensitivity; @@ -354,6 +385,19 @@ public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode _period = period; _predictArray = new double[_lookaheadWindowSize + 1]; _stl = new InnerStl(true); + + if (deseasonalityMode == SrCnnDeseasonalityMode.Stl) + { + _deseasonalityFunction = new StlDeseasonality(); + } + else if (deseasonalityMode == SrCnnDeseasonalityMode.Mean) + { + _deseasonalityFunction = new MeanDeseasonality(); + } + else // if (deseasonalityMode == SrCnnDeseasonalityMode.Median) + { + _deseasonalityFunction = new MedianDeseasonality(); + } } public void Train(double[] values, ref double[][] results) @@ -379,14 +423,11 @@ public void Train(double[] values, ref double[][] results) if (_period > 0) { - bool success = _stl.Decomposition(values, _period); - if (success) - { - _seriesToDetect = _stl.Residual.ToArray(); - } + _deseasonalityFunction.Deseasonality(ref values, _period, ref _seriesToDetect); } SpectralResidual(_seriesToDetect, results, _threshold); + //Optional Steps if (_detectMode == SrCnnDetectMode.AnomalyAndMargin) { @@ -525,6 +566,22 @@ private void BackAdd(double[] data) } } + private void Deseasonality(double[] values) + { + if (_deseasonalityMode == SrCnnDeseasonalityMode.Stl) + { + + } + else if (_deseasonalityMode == SrCnnDeseasonalityMode.Mean) + { + + } + else // _deseasonalityMode == SrCnnDeseasonalityMode.Median + { + + } + } + private double PredictNext(double[] data) { var n = data.Length; From 0d1a038913856c9caf7c96c6f063e49a8f8eec14 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 11 Jun 2020 18:21:37 +0800 Subject: [PATCH 27/49] update deseasonality --- .../Deseasonality/MedianDeseasonality.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/MathsTools.cs | 117 +++++++++++++++++- .../SrCnnEntireAnomalyDetector.cs | 22 +--- .../TimeSeriesDirectApi.cs | 16 ++- 4 files changed, 128 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs index 753254d81c..70036a140a 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs @@ -30,7 +30,7 @@ public override void Deseasonality(ref double[] values, int period, ref double[] AllocateDoubleArray(period); for (int i = 0; i < period; ++i) { - _circularComponent[i] = 1; + _circularComponent[i] = MathUtility.QuickMedian(_subSeries[i]); } // substract the circular component from the original series. diff --git a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs index 8dfe09746a..1d6c45cae0 100644 --- a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Generic; namespace Microsoft.ML.TimeSeries { - public class MathUtility + internal class MathUtility { /// /// calculate the standard sigmoid function @@ -12,5 +13,119 @@ public static double Sigmoid(double x) { return 1.0 / (1.0 + Math.Exp(-x)); } + + /// + /// use quick-sort like method to obtain the median value. + /// the complexity in expectation is O(n), which is faster than using quickSort. + /// + /// the input list of values. note that this list will be modified after calling this method + /// returns the median value + public static double QuickMedian(List values) + { + if (values == null || values.Count == 0) + return double.NaN; + + // here the third parameter is start from 1. so we need to plus 1 to compliant. + return QuickSelect(values, values.Count / 2 + 1); + } + + /// + /// use quick-sort like method to obtain the median value. + /// the complexity in expectation is O(n), which is faster than using quickSort. + /// + /// The list of values + /// The k smallest value in the list + public static double QuickSelect(IReadOnlyList values, int k) + { + var nums = values; + double[] left = new double[values.Count]; + double[] right = new double[values.Count]; + int numsCount = nums.Count; + + while (true) + { + if (numsCount == 1) + return nums[0]; + + int idx = FindMedianIndex(nums, 0, numsCount - 1); + double key = nums[idx]; + + int leftIdx = 0; + int rightIdx = 0; + for (int i = 0; i < numsCount; i++) + { + if (i == idx) + continue; + + if (nums[i] < key) + left[leftIdx++] = nums[i]; + else + right[rightIdx++] = nums[i]; + } + + if (leftIdx == k - 1) + return key; + + if (leftIdx >= k) + { + nums = left; + numsCount = leftIdx; + } + else + { + nums = right; + k = k - leftIdx - 1; + numsCount = rightIdx; + } + } + } + + public static int FindMedianIndex(IReadOnlyList values, int start, int end) + { + // use the middle value among first/middle/end as the guard value, to make sure the average performance good. + // according to unit test, this fix will improve the average performance 10%. and works normally when input list is ordered. + double first = values[start]; + double last = values[end]; + int midIndex = (start + end) / 2; + int medianIndex = -1; + double middleValue = values[midIndex]; + if (first < last) + { + if (middleValue > last) + { + // last is the middle value + medianIndex = end; + } + else if (middleValue > first) + { + // middleValue is the middle value + medianIndex = midIndex; + } + else + { + // first is the middle value + medianIndex = start; + } + } + else + { + if (middleValue > first) + { + // first is the middle value + medianIndex = start; + } + else if (middleValue < last) + { + // last is the middle value + medianIndex = end; + } + else + { + // middleValue is the middle value + medianIndex = midIndex; + } + } + return medianIndex; + } } } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index a037e61f29..f107375f56 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -347,7 +347,7 @@ internal sealed class SrCnnEntireModeler private readonly double _sensitivity; private readonly SrCnnDetectMode _detectMode; private readonly int _period; - private readonly SrCnnDeseasonalityMode _deseasonalityMode; + private readonly DeseasonalityBase _deseasonalityFunction; //used in all modes private readonly double[] _predictArray; @@ -368,14 +368,11 @@ internal sealed class SrCnnEntireModeler private double[] _seriesToDetect; //used in AnomalyAndExpectedValue and AnomalyAndMargin private double[] _deAnomalyData; - private double[] _circularComponent; //used in AnomalyAndMargin mode private double[] _units; private double[] _val; private double[] _trends; private double[] _curWindow; - private readonly InnerStl _stl; - private readonly DeseasonalityBase _deseasonalityFunction; public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode detectMode, int period, SrCnnDeseasonalityMode deseasonalityMode) { @@ -384,7 +381,6 @@ public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode _detectMode = detectMode; _period = period; _predictArray = new double[_lookaheadWindowSize + 1]; - _stl = new InnerStl(true); if (deseasonalityMode == SrCnnDeseasonalityMode.Stl) { @@ -566,22 +562,6 @@ private void BackAdd(double[] data) } } - private void Deseasonality(double[] values) - { - if (_deseasonalityMode == SrCnnDeseasonalityMode.Stl) - { - - } - else if (_deseasonalityMode == SrCnnDeseasonalityMode.Mean) - { - - } - else // _deseasonalityMode == SrCnnDeseasonalityMode.Median - { - - } - } - private double PredictNext(double[] data) { var n = data.Length; diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 0a7e49e532..84ada35e13 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -661,8 +661,10 @@ public void TestSrCnnBatchAnomalyDetector( } } - [Fact] - public void TestSrCnnAnomalyDetectorWithSeasonalData() + [Theory, CombinatorialData] + public void TestSrCnnAnomalyDetectorWithSeasonalData( + [CombinatorialValues(SrCnnDeseasonalityMode.Stl, SrCnnDeseasonalityMode.Mean, SrCnnDeseasonalityMode.Median)] SrCnnDeseasonalityMode mode + ) { var ml = new MLContext(1); IDataView dataView; @@ -677,7 +679,7 @@ public void TestSrCnnAnomalyDetectorWithSeasonalData() // Do batch anomaly detection var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, - threshold: 0.3, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288); + threshold: 0.3, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288, deseasonalityMode: mode); // Getting the data of the newly created column as an IEnumerable of // SrCnnAnomalyDetection. @@ -691,8 +693,10 @@ public void TestSrCnnAnomalyDetectorWithSeasonalData() } } - [Fact] - public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData() + [Theory, CombinatorialData] + public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData( + [CombinatorialValues(SrCnnDeseasonalityMode.Stl, SrCnnDeseasonalityMode.Mean, SrCnnDeseasonalityMode.Median)] SrCnnDeseasonalityMode mode + ) { var ml = new MLContext(1); IDataView dataView; @@ -707,7 +711,7 @@ public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData() // Do batch anomaly detection var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, - threshold: 0.3, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288); + threshold: 0.23, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288, deseasonalityMode: mode); // Getting the data of the newly created column as an IEnumerable of // SrCnnAnomalyDetection. From c87ae956da57372e300a6c899e2a68641bcd7e45 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 16 Jun 2020 15:29:19 +0800 Subject: [PATCH 28/49] update --- .../Deseasonality/MeanDeseasonality.cs | 15 +- .../Deseasonality/MedianDeseasonality.cs | 28 +-- .../STL/BackendStructureBase.cs | 87 ------- .../STL/BasicParameters.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 49 +--- .../STL/LeastSquares.cs | 4 +- .../STL/LocalRegression.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 6 +- .../STL/SeasonalInfo.cs | 82 ------- src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs | 228 ------------------ .../SrCnnEntireAnomalyDetector.cs | 54 +++++ .../SrCnnTransformBase.cs | 2 +- 13 files changed, 77 insertions(+), 484 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs delete mode 100644 src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs delete mode 100644 src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs index 6852d39fa1..fd4867c69c 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs @@ -8,7 +8,8 @@ internal sealed class MeanDeseasonality : DeseasonalityBase public override void Deseasonality(ref double[] values, int period, ref double[] results) { - AllocateDoubleArray(period); + Array.Resize(ref _circularComponent, period); + var length = values.Length; // initialize the circurlar component to 0. @@ -40,17 +41,5 @@ public override void Deseasonality(ref double[] values, int period, ref double[] results[i] -= _circularComponent[indexInPeriod]; } } - - private void AllocateDoubleArray(int length) - { - if (_circularComponent == null) - { - _circularComponent = new double[length]; - } - else if (_circularComponent.Length != length) - { - Array.Resize(ref _circularComponent, length); - } - } } } diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs index 70036a140a..1ef3137ed1 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs @@ -10,7 +10,8 @@ internal sealed class MedianDeseasonality : DeseasonalityBase public override void Deseasonality(ref double[] values, int period, ref double[] results) { - AllocateListDoubleArray(period); + Array.Resize(ref _circularComponent, period); + Array.Resize>(ref _subSeries, period); var length = values.Length; @@ -27,7 +28,6 @@ public override void Deseasonality(ref double[] values, int period, ref double[] } // calculate the median value as circular component. - AllocateDoubleArray(period); for (int i = 0; i < period; ++i) { _circularComponent[i] = MathUtility.QuickMedian(_subSeries[i]); @@ -40,29 +40,5 @@ public override void Deseasonality(ref double[] values, int period, ref double[] results[i] -= _circularComponent[indexInPeriod]; } } - - private void AllocateListDoubleArray(int length) - { - if (_subSeries == null) - { - _subSeries = new List[length]; - } - else if (_subSeries.Length != length) - { - Array.Resize>(ref _subSeries, length); - } - } - - private void AllocateDoubleArray(int length) - { - if (_circularComponent == null) - { - _circularComponent = new double[length]; - } - else if (_circularComponent.Length != length) - { - Array.Resize(ref _circularComponent, length); - } - } } } diff --git a/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs b/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs deleted file mode 100644 index d8e89a7d88..0000000000 --- a/src/Microsoft.ML.TimeSeries/STL/BackendStructureBase.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.ML.TimeSeries -{ - public enum TimeSeriesInfoKind - { - /// - /// (single-time series) the outliers of a single time series - /// - Outlier, - - /// - /// (single-time series) the seasonal signal of a single time series - /// - Seasonal, - - /// - /// (single-time series) the trend curve of a single time series - /// - Trend, - - /// - /// (two-time series) the lead/lag correlation between two time series - /// - CrossCorrelation, - - /// - /// (two-time series) the correlation of outliers from two time series - /// - OutlierCorrelation, - - /// - /// (two-time series) the correlation of trends from two time series - /// - TrendCorrelation, - } - - public abstract class TimeSeriesInfoBase - { - /// - /// each insight should be ranked, so that it can be compared with other insights. - /// - public abstract double Rank { get; set; } - - /// - /// the description of this particular insight - /// - public abstract string Description { get; protected set; } - - /// - /// indicate the kind of insight. - /// - public abstract TimeSeriesInfoKind Kind { get; protected set; } - - /// - /// basic comparison function, used for quick sort. - /// - /// the left element - /// the right element - public static int Compare(TimeSeriesInfoBase left, TimeSeriesInfoBase right) - { - if (object.ReferenceEquals(left, right)) - return 0; - if (left == null) - return -1; - if (right == null) - return 1; - return left.Rank.CompareTo(right.Rank); - } - } - - /// - /// the characteristic of single time series. - /// - public abstract class SingleSeriesInfo : TimeSeriesInfoBase - { - /// - /// x-axis values of original curve - /// - public IReadOnlyList X { get; protected set; } - - /// - /// y-axis values of original curve - /// - public IReadOnlyList Y { get; protected set; } - } -} diff --git a/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs index da4af7cec2..d823ab6d03 100644 --- a/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs +++ b/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs @@ -1,6 +1,6 @@ namespace Microsoft.ML.TimeSeries { - public class BasicParameters + internal class BasicParameters { /// /// the minimum length of a valid time series. a time series with length equals 2 is so trivial. when less than 2, meaningless. diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 28f98d30e9..0cc837b341 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -7,7 +7,7 @@ namespace Microsoft.ML.TimeSeries /// this is the fast version of Loess. there are several alternatives to improve the performance. this one is an approximation approach. /// the smoothing is conducted on a sample set, and then the values on the left points are assigned directly. /// - public class FastLoess + internal class FastLoess { /// /// this class is a sampling based method, so here specifies the sample size. diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 2863f01f86..5955317f92 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -115,18 +115,18 @@ public bool Decomposition(IReadOnlyList yValues, int np) throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); _length = yValues.Count; - AllocateDoubleArray(ref _seasonalComponent, _length); - AllocateDoubleArray(ref _trendComponent, _length); - AllocateDoubleArray(ref _residual, _length); + Array.Resize(ref _seasonalComponent, _length); + Array.Resize(ref _trendComponent, _length); + Array.Resize(ref _residual, _length); - AllocateDoubleArray(ref _s, _length); - AllocateDoubleArray(ref _t, _length); - AllocateDoubleArray(ref _detrendedY, _length); - AllocateDoubleArray(ref _c, _length + np * 2); - AllocateDoubleArray(ref _deseasonSeries, _length); + Array.Resize(ref _s, _length); + Array.Resize(ref _t, _length); + Array.Resize(ref _detrendedY, _length); + Array.Resize(ref _c, _length + np * 2); + Array.Resize(ref _deseasonSeries, _length); - AllocateListDoubleArray(ref _cycleSubSeries, np); - AllocateListDoubleArray(ref _smoothedSubseries, np); + Array.Resize(ref _cycleSubSeries, np); + Array.Resize(ref _smoothedSubseries, np); for (int i = 0; i < _length; ++i) { @@ -318,34 +318,5 @@ private static List MovingAverage(IReadOnlyList s, int length) return results; } - - private void AllocateDoubleArray(ref double[] arr, int length) - { - if (arr == null) - { - arr = new double[length]; - } - else if (arr.Length != length) - { - Array.Resize(ref arr, length); - } - } - - private void AllocateListDoubleArray(ref List[] arr, int length) - { - if (arr == null) - { - arr = new List[length]; - } - else if (arr.Length != length) - { - Array.Resize>(ref arr, length); - } - - for (int i = 0; i < length; ++i) - { - arr[i] = new List(); - } - } } } diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index f31c2f9fcf..1a314087d6 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -7,7 +7,7 @@ namespace Microsoft.ML.TimeSeries /// this class is used to calculate the least squares of the scatterplots. /// please check http://en.wikipedia.org/wiki/Least_squares for more details. /// - public class LeastSquares + internal class LeastSquares { private readonly List _x; private readonly List _y; @@ -102,7 +102,7 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) /// /// indicate a specific polynomial model /// - public class PolynomialModel + internal class PolynomialModel { private readonly List _coeffs; diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index ea52f7af84..ae0e0737a2 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -29,7 +29,7 @@ internal class LocalRegression /// the index of the current point /// number of neighbors, usually should be less then n. if it is equal/larger than n, the weight has slight change. /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - internal LocalRegression(IReadOnlyList x, IReadOnlyList y, int selfIndex, int r, bool isTemporal = true) + public LocalRegression(IReadOnlyList x, IReadOnlyList y, int selfIndex, int r, bool isTemporal = true) { Contracts.CheckValue(x, nameof(x)); Contracts.CheckValue(y, nameof(y)); diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 9616bc0251..f18121e9d6 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -7,7 +7,7 @@ namespace Microsoft.ML.TimeSeries /// /// Loess is short for Robust Locally Weighted Regression and Smoothing Scatterplots. /// - public class Loess + internal class Loess { private const double NumericalThreshold = 1.0e-10; @@ -147,7 +147,7 @@ public double EstimateY(double xValue) /// this class is used to define a set of weight functions. these functions are useful for various purposes for smoothing. /// i.e., the weighted least squares. /// - public class WeightMethod + internal class WeightMethod { /// /// this is used for robust weight, it is one iteration step of loess. @@ -179,7 +179,7 @@ public static double Tricube(double value) /// this class is used to store the parameters which are needed for lowess algorithm. /// the name of these constansts are compliant with the original terms in paper. /// - public class LoessConfiguration + internal class LoessConfiguration { /// /// this value is used for performance concern. when the length of the series goes large, a ratio of neighbors will be significant, diff --git a/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs b/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs deleted file mode 100644 index 2209a3aac2..0000000000 --- a/src/Microsoft.ML.TimeSeries/STL/SeasonalInfo.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Microsoft.ML.TimeSeries -{ - public class SeasonalInfo : SingleSeriesInfo - { - /// - /// Initializes a new instance of the class. - /// the corresponding properties of seasonal component - /// - /// x - /// y - /// the seasonal component - /// period of seasonal component - /// the average amplitude of the seasonal component - /// the statistical confidence - public SeasonalInfo( - IReadOnlyList x, - IReadOnlyList y, - IReadOnlyList seasonal, - int period, - double amplitude, - double confidence) - { - X = x; - Y = y; - SeasonalSignal = seasonal; - Period = period; - Amplitude = amplitude; - - // since seasonal component has sound statistical modeling and tests, so its rank is just the statistical confidence - Rank = confidence; - - Description = String.Format( - "this is a seasonal component, with period = {0}, amplitude = {1}. rank:{2}", - Period, - Amplitude, - Rank); - - Kind = TimeSeriesInfoKind.Seasonal; - } - - /// - /// the curve of seasonal - /// - public IReadOnlyList SeasonalSignal { get; private set; } - - /// - /// the period of the seasonal component. - /// - public int Period { get; private set; } - - /// - /// the average amplitude of the seasonal component - /// - public double Amplitude { get; private set; } - - public override string Description - { - get; - protected set; - } - - public override double Rank - { - get; - set; - } - - public override TimeSeriesInfoKind Kind - { - get; - protected set; - } - - public override string ToString() - { - return Description; - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs b/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs deleted file mode 100644 index f8bb3108bc..0000000000 --- a/src/Microsoft.ML.TimeSeries/STL/TrendInfo.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Microsoft.ML.TimeSeries -{ - /// - /// the trend component of time series. and corresponding mathematical properties of the trend. A default rank is available when this instance is constructed. - /// - public class TrendInfo : SingleSeriesInfo - { - private const double _slopeThreshold = 0.25; - private const double _msrThreshold = 0.1; - private const int _shortTimeseriesLength = 40; - - /// - /// Initializes a new instance of the class. - /// insight item for trend component - /// - /// x-axis values of original curve - /// y-axis values of original curve - /// the curve of trend - /// mean residual squares. which measure the quality of regression model fitting to original curve. - public TrendInfo(IReadOnlyList x, IReadOnlyList y, IReadOnlyList trend, double mrs) - { - X = x; - Y = y; - Trend = trend; - - int length = Trend.Count; - - // double slope = (this.Trend[length - 1] - this.Trend[0]) / (length - 1); - double increase = Trend[length - 1] - Trend[0]; - - // get relative slope, which is unit-invariant - double y0 = Y[0]; - if (Math.Abs(y0) < 0.01) - y0 = 0.01; - - // this is so called relative slope. - Slope = increase / y0; - IsIncrease = Slope > 0; - - // get relative mrs, which is unit-invariant. now the mrs is caclulated directly from original signal rather than trend. which is compliant with user perception. - double average = 0; - foreach (double value in Y) - average += value; - average /= Y.Count; - if (Math.Abs(average) < 0.01) - average = 0.01; - - // conducted normalization - Mrs = mrs / average / average; - IncreaseRatio = CalculateIncreasingRatio(Trend); - - // 0.15 is a magic number - double slopeSignificance = MathUtility.Sigmoid(50 * (Math.Abs(Slope) - 0.15)); - - // the lower the variance, the better the trend. 0.10 is a magic number. - double mesSignificance = 1 - MathUtility.Sigmoid(50 * (mrs - 0.10)); - double consistencySignificance = 1.0; - if (IsIncrease) - { - if (IncreaseRatio <= 0.5) - consistencySignificance = 0; - - // 0.667 is a magic number - consistencySignificance = MathUtility.Sigmoid(50 * (IncreaseRatio - 0.667)); - } - else - { - if (IncreaseRatio >= 0.5) - consistencySignificance = 0; - - // 0.667 is a magic number - consistencySignificance = MathUtility.Sigmoid(50 * (1.0 - IncreaseRatio - 0.667)); - } - Rank = slopeSignificance * mesSignificance * consistencySignificance; - - Description = String.Format("this is a trend, with slope = {0}, Mrs:{1}. rank:{2}", Slope, Mrs, Rank); - - Kind = TimeSeriesInfoKind.Trend; - } - - /// - /// the curve of trend - /// - public IReadOnlyList Trend { get; private set; } - - /// - /// the estimated relative slope of the trend. - /// - public double Slope { get; private set; } - - /// - /// relative mean residual squares. which measure the quality of regression model fitting to original curve. - /// this is already normalized - /// - public double Mrs { get; private set; } - - /// - /// indicate whether the curve is overall increasing or not. - /// this is directly derived from "Slope". if slope is positive, this is true. - /// - public bool IsIncrease { get; set; } - - /// - /// a percentage to indicate in the trend, what's the ratio of local increasing, i.e., delta is positive means increasing. - /// - public double IncreaseRatio { get; set; } - - public override string Description - { - get; - protected set; - } - - public override double Rank - { - get; - set; - } - - public override TimeSeriesInfoKind Kind - { - get; - protected set; - } - - public static double CalcSignificance( - IReadOnlyList trend, - IReadOnlyList rawSignal, - double mrs, - out double slope, - out double increaseRatio, - out double decreaseRatio) - { - // we must assume the length of x, and y are same. - int length = trend.Count; - - // calculate the slope. which is unit-invariant - double increase = trend[length - 1] - trend[0]; - - // let's try to use this value - double y0 = rawSignal[0]; - if (Math.Abs(y0) <= 0.01) - y0 = 0.01; - slope = increase / y0; - bool isIncrease = slope > 0; - - // when the time series is short, we should apply the consistency checking on the original signal, so that the result will be compliant with user perception. - if (trend.Count < _shortTimeseriesLength) - { - CalculateRatio(rawSignal, out increaseRatio, out decreaseRatio); - } - else - { - CalculateRatio(trend, out increaseRatio, out decreaseRatio); - } - - double slopeSignificance = MathUtility.Sigmoid(50 * (Math.Abs(slope) - _slopeThreshold)); - - double mesSignificance = 1 - MathUtility.Sigmoid(50 * (mrs - _msrThreshold)); - double consistencySignificance = 1.0; - double consistencyThreshold = ConsistencyThreshold(trend.Count); - if (isIncrease) - { - if (increaseRatio <= 0.5) - consistencySignificance = 0; - else - consistencySignificance = MathUtility.Sigmoid(50 * (increaseRatio - consistencyThreshold)); - } - else - { - if (decreaseRatio <= 0.5) - consistencySignificance = 0; - else - consistencySignificance = MathUtility.Sigmoid(50 * (decreaseRatio - consistencyThreshold)); - } - return slopeSignificance * mesSignificance * consistencySignificance; - } - - /// - /// calculate the increasing ratio. total number of positive deltas divided by total deltas - /// - private static double CalculateIncreasingRatio(IReadOnlyList trend) - { - if (trend.Count <= 1) - return 0; - int count = 0; - for (int i = 0; i < trend.Count - 1; i++) - { - if (trend[i + 1] - trend[i] > 0) - count++; - } - return count * 1.0 / (trend.Count - 1); - } - - /// - /// calculate the increasing/decreasing ratio. - /// - private static void CalculateRatio(IReadOnlyList signal, out double increaseRatio, out double decreaseRatio) - { - increaseRatio = 0; - decreaseRatio = 0; - if (signal.Count <= 1) - return; - int increaseCount = 0; - int decreaseCount = 0; - for (int i = 0; i < signal.Count - 1; i++) - { - if (signal[i + 1] - signal[i] > 0) - increaseCount++; - else if (signal[i + 1] - signal[i] < 0) - decreaseCount++; - } - increaseRatio = increaseCount * 1.0 / (signal.Count - 1); - decreaseRatio = decreaseCount * 1.0 / (signal.Count - 1); - } - - private static double ConsistencyThreshold(int length) - { - if (length < _shortTimeseriesLength) - return 0.8; - else - return 0.7; - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index f107375f56..252635c4cb 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Microsoft.ML.CommandLine; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; using Microsoft.ML.Runtime; @@ -109,6 +111,52 @@ internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase GetDependencies(Func predicate) } } + public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, Options options) + : base(env, nameof(SrCnnEntireAnomalyDetector), input) + { + _options = options; + } + public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode, int period, SrCnnDeseasonalityMode deseasonalityMode) : base(env, nameof(SrCnnEntireAnomalyDetector), input) { diff --git a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs index c76973bef2..fda4001b69 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnTransformBase.cs @@ -26,7 +26,7 @@ internal abstract class SrCnnArgumentBase 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", + [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the initial window for computing. The default value is set to 0, which means there is no initial window considered.", ShortName = "iwnd", SortOrder = 4)] public int InitialWindowSize = 0; From b59660c8f4e0f149b7c37b74226107fff261bced Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 16 Jun 2020 16:47:20 +0800 Subject: [PATCH 29/49] add options --- .../ExtensionsCatalog.cs | 34 +++- .../SrCnnEntireAnomalyDetector.cs | 158 ++++++++---------- .../TimeSeriesDirectApi.cs | 29 +++- 3 files changed, 128 insertions(+), 93 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index cda59d6ca7..940e2e6f48 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -167,8 +167,6 @@ public static SrCnnAnomalyEstimator DetectAnomalyBySrCnn(this TransformsCatalog /// When set to AnomalyAndMargin, the output vector would be a 7-element Double vector of (IsAnomaly, AnomalyScore, Mag, ExpectedValue, BoundaryUnit, UpperBoundary, LowerBoundary). /// The RawScore is output by SR to determine whether a point is an anomaly or not, under AnomalyAndMargin mode, when a point is an anomaly, an AnomalyScore will be calculated according to sensitivity setting. /// Default value is AnomalyOnly. - /// The period of the data to be detected. If the data has periodical pattern, set this parameter to the length of one period, otherwise 0 which indicates that there is no circular pattern. Default value is 0. - /// If the data has periodcal pattern, set this parameter to specify the method to perform deseasonality on the series. /// /// /// /// public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, - double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyOnly, int period = 0, SrCnnDeseasonalityMode deseasonalityMode = SrCnnDeseasonalityMode.Stl) - => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, inputColumnName, outputColumnName, threshold, batchSize, sensitivity, detectMode, period, deseasonalityMode); + double threshold = 0.3, int batchSize = 1024, double sensitivity = 99, SrCnnDetectMode detectMode = SrCnnDetectMode.AnomalyOnly) + { + var options = new SrCnnEntireAnomalyDetectorOptions() + { + InputColumnName = inputColumnName, + OutputColumnName = outputColumnName, + Threshold = threshold, + BatchSize = batchSize, + Sensitivity = sensitivity, + DetectMode = detectMode, + }; + + return DetectEntireAnomalyBySrCnn(catalog, input, options); + } + + /// + /// Create , which detects timeseries anomalies for entire input using SRCNN algorithm. + /// + /// The AnomalyDetectionCatalog. + /// Input DataView. + /// Defines the settings of the load operation. + /// + /// + /// + /// + /// + public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog catalog, IDataView input, SrCnnEntireAnomalyDetectorOptions options = null) + => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, options); /// /// Create , which localizes root causes using decision tree algorithm. diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 252635c4cb..4cd01c8688 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -56,6 +56,50 @@ public enum SrCnnDeseasonalityMode /// Median = 2 } + public sealed class SrCnnEntireAnomalyDetectorOptions + { + [Argument(ArgumentType.Required, HelpText = "The name of the input column.", ShortName = "input", + SortOrder = 1, Purpose = SpecialPurpose.ColumnName)] + public string InputColumnName; + + [Argument(ArgumentType.Required, HelpText = "The name of the output column.", ShortName = "output", + SortOrder = 2)] + public string OutputColumnName; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", + SortOrder = 3, ShortName = "thr")] + public double Threshold = Defaults.Threshold; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The number of data points to be detected in each batch. It should be at least 12. Set this parameter to -1 to detect anomaly on the entire series.", + SortOrder = 4, ShortName = "bsz")] + public int BatchSize = Defaults.BatchSize; + + [Argument(ArgumentType.AtMostOnce, HelpText = "This parameter is used in AnomalyAndMargin mode the determine the range of the boundaries.", + SortOrder = 4, ShortName = "sen")] + public double Sensitivity = Defaults.Sensitivity; + + [Argument(ArgumentType.AtMostOnce, HelpText = "Specify the detect mode as one of AnomalyOnly, AnomalyAndExpectedValue and AnomalyAndMargin.", + SortOrder = 5, ShortName = "dtmd")] + public SrCnnDetectMode DetectMode = Defaults.DetectMode; + + [Argument(ArgumentType.AtMostOnce, HelpText = "If there is circular pattern in the series, set this value to the number of points in one cycle.", + SortOrder = 5, ShortName = "prd")] + public int Period = Defaults.Period; + + [Argument(ArgumentType.AtMostOnce, HelpText = "Specify the deseasonality mode as one of stl, mean and median.", + SortOrder = 6, ShortName = "dsmd")] + public SrCnnDeseasonalityMode DeseasonalityMode = Defaults.DeseasonalityMode; + + internal static class Defaults + { + public const double Threshold = 0.3; + public const int BatchSize = 2000; + public const double Sensitivity = 55; + public const SrCnnDetectMode DetectMode = SrCnnDetectMode.AnomalyOnly; + public const int Period = 0; + public const SrCnnDeseasonalityMode DeseasonalityMode = SrCnnDeseasonalityMode.Stl; + } + } /// /// Detect timeseries anomalies for entire input using Spectral Residual(SR) algorithm. @@ -96,67 +140,16 @@ public enum SrCnnDeseasonalityMode /// ]]> /// /// - /// + /// + /// internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase { private const int MinBatchSize = 12; private static readonly int[] _outputLengthArray = {3, 7, 4}; - private readonly int _batchSize; - private readonly string _inputColumnName; + private readonly SrCnnEntireAnomalyDetectorOptions _options; private readonly int _outputLength; private readonly Bindings _bindings; - private readonly double _threshold; - private readonly double _sensitivity; - private readonly SrCnnDetectMode _detectMode; - private readonly int _period; - private readonly SrCnnDeseasonalityMode _deseasonalityMode; - private readonly Options _options; - - internal sealed class Options - { - [Argument(ArgumentType.Required, HelpText = "The name of the input column.", ShortName = "input", - SortOrder = 1, Purpose = SpecialPurpose.ColumnName)] - public string InputColumn; - - [Argument(ArgumentType.Required, HelpText = "The name of the output column.", ShortName = "output", - SortOrder = 2)] - public string OutputColumnName; - - [Argument(ArgumentType.AtMostOnce, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", - SortOrder = 3, ShortName = "thr")] - public double Threshold; - - [Argument(ArgumentType.AtMostOnce, HelpText = "The number of data points to be detected in each batch. It should be at least 12. Set this parameter to -1 to detect anomaly on the entire series.", - SortOrder = 4, ShortName = "bsz")] - public int BatchSize; - - [Argument(ArgumentType.AtMostOnce, HelpText = "This parameter is used in AnomalyAndMargin mode the determine the range of the boundaries.", - SortOrder = 4, ShortName = "sen")] - public double Sensitivity; - - [Argument(ArgumentType.AtMostOnce, HelpText = "Specify the detect mode as one of AnomalyOnly, AnomalyAndExpectedValue and AnomalyAndMargin.", - SortOrder = 5, ShortName = "dtmd")] - public SrCnnDetectMode DetectMode; - - [Argument(ArgumentType.AtMostOnce, HelpText = "If there is circular pattern in the series, set this value to the number of points in one cycle.", - SortOrder = 5, ShortName = "prd")] - public int Period; - - [Argument(ArgumentType.AtMostOnce, HelpText = "Specify the deseasonality mode as one of stl, mean and median.", - SortOrder = 6, ShortName = "dsmd")] - public SrCnnDeseasonalityMode DeseasonalityMode; - - internal static class Defaults - { - public const double Threshold = 0.3; - public const int BatchSize = 2000; - public const double Sensitivity = 55; - public const SrCnnDetectMode DetectMode = SrCnnDetectMode.AnomalyOnly; - public const int Period = 0; - public const SrCnnDeseasonalityMode DeseasonalityMode = SrCnnDeseasonalityMode.Stl; - } - } private class Bindings : ColumnBindingsBase { @@ -199,43 +192,37 @@ public Func GetDependencies(Func predicate) } } - public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, Options options) + public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, SrCnnEntireAnomalyDetectorOptions options) : base(env, nameof(SrCnnEntireAnomalyDetector), input) { + Host.CheckValue(options, nameof(options)); + CheckOptionArguments(options); + _options = options; + _outputLength = _outputLengthArray[(int)options.DetectMode]; + + _bindings = new Bindings(input.Schema, options.InputColumnName, options.OutputColumnName, new VectorDataViewType(NumberDataViewType.Double, _outputLength)); } - public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string inputColumnName, string outputColumnName, double threshold, int batchSize, double sensitivity, SrCnnDetectMode detectMode, int period, SrCnnDeseasonalityMode deseasonalityMode) - : base(env, nameof(SrCnnEntireAnomalyDetector), input) + private void CheckOptionArguments(SrCnnEntireAnomalyDetectorOptions options) { - Host.CheckValue(inputColumnName, nameof(inputColumnName)); - _inputColumnName = inputColumnName; - - Host.CheckUserArg(period >= 0, nameof(period), "Must be integer equal or greater than 0."); - _period = period; - - Host.CheckUserArg(batchSize == -1 || batchSize >= MinBatchSize, nameof(batchSize), "BatchSize must be -1 or no less than 12."); - Host.CheckUserArg(batchSize >= 4 * period || batchSize == -1 || period == 0, nameof(batchSize), "BatchSize must be at least four times the length of one period."); - _batchSize = batchSize; + Host.CheckValue(options.InputColumnName, nameof(options.InputColumnName)); - Host.CheckUserArg(threshold >= 0 && threshold <= 1, nameof(threshold), "Must be in [0,1]."); - Host.CheckUserArg(detectMode == SrCnnDetectMode.AnomalyOnly - || detectMode == SrCnnDetectMode.AnomalyAndExpectedValue - || detectMode == SrCnnDetectMode.AnomalyAndMargin, nameof(detectMode), "Invalid detectMode"); + Host.CheckUserArg(options.Period >= 0, nameof(options.Period), "Must be integer equal or greater than 0."); - Host.CheckUserArg(deseasonalityMode == SrCnnDeseasonalityMode.Stl - || deseasonalityMode == SrCnnDeseasonalityMode.Mean - || deseasonalityMode == SrCnnDeseasonalityMode.Median, nameof(detectMode), "Invalid detectMode"); + Host.CheckUserArg(options.BatchSize == -1 || options.BatchSize >= MinBatchSize, nameof(options.BatchSize), "Must be -1 or no less than 12."); + Host.CheckUserArg(options.BatchSize >= 4 * options.Period || options.BatchSize == -1 || options.Period == 0, nameof(options.BatchSize), "Must be at least four times the length of one period."); - Host.CheckUserArg(sensitivity >= 0 && sensitivity <= 100, nameof(sensitivity), "Must be in [0,100]."); + Host.CheckUserArg(options.Threshold >= 0 && options.Threshold <= 1, nameof(options.Threshold), "Must be in [0,1]."); + Host.CheckUserArg(options.DetectMode == SrCnnDetectMode.AnomalyOnly + || options.DetectMode == SrCnnDetectMode.AnomalyAndExpectedValue + || options.DetectMode == SrCnnDetectMode.AnomalyAndMargin, nameof(options.DetectMode), "Invalid detectMode"); - _outputLength = _outputLengthArray[(int)detectMode]; - _threshold = threshold; - _sensitivity = sensitivity; - _detectMode = detectMode; - _deseasonalityMode = deseasonalityMode; + Host.CheckUserArg(options.DeseasonalityMode == SrCnnDeseasonalityMode.Stl + || options.DeseasonalityMode == SrCnnDeseasonalityMode.Mean + || options.DeseasonalityMode == SrCnnDeseasonalityMode.Median, nameof(options.DeseasonalityMode), "Invalid detectMode"); - _bindings = new Bindings(input.Schema, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, _outputLength)); + Host.CheckUserArg(options.Sensitivity >= 0 && options.Sensitivity <= 100, nameof(options.Sensitivity), "Must be in [0,100]."); } protected override ColumnBindingsBase SchemaBindings => _bindings; @@ -244,24 +231,25 @@ protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch curre { if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) return new Delegate[1]; - return new[] { currentBatch.CreateGetter(input, _inputColumnName) }; + return new[] { currentBatch.CreateGetter(input, _options.InputColumnName) }; } - protected override Batch CreateBatch(DataViewRowCursor input) => new Batch(_batchSize, _outputLength, _threshold, _sensitivity, _detectMode, _period, _deseasonalityMode); + protected override Batch CreateBatch(DataViewRowCursor input) + => new Batch(_options.BatchSize, _outputLength, _options.Threshold, _options.Sensitivity, _options.DetectMode, _options.Period, _options.DeseasonalityMode); protected override Func GetIsNewBatchDelegate(DataViewRowCursor input) { - return () => _batchSize == -1 ? input.Position == 0 : input.Position % _batchSize == 0; + return () => _options.BatchSize == -1 ? input.Position == 0 : input.Position % _options.BatchSize == 0; } protected override Func GetLastInBatchDelegate(DataViewRowCursor input) { - return () => _batchSize == -1 ? input.Position == -1 : (input.Position + 1) % _batchSize == 0; + return () => _options.BatchSize == -1 ? input.Position == -1 : (input.Position + 1) % _options.BatchSize == 0; } protected override ValueGetter GetLookAheadGetter(DataViewRowCursor input) { - return input.GetGetter(input.Schema[_inputColumnName]); + return input.GetGetter(input.Schema[_options.InputColumnName]); } protected override Func GetSchemaBindingDependencies(Func predicate) diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 84ada35e13..9bbec1e184 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -678,8 +678,19 @@ public void TestSrCnnAnomalyDetectorWithSeasonalData( string inputColumnName = nameof(TimeSeriesDataDouble.Value); // Do batch anomaly detection - var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, - threshold: 0.3, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288, deseasonalityMode: mode); + var options = new SrCnnEntireAnomalyDetectorOptions() + { + InputColumnName = inputColumnName, + OutputColumnName = outputColumnName, + Threshold = 0.3, + BatchSize = -1, + Sensitivity = 53.0, + DetectMode = SrCnnDetectMode.AnomalyAndMargin, + Period = 288, + DeseasonalityMode = mode + }; + + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, options); // Getting the data of the newly created column as an IEnumerable of // SrCnnAnomalyDetection. @@ -710,8 +721,18 @@ public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData( string inputColumnName = nameof(TimeSeriesDataDouble.Value); // Do batch anomaly detection - var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, - threshold: 0.23, batchSize: -1, sensitivity: 53.0, detectMode: SrCnnDetectMode.AnomalyAndMargin, period: 288, deseasonalityMode: mode); + var options = new SrCnnEntireAnomalyDetectorOptions() + { + InputColumnName = inputColumnName, + OutputColumnName = outputColumnName, + Threshold = 0.23, + BatchSize = -1, + Sensitivity = 53.0, + DetectMode = SrCnnDetectMode.AnomalyAndMargin, + Period = 288, + DeseasonalityMode = mode + }; + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, options); // Getting the data of the newly created column as an IEnumerable of // SrCnnAnomalyDetection. From 5a93fa1edc8fdd712a50a7439510864b0515de39 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 16 Jun 2020 18:09:33 +0800 Subject: [PATCH 30/49] refine code style --- .../Deseasonality/MeanDeseasonality.cs | 2 +- .../Deseasonality/MedianDeseasonality.cs | 4 ++-- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs index fd4867c69c..7cd7fc803d 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs @@ -8,7 +8,7 @@ internal sealed class MeanDeseasonality : DeseasonalityBase public override void Deseasonality(ref double[] values, int period, ref double[] results) { - Array.Resize(ref _circularComponent, period); + Array.Resize(ref _circularComponent, period); var length = values.Length; diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs index 1ef3137ed1..dfe5fea10d 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs @@ -10,8 +10,8 @@ internal sealed class MedianDeseasonality : DeseasonalityBase public override void Deseasonality(ref double[] values, int period, ref double[] results) { - Array.Resize(ref _circularComponent, period); - Array.Resize>(ref _subSeries, period); + Array.Resize(ref _circularComponent, period); + Array.Resize(ref _subSeries, period); var length = values.Length; diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 0cc837b341..f2205dfcc6 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -12,7 +12,7 @@ internal class FastLoess /// /// this class is a sampling based method, so here specifies the sample size. /// - private static int _sampleSize = 100; + private const int _sampleSize = 100; private readonly IReadOnlyList _x; private readonly IReadOnlyList _y; diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index f18121e9d6..4c5b3be20b 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -22,11 +22,11 @@ internal class Loess /// /// key is the index of the given point, value is the corresponding neighbors of the given point. /// - private Dictionary _neighbors; + private readonly Dictionary _neighbors; private IReadOnlyList _x; private IReadOnlyList _y; - private int _length; + private readonly int _length; /// /// Initializes a new instance of the class. @@ -196,11 +196,11 @@ internal class LoessConfiguration /// /// (0, 1], a smooth range ratio. let fn be the number of neighbors of a specific point. /// - public static readonly double F = 0.3; + public const double F = 0.3; /// /// the number of iterations for robust regression. /// - public static readonly int T = 2; + public const int T = 2; } } From 1a7b071ecf51fc527e7f5168b86a5650bf70e469 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 16 Jun 2020 18:32:32 +0800 Subject: [PATCH 31/49] refine code --- .../SrCnnEntireAnomalyDetector.cs | 62 ++++++++----------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 4cd01c8688..0b2d94f59c 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -453,7 +453,7 @@ public void Train(double[] values, ref double[][] results) Array.Resize(ref results, values.Length); } - AllocateDoubleArray(ref _seriesToDetect, values.Length); + Array.Resize(ref _seriesToDetect, values.Length); for (int i = 0; i < values.Length; ++i) { _seriesToDetect[i] = values[i]; @@ -491,18 +491,6 @@ public void Train(double[] values, ref double[][] results) } } - private void AllocateDoubleArray(ref double[] arr, int length) - { - if (arr == null) - { - arr = new double[length]; - } - else if (arr.Length != length) - { - Array.Resize(ref arr, length); - } - } - private void SpectralResidual(double[] values, double[][] results, double threshold) { // Step 1: Get backadd wave @@ -510,15 +498,15 @@ private void SpectralResidual(double[] values, double[][] results, double thresh // Step 2: FFT transformation int length = _backAddArray.Length; - AllocateDoubleArray(ref _fftRe, length); - AllocateDoubleArray(ref _fftIm, length); + Array.Resize(ref _fftRe, length); + Array.Resize(ref _fftIm, length); - AllocateDoubleArray(ref _zeroArray, length); + Array.Resize(ref _zeroArray, length); FftUtils.ComputeForwardFft(_backAddArray, _zeroArray, _fftRe, _fftIm, length); // Step 3: Calculate mags of FFT - AllocateDoubleArray(ref _magList, length); - AllocateDoubleArray(ref _magLogList, length); + Array.Resize(ref _magList, length); + Array.Resize(ref _magLogList, length); for (int i = 0; i < length; ++i) { _magList[i] = Math.Sqrt((Math.Pow(_fftRe[i], 2) + Math.Pow(_fftIm[i], 2))); @@ -534,15 +522,15 @@ private void SpectralResidual(double[] values, double[][] results, double thresh // Step 4: Calculate spectral AverageFilter(_magLogList, _averagingWindowSize); - AllocateDoubleArray(ref _spectralList, length); + Array.Resize(ref _spectralList, length); for (int i = 0; i < length; ++i) { _spectralList[i] = Math.Exp(_magLogList[i] - _cumSumList[i]); } // Step 5: IFFT transformation - AllocateDoubleArray(ref _transRe, length); - AllocateDoubleArray(ref _transIm, length); + Array.Resize(ref _transRe, length); + Array.Resize(ref _transIm, length); for (int i = 0; i < length; ++i) { if (_magLogList[i] != 0) @@ -557,12 +545,12 @@ private void SpectralResidual(double[] values, double[][] results, double thresh } } - AllocateDoubleArray(ref _ifftRe, length); - AllocateDoubleArray(ref _ifftIm, length); + Array.Resize(ref _ifftRe, length); + Array.Resize(ref _ifftIm, length); FftUtils.ComputeBackwardFft(_transRe, _transIm, _ifftRe, _ifftIm, length); // Step 6: Calculate mag and ave_mag of IFFT - AllocateDoubleArray(ref _ifftMagList, length); + Array.Resize(ref _ifftMagList, length); for (int i = 0; i < length; ++i) { _ifftMagList[i] = Math.Sqrt((Math.Pow(_ifftRe[i], 2) + Math.Pow(_ifftIm[i], 2))); @@ -593,7 +581,7 @@ private void BackAdd(double[] data) _predictArray[j++] = data[i]; } var predictedValue = PredictNext(_predictArray); - AllocateDoubleArray(ref _backAddArray, data.Length + _backAddWindowSize); + Array.Resize(ref _backAddArray, data.Length + _backAddWindowSize); for (int i = 0; i < data.Length; ++i) { _backAddArray[i] = data[i]; @@ -620,8 +608,8 @@ private void AverageFilter(double[] data, int n) double cumsum = 0.0f; int length = data.Length; - AllocateDoubleArray(ref _cumSumList, length); - AllocateDoubleArray(ref _cumSumShift, length); + Array.Resize(ref _cumSumList, length); + Array.Resize(ref _cumSumShift, length); for (int i = 0; i < length; ++i) { @@ -800,7 +788,7 @@ private int[] GetAnomalyIndex(double[] scores) private void GetDeanomalyData(double[] data, int[] anomalyIdxList) { - AllocateDoubleArray(ref _deAnomalyData, data.Length); + Array.Resize(ref _deAnomalyData, data.Length); Array.Copy(data, _deAnomalyData, data.Length); int minPointsToFit = 4; foreach (var idx in anomalyIdxList) @@ -857,9 +845,9 @@ private double CalculateInterpolate(List> values, int idx) private void CalculateExpectedValueByFft(double[] data) { int length = data.Length; - AllocateDoubleArray(ref _fftRe, length); - AllocateDoubleArray(ref _fftIm, length); - AllocateDoubleArray(ref _zeroArray, length); + Array.Resize(ref _fftRe, length); + Array.Resize(ref _fftIm, length); + Array.Resize(ref _zeroArray, length); FftUtils.ComputeForwardFft(data, _zeroArray, _fftRe, _fftIm, length); for (int i = 0; i < length; ++i) @@ -871,8 +859,8 @@ private void CalculateExpectedValueByFft(double[] data) } } - AllocateDoubleArray(ref _ifftRe, length); - AllocateDoubleArray(ref _ifftIm, length); + Array.Resize(ref _ifftRe, length); + Array.Resize(ref _ifftIm, length); FftUtils.ComputeBackwardFft(_fftRe, _fftIm, _ifftRe, _ifftIm, length); } @@ -903,7 +891,7 @@ private void CalculateBoundaryUnit(double[] data, bool[] isAnomalys) trendFraction = 1.0; } - AllocateDoubleArray(ref _units, _trends.Length); + Array.Resize(ref _units, _trends.Length); for (int i = 0; i < _units.Length; ++i) { _units[i] = Math.Max(1, averageTrendPart + Math.Abs(_trends[i]) * trendFraction); @@ -918,11 +906,11 @@ private void MedianFilter(double[] data, int window, bool needTwoEnd = false) { int wLen = window / 2 * 2 + 1; int tLen = data.Length; - AllocateDoubleArray(ref _val, tLen); + Array.Resize(ref _val, tLen); Array.Copy(data, _val, tLen); - AllocateDoubleArray(ref _trends, tLen); + Array.Resize(ref _trends, tLen); Array.Copy(data, _trends, tLen); - AllocateDoubleArray(ref _curWindow, wLen); + Array.Resize(ref _curWindow, wLen); if (tLen < wLen) return; From b6fa553db6aeab58d534eb868d8b3b63fd676ec7 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 16 Jun 2020 20:49:38 +0800 Subject: [PATCH 32/49] update --- .../TimeSeriesDirectApi.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 9bbec1e184..9ba2aed6f0 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -616,8 +616,7 @@ public void TestSrCnnBatchAnomalyDetector( var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, threshold: 0.35, batchSize: batchSize, sensitivity: 90.0, mode); - // Getting the data of the newly created column as an IEnumerable of - // SrCnnAnomalyDetection. + // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable( outputDataView, reuseRowObject: false); @@ -692,8 +691,7 @@ public void TestSrCnnAnomalyDetectorWithSeasonalData( var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, options); - // Getting the data of the newly created column as an IEnumerable of - // SrCnnAnomalyDetection. + // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable( outputDataView, reuseRowObject: false); @@ -732,10 +730,10 @@ public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData( Period = 288, DeseasonalityMode = mode }; + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, options); - // Getting the data of the newly created column as an IEnumerable of - // SrCnnAnomalyDetection. + // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable( outputDataView, reuseRowObject: false); From 192d7828fd4cef37dd39e305638170b54e86d883 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 17 Jun 2020 13:41:37 +0800 Subject: [PATCH 33/49] updates --- .../STL/BasicParameters.cs | 26 --------------- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 32 +++++++++---------- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 4 +-- .../STL/LoessBasicParameters.cs | 10 ++++++ src/Microsoft.ML.TimeSeries/STL/MathsTools.cs | 9 ------ .../SrCnnEntireAnomalyDetector.cs | 1 - 7 files changed, 28 insertions(+), 56 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs create mode 100644 src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs diff --git a/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs deleted file mode 100644 index d823ab6d03..0000000000 --- a/src/Microsoft.ML.TimeSeries/STL/BasicParameters.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Microsoft.ML.TimeSeries -{ - internal class BasicParameters - { - /// - /// the minimum length of a valid time series. a time series with length equals 2 is so trivial. when less than 2, meaningless. - /// - public const int MinTimeSeriesLength = 3; - - /// - /// the maximum length of a valid time series. when there are too many data points, the chart will look so dense that details are lost. - /// this number is tuned so that the bird strike data can still preserve results. - /// - public const int MaxTimeSeriesLength = 4000; - - /// - /// the minimum count of repeated periods. this is used for determining a noticeable seasonal signal. - /// - public const int MinPeriodRepeatCount = 3; - - /// - /// the minimum count of regular gaps. when there are too few gaps, the time series will look odd, which will impact the seasonality analysis - /// - public const int MinRegularGap = 5; - } -} diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index f2205dfcc6..b0325eb280 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -35,7 +35,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b Contracts.CheckValue(yValues, nameof(yValues)); Y = new List(); - if (yValues.Count < BasicParameters.MinTimeSeriesLength) + if (yValues.Count < LoessBasicParameters.MinTimeSeriesLength) throw Contracts.Except("input data structure cannot be 0-length: lowess"); _x = xValues; diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 5955317f92..847dc241aa 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -11,8 +11,6 @@ internal class InnerStl private double[] _trendComponent; private double[] _residual; - private int _length; - // arrays for intermediate results private List[] _cycleSubSeries; private List[] _smoothedSubseries; @@ -114,21 +112,21 @@ public bool Decomposition(IReadOnlyList yValues, int np) if (yValues.Count == 0) throw Contracts.Except("input data structure cannot be 0-length: innerSTL"); - _length = yValues.Count; - Array.Resize(ref _seasonalComponent, _length); - Array.Resize(ref _trendComponent, _length); - Array.Resize(ref _residual, _length); + int length = yValues.Count; + Array.Resize(ref _seasonalComponent, length); + Array.Resize(ref _trendComponent, length); + Array.Resize(ref _residual, length); - Array.Resize(ref _s, _length); - Array.Resize(ref _t, _length); - Array.Resize(ref _detrendedY, _length); - Array.Resize(ref _c, _length + np * 2); - Array.Resize(ref _deseasonSeries, _length); + Array.Resize(ref _s, length); + Array.Resize(ref _t, length); + Array.Resize(ref _detrendedY, length); + Array.Resize(ref _c, length + np * 2); + Array.Resize(ref _deseasonSeries, length); Array.Resize(ref _cycleSubSeries, np); Array.Resize(ref _smoothedSubseries, np); - for (int i = 0; i < _length; ++i) + for (int i = 0; i < length; ++i) { _t[i] = 0; } @@ -170,7 +168,7 @@ public bool Decomposition(IReadOnlyList yValues, int np) private void Detrending(IReadOnlyList y, IReadOnlyList t, double[] detrendedY) { - for (int i = 0; i < _length; i++) + for (int i = 0; i < y.Count; i++) detrendedY[i] = y[i] - t[i]; } @@ -183,7 +181,7 @@ private bool CycleSubseriesSmooth(double[] detrendedY, int np, double[] c) } // obtain all the subseries - for (int i = 0; i < _length; i++) + for (int i = 0; i < detrendedY.Length; i++) { int cycleIndex = i % np; _cycleSubSeries[cycleIndex].Add(detrendedY[i]); @@ -239,7 +237,7 @@ private FastLoess LowPassFiltering(double[] c, int np) private void SmoothedCycleSubseriesDetrending(double[] c, FastLoess lowPass, double[] s) { - for (int i = 0; i < _length; i++) + for (int i = 0; i < c.Length; i++) { s[i] = c[i] - lowPass.Y[i]; } @@ -247,7 +245,7 @@ private void SmoothedCycleSubseriesDetrending(double[] c, FastLoess lowPass, dou private void Deseasonalizing(IReadOnlyList y, double[] s, double[] deseasonSeries) { - for (int i = 0; i < _length; i++) + for (int i = 0; i < y.Count; i++) { deseasonSeries[i] = y[i] - s[i]; } @@ -258,7 +256,7 @@ private void TrendSmooth(double[] deseasonSeries, int np, double[] t) List virtualDeseasonSeries = VirtualXValuesProvider.GetXValues(deseasonSeries.Length); FastLoess trender = new FastLoess(virtualDeseasonSeries, deseasonSeries, _isTemporal, Nt(np)); trender.Estimate(); - for (int i = 0; i < _length; i++) + for (int i = 0; i < deseasonSeries.Length; i++) { t[i] = trender.Y[i]; } diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 4c5b3be20b..293a2c3e56 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -40,7 +40,7 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool Contracts.CheckValue(xValues, nameof(xValues)); Contracts.CheckValue(yValues, nameof(yValues)); - if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) + if (xValues.Count < LoessBasicParameters.MinTimeSeriesLength || yValues.Count < LoessBasicParameters.MinTimeSeriesLength) throw Contracts.Except("input data structure cannot be 0-length: lowess"); if (xValues.Count != yValues.Count) @@ -81,7 +81,7 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, int r Contracts.CheckValue(xValues, nameof(xValues)); Contracts.CheckValue(yValues, nameof(yValues)); - if (xValues.Count < BasicParameters.MinTimeSeriesLength || yValues.Count < BasicParameters.MinTimeSeriesLength) + if (xValues.Count < LoessBasicParameters.MinTimeSeriesLength || yValues.Count < LoessBasicParameters.MinTimeSeriesLength) throw Contracts.Except("input data structure cannot be 0-length: lowess"); if (xValues.Count != yValues.Count) diff --git a/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs new file mode 100644 index 0000000000..52f07926c6 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs @@ -0,0 +1,10 @@ +namespace Microsoft.ML.TimeSeries +{ + internal class LoessBasicParameters + { + /// + /// the minimum length of a valid time series. a time series with length equals 2 is so trivial. when less than 2, meaningless. + /// + public const int MinTimeSeriesLength = 3; + } +} diff --git a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs index 1d6c45cae0..b27d9f258b 100644 --- a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs @@ -5,15 +5,6 @@ namespace Microsoft.ML.TimeSeries { internal class MathUtility { - /// - /// calculate the standard sigmoid function - /// - /// the input value - public static double Sigmoid(double x) - { - return 1.0 / (1.0 + Math.Exp(-x)); - } - /// /// use quick-sort like method to obtain the median value. /// the complexity in expectation is O(n), which is faster than using quickSort. diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 0b2d94f59c..e49b48218d 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Cryptography.X509Certificates; using Microsoft.ML.CommandLine; using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; From 1b90669a00143a4281c58087bc7cd183fc6dd270 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 17 Jun 2020 14:10:10 +0800 Subject: [PATCH 34/49] remove max neighbor number constraint --- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 293a2c3e56..0c8ed87197 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -59,12 +59,6 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool else if (_r < LoessConfiguration.MinimumNeighborCount) // the neighbors should be at least 2, or the matrix operations would encounter issues. _r = LoessConfiguration.MinimumNeighborCount; - // DEBUG - // control the performance - if (_r >= LoessConfiguration.MaximumNeighborCount) - { - _r = LoessConfiguration.MaximumNeighborCount; - } Init(xValues, yValues); } From 92b09632d60953602e3f9208fa9741e071dc5af7 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 17 Jun 2020 14:12:26 +0800 Subject: [PATCH 35/49] remove the max neightbor count constraint --- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 0c8ed87197..ef52a796e8 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -175,12 +175,6 @@ public static double Tricube(double value) /// internal class LoessConfiguration { - /// - /// this value is used for performance concern. when the length of the series goes large, a ratio of neighbors will be significant, - /// which leads to unsatisfied slow. so this value is used to bound the maximum # of neighbors one epoch can have. - /// - public const int MaximumNeighborCount = 100; - /// /// minumum number of neighbor counts, to apply underlying regression analysis. /// this number should be even, so that neighbors on left/right side of a given data point is balanced. unbalanced neighbors would make the local-weighted regression biased noticeably at corner cases. From 59e7f2bb48093e1d11ca9c776fb2febbe5454741 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 17 Jun 2020 14:32:02 +0800 Subject: [PATCH 36/49] update SrCnnEntireDetectOptions, move input/output column name out; fix unit tests --- .../ExtensionsCatalog.cs | 11 ++++---- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 2 +- .../SrCnnEntireAnomalyDetector.cs | 26 ++++++++----------- .../TimeSeriesDirectApi.cs | 8 ++---- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs index 940e2e6f48..151e51bea6 100644 --- a/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs +++ b/src/Microsoft.ML.TimeSeries/ExtensionsCatalog.cs @@ -179,15 +179,13 @@ public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog { var options = new SrCnnEntireAnomalyDetectorOptions() { - InputColumnName = inputColumnName, - OutputColumnName = outputColumnName, Threshold = threshold, BatchSize = batchSize, Sensitivity = sensitivity, DetectMode = detectMode, }; - return DetectEntireAnomalyBySrCnn(catalog, input, options); + return DetectEntireAnomalyBySrCnn(catalog, input, outputColumnName, inputColumnName, options); } /// @@ -195,6 +193,9 @@ public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog /// /// The AnomalyDetectionCatalog. /// Input DataView. + /// Name of the column resulting from data processing of . + /// The column data is a vector of . The length of this vector varies depending on . + /// Name of column to process. The column data must be . /// Defines the settings of the load operation. /// /// @@ -203,8 +204,8 @@ public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog /// ]]> /// /// - public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog catalog, IDataView input, SrCnnEntireAnomalyDetectorOptions options = null) - => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, options); + public static IDataView DetectEntireAnomalyBySrCnn(this AnomalyDetectionCatalog catalog, IDataView input, string outputColumnName, string inputColumnName, SrCnnEntireAnomalyDetectorOptions options) + => new SrCnnEntireAnomalyDetector(CatalogUtils.GetEnvironment(catalog), input, outputColumnName, inputColumnName, options); /// /// Create , which localizes root causes using decision tree algorithm. diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 847dc241aa..17967772ef 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -237,7 +237,7 @@ private FastLoess LowPassFiltering(double[] c, int np) private void SmoothedCycleSubseriesDetrending(double[] c, FastLoess lowPass, double[] s) { - for (int i = 0; i < c.Length; i++) + for (int i = 0; i < s.Length; i++) { s[i] = c[i] - lowPass.Y[i]; } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index e49b48218d..9e05240367 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -57,14 +57,6 @@ public enum SrCnnDeseasonalityMode } public sealed class SrCnnEntireAnomalyDetectorOptions { - [Argument(ArgumentType.Required, HelpText = "The name of the input column.", ShortName = "input", - SortOrder = 1, Purpose = SpecialPurpose.ColumnName)] - public string InputColumnName; - - [Argument(ArgumentType.Required, HelpText = "The name of the output column.", ShortName = "output", - SortOrder = 2)] - public string OutputColumnName; - [Argument(ArgumentType.AtMostOnce, HelpText = "The threshold to determine anomaly, score larger than the threshold is considered as anomaly.", SortOrder = 3, ShortName = "thr")] public double Threshold = Defaults.Threshold; @@ -140,13 +132,14 @@ internal static class Defaults /// /// /// - /// + /// internal sealed class SrCnnEntireAnomalyDetector : BatchDataViewMapperBase { private const int MinBatchSize = 12; private static readonly int[] _outputLengthArray = {3, 7, 4}; private readonly SrCnnEntireAnomalyDetectorOptions _options; + private readonly string _inputColumnName; private readonly int _outputLength; private readonly Bindings _bindings; @@ -191,22 +184,25 @@ public Func GetDependencies(Func predicate) } } - public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, SrCnnEntireAnomalyDetectorOptions options) + public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string outputColumnName, string inputColumnName, SrCnnEntireAnomalyDetectorOptions options) : base(env, nameof(SrCnnEntireAnomalyDetector), input) { + Host.CheckValue(outputColumnName, nameof(outputColumnName)); + + Host.CheckValue(inputColumnName, nameof(inputColumnName)); + _inputColumnName = inputColumnName; + Host.CheckValue(options, nameof(options)); CheckOptionArguments(options); _options = options; _outputLength = _outputLengthArray[(int)options.DetectMode]; - _bindings = new Bindings(input.Schema, options.InputColumnName, options.OutputColumnName, new VectorDataViewType(NumberDataViewType.Double, _outputLength)); + _bindings = new Bindings(input.Schema, inputColumnName, outputColumnName, new VectorDataViewType(NumberDataViewType.Double, _outputLength)); } private void CheckOptionArguments(SrCnnEntireAnomalyDetectorOptions options) { - Host.CheckValue(options.InputColumnName, nameof(options.InputColumnName)); - Host.CheckUserArg(options.Period >= 0, nameof(options.Period), "Must be integer equal or greater than 0."); Host.CheckUserArg(options.BatchSize == -1 || options.BatchSize >= MinBatchSize, nameof(options.BatchSize), "Must be -1 or no less than 12."); @@ -230,7 +226,7 @@ protected override Delegate[] CreateGetters(DataViewRowCursor input, Batch curre { if (!SchemaBindings.AnyNewColumnsActive(x => active[x])) return new Delegate[1]; - return new[] { currentBatch.CreateGetter(input, _options.InputColumnName) }; + return new[] { currentBatch.CreateGetter(input, _inputColumnName) }; } protected override Batch CreateBatch(DataViewRowCursor input) @@ -248,7 +244,7 @@ protected override Func GetLastInBatchDelegate(DataViewRowCursor input) protected override ValueGetter GetLookAheadGetter(DataViewRowCursor input) { - return input.GetGetter(input.Schema[_options.InputColumnName]); + return input.GetGetter(input.Schema[_inputColumnName]); } protected override Func GetSchemaBindingDependencies(Func predicate) diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index 9ba2aed6f0..ae7935e7c5 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -679,8 +679,6 @@ public void TestSrCnnAnomalyDetectorWithSeasonalData( // Do batch anomaly detection var options = new SrCnnEntireAnomalyDetectorOptions() { - InputColumnName = inputColumnName, - OutputColumnName = outputColumnName, Threshold = 0.3, BatchSize = -1, Sensitivity = 53.0, @@ -689,7 +687,7 @@ public void TestSrCnnAnomalyDetectorWithSeasonalData( DeseasonalityMode = mode }; - var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, options); + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, options); // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable( @@ -721,8 +719,6 @@ public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData( // Do batch anomaly detection var options = new SrCnnEntireAnomalyDetectorOptions() { - InputColumnName = inputColumnName, - OutputColumnName = outputColumnName, Threshold = 0.23, BatchSize = -1, Sensitivity = 53.0, @@ -731,7 +727,7 @@ public void TestSrCnnAnomalyDetectorWithSeasonalAnomalyData( DeseasonalityMode = mode }; - var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, options); + var outputDataView = ml.AnomalyDetection.DetectEntireAnomalyBySrCnn(dataView, outputColumnName, inputColumnName, options); // Getting the data of the newly created column as an IEnumerable of SrCnnAnomalyDetection. var predictionColumn = ml.Data.CreateEnumerable( From b38c1231a02cc5953126e13d8c10f25c0ae77367 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 17 Jun 2020 14:48:43 +0800 Subject: [PATCH 37/49] refactor the constructor of Loess --- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 4 +- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 45 +++++--------------- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index b0325eb280..166646e3c0 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -48,7 +48,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b if (r == -1) _smoother = new Loess(_x, _y, isTemporal); else - _smoother = new Loess(_x, _y, r, isTemporal); + _smoother = new Loess(_x, _y, isTemporal, r); } else { @@ -65,7 +65,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b if (r == -1) _smoother = new Loess(sampleX, sampleY, isTemporal); else - _smoother = new Loess(sampleX, sampleY, r, isTemporal); + _smoother = new Loess(sampleX, sampleY, isTemporal, r); } } diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index ef52a796e8..1fc6a51ddd 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -30,12 +30,13 @@ internal class Loess /// /// Initializes a new instance of the class. - /// constructing the least square algorithm + /// constructing the least square algorithm. specified with the # of neighbors /// /// the corresponding x-axis value /// the corresponding y-axis value /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal) + /// the smoothing range. If it is not specified, the algorithm will estimate the value of r by ratio. + public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal, int? r = null) { Contracts.CheckValue(xValues, nameof(xValues)); Contracts.CheckValue(yValues, nameof(yValues)); @@ -51,9 +52,15 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool _length = xValues.Count; _isTemporal = isTemporal; - _r = (int)(_length * LoessConfiguration.F); + if (r == null) + { + _r = (int)(_length * LoessConfiguration.F); + } + else + { + _r = (int)r; + } - // r cannot be equal to length. if (_r >= _length) _r = _length - 1; else if (_r < LoessConfiguration.MinimumNeighborCount) // the neighbors should be at least 2, or the matrix operations would encounter issues. @@ -62,36 +69,6 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool Init(xValues, yValues); } - /// - /// Initializes a new instance of the class. - /// constructing the least square algorithm. specified with the # of neighbors - /// - /// the corresponding x-axis value - /// the corresponding y-axis value - /// the smoothing range is not determined by the ratio, but be specified externally. (which can exceed the length of the list) - /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - public Loess(IReadOnlyList xValues, IReadOnlyList yValues, int r, bool isTemporal) - { - Contracts.CheckValue(xValues, nameof(xValues)); - Contracts.CheckValue(yValues, nameof(yValues)); - - if (xValues.Count < LoessBasicParameters.MinTimeSeriesLength || yValues.Count < LoessBasicParameters.MinTimeSeriesLength) - throw Contracts.Except("input data structure cannot be 0-length: lowess"); - - if (xValues.Count != yValues.Count) - throw Contracts.Except("the x-axis length should be equal to y-axis length!: lowess"); - - _neighbors = new Dictionary(); - - _length = xValues.Count; - _isTemporal = isTemporal; - - _r = r; - if (_r < LoessConfiguration.MinimumNeighborCount) // the neighbors should be at least 2, or the matrix operations would encounter issues. - _r = LoessConfiguration.MinimumNeighborCount; - Init(xValues, yValues); - } - /// /// initialize the signal with basic checking /// From d72d97b7c0be2970c1c95f861b51a72729776bd8 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Wed, 17 Jun 2020 14:50:38 +0800 Subject: [PATCH 38/49] remove unused imports --- .../Deseasonality/DeseasonalityBase.cs | 6 +----- src/Microsoft.ML.TimeSeries/STL/MathsTools.cs | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs index 0759341c2b..7381f61392 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.ML.TimeSeries +namespace Microsoft.ML.TimeSeries { internal abstract class DeseasonalityBase { diff --git a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs index b27d9f258b..4923a69ab7 100644 --- a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Microsoft.ML.TimeSeries { From c58a45bef22d83edc5b6a7d50accf12d5e8aff71 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 18 Jun 2020 11:40:21 +0800 Subject: [PATCH 39/49] refactor and optimization --- .../AbstractPolynomialModel.cs | 22 +++++++++ .../PolynomialModel/LinearModel.cs | 19 +++++++ .../PolynomialModel/PolynomialModel.cs | 31 ++++++++++++ src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 3 +- .../STL/LeastSquares.cs | 49 +++---------------- .../STL/LocalRegression.cs | 6 +-- 6 files changed, 83 insertions(+), 47 deletions(-) create mode 100644 src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs create mode 100644 src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs create mode 100644 src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs new file mode 100644 index 0000000000..ae44a3e397 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// This class calculates f(x) = k0 + k1 * x + k2 * x^2 + k3 * x^3 ... kn * x^n with given x and coefs + /// + internal abstract class AbstractPolynomialModel + { + protected IReadOnlyList Coeffs; + + public AbstractPolynomialModel(IReadOnlyList coeffs) + { + Contracts.CheckValue(coeffs, nameof(coeffs)); + Coeffs = coeffs; + } + + public abstract double Y(double x); + } +} diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs new file mode 100644 index 0000000000..50787bd51c --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.TimeSeries +{ + internal class LinearModel : AbstractPolynomialModel + { + public LinearModel(IReadOnlyList coeffs) + : base(coeffs) + { + Contracts.CheckParam(coeffs.Count == 2, nameof(coeffs), "must contain exact 2 elements."); + } + + public override double Y(double x) + { + return Coeffs[0] + (Coeffs[1] * x); + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs new file mode 100644 index 0000000000..b37a81d218 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Microsoft.ML.TimeSeries +{ + /// + /// indicate a general polynomial model + /// + internal class PolynomialModel : AbstractPolynomialModel + { + public PolynomialModel(IReadOnlyList coeffs) + : base(coeffs) + { + } + + /// + /// calculate the y value by given the x value, under this model + /// + /// the specific x value + public override double Y(double x) + { + double result = Coeffs[0]; + double p = 1.0; + for (int i = 1; i < Coeffs.Count; i++) + { + p *= x; + result += Coeffs[i] * p; + } + return result; + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 17967772ef..6351c0298f 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -33,7 +33,8 @@ internal class InnerStl private const int Ni = 2; /// - /// the number of robustness iterations of the outer loop + /// the number of robustness iterations of the outer loop. This parameter is not used in this implementation as we simplify the implementation. + /// Keep this parameter here as it is listed as one of the six parameters described in the original paper. /// private const int No = 10; diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index 1a314087d6..bbc6a10cd1 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -37,7 +37,7 @@ public LeastSquares(List x, List y) /// y=b0+b1x, but the penalty is weighted /// /// the weighted least squares. note that the weight should be non-negative, and equal length to data - public PolynomialModel RegressionDegreeOneWeighted(List weights) + public AbstractPolynomialModel RegressionDegreeOneWeighted(List weights) { Contracts.CheckValue(weights, nameof(weights)); @@ -45,13 +45,7 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) if (weights.Count != _length) throw Contracts.Except("the weight vector is not equal length to the data points"); - foreach (double value in weights) - { - if (value < 0) - throw Contracts.Except("the value in weights should be non-negative!"); - } - - // This part unfold the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W), sqrt(W) .* X] + // This part unfolds the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W), sqrt(W) .* X] double sum00 = 0; double sum01 = 0; double sum10 = 0; @@ -67,7 +61,7 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) sum11 += temp; } - /* calculating the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is + /* calculate the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. */ double a = sum00; @@ -80,7 +74,7 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) double reverseS10 = -b / divider; double reverseS11 = a / divider; - // This part unfold the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W) .* Y] + // This part unfolds the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W) .* Y] double fy0 = 0; double fy1 = 0; for (int i = 0; i < _length; i++) @@ -93,40 +87,9 @@ public PolynomialModel RegressionDegreeOneWeighted(List weights) double b0 = reverseS00 * fy0 + reverseS01 * fy1; double b1 = reverseS10 * fy0 + reverseS11 * fy1; - List results = new List(){ b0, b1 }; - - return new PolynomialModel(results); - } - } - - /// - /// indicate a specific polynomial model - /// - internal class PolynomialModel - { - private readonly List _coeffs; + double[] results = new double[2] { b0, b1 }; - public PolynomialModel(ICollection coeffs) - { - Contracts.CheckValue(coeffs, nameof(coeffs)); - - _coeffs = new List(coeffs); - } - - /// - /// calculate the y value by given the x value, under this model - /// - /// the specific x value - public double Y(double x) - { - double result = _coeffs[0]; - double p = 1.0; - for (int i = 1; i < _coeffs.Count; i++) - { - p *= x; - result += _coeffs[i] * p; - } - return result; + return new LinearModel(results); } } } diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index ae0e0737a2..1c0ae2279a 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -12,12 +12,12 @@ internal class LocalRegression private const double NumericalThreshold = 1.0e-10; private readonly IReadOnlyList _x; private readonly IReadOnlyList _y; - private int _length; + private readonly int _length; /// /// the model is learned by several iterations of local weighted regression. /// - private PolynomialModel _model; + private AbstractPolynomialModel _model; /// /// Initializes a new instance of the class. @@ -241,7 +241,7 @@ public double Y(double xValue) return _model.Y(xValue); } - private PolynomialModel Regression() + private AbstractPolynomialModel Regression() { LeastSquares ls = new LeastSquares(NeighborsX, NeighborsY); return ls.RegressionDegreeOneWeighted(Weights); From 39393805822558221476db1c6dadca5de797d8e5 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 18 Jun 2020 12:20:27 +0800 Subject: [PATCH 40/49] optimize --- .../PolynomialModel/LinearModel.cs | 16 +++++- .../PolynomialModel/PolynomialModel.cs | 4 +- .../STL/LeastSquares.cs | 8 +-- .../STL/LocalRegression.cs | 52 +++++++++++-------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs index 50787bd51c..6d6159ca18 100644 --- a/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs +++ b/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs @@ -3,17 +3,29 @@ namespace Microsoft.ML.TimeSeries { - internal class LinearModel : AbstractPolynomialModel + /// + /// This class calculates f(x) = b0 + b1 * x + /// + internal sealed class LinearModel : AbstractPolynomialModel { + /// + /// Store the coefficients in member variables for better performance. + /// + private readonly double _b0; + private readonly double _b1; + public LinearModel(IReadOnlyList coeffs) : base(coeffs) { Contracts.CheckParam(coeffs.Count == 2, nameof(coeffs), "must contain exact 2 elements."); + + _b0 = coeffs[0]; + _b1 = coeffs[1]; } public override double Y(double x) { - return Coeffs[0] + (Coeffs[1] * x); + return _b0 + (_b1 * x); } } } diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs index b37a81d218..07794f606b 100644 --- a/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs +++ b/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs @@ -3,9 +3,9 @@ namespace Microsoft.ML.TimeSeries { /// - /// indicate a general polynomial model + /// a general polynomial model /// - internal class PolynomialModel : AbstractPolynomialModel + internal sealed class PolynomialModel : AbstractPolynomialModel { public PolynomialModel(IReadOnlyList coeffs) : base(coeffs) diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index bbc6a10cd1..2d0d924a52 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -9,8 +9,8 @@ namespace Microsoft.ML.TimeSeries /// internal class LeastSquares { - private readonly List _x; - private readonly List _y; + private readonly IReadOnlyList _x; + private readonly IReadOnlyList _y; private readonly int _length; /// @@ -19,7 +19,7 @@ internal class LeastSquares /// /// the corresponding x-axis value /// the corresponding y-axis value - public LeastSquares(List x, List y) + public LeastSquares(IReadOnlyList x, IReadOnlyList y) { Contracts.CheckValue(x, nameof(x)); Contracts.CheckValue(y, nameof(y)); @@ -37,7 +37,7 @@ public LeastSquares(List x, List y) /// y=b0+b1x, but the penalty is weighted /// /// the weighted least squares. note that the weight should be non-negative, and equal length to data - public AbstractPolynomialModel RegressionDegreeOneWeighted(List weights) + public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList weights) { Contracts.CheckValue(weights, nameof(weights)); diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index 1c0ae2279a..d005897442 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -44,10 +44,6 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel _length = _x.Count; SelfIndex = selfIndex; - NeighborsX = new List(); - NeighborsY = new List(); - Weights = new List(); - int startIndex = selfIndex; int endIndex = selfIndex; double selfValue = _x[SelfIndex]; @@ -88,6 +84,11 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel StartIndex = startIndex; EndIndex = endIndex; + var neighborsCount = EndIndex - StartIndex + 1; + NeighborsX = new double[neighborsCount]; + NeighborsY = new double[neighborsCount]; + Weights = new double[neighborsCount]; + double leftRange = selfValue - _x[startIndex]; double rightRange = _x[endIndex] - selfValue; double range = Math.Max(leftRange, rightRange); @@ -96,20 +97,20 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel { for (int i = StartIndex; i <= EndIndex; i++) { - NeighborsX.Add(_x[i]); - NeighborsY.Add(_y[i]); - Weights.Add(WeightMethod.Tricube((_x[i] - selfValue) / range)); + NeighborsX[i - StartIndex] = _x[i]; + NeighborsY[i - StartIndex] = _y[i]; + Weights[i - StartIndex] = WeightMethod.Tricube((_x[i] - selfValue) / range); } } else { for (int i = StartIndex; i <= EndIndex; i++) { - NeighborsX.Add(_x[i]); - NeighborsY.Add(_y[i]); + NeighborsX[i - StartIndex] = _x[i]; + NeighborsY[i - StartIndex] = _y[i]; // since we do not consider the local/temporal information, all the neighbors share same weight for further weighted regression - Weights.Add(1.0); + Weights[i - StartIndex] = 1.0; } } } @@ -123,6 +124,11 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel double rightRange = _x[EndIndex] - selfValue; double range = Math.Max(leftRange, rightRange); + var neighborsCount = EndIndex - StartIndex + 1; + NeighborsX = new double[neighborsCount]; + NeighborsY = new double[neighborsCount]; + Weights = new double[neighborsCount]; + // this is the slight modification of the weighting calculation range = range * r / (_length - 1); @@ -130,20 +136,20 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel { for (int i = StartIndex; i <= EndIndex; i++) { - NeighborsX.Add(_x[i]); - NeighborsY.Add(_y[i]); - Weights.Add(WeightMethod.Tricube((_x[i] - selfValue) / range)); + NeighborsX[i - StartIndex] = _x[i]; + NeighborsY[i - StartIndex] = _y[i]; + Weights[i - StartIndex] = WeightMethod.Tricube((_x[i] - selfValue) / range); } } else { for (int i = StartIndex; i <= EndIndex; i++) { - NeighborsX.Add(_x[i]); - NeighborsY.Add(_y[i]); + NeighborsX[i - StartIndex] = _x[i]; + NeighborsY[i - StartIndex] = _y[i]; // since we do not consider the local/temporal information, all the neighbors share same weight for further weighted regression - Weights.Add(1.0); + Weights[i - StartIndex] = 1.0; } } } @@ -152,17 +158,17 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel /// /// the values of the y-axis of the neighbors (include the self point) /// - public List NeighborsY { get; private set; } + public double[] NeighborsY { get; private set; } /// /// the values of the x-axis of the neighbors (include the self point) /// - public List NeighborsX { get; private set; } + public double[] NeighborsX { get; private set; } /// /// the weights for each neighbor. this is used for weighted least squares. /// - public List Weights { get; private set; } + public double[] Weights { get; private set; } /// /// the start index of the neighbors (inclusive) @@ -186,9 +192,9 @@ private void Estimate() _model = Regression(); // calculate the errors - var errors = new double[NeighborsX.Count]; - var absErrors = new double[NeighborsX.Count]; - for (int i = 0; i < NeighborsX.Count; i++) + var errors = new double[NeighborsX.Length]; + var absErrors = new double[NeighborsX.Length]; + for (int i = 0; i < NeighborsX.Length; i++) { double error = NeighborsY[i] - _model.Y(NeighborsX[i]); errors[i] = error; @@ -209,7 +215,7 @@ private void Estimate() } // update new weights. - for (int i = 0; i < Weights.Count; i++) + for (int i = 0; i < Weights.Length; i++) { Weights[i] *= deltas[i]; } From 7e48255dcf4744694d94d1e49b6c20e668631021 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Thu, 18 Jun 2020 16:10:38 +0800 Subject: [PATCH 41/49] unfold pow(x, 2) to x * x for performance optimization --- src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs | 5 +++-- .../SrCnnEntireAnomalyDetector.cs | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index 2d0d924a52..3461a51f97 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -50,9 +50,10 @@ public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList double sum01 = 0; double sum10 = 0; double sum11 = 0; + double temp; for (int k = 0; k < _length; k++) { - double temp = weights[k]; + temp = weights[k]; sum00 += temp; temp *= _x[k]; sum01 += temp; @@ -79,7 +80,7 @@ public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList double fy1 = 0; for (int i = 0; i < _length; i++) { - double temp = weights[i] * _y[i]; + temp = weights[i] * _y[i]; fy0 += temp; fy1 += temp * _x[i]; } diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 9e05240367..c885c7c491 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -504,7 +504,7 @@ private void SpectralResidual(double[] values, double[][] results, double thresh Array.Resize(ref _magLogList, length); for (int i = 0; i < length; ++i) { - _magList[i] = Math.Sqrt((Math.Pow(_fftRe[i], 2) + Math.Pow(_fftIm[i], 2))); + _magList[i] = Math.Sqrt(_fftRe[i] * _fftRe[i] + _fftIm[i] * _fftIm[i]); if (_magList[i] > _eps) { _magLogList[i] = Math.Log(_magList[i]); @@ -548,7 +548,7 @@ private void SpectralResidual(double[] values, double[][] results, double thresh Array.Resize(ref _ifftMagList, length); for (int i = 0; i < length; ++i) { - _ifftMagList[i] = Math.Sqrt((Math.Pow(_ifftRe[i], 2) + Math.Pow(_ifftIm[i], 2))); + _ifftMagList[i] = Math.Sqrt(_ifftRe[i] * _ifftRe[i] + _ifftIm[i] * _ifftIm[i]); } AverageFilter(_ifftMagList, Math.Min(_ifftMagList.Length, _judgementWindowSize)); @@ -828,11 +828,11 @@ private double CalculateInterpolate(List> values, int idx) var n = values.Count; double sumX = values.Sum(item => item.Item1); double sumY = values.Sum(item => item.Item2); - double sumXX = values.Sum(item => Math.Pow(item.Item1, 2)); + double sumXX = values.Sum(item => item.Item1 * item.Item1); double sumXY = values.Sum(item => item.Item1 * item.Item2); - var a = ((double)n * sumXY - sumX * sumY) / ((double)n * sumXX - sumX * sumX); - var b = (sumXX * sumY - sumX * sumXY) / ((double)n * sumXX - sumX * sumX); + var a = ((n * sumXY) - (sumX * sumY)) / ((n * sumXX) - (sumX * sumX)); + var b = ((sumXX * sumY) - (sumX * sumXY)) / ((n * sumXX) - (sumX * sumX)); return a * (double)idx + b; } From c3f74d52aded3622697c8764784bf3bb1dbdb021 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Mon, 22 Jun 2020 11:15:08 +0800 Subject: [PATCH 42/49] refactor polynomial model class and deseasonality functions, refine comments --- .../Deseasonality/DeseasonalityBase.cs | 7 -- .../Deseasonality/MeanDeseasonality.cs | 45 ------- .../Deseasonality/MedianDeseasonality.cs | 44 ------- .../Deseasonality/StlDeseasonality.cs | 31 ----- .../Microsoft.ML.TimeSeries.csproj | 4 - .../AbstractPolynomialModel.cs | 22 ---- .../PolynomialModel/LinearModel.cs | 31 ----- .../PolynomialModel/PolynomialModel.cs | 31 ----- .../STL/Deseasonality.cs | 119 ++++++++++++++++++ src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 13 +- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 50 ++++---- .../STL/LeastSquares.cs | 14 +-- .../STL/LocalRegression.cs | 36 +++--- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 44 +++---- .../STL/LoessBasicParameters.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/MathsTools.cs | 16 +-- .../STL/PolynomialModel.cs | 71 +++++++++++ .../SrCnnEntireAnomalyDetector.cs | 3 +- 18 files changed, 278 insertions(+), 305 deletions(-) delete mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs delete mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs delete mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs delete mode 100644 src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs delete mode 100644 src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs delete mode 100644 src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs delete mode 100644 src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs create mode 100644 src/Microsoft.ML.TimeSeries/STL/Deseasonality.cs create mode 100644 src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs deleted file mode 100644 index 7381f61392..0000000000 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/DeseasonalityBase.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Microsoft.ML.TimeSeries -{ - internal abstract class DeseasonalityBase - { - public abstract void Deseasonality(ref double[] values, int period, ref double[] results); - } -} diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs deleted file mode 100644 index 7cd7fc803d..0000000000 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MeanDeseasonality.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -namespace Microsoft.ML.TimeSeries.Deseasonality -{ - internal sealed class MeanDeseasonality : DeseasonalityBase - { - private double[] _circularComponent; - - public override void Deseasonality(ref double[] values, int period, ref double[] results) - { - Array.Resize(ref _circularComponent, period); - - var length = values.Length; - - // initialize the circurlar component to 0. - for (int i = 0; i < period; ++i) - { - _circularComponent[i] = 0; - } - - // sum up values that locates at the same position in one period. - for (int i = 0; i < length; ++i) - { - var indexInPeriod = i % period; - _circularComponent[indexInPeriod] += values[i]; - } - - // calculate the mean value as circular component. - var cnt = (length - 1) / period; - var rest = (length - 1) % period; - for (int i = 0; i < period; ++i) - { - var lastCircle = i <= rest ? 1 : 0; - _circularComponent[i] = _circularComponent[i] / (cnt + lastCircle); - } - - // substract the circular component from the original series. - for (int i = 0; i < length; ++i) - { - var indexInPeriod = i % period; - results[i] -= _circularComponent[indexInPeriod]; - } - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs deleted file mode 100644 index dfe5fea10d..0000000000 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/MedianDeseasonality.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Microsoft.ML.TimeSeries -{ - internal sealed class MedianDeseasonality : DeseasonalityBase - { - private List[] _subSeries; - private double[] _circularComponent; - - public override void Deseasonality(ref double[] values, int period, ref double[] results) - { - Array.Resize(ref _circularComponent, period); - Array.Resize(ref _subSeries, period); - - var length = values.Length; - - for (int i = 0; i < period; ++i) - { - _subSeries[i] = new List(); - } - - // split the original series into #period subseries. - for (int i = 0; i < length; ++i) - { - var indexInPeriod = i % period; - _subSeries[indexInPeriod].Add(values[i]); - } - - // calculate the median value as circular component. - for (int i = 0; i < period; ++i) - { - _circularComponent[i] = MathUtility.QuickMedian(_subSeries[i]); - } - - // substract the circular component from the original series. - for (int i = 0; i < length; ++i) - { - var indexInPeriod = i % period; - results[i] -= _circularComponent[indexInPeriod]; - } - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs deleted file mode 100644 index 655dbb7e04..0000000000 --- a/src/Microsoft.ML.TimeSeries/Deseasonality/StlDeseasonality.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Microsoft.ML.TimeSeries -{ - internal sealed class StlDeseasonality : DeseasonalityBase - { - private readonly InnerStl _stl; - - public StlDeseasonality() - { - _stl = new InnerStl(true); - } - - public override void Deseasonality(ref double[] values, int period, ref double[] results) - { - bool success = _stl.Decomposition(values, period); - if (success) - { - for (int i = 0; i < _stl.Residual.Count; ++i) - { - results[i] = _stl.Residual[i]; - } - } - else - { - for (int i = 0; i < values.Length; ++i) - { - results[i] = values[i]; - } - } - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj index 6473cc0b57..c5b4550c51 100644 --- a/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj +++ b/src/Microsoft.ML.TimeSeries/Microsoft.ML.TimeSeries.csproj @@ -10,8 +10,4 @@ - - - - diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs deleted file mode 100644 index ae44a3e397..0000000000 --- a/src/Microsoft.ML.TimeSeries/PolynomialModel/AbstractPolynomialModel.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.ML.Runtime; - -namespace Microsoft.ML.TimeSeries -{ - /// - /// This class calculates f(x) = k0 + k1 * x + k2 * x^2 + k3 * x^3 ... kn * x^n with given x and coefs - /// - internal abstract class AbstractPolynomialModel - { - protected IReadOnlyList Coeffs; - - public AbstractPolynomialModel(IReadOnlyList coeffs) - { - Contracts.CheckValue(coeffs, nameof(coeffs)); - Coeffs = coeffs; - } - - public abstract double Y(double x); - } -} diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs deleted file mode 100644 index 6d6159ca18..0000000000 --- a/src/Microsoft.ML.TimeSeries/PolynomialModel/LinearModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using Microsoft.ML.Runtime; - -namespace Microsoft.ML.TimeSeries -{ - /// - /// This class calculates f(x) = b0 + b1 * x - /// - internal sealed class LinearModel : AbstractPolynomialModel - { - /// - /// Store the coefficients in member variables for better performance. - /// - private readonly double _b0; - private readonly double _b1; - - public LinearModel(IReadOnlyList coeffs) - : base(coeffs) - { - Contracts.CheckParam(coeffs.Count == 2, nameof(coeffs), "must contain exact 2 elements."); - - _b0 = coeffs[0]; - _b1 = coeffs[1]; - } - - public override double Y(double x) - { - return _b0 + (_b1 * x); - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs b/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs deleted file mode 100644 index 07794f606b..0000000000 --- a/src/Microsoft.ML.TimeSeries/PolynomialModel/PolynomialModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace Microsoft.ML.TimeSeries -{ - /// - /// a general polynomial model - /// - internal sealed class PolynomialModel : AbstractPolynomialModel - { - public PolynomialModel(IReadOnlyList coeffs) - : base(coeffs) - { - } - - /// - /// calculate the y value by given the x value, under this model - /// - /// the specific x value - public override double Y(double x) - { - double result = Coeffs[0]; - double p = 1.0; - for (int i = 1; i < Coeffs.Count; i++) - { - p *= x; - result += Coeffs[i] * p; - } - return result; - } - } -} diff --git a/src/Microsoft.ML.TimeSeries/STL/Deseasonality.cs b/src/Microsoft.ML.TimeSeries/STL/Deseasonality.cs new file mode 100644 index 0000000000..199950fd5c --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/STL/Deseasonality.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.ML.TimeSeries +{ + internal interface IDeseasonality + { + public abstract void Deseasonality(ref double[] values, int period, ref double[] results); + } + + internal sealed class MeanDeseasonality : IDeseasonality + { + private double[] _circularComponent; + + public void Deseasonality(ref double[] values, int period, ref double[] results) + { + Array.Resize(ref _circularComponent, period); + + var length = values.Length; + + // Initialize the circular component to 0. + for (int i = 0; i < period; ++i) + { + _circularComponent[i] = 0; + } + + // Sum up values that locates at the same position in one period. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + _circularComponent[indexInPeriod] += values[i]; + } + + // Calculate the mean value as circular component. + var cnt = (length - 1) / period; + var rest = (length - 1) % period; + for (int i = 0; i < period; ++i) + { + var lastCircle = i <= rest ? 1 : 0; + _circularComponent[i] = _circularComponent[i] / (cnt + lastCircle); + } + + // Substract the circular component from the original series. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + results[i] -= _circularComponent[indexInPeriod]; + } + } + } + + internal sealed class MedianDeseasonality : IDeseasonality + { + private List[] _subSeries; + private double[] _circularComponent; + + public void Deseasonality(ref double[] values, int period, ref double[] results) + { + Array.Resize(ref _circularComponent, period); + Array.Resize(ref _subSeries, period); + + var length = values.Length; + + for (int i = 0; i < period; ++i) + { + _subSeries[i] = new List(); + } + + // Split the original series into #period subseries. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + _subSeries[indexInPeriod].Add(values[i]); + } + + // Calculate the median value as circular component. + for (int i = 0; i < period; ++i) + { + _circularComponent[i] = MathUtility.QuickMedian(_subSeries[i]); + } + + // Substract the circular component from the original series. + for (int i = 0; i < length; ++i) + { + var indexInPeriod = i % period; + results[i] -= _circularComponent[indexInPeriod]; + } + } + } + + internal sealed class StlDeseasonality : IDeseasonality + { + private readonly InnerStl _stl; + + public StlDeseasonality() + { + _stl = new InnerStl(true); + } + + public void Deseasonality(ref double[] values, int period, ref double[] results) + { + bool success = _stl.Decomposition(values, period); + if (success) + { + for (int i = 0; i < _stl.Residual.Count; ++i) + { + results[i] = _stl.Residual[i]; + } + } + else + { + for (int i = 0; i < values.Length; ++i) + { + results[i] = values[i]; + } + } + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 166646e3c0..45f3d6b7d8 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -4,13 +4,13 @@ namespace Microsoft.ML.TimeSeries { /// - /// this is the fast version of Loess. there are several alternatives to improve the performance. this one is an approximation approach. + /// This is the fast version of Loess. there are several alternatives to improve the performance. this one is an approximation approach. /// the smoothing is conducted on a sample set, and then the values on the left points are assigned directly. /// internal class FastLoess { /// - /// this class is a sampling based method, so here specifies the sample size. + /// This class is a sampling based method, so here specifies the sample size. /// private const int _sampleSize = 100; @@ -42,7 +42,6 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b _y = yValues; _length = _y.Count; - // the sampling is not neccessary if (_length <= FastLoess._sampleSize) { if (r == -1) @@ -52,7 +51,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b } else { - // conduct sampling based strategy, to boost the performance. + // Conduct sampling based strategy, to boost the performance. double step = _length * 1.0 / FastLoess._sampleSize; var sampleX = new double[FastLoess._sampleSize]; var sampleY = new double[FastLoess._sampleSize]; @@ -70,12 +69,12 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b } /// - /// the estimated y values. this is the very cool smoothing method. + /// The estimated y values. this is the very cool smoothing method. /// public List Y { get; } /// - /// assign the smoothing values to all the data points, not only on the sample size. + /// Assign the smoothing values to all the data points, not only on the sample size. /// public void Estimate() { @@ -87,7 +86,7 @@ public void Estimate() } /// - /// estimate any y value by given any x value, event the x value is not one of the input points. + /// Estimate a y value by giving an x value, even if the x value is not one of the input points. /// public double EstimateY(double xValue) { diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 6351c0298f..1f34ede70a 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -11,7 +11,7 @@ internal class InnerStl private double[] _trendComponent; private double[] _residual; - // arrays for intermediate results + // Arrays for intermediate results private List[] _cycleSubSeries; private List[] _smoothedSubseries; @@ -22,26 +22,26 @@ internal class InnerStl private double[] _deseasonSeries; /// - /// the smoothing parameter for the seasonal component. - /// should be odd, and at least 7. + /// The smoothing parameter for the seasonal component. + /// This parameter should be odd, and at least 7. /// private const int Ns = 9; /// - /// the number of passes through the inner loop. /ref this value is set to 2, which works for many cases + /// The number of passes through the inner loop. This parameter is set to 2, which works for many cases. /// private const int Ni = 2; /// - /// the number of robustness iterations of the outer loop. This parameter is not used in this implementation as we simplify the implementation. + /// The number of robustness iterations of the outer loop. This parameter is not used in this implementation as we simplify the implementation. /// Keep this parameter here as it is listed as one of the six parameters described in the original paper. /// private const int No = 10; /// - /// the smoothing parameter for the low-pass filter. - /// /ref: should be the least odd integer greater than or equal to np. - /// it will preventing the trend and seasonal components from competing for the same variation in the data. + /// The smoothing parameter for the low-pass filter. + /// This parameter should be the least odd integer greater than or equal to np. + /// It will preventing the trend and seasonal components from competing for the same variation in the data. /// private int Nl(int np) { @@ -51,9 +51,9 @@ private int Nl(int np) } /// - /// the smoothing parameter for the trend component. - /// /ref: in order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen - /// s.t., satisty the following inequality. + /// The smoothing parameter for the trend component. + /// In order to avoid the trend ans seasonal components compete for variation in the data, the nt should be chosen + /// S.t., satisty the following inequality. /// private int Nt(int np) { @@ -66,9 +66,9 @@ private int Nt(int np) /// /// Initializes a new instance of the class. - /// for a time series, only with y values. assume the x-values are 0, 1, 2, ... - /// since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. - /// otherwise, the smoothing on seasonal component will be very complicated. + /// For a time series, only with y values. assume the x-values are 0, 1, 2, ... + /// Since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. + /// Otherwise, the smoothing on seasonal component will be very complicated. /// /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data public InnerStl(bool isTemporal) @@ -77,7 +77,7 @@ public InnerStl(bool isTemporal) } /// - /// the seasonal component + /// The seasonal component /// public IReadOnlyList SeasonalComponent { @@ -85,7 +85,7 @@ public IReadOnlyList SeasonalComponent } /// - /// the trend component + /// The trend component /// public IReadOnlyList TrendComponent { @@ -93,7 +93,7 @@ public IReadOnlyList TrendComponent } /// - /// the left component after seasonal and trend are eliminated. + /// The left component after seasonal and trend are eliminated. /// public IReadOnlyList Residual { @@ -101,10 +101,10 @@ public IReadOnlyList Residual } /// - /// the core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, - /// see section 2 and 3. especially section 2. + /// The core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, + /// See section 2 and 3. especially section 2. /// - /// return true if the process goes successfully. otherwise, return false. + /// Return true if the process goes successfully. otherwise, return false. public bool Decomposition(IReadOnlyList yValues, int np) { Contracts.CheckValue(yValues, nameof(yValues)); @@ -264,8 +264,8 @@ private void TrendSmooth(double[] deseasonSeries, int np, double[] t) } /// - /// this class provides the virtual x values for multi object usage. - /// the cache mechanism is used for performance consideration. + /// This class provides the virtual x values for multi object usage. + /// The cache mechanism is used for performance consideration. /// internal class VirtualXValuesProvider { @@ -277,10 +277,10 @@ static VirtualXValuesProvider() } /// - /// get a list of virtual x-axis values. the values are from 0 to length - 1. + /// Get a list of virtual x-axis values. the values are from 0 to length - 1. /// - /// specify the length you want to create the x values. - /// if this is cached, return directly. otherwise, create a new list and return + /// Specify the length you want to create the x values. + /// If this is cached, return directly. otherwise, create a new list and return internal static List GetXValues(int length) { lock (_xValuesPool) diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index 3461a51f97..789e72b016 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -4,8 +4,8 @@ namespace Microsoft.ML.TimeSeries { /// - /// this class is used to calculate the least squares of the scatterplots. - /// please check http://en.wikipedia.org/wiki/Least_squares for more details. + /// This class is used to calculate the least squares of the scatterplots. + /// Please check http://en.wikipedia.org/wiki/Least_squares for more details. /// internal class LeastSquares { @@ -15,10 +15,10 @@ internal class LeastSquares /// /// Initializes a new instance of the class. - /// constructing the least square algorithm. the input will be consumed directly without any copy, due to memory usage concern. + /// Constructing the least square algorithm. the input will be consumed directly without any copy, due to memory usage concern. /// - /// the corresponding x-axis value - /// the corresponding y-axis value + /// The corresponding x-axis value + /// The corresponding y-axis value public LeastSquares(IReadOnlyList x, IReadOnlyList y) { Contracts.CheckValue(x, nameof(x)); @@ -34,9 +34,9 @@ public LeastSquares(IReadOnlyList x, IReadOnlyList y) } /// - /// y=b0+b1x, but the penalty is weighted + /// y=b0+b1x, while the penalty is weighted /// - /// the weighted least squares. note that the weight should be non-negative, and equal length to data + /// The weighted least squares. note that the weight should be non-negative, and equal length to data public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList weights) { Contracts.CheckValue(weights, nameof(weights)); diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index d005897442..ebc677d5c3 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -5,7 +5,7 @@ namespace Microsoft.ML.TimeSeries { /// - /// this class is used to maintain the neighbors of a given particular point. + /// This class is used to maintain the neighbors of a given particular point. /// internal class LocalRegression { @@ -15,20 +15,20 @@ internal class LocalRegression private readonly int _length; /// - /// the model is learned by several iterations of local weighted regression. + /// The model is learned by several iterations of local weighted regression. /// private AbstractPolynomialModel _model; /// /// Initializes a new instance of the class. - /// construct the neighborhood information of a given point. note that the input series will not be copies again, due to + /// Construct the neighborhood information of a given point. note that the input series will not be copies again, due to /// memory usage concern. /// - /// the complete values of x-axis - /// the complete values of y-axis - /// the index of the current point - /// number of neighbors, usually should be less then n. if it is equal/larger than n, the weight has slight change. - /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + /// The complete values of x-axis + /// The complete values of y-axis + /// The index of the current point + /// Number of neighbors, usually should be less then n. if it is equal/larger than n, the weight has slight change. + /// If the regression is considered to take temporal information into account. In general, this is true if we are regressing a time series, and false if we are regressing scatter plot data public LocalRegression(IReadOnlyList x, IReadOnlyList y, int selfIndex, int r, bool isTemporal = true) { Contracts.CheckValue(x, nameof(x)); @@ -48,7 +48,7 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel int endIndex = selfIndex; double selfValue = _x[SelfIndex]; - // the normal case, the farthest neighbor is contained in the list. + // The farthest neighbor is contained in the list. This is the normal case. if (r < _length) { int left = r; @@ -156,32 +156,32 @@ public LocalRegression(IReadOnlyList x, IReadOnlyList y, int sel } /// - /// the values of the y-axis of the neighbors (include the self point) + /// The values of the y-axis of the neighbors (include the self point) /// public double[] NeighborsY { get; private set; } /// - /// the values of the x-axis of the neighbors (include the self point) + /// The values of the x-axis of the neighbors (include the self point) /// public double[] NeighborsX { get; private set; } /// - /// the weights for each neighbor. this is used for weighted least squares. + /// The weights for each neighbor. This is used for weighted least squares. /// public double[] Weights { get; private set; } /// - /// the start index of the neighbors (inclusive) + /// The start index of the neighbors (inclusive) /// public int StartIndex { get; private set; } /// - /// the end index of the neighbors (inclusive) + /// The end index of the neighbors (inclusive) /// public int EndIndex { get; private set; } /// - /// the index of the self point. the index is on the complete series, not only on the neighbor series. + /// The index of the self point. The index is on the complete series, not only on the neighbor series. /// public int SelfIndex { get; private set; } @@ -223,7 +223,7 @@ private void Estimate() } /// - /// get the best estimated y for the current value. + /// Get the best estimated y for the current value. /// public double Y() { @@ -235,9 +235,9 @@ public double Y() } /// - /// get the best estimated y for any given x-value, event not one of the observed point + /// Get the best estimated y for any given x-value, event not one of the observed point /// - /// any given x value + /// Any given x value public double Y(double xValue) { if (_model == null) diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index 1fc6a51ddd..d3feb719fe 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -13,14 +13,14 @@ internal class Loess private const double NumericalThreshold = 1.0e-10; /// - /// the ratio to determine the local region + /// The ratio to determine the local region /// private readonly int _r; private readonly bool _isTemporal; /// - /// key is the index of the given point, value is the corresponding neighbors of the given point. + /// Key is the index of the given point, value is the corresponding neighbors of the given point. /// private readonly Dictionary _neighbors; @@ -30,12 +30,12 @@ internal class Loess /// /// Initializes a new instance of the class. - /// constructing the least square algorithm. specified with the # of neighbors + /// Construct the least square algorithm specified with the number of neighbors /// - /// the corresponding x-axis value - /// the corresponding y-axis value - /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - /// the smoothing range. If it is not specified, the algorithm will estimate the value of r by ratio. + /// The corresponding x-axis value + /// The corresponding y-axis value + /// If the regression is considered to take temporal information into account. In general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + /// The smoothing range, if it is not specified, the algorithm will estimate the value of r by ratio. public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal, int? r = null) { Contracts.CheckValue(xValues, nameof(xValues)); @@ -70,10 +70,10 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool } /// - /// initialize the signal with basic checking + /// Initialize the signal with basic checking /// - /// the input x-axis values - /// the input y-axis values + /// The input x-axis values + /// The input y-axis values private void Init(IReadOnlyList xValues, IReadOnlyList yValues) { _x = xValues; @@ -86,8 +86,8 @@ private void Init(IReadOnlyList xValues, IReadOnlyList yValues) } /// - /// estimate any y value by given any x value, even the x value is not one of the input points. - /// when the x value is not one of the input points, find the closed one from input points, and use its model. + /// Estimate a y value by giving an x value, even if the x value is not one of the input points. + /// When the x value is not one of the input points, find the closed one from input points, and use its model. /// /// find the index with value closest to the input x value. public double EstimateY(double xValue) @@ -115,13 +115,13 @@ public double EstimateY(double xValue) } /// - /// this class is used to define a set of weight functions. these functions are useful for various purposes for smoothing. + /// This class is used to define a set of weight functions. These functions are useful for various purposes for smoothing, /// i.e., the weighted least squares. /// internal class WeightMethod { /// - /// this is used for robust weight, it is one iteration step of loess. + /// This is used for robust weight, It is one iteration step of loess. /// public static double BisquareWeight(double value) { @@ -133,8 +133,8 @@ public static double BisquareWeight(double value) } /// - /// a famous weight function, since it enhances a chi-squared distributional approximation of an estimated of the error variance. - /// tricube should provide an adequate smooth in almost all situations. /ref + /// A famous weight function, since it enhances a chi-squared distributional approximation f an estimated of the error variance. + /// Tricube should provide an adequate smooth in almost all situations. /// public static double Tricube(double value) { @@ -147,24 +147,24 @@ public static double Tricube(double value) } /// - /// this class is used to store the parameters which are needed for lowess algorithm. - /// the name of these constansts are compliant with the original terms in paper. + /// This class is used to store the parameters which are needed for lowess algorithm. + /// The name of these constansts are compliant with the original terms in paper. /// internal class LoessConfiguration { /// - /// minumum number of neighbor counts, to apply underlying regression analysis. - /// this number should be even, so that neighbors on left/right side of a given data point is balanced. unbalanced neighbors would make the local-weighted regression biased noticeably at corner cases. + /// Minumum number of neighbor counts, to apply underlying regression analysis. + /// This number should be even, so that neighbors on left/right side of a given data point is balanced. Unbalanced neighbors would make the local-weighted regression biased noticeably at corner cases. /// public const int MinimumNeighborCount = 4; /// - /// (0, 1], a smooth range ratio. let fn be the number of neighbors of a specific point. + /// (0, 1], a smooth range ratio. Let fn be the number of neighbors of a specific point. /// public const double F = 0.3; /// - /// the number of iterations for robust regression. + /// The number of iterations for robust regression. /// public const int T = 2; } diff --git a/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs index 52f07926c6..244e379f3d 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs @@ -3,7 +3,7 @@ internal class LoessBasicParameters { /// - /// the minimum length of a valid time series. a time series with length equals 2 is so trivial. when less than 2, meaningless. + /// The minimum length of a valid time series. A time series with length equals 2 is so trivial and meaningless less than 2. /// public const int MinTimeSeriesLength = 3; } diff --git a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs index 4923a69ab7..39ecdff043 100644 --- a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs @@ -5,11 +5,11 @@ namespace Microsoft.ML.TimeSeries internal class MathUtility { /// - /// use quick-sort like method to obtain the median value. - /// the complexity in expectation is O(n), which is faster than using quickSort. + /// Use quick-sort like method to obtain the median value. + /// The complexity in expectation is O(n), which is faster than using quickSort. /// - /// the input list of values. note that this list will be modified after calling this method - /// returns the median value + /// The input list of values. Note that this list will be modified after calling this method + /// Returns the median value public static double QuickMedian(List values) { if (values == null || values.Count == 0) @@ -20,8 +20,8 @@ public static double QuickMedian(List values) } /// - /// use quick-sort like method to obtain the median value. - /// the complexity in expectation is O(n), which is faster than using quickSort. + /// Use quick-sort like method to obtain the median value. + /// The complexity in expectation is O(n), which is faster than using quickSort. /// /// The list of values /// The k smallest value in the list @@ -72,8 +72,8 @@ public static double QuickSelect(IReadOnlyList values, int k) public static int FindMedianIndex(IReadOnlyList values, int start, int end) { - // use the middle value among first/middle/end as the guard value, to make sure the average performance good. - // according to unit test, this fix will improve the average performance 10%. and works normally when input list is ordered. + // Use the middle value among first/middle/end as the guard value, to make sure the average performance good. + // According to unit test, this fix will improve the average performance 10%. and works normally when input list is ordered. double first = values[start]; double last = values[end]; int midIndex = (start + end) / 2; diff --git a/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs b/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs new file mode 100644 index 0000000000..f9dd05db36 --- /dev/null +++ b/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.TimeSeries +{ + internal abstract class AbstractPolynomialModel + { + protected IReadOnlyList Coeffs; + + public AbstractPolynomialModel(IReadOnlyList coeffs) + { + Contracts.CheckValue(coeffs, nameof(coeffs)); + Coeffs = coeffs; + } + + public abstract double Y(double x); + } + + /// + /// A general polynomial model + /// + internal sealed class PolynomialModel : AbstractPolynomialModel + { + public PolynomialModel(IReadOnlyList coeffs) + : base(coeffs) + { + } + + /// + /// This function calculates the y value by given the x value, under this model + /// + /// the specific x value + public override double Y(double x) + { + double result = Coeffs[0]; + double p = 1.0; + for (int i = 1; i < Coeffs.Count; i++) + { + p *= x; + result += Coeffs[i] * p; + } + return result; + } + } + + /// + /// This class calculates f(x) = b0 + b1 * x + /// + internal sealed class LinearModel : AbstractPolynomialModel + { + /// + /// Store the coefficients in member variables for better performance. + /// + private readonly double _b0; + private readonly double _b1; + + public LinearModel(IReadOnlyList coeffs) + : base(coeffs) + { + Contracts.CheckParam(coeffs.Count == 2, nameof(coeffs), "must contain exact 2 elements."); + + _b0 = coeffs[0]; + _b1 = coeffs[1]; + } + + public override double Y(double x) + { + return _b0 + (_b1 * x); + } + } +} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index c885c7c491..c00d6b48ec 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -9,7 +9,6 @@ using Microsoft.ML.Data; using Microsoft.ML.Data.DataView; using Microsoft.ML.Runtime; -using Microsoft.ML.TimeSeries.Deseasonality; using Microsoft.ML.Transforms.TimeSeries; namespace Microsoft.ML.TimeSeries @@ -384,7 +383,7 @@ internal sealed class SrCnnEntireModeler private readonly double _sensitivity; private readonly SrCnnDetectMode _detectMode; private readonly int _period; - private readonly DeseasonalityBase _deseasonalityFunction; + private readonly IDeseasonality _deseasonalityFunction; //used in all modes private readonly double[] _predictArray; From 816479a7dc99e380d511c875709eeeb73451ca5e Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Mon, 22 Jun 2020 11:37:49 +0800 Subject: [PATCH 43/49] refine --- src/Microsoft.ML.TimeSeries/{STL => }/Deseasonality.cs | 0 src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 7 ++++++- src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs | 2 +- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 6 +++++- .../STL/LoessBasicParameters.cs | 10 ---------- .../SrCnnEntireAnomalyDetector.cs | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) rename src/Microsoft.ML.TimeSeries/{STL => }/Deseasonality.cs (100%) delete mode 100644 src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs diff --git a/src/Microsoft.ML.TimeSeries/STL/Deseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality.cs similarity index 100% rename from src/Microsoft.ML.TimeSeries/STL/Deseasonality.cs rename to src/Microsoft.ML.TimeSeries/Deseasonality.cs diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 45f3d6b7d8..15ccf97c8a 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -14,6 +14,11 @@ internal class FastLoess /// private const int _sampleSize = 100; + /// + /// The minimum length of a valid time series. A time series with length equals 2 is so trivial and meaningless less than 2. + /// + public const int MinTimeSeriesLength = 3; + private readonly IReadOnlyList _x; private readonly IReadOnlyList _y; private readonly int _length; @@ -35,7 +40,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b Contracts.CheckValue(yValues, nameof(yValues)); Y = new List(); - if (yValues.Count < LoessBasicParameters.MinTimeSeriesLength) + if (yValues.Count < MinTimeSeriesLength) throw Contracts.Except("input data structure cannot be 0-length: lowess"); _x = xValues; diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index 789e72b016..00a6fc1bb5 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -43,7 +43,7 @@ public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList Contracts.CheckParam(weights.Count == _length, nameof(weights)); if (weights.Count != _length) - throw Contracts.Except("the weight vector is not equal length to the data points"); + throw Contracts.Except("The length of the weight vector is not equal to the length of the data points."); // This part unfolds the matrix calculation of [sqrt(W), sqrt(W) .* X]^T * [sqrt(W), sqrt(W) .* X] double sum00 = 0; diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index d3feb719fe..d477093dd1 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -9,6 +9,10 @@ namespace Microsoft.ML.TimeSeries /// internal class Loess { + /// + /// The minimum length of a valid time series. A time series with length equals 2 is so trivial and meaningless less than 2. + /// + public const int MinTimeSeriesLength = 3; private const double NumericalThreshold = 1.0e-10; @@ -41,7 +45,7 @@ public Loess(IReadOnlyList xValues, IReadOnlyList yValues, bool Contracts.CheckValue(xValues, nameof(xValues)); Contracts.CheckValue(yValues, nameof(yValues)); - if (xValues.Count < LoessBasicParameters.MinTimeSeriesLength || yValues.Count < LoessBasicParameters.MinTimeSeriesLength) + if (xValues.Count < MinTimeSeriesLength || yValues.Count < MinTimeSeriesLength) throw Contracts.Except("input data structure cannot be 0-length: lowess"); if (xValues.Count != yValues.Count) diff --git a/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs b/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs deleted file mode 100644 index 244e379f3d..0000000000 --- a/src/Microsoft.ML.TimeSeries/STL/LoessBasicParameters.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.ML.TimeSeries -{ - internal class LoessBasicParameters - { - /// - /// The minimum length of a valid time series. A time series with length equals 2 is so trivial and meaningless less than 2. - /// - public const int MinTimeSeriesLength = 3; - } -} diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index c00d6b48ec..117262b5c0 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -202,7 +202,7 @@ public SrCnnEntireAnomalyDetector(IHostEnvironment env, IDataView input, string private void CheckOptionArguments(SrCnnEntireAnomalyDetectorOptions options) { - Host.CheckUserArg(options.Period >= 0, nameof(options.Period), "Must be integer equal or greater than 0."); + Host.CheckUserArg(options.Period >= 0, nameof(options.Period), "Must be an integer equal to or greater than 0."); Host.CheckUserArg(options.BatchSize == -1 || options.BatchSize >= MinBatchSize, nameof(options.BatchSize), "Must be -1 or no less than 12."); Host.CheckUserArg(options.BatchSize >= 4 * options.Period || options.BatchSize == -1 || options.Period == 0, nameof(options.BatchSize), "Must be at least four times the length of one period."); From a239a89a0690dd4999346ddd4362e77a890c88bd Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Mon, 22 Jun 2020 16:59:18 +0800 Subject: [PATCH 44/49] update comment --- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 1f34ede70a..5a971ee54f 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -280,7 +280,7 @@ static VirtualXValuesProvider() /// Get a list of virtual x-axis values. the values are from 0 to length - 1. /// /// Specify the length you want to create the x values. - /// If this is cached, return directly. otherwise, create a new list and return + /// If the input is cached, return the cached output directly. otherwise, create a new list and return internal static List GetXValues(int length) { lock (_xValuesPool) From 1446115bd66949a0c39f777fa25a3df1005ec2af Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Mon, 22 Jun 2020 20:48:16 +0800 Subject: [PATCH 45/49] updates --- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 6 ++++- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 6 ++++- .../STL/LeastSquares.cs | 6 ++++- .../STL/LocalRegression.cs | 6 ++++- src/Microsoft.ML.TimeSeries/STL/Loess.cs | 6 ++++- src/Microsoft.ML.TimeSeries/STL/MathsTools.cs | 6 ++++- .../STL/PolynomialModel.cs | 6 ++++- .../SrCnnEntireAnomalyDetector.cs | 26 +++++++++---------- .../TimeSeriesDirectApi.cs | 3 +-- 9 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 15ccf97c8a..77e4f9b68b 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index 5a971ee54f..d49d1567a1 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -1,4 +1,8 @@ -using System; +// 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.Runtime; diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index 00a6fc1bb5..62a1b86088 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs index ebc677d5c3..e10819257a 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LocalRegression.cs @@ -1,4 +1,8 @@ -using System; +// 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.Internal.CpuMath.Core; diff --git a/src/Microsoft.ML.TimeSeries/STL/Loess.cs b/src/Microsoft.ML.TimeSeries/STL/Loess.cs index d477093dd1..798a5f0cdb 100644 --- a/src/Microsoft.ML.TimeSeries/STL/Loess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/Loess.cs @@ -1,4 +1,8 @@ -using System; +// 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.Runtime; diff --git a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs index 39ecdff043..17f5547284 100644 --- a/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs +++ b/src/Microsoft.ML.TimeSeries/STL/MathsTools.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// 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.Collections.Generic; namespace Microsoft.ML.TimeSeries { diff --git a/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs b/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs index f9dd05db36..3f6f157a88 100644 --- a/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs +++ b/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// 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.Collections.Generic; using Microsoft.ML.Runtime; namespace Microsoft.ML.TimeSeries diff --git a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs index 117262b5c0..5f296d806c 100644 --- a/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs +++ b/src/Microsoft.ML.TimeSeries/SrCnnEntireAnomalyDetector.cs @@ -418,17 +418,18 @@ public SrCnnEntireModeler(double threshold, double sensitivity, SrCnnDetectMode _period = period; _predictArray = new double[_lookaheadWindowSize + 1]; - if (deseasonalityMode == SrCnnDeseasonalityMode.Stl) + switch (deseasonalityMode) { - _deseasonalityFunction = new StlDeseasonality(); - } - else if (deseasonalityMode == SrCnnDeseasonalityMode.Mean) - { - _deseasonalityFunction = new MeanDeseasonality(); - } - else // if (deseasonalityMode == SrCnnDeseasonalityMode.Median) - { - _deseasonalityFunction = new MedianDeseasonality(); + case SrCnnDeseasonalityMode.Stl: + _deseasonalityFunction = new StlDeseasonality(); + break; + case SrCnnDeseasonalityMode.Mean: + _deseasonalityFunction = new MeanDeseasonality(); + break; + default: + Contracts.Assert(deseasonalityMode == SrCnnDeseasonalityMode.Median); + _deseasonalityFunction = new MedianDeseasonality(); + break; } } @@ -655,10 +656,7 @@ private void GetExpectedValuePeriod(double[] values, double[][] results, IReadOn private void GetMarginPeriod(double[] values, double[][] results, IReadOnlyList residual, double sensitivity) { //Step 8: Calculated Expected Value - for (int i = 0; i < values.Length; ++i) - { - results[i][3] = values[i] - residual[i]; - } + GetExpectedValuePeriod(values, results, residual); //Step 9: Calculate Boundary Unit CalculateBoundaryUnit(values, results.Select(x => x[0] > 0).ToArray()); diff --git a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs index ae7935e7c5..1cd4278934 100644 --- a/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs +++ b/test/Microsoft.ML.TimeSeries.Tests/TimeSeriesDirectApi.cs @@ -662,8 +662,7 @@ public void TestSrCnnBatchAnomalyDetector( [Theory, CombinatorialData] public void TestSrCnnAnomalyDetectorWithSeasonalData( - [CombinatorialValues(SrCnnDeseasonalityMode.Stl, SrCnnDeseasonalityMode.Mean, SrCnnDeseasonalityMode.Median)] SrCnnDeseasonalityMode mode - ) + [CombinatorialValues(SrCnnDeseasonalityMode.Stl, SrCnnDeseasonalityMode.Mean, SrCnnDeseasonalityMode.Median)] SrCnnDeseasonalityMode mode) { var ml = new MLContext(1); IDataView dataView; From 8b10cc062b3af941e5df6a3fb78607ef371678c3 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 23 Jun 2020 10:57:19 +0800 Subject: [PATCH 46/49] update some wordings --- src/Microsoft.ML.TimeSeries/Deseasonality.cs | 12 +++++++++++- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 17 ++++++++--------- src/Microsoft.ML.TimeSeries/STL/InnerStl.cs | 8 ++++---- src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs | 7 +++---- .../STL/PolynomialModel.cs | 2 +- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality.cs index 199950fd5c..5ea9fbd701 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality.cs @@ -1,10 +1,20 @@ -using System; +// 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; namespace Microsoft.ML.TimeSeries { internal interface IDeseasonality { + /// + /// Remove the seasonality component of the given time-series. + /// + /// An array representing the input time-series. + /// The period value of the time-series. + /// The de-seasonalized time-series. public abstract void Deseasonality(ref double[] values, int period, ref double[] results); } diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 77e4f9b68b..3cb01ef7f1 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -8,8 +8,8 @@ namespace Microsoft.ML.TimeSeries { /// - /// This is the fast version of Loess. there are several alternatives to improve the performance. this one is an approximation approach. - /// the smoothing is conducted on a sample set, and then the values on the left points are assigned directly. + /// This is the fast version of Loess. There are several alternatives to improve the performance. This one is an approximation approach. + /// The smoothing is conducted on a sample set, and then the values on the left points are assigned directly. /// internal class FastLoess { @@ -31,13 +31,12 @@ internal class FastLoess /// /// Initializes a new instance of the class. - /// the fast version of the Loess method. when the time series is too long, the sampling will be conducted first - /// to improve the performance. + /// The fast version of the Loess method. when the time series is too long, the sampling will be conducted first to improve the performance. /// - /// the input x-axis values - /// the input y-axis values - /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data - /// this method will provide default smoothing ratio if user did not specify + /// The input x-axis values + /// The input y-axis values + /// If the regression is considered to take temporal information into account. In general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + /// This method will provide default smoothing ratio if user did not specify public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, bool isTemporal = true, int r = -1) { Contracts.CheckValue(xValues, nameof(xValues)); @@ -78,7 +77,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b } /// - /// The estimated y values. this is the very cool smoothing method. + /// The estimated y values. This is the very cool smoothing method. /// public List Y { get; } diff --git a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs index d49d1567a1..0093f0ff41 100644 --- a/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs +++ b/src/Microsoft.ML.TimeSeries/STL/InnerStl.cs @@ -74,7 +74,7 @@ private int Nt(int np) /// Since this method supports decompose seasonal signal, which requires the equal-space of the input x-axis values. /// Otherwise, the smoothing on seasonal component will be very complicated. /// - /// if the regression is considered to take temporal information into account. in general, this is true if we are regressing a time series, and false if we are regressing scatter plot data + /// If the regression is considered to take temporal information into account. In general, this is true if we are regressing a time series, and false if we are regressing scatter plot data public InnerStl(bool isTemporal) { _isTemporal = isTemporal; @@ -105,10 +105,10 @@ public IReadOnlyList Residual } /// - /// The core for the robust trend-seasonal decomposition. see the ref: http://www.wessa.net/download/stl.pdf, + /// The core for the robust trend-seasonal decomposition. See the ref: http://www.wessa.net/download/stl.pdf, /// See section 2 and 3. especially section 2. /// - /// Return true if the process goes successfully. otherwise, return false. + /// Return true if the process goes successfully. Otherwise, return false. public bool Decomposition(IReadOnlyList yValues, int np) { Contracts.CheckValue(yValues, nameof(yValues)); @@ -284,7 +284,7 @@ static VirtualXValuesProvider() /// Get a list of virtual x-axis values. the values are from 0 to length - 1. /// /// Specify the length you want to create the x values. - /// If the input is cached, return the cached output directly. otherwise, create a new list and return + /// If the input is cached, return the cached output directly. Otherwise, create a new list and return internal static List GetXValues(int length) { lock (_xValuesPool) diff --git a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs index 62a1b86088..a2895ec509 100644 --- a/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs +++ b/src/Microsoft.ML.TimeSeries/STL/LeastSquares.cs @@ -40,7 +40,7 @@ public LeastSquares(IReadOnlyList x, IReadOnlyList y) /// /// y=b0+b1x, while the penalty is weighted /// - /// The weighted least squares. note that the weight should be non-negative, and equal length to data + /// The weighted least squares. Note that the weight should be non-negative, and equal length to data public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList weights) { Contracts.CheckValue(weights, nameof(weights)); @@ -66,9 +66,8 @@ public AbstractPolynomialModel RegressionDegreeOneWeighted(IReadOnlyList sum11 += temp; } - /* calculate the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is - * [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. - */ + // calculate the reverse of a 2X2 matrix is simple, because suppose the matrix is [a,b;c,d], then its reverse is + // [x1,x2;x3,x4] where x1 = d/K, x2 = -c/K, x3 = -b/K, x4 = a/K, where K = ad-bc. double a = sum00; double b = sum01; double c = sum10; diff --git a/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs b/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs index 3f6f157a88..437916205f 100644 --- a/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs +++ b/src/Microsoft.ML.TimeSeries/STL/PolynomialModel.cs @@ -33,7 +33,7 @@ public PolynomialModel(IReadOnlyList coeffs) /// /// This function calculates the y value by given the x value, under this model /// - /// the specific x value + /// The specific x value public override double Y(double x) { double result = Coeffs[0]; From a817829233c79d7f2c2c23a424e233f1250e6fd0 Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Tue, 23 Jun 2020 21:16:55 +0800 Subject: [PATCH 47/49] update comments --- src/Microsoft.ML.TimeSeries/STL/FastLoess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs index 3cb01ef7f1..6ed4dde87f 100644 --- a/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs +++ b/src/Microsoft.ML.TimeSeries/STL/FastLoess.cs @@ -77,7 +77,7 @@ public FastLoess(IReadOnlyList xValues, IReadOnlyList yValues, b } /// - /// The estimated y values. This is the very cool smoothing method. + /// The estimated y values. /// public List Y { get; } From 8fac10ea5fc66140d54bd18a9b4c39f66a815fde Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Fri, 26 Jun 2020 18:48:00 +0800 Subject: [PATCH 48/49] update some comments --- src/Microsoft.ML.TimeSeries/Deseasonality.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality.cs index 5ea9fbd701..2bcb7647e9 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality.cs @@ -34,7 +34,7 @@ public void Deseasonality(ref double[] values, int period, ref double[] results) _circularComponent[i] = 0; } - // Sum up values that locates at the same position in one period. + // Sum up values that locate at the same position in one period. for (int i = 0; i < length; ++i) { var indexInPeriod = i % period; @@ -98,6 +98,9 @@ public void Deseasonality(ref double[] values, int period, ref double[] results) } } + /// + /// This class takes the residual component of stl decompose as the deseasonality result. + /// internal sealed class StlDeseasonality : IDeseasonality { private readonly InnerStl _stl; From 2cad48f6e6e3cfd66bbb7c13f36144d07a9b920f Mon Sep 17 00:00:00 2001 From: "yuyi@microsoft.com" Date: Sun, 28 Jun 2020 09:45:33 +0800 Subject: [PATCH 49/49] wording --- src/Microsoft.ML.TimeSeries/Deseasonality.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.ML.TimeSeries/Deseasonality.cs b/src/Microsoft.ML.TimeSeries/Deseasonality.cs index 2bcb7647e9..b56613141c 100644 --- a/src/Microsoft.ML.TimeSeries/Deseasonality.cs +++ b/src/Microsoft.ML.TimeSeries/Deseasonality.cs @@ -10,7 +10,7 @@ namespace Microsoft.ML.TimeSeries internal interface IDeseasonality { /// - /// Remove the seasonality component of the given time-series. + /// Remove the seasonality component from the given time-series. /// /// An array representing the input time-series. /// The period value of the time-series.