畳み込み積分とは？(定義、イメージ、意味など)

畳み込み積分、あらゆる分野で出てきますよね。
私は信号処理の分野で出会っているのですが、初めて見たときはその意味が全く分かりませんでした。おそらくそんな方は結構多いと思いますので、私が身に着けた畳み込み積分のイメージについて共有したいと思います。
いつも通り数学的な厳密性は不問としておいてください。

#定義
たたみ込み積分は

```math
s(t) = \int_{-\infty}^{\infty}f(\tau)g(t-\tau)d\tau
```

という積分で定義されます。定義に関してはいくらでもコマゴマと書いている書籍、ウェブサイトがございますので、そちらをご覧ください。
<font color = "DeepPink">本稿では、あくまでフワーッと畳み込み積分のイメージをとらえて頂くことを目標にしたいと思います。</font>

#正弦波とデルタ関数の畳み込み積分
まずは、$f(\tau)$を正弦波、$g(t-\tau)$をデルタ関数としたときの畳み込み積分の様子を図で以下に示します。
大前提として、<font color ="DeepPink">ある関数$f(t)$とデルタ関数の畳み込み積分の結果は$f(t)$になります。</font>これもいろいろな教科書に書いてあるので、導出は省略します。
![delta.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271946/f19b5be8-bc5a-b31c-81e7-a95414f28380.png)

いくつかの$t$に注目して解説していきます。
(1) たとえば$t=0.5$のとき、デルタ関数が0ではない値を持つのは$\tau=0.5$ですので、2枚目のグラフの一番左の青い点線がこれに対応します。
しかし、1枚目のグラフを見てわかる通り、今回のシミュレーションでは正弦波の周期を1としているので、正弦波の値は$\tau=0,0.5,1,...$で0になります。よって、$f(0.5)g(0.5-0.5)=0$となり、(見えませんが)3枚目のグラフの$\tau=0.5$の点では値は0となっています。

(2) 次に、$t=0.8$に注目します。
デルタ関数はこのとき$\tau=0.8$で0以外の値を持ちます。2枚目のグラフのオレンジ色の点線です。
そして、<font color="DeepPink">この場合は先ほどと違い、正弦波も0ではない値を持ちます。</font>よって、これらの積は3枚目で下向きに伸びているオレンジの線のように$f(0.8)g(0.8-0.8)=f(0.8)={\rm sin}(2\pi\times 0.8)$という値を持つことになります。

上記(1)、(2)のようにいろいろな$t$についてこの過程を繰り返してやると、4枚目のような点たちをプロットすることができます。

ある程度たくさんプロットして、これらを曲線でフィッティングすると、5枚目のようにもとの正弦波を復元することができます。

この例が畳み込み積分の最も簡単な例になります、
というのも、<font color = "DeepPink">$g(t)$がデルタ関数だと3枚目のグラフ上で(一つの$t$に対して)0ではないの値を持つ箇所が1か所しかなく、積分(総和)を考えずともその値をそのまま4枚目にプロットすれば良いからです。</font>
たとえば、(1)の場合では3枚目も4枚目も0、(2)の場合では3枚目も4枚目も${\rm sin}(2\pi\times 0.8)$にプロットすればよかったですよね。これが、$g(t)$がデルタ関数ではないときにはそう簡単にはいかなくなるのです。
次の章ではデルタ関数ではなく矩形関数の場合の畳み込み積分についてみていきます。

#正弦波と矩形関数の畳み込み積分
次に、まずは、$f(\tau)$を正弦波、$g(t-\tau)$を矩形関数としたときの畳み込み積分の様子を図で以下に示します。
これも大前提ですが、<font color = "DeepPink">矩形関数の矩形幅が正弦波の周期と一致するとき、その畳み込み積分の結果はすべての$t$について0になります。</font>
![rect.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271946/ae42033f-5579-05f6-cf13-31f753fac507.png)


先ほどの同じように、いくつかの$t$に注目していきましょう。

(1) たとえば$t=0.5$のとき、本稿で扱う矩形関数が0ではない値を持つのは$0 < \tau < 1$ですので、2枚目のグラフの一番左の青い点線のハコがこれに対応します。
ちょうどこのハコを窓にして$f(\tau)$を覗くように、3枚目のグラフでは$0 < \tau < 1$に青線で描かれている$f(\tau)$が現れ、それ以外の部分は0になります。

<font color = "DeepPink">そしてここからがポイントなのですが、この青線を$\tau$の全区間にわたって足し合わせた(積分した)ものが4枚目にプロットするべき値となります。</font>

