用于神经压缩的深度生成建模
1. 深度生成建模在神经压缩中的重要性
在神经压缩领域,基于神经网络的编解码器通常会与量化和熵编码一起进行训练。那么,为什么深度生成建模在神经压缩中如此重要呢?Claude Shannon 早就给出了答案,他指出消息长度与数据熵成正比。但我们往往不知道数据的熵,因为不清楚数据的概率分布 p(x),不过可以利用之前讨论过的深度生成模型来进行估计。
近年来,利用深度生成建模来改进神经压缩的兴趣日益浓厚。我们既可以用深度生成模型为熵编码器建模概率分布,也能通过引入新的推理和重建方案,显著提高最终的重建和压缩质量。
2. 通用压缩方案
2.1 图像压缩方法分类
在深入探讨神经压缩之前,有必要回顾一下图像(或一般数据)压缩的基本概念。图像压缩方法主要分为两类:
-
无损压缩
:能保留所有信息,重建结果无误差。
-
有损压缩
:压缩过程中会丢失部分信息。
2.2 压缩算法设计原则
设计压缩算法的通用方法是设计一种唯一可解码的代码,其期望长度尽可能接近数据的熵。通用压缩系统由编码器和解码器两个组件组成。需要注意的是,它和确定性变分自编码器(VAE)不同,虽然有相似之处,但在压缩任务中,我们更关注发送比特流,而 VAE 通常不关心这一点。
2.3 编码器
编码器的目标是将图像转换为离散信号,该信号不一定是二进制的。所使用的变换可以是可逆的,但并非必需。若变换可逆,可在解码器中使用其逆变换,理论上可实现无损压缩;若不可逆,则会丢失部分信息,属于有损压缩方法。对输入图像应用变换后,离散信号会以无损方式编码为比特流,即把离散符号映射为二进制变量(比特)。通常,熵编码器会利用符号出现概率的信息,如霍夫曼编码器或算术编码器,而很多熵编码器需要知道 p(x),这时就可以使用深度生成模型。
2.4 解码器
消息(即比特)发送和接收后,熵解码器会将比特流解码为离散信号,它是熵编码器的逆过程。熵编码方法能让我们从比特流中恢复原始符号。最后,应用逆变换(不一定是编码器变换的逆)来重建原始图像。
2.5 完整方案
压缩系统(编解码器)的通用方案包含多尺度图像分解(如小波表示),并进一步进行量化。特定的离散变换(如离散余弦变换 DCT)会产生特定的编解码器(如 JPEG)。
2.6 性能评估
编解码器的最终性能通过重建误差和压缩比来评估。重建误差称为失真度量,通常使用均方误差(MSE)或感知指标(如多尺度结构相似性指数 MS - SSIM)计算;压缩比称为速率,通常用每像素比特数(bpp)表示,即编码器输出的总比特大小除以编码器输入的总像素大小。通常,通过检查速率 - 失真平面(在平面上绘制速率为 x 轴、失真为 y 轴的曲线)来比较编解码器的性能。
形式上,假设采用自编码器架构,编码变换 fe : X → Y 将输入 x 转换为离散信号 y(代码),解码器 fd : Y → X 给出重建结果 ˆx。此外,还有一个自适应熵编码模型,用于学习分布 p(y),并通过熵编码器将离散信号 y 转换为比特流。若压缩方法有自适应(超)参数,可通过优化以下目标函数来学习:
[L(x) = d(x, \hat{x}) + \beta r(y)]
其中,d(·, ·) 是失真度量(如 PSNR、MS - SSIM),r(·) 是速率度量(如 r(y) = -ln p(y)),β > 0 是控制速率和失真平衡的加权因子。需要注意的是,失真度量需要编码器和解码器,速率度量需要编码器和熵模型。
3. JPEG 压缩
JPEG 是最常用的编解码器之一。在 JPEG 编解码器中,RGB 图像首先线性转换为 YCbCr 格式:
[\begin{bmatrix}Y\Cb\Cr\end{bmatrix}=\begin{bmatrix}0\128\128\end{bmatrix}+\begin{bmatrix}0.299&0.587&0.114\ - 0.168736&- 0.331264&0.5\0.5&- 0.48688&- 0.081312\end{bmatrix}\begin{bmatrix}R\G\B\end{bmatrix}]
然后,Cb 和 Cr 通道通常会进行 2 到 3 次下采样(第一次压缩阶段)。接着,每个通道被分割成 8×8 块,并进行离散余弦变换(DCT),最后进行量化(第二次压缩阶段)。最后可使用霍夫曼编码。解码时,使用逆 DCT,对 Cb 和 Cr 通道进行上采样,并恢复 RGB 表示。整个过程步骤清晰,超参数也有明确的解释。
4. 神经压缩组件
4.1 编码器和解码器
在神经压缩中,编码器和解码器由神经网络组成,无额外功能。我们更关注架构,而非如何参数化分布。编码器的输出是连续代码(浮点数),解码器的输出是图像的重建结果。以下是使用 PyTorch 实现的编码器和解码器类示例:
import torch
import torch.nn as nn
# The encoder is simply a neural network that takes an image and outputs a corresponding code.
class Encoder(nn.Module):
def __init__(self, encoder_net):
super(Encoder, self).__init__()
self.encoder = encoder_net
def encode(self, x):
h_e = self.encoder(x)
return h_e
def forward(self, x):
return self.encode(x)
# The decoder is simply a neural network that takes a quantized code and returns an image.
class Decoder(nn.Module):
def __init__(self, decoder_net):
super(Decoder, self).__init__()
self.decoder = decoder_net
def decode(self, z):
h_d = self.decoder(z)
return h_d
def forward(self, z, x=None):
x_rec = self.decode(z)
return x_rec
# ENCODER
D = 10 # Example input dimension
M = 20 # Example hidden dimension
C = 5 # Example output dimension
e_net = nn.Sequential(
nn.Linear(D, M*2), nn.BatchNorm1d(M*2), nn.ReLU(),
nn.Linear(M*2, M), nn.BatchNorm1d(M), nn.ReLU(),
nn.Linear(M, M//2), nn.BatchNorm1d(M//2), nn.ReLU(),
nn.Linear(M//2, C)
)
encoder = Encoder(encoder_net=e_net)
# DECODER
d_net = nn.Sequential(
nn.Linear(C, M//2), nn.BatchNorm1d(M//2), nn.ReLU(),
nn.Linear(M//2, M), nn.BatchNorm1d(M), nn.ReLU(),
nn.Linear(M, M*2), nn.BatchNorm1d(M*2), nn.ReLU(),
nn.Linear(M*2, D)
)
decoder = Decoder(decoder_net=d_net)
4.2 可微量化
在压缩中使用神经网络时,需要确保通过反向传播进行训练,即只使用可微操作。但神经网络的离散输出会破坏可微性,需要应用梯度近似(如直通估计器)。不过,我们可以通过相对简单的技巧使代码 y 的量化可微。
假设编码器输出代码 y ∈ RM,存在一个代码本 c ∈ RK,可将代码本视为额外的参数向量(可学习)。量化的思路是将 y 中的每个元素替换为代码本中最接近的值。具体实现步骤如下:
1. 将 y 重复 K 次,c 重复 M 次,得到矩阵 Y ∈ RM×K 和 C ∈ RM×K。
2. 计算相似度矩阵 S = exp{- ||(Y - C)²||} ∈ RM×K,S 在 ym 最接近 ck 的位置有最大值。
3. 对 S 的第二维应用带温度的 softmax 非线性函数,得到 ˆS = softmax2(τ · S),其中 τ ≫ 1(如 τ = 10⁷)。
4. 通过 ˆy = ˆSc 计算量化代码,ˆy 仅包含代码本中的值。
虽然 ˆy 仍然是浮点数,但代码本中只有 K 种可能的值,所以值是离散的。而且,计算 ˆS 的矩阵是离散的,每行只有一个位置为 1,其余为 0。这种量化方法允许我们应用反向传播算法,在许多神经压缩方法中都有应用。以下是实现可微量化的 PyTorch 类:
import torch
import torch.nn as nn
class Quantizer(nn.Module):
def __init__(self, input_dim, codebook_dim, temp=1.e7):
super(Quantizer, self).__init__()
# temperature for softmax
self.temp = temp
# dimensionality of the inputs and the codebook
self.input_dim = input_dim
self.codebook_dim = codebook_dim
# codebook layer (a codebook)
# initialize uniformly and a Parameter (learnable)
self.codebook = nn.Parameter(torch.FloatTensor(1, self.codebook_dim).uniform_(-1/self.codebook_dim, 1/self.codebook_dim))
# A function for codebook indices (a one−hot representation) to values in the codebook.
def indices2codebook(self, indices_onehot):
return torch.matmul(indices_onehot, self.codebook.t()).squeeze()
# A function to change integers to a one−hot representation.
def indices_to_onehot(self, inputs_shape, indices):
indices_hard = torch.zeros(inputs_shape[0], inputs_shape[1], self.codebook_dim)
indices_hard.scatter_(2, indices, 1)
return indices_hard
# The forward function:
# −First, distances are calculated between input values and codebook values.
# −Second, indices (soft −differentiable , hard −non−differentiable) between the encoded values and the codebook values are calculated.
# −Third, the quantizer returns indices and quantized code (the output of the encoder).
# −Fourth, the decoder maps the quantized code to the observable space (i.e., it decodes the code back).
def forward(self, inputs):
# inputs −a matrix of floats , B x M
inputs_shape = inputs.shape
# repeat inputs
inputs_repeat = inputs.unsqueeze(2).repeat(1, 1, self.codebook_dim)
# calculate distances between input values and the codebook values
distances = torch.exp(-torch.sqrt(torch.pow(inputs_repeat - self.codebook.unsqueeze(1), 2)))
# indices (hard, i.e., nondiff)
indices = torch.argmax(distances, dim=2).unsqueeze(2)
indices_hard = self.indices_to_onehot(inputs_shape=inputs_shape, indices=indices)
# indices (soft, i.e., diff)
indices_soft = torch.softmax(self.temp * distances, -1)
# quantized values: we use soft indices here because it allows backpropagation
quantized = self.indices2codebook(indices_onehot=indices_soft)
return (indices_soft, indices_hard, quantized)
2.3 自适应熵编码模型
熵编码是整个压缩过程的最后一环,常用的熵编码器有霍夫曼编码和算术编码。算术编码在压缩系统中更受青睐,因为它速度更快、精度更高。
我们需要估计代码的概率分布 p(y),以便熵编码器进行无损压缩。可以使用深度生成模型来估计 p(y),例如自回归模型。自回归模型以量化代码为输入,输出代码本中每个值的概率。
以下是一个使用自回归模型的自适应熵编码模型的 PyTorch 类:
import torch
import torch.nn as nn
class ARMEntropyCoding(nn.Module):
def __init__(self, code_dim, codebook_dim, arm_net):
super(ARMEntropyCoding, self).__init__()
self.code_dim = code_dim
self.codebook_dim = codebook_dim
self.arm_net = arm_net # it takes B x 1 x code_dim and outputs B x codebook_dim x code_dim
def f(self, x):
h = self.arm_net(x.unsqueeze(1))
h = h.permute(0, 2, 1)
p = torch.softmax(h, 2)
return p
def sample(self, quantizer=None, B=10):
x_new = torch.zeros((B, self.code_dim))
for d in range(self.code_dim):
p = self.f(x_new)
indx_d = torch.multinomial(p[:, d, :], num_samples=1)
codebook_value = quantizer.codebook[0, indx_d].squeeze()
x_new[:, d] = codebook_value
return x_new
def forward(self, z, x):
p = self.f(x)
return -torch.sum(z * torch.log(p), 2)
2.4 神经压缩系统
神经压缩系统将通用压缩方案中的变换替换为神经网络,并结合可微量化过程。与传统编解码器相比,神经压缩器可以进行端到端训练,并针对特定数据进行优化。
神经压缩器的训练目标可以看作是自编码器的惩罚性重建误差。假设我们有训练数据 D = {x₁, …, xₙ} 及其经验分布 pdata(x),编码器网络权重为 φ,可微量化器代码本为 c,解码器网络权重为 θ,熵编码模型权重为 λ。通过最小化以下目标函数来训练模型(β > 0):
[L(\theta, \varphi, \lambda, c) = E_{x \sim p_{data}(x)}[(x - f_{d, \theta}(Q(f_{e, \varphi}(x); c)))^2] + \beta E_{\hat{y} \sim p_{data}(x) \delta(Q(f_{e, \varphi}(x); c) - \hat{y})}[-\ln p_{\lambda}(\hat{y})]]
其中,第一项是均方误差(MSE)损失,即重建误差;第二项是 q(ˆy) = pdata(x) δ(Q(fe,φ(x); c) - ˆy) 和 pλ(ˆy) 之间的交叉熵。
以下是神经压缩器的 PyTorch 类实现:
import torch
import torch.nn as nn
class NeuralCompressor(nn.Module):
def __init__(self, encoder, decoder, entropy_coding, quantizer, beta=1., detaching=False):
super(NeuralCompressor, self).__init__()
print('Neural Compressor by JT.')
self.encoder = encoder
self.decoder = decoder
self.entropy_coding = entropy_coding
self.quantizer = quantizer
# beta determines how strongly we focus on compression against reconstruction quality
self.beta = beta
# We can detach inputs to the rate, then we learn rate and distortion separately
self.detaching = detaching
def forward(self, x, reduction='avg'):
# encoding
# −non−quantized values
z = self.encoder(x)
# −quantizing
quantizer_out = self.quantizer(z)
# decoding
x_rec = self.decoder(quantizer_out[2])
# Distortion (e.g., MSE)
Distortion = torch.mean(torch.pow(x - x_rec, 2), 1)
# Rate: we use the entropy coding here
Rate = torch.mean(self.entropy_coding(quantizer_out[0], quantizer_out[2]), 1)
# Objective
objective = Distortion + self.beta * Rate
if reduction == 'sum':
return objective.sum(), Distortion.sum(), Rate.sum()
else:
return objective.mean(), Distortion.mean(), Rate.mean()
2.5 压缩过程
假设模型已经训练好,神经压缩的完整过程如下:
1. 对输入图像进行编码:y = fe,φ(x)。
2. 对编码进行量化:ˆy = Q(y; c)。
3. 使用 pλ(ˆy) 和算术编码将量化代码 ˆy 转换为比特流。
4. 发送比特流。
5. 使用 pλ(ˆy) 和算术解码将比特流解码为 ˆy。
6. 对 ˆy 进行解码:ˆx = fd,θ(ˆy)。
2.6 示例
在单独的文件(https://github.com/jmtomczak/intro_dgm)中可以找到上述神经压缩器的实现,并进行实践。例如,当 β = 1 时,可能会得到如图 8.5 所示的结果,包括失真曲线、速率曲线以及真实图像、重建图像和自回归熵编码器的采样结果。
综上所述,神经压缩利用神经网络的灵活性,通过可微量化和自适应熵编码模型,在图像(或一般数据)压缩中展现出了很大的潜力。不同的组件设计和参数选择可以适应不同的数据特点和压缩需求。
5. 神经压缩的优势与应用场景
5.1 优势分析
相较于传统的压缩方法,神经压缩具有显著的优势。传统编解码器如 JPEG 采用预定义的变换和数学操作,虽然过程清晰,但缺乏灵活性。以 DCT 变换为例,它并非对所有图像都是最优的变换方式。而神经压缩通过用神经网络替换这些数学操作,将原本的“白盒”变成“黑盒”,大大增加了灵活性。
从性能上看,神经压缩可以针对特定的数据进行端到端的训练,从而在失真和速率方面都有可能取得更好的表现。通过优化目标函数,能够平衡重建误差和压缩比,以满足不同应用场景的需求。
5.2 应用场景
神经压缩在多个领域都有广泛的应用前景:
-
图像和视频存储
:在大规模的图像和视频数据库中,使用神经压缩可以显著减少存储空间的需求。例如,在云存储服务中,对用户上传的大量图像和视频进行神经压缩,能够降低存储成本,提高存储效率。
-
实时通信
:在视频会议、直播等实时通信场景中,需要在有限的带宽下传输高质量的图像和视频。神经压缩可以在保证一定重建质量的前提下,降低数据传输量,减少延迟,提高通信的流畅性。
-
医学影像
:医学影像如 X 光、CT 等数据量较大,且对图像质量要求较高。神经压缩可以在不损失重要诊断信息的情况下,减少数据存储和传输的负担,方便医生进行远程诊断和病例共享。
6. 技术挑战与未来发展方向
6.1 技术挑战
尽管神经压缩具有很多优势,但也面临着一些技术挑战:
-
计算资源需求高
:训练神经网络需要大量的计算资源和时间,特别是对于大规模的数据集和复杂的网络架构。这限制了神经压缩在一些资源受限的设备上的应用。
-
模型解释性差
:神经网络是一个“黑盒”模型,其决策过程难以解释。在一些对安全性和可靠性要求较高的应用场景中,如医疗和金融领域,模型的可解释性是一个重要的问题。
-
量化误差
:虽然可微量化技术可以使量化过程可微,但仍然存在量化误差,这可能会影响重建图像的质量。如何进一步减少量化误差,提高重建质量,是一个需要解决的问题。
6.2 未来发展方向
为了克服这些挑战,神经压缩的未来发展可能会朝着以下方向进行:
-
轻量级模型设计
:研究人员将致力于设计轻量级的神经网络架构,减少计算资源的需求,使其能够在移动设备和嵌入式系统中运行。
-
可解释性研究
:探索提高神经网络可解释性的方法,例如通过引入可解释的模块或特征,使模型的决策过程更加透明。
-
联合优化
:将神经压缩与其他技术如深度学习中的图像增强、超分辨率等进行联合优化,进一步提高压缩性能和重建质量。
7. 总结与建议
7.1 总结
神经压缩是一种基于神经网络的新型压缩技术,它通过深度生成建模、可微量化和自适应熵编码等方法,在图像和数据压缩领域展现出了巨大的潜力。与传统压缩方法相比,神经压缩具有更高的灵活性和更好的性能,能够适应不同的数据特点和应用需求。
7.2 建议
对于想要深入了解和应用神经压缩技术的读者,以下是一些建议:
-
学习基础知识
:掌握深度学习、信息论和压缩算法的基础知识,这是理解神经压缩的前提。
-
实践项目
:通过实践项目,如在 GitHub 上运行示例代码,深入理解神经压缩的实现过程和参数调整。
-
关注研究动态
:关注相关领域的研究进展,了解最新的技术和方法,不断提升自己的技术水平。
8. 附录:关键概念与公式总结
8.1 关键概念
| 概念 | 解释 |
|---|---|
| 无损压缩 | 一种保留所有信息,重建结果无误差的压缩方法 |
| 有损压缩 | 压缩过程中会丢失部分信息的压缩方法 |
| 熵编码 | 利用符号出现概率的信息,将离散信号无损编码为比特流的方法 |
| 可微量化 | 通过相对简单的技巧使代码量化可微,以支持反向传播训练的方法 |
| 自适应熵编码模型 | 学习代码概率分布 p(y),并用于熵编码的模型 |
8.2 关键公式
- 目标函数 :[L(x) = d(x, \hat{x}) + \beta r(y)]
- 训练目标函数 :[L(\theta, \varphi, \lambda, c) = E_{x \sim p_{data}(x)}[(x - f_{d, \theta}(Q(f_{e, \varphi}(x); c)))^2] + \beta E_{\hat{y} \sim p_{data}(x) \delta(Q(f_{e, \varphi}(x); c) - \hat{y})}[-\ln p_{\lambda}(\hat{y})]]
8.3 神经压缩流程图
graph TD;
A[输入图像] --> B[编码 y = fe,φ(x)];
B --> C[量化 ˆy = Q(y; c)];
C --> D[熵编码为比特流];
D --> E[发送比特流];
E --> F[接收比特流];
F --> G[熵解码为 ˆy];
G --> H[解码 ˆx = fd,θ(ˆy)];
H --> I[输出重建图像];
通过以上内容,我们对神经压缩有了全面的了解。从基本概念、组件实现到优势应用、挑战与发展方向,希望能够帮助读者深入认识这一前沿技术,并为相关的研究和实践提供参考。
超级会员免费看
1970

被折叠的 条评论
为什么被折叠?



