读 ncnn 源码(XXIII):fuse_deconvolution_batchnorm——反卷积层的 BN 融合

在之前的篇章中,我们已经深入探讨了标准卷积 (Convolution) 和深度可分离卷积 (ConvolutionDepthWise) 如何与后续的 BatchNorm 层进行融合。Deconvolution(也称为转置卷积或分数步长卷积)作为上采样操作中常用的层,其后同样可能跟随 BatchNorm 层。为了保持优化策略的一致性和覆盖度,ncnn 提供了 fuse_deconvolution_batchnorm Pass 来处理 Deconvolution -> BatchNorm 这一特定模式。

本篇,我们将剖析该函数的源码,理解其如何将 BN 融合的通用原理应用于反卷积层。

TL;DR

  1. 目标: 将 Deconvolution 层后紧随的 BatchNorm 层进行融合,消除 BatchNorm 在推理时的计算开销。
  2. 模式匹配: 遍历网络 layers,查找 Deconvolution -> BatchNorm 的直接连接模式。
  3. 数学原理: fuse_convolution_batchnorm 完全一致Deconvolution 本质上也是一种线性变换(可以表示为与特定稀疏矩阵的乘法),后接 BatchNorm 仍然构成连续的线性计算链。融合公式不变:
    • 令 BN 参数导出系数 b = slope / sqrt(var + eps)a = bias - slope * mean / sqrt(var + eps)
    • 新权重 Wdeconv=WdeconvbW'_{deconv} = W_{deconv} \cdot b (逐输出通道相乘)。
    • 新偏置 bdeconv=bdeconvb+ab'_{deconv} = b_{deconv} \cdot b + a (逐输出通道计算)。
  4. 代码实现: 几乎与 fuse_convolution_batchnorm 完全相同
    • 计算中间系数 ab
    • 遍历 Deconvolution 层的每个输出通道 i
    • 将该通道对应的所有权重 (weight_per_outch 个元素) 乘以 b[i]
    • 更新该通道的偏置 bias[i] = bias[i] * b[i] + a[i] (如果原层无偏置,则先创建)。
  5. 图结构修改: 将 Deconvolution 层的 top 指向原 BatchNormtop,更新 blobproducer,并将 BatchNorm 标记为 "ncnnfused"
  6. 效果: 消除了 BatchNorm 层的计算和内存访问,对于使用 Deconvolution 进行上采样的网络(如某些分割模型、生成模型)具有优化效果。


1. 融合动机:线性变换合并的普适性

Deconvolution 虽然常用于上采样,但其数学本质仍然是一种线性变换。它可以被看作是卷积操作的一种“逆运算”形式(尽管并非严格意义上的逆),并且同样可以表示为输入向量与一个(通常是更大的、结构化的)权重矩阵的乘积,再加上一个偏置项。

y=Wdeconvx+bdeconvy = W_{deconv} * x + b_{deconv}

由于 Deconvolution 和 BatchNorm 都是线性操作,将它们合并为一个等效的 Deconvolution 操作在数学上是可行的,并且能够带来性能上的收益(减少计算步骤和访存)。


2. 原理

融合反卷积(Deconvolution / Transposed Convolution)和批量归一化(Batch Normalization, BN)的数学原理,与融合标准卷积和 BN 的原理是完全一致的

二者融合的核心思想是:反卷积和 BN(在推理时)都是线性变换。我们可以将两个连续的线性变换在数学上合并(absorb)成一个单独的、等效的线性变换,从而减少计算量。

这个过程只在模型推理(Inference)时进行,因为在训练时,BN 层的均值(mean)和方差(variance)是动态变化的。在推理时,我们会使用在整个训练集上统计得到的固定的均值 μ\mu 和方差 σ2\sigma^2


1. 符号定义

