N、C、H、W代表的意义如下:

  • N:一个batch内图片的数量
  • H:垂直高度方向的像素个数
  • W:水平宽度方向的像素个数
  • C:通道数,例如灰度图像为1,彩色RGB图像为3
1
2
3
4
5
6
7
8
9
10
tf.nn.conv2d(
input,
filter,
strides,
padding,
use_cudnn_on_gpu=True,
data_format='NHWC',
dilations=[1, 1, 1, 1],
name=None
)

Tensorflow中tensor是从高维向低维算起。比如:x

1
2
3
4
5
6
7
8
9
10
11
12
[
[
[1,2,3,4],
[5,6,7,8],
[9,10,11,12]
],
[
[13,14,15,16],
[17,18,19,20],
[21,22,23,24]
]
]

此数组为2x3x4,可以看成是两个3x4的二位数组。
对于二维数组,perm=[0,1,2],0表示三维数组的高(即二维数组的个数),1代表二维数组的行,2表示二维数组的列。tf.transpose(x,perm=[1,0,2])表示将三维数组的高和行进行转置。
NHWC与NCHW相互转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import tensorflow as tf
# NHWC -> NCHW
img_nhwc = tf.placeholder(tf.float32, [None, 240, 360, 3]) # input batch
out = tf.transpose(img_nhwc, perm=[0, 3, 1, 2])
print(out.get_shape()) # (?, 3, 240, 360)
# NCHW -> NHWC
out = tf.transpose(out, perm=[0, 2, 3, 1])
print(out.get_shape()) # (?, 240, 360, 3)

x = [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
[[13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24]]]
# x 2x3x4

print(tf.transpose(x, perm=[0, 1, 2]))
# Tensor("transpose_2:0", shape=(2, 3, 4), dtype=int32)
print(tf.transpose(x, perm=[0, 2, 1])) # 第2维和第3维转换
# Tensor("transpose_3:0", shape=(2, 4, 3), dtype=int32)
print(tf.transpose(x, perm=[2, 1, 0])) # 第1维和第3维转换
# Tensor("transpose_4:0", shape=(4, 3, 2), dtype=int32)
print(tf.transpose(x, perm=[1, 2, 0]))
# Tensor("transpose_5:0", shape=(3, 4, 2), dtype=int32)

下面使用RGB三通道图像进行演示:

对图像做彩色转灰度计算,NCHW与NHWC的计算过程如下:

从以上两种数据格式进行RGB到灰度计算的复杂度是相同的,区别在于访存特效,对比可以看出NHWC的访存局部性更好,每3个输入像素即可得到一个输出像素,NCHW则必须等所有通道输入准备好才能得到最终的输出结果,需要占用加大内存。
在 CNN 中常常见到 1x1 卷积,也是每个输入 channel 乘一个权值,然后将所有 channel 结果累加得到一个输出 channel。如果使用 NHWC 数据格式,可以将卷积计算简化为矩阵乘计算,即 1x1 卷积核实现了每个输入像素组到每个输出像素组的线性变换。
Tensorflow中NHWC为默认格式,因为早期开发都是基于CPU,使用NHWC比NCHW稍快一些(NHWC局部性更好,cache利用率高)。NCHW则是NVIDIA cuDNN默认格式,使用GPU加速时用NCHW格式速度会更快。

参考文献:
https://mp.weixin.qq.com/s/I4Q1Bv7yecqYXUra49o7tw?