tf.nn.conv2d

tf.nn.conv2d函数用于实现2D卷积运算

输入X:[b,h,w,cin]

卷积核W:[k,k,cin,cout]

输出O:[b,h',w',cout]

其中,cin表示输入通道数,cout表示卷积核的数量,也是输出特征图的通道数

卷积核大小为k*k

1
2
3
4
5
6
import tensorflow as tf
x=tf.random.normal([2,5,5,3])#模拟输入,3通道,高宽为5,2张图片
#需要根据[k,k,cin,cout]格式创建 W 张量(filter), 4 个 3x3 大小卷积核
w=tf.random.normal([3,3,3,4])
#步长为1,padding为0
out=tf.nn.conv2d(x,w,strides=1,padding=[[0,0],[0,0],[0,0],[0,0]])
1
print(out.shape)#2是2张图片
1
TensorShape([2, 3, 3, 4])

2代表2张图片,3*3便是卷积(步长为1,padding为0)之后的图像大小,4是代表用4各卷积核作用。下图以其中一个卷积核为例,展示了卷积的过程(先左右,再上下)

1596191874866

5*5的图像经过卷积核的作用之后就会变成3*3大小。

其中 padding 参数的设置格式为: padding=[[0,0],[上,下],[左,右],[0,0]]

特别地, 通过设置参数padding='SAME'strides=1 可以直接得到输入、 输出同大小的卷积层, 其中 padding的具体数量TensorFlow自动计算并完成填充操作

1
2
3
4
x=tf.random.normal([2,5,5,3])# 模拟输入, 3 通道,高宽为 5,2张图片
w=tf.random.normal([3,3,3,4])# 4 个 3x3 大小的卷积核,同样必须是3个通道,以和x对应
out = tf.nn.conv2d(x,w,strides=1,padding='SAME')
print(out.shape)
1
TensorShape([2, 5, 5, 4])

此时,5*5尺寸的图像输入,经卷积,得到和原尺寸一样的输出。

layers.Conv2D

通过卷积层类layers.Conv2D 可以不需要手动定义卷积核𝑾和偏置𝒃张量,直接调用类 实例即可完成卷积层的前向计算, 实现更加高层和快捷。 在 TensorFlow 中, API 的命名有 一定的规律, 首字母大写的对象一般表示类,全部小写的一般表示函数,如 layers.Conv2D 表示卷积层类, nn.conv2d 表示卷积运算函数。 使用类方式会(在创建类时或 build 时)自动 创建需要的权值张量和偏置向量等, 用户不需要记忆卷积核张量的定义格式,因此使用起 来更简单方便,但是灵活性也略低。函数方式的接口需要自行定义权值和偏置等,更加灵 活和底层

1
2
3
#4个3*3大小的卷积核
from tensorflow.keras import layers
layer = layers.Conv2D(4,kernel_size=3,strides=1,padding='SAME')

如果卷积核高宽不等,步长行列方向不等,此时需要将kernel_size 参数设计为 tuple 格式(𝑘ℎ 𝑘𝑤),strides 参数设计为(𝑠ℎ 𝑠𝑤)。 如下创建43 ×3 大小的卷积核,竖直方向移 动步长𝑠ℎ = 2,水平方向移动步长𝑠𝑤 = 1

1
layer = layers.Conv2D(4,kernel_size=(3,4),strides=(2,1),padding='SAME')

创建完成后,通过调用实例(的__call__方法)即可完成前向计算

1
2
3
4
# 创建卷积层类
layer = layers.Conv2D(4,kernel_size=3,strides=1,padding='SAME')
out = layer(x) # 前向计算
out.shape # 输出张量的 shape
1
TensorShape([2, 5, 5, 4])

这个和上一部分图解的那个例子是一样的

在类Conv2D 中,保存了卷积核张量𝑾和偏置𝒃,可以通过类成员 trainable_variables 直接返回𝑾和𝒃的列表

