参考 基于slim的残差网络 - 云+社区 - 腾讯云

目录

1、TF-Slim 安装与配置和API列表

1.1、安装与配置

1、下载models模块

2、验证slim库

3、目录结构

4、下载数据集

1.2、API列表

1、Layers

2、Scopes

3、指定VGG的层

4、训练模型

5、损失

6、训练回路

7、恢复部分模型

8、用不同的变量名加载模型

9、针对不同任务对模型进行微调

10、评估模型

11、Evaluation Loop

2、采用 TF-Slim 创建第一个神经网络

2.1、定义回归模型

2.2、创建模型/查看模型结构

2.3、随机生成 1d 回归数据

2.4、拟合模型

2.5、采用多个 loss 函数训练模型

2.6、加载保存的训练进行预测

2.7、测试集上计算评估度量 metrics

3、采用 TF-Slim 读取数据

3.1、Dataset

3.2、DatasetDataProvider

3.3、示例:Flowers 数据集

4、CNN 训练

4.1、模型定义

4.2、对随机生成图片应用模型

4.3、在 Flowers 数据集训练模型

4.4、评估度量 metrics

5、采用预训练模型

5.1、下载 Inception V1 断点文件

5.2、应用 Inception V1 预训练模型

5.3、下载 VGG-16 断点文件

5.4、应用 VGG-16 预训练模型

5.5 、在新数据集上 fine-tune 模型

5.6、应用新数据集的 fine-tune 模型


TF-Slim可以使建立、训练和评估神经网络更加简单。

  • 允许用户通过减少模板代码使得模型更加简洁。这个可以通过使用argument scoping和大量的高层layers、variables来实现;
  • 通过使用常用的正则化( regularizers)使得建立模型更加简单;
  • 一些广泛使用的计算机视觉相关的模型(比如VGG,AlexNet)已经在slim中定义好了,用户可以很方便的使用;这些既可以当成黑盒使用,也可以被扩展使用,比如添加一些“multiple heads”到不同的内部的层;
  • Slim使得扩展复杂模型变得容易,可以使用已经存在的模型的checkpoints来开始训练算法。

1、TF-Slim 安装与配置和API列表

1.1、安装与配置

1、下载models模块

到GitHub - tensorflow/models: Models and examples built with TensorFlow上将models模块下载下载,当然也可以用git下载,命令为

git clone https://github.com/tensorflow/models.git

下载完以后,cd models/research/slim/,slim目录下就是我们要用到的模块。

2、验证slim库

在使用slim库之前,先测试本地tensorflow版本是否集成slim模块。先来验证tf.contrib.slim模块是否有效,执行以下命令:

python -c "import tensorflow.contrib.slim as slim;eval = slim.evaluation.evaluate_once"

没有错误即slim可以工作。

再验证刚下载的模块是否正确,执行以下命令,

cd models/research/slim/

python -c "from nets import cifarnet;mynet = cifarnet.cifarnet"

没有错误即可。

3、目录结构

slim目录下的文件如下图所示,

其主要文件夹和文件用途如下表所示

文件夹或文件名 用途

datasets

定义一些数据集,默认预定义好了4个数据集,分别为MNIST,CIFAR-10,Flowers,ImageNet。如果需要训练自己的数据,则必须在该文件夹下定义,模仿其他数据集的定义即可
nets 定义一些常用网络结构,如AlexNet、VGG16、VGG19、ResNet、Inception等
scripts 一些训练示例脚本,只能在支持shell的系统下使用
preprocessing 在模型读取图片之前,先进行图像的预处理和数据增强,这个文件夹下针对不同的网络,分别定义了其预处理方法
download_and_convert_data.py 下载并转换数据集格式的入口代码
train_image_classifier.py 训练模型的入口代码
eval_image_classifier.py 验证模型性能的入口代码

4、下载数据集

slim集成的数据集如下:

下面就以Flowers数据集为例,用slim下载并训练该数据集。Flowers数据集共有2500张训练图片和2500张测试图片,共5个分类,分别是菊(daisy)、蒲公英(dandelion)、玫瑰(roses)、向日葵(sunflowers)、郁金香(tulips)。

首先下载并转换数据集,执行以下命令,

python download_and_convert_data.py \--dataset_name=flowers --dataset_dir=images_data/flowers

下载完以后,目录内容如下,

可以看到,数据集被转成tfrecord格式了。tfrecord格式是tensorflow推荐的数据集格式,将原始文件转成tensorflow格式,在运行中通过多线程读取,可以减小主线程训练负担,让训练更高效。

也可以从这个网址下载原图数据集:

http://download.tensorflow.org/example_images/flower_photos.tgz

下载后解压,目录结构如下所示,

每个子文件夹下是对应品种的图片,如daisy,

1.2、API列表

TF-Slim由几个独立存在的组件组成,主要包括以下几个:

  • arg_scope:提供一个新的作用域(scope),称为arg_scope,在该作用域(scope)中,用户可以定义一些默认的参数,用于特定的操作;
  • data:包含TF-Slim的dataset定义,data providers,parallel_reader,和 decoding utilities;
  • evaluation:包含用于模型评估的常规函数;
  • layers:包含用于建立模型的高级layers;
  • learning:包含一些用于训练模型的常规函数;
  • losses:包含一些用于loss function的函数;
  • metrics:包含一些热门的评价标准;
  • nets:包含一些热门的网络定义,如VGG,AlexNet等模型;
  • queues:提供一个内容管理者,使得可以很容易、很安全地启动和关闭QueueRunners;
  • regularizers:包含权重正则化;
  • variables:提供一个方便的封装,用于变量创建和使用。

1、Layers

tensorflow的操作符集合是十分广泛的,神经网络开发者通常会以更高层的概念,比如"layers", "losses", "metrics", and "networks"去考虑模型。一个层,比如卷积层、全连接层或bn层,要比一个单独的tensorflow操作符更抽象,并且通常会包含若干操作符。此外,和原始操作符不同,一个层经常(不总是)有一些与自己相关的变量(可调参数)。例如,在神经网络中,一个卷积层由许多底层操作符组成:

  1. 创建权重、偏置变量
  2. 将来自上一层的数据和权值进行卷积
  3. 在卷积结果上加上偏置
  4. 应用激活函数

如果只用普通的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')

完整参数:

  • inputs                        是指需要做卷积的输入图像
  • num_outputs             指定卷积核的个数(就是filter的个数)
  • kernel_size                用于指定卷积核的维度(卷积核的宽度,卷积核的高度)
  • stride                         为卷积时在图像每一维的步长
  • padding                     为padding的方式选择,VALID或者SAME
  • data_format              是用于指定输入的input的格式
  • rate                           这个参数不是太理解,而且tf.nn.conv2d中也没有,对于使用atrous convolution的膨胀率
  • activation_fn             用于激活函数的指定,默认的为ReLU函数
  • normalizer_fn           用于指定正则化函数
  • normalizer_params  用于指定正则化函数的参数
  • weights_initializer     用于指定权重的初始化程序
  • weights_regularizer  为权重可选的正则化程序
  • biases_initializer       用于指定biase的初始化程序
  • biases_regularizer    biases可选的正则化程序
  • reuse                        指定是否共享层或者和变量
  • variable_collections  指定所有变量的集合列表或者字典
  • outputs_collections   指定输出被添加的集合
  • trainable                    卷积层的参数是否可被训练
  • scope                        共享变量所指的variable_scope

slim.fully_connected()

  • 前两个参数分别为网络输入、输出的神经元数量。

slim.max_pool2d()

  • 前两个参数分别为网络和池化核的大小。

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

TF-Slim也提供了两个元运算符----repeat和stack,允许用户可以重复地使用相同的运算符。例如,VGG网络的一个片段,这个网络在两个池化层之间就有许多卷积层的堆叠:

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'。除此之外,TF-Slim的slim.stack操作符允许调用者用不同的参数重复使用相同的操作符是创建一个stack或网络层塔。slim.stack也会为每个创建的操作符生成一个新的scope。例如,下面是一个简单的方法去创建MLP:

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

slim.flatten(inputs,outputs_collections=None,scope=None) 

参数:

  • inputs: 一个大小张量[batch_size,…]。
  • outputs_collections: 用于添加输出的集合。
  • scope: name_scope的可选作用域。
test=([[[1,2,3],[4,5,6],[7,8,9]],[[10,11,12],[13,14,15],[16,17,27]],[[18,19,20],[21,22,23],[24,25,26]]])    #shape is (3,3,3)
test=slim.fatten(test)
test.eval()
array([[ 1,  2,  3, ...,  7,  8,  9],[10, 11, 12, ..., 16, 17, 27],[18, 19, 20, ..., 24, 25, 26]], dtype=int32)
test.get_shape()
TensorShape([Dimension(3), Dimension(9)])  #(3,9)

2、Scopes

