TensorFlow 中的 layers 模块提供用于深度学习的更高层次封装的 API,利用它我们可以轻松地构建模型,这一节我们就来看下这个模块的 API 的具体用法。
layers 模块的路径写法为 tf.layers,这个模块定义在 tensorflow/python/layers/layers.py,其官方文档地址为:https://www.tensorflow.org/api_docs/python/tf/layers,TensorFlow 版本为 1.5。
这里面提供了多个类和方法以供使用,下面我们分别予以介绍。
tf.layers 模块提供的方法有:
tf.layers.Input() 这个方法是用于输入数据的方法,其实类似于 tf.placeholder,相当于一个占位符的作用,当然也可以通过传入 tensor 参数来进行赋值。
Input(
shape=None,
batch_size=None,
name=None,
dtype=tf.float32,
sparse=False,
tensor=None
)
参数说明如下:
返回值: 返回一个包含历史 Meta Data 的 Tensor。
我们用一个实例来感受一下:
x = tf.layers.Input(shape=[32])
print(x)
y = tf.layers.dense(x, 16, activation=tf.nn.softmax)
print(y)
首先我们用 Input() 方法初始化了一个 placeholder,这时我们没有传入 tensor 参数,然后调用了 dense() 方法构建了一个全连接网络,激活函数使用 softmax,然后将二者输出,结果如下:
Tensor("input_layer_1:0", shape=(?, 32), dtype=float32)
Tensor("dense/Softmax:0", shape=(?, 16), dtype=float32)
这时我们发现,shape 它给我们做了转化,本来是 [32],结果它给转化成了 [?, 32],即第一维代表 batch_size,所以我们需要注意,在调用此方法的时候不需要去关心 batch_size 这一维。
如果我们在初始化的时候传入一个已有 Tensor,例如:
data = tf.constant([1, 2, 3])
x = tf.layers.Input(tensor=data)
print(x)
结果如下:
Tensor("Const:0", shape=(3,), dtype=int32)
可以看到它可以自动计算出其 shape 和 dtype。
此方法是批量标准化的方法,经过处理之后可以加速训练速度,其定义在 tensorflow/python/layers/normalization.py,论文可以参考:http://arxiv.org/abs/1502.03167"Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift"。
batch_normalization(
inputs,
axis=-1,
momentum=0.99,
epsilon=0.001,
center=True,
scale=True,
beta_initializer=tf.zeros_initializer(),
gamma_initializer=tf.ones_initializer(),
moving_mean_initializer=tf.zeros_initializer(),
moving_variance_initializer=tf.ones_initializer(),
beta_regularizer=None,
gamma_regularizer=None,
beta_constraint=None,
gamma_constraint=None,
training=False,
trainable=True,
name=None,
reuse=None,
renorm=False,
renorm_clipping=None,
renorm_momentum=0.99,
fused=None,
virtual_batch_size=None,
adjustment=None
)
参数说明如下:
最后的一些参数说明不够详尽,更详细的用法参考:https://www.tensorflow.org/api_docs/python/tf/layers/batch_normalization。
其用法很简单,在输入数据后面加一层 batch_normalization() 即可:
x = tf.layers.Input(shape=[32])
x = tf.layers.batch_normalization(x)
y = tf.layers.dense(x, 20)
dense,即全连接网络,layers 模块提供了一个 dense() 方法来实现此操作,定义在 tensorflow/python/layers/core.py 中,下面我们来说明一下它的用法。
dense(
inputs,
units,
activation=None,
use_bias=True,
kernel_initializer=None,
bias_initializer=tf.zeros_initializer(),
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
trainable=True,
name=None,
reuse=None
)
参数说明如下:
返回值: 全连接网络处理后的 Tensor。
下面我们用一个实例来感受一下它的用法:
x = tf.layers.Input(shape=[32])
print(x)
y1 = tf.layers.dense(x, 16, activation=tf.nn.relu)
print(y1)
y2 = tf.layers.dense(y1, 5, activation=tf.nn.sigmoid)
print(y2)
首先我们用 Input 定义了 [?, 32] 的输入数据,然后经过第一层全连接网络,此时指定了神经元个数为 16,激活函数为 relu,接着输出结果经过第二层全连接网络,此时指定了神经元个数为 5,激活函数为 sigmoid,最后输出,结果如下:
Tensor("input_layer_1:0", shape=(?, 32), dtype=float32)
Tensor("dense/Relu:0", shape=(?, 16), dtype=float32)
Tensor("dense_2/Sigmoid:0", shape=(?, 5), dtype=float32)
可以看到输出结果的最后一维度就等于神经元的个数,这是非常容易理解的。
convolution,即卷积,这里提供了多个卷积方法,如 conv1d()、conv2d()、conv3d(),分别代表一维、二维、三维卷积,另外还有 conv2d_transpose()、conv3d_transpose(),分别代表二维和三维反卷积,还有 separable_conv2d() 方法代表二维深度可分离卷积。它们定义在 tensorflow/python/layers/convolutional.py 中,其用法都是类似的,在这里以 conv2d() 方法为例进行说明。
conv2d(
inputs,
filters,
kernel_size,
strides=(1, 1),
padding='valid',
data_format='channels_last',
dilation_rate=(1, 1),
activation=None,
use_bias=True,
kernel_initializer=None,
bias_initializer=tf.zeros_initializer(),
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
trainable=True,
name=None,
reuse=None
)
参数说明如下:
返回值: 卷积后的 Tensor。
下面我们用实例感受一下它的用法:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=2, padding='same')
print(y)
这里我们首先声明了一个 [?, 20, 20, 3] 的输入 x,然后将其传给 conv2d() 方法,filters 设定为 6,即输出通道为 6,kernel_size 为 2,即卷积核大小为 2 x 2,padding 方式设置为 same,那么输出结果的宽高和原来一定是相同的,但是输出通道就变成了 6,结果如下:
Tensor("conv2d/BiasAdd:0", shape=(?, 20, 20, 6), dtype=float32)
但如果我们将 padding 方式不传入,使用默认的 valid 模式,代码改写如下:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=2)
print(y)
结果如下:
Tensor("conv2d/BiasAdd:0", shape=(?, 19, 19, 6), dtype=float32)
结果就变成了 [?, 19, 19, 6],这是因为步长默认为 1,卷积核大小为 2 x 2,所以得到的结果的高宽即为 (20 - (2 - 1)) x (20 - (2 - 1)) = 19 x 19。
当然卷积核我们也可以变换大小,传入一个列表形式:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=[2, 3])
print(y)
这时我们的卷积核大小变成了 2 x 3,即高为 2,宽为 3,结果就变成了 [?, 19, 18, 6],这是因为步长默认为 1,卷积核大小为 2 x 2,所以得到的结果的高宽即为 (20 - (2 - 1)) x (20 - (3 - 1)) = 19 x 18。
如果我们将步长也设置一下,也传入列表形式:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=[2, 3], strides=[2, 2])
print(y)
这时卷积核大小变成了 2 x 3,步长变成了 2 x 2,所以结果的高宽为 ceil(20 - (2- 1)) / 2 x ceil(20 - (3- 1)) / 2 = 10 x 9,得到的结果即为 [?, 10, 9, 6]。
运行结果如下:
Tensor("conv2d_4/BiasAdd:0", shape=(?, 10, 9, 6), dtype=float32)
另外我们还可以传入激活函数,或者禁用 bias 等操作,实例如下:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d(x, filters=6, kernel_size=2, activation=tf.nn.relu, use_bias=False)
print(y)
这样我们就将激活函数改成了 relu,同时禁用了 bias,运行结果如下:
Tensor("conv2d_5/Relu:0", shape=(?, 19, 19, 6), dtype=float32)
另外还有反卷积操作,反卷积顾名思义即卷积的反向操作,即输入卷积的结果,得到卷积前的结果,其参数用法是完全一样的,例如:
x = tf.layers.Input(shape=[20, 20, 3])
y = tf.layers.conv2d_transpose(x, filters=6, kernel_size=2, strides=2)
print(y)
例如此处输入的图像高宽为 20 x 20,经过卷积核为 2,步长为 2 的反卷积处理,得到的结果高宽就变为了 40 x 40,结果如下:
Tensor("conv2d_transpose/BiasAdd:0", shape=(?, 40, 40, 6), dtype=float32)
pooling,即池化,layers 模块提供了多个池化方法,这几个池化方法都是类似的,包括 max_pooling1d()、max_pooling2d()、max_pooling3d()、average_pooling1d()、average_pooling2d()、average_pooling3d(),分别代表一维二维三维最大和平均池化方法,它们都定义在 tensorflow/python/layers/pooling.py 中,这里以 max_pooling2d() 方法为例进行介绍。
max_pooling2d(
inputs,
pool_size,
strides,
padding='valid',
data_format='channels_last',
name=None
)
参数说明如下:
返回值: 经过池化处理后的 Tensor。
下面我们用一个实例来感受一下:
x = tf.layers.Input(shape=[20, 20, 3])
print(x)
y = tf.layers.conv2d(x, filters=6, kernel_size=3, padding='same')
print(y)
p = tf.layers.max_pooling2d(y, pool_size=2, strides=2)
print(p)
在这里我们首先指定了输入 x,shape 为 [20, 20, 3],然后对其进行了卷积计算,然后池化,最后得到池化后的结果。结果如下:
Tensor("input_layer_1:0", shape=(?, 20, 20, 3), dtype=float32)
Tensor("conv2d/BiasAdd:0", shape=(?, 20, 20, 6), dtype=float32)
Tensor("max_pooling2d/MaxPool:0", shape=(?, 10, 10, 6), dtype=float32)
可以看到这里池化窗口用的是 2,步长也是 2,所以原本卷积后 shape 为 [?, 20, 20, 6] 的结果就变成了 [?, 10, 10, 6]。
dropout 是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃,可以用来防止过拟合,layers 模块中提供了 dropout() 方法来实现这一操作,定义在 tensorflow/python/layers/core.py。下面我们来说明一下它的用法。
dropout(
inputs,
rate=0.5,
noise_shape=None,
seed=None,
training=False,
name=None
)
参数说明如下:
返回: 经过 dropout 层之后的 Tensor。
我们用一个实例来感受一下:
x = tf.layers.Input(shape=[32])
print(x)
y = tf.layers.dense(x, 16, activation=tf.nn.softmax)
print(y)
d = tf.layers.dropout(y, rate=0.2)
print(d)
运行结果:
Tensor("input_layer_1:0", shape=(?, 32), dtype=float32)
Tensor("dense/Softmax:0", shape=(?, 16), dtype=float32)
Tensor("dropout/Identity:0", shape=(?, 16), dtype=float32)
在这里我们使用 dropout() 方法实现了 droput 操作,并制定 dropout rate 为 0.2,最后输出结果的 shape 和原来是一致的。
flatten() 方法可以对 Tensor 进行展平操作,定义在 tensorflow/python/layers/core.py。
flatten(
inputs,
name=None
)
参数说明如下:
返回结果: 展平后的 Tensor。
下面我们用一个实例来感受一下:
x = tf.layers.Input(shape=[5, 6])
print(x)
y = tf.layers.flatten(x)
print(y)
运行结果:
Tensor("input_layer_1:0", shape=(?, 5, 6), dtype=float32)
Tensor("flatten/Reshape:0", shape=(?, 30), dtype=float32)
这里输入数据的 shape 为 [?, 5, 6],经过 flatten 层之后,就会变成 [?, 30],即将除了第一维的数据维度相乘,对原 Tensor 进行展平。
假如第一维是一个已知的数的话,它依然还是同样的处理,示例如下:
x = tf.placeholder(shape=[5, 6, 2], dtype=tf.float32)
print(x)
y = tf.layers.flatten(x)
print(y)
结果如下:
Tensor("Placeholder:0", shape=(5, 6, 2), dtype=float32)
Tensor("flatten_2/Reshape:0", shape=(5, 12), dtype=float32)
除了如上的方法,其实我们还可以直接使用类来进行操作,实际上看方法的实现就是实例化了其对应的类,下面我们首先说明一下有哪些类可以使用:
其实类这些类都和上文介绍的方法是一一对应的,关于它的用法我们可以在方法的源码实现里面找到,下面我们以 Dense 类的用法为例来说明一下这些类的具体调用方法:
x = tf.layers.Input(shape=[32])
dense = tf.layers.Dense(16, activation=tf.nn.relu)
y = dense.apply(x)
print(y)
这里我们初始化了一个 Dense 类,它只接受一个必须参数,那就是 units,相比 dense() 方法来说它没有了 inputs,因此这个实例化的类和 inputs 是无关的,这样就相当于创建了一个 16 个神经元的全连接层。
但创建了不调用是没有用的,我们要将这个层构建到网络之中,需要调用它的 apply() 方法,而 apply() 方法就接收 inputs 这个参数,返回计算结果,运行结果如下:
Tensor("dense/Relu:0", shape=(?, 16), dtype=float32)
因此我们可以发现,这些类在初始化的时候实际上是比其对应的方法少了 inputs 参数,其他的参数都是完全一致的,另外需要调用 apply() 方法才可以应用该层并将其构建到模型中。
所以其他的类的用法在此就不一一赘述了,初始化的参数可以类比其对应的方法,实例化类之后,调用 apply() 方法,可以达到同样的构建模型的效果。
以上便是 TensorFlow layers 模块的详细用法说明,更加详细的用法可以参考官方文档:https://www.tensorflow.org/api_docs/python/tf/layers。本节代码地址:https://github.com/AIDeepLearning/TensorFlowLayers。
崔庆才
静觅博客博主