TensorFlow入门教程(9)CIFAR-10图像识别

#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#

1、概述

我们之前讲的都是基于MNIST数据集,这个数据集虽然比较经典,但是也比较简单,现在,我们来看看一个稍微复杂一丢丢的数据集---CIFAR-10数据集,再结合之前所学的知识,实现对CIFAR-10数据集的图像识别任务。

环境配置:

操作系统:Win10 64位

显卡:GTX 1080ti

Python:Python3.7

TensorFlow:1.15.0

 

2、CIFAR-10数据集简介

CIFAR-10数据集包含10个类别的RGB彩色图片。图片尺寸为32×32,这十个类别包括:飞机、汽车、鸟、猫、鹿、狗、蛙、马、船、卡车。一共有50000张训练图片和10000张测试图片。

CIFAR-10数据集有如下文件:

batches.meta.txt  data_batch_2.bin  data_batch_4.bin  readme.html

data_batch_1.bin  data_batch_3.bin  data_batch_5.bin  test_batch.bin

其中,data_batch_1.bin~data_batch_5.bin五个文件是训练数据,每个文件以二进制的格式存储10000张图片和这些图片对于的标签。test_batch.bin存储的是10000张测试图像的测试标签。一张图片和对于的标签组成一个样本,一个样本有3073个字节组成,第一个字节为标签,后面3072个字节是RGB图片数据---{1024(R)+ 1024(G) + 1024(B)}。

3、下载CIFAR-10数据集

首先下载tensorflow官方提供的识别CIFAR-10的代码,为了跟以前博客代码一样,我们指定下载的版本为r1.11

git clone -b r1.11  https://github.com/tensorflow/models.git

上面这个models文件夹下包含很多TensorFlow官方提供的深度学习的demo,我们要用的识别CIFAR-10的代码位于models/tutorials/image/cifar10目录下,下表是其主要文件及作用,

文件

作用

cifar10_input.py

读取本地CIFAR-10的二进制文件格式的内容。

cifar10.py

建立CIFAR-10的模型。

cifar10_train.py

在CPU或GPU上训练CIFAR-10的模型。

cifar10_multi_gpu_train.py

在多GPU上训练CIFAR-10的模型。

cifar10_eval.py

评估CIFAR-10模型的预测性能。

 下载好代码以后,将cifar10文件夹拷贝到我们的工程目录下。然后运行下面的代码就可以将CIFAR-10数据集下载下来了,

# coding:utf-8
# 导入官方cifar10模块
import cifar10
import tensorflow as tf

# tf.app.flags.FLAGS是tensorflow的一个内部全局变量存储器
FLAGS = tf.app.flags.FLAGS
# cifar10模块中预定义下载路径的变量data_dir为'/tmp/cifar10_eval',预定义如下:
# tf.app.flags.DEFINE_string('data_dir', '/tmp/cifar10_train',
#                           """Path to the CIFAR-10 data directory.""")
#为了方便,我们将这个路径改为当前位置
FLAGS.data_dir = './cifar10_dataset'

#如果不存在数据文件则下载,并且解压
cifar10.maybe_download_and_extract()

运行结果,

缺少tensorflow_datasets库,pip安装即可,执行下面命令,

pip install tensorflow_datasets

再来运行上面的代码,运行结果如下,

代码运行完以后在cifar10_dataset\cifar-10-batches-bin文件夹下得到上图所示的几个文件。

4、将CIFAR-10数据集保存为图片

结合第4讲的队列的知识,我们将CIRAR-10数据集保存为图片的形式,随机地获取50张图片即可。首先是下载数据集的代码,代码如下,

# 查看CIFAR-10数据是否存在,如果不存在则下载并解压
def download(dir):
    # tf.app.flags.FLAGS是tensorflow的一个内部全局变量存储器
    FLAGS = tf.app.flags.FLAGS
    # 为了方便,我们将这个路径改为当前位置
    FLAGS.data_dir = dir
    # 如果不存在数据文件则下载,并且解压
    cifar10.maybe_download_and_extract()

