text: # -*-coding:utf-8-*-
  """Simple transfer learning with an Inception v3 model which
  displays summaries in TensorBoard.
  
  This example shows how to take a Inception v3 model trained on
  ImageNet images, and train a new top layer that can make a multi label image classification model
  
  To use with TensorBoard:
  By default, this script will log summaries to retrain_logs folder.
  Visualize the summaries with this command:
  tensorboard --logdir retrain_logs
  
  """
  import argparse
  import os.path
  import random
  import sys
  import tarfile
  from datetime import datetime
  
  import numpy as np
  import tensorflow as tf
  from tensorflow.python.framework import graph_util
  from tensorflow.python.framework import tensor_shape
  from tensorflow.python.platform import gfile
  
  FLAGS = None
  
  if sys.version_info[0] == 2:
      from urllib import urlretrieve
  else:
      from urllib.request import urlretrieve
  
  # These are all parameters that are tied to the particular model
  # we're using for Inception v3. These include things like tensor names and their
  # sizes. If you want to adapt this script to work with another model, you will
  # need to update these to reflect the values in the network you're using.
  MAX_NUM_IMAGES_PER_CLASS = 2 ** 27 - 1  # ~134M
  
  # ---------------------------
  IMAGE_LABEL_DIR = 'image_labels_dir'
  CACHED_GROUND_TRUTH_VECTORS = {}
  ALL_LABELS_FILE = "labels.txt"
  TRAINING = 'training'
  TESTING = 'testing'
  VALIDATION = 'validation'
  IMAGE_DIR = "images"
  
  
  # 丛数据文件夹读物所有图片的列表，并按训练、测试、验证数据集分开
  # 这个数据文件夹的不用修改。
  def create_image_lists():
      """Builds a list of training images from the file system.
  
      Splits image files into stable training, testing, and validation sets, and returns a data structure
      describing the lists of images for their paths.
  
      Returns:
        A dictionary containing an entry, with images split
        into training, testing, and validation sets within each label.
      """
      # 定义在函数内部，图片所在目录、测试和验证集百分比
      testing_percentage = validation_percentage = 0.1
  
      if not gfile.Exists(IMAGE_DIR):
          tf.logging.error("Image directory not found.")
          print("Image directory not found.")
          exit(1)
  
      file_list = os.listdir(IMAGE_DIR)  # 输出的是图片文件的文件名
      if not (20 < len(file_list) < MAX_NUM_IMAGES_PER_CLASS):
          tf.logging.warning('WARNING: 图片数量异常.')
          print('WARNING: 图片数量异常.')
          exit(1)
  
      testing_count = int(len(file_list) * testing_percentage)
      validation_count = int(len(file_list) * validation_percentage)
      return {
          TESTING: file_list[:testing_count],
          VALIDATION: file_list[testing_count:(testing_count + validation_count)],
          TRAINING: file_list[(testing_count + validation_count):],
      }
  
  
  # -------------------------------
  # 添加读取标签路径的函数  该函数只是简单的修改官网源码中的get_image_path函数
  # 我的图片路径是：主目录/images/1.jpg   标签路径在： 主目录/image_labels_dir/1.jpg.txt
  # 新增get_image_lanbels_path
  def get_image_labels_path(image_lists, index, category):
      """"Returns a path to an image for a label at the given index.
  
      Args:
        image_lists: Dictionary of training images for each label.
        index: Int offset of the image we want. This will be moduloed by the
        available number of images for the label, so it can be arbitrarily large.
        category: Name string of set to pull images from - training, testing, or
        validation.
  
      Returns:
        File system path string to  image labels that meets the requested parameters.
  
      """
      if category not in image_lists:
          tf.logging.fatal('Category does not exist %s.', category)
      category_list = image_lists[category]
      if not category_list:
          tf.logging.fatal('Label %s has no images in the category %s.', category)
      mod_index = index % len(category_list)
      base_name = category_list[mod_index]
      full_path = os.path.join(IMAGE_LABEL_DIR, base_name)
      full_path += '.txt'
      return full_path
  
  
  # 获取单张图片的地址
  def get_image_path(image_lists, index, image_dir, category):
      """"Returns a path to an image for a label at the given index.
  
      Args:
        image_lists: Dictionary of training images for each label.
        index: Int offset of the image we want. This will be moduloed by the
        available number of images for the label, so it can be arbitrarily large.
        category: Name string of set to pull images from - training, testing, or
        validation.
  
      Returns:
        File system path string to an image that meets the requested parameters.
  
      """
      if category not in image_lists:
          tf.logging.fatal('Category does not exist %s.', category)
      category_list = image_lists[category]
      if not category_list:
          tf.logging.fatal('Label %s has no images in the category %s.', category)
      mod_index = index % len(category_list)
      base_name = category_list[mod_index]
      full_path = os.path.join(image_dir, base_name)
      return full_path
  
  
  # 获取经过inceptionV3模型处理后的特征向量文件地址
  def get_bottleneck_path(image_lists, index, bottleneck_dir,
                          category):
      """"Returns a path to a bottleneck file for a label at the given index.
  
      Args:
        image_lists: Dictionary of training images for each label.
        index: Integer offset of the image we want. This will be moduloed by the
        available number of images for the label, so it can be arbitrarily large.
        bottleneck_dir: Folder string holding cached files of bottleneck values.
        category: Name string of set to pull images from - training, testing, or
        validation.
  
      Returns:
        File system path string to an image that meets the requested parameters.
      """
      return get_image_path(image_lists, index, bottleneck_dir, category) + '.txt'
  
  
  def create_model_graph(model_info):
      """"Creates a graph from saved GraphDef file and returns a Graph object.
  
      Args:
        model_info: Dictionary containing information about the model.
  
      Returns:
        Graph holding the trained Inception network, and various tensors we'll be
        manipulating.
      """
      with tf.Graph().as_default() as graph:
          model_path = os.path.join(FLAGS.model_dir, model_info['model_file_name'])
          with gfile.FastGFile(model_path, 'rb') as f:
              graph_def = tf.GraphDef()
              graph_def.ParseFromString(f.read())
              bottleneck_tensor, resized_input_tensor = (tf.import_graph_def(
                  graph_def,
                  name='',
                  return_elements=[
                      model_info['bottleneck_tensor_name'],
                      model_info['resized_input_tensor_name'],
                  ]))
      return graph, bottleneck_tensor, resized_input_tensor
  
  
  def run_bottleneck_on_image(sess, image_data, image_data_tensor,
                              decoded_image_tensor, resized_input_tensor,
                              bottleneck_tensor):
      """Runs inference on an image to extract the 'bottleneck' summary layer.
  
      Args:
        sess: Current active TensorFlow Session.
        image_data: String of raw JPEG data.
        image_data_tensor: Input data layer in the graph.
        decoded_image_tensor: Output of initial image resizing and preprocessing.
        resized_input_tensor: The input node of the recognition graph.
        bottleneck_tensor: Layer before the final softmax.
  
      Returns:
        Numpy array of bottleneck values.
      """
      # First decode the JPEG image, resize it, and rescale the pixel values.
      resized_input_values = sess.run(decoded_image_tensor,
                                      {image_data_tensor: image_data})
      # Then run it through the recognition network.
      bottleneck_values = sess.run(bottleneck_tensor,
                                   {resized_input_tensor: resized_input_values})
      bottleneck_values = np.squeeze(bottleneck_values)
      return bottleneck_values
  
  
  def maybe_download_and_extract(data_url):
      """Download and extract model tar file.
  
      If the pretrained model we're using doesn't already exist, this function
      downloads it from the TensorFlow.org website and unpacks it into a directory.
  
      Args:
        data_url: Web location of the tar file containing the pretrained model.
      """
      dest_directory = FLAGS.model_dir
      if not os.path.exists(dest_directory):
          os.makedirs(dest_directory)
      filename = data_url.split('/')[-1]
      filepath = os.path.join(dest_directory, filename)
      if not os.path.exists(filepath):
          def _progress(count, block_size, total_size):
              # sys.stdout.write 跟print差不多，可以理解成print是它的一个封装
              sys.stdout.write('\r>> Downloading %s %.1f%%' %
                               (filename,
                                float(count * block_size) / float(total_size) * 100.0))
              sys.stdout.flush()
  
          filepath, _ = urlretrieve(data_url, filepath, _progress)
          # _progress 是个回调函数，根据这个函数显示当前下载进度
          # urllib的urlretrieve函数是直接将远程数据下载到本地。参数一：远程路径，参数二：本地路径，参数三：回调函数
  
          statinfo = os.stat(filepath) # stat 系统调用时用来返回相关文件的系统状态信息的。
          tf.logging.info('Successfully downloaded', filename, statinfo.st_size,
                          'bytes.')
      tarfile.open(filepath, 'r:gz').extractall(dest_directory) #解压文件
  
  
  def ensure_dir_exists(dir_name):
      """Makes sure the folder exists on disk.
  
      Args:
        dir_name: Path string to the folder we want to create.
      """
      if not os.path.exists(dir_name):
          os.makedirs(dir_name)
  
  
  # 新版本去除了def write_list_of_floats_to_file(list_of_floats , file_path):
  # def read_list_of_floats_from_file(file_path): 这两个函数
  
  bottleneck_path_2_bottleneck_values = {}
  
  
  # 新版本增加的函数
  def create_bottleneck_file(bottleneck_path, image_lists, index,
                             image_dir, category, sess, jpeg_data_tensor,
                             decoded_image_tensor, resized_input_tensor,
                             bottleneck_tensor):
      """Create a single bottleneck file."""
      tf.logging.info('Creating bottleneck at ' + bottleneck_path)
      image_path = get_image_path(image_lists, index, image_dir, category)
      if not gfile.Exists(image_path):
          tf.logging.fatal('File does not exist %s', image_path)
      image_data = gfile.FastGFile(image_path, 'rb').read()
      try:
          bottleneck_values = run_bottleneck_on_image(
              sess, image_data, jpeg_data_tensor, decoded_image_tensor,
              resized_input_tensor, bottleneck_tensor)
      except Exception as e:
          raise RuntimeError('Error during processing file %s (%s)' % (image_path,
                                                                       str(e)))
      bottleneck_string = ','.join(str(x) for x in bottleneck_values)
      with open(bottleneck_path, 'w') as bottleneck_file:
          bottleneck_file.write(bottleneck_string)
  
  
  def get_or_create_bottleneck(sess, image_lists, index, image_dir,
                               category, bottleneck_dir, jpeg_data_tensor,
                               decoded_image_tensor, resized_input_tensor,
                               bottleneck_tensor):
      """Retrieves or calculates bottleneck values for an image.
  
      If a cached version of the bottleneck data exists on-disk, return that,
      otherwise calculate the data and save it to disk for future use.
  
      Args:
        sess: The current active TensorFlow Session.
        image_lists: Dictionary of training images for each label.
        index: Integer offset of the image we want. This will be modulo-ed by the
        available number of images for the label, so it can be arbitrarily large.
        image_dir: Root folder string of the subfolders containing the training
        images.
        category: Name string of which set to pull images from - training, testing,
        or validation.
        bottleneck_dir: Folder string holding cached files of bottleneck values.
        jpeg_data_tensor: The tensor to feed loaded jpeg data into.
        decoded_image_tensor: The output of decoding and resizing the image.
        resized_input_tensor: The input node of the recognition graph.
        bottleneck_tensor: The output tensor for the bottleneck values.
  
  
      Returns:
        Numpy array of values produced by the bottleneck layer for the image.
      """
      ensure_dir_exists(bottleneck_dir)
      bottleneck_path = get_bottleneck_path(image_lists, index,
                                            bottleneck_dir, category)
      if not os.path.exists(bottleneck_path):
          create_bottleneck_file(bottleneck_path, image_lists, index,
                                 image_dir, category, sess, jpeg_data_tensor,
                                 decoded_image_tensor, resized_input_tensor,
                                 bottleneck_tensor)
      with open(bottleneck_path, 'r') as bottleneck_file:
          bottleneck_string = bottleneck_file.read()
  
      try:
          bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
      except ValueError:
          tf.logging.warning('Invalid float found, recreating bottleneck')
  
          create_bottleneck_file(bottleneck_path, image_lists, index,
                                 image_dir, category, sess, jpeg_data_tensor,
                                 decoded_image_tensor, resized_input_tensor,
                                 bottleneck_tensor)
          with open(bottleneck_path, 'r') as bottleneck_file:
              bottleneck_string = bottleneck_file.read()
          # Allow exceptions to propagate here, since they shouldn't happen after a
          # fresh creation
          bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
      return bottleneck_values
  
  
  def cache_bottlenecks(sess, image_lists, image_dir, bottleneck_dir,
                        jpeg_data_tensor, decoded_image_tensor,
                        resized_input_tensor, bottleneck_tensor):
      """Ensures all the training, testing, and validation bottlenecks are cached.
  
      Because we're likely to read the same image multiple times (if there are no
      distortions applied during training) it can speed things up a lot if we
      calculate the bottleneck layer values once for each image during
      preprocessing, and then just read those cached values repeatedly during
      training. Here we go through all the images we've found, calculate those
      values, and save them off.
  
      Args:
        sess: The current active TensorFlow Session.
        image_lists: Dictionary of training images for each label.
        image_dir: Root folder string of the subfolders containing the training
        images.
        bottleneck_dir: Folder string holding cached files of bottleneck values.
        jpeg_data_tensor: Input tensor for jpeg data from file.
        decoded_image_tensor: The output of decoding and resizing the image.
        resized_input_tensor: The input node of the recognition graph.
        bottleneck_tensor: The penultimate output layer of the graph.
  
      Returns:
        Nothing.
      """
      how_many_bottlenecks = 0
      ensure_dir_exists(bottleneck_dir)
      for category in [TRAINING, TESTING, VALIDATION]:
          category_list = image_lists[category]
          for index, unused_base_name in enumerate(category_list):
              get_or_create_bottleneck(
                  sess, image_lists, index, image_dir, category,
                  bottleneck_dir, jpeg_data_tensor, decoded_image_tensor,
                  resized_input_tensor, bottleneck_tensor)
  
              how_many_bottlenecks += 1
              if how_many_bottlenecks % 100 == 0:
                  tf.logging.info(
                      str(how_many_bottlenecks) + ' bottleneck files created.')
  
  
  # ----------------
  # 新增get_ground_truth(labels_file,labels,class_count): 获取真实的标签值
  
  def get_ground_truth(labels_file, labels, class_count):
      if labels_file in CACHED_GROUND_TRUTH_VECTORS.keys():
          ground_truth = CACHED_GROUND_TRUTH_VECTORS[labels_file]
      else:
          with open(labels_file) as f:
              true_labels = f.read().splitlines()
          ground_truth = np.zeros(class_count, dtype=np.float32)
  
          for index, label in enumerate(labels):
              if label in true_labels:
                  ground_truth[index] = 1.0
  
          CACHED_GROUND_TRUTH_VECTORS[labels_file] = ground_truth
  
      return ground_truth
  
  
  def get_random_cached_bottlenecks(sess, image_lists, how_many, category,
                                    bottleneck_dir, image_dir, jpeg_data_tensor,
                                    decoded_image_tensor, resized_input_tensor,
                                    bottleneck_tensor, labels):
      """Retrieves bottleneck values for cached images.
  
      If no distortions are being applied, this function can retrieve the cached
      bottleneck values directly from disk for images. It picks a random set of
      images from the specified category.
  
      Args:
        sess: Current TensorFlow Session.
        image_lists: Dictionary of training images for each label.
        how_many: If positive, a random sample of this size will be chosen.
        If negative, all bottlenecks will be retrieved.
        category: Name string of which set to pull from - training, testing, or
        validation.
        bottleneck_dir: Folder string holding cached files of bottleneck values.
        image_dir: Root folder string of the subfolders containing the training
        images.
        jpeg_data_tensor: The layer to feed jpeg image data into.
        decoded_image_tensor: The output of decoding and resizing the image.
        resized_input_tensor: The input node of the recognition graph.
        bottleneck_tensor: The bottleneck output layer of the CNN graph.
        labels:All possible labels loaded from file labels.txt
  
  
      Returns:
        List of bottleneck arrays, their corresponding ground truths, and the
        relevant filenames.
      """
      # class_count = len(image_lists.keys())
      class_count = len(labels)
      bottlenecks = []
      ground_truths = []
      filenames = []
  
      # Retrieve a random sample of bottlenecks.
      for unused_i in range(how_many):
          image_index = random.randrange(MAX_NUM_IMAGES_PER_CLASS + 1)
          image_name = get_image_path(image_lists, image_index, image_dir, category)
          bottleneck = get_or_create_bottleneck(
              sess, image_lists, image_index, image_dir, category,
              bottleneck_dir, jpeg_data_tensor, decoded_image_tensor,
              resized_input_tensor, bottleneck_tensor)
          labels_file = get_image_labels_path(image_lists, image_index, category)
  
          # ground_truth = np.zeros(class_count, dtype=np.float32)
          # ground_truth[label_index] = 1.0
          ground_truth = get_ground_truth(labels_file, labels, class_count)
          bottlenecks.append(bottleneck)
          ground_truths.append(ground_truth)
          filenames.append(image_name)
  
      return bottlenecks, ground_truths, filenames
  
  
  def get_random_distorted_bottlenecks(
          sess, image_lists, how_many, category, image_dir, input_jpeg_tensor,
          distorted_image, resized_input_tensor, bottleneck_tensor, labels):
      """Retrieves bottleneck values for training images, after distortions.
  
      If we're training with distortions like crops, scales, or flips, we have to
      recalculate the full model for every image, and so we can't use cached
      bottleneck values. Instead we find random images for the requested category,
      run them through the distortion graph, and then the full graph to get the
      bottleneck results for each.
  
      Args:
        sess: Current TensorFlow Session.
        image_lists: Dictionary of training images for each label.
        how_many: The integer number of bottleneck values to return.
        category: Name string of which set of images to fetch - training, testing,
        or validation.
        image_dir: Root folder string of the subfolders containing the training
        images.
        input_jpeg_tensor: The input layer we feed the image data to.
        distorted_image: The output node of the distortion graph.
        resized_input_tensor: The input node of the recognition graph.
        bottleneck_tensor: The bottleneck output layer of the CNN graph.
        labels:All possible labels loaded from file labels.txt
  
      Returns:
        List of bottleneck arrays and their corresponding ground truths.
      """
      class_count = len(labels)
      bottlenecks = []
      ground_truths = []
      for unused_i in range(how_many):
          image_index = random.randrange(MAX_NUM_IMAGES_PER_CLASS + 1)
          image_path = get_image_path(image_lists, image_index, image_dir, category)
          if not gfile.Exists(image_path):
              tf.logging.fatal('File does not exist %s', image_path)
          jpeg_data = gfile.FastGFile(image_path, 'rb').read()
          # Note that we materialize the distorted_image_data as a numpy array before
          # sending running inference on the image. This involves 2 memory copies and
          # might be optimized in other implementations.
          distorted_image_data = sess.run(distorted_image,
                                          {input_jpeg_tensor: jpeg_data})
          bottleneck_values = sess.run(bottleneck_tensor,
                                       {resized_input_tensor: distorted_image_data})
          bottleneck_values = np.squeeze(bottleneck_values)
          labels_file = get_image_labels_path(image_lists, image_index, category)
          ground_truth = get_ground_truth(labels_file, labels, class_count)
  
          bottlenecks.append(bottleneck_values)
          ground_truths.append(ground_truth)
      return bottlenecks, ground_truths
  
  
  def should_distort_images(flip_left_right, random_crop, random_scale,
                            random_brightness):
      """Whether any distortions are enabled, from the input flags.
  
      Args:
        flip_left_right: Boolean whether to randomly mirror images horizontally.
        random_crop: Integer percentage setting the total margin used around the
        crop box.
        random_scale: Integer percentage of how much to vary the scale by.
        random_brightness: Integer range to randomly multiply the pixel values by.
  
      Returns:
        Boolean value indicating whether any distortions should be applied.
      """
      return (flip_left_right or (random_crop != 0) or (random_scale != 0) or
              (random_brightness != 0))
  
  
  def add_input_distortions(flip_left_right, random_crop, random_scale,
                            random_brightness, input_width, input_height,
                            input_depth, input_mean, input_std):
      """Creates the operations to apply the specified distortions.
  
      During training it can help to improve the results if we run the images
      through simple distortions like crops, scales, and flips. These reflect the
      kind of variations we expect in the real world, and so can help train the
      model to cope with natural data more effectively. Here we take the supplied
      parameters and construct a network of operations to apply them to an image.
  
      Cropping
      ~~~~~~~~
  
      Cropping is done by placing a bounding box at a random position in the full
      image. The cropping parameter controls the size of that box relative to the
      input image. If it's zero, then the box is the same size as the input and no
      cropping is performed. If the value is 50%, then the crop box will be half the
      width and height of the input. In a diagram it looks like this:
  
      <       width         >
      +---------------------+
      |                     |
      |   width - crop%     |
      |    <      >         |
      |    +------+         |
      |    |      |         |
      |    |      |         |
      |    |      |         |
      |    +------+         |
      |                     |
      |                     |
      +---------------------+
  
      Scaling
      ~~~~~~~
  
      Scaling is a lot like cropping, except that the bounding box is always
      centered and its size varies randomly within the given range. For example if
      the scale percentage is zero, then the bounding box is the same size as the
      input and no scaling is applied. If it's 50%, then the bounding box will be in
      a random range between half the width and height and full size.
  
      Args:
        flip_left_right: Boolean whether to randomly mirror images horizontally.
        random_crop: Integer percentage setting the total margin used around the
        crop box.
        random_scale: Integer percentage of how much to vary the scale by.
        random_brightness: Integer range to randomly multiply the pixel values by.
        graph.
        input_width: Horizontal size of expected input image to model.
        input_height: Vertical size of expected input image to model.
        input_depth: How many channels the expected input image should have.
        input_mean: Pixel value that should be zero in the image for the graph.
        input_std: How much to divide the pixel values by before recognition.
  
      Returns:
        The jpeg input layer and the distorted result tensor.
      """
  
      jpeg_data = tf.placeholder(tf.string, name='DistortJPGInput')
      decoded_image = tf.image.decode_jpeg(jpeg_data, channels=input_depth) #将图片变成整形张量
      decoded_image_as_float = tf.cast(decoded_image, dtype=tf.float32) #将uint8变成浮点型
      decoded_image_4d = tf.expand_dims(decoded_image_as_float, 0) # 0
      margin_scale = 1.0 + (random_crop / 100.0)
      resize_scale = 1.0 + (random_scale / 100.0)
      margin_scale_value = tf.constant(margin_scale)
      resize_scale_value = tf.random_uniform(tensor_shape.scalar(),
                                             minval=1.0,
                                             maxval=resize_scale)
      scale_value = tf.multiply(margin_scale_value, resize_scale_value)
      precrop_width = tf.multiply(scale_value, input_width)
      precrop_height = tf.multiply(scale_value, input_height)
      precrop_shape = tf.stack([precrop_height, precrop_width])
      precrop_shape_as_int = tf.cast(precrop_shape, dtype=tf.int32)
      precropped_image = tf.image.resize_bilinear(decoded_image_4d,
                                                  precrop_shape_as_int)
      precropped_image_3d = tf.squeeze(precropped_image, squeeze_dims=[0])
      cropped_image = tf.random_crop(precropped_image_3d,
                                     [input_height, input_width, input_depth])
      if flip_left_right:
          flipped_image = tf.image.random_flip_left_right(cropped_image)
      else:
          flipped_image = cropped_image
      brightness_min = 1.0 - (random_brightness / 100.0)
      brightness_max = 1.0 + (random_brightness / 100.0)
      brightness_value = tf.random_uniform(tensor_shape.scalar(),
                                           minval=brightness_min,
                                           maxval=brightness_max)
      brightened_image = tf.multiply(flipped_image, brightness_value)
      offset_image = tf.subtract(brightened_image, input_mean)
      mul_image = tf.multiply(offset_image, 1.0 / input_std)
      distort_result = tf.expand_dims(mul_image, 0, name='DistortResult')
      return jpeg_data, distort_result
  
  
  def variable_summaries(var):
      """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
      with tf.name_scope('summaries'):
          mean = tf.reduce_mean(var)
          tf.summary.scalar('mean', mean)
          with tf.name_scope('stddev'):
              stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
          tf.summary.scalar('stddev', stddev)
          tf.summary.scalar('max', tf.reduce_max(var))
          tf.summary.scalar('min', tf.reduce_min(var))
          tf.summary.histogram('histogram', var)
  
  
  def add_final_training_ops(class_count, final_tensor_name, bottleneck_tensor,
                             bottleneck_tensor_size):
      """Adds a new softmax and fully-connected layer for training.
  
      We need to retrain the top layer to identify our new classes, so this function
      adds the right operations to the graph, along with some variables to hold the
      weights, and then sets up all the gradients for the backward pass.
  
      The set up for the softmax and fully-connected layers is based on:
      https://www.tensorflow.org/versions/master/tutorials/mnist/beginners/index.html
  
      Args:
        class_count: Integer of how many categories of things we're trying to
        recognize.
        final_tensor_name: Name string for the new final node that produces results.
        bottleneck_tensor: The output of the main CNN graph.
        bottleneck_tensor_size: How many entries in the bottleneck vector.
  
      Returns:
        The tensors for the training and cross entropy results, and tensors for the
        bottleneck input and ground truth input.
      """
      with tf.name_scope('input'):
          bottleneck_input = tf.placeholder_with_default(
              bottleneck_tensor,
              shape=[None, bottleneck_tensor_size],
              name='BottleneckInputPlaceholder')
  
          ground_truth_input = tf.placeholder(tf.float32,
                                              [None, class_count],
                                              name='GroundTruthInput')
  
      # Organizing the following ops as `final_training_ops` so they're easier
      # to see in TensorBoard
      layer_name = 'final_training_ops'
      with tf.name_scope(layer_name):
          with tf.name_scope('weights'):
              initial_value = tf.truncated_normal(
                  [bottleneck_tensor_size, class_count], stddev=0.001)
  
              layer_weights = tf.Variable(initial_value, name='final_weights')
  
              variable_summaries(layer_weights)
          with tf.name_scope('biases'):
              layer_biases = tf.Variable(tf.zeros([class_count]), name='final_biases')
              variable_summaries(layer_biases)
          with tf.name_scope('Wx_plus_b'):
              logits = tf.matmul(bottleneck_input, layer_weights) + layer_biases
              tf.summary.histogram('pre_activations', logits)
              # 这里用的sigmoid激活函数 为什么多标签用sigmoid
      final_tensor = tf.nn.sigmoid(logits, name=final_tensor_name)
      tf.summary.histogram('activations', final_tensor)
  
      with tf.name_scope('cross_entropy'):
          cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(
              labels=ground_truth_input, logits=logits)
          with tf.name_scope('total'):
              cross_entropy_mean = tf.reduce_mean(cross_entropy)
      tf.summary.scalar('cross_entropy', cross_entropy_mean)
  
      with tf.name_scope('train'):
          optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)
          train_step = optimizer.minimize(cross_entropy_mean)
  
      return (train_step, cross_entropy_mean, bottleneck_input, ground_truth_input,
              final_tensor)
  
  
  def add_evaluation_step(result_tensor, ground_truth_tensor):
      """Inserts the operations we need to evaluate the accuracy of our results.
  
      Args:
        result_tensor: The new final node that produces results.
        ground_truth_tensor: The node we feed ground truth data
        into.
  
      Returns:
        Tuple of (evaluation step, prediction).
      """
      with tf.name_scope('accuracy'):
          with tf.name_scope('correct_prediction'):
              correct_prediction = tf.equal(tf.round(result_tensor), ground_truth_tensor)
              # 我们是多个标签 所以不能用argmax返回等于1的index
              # prediction = tf.argmax(result_tensor, 1)
              # correct_prediction = tf.equal(
              #     prediction, tf.argmax(ground_truth_tensor, 1))
          with tf.name_scope('accuracy'):
              evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
      tf.summary.scalar('accuracy', evaluation_step)
      return evaluation_step
  
  
  def save_graph_to_file(sess, graph, graph_file_name):
      output_graph_def = graph_util.convert_variables_to_constants(
          sess, graph.as_graph_def(), [FLAGS.final_tensor_name])
      with gfile.FastGFile(graph_file_name, 'wb') as f:
          f.write(output_graph_def.SerializeToString())
      return
  
  
  def prepare_file_system():
      # Setup the directory we'll write summaries to for TensorBoard
      if tf.gfile.Exists(FLAGS.summaries_dir):
          tf.gfile.DeleteRecursively(FLAGS.summaries_dir)
      tf.gfile.MakeDirs(FLAGS.summaries_dir)
      if FLAGS.intermediate_store_frequency > 0:
          ensure_dir_exists(FLAGS.intermediate_output_graphs_dir)
      return
  
  
  def create_model_info():
      """Given the name of a model, returns information about it.
  
      There are different base image recognition pretrained models that can be
      retrained using transfer learning, and this function translates from the name
      of a model to the attributes that are needed to download and train with it.
  
      Returns:
        Dictionary of information about the model, or None if the name isn't
        recognized
  
      # """
      data_url = 'http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz'
      # pylint: enable=line-too-long
      bottleneck_tensor_name = 'pool_3/_reshape:0'
      bottleneck_tensor_size = 2048
      input_width = 299
      input_height = 299
      input_depth = 3
      resized_input_tensor_name = 'Mul:0'
      model_file_name = 'classify_image_graph_def.pb'
      input_mean = 128
      input_std = 128
  
      return {
          'data_url': data_url,
          'bottleneck_tensor_name': bottleneck_tensor_name,
          'bottleneck_tensor_size': bottleneck_tensor_size,
          'input_width': input_width,
          'input_height': input_height,
          'input_depth': input_depth,
          'resized_input_tensor_name': resized_input_tensor_name,
          'model_file_name': model_file_name,
          'input_mean': input_mean,
          'input_std': input_std,
      }
  
  
  def add_jpeg_decoding(input_width, input_height, input_depth, input_mean,
                        input_std):
      """Adds operations that perform JPEG decoding and resizing to the graph..
  
      Args:
        input_width: Desired width of the image fed into the recognizer graph.
        input_height: Desired width of the image fed into the recognizer graph.
        input_depth: Desired channels of the image fed into the recognizer graph.
        input_mean: Pixel value that should be zero in the image for the graph.
        input_std: How much to divide the pixel values by before recognition.
  
      Returns:
        Tensors for the node to feed JPEG data into, and the output of the
          preprocessing steps.
      """
      jpeg_data = tf.placeholder(tf.string, name='DecodeJPGInput')
      decoded_image = tf.image.decode_jpeg(jpeg_data, channels=input_depth)
      decoded_image_as_float = tf.cast(decoded_image, dtype=tf.float32)
      decoded_image_4d = tf.expand_dims(decoded_image_as_float, 0)
      resize_shape = tf.stack([input_height, input_width])
      resize_shape_as_int = tf.cast(resize_shape, dtype=tf.int32)
      resized_image = tf.image.resize_bilinear(decoded_image_4d,
                                               resize_shape_as_int)
      offset_image = tf.subtract(resized_image, input_mean)
      mul_image = tf.multiply(offset_image, 1.0 / input_std)
      return jpeg_data, mul_image
  
  
  def main(_):
      # Needed to make sure the logging output is visible.
      # See https://github.com/tensorflow/tensorflow/issues/3047
      tf.logging.set_verbosity(tf.logging.INFO)
  
      # Prepare necessary directories that can be used during training
      prepare_file_system()
  
      # Gather information about the model we'll be using.
      model_info = create_model_info()
  
      # Set up the pre-trained grapha.
      maybe_download_and_extract(model_info['data_url'])
      graph, bottleneck_tensor, resized_image_tensor = create_model_graph(model_info)
  
      # Look at the folder structure, and create lists of all the images.
      image_lists = create_image_lists()
      with open(ALL_LABELS_FILE) as f:
          labels = f.read().splitlines()
      class_count = len(labels)
      # if class_count == 0:
      #   tf.logging.error('No valid folders of images found at ' + FLAGS.image_dir)
      #   return -1
      # if class_count == 1:
      #   tf.logging.error('Only one valid folder of images found at ' +
      #                    FLAGS.image_dir +
      #                    ' - multiple classes are needed for classification.')
      #   return -1
  
      # See if the command-line flags mean we're applying any distortions.
      # 数据增强
      do_distort_images = should_distort_images(
          FLAGS.flip_left_right, FLAGS.random_crop, FLAGS.random_scale,
          FLAGS.random_brightness)
  
      with tf.Session(graph=graph) as sess:
          # Set up the image decoding sub-graph.
          jpeg_data_tensor, decoded_image_tensor = add_jpeg_decoding(
              model_info['input_width'], model_info['input_height'],
              model_info['input_depth'], model_info['input_mean'],
              model_info['input_std'])
  
          if do_distort_images:
              # We will be applying distortions, so setup the operations we'll need.
              (distorted_jpeg_data_tensor,
               distorted_image_tensor) = add_input_distortions(
                  FLAGS.flip_left_right, FLAGS.random_crop, FLAGS.random_scale,
                  FLAGS.random_brightness, model_info['input_width'],
                  model_info['input_height'], model_info['input_depth'],
                  model_info['input_mean'], model_info['input_std'])
          else:
              # We'll make sure we've calculated the 'bottleneck' image summaries and
              # cached them on disk.
              cache_bottlenecks(sess, image_lists, FLAGS.image_dir,
                                FLAGS.bottleneck_dir, jpeg_data_tensor,
                                decoded_image_tensor, resized_image_tensor,
                                bottleneck_tensor)
  
          # Add the new layer that we'll be training.
          (train_step, cross_entropy, bottleneck_input, ground_truth_input,
           final_tensor) = add_final_training_ops(
              class_count, FLAGS.final_tensor_name, bottleneck_tensor,
              model_info['bottleneck_tensor_size'])
  
          # Create the operations we need to evaluate the accuracy of our new layer.
          evaluation_step = add_evaluation_step(
              final_tensor, ground_truth_input)
  
          # Merge all the summaries and write them out to the summaries_dir
          merged = tf.summary.merge_all()
          train_writer = tf.summary.FileWriter(FLAGS.summaries_dir + '/train',
                                               sess.graph)
  
          validation_writer = tf.summary.FileWriter(
              FLAGS.summaries_dir + '/validation')
  
          # Set up all our weights to their initial default values.
          init = tf.global_variables_initializer()
          sess.run(init)
  
          # Run the training for as many cycles as requested on the command line.
          for i in range(FLAGS.how_many_training_steps):
              # Get a batch of input bottleneck values, either calculated fresh every
              # time with distortions applied, or from the cache stored on disk.
              if do_distort_images:
                  (train_bottlenecks,
                   train_ground_truth) = get_random_distorted_bottlenecks(
                      sess, image_lists, FLAGS.train_batch_size, TRAINING,
                      FLAGS.image_dir, distorted_jpeg_data_tensor,
                      distorted_image_tensor, resized_image_tensor, bottleneck_tensor, labels)
              else:
                  (train_bottlenecks, train_ground_truth, _) = get_random_cached_bottlenecks(
                      sess, image_lists, FLAGS.train_batch_size, 'training',
                      FLAGS.bottleneck_dir, FLAGS.image_dir, jpeg_data_tensor,
                      decoded_image_tensor, resized_image_tensor, bottleneck_tensor, labels)
              # Feed the bottlenecks and ground truth into the graph, and run a training
              # step. Capture training summaries for TensorBoard with the `merged` op.
              train_summary, _ = sess.run(
                  [merged, train_step],
                  feed_dict={bottleneck_input: train_bottlenecks,
                             ground_truth_input: train_ground_truth})
              train_writer.add_summary(train_summary, i)
  
              # Every so often, print out how well the graph is training.
              is_last_step = (i + 1 == FLAGS.how_many_training_steps)
              if (i % FLAGS.eval_step_interval) == 0 or is_last_step:
                  train_accuracy, cross_entropy_value = sess.run(
                      [evaluation_step, cross_entropy],
                      feed_dict={bottleneck_input: train_bottlenecks,
                                 ground_truth_input: train_ground_truth})
                  tf.logging.info('%s: Step %d: Train accuracy = %.1f%%' %
                                  (datetime.now(), i, train_accuracy * 100))
                  tf.logging.info('%s: Step %d: Cross entropy = %f' %
                                  (datetime.now(), i, cross_entropy_value))
                  validation_bottlenecks, validation_ground_truth, _ = (
                      get_random_cached_bottlenecks(
                          sess, image_lists, FLAGS.validation_batch_size, VALIDATION,
                          FLAGS.bottleneck_dir, FLAGS.image_dir, jpeg_data_tensor,
                          decoded_image_tensor, resized_image_tensor, bottleneck_tensor,
                          labels))
                  # Run a validation step and capture training summaries for TensorBoard
                  # with the `merged` op.
                  validation_summary, validation_accuracy = sess.run(
                      [merged, evaluation_step],
                      feed_dict={bottleneck_input: validation_bottlenecks,
                                 ground_truth_input: validation_ground_truth})
                  validation_writer.add_summary(validation_summary, i)
                  tf.logging.info('%s: Step %d: Validation accuracy = %.1f%% (N=%d)' %
                                  (datetime.now(), i, validation_accuracy * 100,
                                   len(validation_bottlenecks)))
  
              # Store intermediget_random_cached_bottlenecksate results
              intermediate_frequency = FLAGS.intermediate_store_frequency
  
              if (intermediate_frequency > 0 and (i % intermediate_frequency == 0)
                      and i > 0):
                  intermediate_file_name = (FLAGS.intermediate_output_graphs_dir +
                                            'intermediate_' + str(i) + '.pb')
                  tf.logging.info('Save intermediate result to : ' +
                                  intermediate_file_name)
                  save_graph_to_file(sess, graph, intermediate_file_name)
  
          # We've completed all our training, so run a final test evaluation on
          # some new images we haven't used before.
          test_bottlenecks, test_ground_truth, _ = (
              get_random_cached_bottlenecks(
                  sess, image_lists, FLAGS.test_batch_size, TESTING,
                  FLAGS.bottleneck_dir, FLAGS.image_dir, jpeg_data_tensor,
                  decoded_image_tensor, resized_image_tensor, bottleneck_tensor, labels))
          test_accuracy = sess.run(
              evaluation_step,
              feed_dict={bottleneck_input: test_bottlenecks,
                         ground_truth_input: test_ground_truth})
          tf.logging.info('Final test accuracy = %.1f%% (N=%d)' %
                          (test_accuracy * 100, len(test_bottlenecks)))
  
          # if FLAGS.print_misclassified_test_images:
          #     tf.logging.info('=== MISCLASSIFIED TEST IMAGES ===')
          #     for i, test_filename in enumerate(test_filenames):
          #         if predictions[i] != test_ground_truth[i].argmax():
          #             tf.logging.info('%70s  %s' %
          #                             (test_filename,
          #                              list(image_lists.keys())[predictions[i]]))
  
          # Write out the trained graph and labels with the weights stored as
          # constants.
          save_graph_to_file(sess, graph, FLAGS.output_graph)
          with gfile.FastGFile(FLAGS.output_labels, 'w') as f:
              f.write('\n'.join(image_lists.keys()) + '\n')
  
  
  if __name__ == '__main__':
      parser = argparse.ArgumentParser()
      parser.add_argument(
          '--image_dir',
          type=str,
          default='',
          help='Path to folders of labeled images.'
      )
      parser.add_argument(
          '--output_graph',
          type=str,
          default='/tmp/output_graph.pb',
          help='Where to save the trained graph.'
      )
      parser.add_argument(
          '--intermediate_output_graphs_dir',
          type=str,
          default='/tmp/intermediate_graph/',
          help='Where to save the intermediate graphs.'
      )
      parser.add_argument(
          '--intermediate_store_frequency',
          type=int,
          default=0,
          help="""\
           How many steps to store intermediate graph. If "0" then will not
           store.\
        """
      )
      parser.add_argument(
          '--output_labels',
          type=str,
          default='/tmp/output_labels.txt',
          help='Where to save the trained graph\'s labels.'
      )
      parser.add_argument(
          '--summaries_dir',
          type=str,
          default='/tmp/retrain_logs',
          help='Where to save summary logs for TensorBoard.'
      )
      parser.add_argument(
          '--how_many_training_steps',
          type=int,
          default=4000,
          help='How many training steps to run before ending.'
      )
      parser.add_argument(
          '--learning_rate',
          type=float,
          default=0.01,
          help='How large a learning rate to use when training.'
      )
      parser.add_argument(
          '--testing_percentage',
          type=int,
          default=10,
          help='What percentage of images to use as a test set.'
      )
      parser.add_argument(
          '--validation_percentage',
          type=int,
          default=10,
          help='What percentage of images to use as a validation set.'
      )
      parser.add_argument(
          '--eval_step_interval',
          type=int,
          default=10,
          help='How often to evaluate the training results.'
      )
      parser.add_argument(
          '--train_batch_size',
          type=int,
          default=100,
          help='How many images to train on at a time.'
      )
      parser.add_argument(
          '--test_batch_size',
          type=int,
          default=200,
          help="""\
        How many images to test on. This test set is only used once, to evaluate
        the final accuracy of the model after training completes.
        A value of -1 causes the entire test set to be used, which leads to more
        stable results across runs.\
        """
      )
      parser.add_argument(
          '--validation_batch_size',
          type=int,
          default=100,
          help="""\
        How many images to use in an evaluation batch. This validation set is
        used much more often than the test set, and is an early indicator of how
        accurate the model is during training.
        A value of -1 causes the entire validation set to be used, which leads to
        more stable results across training iterations, but may be slower on large
        training sets.\
        """
      )
      parser.add_argument(
          '--print_misclassified_test_images',
          default=False,
          help="""\
        Whether to print out a list of all misclassified test images.\
        """,
          action='store_true'
      )
      parser.add_argument(
          '--model_dir',
          type=str,
          default='/tmp/imagenet',
          help="""\
        Path to classify_image_graph_def.pb,
        imagenet_synset_to_human_label_map.txt, and
        imagenet_2012_challenge_label_map_proto.pbtxt.\
        """
      )
      parser.add_argument(
          '--bottleneck_dir',
          type=str,
          default='/tmp/bottleneck',
          help='Path to cache bottleneck layer values as files.'
      )
      parser.add_argument(
          '--final_tensor_name',
          type=str,
          default='final_result',
          help="""\
        The name of the output classification layer in the retrained graph.\
        """
      )
      parser.add_argument(
          '--flip_left_right',
          default=False,
          help="""\
        Whether to randomly flip half of the training images horizontally.\
        """,
          action='store_true'
      )
      parser.add_argument(
          '--random_crop',
          type=int,
          default=0,
          help="""\
        A percentage determining how much of a margin to randomly crop off the
        training images.\
        """
      )
      parser.add_argument(
          '--random_scale',
          type=int,
          default=0,
          help="""\
        A percentage determining how much to randomly scale up the size of the
        training images by.\
        """
      )
      parser.add_argument(
          '--random_brightness',
          type=int,
          default=0,
          help="""\
        A percentage determining how much to randomly multiply the training image
        input pixels up or down by.\
        """
      )
  
      FLAGS, unparsed = parser.parse_known_args()
      tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
  