之前文章已经讲解了

step1:怎么将你的原始图像数据转成TF-Record格式;(请参考:TF-Record文件制作)

step2:然后运用转成TF-Record个格式的文件在Inception V3上做模型训练(请参考:模型fine-tune和整个权重文件重新训练)

在这两步基础上我们会在训练权重文件夹(我的目录是:slim/satellite/train_dir/)下生成如下文件:

我训练使用的指令如下:

python train_image_classifier.py \
  --train_dir=satellite/train_dir \
  --dataset_name=satellite \
  --dataset_split_name=train \
  --dataset_dir=satellite/data \
  --model_name=inception_v3 \
  --checkpoint_path=satellite/pretrained/inception_v3.ckpt \
  --checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits \
  --trainable_scopes=InceptionV3/Logits,InceptionV3/AuxLogits \
  --max_number_of_steps=100000 \
  --batch_size=32 \
  --learning_rate=0.001 \
  --learning_rate_decay_type=fixed \
  --save_interval_secs=300 \
  --save_summaries_secs=2 \
  --log_every_n_steps=10 \
  --optimizer=rmsprop \
  --weight_decay=0.00004

每个指令的参数含义请参考模型fine-tune和整个权重文件重新训练 ,训练了10万步,其实在训练开始后就可以运行eval来评估模型的效果。实际情况是eval模型也需要加载ckpt文件,需要占用不小内存,训练阶段会适当调整batch大小合理利用显卡性能。因此想实时运行train和eval的话需要调整好两者所需内存。

模型评估

验证模型效果的指令如下(在slim文件夹下运行):

python eval_image_classifier.py \
  --checkpoint_path=satellite/train_dir \
  --eval_dir=satellite/eval_dir \
  --dataset_name=satellite \
  --dataset_split_name=validation \
  --dataset_dir=satellite/data \
  --model_name=inception_v3

其中--checkpoint_path就是模型文件存放路径,这个参数既可以接收一个目录的路径,也可以接收一个文件的路径。 如果接收的是一个目录的路径,如这里的satellite/train_dir,就会在这个目录中寻找最新保存的模型文件,执行验证。也可以指定一个模型进行验证,以第 300 步的模型为例,在 satellite/train_dir 文件夹下把被保存为 model.clcpt-300.meta 、 model.ckpt-300.index、 model.ckpt-300.data-00000-of-00001 三个文件。 此时,如果要对它执行验证,给 checkpoint_path 传递的参数应该为
satellite/train_dir/model.ckpt-300;

--eval_dir是验证结果存放路径;--dataset_name是数据集名称;--dataset_split_name是数据集操作类型名(根据验证集还是训练集实际到对应目录下找这阶段的tf-record数据);--dataset_dir就是验证集tf-record存放目录;--model_name网络模型结构的名字;

用的是slim模块下自带的eval_image_classifier.py文件,里面有很多默认的参数,如传入的batch大小默认100,默认用4线程等;

训练结果打印如下(在TITAN X 内存12G):

 

其中Accuracy是模型分类准确率,而Recall_5是Top5的准确率,表示在输出的类别概率中,正确类别只要落在前5就算对。由于训练目标类别总共才6类,因此可更改Top-5为Top-2或Top-3的准确率。需要再eval_image_classifier.py中修改如下内容:

    # Define the metrics:
    names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({
        'Accuracy': slim.metrics.streaming_accuracy(predictions, labels),
        'Recall_5': slim.metrics.streaming_recall_at_k(
            logits, labels, 5),
    })

把其中的召回率5改成2或3就行;更改后再次运行验证指令,得到如下结果(召回率结果下降):

在slim/satellite/文件夹下会生成eval_dir文件夹,里面存放着验证结果;可用tensorboard进行查看;

只要在命令行输入: tensorboard --logdir=your path\eval_dir

本文第一张图中的events.out.tfevents (我的有8G那么大,根据自己模型步长大小等因素)这个就是训练结果目录下的日志文件,可用tensorboard查看;

导出训练好的模型

