private void CalculateExpectedValueByFft(double[] data) { int length = data.Length; AllocateDoubleArray(ref _fftRe, length); AllocateDoubleArray(ref _fftIm, length); AllocateDoubleArray(ref _zeroArray, length); FftUtils.ComputeForwardFft(data, _zeroArray, _fftRe, _fftIm, length); for (int i = 0; i < length; ++i) { if (i > (double)length * 3 / 8 && i < (double)length * 5 / 8) { _fftRe[i] = 0.0f; _fftIm[i] = 0.0f; } } AllocateDoubleArray(ref _ifftRe, length); AllocateDoubleArray(ref _ifftIm, length); FftUtils.ComputeBackwardFft(_fftRe, _fftIm, _ifftRe, _ifftIm, length); }
private void SpectralResidual(double[] values, double[][] results, double threshold) { // Step 1: Get backadd wave BackAdd(values); // Step 2: FFT transformation int length = _backAddArray.Length; AllocateDoubleArray(ref _fftRe, length); AllocateDoubleArray(ref _fftIm, length); AllocateDoubleArray(ref _zeroArray, length); FftUtils.ComputeForwardFft(_backAddArray, _zeroArray, _fftRe, _fftIm, length); // Step 3: Calculate mags of FFT AllocateDoubleArray(ref _magList, length); AllocateDoubleArray(ref _magLogList, length); for (int i = 0; i < length; ++i) { _magList[i] = Math.Sqrt((Math.Pow(_fftRe[i], 2) + Math.Pow(_fftIm[i], 2))); if (_magList[i] > _eps) { _magLogList[i] = Math.Log(_magList[i]); } else { _magLogList[i] = 0; } } // Step 4: Calculate spectral AverageFilter(_magLogList, _averagingWindowSize); AllocateDoubleArray(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); for (int i = 0; i < length; ++i) { if (_magLogList[i] != 0) { _transRe[i] = _fftRe[i] * _spectralList[i] / _magList[i]; _transIm[i] = _fftIm[i] * _spectralList[i] / _magList[i]; } else { _transRe[i] = 0; _transIm[i] = 0; } } AllocateDoubleArray(ref _ifftRe, length); AllocateDoubleArray(ref _ifftIm, length); FftUtils.ComputeBackwardFft(_transRe, _transIm, _ifftRe, _ifftIm, length); // Step 6: Calculate mag and ave_mag of IFFT AllocateDoubleArray(ref _ifftMagList, length); for (int i = 0; i < length; ++i) { _ifftMagList[i] = Math.Sqrt((Math.Pow(_ifftRe[i], 2) + Math.Pow(_ifftIm[i], 2))); } AverageFilter(_ifftMagList, Math.Min(_ifftMagList.Length, _judgementWindowSize)); // Step 7: Calculate raw score and set result for (int i = 0; i < results.GetLength(0); ++i) { var score = CalculateScore(_ifftMagList[i], _cumSumList[i]); score /= 10.0f; score = Math.Min(score, 1); score = Math.Max(score, 0); var detres = score > threshold ? 1 : 0; results[i][0] = detres; results[i][1] = score; results[i][2] = _ifftMagList[i]; } }
/// <summary> /// This method detects this predictable interval (or period) by adopting techniques of fourier analysis. /// Returns -1 if no such pattern is found, that is, the input values do not follow a seasonal fluctuation. /// </summary> /// <param name="host">The detect seasonality host environment.</param> /// <param name="input">Input DataView.The data is an instance of <see cref="Microsoft.ML.IDataView"/>.</param> /// <param name="inputColumnName">Name of column to process. The column data must be <see cref="System.Double"/>.</param> /// <param name="seasonalityWindowSize">An upper bound on the number of values to be considered in the input values. /// When set to -1, use the whole input to fit model; when set to a positive integer, use this number as batch size. /// Default value is -1.</param> /// <param name="randomessThreshold">Randomness threshold, ranging from [0, 1]. It specifies how confidence the input /// follows a predictable pattern recurring as seasonal data. By default, it is set as 0.95. /// </param> /// <returns>The detected period if seasonality period exists, otherwise return -1.</returns> public int DetectSeasonality( IHostEnvironment host, IDataView input, string inputColumnName, int seasonalityWindowSize, double randomessThreshold) { host.CheckValue(input, nameof(input)); host.CheckValue(inputColumnName, nameof(inputColumnName)); host.CheckUserArg(seasonalityWindowSize == -1 || seasonalityWindowSize >= 0, nameof(seasonalityWindowSize)); var column = input.Schema.GetColumnOrNull(inputColumnName); host.CheckUserArg(column.HasValue, nameof(inputColumnName)); var rowCursor = input.GetRowCursor(new List <DataViewSchema.Column>() { column.Value }); var valueDelegate = rowCursor.GetGetter <double>(column.Value); int length = 0; double valueRef = 0; var valueCache = seasonalityWindowSize == -1 ? new List <double>() : new List <double>(seasonalityWindowSize); while (rowCursor.MoveNext()) { valueDelegate.Invoke(ref valueRef); length++; valueCache.Add(valueRef); if (seasonalityWindowSize != -1 && length >= seasonalityWindowSize) { break; } } double[] fftRe = new double[length]; double[] fftIm = new double[length]; double[] inputRe = valueCache.ToArray(); FftUtils.ComputeForwardFft(inputRe, Enumerable.Repeat(0.0, length).ToArray(), fftRe, fftIm, length); var energies = fftRe.Select((m, i) => new Complex(m, fftIm[i])).ToArray(); /* Periodogram indicates the square of "energy" on the frequency domain. * Specifically, periodogram[j] = a[j]^2+b[j]^2, where a and b are Fourier Coefficients for cosine and sine, * x(t) = a0+sum(a[j]cos(2Pi * f[j]t)+b[j]sin(2Pi * f[j]t) */ var periodogram = energies.Select((t, i) => t * Complex.Conjugate(t)).ToArray(); FindBestTwoFrequencies(periodogram, length, out var bestFreq, out var secondFreq); double[] ifftRe = new double[length]; double[] ifftIm = new double[length]; FftUtils.ComputeBackwardFft( periodogram.Select(t => t.Real).ToArray(), periodogram.Select(t => t.Imaginary).ToArray(), ifftRe, ifftIm, length); var values = ifftRe.Select((t, i) => new Complex(t, ifftIm[i])).ToArray(); int period = FindActualPeriod(values, bestFreq, secondFreq, length, randomessThreshold); return(period < 0 ? -1 : period); }