深度学习中的傅里叶位置编码
- 介绍
-
-
- [1. 核心矛盾:神经网络的"低通滤波器"特性](#1. 核心矛盾:神经网络的“低通滤波器”特性)
- [2. 数学表达](#2. 数学表达)
- [3. 两种主流的应用视角](#3. 两种主流的应用视角)
-
- [A. 在 Transformer 中的"相对位置感知"](#A. 在 Transformer 中的“相对位置感知”)
- [B. 在坐标感知任务(如 NeRF/3D 视觉)中的"特征增强"](#B. 在坐标感知任务(如 NeRF/3D 视觉)中的“特征增强”)
- [4. 为什么它有效?(直观理解)](#4. 为什么它有效?(直观理解))
- [5. 进阶形式:RFF (Random Fourier Features)](#5. 进阶形式:RFF (Random Fourier Features))
- 代码实现示例
- 总结
-
介绍
在深度学习中,傅里叶位置编码(Fourier Positional Encoding) 是一项将连续坐标(或离散索引)映射到高维特征空间的技术。无论是 Transformer 处理序列,还是 NeRF 处理 3D 坐标,其核心逻辑基本一致。
1. 核心矛盾:神经网络的"低通滤波器"特性
深度学习模型(尤其是多层感知机 MLP)存在一种现象,被称为 光谱偏差(Spectral Bias)。
- 问题: 神经网络倾向于优先学习数据中的低频分量 (平滑的部分),而很难捕捉高频分量(精细的纹理、急剧变化的边界)。
- 表现: 如果直接将原始坐标 ( x , y , z ) (x, y, z) (x,y,z) 输入网络,生成的图像或模型往往非常模糊,丢失了大量细节。
傅里叶位置编码的作用就是预先将输入"拉伸"到高频空间,强迫模型感知细微的变化。
2. 数学表达
最常见的傅里叶编码形式(如在 NeRF 或 Transformer 中使用的)是将一个标量值 v v v 映射为一个向量:
γ ( v ) = [ sin ( 2 0 π v ) , cos ( 2 0 π v ) , ... , sin ( 2 L − 1 π v ) , cos ( 2 L − 1 π v ) ] \gamma(v) = [ \sin(2^0 \pi v), \cos(2^0 \pi v), \dots, \sin(2^{L-1} \pi v), \cos(2^{L-1} \pi v) ] γ(v)=[sin(20πv),cos(20πv),...,sin(2L−1πv),cos(2L−1πv)]
- 多尺度频率: 通过指数级增加频率( 2 0 , 2 1 , ... 2^0, 2^1, \dots 20,21,...),编码涵盖了从全局(低频)到局部细节(高频)的所有特征。
- 周期性: 使用 sin \sin sin 和 cos \cos cos 保证了编码在空间上的连续性。
3. 两种主流的应用视角
A. 在 Transformer 中的"相对位置感知"
在 NLP 中,模型本质上是无序的(词袋模型)。傅里叶编码(Sinusoidal Encoding)提供了位置信息:
- 距离度量: 由于三角函数的特性,任意两个位置 p o s pos pos 和 p o s + k pos+k pos+k 之间的编码关系可以通过一个线性变换来表示。这意味着模型可以更容易地学到词与词之间的相对距离。
B. 在坐标感知任务(如 NeRF/3D 视觉)中的"特征增强"
在处理几何数模或 3D 场景时:
- 高维投影: 将 3 维坐标投影到(例如)256 维的空间。
- 类似于算术: 就像我们用二进制表示数字一样,低位(高频)变化极快,代表微小的位移;高位(低频)变化缓慢,代表宏观的位置。这让 MLP 能够像"查表"一样精准地定位空间中的每一个点。
4. 为什么它有效?(直观理解)
想象你要在地图上定位一个点:
- 如果只给你经纬度(原始坐标),数值变化很小,模型很难区分 100.001 100.001 100.001 和 100.002 100.002 100.002 的区别。
- 傅里叶编码相当于给地图加了无数层不同粗细的网格。
- 第一层网格很大(低频)。
- 最后一层网格细如发丝(高频)。
- 模型通过查看点在所有网格中的相对位置,就能瞬间锁定极高精度的坐标。
5. 进阶形式:RFF (Random Fourier Features)
在最新的研究中,研究者发现不一定要用固定的 2 n 2^n 2n 频率,可以使用随机采样的高斯频率:
ϕ ( x ) = [ cos ( 2 π B x ) , sin ( 2 π B x ) ] T \phi(\mathbf{x}) = [\cos(2\pi \mathbf{B}\mathbf{x}), \sin(2\pi \mathbf{B}\mathbf{x})]^T ϕ(x)=[cos(2πBx),sin(2πBx)]T
其中 B \mathbf{B} B 是从高斯分布中随机生成的矩阵。这种方法(常见于 Fourier Feature Networks )可以根据任务需求,通过调整高斯分布的标准差( σ \sigma σ)来控制模型捕捉细节的能力:
- σ \sigma σ 越大: 捕捉的细节越细,但如果过大可能导致混叠噪声。
- σ \sigma σ 越小: 结果越平滑。
代码实现示例
常规实现
python
class FourierPositionalEncoding(nn.Module):
"""Fourier feature encoding for spatial coordinates.
Maps scalar values to high-dimensional space using sinusoidal functions,
helping the network capture fine spatial details even with large coordinate values.
"""
def __init__(self, input_dim: int, num_frequencies: int = 10, log_scale: bool = True):
super().__init__()
self.input_dim = input_dim
self.num_frequencies = num_frequencies
self.output_dim = input_dim * 2 * num_frequencies
if log_scale:
freq_bands = 2.0 ** torch.linspace(0, num_frequencies - 1, num_frequencies)
else:
freq_bands = torch.linspace(1.0, 2.0 ** (num_frequencies - 1), num_frequencies)
# shape: (num_frequencies,)
self.register_buffer("freq_bands", freq_bands)
def forward(self, x: Tensor) -> Tensor:
"""
Args:
x: (..., input_dim)
Returns:
(..., output_dim)
"""
# x_proj shape: (..., input_dim, num_frequencies)
x_proj = x.unsqueeze(-1) * self.freq_bands * math.pi
# sin/cos shape: (..., input_dim * num_frequencies)
sin_feat = torch.sin(x_proj).flatten(-2)
cos_feat = torch.cos(x_proj).flatten(-2)
return torch.cat([sin_feat, cos_feat], dim=-1)
RFF
python
import math
import torch
from torch import nn
class RandomFourierPositionEncoding(nn.Module):
"""
随机傅里叶特征对 3D 坐标编码。
坐标先归一化到 [-1,1](基于场景包围盒),再映射到高维。
这样对"坐标值很大"的场景保持稳定。
"""
def __init__(self, d_out: int, d_pos: int = 3, n_freq: int = 64, sigma: float = 1.0):
super().__init__()
# 固定随机频率矩阵,不参与梯度
B = torch.randn(d_pos, n_freq) * sigma # (d_pos, n_freq)
self.register_buffer("B", B)
self.proj = nn.Linear(n_freq * 2, d_out) # sin+cos → d_out
def forward(self, xyz: torch.Tensor) -> torch.Tensor:
"""
xyz: (..., d_pos) 已归一化坐标
return: (..., d_out)
"""
x = 2 * math.pi * xyz @ self.B # (..., n_freq)
x = torch.cat([x.sin(), x.cos()], dim=-1) # (..., 2*n_freq)
return self.proj(x)
总结
傅里叶位置编码不仅仅是给输入加点"佐料",它是改变了模型观察数据的分辨率。它将简单的数值转化为一组丰富的频率信号,让神经网络这个"近视眼"戴上了一副高倍率的显微镜。