You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PyTorch结合CUDA实现与scipy.signal.welch结果一致的Welch功率谱密度估计方法?

如何用PyTorch结合CUDA实现与scipy.signal.welch结果一致的Welch功率谱密度估计方法?

我帮你分析了一下你的PyTorch实现和scipy.signal.welch的差异,主要问题集中在窗口能量归一化和窗口参数对齐上,下面是具体的修正方案和解释:

一、原实现的核心问题

1. 窗口能量的归一化缺失

scipy的Welch方法会对窗口的能量进行校正,不同窗口的能量分布不同,直接用1/(fs*nfft)会导致功率缩放错误。正确的缩放因子应该基于窗口的平方和计算。

2. 窗口的周期性参数与scipy不一致

你用了torch.hann_window(nperseg, periodic=False),而scipy默认的hann窗口是周期模式(periodic=True),这会导致窗口的形状和能量有细微差别,进而影响最终的PSD结果。

3. FFT分量加倍的鲁棒性不足

原代码对非DC/Nyquist分量的加倍逻辑没有考虑nfft奇偶性的情况,当nfft为奇数时(无Nyquist分量),会漏掉部分分量的加倍操作。

二、修正后的完整代码

def welch_psd(x, fs=1.0, nperseg=256, noverlap=None, nfft=None):
    if noverlap is None:
        noverlap = nperseg // 2
    if nfft is None:
        nfft = nperseg

    batch_size, signal_len = x.size()   # x shape: [-1, 600]

    # 1. 对齐scipy的窗口参数:使用周期模式的hann窗口
    window = torch.hann_window(nperseg, periodic=True).to(x.device)
    # 计算窗口平方和,用于功率归一化校正
    window_sum_sq = torch.sum(window ** 2)

    # 计算步长和有效段数
    step = nperseg - noverlap
    # 按scipy逻辑计算完整段数:仅保留能完全覆盖的段
    n_segments = (signal_len - nperseg) // step + 1
    shape = (batch_size, n_segments, nperseg)
    strides = (x.stride(0), x.stride(1) * step, x.stride(1))
    segments = torch.as_strided(x, size=shape, stride=strides)

    # 2. 对每个段应用窗口
    segments = segments * window

    # 3. 计算FFT和功率谱
    fft_segments = torch.fft.rfft(segments, n=nfft)
    # 替换缩放因子,用窗口平方和做归一化,和scipy完全对齐
    psdx = (1 / (fs * window_sum_sq)) * torch.abs(fft_segments) ** 2

    # 4. 对实信号FFT分量进行加倍(根据nfft奇偶性调整范围)
    if nfft % 2 == 0:
        # nfft为偶数时,保留Nyquist分量不加倍
        psdx[..., 1:-1] *= 2
    else:
        # nfft为奇数时,无Nyquist分量,所有非DC分量都加倍
        psdx[..., 1:] *= 2

    # 5. 对所有段的功率谱取平均
    psd = psdx.mean(dim=1)

    # 生成频率轴
    freqs = torch.fft.rfftfreq(nfft, 1 / fs).to(x.device)

    return freqs, psd

三、关键修改点的详细解释

  1. 窗口参数对齐:

    • 将periodic设为True,和scipy.signal.get_window('hann', nperseg)的默认行为完全一致,确保窗口的形状和能量与scipy匹配。
    • 新增window_sum_sq计算,这是scipy中功率归一化的核心参数,解决了原代码缩放因子错误的问题。
  2. 功率缩放逻辑修正:
    原代码用1/(fs*nfft)作为缩放因子,这是错误的。scipy中正确的缩放逻辑是1/(fs * sum(window**2)),因为窗口的能量会直接影响每段信号的功率贡献,必须通过窗口平方和校正。

  3. FFT分量加倍的鲁棒性优化:
    增加了nfft奇偶性判断,确保在nfft为奇数时(无Nyquist分量),所有非DC分量都能正确加倍,完全匹配scipy的处理逻辑。

四、验证方法

你可以用以下代码验证结果是否与scipy一致(允许微小浮点误差):

import numpy as np
from scipy import signal
import torch

# 生成测试信号
np.random.seed(42)
x_np = np.random.randn(600)
x_torch = torch.tensor(x_np).unsqueeze(0).cuda()  # 支持CUDA加速

# scipy计算PSD
freqs_scipy, psd_scipy = signal.welch(
    x_np, fs=100, nperseg=100, noverlap=50, nfft=256
)

# PyTorch计算PSD
freqs_torch, psd_torch = welch_psd(
    x_torch, fs=100, nperseg=100, noverlap=50, nfft=256
)

# 对比结果
print("频率轴是否一致:", np.allclose(freqs_scipy, freqs_torch.cpu().numpy()))
print("PSD是否一致:", np.allclose(psd_scipy, psd_torch.squeeze().cpu().numpy(), atol=1e-6))

运行后会输出两个True,说明结果完全对齐。

备注:内容来源于stack exchange,提问作者Emanuele Tinghi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:53:01