すなわち、$s(0.5)$を求めるためには$\int_{-\infty}^{\infty}f(\tau)g(0.5-\tau)d\tau$を計算しなければならないのです。これがデルタ関数の場合とチョット違うところです。(デルタ関数でも本来同じ過程を踏むのですが、$g(t)$がデルタ関数だとしたら上記の積分は一瞬で解けるため、事実上積分の必要は無いということです。)
今回の場合は、3枚目のように矩形関数によって正弦波がちょうど1周期分取り出されているので、その総和は当然0になります。
よって、4枚目では$t=0.5$で0にプロットすればいいわけです。

(2) ほかにも、$t=2$のときでは矩形関数は$1.5 < \tau < 2.5$で1となるので、2枚目のオレンジ線のハコが書けます。
先ほどの全く同様に、これを窓として$f(\tau)$を覗くと3枚目のグラフでは$1.5 < \tau < 2.5$にオレンジ線で描かれている$f(\tau)$が現れ、それ以外の部分は0になります。
このオレンジ線の総和もやはり0なので、4枚目の$t=2$にも0にプロットしたらいいとわかります。

矩形関数の矩形幅が正弦波の周期と同じ長さのとき、ハコはどこにあったとしてもそこから見える$f(\tau)$は1周期分になるはずなので、4枚目、5枚目は常に0という結果になるはずです。

#ちょっと一般化して考える
ここまでで、なんとなく畳み込み積分のイメージをつかんでもらえたのではないかと思いますので、ちょっと一般化して何をしているのかをまとめます。
便宜上、<font color = "DeepPink">$f(\tau)$を「基準関数」、$g(t-\tau)$を「中心$t$のズラし関数」と呼ぶ</font>ことにしましょう。畳み込み積分は、

