在TensorFlow中,既可以通过自定义权值的底层实现方式搭建神经网络,也可以直接调用现成的卷积层类的高层方式快速搭建复杂网络。我们主要以2D卷积为例,介绍如何实现卷积神经网络层。
1. 自定义权值
在TensorFlow中,通过tf.nn.conv2d
函数可以方便地实现2D卷积运算。tf.nn.conv2d
基于输入
X
:
[
b
,
h
,
w
,
c
i
n
]
\boldsymbol X:[b,h,w,c_{in}]
X:[b,h,w,cin]和卷积核
W
:
[
k
,
k
,
c
i
n
,
c
o
u
t
]
\boldsymbol W:[k,k,c_{in},c_{out}]
W:[k,k,cin,cout]进行卷积运算,得到输出
O
:
[
b
,
h
′
,
w
′
,
c
o
u
t
]
\boldsymbol O:[b,h',w',c_{out}]
O:[b,h′,w′,cout],其中
c
i
n
c_{in}
cin表示输入通道数,
c
o
u
t
c_{out}
cout表示卷积核的数量,也是输出特征图的通道数。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
# 需要根据[k,k,cin,cout]格式创建w张量,4个3*3大小的卷积核
w = tf.random.normal([3, 3, 3, 4])
# 步长为1,padding为0
out = tf.nn.conv2d(x, w, strides=1, padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
# 输出张量的shape
print('out.shape=', out.shape)
运行结果如下图所示:
其中padding参数的设置格式为:
padding=[[0, 0], [上, 下], [左, 右], [0, 0]]
例如,上下左右各填充一个单位,则padding参数设置为
[
[
0
,
0
]
,
[
1
,
1
]
,
[
1
,
1
]
,
[
0
,
0
]
]
[[0,0],[1,1],[1,1],[0,0]]
[[0,0],[1,1],[1,1],[0,0]],实现如下:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
# 需要根据[k,k,cin,cout]格式创建w张量,4个3*3大小的卷积核
w = tf.random.normal([3, 3, 3, 4])
# 步长为1,padding为1
out = tf.nn.conv2d(x, w, strides=1, padding=[[0, 0], [1, 1], [1, 1], [0, 0]])
# 输出张量的shape
print('out.shape=', out.shape)
运行结果如下图所示:
特别地,通过设置参数padding=‘SAME’
、strides=1
可以直接得到输入、输出同大小的卷积层,其中padding的具体数量由TensorFlow自动计算并完成填充操作。例如:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
# 需要根据[k,k,cin,cout]格式创建w张量,4个3*3大小的卷积核
w = tf.random.normal([3, 3, 3, 4])
# 步长为1,padding设置为输入、输出同大小
# 需要注意的是,padding=same只有在strides=1时才是同大小
out = tf.nn.conv2d(x, w, strides=1, padding='SAME')
# 输出张量的shape
print('out.shape=', out.shape)
运行结果如下图所示:
当s<1时,设置padding=‘SAME’
将使得输出高、宽将成
1
s
\frac{1}{s}
s1倍地减少。例如:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
# 需要根据[k,k,cin,cout]格式创建w张量,4个3*3大小的卷积核
w = tf.random.normal([3, 3, 3, 4])
# 高宽先padding成可以整除3的最小整数6,然后6按3倍减少,得到2*2
out = tf.nn.conv2d(x, w, strides=3, padding='SAME')
# 输出张量的shape
print('out.shape=', out.shape)
运行结果如下图所示:
卷积神经网络层与全连接层一样,可以设置网络带偏置向量。tf.nn.conv2d
函数是没有实现偏置向量计算的,添加偏置只需要手动累加偏置向量即可。例如:
# 根据[out]格式创建偏置向量
b = tf.zeros([4])
# 在卷积输出上叠加偏置向量,它会自动broadcasting为[b,h',w',cout]
out = out + b
2. 卷积层类
通过卷积层类layers.Conv2D可以不需要手动定义卷积核
W
\boldsymbol W
W和偏置
b
\boldsymbol b
b张量,直接调用类实例即可完成卷积层的向前计算,实现更加高层和快捷。在TensorFlow中,API的命名有一定的规律,首字母大写的对象一般表示类,全部小写的一般表示函数,如layers.Conv2D表示卷积层类,nn.conv2d表示卷积运算函数。使用类方式会(在创建类时或build时)自动创建需要的权值张量和偏置向量等,用户不需要记忆卷积核张量的定义格式,因此使用起来更简单方便,但是灵活性也略低。函数方式的接口需要自行定义权值和偏置等,更加灵活和底层。
在新建卷积层类时,只需要指定卷积核数量参数filters,卷积核大小kernel_size,步长strides,填充padding等即可。如下创建了4个
3
×
3
3×3
3×3大小的卷积核的卷积层,步长为1,padding方案为‘SAME’:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
layer = layers.Conv2D(4, kernel_size=3, strides=1, padding='SAME')
out = layer(x)
print(out.shape)
运行结果如下图所示:
果卷积核高宽不等,步长行列方向不等,此时需要将kernel_size参数设计为tuple格式
(
k
h
,
k
w
)
(k_h,k_w)
(kh,kw),strides参数设计为
(
s
h
,
s
w
)
(s_h,s_w)
(sh,sw)。如下创建4个
3
×
4
3×4
3×4大小的卷积核,竖直方向移动步长
s
h
=
2
s_h=2
sh=2,水平方向移动步长为
s
w
=
1
s_w=1
sw=1:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
layer = layers.Conv2D(4, kernel_size=(3, 4), strides=(2, 1), padding='SAME')
out = layer(x)
print(out.shape)
运行结果如下图所示:
创建完成后,通过调用实例即可完成向前计算,例如:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
layer = layers.Conv2D(4, kernel_size=3, strides=1, padding='SAME')
out = layer(x)
print(out.shape)
在类Conv2D中,保存了卷积核张量
W
\boldsymbol W
W和偏置
b
\boldsymbol b
b,可以通过类成员trainable_variables直接返回
W
\boldsymbol W
W和
b
\boldsymbol b
b的列表,例如:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential, losses, optimizers, datasets
x = tf.random.normal([2, 5, 5, 3]) # 模拟输入,3通道,高宽为5
layer = layers.Conv2D(4, kernel_size=3, strides=1, padding='SAME')
out = layer(x)
print(out.shape)
# 输出所有待优化张量列表
print(layer.trainable_variables)
运行结果如下所示:
(2, 5, 5, 4)
[<tf.Variable 'conv2d/kernel:0' shape=(3, 3, 3, 4) dtype=float32, numpy=
array([[[[-0.06861021, 0.15635735, 0.23594084, 0.08823672],
[-0.07579896, 0.28215882, -0.07285108, 0.15759888],
[-0.04988965, -0.21231258, -0.08478491, 0.16820547]],
…,
dtype=float32)>, <tf.Variable 'conv2d/bias:0' shape=(4,) dtype=float32, numpy=array([0., 0., 0., 0.], dtype=float32)>]
通过调用`layer.trainable_variables可以返回Conv2D类维护的 W \boldsymbol W W和 b \boldsymbol b b张量,这个类成员在获取网络层的待优化变量时非常有用。也可以直接调用类实例layer.kernel、layer.bias名访问 W \boldsymbol W W和 b \boldsymbol b b张量。