我们先定义两个层的操作:

  • 输入xx

  • 反卷积层(Deconvolution)

    • 权重: WdeconvW_{deconv}

    • 偏置: bdeconvb_{deconv}

    • 操作: Y=Wdeconvx+bdeconvY = W_{deconv} \circledast x + b_{deconv}

      \circledast 代表反卷积/转置卷积操作)

  • 批量归一化层(Batch Norm)

    • 缩放因子(可学习): γ\gamma (gamma)
    • 平移因子(可学习): β\beta (beta)
    • 训练时统计的均值: μ\mu (running_mean)
    • 训练时统计的方差: σ2\sigma^2 (running_var)
    • 数值稳定常量: ϵ\epsilon (epsilon)
    • 操作: z=γYμσ2+ϵ+βz = \gamma \odot \frac{Y - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta

重要说明bdeconvb_{deconv}, γ\gamma, β\beta, μ\mu, σ2\sigma^2 都是向量,其维度等于反卷积层的输出通道数 CoutC_{out}。在计算时,它们会广播(broadcast)到 YY 张量的 (N,Cout,Hout,Wout)(N, C_{out}, H_{out}, W_{out}) 维度上。


2. 数学推导

我们的目标是找到一组新的反卷积权重 WdeconvW'_{deconv} 和偏置 bdeconvb'_{deconv},使得单独一个反卷积层就能产生与原先两个层序列完全相同的结果 zz

z=Wdeconvx+bdeconv=?γ(Wdeconvx+bdeconv)μσ2+ϵ+βz = W'_{deconv} \circledast x + b'_{deconv} \stackrel{?}{=} \gamma \odot \frac{(W_{deconv} \circledast x + b_{deconv}) - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta

我们从等式右侧开始,将其展开并重新组合成 A(Wdeconvx)+BA \cdot (W_{deconv} \circledast x) + B 的形式。

步骤 1:展开 BN 公式

z=(γσ2+ϵ)(Wdeconvx+bdeconvμ)+βz = \left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot (W_{deconv} \circledast x + b_{deconv} - \mu) + \beta

步骤 2:应用乘法分配律

我们将 (γσ2+ϵ)\left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) 乘入括号内的每一项:

z=(γσ2+ϵ)(Wdeconvx)+(γσ2+ϵ)(bdeconvμ)+βz = \left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot (W_{deconv} \circledast x) + \left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot (b_{deconv} - \mu) + \beta

步骤 3:重新组合

现在我们有两个部分:

  1. xx 相关(卷积)的部分
  2. xx 无关(偏置)的部分

对于第 1 部分(权重部分):

(γσ2+ϵ)(Wdeconvx)\left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot (W_{deconv} \circledast x)

由于 γ\gammaσ2\sigma^2 都是按通道 oo 作用的,这个缩放因子可以直接吸收到 WdeconvW_{deconv}输出通道维度上。

=((γσ2+ϵ)Wdeconv)x= \left( \left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot W_{deconv} \right) \circledast x

因此,我们定义新的权重 WdeconvW'_{deconv}

Wdeconv=(γσ2+ϵ)WdeconvW'_{deconv} = \left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot W_{deconv}

  • 代码实现:对于 WdeconvW_{deconv}(维度 Cin,Cout,Kh,KwC_{in}, C_{out}, K_h, K_w)[注1],我们将向量 γoσo2+ϵ\frac{\gamma_o}{\sqrt{\sigma_o^2 + \epsilon}} 乘到第 oo输出通道对应的所有权重上。

对于第 2 部分(偏置部分):

(γσ2+ϵ)(bdeconvμ)+β\left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot (b_{deconv} - \mu) + \beta

这整一项都不依赖于输入 xx,因此它们共同构成了新的偏置 bdeconvb'_{deconv}

bdeconv=(γσ2+ϵ)(bdeconvμ)+βb'_{deconv} = \left( \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \right) \odot (b_{deconv} - \mu) + \beta

  • 代码实现:这完全是向量运算。γ,β,μ,σ2,bdeconv\gamma, \beta, \mu, \sigma^2, b_{deconv} 都是 (Cout)(C_{out}) 维的向量。

3. 融合公式总结

融合反卷积和 BN 层,就是用一个具有新参数 WdeconvW'_{deconv}bdeconvb'_{deconv} 的新反卷积层来替换它们。新参数的计算公式如下:

A=γσ2+ϵA = \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} (这是一个 (Cout)(C_{out}) 维的向量)

则:

  1. 新权重 WdeconvW'_{deconv}

    Wdeconv=AWdeconvW'_{deconv} = A \odot W_{deconv}

    (将 AA 广播到 WdeconvW_{deconv} 的输出通道维度上相乘)

  2. 新偏置 bdeconvb'_{deconv}

    bdeconv=A(bdeconvμ)+βb'_{deconv} = A \odot (b_{deconv} - \mu) + \beta

注意:如果在反卷积层中没有使用偏置(即 bdeconv=0b_{deconv} = 0),公式依然成立,此时:

bdeconv=(Aμ)+β=βγμσ2+ϵb'_{deconv} = - (A \odot \mu) + \beta = \beta - \frac{\gamma \odot \mu}{\sqrt{\sigma^2 + \epsilon}}

[注1]:PyTorch 中反卷积权重的维度是 (Cin,Cout,...)(C_{in}, C_{out}, ...),而标准卷积是 (Cout,Cin,...)(C_{out}, C_{in}, ...)。但无论哪种情况,γ,β,μ,σ2\gamma, \beta, \mu, \sigma^2 都是作用在输出通道 CoutC_{out} 维度上的。


3. 代码实现:复用成熟的融合逻辑