除了tensorflow中自带的scope机制类型(name_scope, variable_scope)外, TF-Slim添加了一种叫做arg_scope的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设置,而且这三个卷积层都有相同的weights_initializer(权值初始化器)和weight_regularizer(权值正则化器)。这段代码很难读,且包含了很多重复的参数值。一种解决办法是用变量指定默认值:

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使代码更简洁且易于维护。注意,在arg_scope中被指定的参数值,也可以在局部位置进行覆盖。比如,padding参数设置为'SAME', 而第二个卷积层仍然可以通过把它设为'VALID'而覆盖掉arg_scope中的默认设置。我们可以嵌套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对处于它的scope中的conv2d和fully_connected操作层应用相同的weights_initializer andweights_regularizer参数。在第二个arg_scope中,默认参数只是在conv2d中指定。

3、指定VGG的层

通过整合TF-Slim的变量、操作符和scope,我们可以用寥寥几行代码写一个通常非常复杂的网络。例如,完整的VGG结构只需要用下面的一小段代码定义(ResNet也可以简单的实现):

def vgg16(inputs):with slim.arg_scope([slim.conv2d, slim.fully_connected],activation_fn=tf.nn.relu,weights_initializer=tf.truncated_normal_initializer(0.0, 0.01),weights_regularizer=slim.l2_regularizer(0.0005)):net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')net = slim.max_pool2d(net, [2, 2], scope='pool1')net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')net = slim.max_pool2d(net, [2, 2], scope='pool2')net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')net = slim.max_pool2d(net, [2, 2], scope='pool3')net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')net = slim.max_pool2d(net, [2, 2], scope='pool4')net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')net = slim.max_pool2d(net, [2, 2], scope='pool5')net = slim.fully_connected(net, 4096, scope='fc6')net = slim.dropout(net, 0.5, scope='dropout6')net = slim.fully_connected(net, 4096, scope='fc7')net = slim.dropout(net, 0.5, scope='dropout7')net = slim.fully_connected(net, 1000, activation_fn=None, scope='fc8')return net

4、训练模型

训练一个tensorflow模型,需要一个网络模型,一个损失函数,梯度计算方式和用于迭代计算模型权重的训练过程。TF-Slim提供了损失函数,同时也提供了一批运行训练和评估模型的帮助函数。

4.1. 梯度相关操作:

  • add_gradients_summaries:为梯度添加summary。
  • clip_gradient_norms:当梯度大于最大值时,对梯度进行裁剪。
  • multiply_gradients:使用预先定义好的参数 乘以 对应的梯度值。

4.2. create_train_op

  • 作用:通过loss、optimizer等创建train_op,用于训练。
  • 其他功能:
    • 指定除梯度更新操作外的 需要进行的操作。(默认放在tf.GraphKeys.UPDATE_OPS中)
    • 对梯度进行变换。使用了上述的clip_gradient_normsmultiply_gradients
    • 设置是否需要保存所有梯度值。
    • 指定需要训练的参数(这个操作一般用于自己重写train_step的情况)。 比如,在finetune时,首先固定部分权重不变,就可以使用这个实现。
    • 关于 global_step :在optimizer中添加 global_step 参数,在进行minimize操作后,会自动对 global_step 加1。
  • 函数声明
def create_train_op(total_loss,optimizer,global_step=_USE_GLOBAL_STEP,update_ops=None,  # 每个每次调用train_step后,都会进行的操作,默认使用 tf.GraphKeys.UPDATE_OPSvariables_to_train=None,  # 用于指定需要训练的参数,默认使用tf.GraphKeys.TRAINABLE_VARIABLESclip_gradient_norm=0,  # 梯度范围summarize_gradients=False,  # 是否记录梯度数据gate_gradients=tf_optimizer.Optimizer.GATE_OP,  aggregation_method=None,colocate_gradients_with_ops=False,gradient_multipliers=None,check_numerics=True):

4.3. train_step

  • 作用:默认进行一次梯度下降的函数,并指定训练是否停止。train函数会多次调用该函数。
  • 该函数可以自己重写,要求输入四个参数sess, train_op, global_step, train_step_kwargs(除了sess外,其他三个参数都可以在train函数中指定),返回 total_loss, should_stop
  • 默认函数的其他功能:
    • 通过train_step_kwargs['should_log']确定是否需要输出日志信息。
    • 通过train_step_kwargs['should_stop']确定训练是否要停止。
  • 函数声明
def train_step(sess, train_op, global_step, train_step_kwargs):

4.4. train

  • 作用:管理整个训练过程。
  • 功能介绍:
    • 模型初始化:主要包括 init_op init_feed_dict local_init_op init_fn,使用supervisor具体实现,用于模型参数初始化。
    • 判断初始化情况:主要通过 ready_op,使用supervisor具体实现,判断模型参数是否全完初始化完毕。
    • summary相关:主要包括 logdir summary_op save_summaries_secs summary_writer global_step,使用supervisor具体实现。
    • saver相关:主要包括 logdir saver save_interval_secs global_step,使用supervisor具体实现。
  • 其他:
    • summary和saver的路径都是logdir。
    • train_op中就包含了tf.GraphKeys.UPDATE_OPS的操作。
    • train_step_kwargs 的默认值中包含了:should_stop should_log should_trace,用于默认的train_step方法。
    • log指的是使用logging.log,summary指的是使用summary_writer,trace指的是使用Chrome trace format 同时使用logging.logsummary_writer
  • 函数声明
def train(train_op,  # create_train_op结果logdir,  # 保存summary和save的结果train_step_fn=train_step,  # 每一步梯度下降要调用的函数,默认使用train_step,一般我都自己重写train_step_kwargs=_USE_DEFAULT,  # 要传递到train_step函数中的参数log_every_n_steps=1,  # console输出,用于train_stepgraph=None,master='',is_chief=True,global_step=None,number_of_steps=None,  # 最多运行步数,用于train_stepinit_op=_USE_DEFAULT,init_feed_dict=None,local_init_op=_USE_DEFAULT,init_fn=None,ready_op=_USE_DEFAULT,summary_op=_USE_DEFAULT,save_summaries_secs=600,  # 默认600秒保存一次summarysummary_writer=_USE_DEFAULT,startup_delay_steps=0,saver=None,save_interval_secs=600,  # 默认600保存一次ckptsync_optimizer=None,session_config=None,session_wrapper=None,trace_every_n_steps=None,  # 用于train_step,好像是chrome格式的日志ignore_live_threads=False):

4.5.官方实例介绍

  • 在这里的注释中,有一些基本实例。
  • 使用slim.learning的简单脚本。
# 导入数据,建立模型
images, labels = LoadData(...)
predictions = MyModel(images)# 定义loss与optimizer
slim.losses.log_loss(predictions, labels)
total_loss = slim.losses.get_total_loss()
optimizer = tf.train.MomentumOptimizer(FLAGS.learning_rate, FLAGS.momentum)# 创建 train_op
train_op = slim.learning.create_train_op(total_loss, optimizer)# 开始训练,train_op和my_log_dir是必须要有的参数
slim.learning.train(train_op, my_log_dir)
  • 创建train_op的简单实例
# 主要作用:计算loss,更新梯度,返回loss。# 创建train_op,并进行梯度裁剪(设置梯度最大值)
train_op = slim.learning.create_train_op(total_loss,optimizer,clip_gradient_norm=4)# 创建 train_op,并对部分梯度进行自定义设置
gradient_multipliers = {
'conv0/weights': 1.2,
'fc8/weights': 3.4,
}
train_op = slim.learning.create_train_op(total_loss,optimizer,gradient_multipliers=gradient_multipliers)
  • 添加额外操作(除了梯度更新外)
# 不进行任何更新操作
train_op = slim.learning.create_train_op(total_loss,optimizer,update_ops=[])# 使用其他collections,而不是默认
train_op = slim.learning.create_train_op(total_loss,optimizer,update_ops=my_other_update_ops)# 一般使用方法
# 将需要的ops添加到 tf.GraphKeys.UPDATE_OPS 中
tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, my_update0)
tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, my_update1)# 以下两种调用方法效果相同
train_op = slim.learning.create_train_op(total_loss,optimizer)
train_op = slim.learning.create_train_op(total_loss,optimizer,update_ops=tf.get_collection(tf.GraphKeys.UPDATE_OPS))
  • 使用chekpoint文件初始化模型
# 新建train_op
train_op = slim.learning.create_train_op(total_loss, optimizer)# 指定checkpoint文件,并创建相关方法
checkpoint_path = '/path/to/old_model_checkpoint'
variables_to_restore = slim.get_model_variables()
init_assign_op, init_feed_dict = slim.assign_from_checkpoint(checkpoint_path, variables_to_restore)# 新建初始化方法
def InitAssignFn(sess):sess.run(init_assign_op, init_feed_dict)# 训练时使用
# 注意,init_fn 方法在init_op后调用
slim.learning.train(train_op, my_log_dir, init_fn=InitAssignFn)

