欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

卷积神经网络:CIFAR-10训练和测试(单块GPU)

程序员文章站 2022-07-08 09:27:15
...

 正好闲暇,将前段时间搁浅的官方cifar-10的例子运行了一遍。我只有一个GPU,所以并没有完全照搬官方的代码, 而是在此基础上,结合自己的理解,对代码进行了修改,总共有三个子文件,每个文件具体作用如下表所示。

文件 作用
cifar10_input.py 读取本地CIFAR-10的二进制文件格式,定义函数distorted_inputs获得训练数据和inputs函数获取测试数据
cifar10.py 建立卷积神经模型,定义损失函数,训练器和正确率计算等函数
training.py 训练CIFAR-10和评估CIFAR-10模型

完整代码见:https://github.com/skloisMary/cifar10-CNN

CIFAR数据集

cifar-10是由Hinton的两个大弟子Alex Krizhevsky和IIya Sutskever收集的一个用于普适物体识别的数据集。该数据集共有60000张彩色图像,图像大小为32*32*3,分为10个类别,每个类别6000张图片。其中有50000张用于训练,构成5个训练批次,每一个批次共10000张图,余下10000张用于测试,单独构成一批次。 

模型输入

CIFAR数据量较大,使用从文件中读取数据的方式,如果对此不了解,请查看我的博客文章tensorflow的三种数据输入。由于要在训练集上进行训练,在验证集上进行验证,评估模型,所以就使用了Multiple input pipelines方式,使用两个进程,建立两个独立的图和session

  • 训练进程读取训练数据,并且周期性地将模型训练好的变量保存到checkpoint文件中去。
  • 评估进程从checkpoint文件中恢复得到一个inference模型,这个模型读取评估数据。

cifar10_input.py中定义distorted_inputs函数获取训练数据。首先对图片随机剪裁为24*24像素大小,并对图像进行随机的左右翻转,随机变换图像的亮度,随机变换图像的对比度等操作来扩增数据集的大小,最后对图片进行白化处理,使得模型对图片的动态范围变化不敏感。

cifar10_input.py中定义inputs函数获取测试数据,对数据剪裁*区域得到24*24像素大小,并进行白化操作。此外,与distorted_inputs函数不同的还包括未使用乱序进行批处理。

# 获取训练数据
def distorted_inputs(data_dir, batch_size):
    """对cifar训练集中的image数据进行变换,图像预处理
    param data_dir: 数据所处文件夹名称
    param batch_size: 批次大小
    return:
           images: 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3]
           labels: 1D tensor of [batch_size] size
    """
    filename = [os.path.join(data_dir, 'data_batch_%d.bin' % i) for i in range(1, 6)]
    for f in filename:
        if not tf.gfile.Exists(f):
            raise ValueError('Failed to find file: ' + f)

    filename_queue = tf.train.string_input_producer(filename)

    # 数据扩增
    with tf.name_scope('data_augmentation'):
        read_input = read_cifar10(filename_queue)
        reshaped_image = tf.cast(read_input.uint8image, tf.float32)

        height = IMAGE_SIZE
        width = IMAGE_SIZE

        # tf.random_crop 对输入图像进行随意裁剪
        distored_image = tf.random_crop(reshaped_image, [height, width, 3])
        # tf.image.random_flip_left_right 随机左右翻转图片
        distored_image = tf.image.random_flip_left_right(distored_image)
        # tf.image.random_brightness在某范围随机调整图片亮度
        distored_image = tf.image.random_brightness(distored_image, max_delta=63)
        # tf.image.random_contrast 在某范围随机调整图片对比度
        distored_image = tf.image.random_contrast(distored_image, lower=0.2, upper=1.8)
        #归一化, 三维矩阵中的数字均值为0,方差为1, 白化操作
        float_image = tf.image.per_image_standardization(distored_image)

        float_image.set_shape([height, width, 3])
        read_input.label.set_shape([1])

        min_fraction_of_examples_in_queue = 0.4
        min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN * min_fraction_of_examples_in_queue)
    image_batch, label_batch = tf.train.shuffle_batch([float_image, read_input.label], batch_size=batch_size,
                                         capacity= min_queue_examples + 3 * batch_size,
                                         min_after_dequeue=min_queue_examples)
    tf.summary.image('image_batch_train', image_batch)
    return image_batch, tf.reshape(label_batch, [batch_size])

模型架构和训练