接着,将将CIRAR-10数据集的几个.bin文件放入队列中并解析,代码如下,

#检测CIFAR-10数据是否存在,如果不存在则返回False
def check_cifar10_data_files(filenames):
    for file in filenames:
        if os.path.exists(file) == False:
            print('Not found cifar10 data.')
            return False
    return True

#获取图片前的预处理,检测CIFAR10数据是否存在,如果不存在直接退出
#如果存在,用string_input_producer函数创建文件名队列,
# 并且通过get_record函数获取图片标签和图片数据,并返回
def get_image(data_path):
    filenames = [os.path.join(data_path, "data_batch_%d.bin" % i) for i in range(1, 6)]
    print(filenames)
    if check_cifar10_data_files(filenames) == False:
        exit()
    queue = tf.train.string_input_producer(filenames, shuffle=False)
    # return tf.cast((cifar10_input.read_cifar10(queue)).uint8image, tf.float32)
    return get_record(queue)

解析的函数是get_record,我们来看看这个函数的实现,代码如下,

#获取每个样本数据,样本由一个标签+一张图片数据组成
def get_record(queue):
    print('get_record')
    #定义label大小,图片宽度、高度、深度,图片大小、样本大小
    label_bytes = 1
    image_width = 32
    image_height = 32
    image_depth = 3
    image_bytes = image_width * image_height * image_depth
    record_bytes = label_bytes + image_bytes

    #根据样本大小读取数据
    reader = tf.FixedLengthRecordReader(record_bytes)
    key, value = reader.read(queue)

    #将获取的数据转变成一维数组
    #例如
    # source = 'abcde'
    # record_bytes = tf.decode_raw(source, tf.uint8)
    #运行结果为[ 97  98  99 100 101]
    record_bytes = tf.decode_raw(value, tf.uint8)

    #获取label,label数据在每个样本的第一个字节
    label_data = tf.cast(tf.strided_slice(record_bytes, [0], [label_bytes]), tf.int32)

    #获取图片数据,label后到样本末尾的数据即图片数据,
    # 再用tf.reshape函数将图片数据变成一个三维数组
    depth_major = tf.reshape(
        tf.strided_slice(record_bytes, [label_bytes],[label_bytes + image_bytes]),
        [3, 32, 32])

    #矩阵转置,上面得到的矩阵形式是[depth, height, width],即红、绿、蓝分别属于一个维度的,
    #假设只有3个像素,上面的格式就是RRRGGGBBB
    #但是我们图片数据一般是RGBRGBRGB,所以这里要进行一下转置
    #注:上面注释都是我个人的理解,不知道对不对
    image_data = tf.transpose(depth_major, [1, 2, 0])

    return label_data, image_data

上面的代码都是函数的定义,主函数的代码如下,

def main(argv=None):
    dir = "./cifar10_dataset/"
    #查看CIFAR-10数据是否存在,如果不存在则下载并解压
    download(dir)

    #将获取的图片保存到这里
    image_save_path = './cifar10_images/'
    if not os.path.exists(image_save_path):
        os.mkdir(image_save_path)

    #获取图片数据
    key, value = get_image(os.path.join(dir, 'cifar-10-batches-bin/'))

    with tf.Session() as sess:
        #初始化变量
        sess.run(tf.global_variables_initializer())
        coord = tf.train.Coordinator()
        #这里才真的启动队列
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)

        for i in range(50):
            label, data = sess.run([key, value])
            # 保存图片
            filename = os.path.join(image_save_path, '%d_%d.jpg' % (label, i))
            Image.fromarray(data).convert('RGB').save(filename)

        coord.request_stop()
        coord.join()

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

运行结果,

上面图片中,文件名的第一个数字代表图片所属的类别的标签,可以看到,标签为0的图片都是飞机,标签为1的都是汽车,说明我们的解析是没有问题的。

为了更好理解get_record函数怎么将每个样本数据提取并转换的过程,我再写个小例子,注释写得非常清楚了,我就不解释了,代码如下,

#encoding:utf-8
import tensorflow as tf

