本文围绕位置编码(Positional Encoding)在神经网络拟合图片中的作用展开实验。通过PaddlePaddle构建NeRF2D神经网络,分别在不使用和使用位置编码的情况下,以坐标预测像素RGB值。对比发现,加入含三角函数的位置编码后,拟合结果更清晰,验证了其提升神经网络表示能力的效果。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

Positional Encoding 高频位置编码
Positional Encoding 是神经网络设计的常用技巧。 例如 NeRF 提到, 用一个神经网络来表示一个场景。给定任何一个像素点的坐标(和观察方向)作为输入,神经网络输出这个点的像素值。但是, 如果输入只是单纯的坐标, 则神经网络表示的场景往往比较模糊. 但是如果额外输入坐标的多个三角函数值,
[sin(x),sin(2x),sin(4x),sin(8x),...,cos(x),cos(2x),cos(4x),cos(8x),...]
则所得到的结果可能会更加清晰。这被称为位置编码 (positional encoding)。
本项目利用 PaddlePaddle 做一个2D版本的简单实验 (这个实验在 CVPR 2020 Tutorial 的视频中提到):
已有一张图片, 用一个神经网络拟合它:对于任意一个点的二维坐标 (x,y), 输出(预测)图片在 (x,y) 处的像素值 (r,g,b). 如果加入位置编码 (positional encoding), 则输入变成了
[x,y,sin(x),sin(y),sin(2x),sin(2y),...,cos(x),cos(y),cos(2x),cos(2y),...]
输出图片在该点的 RGB 值。
并比较不加入与加入位置编码的两种方法清晰度。
本 notebook 执行用时 (用AI Studio的V100 32GB)
自定义设置的程度更高可以满足大部分中小型企业的建站需求,同时修正了上一版中发现的BUG,优化了核心的代码占用的服务器资源更少,执行速度比上一版更快 主要的特色功能如下: 1)特色的菜单设置功能,菜单设置分为顶部菜单和底部菜单,每一项都可以进行更名、选择是否隐 藏,排序等。 2)增加企业基本信息设置功能,输入的企业信息可以在网页底部的醒目位置看到。 3)增加了在线编辑功能,输入产品信息,企业介绍等栏
下图范例来自CVPR 2020 Tutorial, 左边是真实图片, 中间是不用位置编码的神经网络拟合结果, 右边采用了位置编码更加清晰。
本项目将复现类似的结果。
import paddlefrom matplotlib import pyplot as plt import numpy as np from tqdm import tqdm from PIL import Image
准备工作
很少有更简单的数据准备工作了——随便找一张图片即可。
# 随便找一张图片, 放进项目的文件夹里并用 Image 库打开img = Image.open('work/example1.jpg')
img = np.array(img)print(img.shape)
plt.figure(figsize=(5,7))
plt.imshow(img)
(960, 720, 3)
# 类似 NeRF 的想法, 用一个神经网络表示图片:给一个像素坐标 (x,y), 预测该像素点的 RGB 值。# 神经网络简单地用若干 Linear 层 和 ReLU 激活函数堆叠而成。class NeRF2D(paddle.nn.Layer):
def __init__(self, input_size: int = 2, layers: int = 5, hidden_size: int = 256):
super().__init__() assert layers >= 2, 'MLP should have at least 2 layers.'
input_size :int = input_size # 2d 输入: 像素坐标 (x,y)
output_size:int = 3 # 3d 输出: 坐标的对应RGB值 (R,G,B)
# 保存一些参数
self.input_size = input_size
self.output_size = output_size
self.layers = layers
# 用一个 Layerlist 存放所有层
self.mlps = paddle.nn.LayerList()
mlp_dim = [input_size] + [hidden_size] * (layers - 1) + [output_size] for layer in range(layers): # 全连接层
self.mlps.append(
paddle.nn.Linear(mlp_dim[layer], mlp_dim[layer+1])
) # ReLU层
self.mlps.append(paddle.nn.ReLU()) def forward(self, x):
# 让 x 通过所有层
for layer in range(self.layers * 2):
x = self.mlps[layer](x) return x
不使用 Positional Encoding
用 F(x,y) 表示神经网络函数 ((x,y) 是输入), 则损失函数为神经网络的预测 F(x,y) 与真实图片的像素值 RGB(x,y) 的差距 (L2 损失)。
L=N1all pixels (x,y)∑(F(x,y)−RGB(x,y))2
w , h = img.shape[0], img.shape[1] pi = np.pi # 将 w*h 个像素坐标 (x,y) 作为输入# 这里不妨将图片看成 [-pi/2, pi/2] x [-pi/2, pi/2] 的坐标系# (因为神经网络的输入和输出最好不要太大, 每个值 <= 1 比较好)inputs = paddle.zeros((w,h,2)) inputs[:,:,0] += paddle.linspace(-pi/2, pi/2, num=w).reshape((w,1)) # 纵坐标inputs[:,:,1] += paddle.linspace(-pi/2, pi/2, num=h).reshape((1,h)) # 横坐标# 输入相当于 w*h 条二维数据 (输入不是很大, 可以一次性全部计算, 不需要 batch)inputs = inputs.reshape((w*h, 2))# 作为参照的, 期望的输出是 w*h 个像素的 RGB 值, 即图片本身outputs = paddle.to_tensor(img, dtype='float32').reshape((w*h, 3)) outputs = outputs / 255. # 由于网络最后是 ReLU, 所以期望的输出应该 >= 0, 最好介于 [0,1]
# 创建一个神经网络, 用 Adam 优化器net = NeRF2D() opt = paddle.optimizer.Adam(parameters = net.parameters()) losses = []
# 训练 2000 步 (3分钟左右)epochs = 2000for epoch in tqdm(range(len(losses)+1, len(losses)+epochs+1)):
render = net(inputs) # (w*h, 3)
# 将神经网络根据坐标预测的渲染 (render) 结果与原图片对比, 用 L2 损失作为损失函数
loss = paddle.mean(paddle.square(render - outputs))
losses.append(loss.item()) # 反向传播+梯度下降
opt.clear_grad()
loss.backward()
opt.step()
100%|██████████| 2000/2000 [03:06<00:00, 10.70it/s]
# 观察 loss 曲线 和 最后一次渲染的结果, # 神经网络生成的图片有模有样, 缺点是比较模糊plt.figure(figsize=(12,7)) plt.subplot(1,2,1) plt.semilogy(losses) plt.subplot(1,2,2) plt.imshow(render.reshape((w,h,3)))
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
使用 Positional Encoding
使用了 Positional Encoding, 即将每个输入从 (x,y) 变成
[x,y,sin(x),sin(y),sin(2x),sin(2y),...,cos(x),cos(y),cos(2x),cos(2y),...]
这里我们最高取到 20,21,22,23,24 倍.
# 接下来是使用 Positional Encoding 的改进# 这时候每个输入不再是 2 维的 (x,y)# 而是多维的 (x,y,sinx,siny,cosx,cosy,sin(2x),sin(2y),cos(2x),cos(2y),...)positional_encoding = [inputs]
freqs = [1.,2.,4.,8.,16.]for freq in freqs:
positional_encoding.append(paddle.cos(inputs * freq))
positional_encoding.append(paddle.sin(inputs * freq))
positional_encoding = paddle.to_tensor(positional_encoding, dtype='float32') # shape = [1+2*Freqs, w*h, 2]positional_encoding = positional_encoding.transpose((1,2,0)).reshape((w*h, -1))print(positional_encoding.shape)
[691200, 22]
# 再创建一个神经网络, 这次每条输入的维度是 22net = NeRF2D(input_size = positional_encoding.shape[1]) opt = paddle.optimizer.Adam(parameters = net.parameters()) losses = []
# 训练 2000 步 (3分钟左右)epochs = 2000for epoch in tqdm(range(len(losses)+1, len(losses)+epochs+1)):
render = net(positional_encoding) # (w*h, 3)
# 将神经网络根据坐标预测的渲染 (render) 结果与原图片对比, 用 L2 损失作为损失函数
loss = paddle.mean(paddle.square(render - outputs))
losses.append(loss.item())
opt.clear_grad()
loss.backward()
opt.step()
100%|██████████| 2000/2000 [03:08<00:00, 10.62it/s]
# 观察 loss 曲线 和 最后一次渲染的结果, 可以看出比不用 positional encoding 要清晰很多plt.figure(figsize=(12,7)) plt.subplot(1,2,1) plt.semilogy(losses) plt.subplot(1,2,2) plt.imshow(render.reshape((w,h,3)))
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).









