1 TF-slim简介
TF-slim是一个轻型的TensorFlow高层API (tensorflow.contrib.slim
) 。可以用来定义、训练和评估复杂模型。slim项目包含丰富的源码。可以使用TF-slim来训练和推理许多广泛应用于CNN的图像分类模型。slim项目包含许多脚本,你可以利用它们重新训练,也可以在已训练模型的基础上进行fine-tune。当然也包括一些默认的脚本,用来下载标准的图像数据集,并将它们转换为TensorFlow的TFRecord格式,以及使用TF-Slim的数据读和队列化函数来读取这些数据。也可以使用自己的数据进行训练。
可以参考脚本jupyter notebook,其提供了许多使用TF-Slim的图像分类例子。对于开发或者改进TF-Slim,可以参考main TF-Slim page.
2 安装
这里我们介绍TF-Slim的安装步骤
2.1 安装最新版本的TF-Slim
TF-Slim is available as tf.contrib.slim
via TensorFlow 1.0. To test that your installation is working, execute the following command; it should run without raising any errors.
TF-Slim基于TensorFlow 1.0,库名为tf.contrib.slim
。测试安装是否成功,运行下面的代码
python -c 'import tensorflow.contrib.slim as slim; eval = slim.evaluation.evaluate_once'
2.2 安装TF-Slim图像模型库
要使用TF-Slim进行图像分类,需要安装 TF-Slim image models library。其不是TensorFlow默认安装的。为了使用这些图像分类模型,我们需要将下载 tensorflow/models
cd $HOME/workspace
git clone https://github.com/tensorflow/models/
上面的命令会下将TF-Slim的图像模型库下载到目录$HOME/workspace/models/research/slim
(同时会创建目录models/inception,该目录包含Slim的老版本,这里可以忽略)。
为了验证是否成功,可以执行下面的shell脚本。
cd $HOME/workspace/models/research/slim
python -c "from nets import cifarnet; mynet = cifarnet.cifarnet"
3 准备数据
作为Slim库的一部分,下表中的数据集的下载和格式转换脚本也内置在slim目录下。
Dataset | Training Set Size | Testing Set Size | Number of Classes | Comments |
---|---|---|---|---|
Flowers | 2500 | 2500 | 5 | Various sizes (source: Flickr) |
Cifar10 | 60k | 10k | 10 | 32×32 color |
MNIST | 60k | 10k | 10 | 28×28 gray |
ImageNet | 1.2M | 50k | 1000 | Various size |
对于每个数据集,我们需要下载原始数据,并将其转换为TensorFlow的基本TFRecord格式。每个TFRecord包含一个TF-Example协议缓冲文件。下面的脚本是显示如何转换Flowers数据集为TFRecord。
$ DATA_DIR=/tmp/data/flowers
$ python download_and_convert_data.py \
--dataset_name=flowers \
--dataset_dir="${DATA_DIR}"
完成上面的脚本命令后,会得到如下TFRecord文件:
$ ls ${DATA_DIR}
flowers_train-00000-of-00005.tfrecord
...
flowers_train-00004-of-00005.tfrecord
flowers_validation-00000-of-00005.tfrecord
...
flowers_validation-00004-of-00005.tfrecord
labels.txt
上面的文件中中包括训练和验证数据集,每个数据集包括5个文件。除此之外,还有一个labels.txt文件。该文件中表示了数字到类别名的映射关系。
类似的,可以使用相同的脚本来创建MNIST和CIFAR-10数据集。但是对于ImageNet数据集,需要参考这里。下载ImageNet数据集,需要注册,并且下载数据集需要耗费几个小时,而且要占据接近500GB的存储空间。
3.1 创建TF-Slim数据集描述字
如果已经创建好了数据集的TFRecord格式,那么创建Slim数据集格式将会非常容易。Slim数据集存储了指向数据文件的指针,以及各种多样的数据块,例如类标签,训练/测试数据集分割,以及如何转化为TFExample格式。Slim数据集已经做了Cifar10、ImageNet、Flowers和MNIST的TF-Slim数据描述字。下面展示一个使用TF-Slim数据集描述字的例子(使用TF-Slim的DatasetDataProvider)。
import tensorflow as tf
from datasets import flowers
slim = tf.contrib.slim
# Selects the 'validation' dataset.
dataset = flowers.get_split('validation', DATA_DIR)
# Creates a TF-Slim DataProvider which reads the dataset in the background
# during both training and testing.
provider = slim.dataset_data_provider.DatasetDataProvider(dataset)
[image, label] = provider.get(['image', 'label'])
3.2 一个处理ImageNet数据的自动脚本
我们经常需要基于ImageNet数据集训练模型。为了方便处理ImageNet数据集,Slim提供了一个自动脚本,用来下载和将ImageNet数据集转换为TFRecord格式。
TFRecord格式文件包含一个标准文件集合,该集合中的每个入口是一个序列化的tf.Example
proto。每个tf.Example
proto包含ImageNet图像(JPEG编码格式)以及一些元数据(包括标签和bounding box信息)。
Slim提供了单个脚本来下载和转换ImageNet数据为TFRecord格式数据。下载和预处理这些数据可能需要耗费若干个小时,这以来于你的网速和计算机,请保持耐心哦!
开始已下载的时候,需要注册ImageNet网站的账号。并且得到一个访问密码来下载数据。
在得到USERNAME
和PASSWORD
之后,就可以运行这个脚本了。首先确定你的硬盘空间有超过500GB的空间用来下载和存储数据。这个示例中选择DATA_DIR=$HOME/imagenet-data
作为存储ImageNet数据的文件夹。
当运行下面的脚本之后,需要输入用户名和密码。输入一次之后就OK了。
# location of where to place the ImageNet data
DATA_DIR=$HOME/imagenet-data
# build the preprocessing script.
bazel build slim/download_and_preprocess_imagenet
# run it
bazel-bin/slim/download_and_preprocess_imagenet "${DATA_DIR}"
当运行完上面的脚本以后可以发现有1024和128个训练和验证文件(在DATA_DIR文件夹中)。文件的格式为train-????-of-1024
和validation-?????-of-00128
。
3.3 预训练模型
当神经网络的参数越多的时候,其功能越强大,这使得神经网络可以拟合任何映射关系。但是这也意味着神经网络需要更大的训练数据集。因为从头开始训练神经网络非常耗时,可能需要几周时间,因此Slim提供了许多预训练模型,如下表所示。这些CNN是在ILSVRC-2012-CLS图像分类数据集中已经训练好了。
在下表中,列出了每个模型、对应的TensorFlow模型文件、模型参数文件的链接地址、以及在ImageNet数据集上的Top-1和Top-5准确率。下表的VGG和ResNet V1参数已经从原始的caffe格式转换为TensorFlow格式。而Inception和ResNet V2参数是Google内部训练的结果。它们对应的准确率是基于单个裁剪图像得到的准确率,在一些论文中采用了更多的图像增强方法,使准确率更高。
^ResNet V2模型使用Inception预处理以及299图像尺寸的输入图像(在脚本eval_image_classifier.py
中使用参数--preprocessing_name inception --eval_image_size 299
)。另外,关于NASNet架构信息可以参考NASNet
所有的16个浮点类型的MobileNet V1模型均来自论文,而所有的16个量化的TensorFlow Lite的相关信息可以参考这里。更多的关于Mobile Net V2的信息可以参考这里。下面是下载Inception V3 checkpoint的例子:
$ CHECKPOINT_DIR=/tmp/checkpoints
$ mkdir ${CHECKPOINT_DIR}
$ wget http://download.tensorflow.org/models/inception_v3_2016_08_28.tar.gz
$ tar -xvf inception_v3_2016_08_28.tar.gz
$ mv inception_v3.ckpt ${CHECKPOINT_DIR}
$ rm inception_v3_2016_08_28.tar.gz
4 重新训练
Slim可以很方便的使用TF-Slim数据集来重新训练模型。下面的代码展示如何使用ImageNet数据集的默认参数来训练Inception V3模型。
DATASET_DIR=/tmp/imagenet
TRAIN_DIR=/tmp/train_logs
python train_image_classifier.py \
--train_dir=${TRAIN_DIR} \
--dataset_name=imagenet \
--dataset_split_name=train \
--dataset_dir=${DATASET_DIR} \
--model_name=inception_v3
上面的代码可能要运行好几天,甚至几个星期。这主要依赖于所使用的硬件。为提高训练速度,可以使用多GPU或多CPU,甚至同步或异步,具体可以参照这里。
为了监视训练过程,可以使用TensorBoard,运行如下代码:
tensorboard --logdir=${TRAIN_DIR}
运行了上面的命令之后,在浏览器上访问地址http://localhost:6006。
5 从现有checkpoint上Fine-tuning模型(所谓的迁移学习)
重新训练模型太慢,一般我们在已有的模型基础上进行fine-tuning。通过给参数--checkpoint_path
设置绝对路径来指定fine-tune的checkpoint。
当fine-tune一个模型时,我们需要仔细的处理checkpoint权值。特殊的,当我们fine-tune一个模型,其输出标签数目不同的情况,我们希望重新训练最后的分类层。要实现这个功能,可以设置参数--checkpoint_exclude_scopes
。该参数实现解冻部分层。当输出的类别数与预训练模型不同的情况下,则新模型将使用不同类别数(与预训练模型不同)。在Flowers数据集上fine-tune一个ImageNet预训练模型,这里的预训练模型logits的维数为,而Flowers数据集的logits维数为。换言之,该参数可以防止Slim从预训练的checkpoint中载入你不想载入的参数。
可以认为从一个checkpoint中热启动,仅仅影响模型的权值初始化。一旦一个模型开始训练,在目录$(TRAIN_DIR)
中将会创建新的checkpoint。如果fine-tune过程停止或者重启,并且新的checkpoint将会创建到新的权值存储位置,而不是存储在$(checkpoint_path)$
。因此,参数--checkpoint_path
和--checkpoint_exclude_scopes
也只是仅仅在模型初始化的时候才起作用。当希望训练一个层的子集,可以使用参数--trainable_scopes
来指定哪些层被训练,其余的部分参数将被冻结。
下面给出了一个在Flowers数据集上fine-tune一个Inception-v3的例子。inception v3在ImageNet数据集上已训练好,其类别有1000个,但是Flowers数据集只有5类。因此数据集非常小,我们仅仅训练新层。
$ DATASET_DIR=/tmp/flowers
$ TRAIN_DIR=/tmp/flowers-models/inception_v3
$ CHECKPOINT_PATH=/tmp/my_checkpoints/inception_v3.ckpt
$ python train_image_classifier.py \
--train_dir=${TRAIN_DIR} \
--dataset_dir=${DATASET_DIR} \
--dataset_name=flowers \
--dataset_split_name=train \
--model_name=inception_v3 \
--checkpoint_path=${CHECKPOINT_PATH} \
--checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits \
--trainable_scopes=InceptionV3/Logits,InceptionV3/AuxLogits
6 评估模型的性能
若要评估一个模型的性能(预训练模型或者新模型),Slim提供了一个脚本eval_image_classifier.py。如下所示。
下面的例子为下载预训练的inception模型,并且在ImageNet数据集上评估它的性能。
CHECKPOINT_FILE = ${CHECKPOINT_DIR}/inception_v3.ckpt # Example
$ python eval_image_classifier.py \
--alsologtostderr \
--checkpoint_path=${CHECKPOINT_FILE} \
--dataset_dir=${DATASET_DIR} \
--dataset_name=imagenet \
--dataset_split_name=validation \
--model_name=inception_v3
若要评估多个checkpoint可以参考这里。
7 导出推理图
保存包含模型架构的GraphDef。可以用Slim定义的模型名称来使用它,如下所示。
$ python export_inference_graph.py \
--alsologtostderr \
--model_name=inception_v3 \
--output_file=/tmp/inception_v3_inf_graph.pb
$ python export_inference_graph.py \
--alsologtostderr \
--model_name=mobilenet_v1 \
--image_size=224 \
--output_file=/tmp/mobilenet_v1_224.pb
7.1 冻结导出的计算图
如果想使用带有自定义训练或者预训练的模型作为移动模型的一部分,可以使用freeze_graph
来得到带有内联变量的graph def,如下所示。
bazel build tensorflow/python/tools:freeze_graph
bazel-bin/tensorflow/python/tools/freeze_graph \
--input_graph=/tmp/inception_v3_inf_graph.pb \
--input_checkpoint=/tmp/checkpoints/inception_v3.ckpt \
--input_binary=true --output_graph=/tmp/frozen_inception_v3.pb \
--output_node_names=InceptionV3/Predictions/Reshape_1
上面的输出节点的名称与模型相关,但是你也可以使用summarize_graph
工具来修改,如下所示。
bazel build tensorflow/tools/graph_transforms:summarize_graph
bazel-bin/tensorflow/tools/graph_transforms/summarize_graph \
--in_graph=/tmp/inception_v3_inf_graph.pb
7.2 在C++上运行标签图像
若要在C++上运行计算图,可以使用如下示例(label_image)。
bazel build tensorflow/examples/label_image:label_image
bazel-bin/tensorflow/examples/label_image/label_image \
--image=${HOME}/Pictures/flowers.jpg \
--input_layer=input \
--output_layer=InceptionV3/Predictions/Reshape_1 \
--graph=/tmp/frozen_inception_v3.pb \
--labels=/tmp/imagenet_slim_labels.txt \
--input_mean=0 \
--input_std=255
8 Bugs
- 如果模型运行的时候超过CPU内存,可以参考Model Runs out of CPU memory。
- 如果模型运行的时候超过GPU显存,可以参考Adjusting Memory Demands。
- 模型训练时出现NaNs,可以参考Model Resulting in NaNs。
- ResNet和VGG模型有1000类的输出,而ImageNet有1001类的输出。
ImageNet数据集包括一个背景类,该类可以用于fine-tune用于其他任务的模型。如果想在ImageNet数据集上训练或者fine-tuning这个VGG或者ResNet模型,可能会出现下面的错误。
这是因为VGG或者ResNet的输出层为1000维,而ImageNet数据集的输出类别为1001。若要处理这个错误,可以设置参数--labels_offset=1
。该参数可以使得ImageNet的标签向下移动一位。
InvalidArgumentError: Assign requires shapes of both tensors to match. lhs shape= [1001] rhs shape= [1000]
- 训练一个不同图像尺寸的模型(不是224的输入)
预处理函数使用参数height
和width
。可以使用以下参数定义来改变默认值:
image_preprocessing_fn = preprocessing_factory.get_preprocessing(
preprocessing_name,
height=MY_NEW_HEIGHT,
width=MY_NEW_WIDTH,
is_training=True)
- 这些超参数的目标是用于哪些硬件设置Hardware Specifications。