PS:由于目前用不到集群,所以与分布式相关的内容就略过。

  • 初始化参数:

    • 功能:对网络中所有参数进行初始化,方便后续使用。
    • 包括 使用initializer 与 checkpoint file初始化。
  • 梯度下降的实现:
    • 功能:确定loss,建立optimizer,构建train_op,并运行该train_op。
  • summary:
    • 功能:记录训练过程中的中间变量,便于后续分析(如使用tensorboard)。
    • 需要输入一个 summary_op。
  • saver:保存当前模型结构与参数数值。
  • 其他参数更新:如Batch_Norm层、一些metrics的更新等。
  • log:通过logging输出训练信息。
  • trace:通过log和summary输出训练信息。
  • 每次learning前,会在先restore在logdir的checkpoint file。相关具体信息要查看 Supervisor源码
  • 对于dataset:
    • 看到的实例中,使用默认train_step函数的,都会用到slim中的data_provider.pydataset_data_provider.py
    • 但我想用tf.data相关API……
  • 自己搞了个demo:
    • 使用tf.data.Dataset建立数据源。
    • 重写train_step函数,在该函数中中读取tf.data.Dataset数据。
    • 最终调用train进行训练,感受了一下。
    • 感觉不太好,主要原因如下:
      • 之前习惯基于epoch进行训练,每个epoch后进行save和summary操作,但learning中都是基于一定时间间隔进行save和summary操作。
      • 可以在train_step函数中定制上述功能,但这样做没有体现出learning的优势,代码量与不使用learning差不多。
      • 添加metrics、learning_rate_decay等功能,稍微有点麻烦。
      • 按照目前的理解,metrics相关操作,都是在evaluation中进行,但如果我想看到训练过程中的metrics怎么做才比较方便呢?
  • 理解saver的具体实现细节(自动保存机制是是每隔一段时间保存model,那如果后面的model没有之前的好,要如何实现呢)
  • 如何将learningmetrics配合使用?
  • 方便地实现根据 epoch 进行summary。

5、损失

损失函数定义了我们想最小化的量。对于分裂问题,它通常是真实分布和预测概率分布的交叉熵。对于回归问题,它通常是真实值和预测值的平方和。

对于特定的模型,比如多任务学习模型,可能需要同时使用多个损失函数。换句话说,正在最小化的损失函数是其他一些损失函数的和。例如,有一个模型既要预测图像中场景的类型,又要预测每个像素的深度。那这个模型的损失函数就是分类损失和深度预测损失的和。

TF-Slim通过losses模块,提供了一种易用的机制去定义和跟踪损失函数的足迹。看一个简单的例子,我们想训练VGG网络:

import tensorflow as tfvgg = tf.contrib.slim.nets.vgg# Load the images and labels.images, labels = ...# Create the model.predictions, _ = vgg.vgg_16(images)# Define the loss functions and get the total loss.loss = slim.losses.softmax_cross_entropy(predictions, labels)

在上面的例子中,我们首先创建了模型(用TF-Slim的VGG接口实现),并添加了标准的分类损失。现在,我们再看一个产生多输出的多任务模型:

# Load the images and labels.images, scene_labels, depth_labels = ...# Create the model.scene_predictions, depth_predictions = CreateMultiTaskModel(images)# Define the loss functions and get the total loss.classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)# The following two lines have the same effect:total_loss = classification_loss + sum_of_squares_losstotal_loss = slim.losses.get_total_loss(add_regularization_losses=False)

在这个例子中,我们有两个损失,分别是通过slim.losses.softmax_cross_entropy和

slim.losses.sum_of_squares得到的。我们既可以通过相加得到total_loss,也可以通过slim.losses.get_total_loss()得到total_loss。这是怎么做到的呢?当你通过TF-Slim创建一个损失函数时,TF-Slim会把损失加入到一个特殊的Tensorflow的损失函数集合中。这样你既可以手动管理损失函数,也可以托管给TF-Slim。

如果我们有一个自定义的损失函数,现在也想托管给TF-Slim,该怎么做呢?loss_ops.py也有一个函数可以将这个损失函数加入到TF-Slim集合中。例如

# Load the images and labels.images, scene_labels, depth_labels, pose_labels = ...# Create the model.scene_predictions, depth_predictions, pose_predictions = CreateMultiTaskModel(images)# Define the loss functions and get the total loss.classification_loss = slim.losses.softmax_cross_entropy(scene_predictions, scene_labels)sum_of_squares_loss = slim.losses.sum_of_squares(depth_predictions, depth_labels)pose_loss = MyCustomLossFunction(pose_predictions, pose_labels)slim.losses.add_loss(pose_loss) # Letting TF-Slim know about the additional loss.# The following two ways to compute the total loss are equivalent:regularization_loss = tf.add_n(slim.losses.get_regularization_losses())total_loss1 = classification_loss + sum_of_squares_loss + pose_loss + regularization_loss# (Regularization Loss is included in the total loss by default).total_loss2 = slim.losses.get_total_loss()

这个例子中,我们同样既可以手动管理损失函数,也可以让TF-Slim知晓这个自定义损失函数,然后托管给TF-Slim。

6、训练回路

在learning.py中,TF-Slim提供了简单却非常强大的训练模型的工具集。包括Train函数,可以重复地测量损失,计算梯度以及保存模型到磁盘中,还有一些方便的函数用于操作梯度。例如,当我们定义好了模型、损失函数以及优化方式,我们就可以调用slim.learning.create_train_op andslim.learning.train 去执行优化:

g = tf.Graph()# Create the model and specify the losses......total_loss = slim.losses.get_total_loss()optimizer = tf.train.GradientDescentOptimizer(learning_rate)# create_train_op ensures that each time we ask for the loss, the update_ops# are run and the gradients being computed are applied too.train_op = slim.learning.create_train_op(total_loss, optimizer)logdir = ... # Where checkpoints are stored.slim.learning.train(train_op,logdir,number_of_steps=1000,save_summaries_secs=300,save_interval_secs=600):

在该例中,slim.learning.train根据train_op计算损失、应用梯度step。logdir指定了checkpoints和event文件的存储路径。我们可以限制梯度step到任何数值。这里我们采用1000步。最后,save_summaries_secs=300表示每5分钟计算一次summaries,save_interval_secs=600表示每10分钟保存一次模型的checkpoint。

为了说明,让我们测试以下训练VGG的例子:

import tensorflow as tfslim = tf.contrib.slimvgg = tf.contrib.slim.nets.vgg...train_log_dir = ...if not tf.gfile.Exists(train_log_dir):tf.gfile.MakeDirs(train_log_dir)with tf.Graph().as_default():# Set up the data loading:images, labels = ...# Define the model:predictions = vgg.vgg16(images, is_training=True)# Specify the loss function:slim.losses.softmax_cross_entropy(predictions, labels)total_loss = slim.losses.get_total_loss()tf.summary.scalar('losses/total_loss', total_loss)# Specify the optimization scheme:optimizer = tf.train.GradientDescentOptimizer(learning_rate=.001)# create_train_op that ensures that when we evaluate it to get the loss,# the update_ops are done and the gradient updates are computed.train_tensor = slim.learning.create_train_op(total_loss, optimizer)# Actually runs training.slim.learning.train(train_tensor, train_log_dir)

对已有模型进行微调:

简要回顾一下如何从checkpoint加载variables,对从checkpoint加载variables的简略概括。在一个模型训练完成后,我们可以用tf.train.Saver()通过指定checkpoing加载variables的方式加载这个模型。对于很多情况,tf.train.Saver()提供了一种简单的机制去加载所有或一些varialbes变量。

# Create some variables.v1 = tf.Variable(..., name="v1")v2 = tf.Variable(..., name="v2")...# Add ops to restore all the variables.restorer = tf.train.Saver()# Add ops to restore some variables.restorer = tf.train.Saver([v1, v2])# Later, launch the model, use the saver to restore variables from disk, and# do some work with the model.with tf.Session() as sess:# Restore variables from disk.restorer.restore(sess, "/tmp/model.ckpt")print("Model restored.")# Do some work with the model...

See  Restoring Variables and Choosing which Variables to Save and Restore sections of the Variables page for more details.

参阅Variables章中Restoring Variables和Choosing which Variables to Save and Restore 相关部分,获取更多细节。

7、恢复部分模型

有时我们希望在一个全新的数据集上或面对一个信息任务方向去微调预训练模型。在这些情况下,我们可以使用TF-Slim's的帮助函数去加载模型中变量的一个子集:

# Create some variables.v1 = slim.variable(name="v1", ...)v2 = slim.variable(name="nested/v2", ...)...# Get list of variables to restore (which contains only 'v2'). These are all# equivalent methods:variables_to_restore = slim.get_variables_by_name("v2")# orvariables_to_restore = slim.get_variables_by_suffix("2")# orvariables_to_restore = slim.get_variables(scope="nested")# orvariables_to_restore = slim.get_variables_to_restore(include=["nested"])# orvariables_to_restore = slim.get_variables_to_restore(exclude=["v1"])# Create the saver which will be used to restore the variables.restorer = tf.train.Saver(variables_to_restore)with tf.Session() as sess:# Restore variables from disk.restorer.restore(sess, "/tmp/model.ckpt")print("Model restored.")# Do some work with the model...

8、用不同的变量名加载模型

当从checkpoint加载变量时,Saver先在checkpoint中定位变量名,然后映射到当前图的变量中。我们也可以通过向saver传递一个变量列表来创建saver。这时,在checkpoint文件中用于定位的变量名可以隐式地从各自的var.op.name中获得。当checkpoint文件中的变量名与当前图中的变量名完全匹配时,这会运行得很好。但是,有时我们想从一个变量名与与当前图的变量名不同的checkpoint文件中装载一个模型。这时,我们必须提供一个saver字典,这个字典对checkpoint中的每个变量和每个图变量进行了一一映射。请看下面这个例子,checkpoint的变量是通过一个简单的函数获得的:

# Assuming than 'conv1/weights' should be restored from 'vgg16/conv1/weights'def name_in_checkpoint(var):return 'vgg16/' + var.op.name# Assuming than 'conv1/weights' and 'conv1/bias' should be restored from 'conv1/params1' and 'conv1/params2'def name_in_checkpoint(var):if "weights" in var.op.name:return var.op.name.replace("weights", "params1")if "bias" in var.op.name:return var.op.name.replace("bias", "params2")variables_to_restore = slim.get_model_variables()variables_to_restore = {name_in_checkpoint(var):var for var in variables_to_restore}restorer = tf.train.Saver(variables_to_restore)with tf.Session() as sess:# Restore variables from disk.restorer.restore(sess, "/tmp/model.ckpt")

9、针对不同任务对模型进行微调

假设我们有一个已经预训练好的VGG16的模型。这个模型是在拥有1000分类的ImageNet数据集上进行训练的。但是,现在我们想把它应用在只具有20个分类的Pascal VOC数据集上。为了能这样做,我们可以通过利用除最后一些全连接层的其他预训练模型值来初始化新模型的达到目的:

# Load the Pascal VOC dataimage, label = MyPascalVocDataLoader(...)images, labels = tf.train.batch([image, label], batch_size=32)# Create the modelpredictions = vgg.vgg_16(images)train_op = slim.learning.create_train_op(...)# Specify where the Model, trained on ImageNet, was saved.model_path = '/path/to/pre_trained_on_imagenet.checkpoint'metric_ops.py# Specify where the new model will live:log_dir =from_checkpoint_'/path/to/my_pascal_model_dir/'# Restore only the convolutional layers:variables_to_restore = slim.get_variables_to_restore(exclude=['fc6', 'fc7', 'fc8'])init_fn = assign_from_checkpoint_fn(model_path, variables_to_restore)# Start training.slim.learning.train(train_op, log_dir, init_fn=init_fn)

10、评估模型

一旦我们训练好了一个模型(或者模型还在训练中),我们想看一下模型在实际中性能如何。这可以通过获取一系列表征模型性能的评估指标来实现,评估代码一般会加载数据,执行前向传播,和ground truth进行比较并记录评估分数。这个步骤可能执行一次,也可能周期性地执行。

度量

比如我们定义了一个不是损失函数的性能度量指标(损失在训练过程中进行直接优化),而这个指标出于评估模型的目的我们还非常感兴趣。比如说我们想最小化log损失,但是我们感兴趣的指标可能是F1 score(测试准确率),或者IoU分数(这个指标不可微,因此不能作为损失)。

TF-Slim提供了一系列指标操作符,它们可以使模型评估更简单。抽象来讲,计算一个指标值可以分为3步:

1. 初始化:初始化用于计算指标的变量。

2. 聚合:执行用于计算指标的运算流程(比如sum)。

3. 收尾:(可选)执行其他用于计算指标值的操作。例如,计算mean、min、max等。

例如,为了计算绝对平均误差,一个count变量和一个total变量需要初始化为0. 在聚合阶段,我们可以观察到一系列预测值及标签,计算他们差的绝对值,并加到total中。每次循环,count变量自加1。最后,在收尾阶段,total除以count就得到了mean。

下面的例子演示了定义指标的API。因为指标通常是在测试集上计算,而不是训练集(训练集上是用于计算loss的),我们假设我们在使用测试集:

images, labels = LoadTestData(...)predictions = MyModel(images)mae_value_op, mae_update_op = slim.metrics.streaming_mean_absolute_error(predictions, labels)mre_value_op, mre_update_op = slim.metrics.streaming_mean_relative_error(predictions, labels)pl_value_op, pl_update_op = slim.metrics.percentage_less(mean_relative_errors, 0.3)

如上例所示,指标的创建会返回两个值,一个value_op和一个update_op。value_op表示和当前指标值幂等的操作。update_op是上文提到的执行聚合步骤并返回指标值的操作符。跟踪每个value_opupdate_op是非常费劲的。为了解决这个问题,TF-Slim提供了两个方便的函数:

# Aggregates the value and update ops in two lists:value_ops, update_ops = slim.metrics.aggregate_metrics(slim.metrics.streaming_mean_absolute_error(predictions, labels),slim.metrics.streaming_mean_squared_error(predictions, labels))# Aggregates the value and update ops in two dictionaries:names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({"eval/mean_absolute_error": slim.metrics.streaming_mean_absolute_error(predictions, labels),"eval/mean_squared_error": slim.metrics.streaming_mean_squared_error(predictions, labels),})

把上面讲的整合在一起:

import tensorflow as tfslim = tf.contrib.slimvgg = tf.contrib.slim.nets.vgg# Load the dataimages, labels = load_data(...)# Define the networkpredictions = vgg.vgg_16(images)# Choose the metrics to compute:names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({"eval/mean_absolute_error": slim.metrics.streaming_mean_absolute_error(predictions, labels),"eval/mean_squared_error": slim.metrics.streaming_mean_squared_error(predictions, labels),})# Evaluate the model using 1000 batches of data:num_batches = 1000with tf.Session() as sess:sess.run(tf.global_variables_initializer())sess.run(tf.local_variables_initializer())for batch_id in range(num_batches):sess.run(names_to_updates.values())metric_values = sess.run(names_to_values.values())for metric, value in zip(names_to_values.keys(), metric_values):print('Metric %s has value: %f' % (metric, value))

注意,metric_ops.py可以在没有layers.py和loss_ops.py的情况下独立使用。

11、Evaluation Loop

TF-Slim提供了一个评估模块(evaluation.py),这个模块包含了一些利用来自metric_ops.py模块的指标写模型评估脚本的帮助函数。其中包含一个可以周期运行评估,评估数据batch之间的指标、打印并总结指标结果的函数。例如:

import tensorflow as tfslim = tf.contrib.slim# Load the data
images, labels = load_data(...)# Define the network
predictions = MyModel(images)# Choose the metrics to compute:
names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({'accuracy': slim.metrics.accuracy(predictions, labels),'precision': slim.metrics.precision(predictions, labels),'recall': slim.metrics.recall(mean_relative_errors, 0.3),
})# Create the summary ops such that they also print out to std output:
summary_ops = []
for metric_name, metric_value in names_to_values.iteritems():op = tf.summary.scalar(metric_name, metric_value)op = tf.Print(op, [metric_value], metric_name)summary_ops.append(op)num_examples = 10000
batch_size = 32
num_batches = math.ceil(num_examples / float(batch_size))# Setup the global step.
slim.get_or_create_global_step()output_dir = ... # Where the summaries are stored.
eval_interval_secs = ... # How often to run the evaluation.
slim.evaluation.evaluation_loop('local',checkpoint_dir,log_dir,num_evals=num_batches,eval_op=names_to_updates.values(),summary_op=tf.summary.merge(summary_ops),eval_interval_secs=eval_interval_secs)

2、采用 TF-Slim 创建第一个神经网络

以一个简单多层感知机(Multilayer Perceptron, MLP) 解决回归问题为例.该 MLP 模型有 2 个隐藏层,模型输出是单个节点.
当函数调用时,会创建很多节点node,并自动调价到当前作用域内的全局 TF Graph 中。当创建带有可调参数的网络层(如,FC层)时,会自动创建参数变量节点,并添加到 Graph 中,采用变量作用域(variable scope) 来将所有的节点放于通用名字,因此 Graph 具有分层结构。这有助于在 tensorboard 中可视化 TF Graph,及相关变量的查询。正如 arg_scope中所定义,FC 层都采用相同的 L2 weight decay 和 ReLU 激活。(不过,最终的网络层复写了这些默认值,使用了相同的激活函数)。此外,示例了在第一个全连接层FC1 后如何添加 Dropout 层。在测试时,不需要 dropout 节点,而是采用了平均激活(average activations)。因此,需要知道该模型是处于 training 或 testing 阶段,因为在两种情况下的计算图是不同的.(虽然保存着模型参数的变量variables 是共享的,具有相同的变量名/作用域 name/scope)

