適応フィルタ

Last update : 2018/0822

適応フィルタの概要

適応フィルタは、フィルタ構造を持つ、線形のニューラルネットと考えることができ、学習により、所望の特性が得られるよう、フィルタ係数を調整することができます。

ウィナーフィルタ

ウィナーフィルタ (Wiener filter) は、適応フィルタの理論的背景となるものです。ウィナーフィルタでは、図1の望みの信号(desired signal)とフィルタ出力 y(n)との差 e(n) の二乗平均 E[e2(n)] を最小化するようフィルタ係数 w を決定します。これを適応フィルタの学習と呼びます。最適なフィルタ係数は、次式の正規方程式の解となります。

(1)

ここで、R および r は、次式で定義される、自己相関行列および相互相関ベクトルです。

(2)

図1:適応フィルタのブロックダイアグラム

適応フィルタ・アルゴリズム

適応フィルタは、式(1)の解を逐次的に与えます。適応フィルタのアルゴリズムには、様々なものがありますが、ここでは、代表的な3つのアルゴリズムを簡単に紹介します。

LMS法

Least mean square (LMS) 法を正規化したN-LMS法は、最急降下法の近似であり、フィルタ更新式は、次式で与えられます。

(3)ここで、u(n) = [u(n), ..., u(n-M+1]T はフィルタの入力ベクトル、Mは、フィルタ係数の数を表します。ξ(n) = d(n) - wn-1Tu(n) は事前誤差、μは学習の速度を制御するステップサイズパラメタ、α は0で割るのを防ぐための正則化定数です。

APA法

Affine Projection algorithm (APA)法は、NLMS法をブロック入力に拡張した方法であり、フィルタ更新式は、次式で与えられます。

(4)

ここで、ブロック入力は次式で定義されます。

(5)

事前誤差もベクトル化されています。

(6)

RLS法

Recursive least square(RLS)法は、式(1)の正規方程式における自己相関行列 R の逆行列を、次式を用いて逐次的に与えます。

(7)

ここで、

(8)

最適解は、次式で得られます。

(9)アルゴリズムは、これを基に、カルマンフィルタに似た形式に書き替えられています。

参考文献

[1] 浅野太、「音のアレイ信号処理」、コロナ社、2011年

適応フィルタモジュール

adaptiveFilter.py

import numpy as np

import numpy.linalg as lin

########################

# Class LMS

########################

class LMS:

def __init__(self,M,mu,alpha):

self.M = M

self.mu = mu

self.alpha = alpha

def fit(self,x,d):

M = self.M

mu = self.mu

alpha = self.alpha

K = x.size

w = np.zeros(M)

e = np.zeros(K)

for k in np.arange(M,K):

u = x[k:k-M:-1]

ep = d[k] - np.dot(w,u)

w = w + mu/(alpha+lin.norm(u)**2)*ep*u

e[k] = d[k] - np.dot(w,u)

self.w = w

self.e = e

def predict(self,x):

K = x.size

y = np.convolve(self.w,x)[0:K]

return y

########################

# Class APA

########################

class APA:

def __init__(self,M,L,mu,alpha):

self.M = M

self.L = L

self.mu = mu

self.alpha = alpha

def fit(self,x,d):

M = self.M

L = self.L

mu = self.mu

alpha = self.alpha

K = x.size

w = np.zeros(M)

e = np.zeros(K)

for k in np.arange(M+L,K):

U = np.zeros((M,L))

# Block input

for l in range(L):

u = x[k-l:k-l-M:-1]

U[:,L-l-1] = u

Q = lin.inv(alpha*np.eye(L) + np.dot(U.T,U))

# Prior Error

dd = d[k-L+1:k+1]

ep = dd - np.dot(w,U)

# Delta

delta = np.dot(np.dot(U,Q),ep)

w = w + mu*delta

u = x[k:k-M:-1]

e[k] = d[k] - np.dot(w,u)

self.w = w

self.e = e

def predict(self,x):

K = x.size

y = np.convolve(self.w,x)[0:K]

return y

########################

# Class RLS

########################

class RLS:

def __init__(self,M,c):

self.M = M

self.c = c

def fit(self,x,d):

M = self.M

K = x.size

P = (1/self.c) * np.eye(M)

w = np.zeros(M)

e = np.zeros(K)

for k in np.arange(M,K):

# Input vector

u = x[k:k-M:-1]

# Gain vector

Pu = np.dot(P,u)

g = Pu/(1 + np.dot(u,Pu))

# Prior error

ep = d[k] - np.dot(w,u)

# Update

w = w + g*ep

P -= np.outer(g,Pu.T)

# Error

e[k] = d[k] - np.dot(w,u)

self.w = w

self.e = e

def predict(self,x):

K = x.size

y = np.convolve(self.w,x)[0:K]

return y

サンプルプログラム

ここでは、適応フィルタの応用として、図2のような雑音除去問題を考えます。望みの応答には、信号s(n)に雑音v(n)が加わったものが観測されます。例えば、工場などの騒音のある場所で、マイクに向かってしゃべっている場合が、これに当たります。工場の中にある機械からの雑音は、インパルス応答 h(n) により変形されています。もし、工場の中の機械のそばに行って、雑音だけを収録できる場合は、これを適応フィルタの参照入力 u(n) として学習を行い、雑音成分を望みの応答から差し引くことで、信号を回復することができます。この場合、適応フィルタは、インパルス応答 h(n) を学習します。

図2:雑音除去のブロックダイアグラム

adaptiveFilterExample.py

import numpy as np

import matplotlib.pyplot as plt

import numpy.linalg as lin

import scipy.stats as st

import adaptiveFilter as af

# Parameters

K = 30000 # データのサンプル数

T = 1000 # サイン波の周期

snr = 1.0 # SNR (信号対雑音比)

mu = 0.001 # ステップサイズパラメタ

alpha = 1e-3 # 正則化定数

M = 8 # フィルタの次数

L = 4 # [APA] アフィン変換の次数

c = 1 # [RLS] 初期化定数

figSize = (8,10)

# 観測値の生成

t = np.arange(K)

signal = np.sin( np.pi/T*t )

noise = st.norm.rvs(size=K)

h = np.array([0.0,1.0,-0.8,0.6,0,0,0,0]) # インパルス応答

d = np.convolve(h,noise)[0:K] + snr*signal

z = noise

plt.figure(figsize=(6,4))

plt.stem(h)

plt.figure(figsize=figSize)

plt.subplot(3,1,1), plt.plot(t,signal), plt.title('Signal')

plt.subplot(3,1,2), plt.plot(t,noise), plt.title('Noise')

plt.subplot(3,1,3), plt.plot(t,d), plt.title('Signal + Noise')

# 正規方程式

R = np.zeros((M,M))

r = np.zeros((M))

for k in range(M,K):

u = z[k:k-M:-1]

R += np.outer(u,u)

r += d[k]*u

w = np.dot(lin.inv(R),r)

y = np.convolve(w,z)[0:K]

plt.figure(figsize=figSize)

plt.subplot(3,1,1), plt.plot(t,d), plt.title('Signal + Noise')

plt.subplot(3,1,2), plt.plot(t,d-y), plt.title('Estimated signal')

# LMS

lms = af.LMS(M,mu,alpha)

lms.fit(z,d)

yLMS = lms.predict(z)

# APA

apa = af.APA(M,L,mu,alpha)

apa.fit(z,d)

yAPA = apa.predict(z)

# RLS

rls = af.RLS(M,c)

rls.fit(z,d)

yRLS = apa.predict(z)

# Graph

titleString = ['LMS','APA','RLS']

plt.figure(figsize=figSize)

gmax = 4.0

for n in range(3):

if n==0:

g = lms.e

elif n==1:

g = apa.e

elif n==2:

g = rls.e

plt.subplot(3,1,n+1), plt.plot(t,g)

plt.title(titleString[n])

plt.xlim(0,K)

plt.ylim(-gmax,gmax)

plt.figure(figsize=figSize)

gmax = 4.0

for n in range(3):

if n==0:

g = lms.e-signal

elif n==1:

g = apa.e-signal

elif n==2:

g = rls.e-signal

plt.subplot(3,1,n+1), plt.plot(t,g)

plt.title(titleString[n])

plt.xlim(0,K)

plt.ylim(-gmax,gmax)

plt.figure(figsize=figSize)

gmax = 1.2

for n in range(3):

if n==0:

g = d-yLMS

elif n==1:

g = d-yAPA

elif n==2:

g = d-yRLS

plt.subplot(3,1,n+1), plt.plot(t,g)

plt.title(titleString[n])

plt.xlim(0,K)

plt.ylim(-gmax,gmax)

plt.show()

上記のサンプルプログラム例では、信号 s(n)をサイン波、雑音をガウス雑音、インパルス応答をh(n)=[0,0, 1.0, -0.8, 0.6] (図3参照)として、学習を行います。図4は、s(n), v(n), d(n)をそれぞれ表します。図5は、望みの応答から、フィルタ出力を引いたe(n)を表します。学習が進むにつれ、雑音が除去され、信号のサイン波が現れてくるのがわかります。図6は、雑音の推定誤差を表します。LMS < APA < RLSの順で、収束が早くなっているのがわかります。これは、時刻nのフィルタ係数 wn を更新するために用いられるデータのサンプル数に概ね比例する結果となっています。収束が早くなる代償として、計算量もこの順で大きくなります。なお、プログラムでは、結果がわかりやすくなるよう、様々な定数を調整してあります。定数の値によって、結果は変化します。

図3:インパルス応答 h(n)

図4:(a)信号、(b) 雑音、(c)望みの応答(信号+雑音)

図5:事後誤差 e(n)

図6:雑音推定誤差 e(n)-s(n)