ディジタル信号処理 第12回 適応信号処理

💡
入力される信号がいつも同じとは限らない。そんな中でも「いい感じ」にその信号をしょりしてくれる信号処理があると嬉しい。そのように、入力信号に適応する信号処理を考えよう。

0. 復習

ノイズキャンセリングイヤホン

 ノイズキャンセリングイヤホンは、様々な環境の中でもその音をキャンセルしなければならない(= 適応信号処理)。

その中身は適応信号処理で書かれている。(この動画シリーズはいろいろ解説しているので、興味があれば見てください)

最急降下法 (勾配法の一つ)

 ある関数について、その値を最小化(あるいは最大化)するようにパラメータを反復的に更新する数値計算法。最小化する関数を f(x1,x2,,xN)f(x_1, x_2, \ldots, x_N)、更新するパラメータを x=[x1,x2,,xN]\bm{x} = [x_1, x_2, \ldots, x_N]^\top とすると、最急降下法のアルゴリズムは以下で記述される。

  1. 反復回数 kk を 0 に初期化。パラメータを x(k=0)=[x1(0),x2(0),,xN(0)]\bm{x}^{(k=0)} = [x_1^{(0)}, x_2^{(0)}, \ldots, x_N^{(0)}]^\top に初期化。
  1. 勾配 g=[fx1x=x(k),fx2x=x(k),,fxNx=x(k)]\bm{g} = \left[ \left. \frac{\partial f}{\partial x_1}\right|_{\bm{x}=\bm{x}^{(k)}}, \left. \frac{\partial f}{\partial x_2}\right|_{\bm{x}=\bm{x}^{(k)}}, \ldots, \left. \frac{\partial f}{\partial x_N}\right|_{\bm{x}=\bm{x}^{(k)}}\right]^\top を求める。
  1. パラメータをx(k+1)=x(k)αg\bm{x}^{(k+1)} = \bm{x}^{(k)} - \alpha \bm{g} で更新する(α\alpha はハイパーパラメータ)。kk を 1つ増やす。
  1. 3. の更新量が大きければ 2. に戻る。
  1. x(k)\bm{x}^{(k)} を最終的なパラメータとする。

例題f(x)=x22x+4y2f(x) = x^2 - 2x + 4y^2 を最小化するパラメータ x,yx, y を求めよ。

  1. k=0k = 0 に初期化、x(0)=1,y(0)=0x^{(0)} = 1, y^{(0)} = 0 に初期化
  1. 勾配を求める
    1. fx=2x2,fy=8y\frac{\partial f}{\partial x}=2x-2, \frac{\partial f}{\partial y}=8y
  1. パラメータを更新する。
    1. x(k+1)=x(k)α(2x(k)2),    y(k+1)=y(k)α(8y(k))x^{(k+1)} = x^{(k)} - \alpha(2x^{(k)}-2),\;\; y^{(k+1)} = y^{(k)} - \alpha(8y^{(k)})
  1. (以下省略)
# 参考:上図のプロットに使ったコード
import numpy as np
import matplotlib.pyplot as plt

def f(x, y): # function
    return x**2 - 2*x + 4*y**2

def g(x, y): # gradient
    return 2*x - 2, 8*y

# for draw
X, Y = np.meshgrid(np.linspace(0.9, 2.1, 12), np.linspace(-0.1, 1.1, 12))
cont = plt.contour(X, Y, f(X, Y), 20) 

# gradient descent
k = 0
x, y = 2, 1
alpha = 0.1
x_hist, y_hist = [x], [y]

for i in range(5):
    # gradients
    dx, dy = g(x, y)
    
    # update
    x -= alpha * dx
    y -= alpha * dy
    k += 1

    # for draw
    x_hist.append(x)
    y_hist.append(y)

# draw
plt.plot(x_hist, y_hist, 'ro-')
for k, (x, y) in enumerate(zip(x_hist, y_hist)):
    plt.text(x - 0.04, y - 0.02, f'{k}', fontsize=12)
plt.savefig('plot.png')

 この図から

ならば良い解にたどり着けることがわかる。これは、関数が合成関数であろうと同様に実行可能である(=ニューラルネットワークの学習)。

1. 適応信号処理 