2.1、定义回归模型

def regression_model(inputs, is_training=True, scope="deep_regression"):"""创建回归模型Args:inputs: A node that yields a `Tensor` of size [batch_size, dimensions].is_training: Whether or not we're currently training the model.scope: An optional variable_op scope for the model.Returns:predictions: 1-D `Tensor` of shape [batch_size] of responses.end_points: A dict of end points representing the hidden layers."""with tf.variable_scope(scope, 'deep_regression', [inputs]):end_points = {}# Set the default weight _regularizer and acvitation for each fully_connected layer.with slim.arg_scope([slim.fully_connected],activation_fn=tf.nn.relu,weights_regularizer=slim.l2_regularizer(0.01)):# Creates a fully connected layer from the inputs with 32 hidden units.net = slim.fully_connected(inputs, 32, scope='fc1')end_points['fc1'] = net# Adds a dropout layer to prevent over-fitting.net = slim.dropout(net, 0.8, is_training=is_training)# Adds another fully connected layer with 16 hidden units.net = slim.fully_connected(net, 16, scope='fc2')end_points['fc2'] = net# Creates a fully-connected layer with a single hidden unit. Note that the# layer is made linear by setting activation_fn=None.predictions = slim.fully_connected(net, 1, activation_fn=None, scope='prediction')end_points['out'] = predictionsreturn predictions, end_points

2.2、创建模型/查看模型结构

with tf.Graph().as_default():# Dummy placeholders for arbitrary number of 1d inputs and outputsinputs = tf.placeholder(tf.float32, shape=(None, 1))outputs = tf.placeholder(tf.float32, shape=(None, 1))# 创建模型predictions, end_points = regression_model(inputs) # 添加nodes(tensors) 到 Graph.# 打印每个 tensor 的 name 和 shape.print("Layers")for k, v in end_points.items():print('name = {}, shape = {}'.format(v.name, v.get_shape()))# 打印参数节点(parameter nodes) 的 name 和 shape(值还未初始化)print("\n")print("Parameters")for v in slim.get_model_variables():print('name = {}, shape = {}'.format(v.name, v.get_shape()))

2.3、随机生成 1d 回归数据

def produce_batch(batch_size, noise=0.3):xs = np.random.random(size=[batch_size, 1]) * 10ys = np.sin(xs) + 5 + np.random.normal(size=[batch_size, 1], scale=noise) # 添加了随机噪声return [xs.astype(np.float32), ys.astype(np.float32)]x_train, y_train = produce_batch(200)
x_test, y_test = produce_batch(200)
plt.scatter(x_train, y_train)

2.4、拟合模型

模型训练需要指定 loss 函数和 optimizer,再采用 slim.

slim.learning.train 函数主要工作:

  • 对于每次迭代,评估 train_op,其采用 optimizer 应用到当前 minibatch 数据,更新参数. 同时,更新 global_step.
  • 周期性地保存模型断点到指定路径. 有助于根据断点文件重新训练.
def convert_data_to_tensors(x, y):inputs = tf.constant(x)inputs.set_shape([None, 1])outputs = tf.constant(y)outputs.set_shape([None, 1])return inputs, outputs# 采用均方差 loss 训练回归模型.
ckpt_dir = '/tmp/regression_model/'with tf.Graph().as_default():tf.logging.set_verbosity(tf.logging.INFO) # 日志信息inputs, targets = convert_data_to_tensors(x_train, y_train)# 模型创建predictions, nodes = regression_model(inputs, is_training=True)# 添加 loss 函数到 Graphloss = tf.losses.mean_squared_error(labels=targets, predictions=predictions)# 总 loss 是定义的 loss 加上任何正则 losses.total_loss = slim.losses.get_total_loss()# 设定 optimizer,并创建 train op:optimizer = tf.train.AdamOptimizer(learning_rate=0.005)train_op = slim.learning.create_train_op(total_loss, optimizer) # 在会话Session 内运行模型训练.final_loss = slim.learning.train(train_op,logdir=ckpt_dir,number_of_steps=5000,save_summaries_secs=5,log_every_n_steps=500)print("Finished training. Last batch loss:", final_loss)
print("Checkpoint saved in %s" % ckpt_dir)

2.5、采用多个 loss 函数训练模型

在某些任务场景中,需要同时优化多个目标.
TF-Slim 提供了易用的多个 losses 计算.
(这里,示例未优化 total loss,但是给出了如何计算)

with tf.Graph().as_default():inputs, targets = convert_data_to_tensors(x_train, y_train)predictions, end_points = regression_model(inputs, is_training=True)# 添加多个 losses 节点到 Graph.mean_squared_error_loss = tf.losses.mean_squared_error(labels=targets, predictions=predictions)absolute_difference_loss = slim.losses.absolute_difference(predictions, targets)# 下面两种计算 total loss 的方式是等价的.regularization_loss = tf.add_n(slim.losses.get_regularization_losses())total_loss1 = mean_squared_error_loss + absolute_difference_loss + regularization_loss# 默认情况下,Regularization Loss 被包括在 total loss 中.# 有益于 training, 但不益于 testing.total_loss2 = slim.losses.get_total_loss(add_regularization_losses=True)# 初始化变量init_op = tf.global_variables_initializer()with tf.Session() as sess:sess.run(init_op) # 采用随机权重初始化参数.total_loss1, total_loss2 = sess.run([total_loss1, total_loss2])print('Total Loss1: %f' % total_loss1)print('Total Loss2: %f' % total_loss2)print('Regularization Losses:')for loss in slim.losses.get_regularization_losses():print(loss)print('Loss Functions:')for loss in slim.losses.get_losses():print(loss)

2.6、加载保存的训练进行预测

with tf.Graph().as_default():inputs, targets = convert_data_to_tensors(x_test, y_test)# 创建模型结构. (后面再加载参数.)predictions, end_points = regression_model(inputs, is_training=False)# 创建会话,从断点文件恢复参数.sv = tf.train.Supervisor(logdir=ckpt_dir)with sv.managed_session() as sess:inputs, predictions, targets = sess.run([inputs, predictions, targets])plt.scatter(inputs, targets, c='r')
plt.scatter(inputs, predictions, c='b')
plt.title('red=true, blue=predicted')

2.7、测试集上计算评估度量 metrics

TF-Slim 术语中,losses 用于优化,但 metrics 仅用于评估,二者可能不一样,比如 precision & recall.
例如,计算的均方差误差和平均绝对值误差度量.

每个 metric 声明创建了几个局部变量(必须通过 tf.initialize_local_variables() 初始化),并同时返回 value_opupdate_op.
在评估时,value_op 返回当前 metric 值. update_op 加载一个新的 batch 数据,获得预测值,并在返回当前 metric 值之前累积计算 metric 统计结果.
value 节点和 update 节点保存为 2 个字典里.

创建 metric 节点之后,即可传递到 slim.evaluation,重复地评估这些节点多次.
最后,打印每个 metric 的最终值.

with tf.Graph().as_default():inputs, targets = convert_data_to_tensors(x_test, y_test)predictions, end_points = regression_model(inputs, is_training=False)# Specify metrics to evaluate:names_to_value_nodes, names_to_update_nodes = slim.metrics.aggregate_metric_map({'Mean Squared Error': slim.metrics.streaming_mean_squared_error(predictions, targets),'Mean Absolute Error': slim.metrics.streaming_mean_absolute_error(predictions, targets)})# Make a session which restores the old graph parameters, and then run eval.sv = tf.train.Supervisor(logdir=ckpt_dir)with sv.managed_session() as sess:metric_values = slim.evaluation.evaluation(sess,num_evals=1, # Single pass over dataeval_op=names_to_update_nodes.values(),final_op=names_to_value_nodes.values())names_to_values = dict(zip(names_to_value_nodes.keys(), metric_values))for key, value in names_to_values.items():print('%s: %f' % (key, value))

3、采用 TF-Slim 读取数据

采用 TF-Slim 读取数据主要包括两个部分:

  • Dataset - 数据集描述
  • DatasetDataProvider - 真实数据读取的必要操作.

3.1、Dataset