# 为了简化过程,假设一个4×4×3的样本数据如下,
# 其中,第一个字符“0”表示图片的标签label
# “1”表示图片颜色值的R通道,“2”表示G通道,“3”表示B通道
source = '0111111111111111122222222222222223333333333333333'
sourcelist = tf.decode_raw(source, tf.uint8)
#上面运行后得到的数据如下:(0的ASCII值是48,同理推出1、2、3的值为49,50,51,这不是重点不用关心)
#[48 49 49 49 49 49 49 49 49 49 49 49 49 49 49 49 49 50 50 50 50 50 50 50
# 50 50 50 50 50 50 50 50 50 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51
# 51]

#获取label
label = tf.strided_slice(sourcelist, [0], [1]);

#获取图片数据,并转为[3, 4, 4]的矩阵形式,其中,
#[1]表示从1下标开始截取,[49]表示截取到49下标,[3, 4, 4]中, 3表示通道数,4分别表示宽度和高度
image = tf.reshape(tf.strided_slice(sourcelist, [1], [49]), [3, 4, 4])
#上面运行后得到数据如下:
# [[[49 49 49 49]
#   [49 49 49 49]
#   [49 49 49 49]
#   [49 49 49 49]]
#
#  [[50 50 50 50]
#   [50 50 50 50]
#   [50 50 50 50]
#   [50 50 50 50]]
#
#  [[51 51 51 51]
#   [51 51 51 51]
#   [51 51 51 51]
#   [51 51 51 51]]]
#可以看到,RGB数据都分别在同一维度

#这里就是对上面得到的矩阵进行转置
image_transpose = tf.transpose(image, [1, 2, 0])
#上面运行后得到的数据如下
# [[[49 50 51]
#   [49 50 51]
#   [49 50 51]
#   [49 50 51]]
#
#  [[49 50 51]
#   [49 50 51]
#   [49 50 51]
#   [49 50 51]]
#
#  [[49 50 51]
#   [49 50 51]
#   [49 50 51]
#   [49 50 51]]
#
#  [[49 50 51]
#   [49 50 51]
#   [49 50 51]
#   [49 50 51]]]


with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    result = sess.run(tf.cast(sourcelist, tf.int32))
    print(result)
    result = sess.run(tf.cast(image, tf.int32))
    print(result)
    result = sess.run(tf.cast(image_transpose, tf.int32))
    print(result)

5、数据增强

结合第三讲的知识,我们为了防止过拟合,会使用数据增强的正则化方法。数据增强,说白了就是创建很多“假数据”并添加到数据集中,让数据集中数据量增加。对于图像任务,可以利用平移、翻转、缩放、颜色变换等操作,增大训练样本个数,但前提是,进行数据增强操作后,不会改变图像和标签的对应关系。比如说,如果是手写数字识别的任务,如果进行翻转或折叠操作,操作后的图片就不对应原来的标签,甚至都不是字了,这种情况下是不允许的。而如果对一只猫的图片进行翻转或者折叠操作,它还是一只猫,这种情况就是允许的。回到当前任务,对飞机或汽车等进行翻转、折叠、颜色变换操作以后,它还是原来的物种,所以,我们这里就可以使用数据增强来对数据集进行扩充。

TensorFlow对数据增强的操作进行了封装,使用起来就很方便了,我们简单举个上下翻转的例子来看看,首先是显示原图的代码,代码如下,

#encoding:utf-8
import tensorflow as tf
import matplotlib.pyplot as plt

def main(argv=None):
    # 读取原始图像数据
    image_data = tf.gfile.FastGFile('dog.jpg', 'rb').read()

    with tf.Session() as sess:
        # 对图像使用jpg格式解码,得到三维数据
        pltdata = tf.image.decode_jpeg(image_data)

        # 显示图像
        plt.imshow(pltdata.eval())
        plt.show()

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

运行结果,

可以看到这是一张狗的图片,然后,我们对其进行上下翻转操作看看,代码如下,

#encoding:utf-8
import tensorflow as tf
import matplotlib.pyplot as plt

