[データマイニング] 時系列のフーリエ変換: numpy で解釈した高速畳み込み

1. 説明

        この記事では、高度な数学モデルであるフーリエ モデルの使用について説明します。 今日、フーリエ変換とそのすべての変形は現代世界の基礎を形成し、圧縮、通信、画像処理などのテクノロジを強化しています。私たちは根本から理解し、根本から適用します、そしてそれは支払う価値のある代償です。

2. FFTの歴史的ルーツ

        フーリエ変換アルゴリズムは、数学全体の中で最も偉大な発見の 1 つと考えられています。フランスの数学者ジャンバティスト・ジョセフ・フーリエは、1822 年の著書『理論分析理論』で調和解析の基礎を築きました。

        19 世紀初頭のフランスの数学者ジャン バティスト ジョセフ フーリエ (1768-1830) の肖像画が刻まれています。[出典: Wikipedia、画像はパブリックドメインから]

        この素晴らしいフレームワークは、時系列を分析するための優れたツールも提供します...それが私たちがここにいる理由です!

        この投稿はフーリエ変換に関するシリーズの一部です。今日は、畳み込みと、フーリエ変換がどのように最速の方法を提供するかについて説明します。

        

3. 離散フーリエ変換 (DFT) の定義

        基本的な定義から始めましょう。N 要素の離散時系列 x の離散フーリエ変換は次のとおりです。

        離散フーリエ変換 (DFT) の定義。他の定義も存在しますが、1 つを選択してそれに固執するだけです (著者が作成したものです)

        ここで、k は x のスペクトルの k 番目の周波数を示します。一部の著者はこの定義に 1/N のスケール係数を追加していることに注意してください。しかし、それはこの記事では重要ではありません。結局のところ、これは単なる定義の問題であり、それに固執します。

        次に、逆フーリエ変換 (順フーリエ変換の定義が与えられた場合):

        上記の順定義 (著者が作成) に基づく逆離散フーリエ変換。

        そうは言っても、フーリエ変換の最も重要な定理の 1 つは、ある空間での畳み込みは別の空間での乗算と同等であるということです。言い換えれば、積のフーリエ変換は対応するフーリエ スペクトルの畳み込みであり、畳み込みのフーリエ変換は対応するフーリエ スペクトルの積です。

        時間領域での乗算は、フーリエ領域での循環畳み込みに相当します(筆者作成)。

        そして

        時間領域での循環畳み込みは、フーリエ領域での乗算に相当します (著者が作成)。

        ここで、点は標準積 (乗算) を表し、丸で囲まれた星は循環畳み込みを表します。

        2 つの重要な注意事項:

  • 周期信号: フーリエ解析フレームワークは、扱う信号が周期的であることを前提としています。つまり、負の無限大から無限大まで繰り返します。ただし、メモリが限られているコンピューターでこのような信号を処理するのは必ずしも現実的ではないため、後で説明するように、1 サイクルでのみ「再生」します。
  • 循環畳み込み: 畳み込み定理は、乗算が循環畳み込みと同等であると述べています。これは、私たちが慣れ親しんでいる線形畳み込みとは少し異なります。これから見ていきますが、それほど変わったものでも、それほど複雑なものでもありません。

4. 循環畳み込みと線形畳み込み

        線形畳み込み (単に「畳み込み」と呼ばれることも多い) に慣れている場合は、循環畳み込みに混乱することはありません。基本的に、循環畳み込みは、周期信号を畳み込む方法にすぎませんご想像のとおり、線形畳み込みは、負の無限大から無限大までの範囲にない有限長の信号に対してのみ意味を持ちます。私たちの場合、フーリエ解析のコンテキストでは、信号は周期的であるため、この条件を満たしていません。(線形) 畳み込みについて話すことはできません。

        ただし、周期信号に対して線形畳み込みスタイルの演算を直感的に実行することはできます。周期信号を周期長にわたって畳み込むだけです。これが循環畳み込みの動作です。周期スパンにわたって同じ長さの 2 つの周期信号を畳み込みます。

違いをさらに理解するには、離散線形畳み込みと離散循環畳み込みの 2 つの公式を比較してください。

