Вычисление автокорреляции с использованием FFT с использованием библиотеки JTransforms

Я пытаюсь вычислить автокорреляцию образцов окон во временном ряду, используя приведенный ниже код. Я применяю БПФ к этому окну, а затем вычисляю величины реальных и мнимых частей и устанавливая мнимую часть в ноль, наконец, беря обратное преобразование из нее, чтобы получить автокорреляцию:

DoubleFFT_1D fft = new DoubleFFT_1D(magCnt);
fft.realForward(magFFT);

magFFT[0] = (magFFT[0] * magFFT[0]);
for (int i = 1; i < (magCnt - (magCnt%2)) / 2; i++) {
    magFFT[2*i] = magFFT[2*i] * magFFT[2*i] + magFFT[2*i + 1] * magFFT[2*i + 1];
    magFFT[2*i + 1] = 0.0;
}

if (magCnt % 2 == 0) {
    magFFT[1] = (magFFT[1] * magFFT[1]);
} else {
    magFFT[magCnt/2] = (magFFT[magCnt-1] * magFFT[magCnt-1] + magFFT[1] * magFFT[1]);
}

autocorr = new double[magCnt];
System.arraycopy(magFFT, 0, autocorr, 0, magCnt);
DoubleFFT_1D ifft = new DoubleFFT_1D(magCnt);
ifft.realInverse(autocorr, false);

for (int i = 1; i < autocorr.length; i++)
    autocorr[i] /= autocorr[0];
autocorr[0] = 1.0;

Первый вопрос: видно, что этот код отображает результат автокорреляции в диапазон [0,1], хотя корреляция должна быть между -1 и 1. Конечно, легко сопоставить результаты с диапазоном [-1,1] но я не уверен, правильно ли это сопоставление. Как мы можем интерпретировать значения в результирующем массиве autocorr?

Во-вторых, с этим кодом я получаю хорошие результаты для некоторых периодических рядов, то есть получаю более высокие значения для конкретных индексов автокорреляции в соответствии с периодом сигнала. Однако результат становится странным, когда я применяю его к непериодическим сигналам: все значения в массиве autocorr оказываются очень близкими к 1. В чем причина этого?

Ответ 1

Для работы алгоритмов, основанных на БПФ, вы должны уделять пристальное внимание определениям, в том числе соглашениям библиотеки, которые вы используете. Кажется, вы путаете "соглашение о обработке сигналов" для AC и "статистического". А затем есть FFT-упаковка и нулевое дополнение.

Вот код, который работает для четного N случая, соглашения о обработке сигналов. Он был протестирован против автоматической корреляции с грубой силой. В комментариях показано, как преобразовать его в соглашение о обработке сигналов. Для статистики ac вычитается среднее значение данных. Это можно сделать только путем обнуления компонента "0 Гц" БПФ. Тогда нулевой элемент ac является дисперсией, и вы можете нормализовать, делясь на эту величину. Полученные значения будут падать в -1.1, как вы говорите.

Ваш код, кажется, выполняет деление, но не игнорирует компонент данных 0 Гц. Таким образом, он вычисляет какой-то mashup из условностей.

import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;
import java.util.Arrays;

public class TestFFT {

    void print(String msg, double [] x) {
        System.out.println(msg);
        for (double d : x) System.out.println(d);
    }

    /**
     * This is a "wrapped" signal processing-style autocorrelation. 
     * For "true" autocorrelation, the data must be zero padded.  
     */
    public void bruteForceAutoCorrelation(double [] x, double [] ac) {
        Arrays.fill(ac, 0);
        int n = x.length;
        for (int j = 0; j < n; j++) {
            for (int i = 0; i < n; i++) {
                ac[j] += x[i] * x[(n + i - j) % n];
            }
        }
    }

    private double sqr(double x) {
        return x * x;
    }

    public void fftAutoCorrelation(double [] x, double [] ac) {
        int n = x.length;
        // Assumes n is even.
        DoubleFFT_1D fft = new DoubleFFT_1D(n);
        fft.realForward(x);
        ac[0] = sqr(x[0]);
        // ac[0] = 0;  // For statistical convention, zero out the mean 
        ac[1] = sqr(x[1]);
        for (int i = 2; i < n; i += 2) {
            ac[i] = sqr(x[i]) + sqr(x[i+1]);
            ac[i+1] = 0;
        }
        DoubleFFT_1D ifft = new DoubleFFT_1D(n); 
        ifft.realInverse(ac, true);
        // For statistical convention, normalize by dividing through with variance
        //for (int i = 1; i < n; i++)
        //    ac[i] /= ac[0];
        //ac[0] = 1;
    }

    void test() {
        double [] data = { 1, -81, 2, -15, 8, 2, -9, 0};
        double [] ac1 = new double [data.length];
        double [] ac2 = new double [data.length];
        bruteForceAutoCorrelation(data, ac1);
        fftAutoCorrelation(data, ac2);
        print("bf", ac1);
        print("fft", ac2);
        double err = 0;
        for (int i = 0; i < ac1.length; i++)
            err += sqr(ac1[i] - ac2[i]);
        System.out.println("err = " + err);
    }

    public static void main(String[] args) {
        new TestFFT().test();
    }
}