如第一张图所示,训练完成后会在train_dir下生成  .meta  ;  .index  ;  .ckpt  ;  checkpoint文件。其中.meta文件保存了graph和metadata, 而ckpt保存了网络的weights。而在生产环境中进行预测的时候是只需要模型和权重,不需要metadata,所以需要将其提出进行freeze操作,将所需的部分放到一个文件,方便之后的调用,也减少模型加载所需的内存。(在下载的预训练模型解压后可以找到4个文件,其中名为frozen_inference_graph.pb的文件就是freeze后产生的模型文件,比weights文件大,但是比weights和meta文件加起来要小不少。)

tensorflow提供了两个代码文件: freeze_graph.py 和 classify_image_inception_v3.py。 前者可以导出一个用于识别的模型, 后者则是使用 inception_v3 模型对单张图片做识别的脚本。

tensorflow/python/tools/freeze_graph.py提供了freeze model的api,但是需要提供输出的final node names(一般是softmax之类的最后一层的激活函数命名),而object detection api提供了预训练好的网络,final node name并不好找,所以object_detection目录下还提供了export_inference_graph.py (放置于slim目录下)

首先将freeze_graph.py文件拷贝到slim同级目录下;

运行如下指令:

python export_inference_graph.py \
  --alsologtostderr \
  --model_name=inception_v3 \
  --output_file=satellite/inception_v3_inf_graph.pb \
  --dataset_name satellite

会在satellite文件夹下生成一个pb文件;注意:inception_v3_inf_graph.pb 文件中只保存了 Inception V3 的网络结构, 并不包含训练得到的模型参数,需要将 checkpoint 中的模型参数保存进来。 方法是使用 freeze_graph. py 脚本(在 chapter_3 文件夹下运行):

python freeze_graph.py \
  --input_graph slim/satellite/inception_v3_inf_graph.pb \
  --input_checkpoint slim/satellite/train_dir/model.ckpt-100000 \
  --input_binary true \
  --output_node_names InceptionV3/Predictions/Reshape_1 \
  --output_graph slim/satellite/frozen_graph.pb

这里参数的含义为:
• --input_graph slim/satellite/inception_ v3 _inf_graph.pb。它表示使用的网络结构文件,即之前已经导出的
inception_ v3_inf_graph.pb。

• --input_ checkpoint slim/satellite/train_dir/model.ckpt-100000 。具体将哪一个 checkpoint 的参数载入到网络结构中 。这里使用的是训练文件夹 train_dir 中的第 100000 步模型文件。 需要根据训练文件夹下 checkpoint 的实际步数,将 100000 修改成对应的数值。

• --input_binary true导入的 inception_v3 inf_graph.pb 实际是一个 protobuf 文件。 而protobuf文件有两种保存格式,一种是文本形式,一种是二进 制形式。 inception_v3 _inf _graph. pb 是二进制形式,所以对应的参数是 --input_binary true。 初学的话对此可以不用深究,若高兴趣的话可以参考资料。

•--output_node_names InceptionV3/Predictions/Reshape_1在导出的模型中,指定一个输出结点, InceptionV3/Predictions/Reshape _ l 是 Inception V3 最后的输出层。
•--output_graph slim/satellite/frozen_graph.pb 。最后导出的模型保存为 slim/satellite/frozen_graph.pb 文件。

最后生成的文件如下:

模型调用
可以直接使用官方提供的https://github.com/tensorflow/models/blob/master/research/object_detection/object_detection_tutorial.ipynb,使用jupyter  notebook测试

何大神提供的py文件classify_image_inception_v3.py (其原代码如下)可完成对单张图像进行预测。

# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
 
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
 
import argparse
import os.path
import re
import sys
import tarfile
 
import numpy as np
from six.moves import urllib
import tensorflow as tf
 
FLAGS = None
 
class NodeLookup(object):
  def __init__(self, label_lookup_path=None):
    self.node_lookup = self.load(label_lookup_path)
 
  def load(self, label_lookup_path):
    node_id_to_name = {}
    with open(label_lookup_path) as f:
      for index, line in enumerate(f):
        node_id_to_name[index] = line.strip()
    return node_id_to_name
 
  def id_to_string(self, node_id):
    if node_id not in self.node_lookup:
      return ''
    return self.node_lookup[node_id]
 
 
