tensorflow的操作符集合是十分广泛的,神经网络开发者通常会以更高层的概念,比如"layers", "losses", "metrics", and "networks"去考虑模型。一个层,比如卷积层、全连接层或bn层,要比一个单独的tensorflow操作符更抽象,并且通常会包含若干操作符。此外,和原始操作符不同,一个层经常(不总是)有一些与自己相关的变量(可调参数)。例如,在神经网络中,一个卷积层由许多底层操作符组成:
input = ...
with tf.name_scope('conv1_1') as scope:
kernel = tf.Variable(tf.truncated_normal([3, 3, 64, 128], dtype=tf.float32,
stddev=1e-1), name='weights')
conv = tf.nn.conv2d(input, kernel, [1, 1, 1, 1], padding='SAME')
biases = tf.Variable(tf.constant(0.0, shape=[128], dtype=tf.float32),
trainable=True, name='biases')
bias = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(bias, name=scope)
为了缓解重复这些代码,TF-Slim在更抽象的神经网络层的层面上提供了大量方便使用的操作符。比如,将上面的代码和TF-Slim响应的代码调用进行比较:
input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')
TF-Slim提供了标准接口用于组建神经网络,包括:
Layer | TF-Slim |
---|---|
BiasAdd | slim.bias_add |
BatchNorm | slim.batch_norm |
Conv2d | slim.conv2d |
Conv2dInPlane | slim.conv2d_in_plane |
Conv2dTranspose (Deconv) | slim.conv2d_transpose |
FullyConnected | slim.fully_connected |
AvgPool2D | slim.avg_pool2d |
Dropout | slim.dropout |
Flatten | slim.flatten |
MaxPool2D | slim.max_pool2d |
OneHotEncoding | slim.one_hot_encoding |
SeparableConv2 | slim.separable_conv2d |
UnitNorm | slim.unit_norm |
net = ...
net = slim.conv2d(net, 256, [3, 3], scope='conv3_1')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_2')
net = slim.conv2d(net, 256, [3, 3], scope='conv3_3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
一种减少这种代码重复的方法是使用for循环:
net = ...
for i in range(3):
net = slim.conv2d(net, 256, [3, 3], scope='conv3_' % (i+1))
net = slim.max_pool2d(net, [2, 2], scope='pool2')
若使用TF-Slim的repeat操作符,代码看起来会更简洁:
net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
slim.repeat不但可以在一行中使用相同的参数,而且还能智能地展开scope,即每个后续的slim.conv2d调用所对应的scope都会追加下划线及迭代数字。更具体地讲,上面代码的scope分别为 'conv3/conv3_1', 'conv3/conv3_2' and 'conv3/conv3_3'.
# Verbose way:
x = slim.fully_connected(x, 32, scope='fc/fc_1')
x = slim.fully_connected(x, 64, scope='fc/fc_2')
x = slim.fully_connected(x, 128, scope='fc/fc_3')
# Equivalent, TF-Slim way using slim.stack:
slim.stack(x, slim.fully_connected, [32, 64, 128], scope='fc')
在这个例子中,slim.stack调用slim.fully_connected
三次,前一个层的输出是下一层的输入。而每个网络层的输出通道数从32变到64,再到128. 同样,我们可以用stack简化一个多卷积层塔:
# Verbose way:
x = slim.conv2d(x, 32, [3, 3], scope='core/core_1')
x = slim.conv2d(x, 32, [1, 1], scope='core/core_2')
x = slim.conv2d(x, 64, [3, 3], scope='core/core_3')
x = slim.conv2d(x, 64, [1, 1], scope='core/core_4')
# Using stack:
slim.stack(x, slim.conv2d, [(32, [3, 3]), (32, [1, 1]), (64, [3, 3]), (64, [1, 1])], scope='core')