NumPyでフーリエ変換を計算するためのメモ〜sin, cosで展開したときの係数をどう読み取るか〜
はじめに
内容
numpy.fft.fft/rfft
の返り値は何を表しているか?numpy.fft.fft/rfft
の出力値から、でFourier変換した表現に書き直すにはどうすればいいか?- その際、データ数の偶奇で出てくる項・和の範囲が異なるのは何故か?
背景
- Fourier変換は時系列データの周期性を調べるときによく使われる
- 時系列を三角関数の重ね合わせとして表現する
- PythonでFourier変換を計算してみようとすると、引っかかる点が多い
- 三角関数表現をイメージをしながら
numpy.fft
を触れるように、対応関係を整理したい。
TL;DR
- 長さの時系列データを
numpy.fft.fft
でFourier変換した際の返り値の中身は、以下に示す指数関数表現の係数である:
- 三角関数を用いて展開した際の係数との対応は、以下のようになっている:
ただし、
- 三角関数表現にてデータ長の偶奇で計算方法が異なるのは、和をとる際に複素共役の関係を利用してで折り返しとでペアを作るためである。
- 入力データが実数であれば、
numpy.fft.rfft
を使って計算するのが便利である
序章:Fourier変換とはどのようなものか?
長さの時系列データが与えられた時、 (離散)Fourier変換では以下のように時系列を三角関数を使って書き表します1:
はともに周期の関数ですので、ざっくりいうといかなる時系列も周期関数に分解できる、ということを意味します。
この性質を利用して、Fourier変換はある時系列がどのような周期を持っているかを調べるときに使われます。
例えば、以下のような長さの時系列があったとします:
全体的に周期50のカーブを描いていますが、その周りで細かい動きも乗っています。 この細かい動きは単なるノイズなのか、またはなんらかの周期を持っているかまではよくわかりません。
ここでFourier変換を行い、さらに周期を持つ項の係数の大きさに着目するために、パワースペクトルと呼ばれるを見てやると、以下のようになります:
この結果ではの強いピークに加えて、のところにもピークが出ていますので、周期の成分に加えて、周期の成分も持っている、と理解できます。
後出しになりますが、先ほどの時系列は、以下のように周期50のsin波と周期10のcos波を重ね合わせたものにノイズを加えて生成しました。したがって、上記のパワースペクトルの結果は、周期性をうまく捉えられているといえそうです。
t = np.arange(100) y = 2.0 * np.sin(2*np.pi * t / 50) + 1.0 * np.cos(2 * np.pi * t / 10) + 0.8 * np.random.randn(t.shape[0])
なお、式(\ref{sincos_fourier})の中で、の範囲でという変数を用いましたが、こちらは以下のように入力データの長さの偶奇によって変わります:
どちらの場合も「未満の最大の整数」となっています。 何故このようになるかについては、後ほど解説します。
NumPyでFourier変換を行う方法
Fourier変換を実際にプログラミングで計算してみたい、という場合には、PythonのNumPyパッケージにあるnumpy.fft
を使うのが便利です。
ただし、使う際にいくつか気を付けるべきことがあります。
試しに先ほどの例で示した例を、関数numpy.fft.fft
にそのまま突っ込んでみます。
fy = np.fft.fft(y) print(fy.shape) print(fy) """ (100,) [-5.09497885e+00+0.00000000e+00j -1.47588178e+00-7.84995285e+00j 2.48806875e+00-9.64778465e+01j 3.05813510e+00-1.08640360e+01j -1.88409511e+00-4.69607698e+00j -4.18655969e-01+8.53439687e+00j 4.24523418e+00+8.25281748e+00j -5.00154635e+00+3.56548344e+00j ... """
このように、複素数のよくわからない配列が返ってくるのです。
これの意味を理解し、先ほどの式(\ref{sincos_fourier})で示した三角関数を用いた表現と対応付けるには、
- Fourier変換の指数関数表現とは何か
- 指数関数表現と三角関数表現はどのように対応付くか
を知る必要があります。
numpy.fft.fft
の出力と指数関数を使った表現
Fourier変換には、三角関数を用いた式(\ref{sincos_fourier})とは別に指数関数を用いた表現方法もあります。
そして、先ほどのnp.fft.fft
で出てきた複素数の配列の正体は、下記の指数関数表現の係数がそのまま対応しています:
いきなり指数関数と虚数が出てきましたが、の部分はオイラーの公式を用いて
のように表すことができます(オイラーの公式の簡単な導出は、最後に補足します)。
これを用いれば三角関数を使って書き直すことができるものの、複素数が含まれているため、 実数である式(\ref{sincos_fourier})の係数とは このままでは対応がつきません。 さらに、で和をとる範囲も2つの表現で異なっています。
指数関数表現と三角関数表現の対応
では、冒頭の式(\ref{sincos_fourier})で示した三角関数による表現と、先ほどの式(\ref{exp_fourier})の指数関数表現とは、どう対応しているのでしょうか?
結論からいくと、以下のように対応します:
これを以下で示します。
先ほど式(\ref{euler})でお見せしたオイラーの公式を、式(\ref{exp_fourier})の指数関数表現に代入して、に分けて行きます。
その際に、以下の関係に着目します:
これを用いて 和をとる際にで折り返し、とをペアにすると、
のように書き表すことができます。
このとき、についてはペアを作れないため、がそのまま残ります。
さらに、時系列の長さが偶数の際にも、がペアを作れないためがそのまま出てきます。
この偶奇による違いを理解するには、円周上でを表示してみるとわかりやすいです:
以上から、
と置くことで、式(\ref{sincos_fourier})のように表せることがわかりました。
依然として係数が複素数のように見えますが、入力時系列が実数(たいていの場合実数ですが)であれば、も実数となります。
何故なら、ととは以下のように複素共役の関係にあるからです:
途中、が整数なのでという関係を使いました。
この関係を用いると、は以下のように書き直せます、両者が実数となることがわかります:
(係数に2倍かかることに気をつけてください。)
なお、との間には、
という関係があることもわかります。
numpy.fft
を使って三角関数表現のFourier変換を行う
以上をもとに、numpy.fft
を使って時系列ををでFourier変換します。
前章ではnumpy.fft.fft
という関数を使いましたが、
今回はnumpy.fft.rfft
という別の関数を使います。
これは入力データが実数のときに使える関数で、
式(\ref{conjugate})に示したという複素共役の関係に着目し、numpy.fft.fft
の出力のうち前半部分だけを取り出したものです。
これを使うと、冒頭の例でお見せしたデータは以下のようにFourier変換することができます:
T = y.shape[0] if T %2 == 0: c = (ck[0].real + ck[-1].real) / T ak = ck[1:-1].real * 2 / T bk = -ck[1:-1].imag * 2 / T else: c = ck[0].real / T ak = ck[1:].real * 2 / T bk = -ck[1:].imag * 2 / T y_sum = c * np.ones_like(t) for k, (a, b) in enumerate(zip(ak, bk), 1): y_k = a * np.cos(2*np.pi*k / T * t) + b * np.sin(2*np.pi*k / T * t) y_sum += y_k
各周波数成分と、あるまでの累積和をプロットすると以下のようになります。
おわりに
本記事では、numpy.fft
の出力値の読み取りかたと、三角関数表現への変換方法を解説しました。
冒頭で触れたパワースペクトルの計算方法には、時系列そのものをFourier変換するほかに、自己共分散をFourier変換する方法もあります。
また、実際にパワースペクトルを計算する際には、1からFourier変換を行わずに、SciPyの専用の関数2を使うことができます。
その辺りについても、また記事として書きたいと思います。
最後になりますが、今回の計算・作図を行った際のコードは、こちらに公開しています。
補足
係数のかけかたのパターン
Fourier変換の際に、をどこでかけるかについては、以下の3パターンの流儀があります。
いずれの場合も最終的に得られる結果は同じです。
本記事では、NumPyの仕様に合わせてPattern 1で書きました。
人や分野によってどのパターンを用いるかが異なってくるので3、専門書を読み比べる時や複数のライブラリを扱う際にはお気をつけください。
Eulerの公式の簡易な導出
式(\ref{euler})で用いたEulerの公式
について、Tayler展開を用いた簡単な導出を示します。
さらに,のTayler展開はであることを利用し,
参考文献
-
詳細は省きますが、これは近似式ではなく、厳密かつ一意に成り立ちます。↩