[关闭]
@zsh-o 2019-04-18T16:13:53.000000Z 字数 11211 阅读 3025

Variational Mode Decomposition

数学


这一篇写一下变分模态分解(原始论文:Variational Mode Decomposition),跟原始论文思路思路一致但有一点点不太一样,原始论文写的很好,但我不是通信专业没有学过信号相关课程一开始看起来有点费劲。模态分解认为信号是由不同“模态”的子信号叠加而成的,而变分模态分解则认为信号是由不同频率占优的子信号叠加而成的,其目的是要把信号分解成不同频率的子信号。变分模态分解的分解结果如图所示

image.png-133.8kB

基础

一开始论文看不懂的原因是缺少相关前置知识,但一旦顺下来就会感觉其实没有那么难,难的是作者的思路很巧妙,先写下我遇到的这些知识盲点

第一点是傅立叶变换的微分性质的傅立叶变换为,其导数的傅立叶变换为

另一点是解析信号,现实世界只能采集实信号,但实信号有很多不好用的性质,如存在负频率,无法直接得到调制频率后的实信号等。
设原始信号是一个实信号

为了方便表示为随变化的函数,相当于瞬时频率,解析信号是一个复信号,可以通过希尔伯特变换得到
解析信号的实部是原本的实数信号,并且经过调频之后复数信号的实部仍然是调频之后的实信号,如对信号增加频率,只需要乘以即可
由此,其实部相当于在原本频率的基础上增加了频率,如下面的matlab脚本

  1. clear;close all;clc;
  2. t = 1:0.01:10;
  3. %%
  4. f1 = sin(20*t).*(t-5).^2;
  5. subplot(3,1,1);
  6. plot(f1);
  7. ylim([-25 25]);
  8. %%
  9. f2 = sin(50*t).*(t-5).^2;
  10. subplot(3,1,2);
  11. plot(f2);
  12. ylim([-25 25]);
  13. %%
  14. H = hilbert(f1);
  15. f_hat = H.*exp(1i*30.*t);
  16. subplot(3,1,3);
  17. plot(real(f_hat));
  18. ylim([-25 25]);

image.png-72.7kB

matlabhilbert函数包括希尔伯特变换和解析函数转换两部分,直接得到实信号的解析信号,其中希尔伯特变换

正文

接下来我们看看如何一步一步得到变分模态分解的思路

原论文通过一个信号降噪问题进行说明,现需要对采样信号进行降噪重构,假设观测信号是由原始信号叠加一个独立的高斯噪音

,需要求,又说该等式是一个不适定问题(ill-posed problem),不满足识定问题的三个条件,所以要用一个正则化的方法
第一部分是对原信号进行重构,第二部分是为了解决不适定问题的解不唯一,而且不同于机器学习的建模问题一样是一个权值形式可以直接加权值的L1-norm或L2-norm,这里的是一个纯函数的形式,其导数的L2-norm最小化感觉上应该是保证了函数不会产生太大的波动,这里可不可以跟防止过拟合联系起来,接下来说明这个约束会使在频率域产生什么影响。

下面解这个式子,首先来看一下直接最小化泛函能不能解出来

,然后根据E-L方程的引理

,往下忘了怎么求了。。。。。想起来怎么求再继续写-_-!,这个直接求的方法与原文没什么关系的

由上面的傅立叶变换的微分性质知道,用傅立叶变换在复数域很方便可以把微分约掉,而且还有一个叫什么Plancherel傅立叶等距映射的东西,时域的L2范数与傅立叶变换到的频率域的L2范数等距,故上面的最小化的项可以直接用傅立叶变换转换到频域

,这里需要注意的是利用上面的那个什么等距定理有是同一个,这一点很重要,第二个是L2范数大于0,则把其展开为泛函
求最极值,由于这里面只有直接求偏导即可也不用解微分方程

可以看到得到的相当于对观测信号在在频率段进行滤波,过滤掉了高频部分,这说明加了该导数的L1正则约束与上面的直观感觉是一样的,过滤掉了高频部分,减弱的波动

