Caffe、Tensorflow和Pytorch通道维度顺序小结
N: batch;
C: channel
H: height
W: width
Caffe 的Blob通道顺序是:NCHW;
Tensorflow的tensor通道顺序:默认是NHWC, 也支持NCHW,使用cuDNN会更快;
Pytorch中tensor的通道顺序:NCHW
TensorRT中的tensor 通道顺序: NCHW
numpy image: H x W x C
torch image: C X H X W
目前世面上深度学习框架比较多,常用的有三大类:Caffe、Tensorflow和Pytorch,这三种深度学习框架都主要在英伟达显卡上面进行训练和测试,很奇怪的是,它们之间的通道维度顺序并没有保持一致,在多个框架中替换着使用,很容易让人混淆了,所以做一个小结。
Caffe 的通道顺序是NCHW;
Tensorflow的通道顺序默认是NHWC(但可以设置成NCHW),NHWC 的访存局部性更好(每三个输入像素即可得到一个输出像素),NCHW 则必须等所有通道输入准备好才能得到最终输出结果,需要占用较大的临时空间。
TensorFlow 为什么选择 NHWC 格式作为默认格式?因为早期开发都是基于 CPU,使用 NHWC 比 NCHW 稍快一些(不难理解,NHWC 局部性更好,cache 利用率高)。
NCHW 则是 Nvidia cuDNN 默认格式,使用 GPU 加速时用 NCHW 格式速度会更快(也有个别情况例外)。
最佳实践:设计网络时充分考虑两种格式,最好能灵活切换,在 GPU 上训练时使用 NCHW 格式,在 CPU 上做预测时使用 NHWC 格式。
Pytorch的通道顺序是NCHW
下采样
常见下采样方法 | ||
---|---|---|
最大池化 | ||
平均池化 | ||
卷积(strides>1) | ||
空洞卷积(Atrous Convolution / Dilated Convolution) | 也是一种变相的下采样方式,但它通常用于保持特征图尺寸的同时增加感受野 | |
自适应下采样(Adaptive Sampling): |
上采样
(将低分辨率特征图放大至高分辨率)
Option | Function | |
---|---|---|
Nearest Neighbor Upsampling (最近邻插值 Bilinear Interpolation Upsampling (双线性插值) |
old函数 nn.Upsample() F.interpolate (1.6版本之后) |
|
Transposed Convolution(转置卷积/反卷积) | nn.ConvTranspose2d | |
PixelShuffle(像素重组) | nn.PixelShuffle |
spectral_norm
是深度学习中的一种正则化技术,主要用于稳定和约束神经网络中的权重矩阵,特别是对于生成对抗网络(GANs)和其他涉及大型权重矩阵的模型,可以有效地解决训练过程中的梯度消失或爆炸问题,以及防止模型过度拟合。
Spectral normalization(谱范数归一化)通过对权重矩阵的谱范数(即该矩阵的最大奇异值)进行规范化,强制限制权重矩阵的影响范围,从而使网络的训练更加稳定。
- 给定一个卷积层
conv
,我们可以应用谱范数归一化:
1 | from torch.nn.utils import spectral_norm |
- 或者在初始化时直接使用
nn.SpectralNorm
:
1 | from torch.nn import SpectralNorm |
Container Class
nn.Sequential
是一个有序的模块容器,其中的子模块会按照添加的顺序依次执行。它非常适用于堆叠一系列简单的线性操作序列,如连续的卷积和全连接层。定义和使用方式如下:
1 | from torch.nn import Sequential, Conv2d, Linear |
nn.ModuleList
是一个可迭代的模块容器,但不保持顺序索引。它可以容纳任意数量的 nn.Module
子类实例,并且可以通过索引访问和修改。
1 | from torch.nn import ModuleList, Conv2d |
nn.ModuleDict
nn.ModuleDict
是一个键值对形式的模块容器,其中键是字符串类型,值是 nn.Module
实例。它允许通过字符串关键字来访问和管理子模块,这对于具有命名组件的复杂网络结构非常有用。
1 | from torch.nn import ModuleDict, Conv2d |
functional
Leak_relu
F.leaky_relu_()
和 F.leaky_relu()
都是用来实现Leaky ReLU激活函数的,它们的区别在于是否原地修改输入张量:
F.leaky_relu(): 这是一个普通的函数调用,它接收一个张量作为输入,计算并返回带有Leaky ReLU激活的输出张量。这个操作不会改变原始输入张量的内容,而是返回一个新的张量。
1
2
31import torch.nn.functional as F
2input = torch.randn(10, 10)
3output = F.leaky_relu(input, negative_slope=0.01) # 返回一个新的张量F.leaky_relu_(): 这是一个原地(in-place)操作符版本,它会在原始输入张量上直接进行Leaky ReLU激活计算,覆盖掉原来的值,不返回新的张量,而是直接修改输入张量。
1
2
31import torch.nn.functional as F
2input = torch.randn(10, 10)
3F.leaky_relu_(input, negative_slope=0.01) # 直接在原始输入上修改
总结来说,如果你希望保留原始输入张量以便后续使用,请使用 F.leaky_relu()
;如果你愿意直接在原始输入上进行操作且不需要保留原始值,可以使用 F.leaky_relu_()
,这在内存有限的情况下有助于节省存储空间。