TF-Slim Dataset 包含了数据集的描述信息,用于数据读取,例如,数据文件列表,以及数据编码方式。此外,还包含一些元数据(metadata),包括类别标签,train/test 划分的数据集大小,数据集提供的张量描述等。例如,某些数据集包含图片images 和标签labels,其它边界框标注等。Dataset 对象允许针对不同的数据内容和编码类型使用相同的 API。TF-Slim [Dataset] 对于存储为 TFRecords 文件 的数据甚为有效,其中,每个 record 包含一个 tf.train.Example protocol buffer。TF-Slim 采用一致约定,用于每个 Example record 的 keys 和 vaules 的命名。

3.2、DatasetDataProvider

TF-Slim DatasetDataProvider 是用于从数据集真实读取数据的类Class。非常适合训练过程不同方式的数据读取。例如,DatasetDataProvider 是单线程或多线程。如果数据是多个文件的分片,DatasetDataProvider 可以序列的读取每个文件,或者同时从每个文件读取。

3.3、示例:Flowers 数据集

这里给出了将几个常用图片数据集转换为 TFRecord 格式的脚本,以及用于读取的 Dataset 描述。

  • Flowers TFRecord 格式数据集下载:

    import tensorflow as tf
    from datasets import dataset_utilsurl = "http://download.tensorflow.org/data/flowers.tar.gz"
    flowers_data_dir = '/tmp/flowers'if not tf.gfile.Exists(flowers_data_dir):tf.gfile.MakeDirs(flowers_data_dir)dataset_utils.download_and_uncompress_tarball(url, flowers_data_dir)
    
  • Flowers TFRecord 部分数据可视化
    from datasets import flowers
    import tensorflow as tffrom tensorflow.contrib import slimwith tf.Graph().as_default(): dataset = flowers.get_split('train', flowers_data_dir)data_provider = slim.dataset_data_provider.DatasetDataProvider(dataset, common_queue_capacity=32, common_queue_min=1)image, label = data_provider.get(['image', 'label'])with tf.Session() as sess:    with slim.queues.QueueRunners(sess):for i in range(4):np_image, np_label = sess.run([image, label])height, width, _ = np_image.shapeclass_name = name = dataset.labels_to_names[np_label]plt.figure()plt.imshow(np_image)plt.title('%s, %d x %d' % (name, height, width))plt.axis('off')plt.show()
    

4、CNN 训练

基于一个简单 CNN 网络训练图片分类器。

4.1、模型定义

def my_cnn(images, num_classes, is_training):  # is_training is not used...with slim.arg_scope([slim.max_pool2d], kernel_size=[3, 3], stride=2):net = slim.conv2d(images, 64, [5, 5])net = slim.max_pool2d(net)net = slim.conv2d(net, 64, [5, 5])net = slim.max_pool2d(net)net = slim.flatten(net)net = slim.fully_connected(net, 192)net = slim.fully_connected(net, num_classes, activation_fn=None)return net

4.2、对随机生成图片应用模型

import tensorflow as tfwith tf.Graph().as_default():# 该模型可以处理任何大小的输入,因为第一层是卷积层.# 模型的大小是由 image_node 第一次传递到 my_cnn 函数时来决定的.# 一旦初始化了变量,所有权重矩阵的大小都会固定.# 由于全连接层,所有后续的图片必须具有与第一张图片具有相同的尺寸大小.batch_size, height, width, channels = 3, 28, 28, 3images = tf.random_uniform([batch_size, height, width, channels], maxval=1)# 创建模型num_classes = 10logits = my_cnn(images, num_classes, is_training=True)probabilities = tf.nn.softmax(logits)#随机初始化变量,包括参数初始化.init_op = tf.global_variables_initializer()with tf.Session() as sess:# 运行 init_op, 计算模型输出,并打印结果:sess.run(init_op)probabilities = sess.run(probabilities)print('Probabilities Shape:')
print(probabilities.shape)  # batch_size x num_classes print('\nProbabilities:')
print(probabilities)print('\nSumming across all classes (Should equal 1):')
print(np.sum(probabilities, 1)) # Each row sums to 1

4.3、在 Flowers 数据集训练模型

TF-Slim的learning.py中training函数的使用。首先,创建 load_batch 函数,从数据集加载 batchs 数据。然后,训练模型一次,评估结果。

from preprocessing import inception_preprocessing
import tensorflow as tffrom tensorflow.contrib import slimdef load_batch(dataset, batch_size=32, height=299, width=299, is_training=False):"""加载单个 bacth 的数据.Args:dataset: The dataset to load.batch_size: The number of images in the batch.height: The size of each image after preprocessing.width: The size of each image after preprocessing.is_training: Whether or not we're currently training or evaluating.Returns:images: A Tensor of size [batch_size, height, width, 3], image samples that have been preprocessed.images_raw: A Tensor of size [batch_size, height, width, 3], image samples that can be used for visualization.labels: A Tensor of size [batch_size], whose values range between 0 and dataset.num_classes."""data_provider = slim.dataset_data_provider.DatasetDataProvider(dataset, common_queue_capacity=32,common_queue_min=8)image_raw, label = data_provider.get(['image', 'label'])# Preprocess image for usage by Inception.image = inception_preprocessing.preprocess_image(image_raw, height, width, is_training=is_training)# Preprocess the image for display purposes.image_raw = tf.expand_dims(image_raw, 0)image_raw = tf.image.resize_images(image_raw, [height, width])image_raw = tf.squeeze(image_raw)# Batch it up.images, images_raw, labels = tf.train.batch([image, image_raw, label],batch_size=batch_size,num_threads=1,capacity=2 * batch_size)return images, images_raw, labels##
from datasets import flowers# This might take a few minutes.
train_dir = '/tmp/tfslim_model/'
print('Will save model to %s' % train_dir)with tf.Graph().as_default():tf.logging.set_verbosity(tf.logging.INFO)dataset = flowers.get_split('train', flowers_data_dir)images, _, labels = load_batch(dataset)# 创建模型:logits = my_cnn(images, num_classes=dataset.num_classes, is_training=True)# loss 函数:one_hot_labels = slim.one_hot_encoding(labels, dataset.num_classes)slim.losses.softmax_cross_entropy(logits, one_hot_labels)total_loss = slim.losses.get_total_loss()# 创建 summaries,以可视化训练进程:tf.summary.scalar('losses/Total Loss', total_loss)# 设定 optimizer, 创建 train op:optimizer = tf.train.AdamOptimizer(learning_rate=0.01)train_op = slim.learning.create_train_op(total_loss, optimizer)# 开始训练:final_loss = slim.learning.train(train_op,logdir=train_dir,number_of_steps=1, # For speed, we just do 1 epochsave_summaries_secs=1)print('Finished training. Final batch loss %d' % final_loss)

4.4、评估度量 metrics

以预测准确率(prediction accuracy) 和 top5 分类准确率为例。

from datasets import flowers# This might take a few minutes.
with tf.Graph().as_default():tf.logging.set_verbosity(tf.logging.DEBUG)dataset = flowers.get_split('train', flowers_data_dir)images, _, labels = load_batch(dataset)logits = my_cnn(images, num_classes=dataset.num_classes, is_training=False)predictions = tf.argmax(logits, 1)# metrics 定义:names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({'eval/Accuracy': slim.metrics.streaming_accuracy(predictions, labels),'eval/Recall@5': slim.metrics.streaming_recall_at_k(logits, labels, 5),})print('Running evaluation Loop...')checkpoint_path = tf.train.latest_checkpoint(train_dir)metric_values = slim.evaluation.evaluate_once(master='',checkpoint_path=checkpoint_path,logdir=train_dir,eval_op=names_to_updates.values(),final_op=names_to_values.values())names_to_values = dict(zip(names_to_values.keys(), metric_values))for name in names_to_values:print('%s: %f' % (name, names_to_values[name]))

5、采用预训练模型

神经网络模型参数量比较大时,表现最佳,且是比较灵活的函数逼近器。但是,也就是需要在大规模数据集上进行训练。由于训练比较耗时,TensorFlow 提供和很多预训练模型,如 Pre-trained Models:

基于开源的预训练模型,可以在其基础上进一步应用到具体场景。例如,一般是修改最后的 pre-softmax层,根据具体任务修改权重初始化,类别标签数等。对于小数据集而言,十分有帮助。下面 [inception-v1] 的例子,虽然 [inception-v3]表现更好,但前者速度更快。VGG和ResNet 的最后一层是1000维输出,而不是10001维。ImageNet数据集提供了一个背景类background class,但 VGG和ResNet没有用到该背景类。下面给出 Inception V1和VGG-16预训练模型的示例。

5.1、下载 Inception V1 断点文件

from datasets import dataset_utilsurl = "http://download.tensorflow.org/models/inception_v1_2016_08_28.tar.gz"
checkpoints_dir = '/tmp/checkpoints'if not tf.gfile.Exists(checkpoints_dir):tf.gfile.MakeDirs(checkpoints_dir)dataset_utils.download_and_uncompress_tarball(url, checkpoints_dir)