再往下进行,模态分解需要把原始信号分解成多个子信号的和,我们为了和原文对应修改一下符号表示,表示观测的采样信号,表示分解得到的基函数,则上面的约束对象变为

同样先转化为频率域再求极值
泛函

每个基函数基于其他的基函数更新,相当于每个基函数是原信号剩余部分的低通滤波,每次迭代都是保留剩余信号的低频率部分。

到现在为止我们发现每个基函数都会趋向于每次的剩余信号分量的低频部分,这与我们原始的假设“每个基函数都有不同的频率分量”是相悖的,但根据上面的低通滤波的性质,每个基函数进行特定频率的滤波应该就能解决这个问题了,那么上面的式子就简单的变为

其中,为每个基函数的中心频率,该式就是变分模态分解的基函数的更新公式,我们来看一下这个式子应该如何得到,以便于找到中心频率的更新公式

由上面的一步一步的演化发现,基函数对剩余信号的低通滤波是由导数的L2正则最小化带来的,要得到基函数的中心频率约束也要从这个地方入手,由上面的推导可知每个基函数都会被约束到频率附近,那么我们把基函数的频率增加各自的中心频率得到,并保证即可,则相当于对每个基函数乘以了一个,这里需要对频率进行变换,我们沿用文章开头的解析信号的性质,认为是一个复数的解析信号,同样也需要把观测信号预先转化为解析信号,下文默认都是解析信号,则每一个基函数转换频率后的导数变为

傅立叶变换得
先来看看傅立叶变换为什么会得到这个,而不是
反傅立叶变换
感觉应该不是这样证的,我数学不是很好,不怎么会这个变量变换

至此,约束变为


傅立叶变换
根据论文原文把
然后求最小就可以得到上面的更新公式

最后,为了保证每个点处的重构信号与原信号尽可能相似,增加了每个点处的重构约束,其实这一项并不是必需的,最终的约束对象为

,然后拉格朗日乘子法带进去,但其需要满足下式才有意义
这个式子还是符合Parseval定理,故整理一下

最后整理一下更新公式:

其中使用梯度下降更新

代码

  1. %matplotlib inline
  2. from matplotlib import pyplot as plt
  3. import numpy as np
  4. from scipy.signal import hilbert
  1. T = 1000
  2. fs = 1./T
  3. t = np.linspace(0, 1, 1000,endpoint=True)
  1. f_1 = 10
  2. f_2 = 50
  3. f_3 = 100
  4. mode_1 = (2 * t) ** 2
  5. mode_2 = np.sin(2 * np.pi * f_1 * t)
  6. mode_3 = np.sin(2 * np.pi * f_2 * t)
  7. mode_4 = np.sin(2 * np.pi * f_3 * t)
  8. f = mode_1 + mode_2 + mode_3 + mode_4 + 0.5 * np.random.randn(1000)
  1. plt.figure(figsize=(6,3), dpi=150)
  2. plt.plot(f, linewidth=1)
[<matplotlib.lines.Line2D at 0x7fac533f6780>]

