Tensorflow学习: Slim tutorial

因为在写ResNet时候用到了Slim所以打算拿出时间将这个库写写。好久没时间锻炼一下翻译了。
时间仓促,并为打算作为教程,里面不乏自己的胡言乱语,一知半解,还望批评指正!
参考链接(英文):https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim

Tensorflow-Slim

TF-Slim是一个TF中重要性不算很重要的一个包,它多用于来定义,训练和评估复杂的模型。TF-slim的内容包括一些纯粹的TF内容,当然也包含其他的框架,例如tf.contrilib.learn。

Usage(用法)

用法0:(官方文档)

import tensorflow.contrib.slim as slim

用法1:(代码里曾经出现过的方法)

import tensorflow as tf
slim = tf.contrib.slim

两种方法“应该”是等价的。

Why TF-Slim?(为什么是TF-Slim?)

TF-Slim这个包可以让网络的建立,训练已经评估变得简单。在ResNet代码中之所以使用就是因为动则上百层,所以使用Slim显而易见。

  • 允许用户在定义模型的时候,通过减少代码的引用变得更为紧凑。这些是通过运用 以及更多高水平的层数和变量来实现(英文为:argument scoping, high lecel layers and variables)。这些方法增加了代码的可读性和可维护性,减少了由于对参数值的粘贴和复制带来的错误,并且简化了超参数的调试程度。
  • 通过提供通常用到的正则项让模型变得更加简单。(也就是说这个slim肯定有正则项的相关功能)
  • 在Slim中几个常用的计算机视觉网络模型(VGG,AlexNet等)都已经包含在能,让用户随意选择。这些可以被当作黑盒子利用,当然也可以通过各种方式进行扩展,例如可以添加“multiple heads”到不同的内部层里面去。
  • Slim能够延伸复杂的模型,并且通过利用以前存在的模型检查点来训练自己的算法。(不知道什么鬼?)

What are the various components of TF-Slim?(TF-Slim里面各种各样的内容)

TF-Slim由各种独立的存在的成分组成。如下:(请稍安勿躁,细节方面会被详细介绍)

  • arg_scope: 提过了一个新的命名方式,允许用户对于在此参数范围内对于特定的操作定义默认的参数值
  • data: 包含TF-slim数据集的定义,数据提供,并行读入以及解码工具。
  • evaluation: 包含模型的评估
  • layers:包含对于用tensorflow模型建立的高级层
  • learning: 包含训练模型
  • losses: 包含通常所用的损失函数
  • metrics: 包含常用的苹果度量
  • nets: 包含时下比较流行的网络,例如VGG和Alex net
  • queues: 提供了上下文管理器,对于简单的安全的开启或者关闭QueueRunners
  • regularizers: 包含了权重正则项
  • variables: 对于变量的创建和操作提供了方便的封装系统

Defining Models(定义模型)

模型可以通过TF-Slim联合他的变量,层数以及范围进行简单的定义。如下:

变量

创建Variable在原本的TF里面(naive TF)通常要求提前定义值,例如服从高斯分布。更多的是,如果一个变量需要在特殊的设备上面,例如GPU,这个操作必须需要明确。为了减少代码对建立的要求,TF-slim提供了一系列的函数供用户使用,文件名为variables.py
例如:
需要在CPU上面创建一个叫做“weight”的变量,服从的分布为截断的正态分布,正则项为L2,代码为:

weights = slim.variable('weights',
                             shape=[10, 10, 3 , 3], #形态
                             #参数初始化
                             initializer=tf.truncated_normal_initializer(stddev=0.1),
                             #正则项选择
                             regularizer=slim.l2_regularizer(0.05),
                             device='/CPU:0') # 设备

注意到在原本的TF中,有两种类型的变量:常规变量和局部变量。具有重要意义的是常规变量,他们一旦被创建,就会被保存下来。什么意思呢?常规变量是存储在电脑盘上的,方便评估的时候应用。局部变量只是存在于会话中,不会被保存。一个非专业CS的学生认为其实留下有用的,加快运行速度。
TF-Slim能够区分各种变量是定义模型变量,即:代表模型的参数。模型变量在学习的时候能够被训练和调试,或者在评估预测时候能够被重载。例子包含创造的slim.fully_connected 和slim.conv2d 层。非模型变量是那些其他的变量,在学习和评估中被用到,但不会在推断预测中有要求。例如:global_step是在学习和苹果中用到的变量但是并不是模型中的一部分。
模型变量和常规变量能够被简单的创建和调用,例如:

# Model Variables
weights = slim.model_variable('weights',
                              shape=[10, 10, 3 , 3],
                              initializer=tf.truncated_normal_initializer(stddev=0.1),
                              regularizer=slim.l2_regularizer(0.05),
                              device='/CPU:0')
model_variables = slim.get_model_variables()

# Regular variables
my_var = slim.variable('my_var',
                       shape=[20, 1],
                       initializer=tf.zeros_initializer())
regular_variables_and_model_variables = slim.get_variables()

他们是怎样工作的呢?当你通过TF-slim层创建一个模型变量或者直接通过slim.model_variable函数创建,TF-slim会将变量添加到tf.GraphKeys.MODEL_VARIABLES中。但是如果你想让你自己创建层或者创建的变量也成为模型变量或者让TF-slim来管理怎么办呢? TF-slim提供了方便的函数来完成:

my_model_variable = CreateViaCustomCode()

# Letting TF-Slim know about the additional variable.
slim.add_model_variable(my_model_variable)

到这里,有一个疑问:我明确了模型变量,但是没有找到一个合适的解答告诉我‘regular variable’, ‘local variable’以及’model variable’的区别和联系。很多博客含糊其辞,我决定总结下我的看法:
首先要明确范围:
原文档中,如果用的是原生态的TF变量那么就只有regular和local变量,没有model变量。这里区分他们就看电脑需不需要将其保存。如果是model变量,那么一定是在TF-slim中了。它代表的是模型的参数,也就是我们模型中需要保存的网络设置(包括kernel size, 网络层数,已经各种层数中的卷积参数已经bias),但是像评估的参数如迭代步数,batch数目都不会存到模型里面去,那么他们就不是model变量。

Layers(层)

尽管TF的操作集合相当的深,神经网络的开发者册承认高层次的概念:“层”,“损失函数”,“度量”,和“网络”。一层,例如一个卷积层,一个全连接层对于TF简单的操作比较抽象,并且包含几个操作。更多的是,一个层通常有与之相关的变量,不象很多初级的操作。例如,神经网络中的卷积层有以下低级操作:

    1. 创建权重变量和偏差变量
    1. 对输入的层进行卷积操作
    1. 添加偏置到卷积结果
    1. 应用激活函数

用比较简单的TF代码如下,可能比较繁琐:(毕竟slim就是为了简单而生,可是这样也让我们这些半路出身的家伙开始找原有的Tutorial…)

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提供了几个方便的操作用来定义更为抽象的神经网络层。如下:

input = ...
net = slim.conv2d(input, 128, [3, 3], scope='conv1_1')

标注下: 其实大家心里明白,写个类然后调用个函数有啥了不起的,当年老子写C++的时候嫌麻烦不也是天天写头文件么。不吹逼,有人帮我们免费写,我们高兴的接受就好。这个代码跟原生态的TF对比不难发现,输入是图像,输出层数为128,卷积核为[3,3],名字就是conv1_1。一目了然,做个标注。
TF-Slim提供了几个标准,供用户建立神经网络来使用:
这里写图片描述
原谅我直接贴了照片,如果有需要最好的办法是Google,没有其他方法。The only choice is the best choice!
TF-Slim 也两个其他的操作repeat和stack,这个允许用户能够重复的实现相同的操作。例如,考虑一个简单的VGGnet,对此进行卷积和池化:

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')

这一下子就豁然开朗了!简单明了,debug也简单。直接repeat 和stack起来就够了。
当然,同志们开了不爽,简单重复的工作,明显有规律,直接for循环就够了,扯其他啥西瓜皮,上代码:

net = ...
for i in range(3):
  net = slim.conv2d(net, 256, [3, 3], scope='conv3_%d' % (i+1))
net = slim.max_pool2d(net, [2, 2], scope='pool2')

这里的源代码我认为有点儿错误,在第二行的时候很显然%d需要添加的,我做了修改。
看了上面爽点了吧? Naive,TF-Slim来了:

net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool2')