5.2、应用 Inception V1 预训练模型

假设已经将每张图片尺寸调整为模型断点对应的尺寸。

import numpy as np
import os
import tensorflow as tftry:import urllib2 as urllib
except ImportError:import urllib.request as urllibfrom datasets import imagenet
from nets import inception
from preprocessing import inception_preprocessingfrom tensorflow.contrib import slimimage_size = inception.inception_v1.default_image_size # 输入图片尺寸with tf.Graph().as_default():url = 'https://upload.wikimedia.org/wikipedia/commons/7/70/EnglishCockerSpaniel_simon.jpg'image_string = urllib.urlopen(url).read()image = tf.image.decode_jpeg(image_string, channels=3)processed_image = inception_preprocessing.preprocess_image(image, image_size, image_size, is_training=False)processed_images  = tf.expand_dims(processed_image, 0)# 创建模型, 采用默认的 arg scope 作用域来配置 batch norm 参数.with slim.arg_scope(inception.inception_v1_arg_scope()):logits, _ = inception.inception_v1(processed_images, num_classes=1001, is_training=False)probabilities = tf.nn.softmax(logits)init_fn = slim.assign_from_checkpoint_fn(os.path.join(checkpoints_dir, 'inception_v1.ckpt'),slim.get_model_variables('InceptionV1'))with tf.Session() as sess:init_fn(sess)np_image, probabilities = sess.run([image, probabilities])probabilities = probabilities[0, 0:]sorted_inds = [i[0] for i in sorted(enumerate(-probabilities), key=lambda x:x[1])]plt.figure()plt.imshow(np_image.astype(np.uint8))plt.axis('off')plt.show()names = imagenet.create_readable_names_for_imagenet_labels()for i in range(5):index = sorted_inds[i]print('Probability %0.2f%% => [%s]' % (probabilities[index] * 100, names[index]))

5.3、下载 VGG-16 断点文件

from datasets import dataset_utils
import tensorflow as tfurl = "http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz"
checkpoints_dir = '/tmp/checkpoints'if not tf.gfile.Exists(checkpoints_dir):tf.gfile.MakeDirs(checkpoints_dir)dataset_utils.download_and_uncompress_tarball(url, checkpoints_dir)

5.4、应用 VGG-16 预训练模型

注意:1000 个类别而不是 1001。

import numpy as np
import os
import tensorflow as tftry:import urllib2
except ImportError:import urllib.request as urllibfrom datasets import imagenet
from nets import vgg
from preprocessing import vgg_preprocessingfrom tensorflow.contrib import slimimage_size = vgg.vgg_16.default_image_sizewith tf.Graph().as_default():url = 'https://upload.wikimedia.org/wikipedia/commons/d/d9/First_Student_IC_school_bus_202076.jpg'image_string = urllib.urlopen(url).read()image = tf.image.decode_jpeg(image_string, channels=3)processed_image = vgg_preprocessing.preprocess_image(image, image_size, image_size, is_training=False)processed_images  = tf.expand_dims(processed_image, 0)# Create the model, use the default arg scope to configure the batch norm parameters.with slim.arg_scope(vgg.vgg_arg_scope()):# 1000 classes instead of 1001.logits, _ = vgg.vgg_16(processed_images, num_classes=1000, is_training=False)probabilities = tf.nn.softmax(logits)init_fn = slim.assign_from_checkpoint_fn(os.path.join(checkpoints_dir, 'vgg_16.ckpt'),slim.get_model_variables('vgg_16'))with tf.Session() as sess:init_fn(sess)np_image, probabilities = sess.run([image, probabilities])probabilities = probabilities[0, 0:]sorted_inds = [i[0] for i in sorted(enumerate(-probabilities), key=lambda x:x[1])]plt.figure()plt.imshow(np_image.astype(np.uint8))plt.axis('off')plt.show()names = imagenet.create_readable_names_for_imagenet_labels()for i in range(5):index = sorted_inds[i]# Shift the index of a class name by one. print('Probability %0.2f%% => [%s]' % (probabilities[index] * 100, names[index+1]))

5.5 、在新数据集上 fine-tune 模型

基于 Flower 数据集 fine-tune Inception 模型。

# Note that this may take several minutes.import osfrom datasets import flowers
from nets import inception
from preprocessing import inception_preprocessingfrom tensorflow.contrib import slim
image_size = inception.inception_v1.default_image_sizedef get_init_fn():"""Returns a function run by the chief worker to warm-start the training."""checkpoint_exclude_scopes=["InceptionV1/Logits", "InceptionV1/AuxLogits"]  #原输出层exclusions = [scope.strip() for scope in checkpoint_exclude_scopes]variables_to_restore = []for var in slim.get_model_variables():for exclusion in exclusions:if var.op.name.startswith(exclusion):breakelse:variables_to_restore.append(var)return slim.assign_from_checkpoint_fn(os.path.join(checkpoints_dir, 'inception_v1.ckpt'),variables_to_restore)train_dir = '/tmp/inception_finetuned/'with tf.Graph().as_default():tf.logging.set_verbosity(tf.logging.INFO)dataset = flowers.get_split('train', flowers_data_dir)images, _, labels = load_batch(dataset, height=image_size, width=image_size)# Create the model, use the default arg scope to configure the batch norm parameters.with slim.arg_scope(inception.inception_v1_arg_scope()):logits, _ = inception.inception_v1(images, num_classes=dataset.num_classes, is_training=True)# Specify the loss function:one_hot_labels = slim.one_hot_encoding(labels, dataset.num_classes)slim.losses.softmax_cross_entropy(logits, one_hot_labels)total_loss = slim.losses.get_total_loss()# Create some summaries to visualize the training process:tf.summary.scalar('losses/Total Loss', total_loss)# Specify the optimizer and create the train op:optimizer = tf.train.AdamOptimizer(learning_rate=0.01)train_op = slim.learning.create_train_op(total_loss, optimizer)# Run the training:final_loss = slim.learning.train(train_op,logdir=train_dir,init_fn=get_init_fn(),number_of_steps=2)print('Finished training. Last batch loss %f' % final_loss)

5.6、应用新数据集的 fine-tune 模型

import numpy as np
import tensorflow as tf
from datasets import flowers
from nets import inceptionfrom tensorflow.contrib import slimimage_size = inception.inception_v1.default_image_size
batch_size = 3with tf.Graph().as_default():tf.logging.set_verbosity(tf.logging.INFO)dataset = flowers.get_split('train', flowers_data_dir)images, images_raw, labels = load_batch(dataset, height=image_size, width=image_size)# Create the model, use the default arg scope to configure the batch norm parameters.with slim.arg_scope(inception.inception_v1_arg_scope()):logits, _ = inception.inception_v1(images, num_classes=dataset.num_classes, is_training=True)probabilities = tf.nn.softmax(logits)checkpoint_path = tf.train.latest_checkpoint(train_dir)init_fn = slim.assign_from_checkpoint_fn(checkpoint_path,slim.get_variables_to_restore())with tf.Session() as sess:with slim.queues.QueueRunners(sess):sess.run(tf.initialize_local_variables())init_fn(sess)np_probabilities, np_images_raw, np_labels = sess.run([probabilities, images_raw, labels])for i in range(batch_size): image = np_images_raw[i, :, :, :]true_label = np_labels[i]predicted_label = np.argmax(np_probabilities[i, :])predicted_name = dataset.labels_to_names[predicted_label]true_name = dataset.labels_to_names[true_label]plt.figure()plt.imshow(image.astype(np.uint8))plt.title('Ground Truth: [%s], Prediction [%s]' % (true_name, predicted_name))plt.axis('off')plt.show()

5.7、残差网络

def resnet_v1_block(scope, base_depth, num_units, stride):return resnet_utils.Block(scope, bottleneck, [{'depth': base_depth * 4,'depth_bottleneck': base_depth,'stride': 1}] * (num_units - 1) + [{'depth': base_depth * 4,'depth_bottleneck': base_depth,'stride': stride}])

用于创建resnet_v1 bottleneck块的Helper函数。

参数:

  • scope:块的范围。
  • base_depth:每个单元的瓶颈层的深度。
  • num_units:块中的单元数。
  • stride:块体的跨步,作为最后一个单元的跨步执行。所有其他单位都有stride=1。

返回值:

resnet_v1的bottleneck块。