output_3_1.png-64.1kB

  1. class VMD:
  2. def __init__(self, K, alpha, tau, tol=1e-7, maxIters=200, eps=1e-9):
  3. """
  4. :param K: 模态数
  5. :param alpha: 每个模态初始中心约束强度
  6. :param tau: 对偶项的梯度下降学习率
  7. :param tol: 终止阈值
  8. :param maxIters: 最大迭代次数
  9. :param eps: eps
  10. """
  11. self.K =K
  12. self.alpha = alpha
  13. self.tau = tau
  14. self.tol = tol
  15. self.maxIters = maxIters
  16. self.eps = eps
  17. def __call__(self, f):
  18. T = f.shape[0]
  19. t = np.linspace(1, T, T) / T
  20. omega = t - 1. / T
  21. # 转换为解析信号
  22. f = hilbert(f)
  23. f_hat = np.fft.fft(f)
  24. u_hat = np.zeros((self.K, T), dtype=np.complex)
  25. omega_K = np.zeros((self.K,))
  26. lambda_hat = np.zeros((T,), dtype=np.complex)
  27. # 用以判断
  28. u_hat_pre = np.zeros((self.K, T), dtype=np.complex)
  29. u_D = self.tol + self.eps
  30. # 迭代
  31. n = 0
  32. while n < self.maxIters and u_D > self.tol:
  33. for k in range(self.K):
  34. # u_hat
  35. sum_u_hat = np.sum(u_hat, axis=0) - u_hat[k, :]
  36. res = f_hat - sum_u_hat
  37. u_hat[k, :] = (res - lambda_hat / 2) / (1 + self.alpha * (omega - omega_K[k]) ** 2)
  38. # omega
  39. u_hat_k_2 = np.abs(u_hat[k, :]) ** 2
  40. omega_K[k] = np.sum(omega * u_hat_k_2) / np.sum(u_hat_k_2)
  41. # lambda_hat
  42. sum_u_hat = np.sum(u_hat, axis=0)
  43. res = f_hat - sum_u_hat
  44. lambda_hat -= self.tau * res
  45. n += 1
  46. u_D = np.sum(np.abs(u_hat - u_hat_pre) ** 2)
  47. u_hat_pre[::] = u_hat[::]
  48. # 重构,反傅立叶之后取实部
  49. u = np.real(np.fft.ifft(u_hat, axis=-1))
  50. omega_K = omega_K * T
  51. idx = np.argsort(omega_K)
  52. omega_K = omega_K[idx]
  53. u = u[idx, :]
  54. return u, omega_K
  1. K = 4
  2. alpha = 2000
  3. tau = 1e-6
  4. vmd = VMD(K, alpha, tau)
  1. u, omega_K = vmd(f)
  1. omega_K
array([0.85049797, 10.08516203, 50.0835613, 100.13259275]))
  1. plt.figure(figsize=(5,7), dpi=200)
  2. plt.subplot(4,1,1)
  3. plt.plot(mode_1, linewidth=0.5, linestyle='--')
  4. plt.plot(u[0,:], linewidth=0.2, c='r')
  5. plt.subplot(4,1,2)
  6. plt.plot(mode_2, linewidth=0.5, linestyle='--')
  7. plt.plot(u[2,:], linewidth=0.2, c='r')
  8. plt.subplot(4,1,3)
  9. plt.plot(mode_3, linewidth=0.5, linestyle='--')
  10. plt.plot(u[1,:], linewidth=0.2, c='r')
  11. plt.subplot(4,1,4)
  12. plt.plot(mode_4, linewidth=0.5, linestyle='--')
  13. plt.plot(u[3,:], linewidth=0.2, c='r')
[<matplotlib.lines.Line2D at 0x7fac532f4dd8>]

output_8_1.png-393.3kB


可以看到有比较强的端点效应,端点处会有重叠,文章原始论文中采用的方法是对称拼接的方法,把原信号复制一份然后拼成两半,一半对称放前面,一般对称放后面

  1. %matplotlib inline
  2. from matplotlib import pyplot as plt
  3. import numpy as np
  4. from scipy.signal import hilbert
  1. T = 1000
  2. fs = 1./T
  3. t = np.linspace(0, 1, 1000,endpoint=True)
  1. f_1 = 10
  2. f_2 = 50
  3. f_3 = 100
  4. mode_1 = np.sin(2 * np.pi * f_1 * t)
  5. mode_2 = np.sin(2 * np.pi * f_2 * t)
  6. mode_3 = np.sin(2 * np.pi * f_3 * t)
  7. f = np.concatenate((mode_1[:301], mode_2[301:701], mode_3[701:])) + 0.1 * np.random.randn(1000)
  1. plt.figure(figsize=(6,3), dpi=150)
  2. plt.plot(f, linewidth=0.5)
[<matplotlib.lines.Line2D at 0x7fc2134b8630>]