“適応”する信号処理

 下図のようなノイズキャンセリングヘッドホンを考えよう。

ヘッドホンの外で雑音(例えば、人混み、電車)x(n)x(n) がなっている。この雑音の一部はヘッドホン内部に漏れ、d(n)d(n) として鼓膜に伝わる。このとき、x(n)x(n)d(n)d(n) の間の伝達特性は未知かつ時変とする(例えば、人の頭の形によってヘッドホンの装着具合は異なるし、その装着具合も時々刻々と変化する)。

 この漏れ聞こえる雑音を除去したい(=ノイズキャンセリング)。このためにヘッドホン内部にディジタル回路を用意する。このディジタル回路のパラメータ(フィルタ係数)は制御可能とし、x(n)x(n) の入力に対し何らかの出力 y(n)y(n) を出力する。この出力 y(n)y(n) はスピーカを通じてヘッドホン内部に再生される。このとき、 d(n)d(n)y(n)y(n) が逆相、すなわち y(n)=d(n)y(n)=-d(n) となれば漏れ聞こえる雑音は 0 となる。

 これを実現するフィルタとして FIR フィルタを考える。

過去の NN サンプルを用いて y(n)y(n) を出力する。フィルタ係数は、w0(n),w1(n),,wN1(n)w_0(n), w_1(n), \ldots, w_{N-1}(n)NN 個である。ここで、d(n)d(n) の特性は時変であるため、その効果を打ち消すフィルタも時変であるべきである。そのため、フィルタ係数に "(n)""(n)" をつけて係数が時変であることを表す。式で表すと、

y(n)=k=0N1wk(n)x(nk)y(n)=\sum\limits_{k=0}^{N-1}w_{k}(n)x(n-k)

である(総和範囲が教科書と 1 ずれていることに注意する)。このとき、打ち消したい信号との差の二乗、すなわち、

e2(n)=(d(n)y(n))2e^2(n) = \left(d(n) - y(n) \right)^2

を最小化するようにフィルタ係数を決定すればよい。

 フィルタ係数を決定するうえで、満たしたほうが良い条件がいくつかある。

  1. 雑音を高精度に除去できること
  1. フィルタ係数の計算量が小さいこと
    1. フィルタ係数を時々刻々と決定するため、定められた更新タイミング内で終了する計算量でなければならない。例えば 0.1 秒ごとにフィルタ係数を更新したいのに、その計算に 1 秒かかっては使い物にならない。
  1. (本講義では触れないが) 被対象音の混入に対して頑健であること

線形予測は使えないのか?

 前回の講義で線形予測 (linear prediction) を習った。この方法でフィルタ係数を求めればよいのではないだろうか?

 確かにこの方法を使えば、フィルタ係数を解析的に求められる。しかしながら、この方法は計算業が大きい。このフィルタ係数 hh は以下の行列の式を解くと求められる。ϕxx\phi_{xx} は自己相関関数である。

