TensorFlow之Cifar10数据集训练

一 Cifar10数据集介绍

    该数据集共有60000张彩色图像,这些图像是32*32,分为10个类,每类6000张图。这里面有50000张用于训练,构成了5个训练批,每一批10000张图;另外10000用于测试,单独构成一批。测试批的数据里,取自10类中的每一类,每一类随机取1000张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有5000张图。

    下面这幅图就是列举了10各类,每一类展示了随机的10张图片:

TensorFlow之Cifar10数据集训练_第1张图片

    该数据集共有三个版本:Python,Matlab,bin version。

    我们使用的是二进制版本,下载地址:CIFAR-10 and CIFAR-100 datasets(在程序代码中会自动下载)

    该版本包含5个训练批data_batch_1.bin, data_batch_2.bin, ..., data_batch_5.bin,1个测试批test_batch.bin。他们的格式都是:

<1 x label><3072 x pixel>
...
<1 x label><3072 x pixel> 

     也就是说,第一个字节是指类标,在0-9之间。接下来就是3072个字节,每个文件都有10000这样的3073个字节,没有任何分隔行,所以每个文件都是30730000字节的长度。

二 代码实现

    将训练程序和测试程序分开,先讲下训练程序。

def main(argv=None):  
  cifar10.maybe_download_and_extract() #下载数据   定义在cifar10.py文件中
  if gfile.Exists(FLAGS.train_dir):  #判断目录存不存在
    gfile.DeleteRecursively(FLAGS.train_dir)    #递归删除所有目录及其文件,dirname即目录名,无返回。
  gfile.MakeDirs(FLAGS.train_dir)     #创建一个目录,dirname为目录名字,无返回。
  train()                             #训练函数


if __name__ == '__main__':
  tf.app.run()

    关于tf.app.run() tensorflow文档中的解释如下:

模块:tf.app
定义在:tensorflow/python/platform/app.py

通用入口点脚本。

flags 模块
flags 模块:实现标志接口。

函数
run(...):使用可选的 “main” 函数和 “argv” 列表运行程序。

tf.app.run
run (  
    main = None ,  
    argv = None
  )
定义在:tensorflow/python/platform/app.py

使用可选的 “main” 函数和 “argv” 列表运行程序。

    相当于一个程序入口,其中FLAGS可以定义一些程序用到的参数,在我们训练中,需要定义的参数如下:

FLAGS = tf.app.flags.FLAGS

#添加命令行对的可选参数
#训练时存放事件文件的路径
tf.app.flags.DEFINE_string('train_dir', 'cifar10_train/',
                           """Directory where to write event logs """
                           """and checkpoint.""")
#最大训练
tf.app.flags.DEFINE_integer('max_steps', 20000,
                            """Number of batches to run.""")

tf.app.flags.DEFINE_boolean('log_device_placement', False,
                            """Whether to log device placement.""")

    接下来我们来看下载数据得函数: cifar10.maybe_download_and_extract()