線形畳み込み方程式: 信号処理ではほとんどの場合、この方程式がゼロで埋められて使用されます (著者が作成)。

Circular Convolution: フーリエ解析など周期信号を扱う際に使用する畳み込みです(筆者作成)。

違いに注意してください:

範囲: 線形畳み込みは負の無限大から正の無限大までのサンプルを使用します — 前述したように、この場合、x と y のエネルギーは有限であり、合計は意味を持ちます。循環畳み込みの場合は、1 つの期間に起こったことだけが必要なので、合計は 1 つの期間にのみ広がります。

- 循環インデックス:循環畳み込みでは、長さ N のモジュロ演算で y のインデックスを「ラップ」します。これは、y が周期 N で周期的であるとみなされることを確認するための単なる方法です。位置 k での y の値を知りたいときは、位置 k%N での y の値のみを使用します。y は N 周期であるため、次のようになります。正しい値。繰り返しますが、これは無限長のサンプルの周期的なシーケンスを扱う数学的な方法にすぎません。

5. numpyでの実装

        Numpy は有限長信号用の優れたツールを提供します。これは良いニュースです。なぜなら、先ほど見たように、無限長の周期信号は単一の周期で表現できるからです。

        これらの信号を表す単純なクラスを作成してみましょう。配列をプロットする簡単な方法と、一連のサイクルを扱っていることを忘れないよう「基本」配列の前後に追加のサイクルを追加しました。

import numpy as np
import matplotlib.pyplot as plt

class PeriodicArray:
    """A class to represent a periodic signal, using a single
    period of the sequence.
    """

    def __init__(self, base):
        """base is the base sequence representing a full period."""
        self.base = base
    
    @property
    def N(self): 
        """Lenght of the base array, which is also the 
        period of our infinite-periodic sequence"""
        return len(self.base)
    
    def __getitem__(self, n):
        """We can get the value at any index, from -infinity
        to +infinity using the fact that the sequence is N-
        periodic, so we use the modulo operator.
        
        Examples
        --------
        >>> x = PeriodicArray([1, 2, 3])
        >>> x[0]
        1
        >>> x[4]
        2
        >>> x[5]
        3
        """
        return self.base[n%self.N]
    
    def plot(self, ax=None):
        """Quickly plot the sequence, with a period before and after
        the base array."""
        if ax is None:
            fig, ax = plt.subplots()
        line = ax.plot(self.base, '-o')
        ax.plot(np.arange(-self.N, 0), self.base, '--o', color=line[0].get_color(), alpha=0.5)
        ax.plot(np.arange(self.N, self.N*2), self.base, '--o', color=line[0].get_color(), alpha=0.5)
        return ax

        2 つの例を見てみましょう。最初はサンプリングされた洞シーケンス、次に線形シーケンスです。どちらも N 周期とみなされます (この場合は N=10)。

periodic_sampled_sinus = PeriodicArray(np.sin(2*np.pi*1*np.linspace(0, np.pi/10, 10)))
periodic_sampled_sinus.plot()


periodic_slope = PeriodicArray(np.linspace(-5, 5, num=10)*0.5)
periodic_slope.plot()

PeriodicArray の 2 つの例: 「ベース」周期は 0 から N までの濃い青色で描画され、他の 2 つの周期は、周期的なシーケンス (著者が作成した) を扱っているという事実を表すために前後に追加されます。

6. 循環畳み込み、低速モード

        上記の循環畳み込み方程式を実装してみましょう。インデックス付けとモジュロ演算子を使用すると、非常に簡単になります。

        上記 2 つの周期シーケンス間の循環畳み込み (著者が作成)。

        なるほど、これで 2 つの信号間の循環畳み込みがどのようなものかを確認できました。すべてを 1 つの図にまとめます。

        左: 最初の周期配列。中央: 第 2 周期の配列。右: 2 つの周期配列の循環畳み込み。これも周期配列です (著者が作成)。

        このソリューションはかなりうまく機能しますが、遅いという大きな欠点が 1 つあります。ご覧のとおり、結果を計算するには 2 つのネストされたループを通過する必要があります。1 つは結果配列の各位置に対して 1 つ、もう 1 つはその位置での結果を計算します。N 増加として、アルゴリズムは O(N²) であるとします。 , 演算回数は N の 2 乗に応じて増加します。

        この例の小さな配列の場合、これは問題になりませんが、配列が大きくなると、大きな問題になります。

        また、Python では、数値データをループすることは、ほとんどの場合、悪い習慣であると考えられています。もっと良い方法があるはずです...