def resnet_v1(inputs,blocks,num_classes=None,is_training=True,global_pool=True,output_stride=None,include_root_block=True,spatial_squeeze=False,reuse=None,scope=None):with tf.variable_scope(scope, 'resnet_v1', [inputs], reuse=reuse) as sc:end_points_collection = sc.name + '_end_points'with slim.arg_scope([slim.conv2d, bottleneck,resnet_utils.stack_blocks_dense],outputs_collections=end_points_collection):with slim.arg_scope([slim.batch_norm], is_training=is_training):net = inputsif include_root_block:if output_stride is not None:if output_stride % 4 != 0:raise ValueError('The output_stride needs to be a multiple of 4.')output_stride /= 4net = resnet_utils.conv2d_same(net, 64, 7, stride=2, scope='conv1')net = slim.max_pool2d(net, [3, 3], stride=2, scope='pool1')net = resnet_utils.stack_blocks_dense(net, blocks, output_stride)if global_pool:# Global average pooling.net = tf.reduce_mean(net, [1, 2], name='pool5', keep_dims=True)# yjr_feature = tf.squeeze(net, [0, 1, 2])if num_classes is not None:net = slim.conv2d(net, num_classes, [1, 1], activation_fn=None,normalizer_fn=None, scope='logits')if spatial_squeeze:logits = tf.squeeze(net, [1, 2], name='SpatialSqueeze')else:logits = net# Convert end_points_collection into a dictionary of end_points.end_points = slim.utils.convert_collection_to_dict(end_points_collection)if num_classes is not None:end_points['predictions'] = slim.softmax(logits, scope='predictions')#### end_points['yjr_feature'] = yjr_featurereturn logits, end_points
resnet_v1.default_image_size = 224

ResNet v1模型的生成器为。该函数生成一系列ResNet v1模型。有关特定的模型实例化,请参见resnet_v1_*()方法,该方法通过选择产生不同深度的resnet的不同块实例化获得。Imagenet上的图像分类训练通常使用[224,224]输入,对于[1]中定义的、标称步长为32的ResNet,在最后一个ResNet块的输出处生成[7,7]feature map。然而,对于密集预测任务,我们建议使用空间维度为32 + 1的倍数的输入,例如[321,321]。在这种情况下,ResNet输出处的特征映射将具有空间形状[(height - 1) / output_stride + 1, (width - 1) / output_stride + 1]和与输入图像角完全对齐的角,这极大地促进了特征与图像的对齐。使用作为输入的[225,225]图像在最后一个ResNet块的输出处生成[8,8]feature map。对于密集预测任务,ResNet需要在全卷积(FCN)模式下运行,global_pool需要设置为False。[1,2]中的ResNets都有公称stride= 32,在FCN模式下,一个很好的选择是使用output_stride=16,以便在较小的计算和内存开销下增加计算特性的密度。

[1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun Deep Residual Learning for Image Recognition. arXiv:1512.03385
[2] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun Identity Mappings in Deep Residual Networks. arXiv: 1603.05027

参数:

  • inputs:大小张量[batch, height_in, width_in, channels]。
  • blocks:长度等于ResNet块数量的列表。每个元素都是一个resnet_utils。块对象,描述块中的单元。
  • num_classes:用于分类任务的预测类的数量。如果没有,则返回logit层之前的特性。
  • is_training:是否进行培训。
  • global_pool:如果为真,则在计算日志之前执行全局平均池。图像分类设为真,预测密度设为假。
  • output_stride:如果没有,那么输出将在标称网络步长处计算。如果output_stride不为None,则指定请求的输入与输出空间分辨率之比。
  • include_root_block:如果为真,则包含初始卷积后的最大池,如果为假则排除它。
  • spatial_squeeze:如果为真,logits为shape [B, C],如果为假,logits为shape [B, 1,1, C],其中B为batch_size, C为类数。
  • reuse:是否应该重用网络及其变量。为了能够重用“范围”,必须给出。
  • scope:可选variable_scope。

返回值:

  • net:一个大小为[batch, height_out, width_out, channels_out]的秩4张量。如果global_pool是假的,那么与各自的height_in和width_in相比,height_out和width_out将减少一个output_stride因子,否则height_out和width_out都等于1。如果num_classes为None,则net是最后一个ResNet块的输出,可能在全局平均池之后。如果num_classes不是None, net包含pre-softmax激活。
  • end_points:字典从网络组件到相应的激活。

可能产生的异常:

  • ValueError: If the target output_stride is not valid.

原地址:models/slim_walkthrough.ipynb at master · tensorflow/models · GitHub

TensorFlow - TF-Slim 使用总览相关推荐

  1. CV之NS之VGG16:基于TF Slim(VGG16)利用七个不同的预训练模型实现快速NS风格

    CV之NS之VGG16:基于TF Slim(VGG16)利用七个不同的预训练模型实现快速NS风格 目录 实现结果 部分实例代码 实现结果 1.本博主,以前几天拍过的东方明珠照片,为例进行快速NS风格 ...

  2. 【Tensorflow】slim.arg_scope()的使用

    [fishing-pan:https://blog.csdn.net/u013921430 转载请注明出处] slim.arg_scope() 函数的使用 slim是一种轻量级的tensorflow库 ...

  3. TensorFlow 使用 slim 模块搭建复杂网络

    原文链接: TensorFlow 使用 slim 模块搭建复杂网络 上一篇: scrapy 代理使用 下一篇: TensorFlow infogan 生成 mnist 数据集 参考 https://b ...

  4. tensorflow的slim调用预训练模型的权重进行迁移学习的一些感触

    背景:这几天找了几个二分类的网络,准备对一些算法进行集成,集成之前决定先把代码跑通. 几个算法是Inception系列的InceptionV3,InceptionV4,Inception_Resnet ...

  5. CV之NS之VGG16:基于TF Slim库利用VGG16算法的预训练模型实现七种不同快速图像风格迁移设计(cubist/denoised_starry/mosaic/scream/wave)案例

    CV之NS之VGG16:基于TF Slim库利用VGG16算法的预训练模型实现七种不同快速图像风格迁移设计(cubist/denoised_starry/feathers/mosaic/scream/ ...

  6. TensorFlow之Slim

    文章目录 What are the various components of TF-Slim? Defining Models Variables Layers Scopes Working Exa ...

  7. tensorflow中slim详解

    1.变量的定义 from __future__ import absolute_import from __future__ import division from __future__ impor ...

  8. slim php dd model,第二十四节,TensorFlow下slim库函数的使用以及使用VGG网络进行预训练、迁移学习(附代码)...

    在介绍这一节之前,需要你对slim模型库有一些基本了解,具体可以参考第二十二节,TensorFlow中的图片分类模型库slim的使用.数据集处理,这一节我们会详细介绍slim模型库下面的一些函数的使用 ...

  9. tensorflow tf.keras.utils.plot_model 画深度学习神经网络拓扑图

    tensorflow tf.keras.utils.plot_model 画网络拓扑图 # pip install graphviz # pip install pydot # 下载 graphviz ...

  10. TensorFlow tf.data 导入数据(tf.data官方教程) * * * * *

    原文链接:https://blog.csdn.net/u014061630/article/details/80728694 TensorFlow版本:1.10.0 > Guide > I ...

最新文章

  1. CSS实现 全兼容的多列均匀布局问题
  2. python爬虫教程i-Python 爬虫速成教程,还有35个实战项目送给你!
  3. Linux云服务器下Tomcat部署超详细
  4. html 两个iframe重叠,解决同一页面中两个iframe互相调用jquery,js函数的方法
  5. 程序员面试题精选100题(47)-数组中出现次数超过一半的数字[算法]
  6. Linux下ps -ef和ps aux的区别
  7. Cisco交换机实现端口安全与帮定
  8. [TypeScript] Export public types from your library
  9. [css] 如何让表格单元格等宽显示
  10. HTML里面Textarea换行总结
  11. Android四大组件---Activity
  12. Maven下载及安装教程
  13. Mybatis缓存详解
  14. 安卓接入融云即时通讯的简单步骤
  15. 笔记本连接android手机屏幕,实现手机、电脑屏幕共享的7个步骤
  16. 奇趣分享综合趣事百科文章类型discuz模板
  17. 一个基于信息论的人生观
  18. pg_stat_database 视图 tup_returned、tup_fetched 的含义
  19. 极光尔沃A6-3d打印机体验
  20. 2020-2-10新生赛

热门文章

  1. 为何女性经期前后的情绪变化会特别大?怎么才能缓解?
  2. 虚拟机网络设置(一):Bridged(桥接模式)
  3. 计算机怎样辅助与美工结合,幼儿园环保美工与信息技术整合案例(18页)-原创力文档...
  4. 仅剩一位 73 岁开发者苦撑!这个计算程序快要没人维护了
  5. 联想机器换完主板以后,开机慢解决方法
  6. 小米发行区间确定!发行市值最高871亿美元,凭什么比BAT还贵?
  7. 编写程序:有92号和95号汽油可以选择,选择你需要的汽油,并输入需要加油的升数,点击按钮“`计算总价钱`“在div中可以得到你所需要支付的价格
  8. XSS挑战第二期 Writeup
  9. 力扣105 先序和中序遍历数组构建二叉树
  10. 上海交大计算机考研复试刷人,2019上海交大初试第4也被刷,复试真要早准备!...