def maybe_download_and_extract():
  """Download and extract the tarball from Alex's website."""
  dest_directory = FLAGS.data_dir
  if not os.path.exists(dest_directory):
    os.mkdir(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('\r>> Downloading %s %.1f%%' % (filename,
          float(count * block_size) / float(total_size) * 100.0))
      sys.stdout.flush()
    filepath, _ = urllib.request.urlretrieve(DATA_URL, filepath,
                                             reporthook=_progress)
    print()
    statinfo = os.stat(filepath)
    print('Successfully downloaded', filename, statinfo.st_size, 'bytes.')
    tarfile.open(filepath, 'r:gz').extractall(dest_directory)

    下载数据且解压后,我们看到在我们的项目目录下已经可以看到下载好的数据。

TensorFlow之Cifar10数据集训练_第2张图片

    进入train()函数:

def train():
  """Train CIFAR-10 for a number of steps."""
  #在默认图上进行操作
  with tf.Graph().as_default():
    #全局步骤
    global_step = tf.Variable(0, trainable=False)

    # Get images and labels for CIFAR-10.
    #调用输入函数得到数据和标签
    images, labels = cifar10.distorted_inputs()

    首先调用cifar10.distorted_inputs()

def distorted_inputs():
#该函数用于从文件中读取数据预处理
#图像数据一个四维张量 包括批处理大小 宽 高 信道数
#标签数据 一个一维张量 批处理大小
  """Construct distorted input for CIFAR training using the Reader ops.

  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.

  Raises:
    ValueError: If no data_dir
  """
  #如果数据路径不存在,将会抛异常
  if not FLAGS.data_dir:
    raise ValueError('Please supply a data_dir')
  data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin')
  #调用 cifar10_input.distorted_inputs函数进行数据导入
  return cifar10_input.distorted_inputs(data_dir=data_dir,
                                        batch_size=FLAGS.batch_size)

    cifar10_input.distorted_inputs函数

def distorted_inputs(data_dir, batch_size):
  #合并数据目录
  filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
               for i in xrange(1, 6)]

  #判断目录是否存在

  for f in filenames:
    if not gfile.Exists(f):
      raise ValueError('Failed to find file: ' + f)

  #生成一个文件队列对象
  # Create a queue that produces the filenames to read.
  filename_queue = tf.train.string_input_producer(filenames)

  # Read examples from files in the filename queue.
  #对图片格式进行转换为float32
  read_input = read_cifar10(filename_queue)
  #得到的图像张量转为float32格式
  reshaped_image = tf.cast(read_input.uint8image, tf.float32)

  height = IMAGE_SIZE
  width = IMAGE_SIZE

 
  #接下来就是对图像进行一些扭曲操作,来增强数据。
  #对图像进行随机裁剪
  distorted_image = tf.random_crop(reshaped_image, [height, width,3])

 
  #对图像进行左右翻转
  distorted_image = tf.image.random_flip_left_right(distorted_image)

 
  #对图像进行亮度变化
  distorted_image = tf.image.random_brightness(distorted_image,

                                   max_delta=63)
  # 对图像进行对比度变化
  distorted_image = tf.image.random_contrast(distorted_image,
                                             lower=0.2, upper=1.8)

 
  #对图像归一化操作也叫做白化
  float_image = tf.image.per_image_standardization(distorted_image)

  # Ensure that the random shuffling has good mixing properties.
  #确保随机洗牌具有好的混合性能
  
  #随机从文件队列取出的数据数量
  min_fraction_of_examples_in_queue = 0.4
  min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN *
                           min_fraction_of_examples_in_queue)
  print ('Filling queue with %d CIFAR images before starting to train. '
         'This will take a few minutes.' % min_queue_examples)

  # Generate a batch of images and labels by building up a queue of examples.
  #通过建立一个示例队列来生成一批图像和标签。
  #返回
  return _generate_image_and_label_batch(float_image, read_input.label,
                                         min_queue_examples, batch_size)

    我们先讲下这个函中一些API。

os.path.join
os.path.join()函数用于路径拼接文件路径。 
os.path.join()函数中可以传入多个路径

filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
               for i in xrange(1, 6)]

相当于将五个测试文件目录放到组成一个列表,路径是我电脑中的路径
filenames = [E:\tensorflow.cifar10-master\tensorflow.cifar10-master\cifar10_data\cifar-10-batches-bin\data_batch_1.bin,...,E:\tensorflow.cifar10-master\tensorflow.cifar10-master\cifar10_data\cifar-10-batches-bin\data_batch_5.bin]这样一个列表

从文件导入
从文件导入记录的典型管道有以下几个阶段:

文件名列表
可选文件名洗牌
可选时期限制
文件名队列
用于文件格式的读取器
读者用于读取记录的解码器
可选预处理
示例队列

文件名,shuffling 和 epoch 限制
对于文件名列表,请使用常量字符串张量(如["file0", "file1"]或[("file%d" % i) for i in range(2)])或函数:tf.train.match_filenames_once。

将文件名列表传递给 tf.train.string_input_producer 函数。string_input_producer 创建一个 FIFO 队列,用于保存文件名,直到读取器需要它们为止。

string_input_producer 有选择的 shuffling 和设置一个最大的 epoch 数。队列运行程序为每个 epoch 将文件名的整个列表添加到队列中一次,如果洗牌 = True,则在一个 epoch 中重新排列文件名。此过程提供了一个统一的文件取样,以便相对于彼此不会对示例进行低估或过度采样。

队列运行程序在与从队列中抽取文件名的读取器分开的线程中工作,因此,shuffling 和  enqueuing 进程不会阻止读取器。