def main(argv=None):
    # 读取原始图像数据
    image_data = tf.gfile.FastGFile('dog.jpg', 'rb').read()

    with tf.Session() as sess:
        # 对图像使用jpg格式解码,得到三维数据
        pltdata = tf.image.decode_jpeg(image_data)
        
        # 对图像上下翻转
        pltdata = tf.image.flip_up_down(pltdata)

        # 显示图像
        plt.imshow(pltdata.eval())
        plt.show()

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

运行结果,

可以看到,虽然对图片进行了翻转操作,但这并不影响它还是一条狗的图片。而翻转的代码也很简单,只需一行代码即可,

# 对图像上下翻转
pltdata = tf.image.flip_up_down(pltdata)

当然,还有很多数据增强操作的函数,我们后面用到再说,用法都是一样的。

6、指数衰减法设置学习率

在第三讲提到学习率的设置时有说过指数衰减法,TensorFlow也帮我们封装好了,函数是,

tf.train.exponential_decay

7、滑动平均模型

滑动平均模型可以使模型在测试数据上更加健壮。在采用随即梯度下降法训练神经网络时,使用滑动平均模型在很多应用中可以在一定程度上提高最终模型在测试数据上的表现。

TensorFlow提供了tf.train.ExponentialMovingAverage函数来实现滑动平均模型。在初始化该函数时需要提供一个衰减率(decay),这个衰减率将用于控制模型更新的速度。decay决定了模型更新速度,decay越大,模型越趋于稳定,一般设置成非常接近1的数,比如0.999或0.9999。

 

8、训练模型

我们这一讲主要是解析TensorFlow官方提供的识别CIFAR-10数据集的代码,主要讲解训练模型的代码,首先得让代码跑起来。

为了不重复下载数据集,我们将cifar10.py文件里的data_dir的路径改成'./cifar10_dataset',也就是我们上面下载的数据集的路径,修改后的代码如下,

tf.app.flags.DEFINE_string('data_dir', './cifar10_dataset', #'/tmp/cifar10_data',
                           """Path to the CIFAR-10 data directory.""")

为了将模型保存到当前工程目录下,我们再将cifar10_train.py文件的train_dir路径改成'./cifar10_train',修改后的代码如下,

tf.app.flags.DEFINE_string('train_dir', './cifar10_train', #'/tmp/cifar10_train',
                           """Directory where to write event logs """
                           """and checkpoint.""")

再执行下面命令即可。

python cifar10_train.py

运行结果如下,

9、源码分析

main函数

上面运行训练模型的代码既然是cifar10_train.py文件,那么就从cifar10_train.py文件的main函数看起,代码如下,

def main(argv=None):  # pylint: disable=unused-argument
  # 下载数据集
  cifar10.maybe_download_and_extract()
  if tf.gfile.Exists(FLAGS.train_dir):
    tf.gfile.DeleteRecursively(FLAGS.train_dir)
  tf.gfile.MakeDirs(FLAGS.train_dir)
  
  # 训练模型
  train()

上面代码是先下载数据集,然后创建保存模型的文件夹,然后再通过train函数训练模型,我们接着看这个train函数做了什么。

train函数

train函数内容比较多,我们将它拆分来看,代码如下,

def train():
  """Train CIFAR-10 for a number of steps."""
  with tf.Graph().as_default():
    global_step = tf.train.get_or_create_global_step()

    # Get images and labels for CIFAR-10.
    # Force input pipeline to CPU:0 to avoid operations sometimes ending up on
    # GPU and resulting in a slow down.
    with tf.device('/cpu:0'):
      images, labels = cifar10.distorted_inputs()
......

首先是创建一个global_step,这个在使用指数衰减法设置学习率和使用滑动平均模型时会用到,在使用tf.train.MonitoredTrainingSession方法保存模型也会用到这个变量。

cifar10.distorted_inputs()函数则是用来获取数据集图片和标签的,我们来看看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')
  images, labels = cifar10_input.distorted_inputs(data_dir=data_dir,
                                                  batch_size=FLAGS.batch_size)
  if FLAGS.use_fp16:
    images = tf.cast(images, tf.float16)
    labels = tf.cast(labels, tf.float16)
  return images, labels

