[旧日谈] 再考 IIR 与 FIR 滤波器对相位影响的定量分析
IIR 与 FIR 濾波器对音频相位的影响 先前我有写过一个简单的文章分析过两种滤波器对音频相位的影响,但是我只是知其然不知其所以然。对于音频,我虽然知道相位是一个很重要的概念,但是我始终不知道相位对实际音频的印象是什么水平的。这个问题在这些年的开发过程中始终萦绕在心头。虽然不做音频了,但是我仍然对这个问题保持好奇,综上,这也是为什么有了这个文章。 一、从一个问题开始 假设我们有一个 1kHz 的正弦信号,经过一个低通滤波器之后,输出还是 1kHz 的正弦信号,幅度变小了——这很好理解,滤波器嘛,该衰减的衰减。 但仔细看输出波形,会发现它相对于输入信号产生了一个时间上的延迟。这个延迟不是简单的"整体往后挪了 N 个采样点",而是不同频率的信号延迟不一样。 1kHz 的信号延迟了 0.5ms,500Hz 的信号延迟了 0.8ms,2kHz 的信号延迟了 0.3ms——每个频率成分的延迟都不一样。 这就是相位失真。 对于音频处理来说,这个问题比听起来严重得多。人耳对相位差的感知不如幅度那么直接,但当不同频率成分的延迟差异大到一定程度时,会导致: 瞬态信号(比如鼓点、齿音)的波形被"模糊化" 立体声声像偏移 某些频段的"堆叠"或"空洞" 所以,理解滤波器的相位特性,是做音频处理的基本功。 先说结论,IIR 的相位响应受幅度响应约束(最小相位特性),无法独立控制;FIR 可以独立控制幅度和相位,因此能实现线性相位或任意指定相位。 但是至于为什么音频行业常用IIR滤波器,这个问题我将在补充后说明。 二、先回顾一下:FIR 和 IIR 是什么 FIR(有限脉冲响应) FIR 滤波器的差分方程: $$ y[n] = \sum_{k=0}^{M} b_k \, x[n-k] $$输出只依赖于当前和过去的输入,没有反馈。脉冲响应是有限长的(长度 M+1)。 IIR(无限脉冲响应) IIR 滤波器的差分方程: $$ y[n] = \sum_{k=0}^{M} b_k \, x[n-k] - \sum_{k=1}^{N} a_k \, y[n-k] $$输出同时依赖于输入和过去的输出(反馈)。脉冲响应理论上是无限长的。 两者的核心区别在于有没有反馈。这个结构上的差异,直接决定了它们的相位特性。 三、相位响应的推导 从频率响应说起 一个 LTI(线性时不变)系统的频率响应可以写成: ...
Wendland Functions
Wendland Functions Wendland 函数是一类具有紧支集的径向基函数(Compactly Supported Radial Basis Functions),由 Holger Wendland 在其 1995 年的论文 Piecewise Polynomial, Positive Definite and Compactly Supported Radial Functions of Minimal Degree 中提出。 这类函数广泛应用于散乱数据插值(scattered data interpolation)、无网格方法(meshfree methods)等领域,其紧支撑特性使得插值矩阵具有稀疏性,显著降低了大规模问题的计算复杂度。 Loading PDF... ← Previous Page 0 / 0 Next →
Wendland 紧支撑核的散点场重建方法
Wendland 紧支撑核的散点场重建方法 Wendland Functions(这篇笔记对 Wendland 核函数的数学构造有更完整的推导) 从散点到连续场 Wendland 并不是“力场重建”专用算法。它更像是一种通用工具:当你手里有一批离散采样点,想把它们变成一个连续场时,Wendland C2 紧支撑核可以作为一个很好的重建核。 问题可以抽象成这样:平面上有一组采样点 $$ (x_i, y_i, v_i), \quad i = 1,2,\dots,N $$其中 $(x_i,y_i)$ 是采样位置,$v_i$ 是这个位置的观测值。我们想得到一个连续函数 $f(x,y)$,让它在没有传感器的位置也能给出合理估计。压力传感器数据只是这个问题的一个例子。换成温度站点、高程采样点、点云属性,甚至仿真里的 collocation points,本质都是“散点样本 → 连续场”。 这里有一个容易混淆的点:这件事到底是“重建”,还是只是“平滑”?我的理解是,平滑更像后期修图,它不关心背后有没有真实函数,只是让数据看起来更顺;重建则先假设背后确实存在一个连续函数,而采样点只是对它的离散观测。对于压力传感器来说,我们认为背后存在一个连续压力分布 $P(x,y)$,传感器只是采到了若干个 $P(x_i,y_i)$,所以目标是从这些离散值恢复连续压力分布的近似,而不只是让图变得柔和。 这也是为什么我更愿意把这个问题叫“散点场重建”。它不是某个物理量专属的问题,而是一类数据重建问题。 为什么会选到 Wendland 从散点构造连续场的方法很多。最朴素的是最近邻:每个位置直接拿最近采样点的值。这很诚实,但画出来会像马赛克。线性插值或三角插值更自然一些,不过它往往需要明确的拓扑结构,边界处理也麻烦。点高斯叠加看起来很直觉:每个采样点盖一个光斑,所有光斑相加。但它有一个致命问题——如果不归一化,采样点密集的地方会天然更亮。亮度来自采样密度,而不是来自真实场值。 KNN-IDW 往前走了一步:找附近 K 个点,按距离倒数加权平均。它已经是“插值”的思路了,但邻居集合会随着像素位置切换,梯度大的地方容易出现细小斑点。局部平面拟合能表达局部趋势,不过不同邻域拟合出的平面一切换,就容易出现片状接缝。全局 RBF 插值理论上更完整,但需要解线性方程组,计算成本会比较高。 所以这个场景真正需要的不是“最花哨”的方法,而是同时满足几个工程约束的方法。第一,它要归一化,否则采样点密度会污染颜色。第二,它要局部计算,否则输出图像一大,每个像素都遍历所有采样点就不现实。第三,它要视觉连续,不能有块状边界、凸包、片状拼缝。Wendland C2 恰好把这几件事放在一起:它是归一化加权平均;它是紧支撑核,超出半径就严格为零;它在支撑边界处 C² 连续,不容易产生拼缝;同时它还是正定 RBF,作为插值或近似核有数学基础。 也就是说,Wendland 不是因为“它专门适合压力场”才被选中,而是因为当前任务具备这些条件:散点采样、需要连续场、采样密度可能不均匀、要求实时、要求视觉上没有明显伪影。 从旧的高斯光斑到 Wendland 核 旧的点高斯方案很容易理解:在每个采样点位置盖一个高斯光斑,然后把所有光斑相加。 $$ \text{field}{[x][y]} = \sum_i \text{value}_i \times \exp\!\left(-\frac{d_i^2}{2\sigma^2}\right) $$这里 $\text{value}_i$ 是第 $i$ 个采样点的值,$d_i$ 是像素到这个采样点的距离,$\sigma$ 控制光斑扩散范围。这个公式最大的问题就在求和符号上。两个采样点靠得近,光斑重叠区域就会叠得更亮。结果是:采样点密度越高的地方越亮,即便真实场值并没有更高。 ...
GTCRN:一个 23.7K 参数语音降噪网络的设计思路完整解读
GTCRN:一个 23.7K 参数语音降噪网络的设计思路完整解读 作者从零讲起:背景、骨架、模块、训练、部署、设计哲学,一篇看完。 配套材料:GTCRN 论文与公开实现都可以作为辅助阅读材料,但本文尽量按原理本身展开,不依赖具体工程代码跳转。 目录 前言:这篇文章写给谁看 第一章 设计思路与背景:作者到底要解决什么问题? 第二章 整体架构:一张图看懂 GTCRN 是怎么搭起来的 第三章 输入处理与 ERB:怎么把 257 个频点压成 129 个还不丢信息? 第四章 GT-Conv 详解:ShuffleNetV2 + 时间空洞,省到极致的卷积块 第五章 DPGRNN 详解:双路径 + 分组,RNN 的极致瘦身 第六章 SFE 与 TRA:两个"点睛"模块 第七章 输出与损失函数:复数掩码 CRM 和"混合损失"的玄学 第八章 流式推理:从离线训练到逐帧实时 第九章 设计哲学总结:从 GTCRN 学到的可迁移工程思维 前言 如果你和我一样,是一名有信号处理(STFT、滤波器组、感知声学)和经典机器学习基础的工程师,能看懂代码,知道"卷积"、“RNN”、“注意力"大概是什么,但是看到 GTCRN 这样的网络结构图时会想: “作者为什么这么搭?这些模块之间凭什么能拼成一个能降噪的东西?” “23.7K 参数怎么做到打过 RNNoise(60K参数),并且打平甚至超过几百万参数的大模型的?” “如果让我从零设计,我能想到这些 trick 吗?我和作者之间差的是什么?” 那这篇文章可能对你有用。 我不打算把它写成论文复读机,那样毫无价值——论文你自己就能看。我想做的是 还原作者的思考路径:当他坐在工位上想"我要做一个能放在耳机里跑的降噪模型”,他第一步会想什么,第二步又怎么走到 ERB、ShuffleNet、DPRNN 这些组件上,每一个看起来很骚的设计背后到底解决的是什么实际问题。 读完这一系列,你应该能: 理解 GTCRN 每个模块解决的具体问题,而不是只记住名字; 看到一个新的 SE 模型时,能识别其中哪些是骨架、哪些是 trick; 在自己的项目里能做出取舍:什么时候上 attention、什么时候用 grouped conv、什么时候 CRM 不如 mag-mask。 阅读建议: ...
[RL] 强化学习指导搭建 IC2E 核反应堆
Minecraft 工业2 实验版核反应堆计算 强化学习模块训练路径 最近在玩Minecraft IC2 Classic,但是对于摆核反应堆总是感觉不是很得心应手,不管怎么摆效率都很低,为了解决这个问题,所以我写了一个强化学习的模块,让神经网络自己去学习如何摆弄这个网络。 不过看了下,IC2 Classic 的核反应堆因为似乎不涉及中子流,所以任务是比较简单的,为了节目效果,我准备研究一下IC2 experiment版的核电站,这个玩起来更有趣,学习的深度也更深。 任务简单分为三步: 明确任务目标和行为 搭建网络 训练 一、任务目标和行为指南 这一步往往是比较重要的,因为这一步决定了AI到底要学什么,以及怎么学。 1.1 问题定义 IC2E的核反应堆设计本质上是一个组合优化问题。你有一个9×6的网格,54个位置,每个位置可以放18种不同的组件(包括空槽位)。理论上有18^54种可能的配置,这个数字大到宇宙中的原子都数不过来。 但问题是,这些配置里99.99%都是垃圾——要么发电量低得可怜,要么直接爆炸。我们要找的是那0.01%既能高效发电,又不会炸的设计。 1.2 核反应堆的物理机制 在开始训练之前,得先搞清楚IC2E核反应堆到底是怎么工作的。不然AI学出来的东西可能完全不符合物理规律。 核脉冲机制: 燃料棒工作时会向四周发射核脉冲 相邻的燃料棒接收到核脉冲后,发电量会成倍增加 中子反射板可以把核脉冲反射回去,相当于"虚拟"的燃料棒 举个例子,一个单铀棒(U): 单独放置:发电5 EU/t 旁边有1个燃料棒:发电10 EU/t 旁边有2个燃料棒:发电15 EU/t 旁边有4个燃料棒:发电25 EU/t 所以燃料棒越密集,发电效率越高。但问题来了—— 热量产生机制: 燃料棒产生的热量跟相邻的燃料棒/反射板数量有关,公式是: 热量 = 倍数 × (n+1) × (n+2) 其中n是相邻的燃料棒或反射板数量(0-4)。 这个公式很狠,是二次增长的。比如单铀棒(倍数=2): n=0(孤立):2×1×2 = 4 HU/tick n=1(1个邻居):2×2×3 = 12 HU/tick n=2(2个邻居):2×3×4 = 24 HU/tick n=4(4个邻居):2×5×6 = 60 HU/tick 看到没?发电量是线性增长(5→25),但热量是二次增长(4→60)。这就是核反应堆设计的核心矛盾:你想要高功率,就得承受高热量。 散热系统: 热量如果散不出去,反应堆温度就会一路飙升,到10000 HU就爆炸。所以必须有足够的散热系统: 散热片(H):自身散热6 HU/tick 反应堆散热片(R):从堆温吸热5 HU/tick,然后自己散热5 HU/tick 高级散热片(A):自身散热12 HU/tick 超频散热片(O):自身散热20 HU/tick 还有热交换器,可以在组件之间转移热量,把热量从燃料棒转移到散热片上。 ...
GTCRN 演进路径:v4 → v5 → 落地
GTCRN 演进路径:v4 → v5 → 落地 记录噪声抑制模型从架构精简开始,经历质量优化、极限压缩,到最终在嵌入式 C 端落地的全过程。 前言 v4.1 把 464 KB 的推理管线交到了 C 端手里。这个数字已经够小——能在大多数嵌入式芯片上跑起来,RTF 不到 0.04。但我们还想要更多。 不是「把模型再做小一点」这么简单。键盘敲击声和风扇底噪的压制效果已经不错了,如果裁剪的过程中把这两个能力丢掉,小就没有意义。换句话说,压缩是手段,质量是底线。每次下手之前,先问一句:压完之后,瞬态噪声还能不能盖住?听感会不会变差? v5 这条线走了四个月。它从 v4.1 的 464 KB 跑到了最终的 412 KB,中间踩了不少坑。这份文档把踩过的坑、走通的路、放弃的岔路都记下来。 时间线:2026-03(v4.1 交付) → 2026-06(v5.6 C 端落地) 版本总览 版本 改了什么 参数 关键指标 内存 结论 v4.1 INT8 混合精度 C 推理 87K PESQ 2.037 464 KB 基线,已交付 v5.1 架构定型 (4层, CH=20) 55.6K PESQ 2.462 ~466 KB 可靠起点 v5.2 多模块 all-in 73.9K PESQ ~1.20 ~538 KB 失败,复盘后放弃 v5.3 网络优化,单模块消融 61.6K [5,10) PESQ 1.92 ~466 KB n4 被接受 v5.4 宽度裁剪 CH→16 41.2K [5,10) PESQ 1.46 ~400 KB 失败,暂停 v5.5 极限压缩 (INT4/INT8) — PESQ drop < 0.05 ~314-349 KB (投) 过门,主线收敛 v5.6 C 端落地:GTC6/INT4/hidden INT8 60.3K DNSMOS SIG +0.86 412 KB 交付 网络结构 (v5.3-n4 最终) 输入 spec (B, 513, T, 2) │ ▼ ERB_48k.bm(): 513 → 219 │ ▼ in_conv: Conv2d(2→3) │ ▼ ┌─ CausalEncoder ───────────────────────────┐ │ DSConv: 219→110 ← skip1 │ │ DSConv: 110→55 ← skip2 │ │ CausalGTConv×4 (d=1,2,4,2) ← skip3-6│ │ SubbandAttention │ └───────────────────────────────────────────┘ │ ▼ CausalDPGRNN × 2 │ intra: 双向GRU (频率轴) │ inter: 单向GRU (时间轴) │ ▼ ┌─ CausalDecoder ───────────────────────────┐ │ SkipResidualFusion + CausalGTConv×4 │ │ Fuse + DSDeconv: 55→110 │ │ SkipResidualFusion + DSDeconv: 110→219 │ └───────────────────────────────────────────┘ │ ▼ out_conv → ERB_48k.bs() → CRM → 输出 相比 v4,decoder 的 skip 连接从简单的 x + skip 换成了带门控和残差分支的融合模块。 ...
PPG 情绪识别:几条路线的取舍与实测
PPG 情绪识别:几条路线的取舍与实测 写在前面 这个工作并不算solid,目前正在数据收集和实测阶段,涉及的数据集也只有WESAD和DEAP,并没有很强的参考意义,请谨慎参考。 摘要 要解决的问题很简单:在手表侧只有 PPG 和三轴加速度这两类输入的前提下,能不能先估计 valence 和 arousal,再映射到 Russell 情绪模型中的象限和离散标签,从而形成一条可闭环的情绪识别链路。 当前项目一共走过三条路线。第一条是 XGBoost Feature Baseline,它使用 HRV + ACC + SQI 这类手工特征,目标是先把任务跑通,并给后续复杂模型提供一个稳定参照。第二条是 EMCNN PPG-only,它直接建模单通道 PPG 波形,是当前的默认主方案。第三条是 EMCNN + aux,在 EMCNN 的基础上再加入 ACC/SQI 辅助输入,作为实验增强路线。 在当前 WESAD LOSO 实测下,EMCNN PPG-only 是综合表现最好的方案:它同时超过了 XGBoost 基线,并且比 EMCNN + aux 保持了更好的连续回归误差。EMCNN + aux 虽然在分类指标上更强,但 VA MAE 更差,更像分类增强方案。除此之外,本文还补充了推理性能、CPU/GPU、并发、内存和显存开销的实测,目的是把“能不能识别”和“值不值得部署”放在同一张桌子上讨论。 本文不公开项目实现和数据实测,仅作交流学习。 这个工作并不算solid,目前正在数据收集和实测阶段,涉及的数据集也只有WESAD和DEAP,并没有很强的参考意义,请谨慎参考。 一、这件事到底在做什么 当前项目的目标不是做一个泛泛的“情绪分类器”,而是要在手表侧有限传感器条件下,建立一条可以闭环的 Russell 情绪建模链路。输入是 PPG + ACC,中间层输出是连续的 valence 和 arousal,最后再映射到 Russell 情绪模型中的象限和离散标签。当前仓库中的推理接口已经对应到了这条链路: 连续层:valence / arousal 决策层:quadrant 展示层:21 个 Russell 标签 + Top-3 这里有一个必须讲清楚的前提:当前项目并不预设 PPG 能直接“表达情绪”,也不假设 PPG 和 valence/arousal 之间存在简单、稳定、显式的一一对应关系。更稳妥的说法是,情绪状态会通过自主神经系统影响心率、血管张力、外周循环和节律变化,而 PPG 恰好能观测到这些变化的一部分。因此,当前项目真正检验的是这样一个工程假设:PPG/ACC 中可能存在与 valence/arousal 相关的可学习统计模式。如果这个假设成立,就有机会在手表侧形成一套弱侵入、持续性的情绪状态估计能力。 ...
带噪信号的趋势分析方法
带噪信号的趋势分析方法 前言 手头有两组时域采样数据,每组大约240个采样点。采样周期固定,信号值在两千到四千之间游走。 肉眼扫过去——毛刺很多,高频抖动明显。但拉远了看,似乎底下藏着某种缓慢变化的结构。就像隔着一层磨砂玻璃看一幅画,大色块能辨认,细节却糊成一团。 DSP里降噪去扰的招数不少。卡尔曼、Holt-Winters、高斯平滑、巴特沃斯、Savitzky-Golay,各有各的路子。我想试试——把这套工具搬到这个信号上,看谁能把底下的轮廓挖出来,谁会被噪声带跑偏。 另外再拿两组最简单的滑动平均(SMA和EMA)当参考线,看看"简单粗暴"和"精心设计"之间到底差多少。 信号概览 先看一眼这两组信号的素颜: 特征 Signal A Signal B 值域 2,863 ~ 3,652 3,097 ~ 4,030 振幅(span) 789 933 采样点 242 243 逐点变化率 std 1.17% 1.53% 均值 3,174 3,575 Signal A的振幅比Signal B小,但逐点变化率的峰值很大——有过几次接近 ±5% 的跳变。整体走势像一条被反复折叠的绳子:先往下摔,弹回来,又摔一次,再弹。方向频繁切换。 Signal B的振幅更大,但方向很一致——从头到尾在往上走。中途有几次小幅回撤,但不改大方向。 这两组信号的差异,决定了后面每种方法的命运。 一、卡尔曼滤波——让状态方程替你猜 原理 卡尔曼不直接相信观测值,也不全信预测值。它在两者之间做加权,权重由各自的不确定性决定。 设一个状态向量 $x_k = [p_k, v_k]^T$,装着"当前位置"和"变化速度"。假设信号按匀速模型演化: $$ x_k = F x_{k-1} + w_k, \quad F = \begin{bmatrix}1 & \Delta t \\ 0 & 1\end{bmatrix} $$每来一个新的观测 $z_k$,先预测、再修正: ...
浮点数的 IEEE 754 标准与内存表达
前言 项目上遇到了double类型数据精度问题,嵌入式开发和算法争论了一会有关double和float的精度问题,究竟是强转造成的精度损失更多,还是在计算的过程中精度损失更多?这个问题很显然是使用float在计算过程中造成的精度损失更多,但是面对这样的问题,不能只靠猜测,而是需要进行一系列量化的测算。 IEEE754标注中的浮点数表达公式 $$ value = (-1)^{sign} \times 2^{exponent} \times (1 + mantissa) $$其中,sign为符号位,exponent为指数位,mantissa为尾数位。 float float类型通常占用4个字节(32位)的内存。具体分配如下: 符号位(Sign bit):1位 指数位(Exponent):8位 尾数位(Fraction/Mantissa):23位 内存布局示例 假设我们有一个单精度浮点数3.14,它的二进制表示如下: 符号位:0 指数位:10000000 尾数位:10010001111010111000011 0 10000000 10010001111010111000011 double 符号位(Sign bit):1位 指数位(Exponent):11位 尾数位(Fraction/Mantissa):52位 内存布局 假设我们有一个双精度浮点数3.14,它的二进制表示如下: 符号位:0 指数位:10000000000 尾数位:1001000111101011100001010001111010111000010100011110101110000101 float与doule之间的转换 float转double 这种转换称为扩展转换(promotion),因为double有更多的位数来表示数字。 1.内存模型变化: float使用32位存储,而double使用64位存储。 在将float转换为double时,计算机会将float的值复制到double的尾数部分,并扩展指数部分。 由于double的尾数部分更长,可以精确表示的有效数字更多,所以这种转换通常不会损失精度。 2.转换过程 符号位保持不变。 指数位从float的8位扩展到double的11位,计算机会根据需要调整指数的偏移量。 尾数位从23位扩展到52位,不足的部分用0填充。 double 转 float 这种转换称为缩减转换(narrowing),因为float有较少的位数来表示数字。 1. 内存模型变化: double使用64位存储,而float使用32位存储。 在将double转换为float时,计算机会将double的值截断或舍入以适应float的尾数部分和指数部分。 由于float的尾数部分较短,这种转换可能会损失精度。 2.转换过程 符号位保持不变。 指数位从double的11位缩减到float的8位,计算机会调整指数的偏移量,并可能会进行舍入。 尾数位从52位缩减到23位,超出的部分会被截断或舍入,这可能导致精度损失。 这里需要注意的是,在C++中,并不是做了简单的截断,而是做了舍入操作,这也是为什么我们在实际操作中,可以观测到逢7进1 例子: float转换double 假设我们有一个float值3.14: float: 0 10000000 10010001111010111000011 ...
要团结
最近听了未明子一个很简短的切片,叫世界之野,大概讲的是一个双向的女孩问他,悲伤了怎么办 他说,悲伤了要用愤怒,去找到悲伤的根源,把根源解决掉,勇敢面对又不害怕。 他说,我感觉自己这个答案答错了,会后悔一辈子,有一辈子负罪感。 最后女孩去世了,内疚感一直在折磨他。 再让他答一遍,悲伤了怎么办。 他说:“要团结起来,要与众人团结起来,要体谅,要柔和,要坚决的去行动,要呵护这个悲伤的孤独的孩子,而不是要愤怒。” 他说,我可能没有第二次机会回答这个问题了。 我想起很多往事,自己曾经也交往过一个双向的女孩,她没有问过我这个问题,可能她并不打心底信任过我,可我也在脑海里想过很多次很多次这个问题,如果还有机会,我也许当时会告诉她:”去爱,带着对世界的温柔,去包容和实践。“ 我其实一直不是很喜欢未明子这个人,但是我相信他肯定也在无数个夜里深深地问过自己如果能回到过去,他会如何回答这个问题。 但是经历过很多事情之后,在经历后AI时代的席卷之后,我才后知后觉的明白,要团结。