文件格式
选择与您的输入文件格式相匹配的读取器,并将文件名队列传递给读取器的读取方法。read 方法输出一个标识文件和记录的密钥 (如果有一些奇怪的记录,则对调试有用) 和一个标量字符串值。使用一个 (或多个) 解码器和转换 ops 将此字符串解码为构成示例的张量。
filename_queue = tf.train.string_input_producer(filenames)

tf.train.string_input_producer
创建一个文件处理队列

    接下来调用一个函数read_input = read_cifar10(filename_queue)

def read_cifar10(filename_queue):

  #创建一个类,用于返回
  class CIFAR10Record(object):
    pass
  #创建一个类对象,用于返回
  result = CIFAR10Record()

  #标签字节数
  label_bytes = 1  # 2 for CIFAR-100
  #图像大小 高 宽 信道
  result.height = 32
  result.width = 32
  result.depth = 3
  #一张图片字节数
  image_bytes = result.height * result.width * result.depth
  #一次读文件需要读取的字节数 包括图像和标签 
  record_bytes = label_bytes + image_bytes
  
  #创建一个阅读器reader,读取的长度为record_bytes
  reader = tf.FixedLengthRecordReader(record_bytes=record_bytes)
  #从文件队列中,读取返回图像和标签。
  result.key, value = reader.read(filename_queue)

  #将图像和标签从字符串格式转化为uint8的张量
  record_bytes = tf.decode_raw(value, tf.uint8)

  #slice切片操作,第一个字节是label,[0]表示从第一维第一个位置开始,label_bytes代表大小
  #tf.int32为转化的格式

  result.label = tf.cast(
      tf.slice(record_bytes, [0], [label_bytes]), tf.int32)

  #slice继续将剩下的字节切成图像
  #然后将其reshape变成深 高 宽
  depth_major = tf.reshape(tf.slice(record_bytes, [label_bytes], [image_bytes]),
                           [result.depth, result.height, result.width])
  # Convert from [depth, height, width] to [height, width, depth].
  #利用transpose改变维度的位置  将深 高 宽 变为 高 宽 深
  result.uint8image = tf.transpose(depth_major, [1, 2, 0])
  返回我们得到的图像和标签结果。
  return result

    来看_generate_image_and_label_batch(float_image, read_input.label, min_queue_examples, batch_size)函数。这个函数建立一批图像和标签。