可以看到,其将CIFAR-10数据集的路径和我们设置的batch_size传给cifar10_input.distorted_inputs函数,cifar10_input.distorted_inputs函数再返回图片和标签数据,那么,我们再来看cifar10_input.distorted_inputs函数怎么实现的。

cifar10_input.distorted_inputs函数

代码如下,

def distorted_inputs(data_dir, batch_size):
  """Construct distorted input for CIFAR training using the Reader ops.

  Args:
    data_dir: Path to the CIFAR-10 data directory.
    batch_size: Number of images per batch.

  Returns:
    images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
    labels: Labels. 1D tensor of [batch_size] size.
  """
  filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i)
               for i in xrange(1, 6)]
  for f in filenames:
    if not tf.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)

因为数据集的图片和标签数据是放在数据集的data_batch_1.bin~data_batch_5.bin文件里的,所以上面代码先将这些文件路径放在filenames 列表里,然后通过tf.train.string_input_producer(filenames)函数创建队列。接着往下看,

with tf.name_scope('data_augmentation'):
  # Read examples from files in the filename queue.
  read_input = read_cifar10(filename_queue)
  reshaped_image = tf.cast(read_input.uint8image, tf.float32)

read_cifar10函数的作用跟我们上面自己写的将数据集保存为图片代码中的get_record函数的作用是类似的,只不过这个函数返回的是一个类,标签数据存在read_input.label中,图片数据存在read_input.uint8image中。得到图片数据以后,再用tf.cast函数将其数据类型从uint8转成float32 。接着往下看,

height = IMAGE_SIZE
  width = IMAGE_SIZE

  # Image processing for training the network. Note the many random
  # distortions applied to the image.

  # Randomly crop a [height, width] section of the image.
  distorted_image = tf.random_crop(reshaped_image, [height, width, 3])

  # Randomly flip the image horizontally.
  distorted_image = tf.image.random_flip_left_right(distorted_image)

  # Because these operations are not commutative, consider randomizing
  # the order their operation.
  # NOTE: since per_image_standardization zeros the mean and makes
  # the stddev unit, this likely has no effect see tensorflow#1458.
  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)

  # Subtract off the mean and divide by the variance of the pixels.
  float_image = tf.image.per_image_standardization(distorted_image)

  # Set the shapes of tensors.
  float_image.set_shape([height, width, 3])
  read_input.label.set_shape([1])

  # 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,
                                       shuffle=True)

上面的操作都是对图片进行数据增强操作,先将图片随机切割成24x24大小的图片,然后随机进行左右翻转等等,最后将图片数据转成张量[24,24,3]的形式。我们来看看_generate_image_and_label_batch函数又做了什么。

_generate_image_and_label_batch函数

代码如下,