1
2
# 返回所有待优化张量列表
layer.trainable_variables
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
[<tf.Variable 'conv2d_3/kernel:0' shape=(3, 3, 3, 4) dtype=float32, numpy=
array([[[[ 0.26849422, 0.19578537, 0.26145306, -0.08229557],
[ 0.15586495, -0.16629952, -0.28540888, -0.07016225],
[-0.15017624, 0.3068613 , -0.07887723, 0.10123444]],

[[ 0.22432038, 0.0565404 , 0.12941647, 0.09906727],
[ 0.16842848, 0.12426731, 0.23842916, -0.1283393 ],
[ 0.04178131, 0.06155536, 0.26901218, -0.17057599]],

[[-0.1754628 , 0.22139618, 0.20181099, -0.0549061 ],
[ 0.2804089 , 0.09290037, -0.05881791, -0.18321809],
[-0.24344192, 0.30082038, 0.15440792, -0.02978626]]],


[[[-0.09762125, 0.24588814, -0.05257043, -0.07488932],
[ 0.08860514, 0.29339835, -0.06584421, 0.10579816],
[-0.25776446, -0.21827325, -0.18117602, 0.15594906]],

[[ 0.16299537, 0.04286107, -0.25221378, 0.06759065],
[-0.24693958, -0.2568909 , -0.077087 , 0.03058854],
[ 0.21647856, 0.16148275, -0.0668918 , 0.22095159]],

[[-0.2938962 , -0.24901226, 0.06835467, -0.13457522],
[ 0.22625688, 0.00778407, 0.24120173, -0.0922821 ],
[ 0.21996793, -0.30792752, -0.12364005, 0.23726341]]],


[[[ 0.02748409, -0.17573136, -0.29806593, -0.18748367],
[-0.09907596, 0.02415624, 0.26827005, -0.27712965],
[-0.112516 , -0.10590065, -0.25308898, 0.2885895 ]],

[[-0.17336473, -0.18374749, -0.25417763, -0.14162418],
[ 0.21973309, 0.30036303, 0.23560277, 0.25980887],
[-0.16107993, -0.10360201, -0.16716456, -0.04651383]],

[[ 0.21558437, -0.06225148, 0.0479238 , -0.07939483],
[-0.00610194, -0.08845797, -0.01725474, -0.18019788],
[-0.28517705, 0.22811398, -0.12600969, 0.18438187]]]],
dtype=float32)>,
<tf.Variable 'conv2d_3/bias:0' shape=(4,) dtype=float32, numpy=array([0., 0., 0., 0.], dtype=float32)>]

也可以通过下面的方式分别对权重和偏置进行访问 :

查看权重:

1
layer.kernel
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
<tf.Variable 'conv2d_3/kernel:0' shape=(3, 3, 3, 4) dtype=float32, numpy=
array([[[[ 0.26849422, 0.19578537, 0.26145306, -0.08229557],
[ 0.15586495, -0.16629952, -0.28540888, -0.07016225],
[-0.15017624, 0.3068613 , -0.07887723, 0.10123444]],

[[ 0.22432038, 0.0565404 , 0.12941647, 0.09906727],
[ 0.16842848, 0.12426731, 0.23842916, -0.1283393 ],
[ 0.04178131, 0.06155536, 0.26901218, -0.17057599]],

[[-0.1754628 , 0.22139618, 0.20181099, -0.0549061 ],
[ 0.2804089 , 0.09290037, -0.05881791, -0.18321809],
[-0.24344192, 0.30082038, 0.15440792, -0.02978626]]],


[[[-0.09762125, 0.24588814, -0.05257043, -0.07488932],
[ 0.08860514, 0.29339835, -0.06584421, 0.10579816],
[-0.25776446, -0.21827325, -0.18117602, 0.15594906]],

[[ 0.16299537, 0.04286107, -0.25221378, 0.06759065],
[-0.24693958, -0.2568909 , -0.077087 , 0.03058854],
[ 0.21647856, 0.16148275, -0.0668918 , 0.22095159]],

[[-0.2938962 , -0.24901226, 0.06835467, -0.13457522],
[ 0.22625688, 0.00778407, 0.24120173, -0.0922821 ],
[ 0.21996793, -0.30792752, -0.12364005, 0.23726341]]],


[[[ 0.02748409, -0.17573136, -0.29806593, -0.18748367],
[-0.09907596, 0.02415624, 0.26827005, -0.27712965],
[-0.112516 , -0.10590065, -0.25308898, 0.2885895 ]],

[[-0.17336473, -0.18374749, -0.25417763, -0.14162418],
[ 0.21973309, 0.30036303, 0.23560277, 0.25980887],
[-0.16107993, -0.10360201, -0.16716456, -0.04651383]],

[[ 0.21558437, -0.06225148, 0.0479238 , -0.07939483],
[-0.00610194, -0.08845797, -0.01725474, -0.18019788],
[-0.28517705, 0.22811398, -0.12600969, 0.18438187]]]],
dtype=float32)>

查看偏置:

1
layer.bias
1
<tf.Variable 'conv2d_3/bias:0' shape=(4,) dtype=float32, numpy=array([0., 0., 0., 0.], dtype=float32)>

总结

两种方法都能实现卷积层,前者是tf的,后者是其高层API,,即Keras中的。