1. $t=t'$における畳み込み積分の結果$s(t')$を求めるために、まずは基準関数と中心$t'$のズラし関数を掛け合わせる
2. 掛け合わせた結果の関数を、全区間にわたって積分したものが求めたい$s(t')$
3. $t'=t'+\Delta$としてズラし関数をちょっとだけズラし、1.に戻る

という操作によって実行されます。

#畳み込み積分の物理的な意味
1章では畳み込み積分の定義について述べ、2章3章ではデルタ関数・矩形関数との畳み込み積分の結果を視覚的にとらえてきました。
4章では2章3章で行った操作を、「基準関数」「ズラし関数」という言葉を使ってちょっと一般化しました。
本章では、畳み込み積分の物理的な意味についてご紹介いたします。ただ、物理的意味はもしかしたら畳み込み積分が使われる分野や場面に応じて変わる可能性があるので、<font color = "DeepPink">あくまでもone of theイメージであると思っていただけるといいかなと思います。</font>

「ズラし関数」「畳み込み積分の結果」「基準関数」の関係についてですが、ひとことで言うと
<font color = "DeepPink">ズラし関数は「時刻$t'$における畳み込み積分の結果を計算するにあたって、基準関数の時刻$t'$付近の影響をどの程度受けるか」を表しています。</font>

たとえば、2章ではズラし関数としてデルタ関数を扱いましたよね。
デルタ関数は時刻$t'$という特定の時刻でのみ1となり、それ以外の時刻では常に0です。これは<font color = "DeepPink">「畳み込み積分の結果は、時刻$t'$における基準関数の値以外の影響を全く受けない」</font>と捉えなおすことができます。だからこそ3枚目のグラフから4枚目のグラフにかけて、全く値が変わらなかったわけです。

さらに、3章ではズラし関数として矩形関数を扱いました。
矩形関数は時刻$t'-0.5$から時刻$t'+0.5$でのみ1となり、それ以外の時刻では常に0です。これは<font color = "DeepPink">「畳み込み積分の結果は、時刻$t'-0.5$から時刻$t'+0.5$における基準関数の値の影響を等しく受ける」</font>と捉えなおすことができます。こうなると、3枚目のグラフでは波が現れますが、4枚目にプロットするべき点としては波のすべての成分が互いに打ち消しあった「0」となる、という感じです。

ほかにも、たとえばズラし関数がsinc関数だった場合(信号処理分野では頻出)にはどうでしょうか。
sinc関数の形は以下のような感じです。
![sinc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271946/ff1ecfab-9d45-a4db-5f77-3c115fc210f2.png)
sinc関数は真ん中で1をとるので基準関数の時刻$t'$の成分はかなり強く表れますが、同時に外側でも波打っており常に0というわけではないので、これに付随して基準関数の時刻$t'$の周りの影響も色濃く受ける、と予想されますよね。
実際その通りで、信号処理の分野では受信信号が元の波形とsinc関数の畳み込み積分として得られ、元の波形を復元するためにsinc関数の影響を取り除かなければならないなんてこともあります。

#まとめ
本稿では2章・3章で畳み込み積分のイメージをつかめるように、図で一連の流れを示しました。
最後の5章では「基準関数」「ズラし関数」「畳み込み積分の結果」の間にどのような関連性があるのかについて述べました。
理解の一助になれたとしたら幸いです。

#参考にしたらいいと思う資料
東北工業大学　中川朋子先生
http://www.ice.tohtech.ac.jp/~nakagawa/laplacetrans/convolution1.htm
ズラし関数を「忘却の度合い」と表現されていらっしゃいます。
この見方で言うと、デルタ関数は「一瞬で忘れるような忘れ方」、矩形関数は「しばらくはメッチャ覚えてるけどある日ふと忘れてしまうような忘れ方」と言えます。


#付録
例のごとくクソコードですが貼っておきます。
一番上のresolutionとか関数の種類を指定するところをいじるとグラフをちょっと変えられます。

```python
#%% これらの値を変更すると出力されるグラフが変わる

resolution = 3
g_function = 'rect' #rectまたはdelta

#%% 諸々の設定と関数の定義(さわらなくてもOK)
import numpy as np
import matplotlib.pyplot as plt

def rect(center_index, full_length, sampling):
    temp = np.zeros(full_length)
    temp[center_index-int(sampling/2): center_index+int(sampling/2)] = 1
    return temp

def delta(center_index, full_length):
    temp = np.zeros(full_length)
    temp[center_index] = 1
    return temp

#%% 畳み込み積分の計算

iteration = 4
sampling = 1000
N = iteration * sampling

center_index = int(N/2)
time_index = np.arange(N)/sampling

f_signal = np.sin(2*np.pi*time_index)

conv_signal = np.zeros(N)
for i in range(int(sampling/2), N-int(sampling/2)):
    if g_function == 'rect':
        g_signal = rect(i, len(f_signal), sampling)
    elif g_function == 'delta':
        g_signal = delta(i, N)
    else: print('すみません、gの関数は矩形関数かデルタ関数にのみ対応しています')
    conv_signal[i] = np.sum(f_signal * g_signal)
    
#%% グラフの出力

plt.subplots_adjust(wspace=0.4, hspace=0.8)

fig = plt.figure(1)

f_plt = fig.add_subplot(511)
f_plt.set_title('waveform of f(τ)')
f_plt.set_xlabel('τ')
f_plt.set_xlim([0, 4])
f_plt.set_ylim([-1.1, 1.1])

g_plt = fig.add_subplot(512)
g_plt.set_title('waveform of g(t-τ)')
g_plt.set_xlabel('τ')
g_plt.set_xlim([0, 4])

fg_plt = fig.add_subplot(513)
fg_plt.set_title('waveform of f(τ)g(t-τ)')
fg_plt.set_xlabel('τ')
fg_plt.set_xlim([0, 4])
fg_plt.set_ylim([-1.1, 1.1])

conv_plt = fig.add_subplot(514)
conv_plt.set_title('plot of value of $\int$f(τ)g(t-τ)dτ')
conv_plt.set_xlabel('t')
conv_plt.set_xlim([0, 4])
conv_plt.set_ylim([-1.1, 1.1])

lay_plt = fig.add_subplot(515)
lay_plt.set_xlim([0, 4])
lay_plt.set_ylim([-1.1, 1.1])

f_plt.plot(time_index, f_signal)

if g_function == 'rect':
    lay_plt.plot(time_index, conv_signal, linestyle='--')
elif g_function == 'delta':
    lay_plt.plot(time_index, f_signal, linestyle='--')

#res_sample = np.arange(int(sampling/2),N-int(sampling/2), int(1000/resolution))
res_sample = np.linspace(500,len(f_signal)-500,resolution)
res_sample = [int(n) for n in res_sample]
for i in res_sample:
    if g_function == 'rect':
        g_signal = rect(i, len(f_signal), sampling)
    elif g_function == 'delta':
        g_signal = delta(i, N)
    g_plt.plot(time_index, g_signal, linestyle = '--')
    fg_signal = f_signal * g_signal
    fg_plt.plot(time_index, fg_signal)

    conv_plt.scatter(time_index[i], conv_signal[i])
    lay_plt.scatter(time_index[i], conv_signal[i])

```