7. 循環畳み込み、フーリエ法

        ここでフーリエ変換と畳み込み定理が登場します。離散フーリエ変換の実装方法、高速フーリエ変換 (FFT) を使用して非常に高速かつ最適化された方法で実装されているため、演算は非常に高速です (FFT がO (N log N) であるとします。これは、 O(N²) ずっと)。

        畳み込み定理を使用すると、2 つの系列の DFT の積を取得でき、逆 DFT を使用して時間領域に変換し直すと、入力時系列の畳み込みが得られます。言い換えれば、次のようになります。

        直接および逆フーリエ変換を使用した x と y 間の循環畳み込み (著者が作成)。

        このうち、DFT は離散フーリエ変換、IDFT は逆演算の略です。

        このアルゴリズムを非常に簡単に実装して、x と y の畳み込みを計算できます。

def circconv_fast(x, y):
    """Fast circular convolution using DFT.
    Return the full array of the circulard 
    convolution between x and y.
    """
    X = np.fft.fft(x)
    Y = np.fft.fft(y)
    return np.real(np.fft.ifft(np.multiply(X, Y)))

# let's compute the circular convolution for our 2 signals
circ_fast = circconv_fast(periodic_sampled_sinus.base, periodic_slope.base)
circ_fast = PeriodicArray(circ_fast)

八、値と時間の比較

        最後に、2 つの方法が同じ結果を生成することを確認し、Python がこれら 2 つの手法を使用して循環畳み込みを計算するのにかかる時間を比較してみましょう。

# compare both ways : "slow" way, and DFT-way
fig, ax = plt.subplots()
ax.plot(circ.base, '-o', label="slow-way")
ax.plot(circ_fast.base, '-o', label="DFT-way")
ax.legend()
ax.set_title('Comparison of 2 ways to compute convolution : \nslow-algebraic way VS using DFT and the convolution theorem')

        2 つの周期シーケンス間の循環畳み込みを計算する 2 つの方法を比較します。「遅い方法」は、青色のサイクルと加算を使用する単純な代数であり、オレンジ色の「フーリエ方法」と重ねられています。どちらの方法でも、(数値精度まで) まったく同じ結果が得られます (著者が作成したもの)。

        これは完璧な組み合わせです!この 2 つは数値的には厳密に等価です。

        次に時間の比較です。

N = 1000
long_x = np.sin(2*np.pi*1*np.linspace(0, np.pi/10, N))
long_y = np.cos(2*np.pi*1*np.linspace(0, np.pi/10, N))

print(circconv(long_x, long_y))
print(circconv_fast(long_x, long_y))
# first make sure that both method yield the same result
assert np.allclose(circconv(long_x, long_y), circconv_fast(long_x, long_y))

%timeit circconv(long_x, long_y)
%timeit circconv_fast(long_x, long_y)

# for N = 10   :  90.2 µs ± 10.2 µs for the slow way VS 14.1 µs ± 161 ns  for the DFT-way
# for N = 1000 : 579   ms ± 9.14 ms for the slow way VS 69.4 µs ± 2.35 µs for the DFT-way

from physipy import units
ms = units['ms']
mus = units['mus']
print("Gain in speed for 10 samples length: ", 90*mus/(14*mus))
print("Gain in speed for 1000 samples length: ", 579*ms/(69*mus))

消す:

  • N=10 サンプルの場合、DFT は 6 倍高速になります
  • N=1000 サンプルの場合、DFT は約 10000 倍高速になります

大きいですね!ここで、数千のサンプルを含む時系列を分析するときに何ができるかを考えてみましょう。

時系列のフーリエ変換: numpy を使用した高速畳み込みの説明 | ヨアン・モッキン著 | 2023 年 7 月 | データサイエンスに向けて

おすすめ

転載: blog.csdn.net/gongdiwudu/article/details/131867755