fuse_deconvolution_batchnorm 的实现代码再次证明了 BN 融合逻辑的高度可复用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
int NetOptimize::fuse_deconvolution_batchnorm()
{
const size_t layer_count = layers.size();
for (size_t i = 0; i < layer_count; i++) // 遍历查找 Deconvolution
{
if (layers[i]->type != "Deconvolution") continue;
int top_blob_index = layers[i]->tops[0];

// 查找后续的 BatchNorm
// ... (模式匹配代码与 fuse_convolution_batchnorm 完全一致) ...
size_t j = i + 1;
// ... (find BatchNorm j) ...
if (j == layer_count) continue;

ncnn::Deconvolution* deconvolution = (ncnn::Deconvolution*)layers[i];
ncnn::BatchNorm* batchnorm = (ncnn::BatchNorm*)layers[j];

fprintf(stderr, "fuse_deconvolution_batchnorm %s %s\n", deconvolution->name.c_str(), batchnorm->name.c_str());

// --- 参数变换核心 (与 fuse_convolution_batchnorm 完全一致) ---
{
int channels = batchnorm->channels; // = deconvolution->num_output
float eps = batchnorm->eps;

// 1. 计算中间系数 a 和 b
std::vector<float> a(channels);
std::vector<float> b(channels);
for (int ch = 0; ch < channels; ch++)
{
float sqrt_var = static_cast<float>(sqrt(batchnorm->var_data[ch] + eps));
a[ch] = batchnorm->bias_data[ch] - batchnorm->slope_data[ch] * batchnorm->mean_data[ch] / sqrt_var;
b[ch] = batchnorm->slope_data[ch] / sqrt_var;
}

// 2. 如果 Deconvolution 没有偏置,则创建并初始化为 0
if (deconvolution->bias_term == 0)
{
deconvolution->bias_term = 1;
deconvolution->bias_data = ncnn::Mat(channels);
deconvolution->bias_data.fill(0.f);
}

// 3. 计算每个输出通道的权重数量
// Deconvolution 的权重大小与 Convolution 类似
const int weight_per_outch = deconvolution->weight_data_size / channels;

float* weight = deconvolution->weight_data; // 指向 Deconvolution 权重
float* bias = deconvolution->bias_data; // 指向 Deconvolution 偏置

// 4. 遍历每个输出通道,更新权重和偏置
for (int ch = 0; ch < channels; ch++)
{
// 获取指向当前通道 ch 的权重的指针
float* conv_weight_outch = weight + weight_per_outch * ch;
// 将该通道的所有权重乘以 b[ch]
for (int k = 0; k < weight_per_outch; k++)
{
conv_weight_outch[k] *= b[ch]; // W'_deconv = W_deconv * b
}
// 更新偏置 b'_deconv = b_deconv * b + a
bias[ch] = bias[ch] * b[ch] + a[ch];
}
} // --- 参数变换结束 ---

// --- 图结构修改 (标准融合操作) ---
int top_blob_index_final = batchnorm->tops[0];
deconvolution->tops[0] = top_blob_index_final;
blobs[top_blob_index_final].producer = i;
batchnorm->type = "ncnnfused";
// --- 图结构修改结束 ---
}
return 0;
}

关键点:

  • 模式匹配: 查找 Deconvolution -> BatchNorm 的直接连接。
  • 参数计算: 计算 a, b 系数的逻辑不变。
  • 权重更新: 将 Deconvolution 层每个输出通道 ch 对应的所有权重(weight_per_outch 个)乘以该通道的 b[ch]
  • 偏置更新: 将偏置 bias[ch] 更新为 bias[ch] * b[ch] + a[ch]
  • 图修改: 标准的重定向连接 + 标记融合操作。

4. 意义:完善优化覆盖面

虽然 Deconvolution 在 CNN 中可能不如标准卷积或深度卷积那么常见,但在需要上采样的任务(如图像分割、图像生成、超分辨率)中仍然扮演着重要角色。提供 fuse_deconvolution_batchnorm Pass 确保了 ncnn 对这类网络结构也能应用关键的 BN 融合优化,体现了其优化策略的全面性。


5. 结语

fuse_deconvolution_batchnorm 再次印证了 ncnn 图优化中合并连续线性变换这一核心思想的强大威力与通用性。通过复用与标准卷积融合 BN 完全相同的参数变换逻辑,ncnn 能够有效地消除 Deconvolution 层后的 BatchNorm 计算,为使用反卷积进行上采样的网络结构带来了实实在在的性能提升。这一系列针对不同卷积变体(标准、深度、反卷积)的 BN 融合 Pass,共同构成了 ncnnoptimize 优化流水线中的关键环节。

该封面图片由Frauke RietherPixabay上发布