估计啊,slim里面的源代码跟上面的那个差不多,就是个for循环,关键是这样我们清晰了,slim了!
注意到,slim.repeat不但能够将相同的参数放置到同一行,并且也能将其”展开“,使得这些scopes能够通过重复次数(这里为3)将slim.conv2d这个操作添加到每一个scope里面去。更进一步的说,这些scopes的名字将会被命名为 ‘conv3/conv3_1’, ‘conv3/conv3_2’ and ‘conv3/conv3_3’.这样貌似暗示着随时可以调用。
上面讲了卷积层,下面来看全连接层。
更多的是, TF-Slim的 slim.stack操作允许调用者能够重复执行相同的惭怍运用不同的参数来创造一个堆或者层数的塔。slim.stack同时也能创造新的 tf.variable_scope。例如创建一个简单的多层感知机:

# 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')

Scopes(模块)

除了典型的TF中的模块(name_scope, variable_scope),TF-Slim也添加了一个新的模块就是arg_scope。这个新型的scope能够永讯宇哥用户明确一个或者多个操作。并且一系列的参数将会被传递到定义在arg_scope的操作中。例子解释这个功能:

net = slim.conv2d(inputs, 64, [11, 11], 4, padding='SAME',
                  weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                  weights_regularizer=slim.l2_regularizer(0.0005), scope='conv1')
net = slim.conv2d(net, 128, [11, 11], padding='VALID',
                  weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                  weights_regularizer=slim.l2_regularizer(0.0005), scope='conv2')
net = slim.conv2d(net, 256, [11, 11], padding='SAME',
                  weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                  weights_regularizer=slim.l2_regularizer(0.0005), scope='conv3')

明确地看到这三个卷积层共享很多相同的参数,例如初始化,正则项类型,核大小,其中两个有相同的扩充方式。这种代码可阅读性不强,并且包含了很多重复性数值,一个解决方法如下:

padding = 'SAME'
initializer = tf.truncated_normal_initializer(stddev=0.01)
regularizer = slim.l2_regularizer(0.0005)
net = slim.conv2d(inputs, 64, [11, 11], 4,
                  padding=padding,
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv1')
net = slim.conv2d(net, 128, [11, 11],
                  padding='VALID',
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv2')
net = slim.conv2d(net, 256, [11, 11],
                  padding=padding,
                  weights_initializer=initializer,
                  weights_regularizer=regularizer,
                  scope='conv3')

这个方法能够保证上面卷积层共享了很多参数,但是还是不能将代码简化,那么请arg_scope出场,

  with slim.arg_scope([slim.conv2d], padding='SAME',
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.01)
                      weights_regularizer=slim.l2_regularizer(0.0005)):
    net = slim.conv2d(inputs, 64, [11, 11], scope='conv1')
    net = slim.conv2d(net, 128, [11, 11], padding='VALID', scope='conv2')
    net = slim.conv2d(net, 256, [11, 11], scope='conv3')

如例子中所展示,利用arg_scope让代码简单,可维护性高。注意到尽管这些值在初始时进行了明确,他们却能够被重写,类似于类。在这个代码中就是第二层网络的padding方式被重写为“VALID”。
人们当然也可以套用arg_scope,在同一个scope中用多个操作,例如:

with slim.arg_scope([slim.conv2d, slim.fully_connected],
                      activation_fn=tf.nn.relu,
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.01),
                      weights_regularizer=slim.l2_regularizer(0.0005)):
  with slim.arg_scope([slim.conv2d], stride=1, padding='SAME'):
    net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID', scope='conv1')
    net = slim.conv2d(net, 256, [5, 5],
                      weights_initializer=tf.truncated_normal_initializer(stddev=0.03),
                      scope='conv2')
    net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc')

这个例子中,arg_scope应用相同的weights_initializer和 weights_regularizer参数在conv2d and fully_connected 实现。第二个scope中,只明确了conv2d的参数设置。
特别说明下,这里就是两层的一个重写问题。外层arg_scope默认了n个参数设置(对于卷积和全连接都有),第二个只有对conv2d有设置。
例如:第一层卷积网络padding对于内层的arg_scope进行了重写,第二层还是卷积对参数初始化的标准差进行了重写,这个涉及到外层的arg_scope,第三层是全连接层,只有外层arg_scope与之相关,所以支队万层的激活函数进行了重写,改为了None。

草草翻译学习,定有n多错误,欢迎大家进行指正批评。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值