output_3_1.png-93.8kB

  1. class VMD:
  2. def __init__(self, K, alpha, tau, tol=1e-7, maxIters=200, eps=1e-9):
  3. """
  4. :param K: 模态数
  5. :param alpha: 每个模态初始中心约束强度
  6. :param tau: 对偶项的梯度下降学习率
  7. :param tol: 终止阈值
  8. :param maxIters: 最大迭代次数
  9. :param eps: eps
  10. """
  11. self.K =K
  12. self.alpha = alpha
  13. self.tau = tau
  14. self.tol = tol
  15. self.maxIters = maxIters
  16. self.eps = eps
  17. def __call__(self, f):
  18. N = f.shape[0]
  19. # 对称拼接
  20. f = np.concatenate((f[:N//2][::-1], f, f[N//2:][::-1]))
  21. T = f.shape[0]
  22. t = np.linspace(1, T, T) / T
  23. omega = t - 1. / T
  24. # 转换为解析信号
  25. f = hilbert(f)
  26. f_hat = np.fft.fft(f)
  27. u_hat = np.zeros((self.K, T), dtype=np.complex)
  28. omega_K = np.zeros((self.K,))
  29. lambda_hat = np.zeros((T,), dtype=np.complex)
  30. # 用以判断
  31. u_hat_pre = np.zeros((self.K, T), dtype=np.complex)
  32. u_D = self.tol + self.eps
  33. # 迭代
  34. n = 0
  35. while n < self.maxIters and u_D > self.tol:
  36. for k in range(self.K):
  37. # u_hat
  38. sum_u_hat = np.sum(u_hat, axis=0) - u_hat[k, :]
  39. res = f_hat - sum_u_hat
  40. u_hat[k, :] = (res - lambda_hat / 2) / (1 + self.alpha * (omega - omega_K[k]) ** 2)
  41. # omega
  42. u_hat_k_2 = np.abs(u_hat[k, :]) ** 2
  43. omega_K[k] = np.sum(omega * u_hat_k_2) / np.sum(u_hat_k_2)
  44. # lambda_hat
  45. sum_u_hat = np.sum(u_hat, axis=0)
  46. res = f_hat - sum_u_hat
  47. lambda_hat -= self.tau * res
  48. n += 1
  49. u_D = np.sum(np.abs(u_hat - u_hat_pre) ** 2)
  50. u_hat_pre[::] = u_hat[::]
  51. # 重构,反傅立叶之后取实部
  52. u = np.real(np.fft.ifft(u_hat, axis=-1))
  53. u = u[:, N//2 : N//2 + N]
  54. omega_K = omega_K * T / 2
  55. idx = np.argsort(omega_K)
  56. omega_K = omega_K[idx]
  57. u = u[idx, :]
  58. return u, omega_K
  1. K = 3
  2. alpha = 2000
  3. tau = 1e-6
  4. vmd = VMD(K, alpha, tau)
  1. u, omega_K = vmd(f)
  1. omega_K
array([  9.64477193,  50.06365397, 100.18114375])
  1. plt.figure(figsize=(5,7), dpi=200)
  2. plt.subplot(3,1,1)
  3. plt.plot(mode_1, linewidth=0.5, linestyle='--')
  4. plt.plot(u[0,:], linewidth=0.2, c='r')
  5. plt.subplot(3,1,2)
  6. plt.plot(mode_2, linewidth=0.5, linestyle='--')
  7. plt.plot(u[1,:], linewidth=0.2, c='r')
  8. plt.subplot(3,1,3)
  9. plt.plot(mode_3, linewidth=0.5, linestyle='--')
  10. plt.plot(u[2,:], linewidth=0.2, c='r')
[<matplotlib.lines.Line2D at 0x7fc2134075c0>]

output_8_1.png-554.6kB

好像结果要好一点,VMD的一个缺点是K的值对结果有很大影响,但这个迭代过程,怎么开始怎么迭代怎么结束都可以自己控制,感觉可以按照自己的需求来定制怎么动态决定K


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注