在讲频域分块LMS之前建议大家回顾一个时域分块LMS算法
时域分块LMS:
Block LMS的误差计算 和 权重更新公式中,
:输出信号是输入信号与滤波器系数的线性卷积
:更新梯度是误差信号与输入信号的线性相关
对于线性卷积和线性相关的运算量较大,特别是在当回声路径很长且复杂,并且回声延迟较高时,时域自适应滤波算法中的线性卷积和线性相关运算量较大,导致计算复杂度升高,我们更愿意把这两个信号变换到频域,通过频域相乘的方式来取代时域复杂度相当高的卷积或相关运算。所以我们把时域自适应滤波算法,衍生到频域自适应滤波算法。
预备知识:线性卷积(相关)和圆周(循环)卷积(相关)之间的关系
- 一般的,如果两个有限长序列的长度为N1和N2,且满足N1≥N2,则有圆周卷积的后 N1−N2+1个点,与线性卷积的结果一致。
- 一般的,如果两个有限长序列的长度为N1和N2,且满足N1≥N2,则有圆周相关的前 N1−N2+1个点,与线性相关的结果一致。
- 时域中的圆周卷积对应于其离散傅里叶变换的乘积
- 时域中的圆周相关对应于其离散傅里叶变换共轭谱的乘积
- 在利用循环卷积计算线性卷积时,为了防止混叠,首先要将输入信号长度N1和滤波器系数长度N2,补零至长度大于等于L(L=N1−N2+1)
因此我们需要将分块LMS自适应滤波算法中的线性卷积和线性相关 通过快速傅里叶变换(FFT)转换到频域来实现。这样实现的分块LMS自适应滤波算法称为频域块LMS自适应滤波算法(FDAF,Frequency-Domain Block Least Mean Square Adaptive Filter)。FDAF算法将长度为L的自适应滤波器分成FFT长度的整数倍个子块,对输入信号的每个子块进行频域内的LMS算法。
第一步:计算线性卷积
利用FFT计算线性卷积的方法有:
- 重叠存储法(overlap save method,更常用)
- 重叠相加法(overlap add method)
我们这里以overlap save method为例,为了确保能得到N个点的线性卷积输出信号,我们至少要保证有N个点的线性卷积和圆周卷积的结果一致(预备知识)
由于N1≥N2 (输入信号长度N1通常大于滤波器的阶数N2),且N2=N (滤波器的阶数为N),那么要求每次参与运算的输入信号长度N1至少为2N−1(N1=N+N2-1=2N+1),为了计算FFT方便,我们令输入信号的长度为:N1=2N,那么我们FFT的长度也为2N
为了构造长度为2N的数据,我们需要在每个N阶滤波器后面N补零
要求线性卷积(预备知识1),我们就需要求圆周卷积后N1−N2+1个点,根据预备知识3,我们只需要求离散傅里叶变换的乘积就能得到圆周卷积的结果,接下来我们分别计算输入信号向量和滤波器系数向量的FFT:
第二步:计算线性相关
根据预备知识2,4可知,需要求线性相关,我们可以通过获得圆周相关来获得。因此我们需要求输入信号的共轭谱与误差信号谱的乘积。
输入信号X(k)在上一步处理卷积运算是已经求得。
第三步:滤波器系数更新
注意:
- 第一,滤波器系数直接在频域更新,所以需要将梯度向量再次变换到频域;
- 第二,由于滤波器系数向量后面补了N 个零 ,为了保证结果的正确性梯度向量也需要在后面补 N 个零。
- 优点:
- 降低了时域自适应滤波器的计算复杂度
- 提高了收敛速度
- 缺点:
- 增加了延迟(需要通过频域滤波器延迟期望信号)
- 增加了内存需求(需要同时存储激励信号和期望信号)
如前所述,回声消除等应用回波路径较长,会导致较大的延迟和存储需求。该缺点可以通过诸如多延迟自适应滤波器之类的方法来克服。在这种方法中,块大小可以小于所需的时域自适应滤波器,并且可以应用每个频点中的自适应滤波器来代替单个系数。因此,可以减轻FDAF的缺点,同时保持降低的计算复杂性和提高的收敛速度。
MATLAB代码如下:
% 参考:https://github.com/CharlesThaCat/acoustic-interference-cancellation
function [en, yk, W] = myFDAF(d,x,mu,mu_unconst, M, select)
% 参数:
% d 输入信号(麦克风语音)
% x 远端语音
% mu 约束 FDAF的步长
% mu_unconst 不受约束的 FDAF的步长
% M 滤波器阶数
% select; 选择有约束或无约束FDAF算法
%
% 参考:
% S. Haykin, Adaptive Filter Theory, 4th Ed, 2002, Prentice Hall
% by Lee, Gan, and Kuo, 2008
% Subband Adaptive Filtering: Theory and Implementation
% Publisher: John Wiley and Sons, Ltd
x_new = zeros(M,1); % 将新块的M个样本初始化为0
x_old = zeros(M,1); % 将旧块的M个样本初始化为0
AdaptStart = 2*M; % 在获得2M个样本块后开始自适应
W = zeros(2*M,1); % 将2M个滤波器权重初始化为0
d_block = zeros(M,1); % 将期望信号的M个样本初始化为0
power_alpha = 0.5; % 常数以更新每个frequency bin的功率
power = zeros(2*M,1); % 将每个bin的平均功率初始化为0
d_length = length(d); % 输入序列的长度
en = []; % 误差向量
window_save_first_M = [ones(1,M), zeros(1,M)]'; % 设置向量以提取前M个元素 (2M,1)
for k = 1:d_length
x_new = [x_new(2:end); x(k)]; % 开始的输入信号块 (2M,1)
d_block = [d_block(2:end); d(k)]; % 开始的期望信号快 (M,1)
if mod(k,M)==0 % If iteration == block length,
x_blocks = [x_old; x_new]; % 2M样本的输入信号样本块 (2M,1)
x_old = x_new;
if k >= AdaptStart % 频域自适应滤波器
% 将参考信号转换到频域
Xk = fft(x_blocks); % (2M,1)
% FFT[old block; new block]
% Old block 包含M个先前的输入样本 (u_old)
% New 包含M个新的输入样本 (u_new)
% 计算滤波器估计信号
Yk = Xk.*W; % 输入和权重向量的乘积(2M,1)*(2M,1)=(2M,1)
temp = real(ifft(Yk)); % IFFT 输出的实部 (2M,1)
yk = temp(M+1:2*M); % 抛弃前M个元素,保留后M个元素 (M,1)
% 计算误差信号
error = d_block-yk; % 误差信号块 (M,1)
Ek = fft([zeros(1,M),error']'); % 在FFT之前插入零块以形成2M块(2M,1)
% 更新信号功率估算
power = (power_alpha.*power) + (1 - power_alpha).*(abs(Xk).^2); % (2M,1)
% 计算频域中的梯度和权重更新
if select == 1
gradient = real(ifft((1./power).*conj(Xk).* Ek)); % (2M,1)
gradient = gradient.*window_save_first_M; % 去除后一个数据块,并且补零 (2M,1)
W = W + mu.*(fft(gradient)); % 权重是频域的 (2M,1)
else
gradient = conj(Xk).* Ek; % (2M,1)
W = W + mu_unconst.*gradient; % (2M,1)
end
en = [en; error]; % 更新误差块
end
end
end
学习速率如何选取
我们在推导频域自适应滤波方法的时候,为了简化问题,将每一个frequency bin 上的学习速率均设为常数μ。
在实际工程应用中,用的更多的一种方法是,对第m个 frequency bin ,利用输入信号在这个频点的功率 对学习速率Pm(k)进行归一化:
频点功率Pm(k)通常采用迭代的方式求得:
参考链接: