WebRtc AEC核心算法之一:频域自适应滤波
WebRtc和Speex作为目前开源的语音增强平台,给非科班出身的工程师一探究竟的机会,本文接下来以WebRtc的Aec模块为主线,研究一下比较核心的算法和实现。
一直以来在心目中AEC就是利用自适应滤波器求解梯度的迭代过程,时域的LMS/RLS经典算法在教科书和主流文章中都有介绍,但频域的介绍的就不多了,恰恰两个流行的平台都选用了频域自适应滤波器,首先是因为频域方法避免了时域FIR滤波器抽头太多,计算复杂度较大的缺点,另外就是最近二三十年频域滤波的发展迅速,很多优秀的算法已经产品化,只是我们接触的少不知道而已。在《Acoustic MIMO Signal Processing》第五章 Frequency-Domain Adaptive Filters,有以下介绍:相比于时域方法,频域分块处理的计算复杂度显著的降低了。FFT的运用,令输入信号的时移属性非常适合Toeplitz矩阵结构,意味着设计一个频域自适应滤波算法仅仅是通过被显式的Toeplitz和循环矩阵重写时域的误差尺度而已。(说的真轻松!)据这本书介绍,最早是1978年Dentino等在《Adaptive filtering in the frequency domain》提出了频域滤波器,紧接着Ferrara在1980年的《Fast implementations of LMS adaptive filters》提出了频域最小均方差方法使得频域地维纳解实现了快速收敛。Mansour和Gray在1982年提出了《Unconstrained frequency-domain adaptive filter》,以及后来MDF(Multi delay filter is selected by speex)的提出和应用,这些基本奠定了频域自适应滤波器的基础。《An adaptive filtering algorithm using an orthogonal projection to an affine subspace and its properties》还介绍了 affine project方法,也是热门的话题。
自适应滤波器-从时域到频域
经典的LMS自适应滤波器是采用时域实现的,标准的权重因子w(n)w(n)w(n)迭代表达如下:
y(n)=wH(n)u(n)e(n)=d(n)−y(n)w(n+1)=w(n)+μu(n)e(n)
y(n)=w^H(n)u(n)\\
e(n)=d(n)-y(n)\\
w(n+1)=w(n)+\mu u(n)e(n)
y(n)=wH(n)u(n)e(n)=d(n)−y(n)w(n+1)=w(n)+μu(n)e(n)
u(n)u(n)u(n)是输入矢量,d(n)d(n)d(n)是期望的相应,w(n)w(n)w(n)是FIR滤波器抽头,μ\muμ是收敛因子。普通LMS的收敛因子是常数,而NLMS是对μ\muμ做了归一化处理,加速收敛速度的变形。但是实际实现过程遇到了两个挑战:
- 应用于回声消除等延时比较敏感的场景下,FIR的抽头(阶数)数量可观,意味需要较大的存储空间来存放权重因子,更为烦恼的是高阶卷积的运算量巨大,算法复杂度、稳定性等等都会遇到极大的挑战
- 收敛速度和稳定性一直是自适应滤波器需要追求的一种平衡,能否在频域发现更加优秀的算法取得收敛速度和稳定性的统一考量。
因此,频域自适应滤波(FDAF)、正交自适应滤波和子带自适应滤波被提出来,有书中归纳为块自适应滤波,因为输入信号序列u(n)u(n)u(n)被分成LLL点的数据块来处理,自适应过程是以块为单位来更新,这区别原始的以采样点为更新基础的算法。
在进行分块自适应讨论之前,我们先将上面的式子展开,便于理解,假设滤波器抽头为N:
[w0(k+1)w1(k+1)....wN−1(k+1)]=[w0(k)w1(k)....wN−1(k)]+μ[u(k)u(k−1)....u(k−N+1)]e(n)=[w0(k)w1(k)....wN−1(k)]+μ[u(k)u(k−1)....u(k−N+1)][d(n)−y(n)] \begin{aligned} \begin{bmatrix} w_0(k+1) \\ w_1(k+1)\\ ....\\ w_{N-1}(k+1)\\ \end{bmatrix}&=\begin{bmatrix} w_0(k) \\ w_1(k)\\ ....\\ w_{N-1}(k)\\ \end{bmatrix}+\mu\begin{bmatrix} u(k) \\ u(k-1)\\ ....\\ u(k-N+1)\\ \end{bmatrix}e(n)\\ &=\begin{bmatrix} w_0(k) \\ w_1(k)\\ ....\\ w_{N-1}(k)\\ \end{bmatrix}+\mu\begin{bmatrix} u(k) \\ u(k-1)\\ ....\\ u(k-N+1)\\ \end{bmatrix}[d(n)-y(n)]\\ \end{aligned} ⎣⎢⎢⎡w0(k+1)w1(k+1)....wN−1(k+1)⎦⎥⎥⎤=⎣⎢⎢⎡w0(k)w1(k)....wN−1(k)⎦⎥⎥⎤+μ⎣⎢⎢⎡u(k)u(k−1)....u(k−N+1)⎦⎥⎥⎤e(n)=⎣⎢⎢⎡w0(k)w1(k)....wN−1(k)⎦⎥⎥⎤+μ⎣⎢⎢⎡u(k)u(k−1)....u(k−N+1)⎦⎥⎥⎤[d(n)−y(n)]
从这个公式很容易得出,每计算出一个滤波器输出y(n)y(n)y(n),都需要更新权重因子,如果处理一帧数据是L个samples,那么随着输出更新L次,权重因子也更新L次,计算量是客观的。
块自适应滤波-block adaptive filtering[5]
假设输入数据块为向量表达:
u(n)=[u(n),u(n−1),...,u(n−N+1)]T
u(n)=[u(n),u(n-1),...,u(n-N+1)]^T
u(n)=[u(n),u(n−1),...,u(n−N+1)]T
此时的权重因子向量为:
w^(n)=[w0^(n),w1^(n),...,w^M−1(n)]T
\hat{w}(n)=[\hat{w_0}(n),\hat{w_1}(n),...,\hat{w}_{M-1}(n)]^T
w^(n)=[w0^(n),w1^(n),...,w^M−1(n)]T
对于nnn进一步的用如下定义来分解:
n=kL+i,i=0,1,...L−1,k=1,2,...,
\begin{aligned}
n=kL+i,\quad &i=0,1,...L-1,\\
&k=1,2,...,
\end{aligned}
n=kL+i,i=0,1,...L−1,k=1,2,...,
LLL表示块长度,这样对于输入数据第kkk块,可以定义为:
AT(k)=u(kL+i)i=0L−1=[u(kL),u(kL+1),...,u(kL+L−1)]
\begin{aligned}
\bold{A}^T(k)&={u(kL+i)}_{i=0}^{L-1}\\
&=[u(kL),u(kL+1),...,u(kL+L-1)]
\end{aligned}
AT(k)=u(kL+i)i=0L−1=[u(kL),u(kL+1),...,u(kL+L−1)]
这个时候容易瞢,回头看看u(n)u(n)u(n)可是MMM维数组,所以A(k)A(k)A(k)是一个矩阵,假设M=6,L=4M=6,L=4M=6,L=4,矩阵的格式如下
再举一例,由此我们得到y(n)y(n)y(n)的更新过程如下图(M=3,L=3)(M=3,L=3)(M=3,L=3):
这里很明显的看出A(k)A(k)A(k)是Toeplitz矩阵,主对角线以及平行主对角线的元素均相等。
block adaptive filtering权重因子更新算法是显著区别于经典算法的,下面只列出结果,有兴趣的同学可以参考[4][5]文献的介绍。
w(k+1)=w(k)+μ∑i=0L−1u(kL+i)e(kL+i)=w(k)+AT(k)e(k)
\begin{aligned}
w(k+1)&=w(k)+\mu \sum_{i=0}^{L-1}\bold{u}(kL+i)e(kL+i)\\
&=w(k)+\bold{A}^T(k)e(k) \\
\end{aligned}
w(k+1)=w(k)+μi=0∑L−1u(kL+i)e(kL+i)=w(k)+AT(k)e(k)
这里的k是表示第k*L个模块,和之前时域的k不一样了。也就是每个L个samples算一次,我们可以把上式得矩阵表示进一步展开:
[w0(k+1)w1(k+1)....wN−1(k+1)]=[w0(k)w1(k)....wN−1(k)]+μ[u(kL)x(kL−1)....x(kL−N+1)]e(kL)+μ[u(kL+1)x(kL)....x(kL−N+2)+]e(kL+1)+...+μ[u(kL+L−1)x(kL+L−2)....x(kL−N+L)]e(kL+L−1)
\begin{aligned}
\begin{bmatrix} w_0(k+1) \\ w_1(k+1)\\ ....\\ w_{N-1}(k+1)\\ \end{bmatrix}&=\begin{bmatrix} w_0(k) \\ w_1(k)\\ ....\\ w_{N-1}(k)\\ \end{bmatrix}+\mu\begin{bmatrix} u(kL) \\ x(kL-1)\\ ....\\ x(kL-N+1)\\ \end{bmatrix}e(kL)+\mu\begin{bmatrix} u(kL+1) \\ x(kL)\\ ....\\ x(kL-N+2)\\ +\end{bmatrix}e(kL+1)+\\&...+\mu\begin{bmatrix} u(kL+L-1) \\ x(kL+L-2)\\ ....\\ x(kL-N+L)\\ \end{bmatrix}e(kL+L-1)
\end{aligned}
⎣⎢⎢⎡w0(k+1)w1(k+1)....wN−1(k+1)⎦⎥⎥⎤=⎣⎢⎢⎡w0(k)w1(k)....wN−1(k)⎦⎥⎥⎤+μ⎣⎢⎢⎡u(kL)x(kL−1)....x(kL−N+1)⎦⎥⎥⎤e(kL)+μ⎣⎢⎢⎢⎢⎡u(kL+1)x(kL)....x(kL−N+2)+⎦⎥⎥⎥⎥⎤e(kL+1)+...+μ⎣⎢⎢⎡u(kL+L−1)x(kL+L−2)....x(kL−N+L)⎦⎥⎥⎤e(kL+L−1)
这样只需每个块L算一次,并且对这个块内的e(n)做了平滑处理。最后提一下,实际应用中往往采用M(N)=L,这是综合考量计算复杂度等因素,对理解算法也有帮助,上文的第二个例子就说明了当M(N)=L之后,很容易设计出A(k)A(k)A(k)的Toeplitz矩阵形式,尤其下面引入频域和傅立叶变换之后。
Fast Block LMS Algorithm & FDAF
回顾上一节的滤波器输出,用公式来表示:
y(kL+i)=w^T(k)u(kL+i)=∑j=0M−1w^j(k)u(kL+i−j),i=0,1,..,L−1.
\begin{aligned}
y(kL+i)&=\hat{w}^T(k)\bold{u}(kL+i)\\
&=\sum_{j=0}^{M-1}\hat{w}_j(k)u(kL+i-j), \quad i=0,1,..,L-1.
\end{aligned}
y(kL+i)=w^T(k)u(kL+i)=j=0∑M−1w^j(k)u(kL+i−j),i=0,1,..,L−1.
以及ϕ(k)\phi(k)ϕ(k)的定义:
ϕ(k)=∑i=0L−1u(kL+i)e(kL+i)=AT(k)e(k)
\begin{aligned}
\phi(k)&=\sum_{i=0}^{L-1}\bold{u}(kL+i)e(kL+i)\\
&=\bold{A}^T(k)e(k)
\end{aligned}
ϕ(k)=i=0∑L−1u(kL+i)e(kL+i)=AT(k)e(k)
这两个公式可以用线性卷积结构实现,而FFT经过特殊的处理就可以满足线性卷积的处理需求,即50%重叠存储法,如果M个抽头的滤波器,补充M个0,构建一个N=2M长度的FFT:
W^(k)=FFT[w^(k)0]
\hat{W}(k)=FFT\begin{bmatrix} \hat{w}(k) \\ 0 \\ \end{bmatrix}
W^(k)=FFT[w^(k)0]
构建频域对角阵
U(k)=diag{FFT[u(kM−M),...,u(kM−1),⎵(k−1)thblocku(kM),...,u(kM+M−1),⎵(k)thblock]}
\bold{U}(k)=diag\lbrace FFT[\underbrace{u(kM-M),...,u(kM-1),}_{(k-1)th\quad block}\underbrace{u(kM),...,u(kM+M-1),}_{(k)th\quad block}]\rbrace
U(k)=diag{FFT[(k−1)thblocku(kM−M),...,u(kM−1),(k)thblocku(kM),...,u(kM+M−1),]}
最后可以得到:
yT=[y(kM),y(kM+1),...,y(kM+M−1)]=last M elements of IFFT[U(k)W^(k)]
\begin{aligned}
y^T&=[y(kM),y(kM+1),...,y(kM+M-1)]\\
&=last\space M\space elements\space of\space IFFT [\bold{U}(k)\hat{\bold{W}}(k)]
\end{aligned}
yT=[y(kM),y(kM+1),...,y(kM+M−1)]=last M elements of IFFT[U(k)W^(k)]
而前M个元素被扔掉了。
定义一维期望信号向量和误差向量为:
d(k)=[d(kM),d(kM+1),...,d(kM+M−1)]Te(k)=[e(kM),e(kM+1),...,e(kM+M−1)]T=d(k)−y(k)
\begin{aligned}
d(k)&=[d(kM),d(kM+1),...,d(kM+M-1)]^T\\
e(k)&=[e(kM),e(kM+1),...,e(kM+M-1)]^T\\
&=\bold{d}(k)-\bold{y}(k)
\end{aligned}
d(k)e(k)=[d(kM),d(kM+1),...,d(kM+M−1)]T=[e(kM),e(kM+1),...,e(kM+M−1)]T=d(k)−y(k)
与输出向量的定义过程类似,定义一个2M维向量,但前M个用0填充:
E(k)=FFT[0e(k)]
E(k)=FFT\begin{bmatrix} 0 \\ \bold{e}(k) \\ \end{bmatrix}
E(k)=FFT[0e(k)]
将重叠保存的方法应用于线性相关,求得ϕ(k)\phi(k)ϕ(k):
ϕ(k)=first M elements of IFFT[UH(k)E(k)]
\phi(k)=first\space M\space elements\space of\space IFFT[U^H(k)E(k)]
ϕ(k)=first M elements of IFFT[UH(k)E(k)]
而权重因子的频域更新公式如下:
W^(k+1)=W^(k)+μFFT[ϕ(k)0]
\hat{W}(k+1)=\hat{W}(k) + \mu FFT \begin{bmatrix} \phi(k) \\ 0 \\ \end{bmatrix}
W^(k+1)=W^(k)+μFFT[ϕ(k)0]
频域实现的基本数学过程大致如此。相比较Ns算法中利用谱减规则对频域功率谱分析的侧重点,此处FFT更多的是为了算法效率上的考虑。
参考文献
[1]:《Acoustic MIMO Signal Processing》Yiteng Huang etc. Springer
[2]:《频域自适应滤波算法及应用》华南理工大学-本科毕业设计
[3]:《频域块 LMS 自适应滤波算法的研究》田超 西安理工大学 - 硕士毕业论文
[4]:《Frequency Domain and MutiRate Adaptive Filtering》 John J. shynk. IEEE Digital Signal Process Magzine.
[5]:《Adaptive Filter Theory》Fifth Edition Simon Haykin.