def create_graph():
  """Creates a graph from saved GraphDef file and returns a saver."""
  # Creates graph from saved graph_def.pb.
  with tf.gfile.FastGFile(FLAGS.model_path, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    _ = tf.import_graph_def(graph_def, name='')
 
def preprocess_for_eval(image, height, width,
                        central_fraction=0.875, scope=None):
  with tf.name_scope(scope, 'eval_image', [image, height, width]):
    if image.dtype != tf.float32:
      image = tf.image.convert_image_dtype(image, dtype=tf.float32)
    # Crop the central region of the image with an area containing 87.5% of
    # the original image.
    if central_fraction:
      image = tf.image.central_crop(image, central_fraction=central_fraction)
 
    if height and width:
      # Resize the image to the specified height and width.
      image = tf.expand_dims(image, 0)
      image = tf.image.resize_bilinear(image, [height, width],
                                       align_corners=False)
      image = tf.squeeze(image, [0])
    image = tf.subtract(image, 0.5)
    image = tf.multiply(image, 2.0)
    return image
 
def run_inference_on_image(image):
  """Runs inference on an image.
  Args:
    image: Image file name.
  Returns:
    Nothing
  """
  with tf.Graph().as_default():
    image_data = tf.gfile.FastGFile(image, 'rb').read()
    image_data = tf.image.decode_jpeg(image_data)
    image_data = preprocess_for_eval(image_data, 299, 299)
    image_data = tf.expand_dims(image_data, 0)
    with tf.Session() as sess:
      image_data = sess.run(image_data)
 
  # Creates graph from saved GraphDef.
  create_graph()
 
  with tf.Session() as sess:
    softmax_tensor = sess.graph.get_tensor_by_name('InceptionV3/Logits/SpatialSqueeze:0')
    predictions = sess.run(softmax_tensor,
                           {'input:0': image_data})
    predictions = np.squeeze(predictions)
 
    # Creates node ID --> English string lookup.
    node_lookup = NodeLookup(FLAGS.label_path)
 
    top_k = predictions.argsort()[-FLAGS.num_top_predictions:][::-1]
    for node_id in top_k:
      human_string = node_lookup.id_to_string(node_id)
      score = predictions[node_id]
      print('%s (score = %.5f)' % (human_string, score))
 
 
def main(_):
  image = FLAGS.image_file
  run_inference_on_image(image)
 
 
if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--model_path',
      type=str,
  )
  parser.add_argument(
      '--label_path',
      type=str,
  )
  parser.add_argument(
      '--image_file',
      type=str,
      default='',
      help='Absolute path to image file.'
  )
  parser.add_argument(
      '--num_top_predictions',
      type=int,
      default=5,
      help='Display this many predictions.'
  )
  FLAGS, unparsed = parser.parse_known_args()
  tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

运行该脚本的指令为:

python classify_image_inception_v3.py \
  --model_path slim/satellite/frozen_graph.pb \
  --label_path data_prepare/pic/label.txt \
  --image_file test_image.jpg

--model_path 就是之前导出的模型 frozen_graph.pb。 模型的 输出实际是“第 0 类’\“第 1 类”……所以用--label_path 指定了一个 label 文件, label 文件中按顺序存储了各个类别的名称,这样脚本就可以把类别的id号转换为实际的类别名。 --image_file 是需要测试的单张图片。 

测试结果如下:

这就表示模型预测图片对应的最可能的类别是 water,接着是 wetland、 urban、 wood 等。 score 是各个类别对应的 Logit。默认取了5个类别预测Logit的输出,可在运行脚本时用--num_top_predictions参数来改变默认值。

至此,从对原始图像转为tfrecord格式文件,接着训练权重文件,模型的验证评估,模型文件的导出和测试整个流程就讲完了。该文章目前只针对分类用途。