模型的架构为conv1-->pooling1-->norm1-->conv2-->pooling_2-->norm_2-->local3-->local4-->softmax_linear,为一个多层架构,由卷积层和非线性层交替多次排列后构成。定义在cifar10.py中的inference()函数中, 输入是一批次的图片,格式为[batch_size, height, width, 3],输出是softmax_linear的线性返回值logits,格式为[batch_size, 10]。由于CIFAR-10中的图片被标记为唯一的一个标签,而不是常见的one-hot形式,损失定义损失函数时,使用tf.nn.sparse_softmax_cross_entropy_with_logits(logits, lables)函数。

在定义学习率时,采用灵活的指数衰减法,其核心思想是先用较大的学习率快速得到一个较优的解,随着迭代步数的增多减少学习率,使得模型更加稳定。函数形式如下所示:

tf.train.exponential_decay(learning_rate, global_step, decay_steps, decay_rate, staircase=True/False)
其中learning_rate为初始的学习率,global_step是当前全局的迭代步数,decay_steps:为每次迭代时需要经过多少步数,decay_rate是衰减比例,以及staircase表示是否呈现阶梯状衰减。计算公式如下:

卷积神经网络:CIFAR-10训练和测试(单块GPU)

staircase默认是False,即每一次迭代都要重新计算学习率,然而当staircase为True时,global_steps / decay_steps的值会被转化成整数,所以当global_steps / decay_steps整除时,学习率才会改变,故成阶梯状。

def loss(logits, labels):
    with tf.name_scope('loss'):
        labels = tf.cast(labels, tf.int64)
        # logits通常是神经网络最后连接层的输出结果,labels是具体哪一类的标签
        # 这个函数是直接使用标签数据的,而不是采用one-hot编码形式
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            logits=logits, labels=labels, name='cross_entropy')
        cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy_mean')
        return cross_entropy_mean

def train_step(loss_value, global_step):
    learning_rate = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                               global_step=global_step, decay_steps=DECAY_STEP,
                                               decay_rate=DECAY_RATE, staircase=True)
    tf.summary.scalar('learning_rate', learning_rate)
    optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_value)
    return optimizer

在一块GPU上进过两三个小时,50000次迭代训练,模型的预测精度大约在85%-90%之间徘徊。显示在tensorboard中的每一步的准确度如下图所示。

卷积神经网络:CIFAR-10训练和测试(单块GPU)

将模型训练好的变量保存到checkpoint文件中去,以便测试程序能够恢复出inference模型,喂入测试数据,评估模型性能。

模型评估

因为我仅仅在最后保存了模型中的参数,使用saver.restore函数恢复变量,利用inference函数重构模型,喂入测试数据,输出测试数据的计算记过logits。接下来使用tf.nn.in_top_k(eval_logits, eval_lables, 1)进行准确度计算,此函数的作用是计算每个样本预测的结果前K个最大的数里面和实际结果的是否相等(预测结果大小[batch_size, 10],真实标签大小为[128]),返回一个bool类型的张量,k一般取1。之所以不使用和训练模型时的accuracy_value=session.run(accuracy),是因为如上述所言,训练和评估是两个线程,所以不能串用,如果串用,准确率大概是10%左右,就相当于瞎猜嘛,训练得到的变量根本没起到任何作用。最后评估得出的准确度是82.1%。

def evaluation():
    with tf.Graph().as_default():
        n_test = cifar10_input.NUM_EXAMPLES_PER_EPOCH_FOR_EVAL
        eval_images, eval_lables = cifar10_input.inputs(DATA_DIR, BATCH_SIZE)
        eval_logits = cifar10.inference(eval_images)
        # tf.nn.in_top_k(predictions, targets, k, name=None)
        # 每个样本的预测结果的前k个最大的数里面是否包括包含targets预测中的标签,一般取1,
        # 即取预测最大概率的索引与标签的对比
        top_k_op = tf.nn.in_top_k(eval_logits, eval_lables, 1)
        saver = tf.train.Saver()
        with tf.Session() as session:
            ckpt = tf.train.get_checkpoint_state('F:\\tensorflow-CIFAR10\\saver')
            if ckpt and ckpt.model_checkpoint_path:
                saver.restore(session, ckpt.model_checkpoint_path)
            coord = tf.train.Coordinator()
            threads = tf.train.start_queue_runners(sess=session, coord=coord)
            num_iter = int(n_test / BATCH_SIZE)
            true_count = 0
            for step in range(num_iter):
                predictions = session.run(top_k_op)
                true_count = true_count + np.sum(predictions)
            precision = true_count / (num_iter * BATCH_SIZE)
            print('precision=', precision)
            coord.request_stop()
            coord.join(threads)