def _generate_image_and_label_batch(image, label, min_queue_examples,
                                    batch_size):
  #构造一批图像和标签的队列
  """Construct a queued batch of images and labels.

  Args:
    image: 3-D Tensor of [height, width, 3] of type.float32.
    label: 1-D Tensor of type.int32
    #最小的保留样本
    min_queue_examples: int32, minimum number of samples to retain
      in the queue that provides of batches of examples.
    #每一批数据的大小
    batch_size: Number of images per batch.

  Returns:
    images: Images. 4D tensor of [batch_size, height, width, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  # Create a queue that shuffles the examples, and then
  # read 'batch_size' images + labels from the example queue.
  #创建一个队列,该队列将这些示例进行洗牌,然后读取“batchsize”图像+来自示例队列的标签。

  #线程数量=16
  num_preprocess_threads = 16

  #将队列中的数据洗牌在读取,默认返回一个读取tensor_list数据类型和一个tensor列表.
  images, label_batch = tf.train.shuffle_batch(
      [image, label],                       #入队的张量列表
      batch_size=batch_size,                #批大小
      num_threads=num_preprocess_threads,   #设置num_threads的值大于1,使用多个线程在
                                            #tensor_list中读取文件,这样保证了同一时刻只在一
                                            #个文件中进行读取操作(但是读取速度依然优于单线
                                            #程),而不是之前的同时读取多个文件。

      capacity=min_queue_examples + 3 * batch_size, #队列容量
      min_after_dequeue=min_queue_examples)  #最小保留在队列中元素个数

  # Display the training images in the visualizer.
  #展示训练过程中的图像。
  tf.summary.image('images', images)
  #将形成批次的图像和标签返回
  return images, tf.reshape(label_batch, [batch_size])

    经过如上步骤,我们图像输入处理就准备完了,接下来我们来看下train()函数剩余的代码。

    logits = cifar10.inference(images),inference函数用来构建神经网络结构。

def inference(images):
  """Build the CIFAR-10 model.
  #输入我们获取的批处理图的张量
  Args:
    images: Images returned from distorted_inputs() or inputs().
  #返回神经网络最后的输出结果
  Returns:
    Logits.
  """
  # We instantiate all variables using tf.get_variable() instead of
  # tf.Variable() in order to share variables across multiple GPU training runs.
  # If we only ran this model on a single GPU, we could simplify this function
  # by replacing all instances of tf.get_variable() with tf.Variable().
  #
  # conv1
  #第一层卷积层
  #创建变量范围,所有在该范围定义的变量
  with tf.variable_scope('conv1') as scope:
    #生成卷积核
    kernel = _variable_with_weight_decay('weights', shape=[5, 5, 3, 64],
                                         stddev=1e-4, wd=0.0)
    #卷积操作
    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
    #偏置初始化
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
    #加上偏置
    bias = tf.nn.bias_add(conv, biases)
    #激活函数选用Relu
    conv1 = tf.nn.relu(bias, name=scope.name)
    _activation_summary(conv1)

  #第一个池化层
  # pool1
  pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                         padding='SAME', name='pool1')
  # norm1
  #局部响应归一化,主要用来防止过拟合,一般用在激活函数之后
  norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm1')

  # conv2
  with tf.variable_scope('conv2') as scope:
    kernel = _variable_with_weight_decay('weights', shape=[5, 5, 64, 64],
                                         stddev=1e-4, wd=0.0)
    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
    bias = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(bias, name=scope.name)
    _activation_summary(conv2)

  # norm2
  norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm2')
  # pool2
  pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                         strides=[1, 2, 2, 1], padding='SAME', name='pool2')

  # local3
  with tf.variable_scope('local3') as scope:
    # Move everything into depth so we can perform a single matrix multiply.
    dim = 1
    #pool2的维度为 batch_size,height,weight,depth.所以从第一个维度也即height计算相乘。
    for d in pool2.get_shape()[1:].as_list():
      dim *= d
    #将其维度改变
    reshape = tf.reshape(pool2, [FLAGS.batch_size, dim])

    weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
    #矩阵乘法 (B_size x dim) * (dim X 384) = B_size X 384
    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
    _activation_summary(local3)

  # local4
  # local3 = B_size X 384
  with tf.variable_scope('local4') as scope:
    weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))

    #矩阵乘法 (B_size X 384) * (384 X 192) = B_szie X 192

    local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
    _activation_summary(local4)

  # softmax, i.e. softmax(WX + b)
  # local4 = B_szie X 192

  with tf.variable_scope('softmax_linear') as scope:
    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                          stddev=1/192.0, wd=0.0)
    biases = _variable_on_cpu('biases', [NUM_CLASSES],
                              tf.constant_initializer(0.0))
   #矩阵乘法 (B_size X 192) * (192 X NUM_CLASSES=10) = B_size X NUM_CLASSES

    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
    _activation_summary(softmax_linear)
  
  #将最后结果返回
  return softmax_linear

    在inference函数中,有一些函数我们需要解释下。

def _variable_with_weight_decay(name, shape, stddev, wd):
  #参数 name 名字
  #shape 张量维度
  #stddev 标准差  
  #wd 用来指定是否权重衰减,也就是后面L2正则化。

  #截取的正太分布
  var = _variable_on_cpu(name, shape,
                         tf.truncated_normal_initializer(stddev=stddev))
  #如果使用权重衰减,则使用L2正则化。
  if wd:
    #tf.nn.l2_loss(var) 利用L2范数来计算张量的误差值。
    weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss')
    #向计算图中添加张量集合。
    tf.add_to_collection('losses', weight_decay)
  #返回得到的权重weight
  return var

    在上面那个函数中我们用到了_variable_on_cpu函数。

def _variable_on_cpu(name, shape, initializer):
  #创建一个存储在Cpu上的变量
  """Helper to create a Variable stored on CPU memory.

  Args:
    name: name of the variable
    shape: list of ints
    initializer: initializer for Variable

  Returns:
    Variable Tensor
  """
  with tf.device('/cpu:0'):
    var = tf.get_variable(name, shape, initializer=initializer)
  return var

    在第一层卷积中,最后调用了_activation_summary(conv1)。

def _activation_summary(x):
  """Helper to create summaries for activations.

  #创建一个汇总,提供激活的直方图
  Creates a summary that provides a histogram of activations.
  #创建一个总结,用来测量激活的稀疏性
  Creates a summary that measure the sparsity of activations.

  Args:
    x: Tensor
  Returns:
    nothing
  """
  # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training
  # session. This helps the clarity of presentation on tensorboard.
  #re.sub功能是对于一个输入的字符串,利用正则表达式,来实现字符串替换处理的功能返回处理后的字符串
  tensor_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', x.op.name)
  #添加x到直方图中
  tf.summary.histogram(tensor_name + '/activations', x)
  #添加标量
  tf.summary.scalar(tensor_name + '/sparsity', tf.nn.zero_fraction(x))

    inference函数返回,继续回到train函数。loss = cifar10.loss(logits, labels)。

def loss(logits, labels):
  #增加L2正则化给所有训练的参数
  #增加
  """Add L2Loss to all the trainable variables.

  Add summary for for "Loss" and "Loss/avg".
  Args:
    logits: Logits from inference().
    labels: Labels from distorted_inputs or inputs(). 1-D tensor
            of shape [batch_size]

  Returns:
    Loss tensor of type float.
  """

  # Reshape the labels into a dense Tensor of
  # shape [batch_size, NUM_CLASSES].

  #将标签转化为batch大小的一维向量
  sparse_labels = tf.reshape(labels, [FLAGS.batch_size, 1])

  #将0~bacth_size转换为一维向量
  indices = tf.reshape(tf.range(FLAGS.batch_size), [FLAGS.batch_size, 1])
  #拼接成
  # 0   num1
  # 1   num2
  # 2   num3
  #这样的形式
  concated = tf.concat([indices, sparse_labels],1)
  
  #tf.sparse_to_dense函数
  #第一个参数 如果是个矩阵,那么它可以指定二维矩阵多个元素
  #第二个参数 是指输出矩阵的维度 
  #第三个 指定为1
  #第四个 没指定的为0
  #例子  如果concated = [[0,1],[1,4]],且batch_size,num_classes = 4, 5.
  #则结果为:
  #    0 1 0 0 0
  #    0 0 0 0 1
  #    0 0 0 0 0
  #    0 0 0 0 0
  dense_labels = tf.sparse_to_dense(concated,
                                    [FLAGS.batch_size, NUM_CLASSES],
                                    1.0, 0.0)

  #求交叉熵 返回一个向量
  cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
      logits=logits, labels=dense_labels, name='cross_entropy_per_example')
  #求一批数据均值
  cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
  #将交叉熵均值放入集合“losses”中
  tf.add_to_collection('losses', cross_entropy_mean)

  # The total loss is defined as the cross entropy loss plus all of the weight
  #总的损失定义为交叉熵损失加上所以权重
  # decay terms (L2 loss).
  #将集合中所有损失相加再返回
  return tf.add_n(tf.get_collection('losses'), name='total_loss')

    train_op = cifar10.train(loss, global_step)进入这个函数。

def train(total_loss, global_step):
  #创建一个优化器,应用于所有的可训练变量
  #给所有可训练变量添加移动平均计算
  """Train CIFAR-10 model.

  Create an optimizer and apply to all trainable variables. Add moving
  average for all trainable variables.

  Args:
    total_loss: Total loss from loss().
    global_step: Integer Variable counting the number of training steps
      processed.
  Returns:
    train_op: op for training.
  """
  # Variables that affect learning rate.
  #多少批次一个训练周期
  num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
  #衰减速度
  decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)

  # Decay the learning rate exponentially based on the number of steps.
  #1 初始学习率
  #2 全局变量 训练步数
  #3 decay_step
  #4 为衰减速率
  #5 =True 每decay_step改变一次学习率,=False每一步都改变。
  # learn_rate = INITIAL_LEARNING_RATE *  LEARNING_RATE_DECAY_FACTOR
  lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                  global_step,
                                  decay_steps,
                                  LEARNING_RATE_DECAY_FACTOR,
                                  staircase=True)
  #将标量lr添加
  tf.summary.scalar('learning_rate', lr)

  # Generate moving averages of all losses and associated summaries.
  #生成所有损失和相关摘要的移动平均值。

  loss_averages_op = _add_loss_summaries(total_loss)

  # Compute gradients.
  #计算梯度
  #tf.control_dependencies用来实现某些操作的依赖关系
  #也就是说loss_average_op执行完,才能执行下面的语句。
  with tf.control_dependencies([loss_averages_op]):
    #梯度下降
    opt = tf.train.GradientDescentOptimizer(lr)
    #计算梯度 应该是个字典
    grads = opt.compute_gradients(total_loss)

  # Apply gradients.
  #应用梯度
  apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

  # Add histograms for trainable variables.
  #给可训练变量增加一个柱状图
  for var in tf.trainable_variables():
    tf.summary.histogram(var.op.name, var)

  # Add histograms for gradients.
  for grad, var in grads:
      if grad is not None:
          tf.summary.histogram(var.op.name + '/gradients', grad)

  # Track the moving averages of all trainable variables.
  #追踪所有可训练变量的移动平均值

  variable_averages = tf.train.ExponentialMovingAverage(
      MOVING_AVERAGE_DECAY, global_step)
  variables_averages_op = variable_averages.apply(tf.trainable_variables())

  with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
    train_op = tf.no_op(name='train')   #什么也不做

  return train_op

    来看下_add_loss_summaries(total_loss)函数。

def _add_loss_summaries(total_loss):
  """Add summaries for losses in CIFAR-10 model.

  #为所有损失和相关摘要生成移动平均可视化网络的性能。
  Generates moving average for all losses and associated summaries for
  visualizing the performance of the network.

  Args:
    total_loss: Total loss from loss().
  Returns:
    loss_averages_op: op for generating moving averages of losses.
  """
  # Compute the moving average of all individual losses and the total loss.
  #指数移动平均计算损失
  loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')

  #获取集合中添加的损失值
  losses = tf.get_collection('losses')
  
  #计算losses和total_loss中的指数加权平均
  loss_averages_op = loss_averages.apply(losses + [total_loss])

  # Attach a scalar summary to all individual losses and the total loss; do the
  # same for the averaged version of the losses.
  #对所有的个人损失和全部损失附加一个标量摘要;对平均的损失也做同样的事情。
  for l in losses + [total_loss]:
    # Name each loss as '(raw)' and name the moving average version of the loss
    # as the original loss name.
    #将每个损失命名为“(原始的)”,并将损失的移动平均版本命名为原始损失名称。

    tf.summary.scalar(l.op.name +' (raw)', l)
    tf.summary.scalar(l.op.name, loss_averages.average(l))
  #将指数加权平均操作返回
  return loss_averages_op

    接下来三个语句代码:

# Create a saver.
    #创建一个Saver保存模型
    saver = tf.train.Saver(tf.all_variables())

    # Build the summary operation based on the TF collection of Summaries.
    #自动管理
    summary_op = tf.summary.merge_all()
    
    #所有变量初始化
    init = tf.initialize_all_variables()

    剩下来的语句就是输出:

 # Start running operations on the Graph.
    #开始在图中计算
    #进行一些配置
    sess = tf.Session(config=tf.ConfigProto(
        log_device_placement=FLAGS.log_device_placement))
    sess.run(init)

    # Start the queue runners.
    #TensorFlow的Session对象是可以支持多线程的,因此多个线程可以很方便地使用同一个会话
    #(Session)并且并行地执行操作。然而,在Python程序实现这样的并行运算却并不容易。
    #所有线程都必须能被同步终止,异常必须能被正确捕获并报告,回话终止的时候, 队列必须能被正确地
    #关闭。
    tf.train.start_queue_runners(sess=sess)

    #将事件写入目录
    summary_writer = tf.summary.FileWriter(FLAGS.train_dir,
                                            graph_def=sess.graph_def)

    开始训练
    for step in xrange(FLAGS.max_steps):
      #记录开始的事件
      start_time = time.time()   
      _, loss_value = sess.run([train_op, loss])
      #计算一批次花了多少事件
      duration = time.time() - start_time
      assert not np.isnan(loss_value), 'Model diverged with loss = NaN'

      #10次一个输出
      if step % 10 == 0:
        num_examples_per_step = FLAGS.batch_size
        
        #多少批次一秒
        examples_per_sec = num_examples_per_step / duration 
        sec_per_batch = float(duration)

        format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                      'sec/batch)')
        #输出多少样本一秒,多少批次一秒
        print (format_str % (datetime.now(), step, loss_value,
                             examples_per_sec, sec_per_batch))
      #每100次 就写入summary
      if step % 100 == 0:
        summary_str = sess.run(summary_op)
        summary_writer.add_summary(summary_str, step)

      # Save the model checkpoint periodically.
      #每1000次就保存一个模型
      if step % 1000 == 0 or (step + 1) == FLAGS.max_steps:
        checkpoint_path = os.path.join(FLAGS.train_dir, 'model.ckpt')
        saver.save(sess, checkpoint_path, global_step=step)

    训练完成后,模型保存在我们的目录中:

TensorFlow之Cifar10数据集训练_第3张图片

三 训练结果

     特别吐槽下渣渣笔记本电脑,训练很久。

     先看下随着迭代次数的增加,Loss的变化情况。我们一共迭代了20000次,所以每5000选取100个情况来看下Loss的值。

TensorFlow之Cifar10数据集训练_第4张图片

     在最开始的训练,loss大约在4.5左右。

TensorFlow之Cifar10数据集训练_第5张图片

    迭代次数达到5000次,我们看到loss已经变为1.1左右。说明随着迭代次数的增加,loss在减少。

TensorFlow之Cifar10数据集训练_第6张图片

    迭代次数达到10000次,此时loss和之前5000迭代次数相差不大。

TensorFlow之Cifar10数据集训练_第7张图片

    

TensorFlow之Cifar10数据集训练_第8张图片

    虽然经过了20000次的训练次数,但loss已经不能在下降了。说明loss在5000次迭代下,已经达到了最优。只能通过别的方法来优化模型。

    接下来来看下tensorboard下,各个变量以及参数的变化情况。

TensorFlow之Cifar10数据集训练_第9张图片

    可以看出学习率在5000左右已经不再明显变化,也正好验证了在5000次迭代次数下,loss已经收敛。来看下total_loss的情况。

TensorFlow之Cifar10数据集训练_第10张图片

    total_loss开始随着迭代次数逐渐减小,近5000左右,开始趋于平稳。

    来看下各层卷积的参数变化。

    第一卷积层偏置的梯度。

TensorFlow之Cifar10数据集训练_第11张图片

        第二层卷积层偏置梯度变化。

TensorFlow之Cifar10数据集训练_第12张图片

    第一层卷积层权重

TensorFlow之Cifar10数据集训练_第13张图片

    第二层卷积层权重

TensorFlow之Cifar10数据集训练_第14张图片

    我们来看下两层卷积神经激活函数的稀疏性。

TensorFlow之Cifar10数据集训练_第15张图片

TensorFlow之Cifar10数据集训练_第16张图片

    关于激活函数的稀疏性,可以看这篇博文:ReLu(Rectified Linear Units)激活函数 - Physcal - 博客园

    最后看下测试的结果。有83%的准确率。

TensorFlow之Cifar10数据集训练_第17张图片

四 代码

    将修改后的代码放在百度云上(没修改的代码都是基于tensorflow1.0以前的API,我用的是TensorFLow1.9,许多函数都已经弃用了)                                       

    百度网盘-链接不存在

    下载完后运行cifar10_train.py就可以直接训练,下载数据可能需要点时间。实测如果的话速度会快点。

    训练完毕后,运行cifar10_eval.py可以进行测试数据。因为还没怎么看懂测试的代码,以后再补上。

五 参考

源码来自TensorFlowtensorflow/models/image/cifar10/.

TensorFlow学习笔记(九):CIFAR-10训练例子报错解决_沫尘的博客-CSDN博客

CIFAR-10数据集说明 - 从菜鸟开始 - 博客园

tf.train.shuffle_batch函数解析_遗世独立的乌托邦-CSDN博客_shuffle_batch

tf.train.exponential_decay(学习率衰减)_张帅的博客-CSDN博客_tf.train.exponential_decay()

关于tensorflow 的数据读取线程管理QueueRunner_sunquan_ok的博客-CSDN博客

tf.control_dependencies()作用及用法_qishi的博客-CSDN博客

http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/deep_cnn.html

你可能感兴趣的:(Cifar10,卷积神经网络,数据集,TensorFlow,深度学习)