def _generate_image_and_label_batch(image, label, min_queue_examples,
                                    batch_size, shuffle):
  """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.
    shuffle: boolean indicating whether to use a shuffling queue.

  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.
  num_preprocess_threads = 16
  if shuffle:
    images, label_batch = tf.train.shuffle_batch(
        [image, label],
        batch_size=batch_size,
        num_threads=num_preprocess_threads,
        capacity=min_queue_examples + 3 * batch_size,
        min_after_dequeue=min_queue_examples)
  else:
    images, label_batch = tf.train.batch(
        [image, label],
        batch_size=batch_size,
        num_threads=num_preprocess_threads,
        capacity=min_queue_examples + 3 * batch_size)

  # Display the training images in the visualizer.
  tf.summary.image('images', images)

  return images, tf.reshape(label_batch, [batch_size])

主要看tf.train.shuffle_batch函数,该函数主要输出一个打乱顺序排列的样本batch,[image, label]表示样本和样本标签,batch_size是样本batch长度,capacity是队列的容量,num_threads表示开启多少个线程,min_after_dequeue表示出队后,队列中最少要有min_after_dequeue个数据。所以可知,经过这些运算以后,得到的图片数据为一个四维张量[batch_size, height, width, 3],标签为一维张量[batch_size]。回到train()函数,

train()函数

继续往下看,代码如下,

# Build a Graph that computes the logits predictions from the
# inference model.
logits = cifar10.inference(images)

这里cifar10.inference函数就是我们卷积模型的重点了,进去看看,这里cifar10.inference函数就是我们神经网络模型的重点了,进去看看,

cifar10.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=5e-2,
                                         wd=None)
    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(pre_activation, 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=5e-2,
                                         wd=None)
    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(pre_activation, 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.
    reshape = tf.reshape(pool2, [images.get_shape().as_list()[0], -1])
    dim = reshape.get_shape()[1].value
    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))
    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
    _activation_summary(local3)

  # local4
  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))
    local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
    _activation_summary(local4)

  # linear layer(WX + b),
  # We don't apply softmax here because
  # tf.nn.sparse_softmax_cross_entropy_with_logits accepts the unscaled logits
  # and performs the softmax internally for efficiency.
  with tf.variable_scope('softmax_linear') as scope:
    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                          stddev=1/192.0, wd=None)
    biases = _variable_on_cpu('biases', [NUM_CLASSES],
                              tf.constant_initializer(0.0))
    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
    _activation_summary(softmax_linear)

  return softmax_linear

可以看到,这个模型跟我们讲的两层卷积神经网络识别MNIST模型是类似的,经过第一层卷积层和池化层,第二层卷积层和池化层,再经过三层全连接层。再回到train()函数,

train()函数

接着往下看,代码如下,

# Calculate loss.
loss = cifar10.loss(logits, labels)

这里就是计算损失函数了,进去看看,

cifar10.loss函数

代码如下,

def loss(logits, labels):
  """Add L2Loss to all the trainable variables.

  Add summary 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.
  """
  # Calculate the average cross entropy loss across the batch.
  labels = tf.cast(labels, tf.int64)
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
      labels=labels, logits=logits, name='cross_entropy_per_example')
  cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
  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')

其中,tf.nn.sparse_softmax_cross_entropy_with_logits函数是计算logits和labels的softmax交叉熵,再用tf.reduce_mean求均值方差,再用tf.add_n求和。回到train()函数继续往下看,

train()函数

代码如下,

# Build a Graph that trains the model with one batch of examples and
# updates the model parameters.
train_op = cifar10.train(loss, global_step)

这个就是训练的函数,进去看看,

cifar10.train函数

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.
  lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                  global_step,
                                  decay_steps,
                                  LEARNING_RATE_DECAY_FACTOR,
                                  staircase=True)
  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.
  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)
  with tf.control_dependencies([apply_gradient_op]):
    variables_averages_op = variable_averages.apply(tf.trainable_variables())

  return variables_averages_op

其中,tf.train.exponential_decay函数就是上面提到的用指数衰减法设置学习率,设置学习率后,再使用梯度下降法tf.train.GradientDescentOptimizer优化损失函数,而grads = opt.compute_gradients(total_loss)和opt.apply_gradients(grads, global_step=global_step)函数,其实和前面用到的tf.train.Optimizer.minimize一样的,只不过minimize合并了这两个函数。tf.train.ExponentialMovingAverage函数是使用滑动平均法更新参数,再回到cifar10_train.train()函数,

train()函数

继续往下看,代码如下,

class _LoggerHook(tf.train.SessionRunHook):
  """Logs loss and runtime."""

  def begin(self):
    self._step = -1
    self._start_time = time.time()

  def before_run(self, run_context):
    self._step += 1
    return tf.train.SessionRunArgs(loss)  # Asks for loss value.

  def after_run(self, run_context, run_values):
    if self._step % FLAGS.log_frequency == 0:
      current_time = time.time()
      duration = current_time - self._start_time
      self._start_time = current_time

      loss_value = run_values.results
      examples_per_sec = FLAGS.log_frequency * FLAGS.batch_size / duration
      sec_per_batch = float(duration / FLAGS.log_frequency)

      format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                    'sec/batch)')
      print (format_str % (datetime.now(), self._step, loss_value,
                           examples_per_sec, sec_per_batch))

with tf.train.MonitoredTrainingSession(
    checkpoint_dir=FLAGS.train_dir,
    hooks=[tf.train.StopAtStepHook(last_step=FLAGS.max_steps),
           tf.train.NanTensorHook(loss),
           _LoggerHook()],
    config=tf.ConfigProto(
        log_device_placement=FLAGS.log_device_placement)) as mon_sess:
  while not mon_sess.should_stop():
    mon_sess.run(train_op)

上面就是真的开始计算了,这里不用之前的tf.Session()会话来计算,而是用tf.train.MonitoredTrainingSession,好处是,这个会话能自动保存和载入模型的文件,默认每10分钟保存一次,就不需要我们自己写保存代码了。checkpoint_dir传入保存的路径,tf.train.StopAtStepHook函数指定训练多少步后就停止,tf.train.NanTensorHook用于监控loss,如果loss是Nan,则停止训练。_LoggerHook则用于打印时间、步数、损失值等,打印格式如下:

2020-02-09 16:49:53.703018: step 79860, loss = 0.74 (3547.8 examples/sec; 0.036 sec/batch)

2020-02-09 16:49:54.049544: step 79870, loss = 0.73 (3693.8 examples/sec; 0.035 sec/batch)

2020-02-09 16:49:54.411578: step 79880, loss = 0.68 (3535.6 examples/sec; 0.036 sec/batch)

2020-02-09 16:49:54.768150: step 79890, loss = 0.73 (3589.7 examples/sec; 0.036 sec/batch)

10、准确率

执行以下命令就可以查看我们模型预测的准确率了,

python cifar10_eval.py --eval_dir ./cifar10_eval --checkpoint_dir ./cifar10_train --run_once True

运行结果,

2020-02-09 17:00:38.457926: precision @ 1 = 0.853

我只运行了八万步,得到上面的结果,如果运行的步数更多,估计准确率会更高的。

11、完整代码

完整代码链接如下,

https://mianbaoduo.com/o/bread/YpeVlJY=

 

 

下一讲,我们自己写个双层CNN来对CIFAR-10进行图像识别。

好的,下面是一个简单的使用 TensorFlow 2.7 解决 CIFAR-10 图像识别问题的代码示例: ```python import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers # 加载 CIFAR-10 数据集 (x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data() # 将像素值缩放到 0 到 1 之间 x_train = x_train.astype("float32") / 255.0 x_test = x_test.astype("float32") / 255.0 # 定义模型 model = keras.Sequential( [ keras.Input(shape=(32, 32, 3)), layers.Conv2D(32, kernel_size=(3, 3), activation="relu"), layers.MaxPooling2D(pool_size=(2, 2)), layers.Conv2D(64, kernel_size=(3, 3), activation="relu"), layers.MaxPooling2D(pool_size=(2, 2)), layers.Flatten(), layers.Dropout(0.5), layers.Dense(10, activation="softmax"), ] ) # 编译模型 model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) # 训练模型 model.fit(x_train, y_train, batch_size=64, epochs=10, validation_split=0.1) # 在测试集上评估模型 model.evaluate(x_test, y_test) ``` 上面的代码中,我们首先加载了 CIFAR-10 数据集,并将像素值缩放到 0 到 1 之间。然后定义了一个简单的卷积神经网络模型,包含两个卷积层、两个池化层、一个全连接层和一个 Dropout 层。最后编译模型,并在训练集上训练模型,然后在测试集上评估模型的性能。 注意,在编译模型时,我们使用了 `sparse_categorical_crossentropy` 作为损失函数,这是因为 CIFAR-10 数据集的标签是整数,而不是 one-hot 编码的向量。如果标签是 one-hot 编码的向量,则应该使用 `categorical_crossentropy` 作为损失函数。 另外,我们还使用了 `accuracy` 作为模型的评估指标,以衡量模型在测试集上的分类准确率。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值