[ϕxx(0)ϕxx(1)ϕxx(2)ϕxx(3)ϕxx(4)ϕxx(5)ϕxx(6)ϕxx(7)ϕxx(1)ϕxx(0)ϕxx(1)ϕxx(2)ϕxx(3)ϕxx(4)ϕxx(5)ϕxx(6)ϕxx(2)ϕxx(1)ϕxx(0)ϕxx(1)ϕxx(2)ϕxx(3)ϕxx(4)ϕxx(5)ϕxx(3)ϕxx(2)ϕxx(1)ϕxx(0)ϕxx(1)ϕxx(2)ϕxx(3)ϕxx(4)ϕxx(4)ϕxx(3)ϕxx(2)ϕxx(1)ϕxx(0)ϕxx(1)ϕxx(2)ϕxx(3)ϕxx(5)ϕxx(4)ϕxx(3)ϕxx(2)ϕxx(1)ϕxx(0)ϕxx(1)ϕxx(2)ϕxx(6)ϕxx(5)ϕxx(4)ϕxx(3)ϕxx(2)ϕxx(1)ϕxx(0)ϕxx(1)ϕxx(7)ϕxx(6)ϕxx(5)ϕxx(4)ϕxx(3)ϕxx(2)ϕxx(1)ϕxx(0)][h(1)h(2)h(3)h(4)h(5)h(6)h(7)h(8)]=[ϕxx(1)ϕxx(2)ϕxx(3)ϕxx(4)ϕxx(5)ϕxx(6)ϕxx(7)ϕxx(8)]\left[\begin{array}{cccccccc}\phi_{xx}(0) & \phi_{xx}(1) & \phi_{xx}(2) & \phi_{xx}(3) & \phi_{xx}(4) & \phi_{xx}(5) & \phi_{xx}(6) & \phi_{xx}(7) \\\phi_{xx}(1) & \phi_{xx}(0) & \phi_{xx}(1) & \phi_{xx}(2) & \phi_{xx}(3) & \phi_{xx}(4) & \phi_{xx}(5) & \phi_{xx}(6) \\\phi_{xx}(2) & \phi_{xx}(1) & \phi_{xx}(0) & \phi_{xx}(1) & \phi_{xx}(2) & \phi_{xx}(3) & \phi_{xx}(4) & \phi_{xx}(5) \\\phi_{xx}(3) & \phi_{xx}(2) & \phi_{xx}(1) & \phi_{xx}(0) & \phi_{xx}(1) & \phi_{xx}(2) & \phi_{xx}(3) & \phi_{xx}(4) \\\phi_{xx}(4) & \phi_{xx}(3) & \phi_{xx}(2) & \phi_{xx}(1) & \phi_{xx}(0) & \phi_{xx}(1) & \phi_{xx}(2) & \phi_{xx}(3) \\\phi_{xx}(5) & \phi_{xx}(4) & \phi_{xx}(3) & \phi_{xx}(2) & \phi_{xx}(1) & \phi_{xx}(0) & \phi_{xx}(1) & \phi_{xx}(2) \\\phi_{xx}(6) & \phi_{xx}(5) & \phi_{xx}(4) & \phi_{xx}(3) & \phi_{xx}(2) & \phi_{xx}(1) & \phi_{xx}(0) & \phi_{xx}(1) \\\phi_{xx}(7) & \phi_{xx}(6) & \phi_{xx}(5) & \phi_{xx}(4) & \phi_{xx}(3) & \phi_{xx}(2) & \phi_{xx}(1) & \phi_{xx}(0) \\\end{array}\right] \left[\begin{array}{cccccccc} h(1) \\ h(2) \\ h(3) \\ h(4) \\ h(5) \\ h(6) \\ h(7) \\ h(8) \\\end{array}\right] = \left[\begin{array}{cccccccc} \phi_{xx}(1) \\ \phi_{xx}(2) \\ \phi_{xx}(3) \\ \phi_{xx}(4) \\ \phi_{xx}(5) \\ \phi_{xx}(6) \\ \phi_{xx}(7) \\ \phi_{xx}(8) \\\end{array}\right]

この計算は、

を要請される。そのため、計算量を抑えたい目的にそぐわない。

(余談) 上記の計算量はあくまでナイーブに計算したときの計算量である。例えば、

のように計算量を抑えられる。興味のある方は調べてほしい。

勾配法(最急降下法)を用いて更新しよう

 前述したように、勾配法なら計算量が少なくて済む。具体的には、

である。これを踏まえてフィルタ係数の勾配を求めてみる。誤差関数の2乗は、

e2(n)=(d(n)y(n))2=(d(n)k=0N1wk(n)x(nk))2\begin{align} e^2(n) &= \left(d(n) - y(n) \right)^2 \\ &= \left(d(n) - \sum\limits_{k=0}^{N-1}w_{k}(n)x(n-k) \right)^2 \end{align}

 であり、この微分は、

e2(n)wk(n)=2e(n)x(nk)e(n)x(nk)\begin{align} \frac{\partial e^2(n)}{\partial w_k(n)} &= - 2 e(n) \cdot x(n-k) \\ &\propto - e(n)\cdot x(n-k) \end{align}

となる。すなわち更新式は、

ωk(n+1)=ωk(n)+αe(n)x(nk)\omega_{k}(n+1) = \omega_{k}(n) + \alpha e(n) x(n-k)

となる。α\alpha を適切に設定できれば、この式によって更新が可能(同じ時刻でこの反復を繰り返すことも可能)。この式は、

2. まとめ

3. 次回(演習)・次々回(期末試験)について