10

tensorflow学习,建立深度学习网络,识别CIFAR-10数据集中不同类别的物体(16)

 3 years ago
source link: https://blog.popkx.com/tensorflow-study-build-deep-learning-net-work-to-recognize-different-objects-in-cifar-10-dataset/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

tensorflow学习,建立深度学习网络,识别CIFAR-10数据集中不同类别的物体(16)

发表于 2018-07-09 22:07:39   |   已被 访问: 506 次   |   分类于:   tensorflow   |   4 条评论

前面几节,我们介绍了CIFAR-10数据集,以及 tensorflow 高效率导入数据的方法。本节将尝试将之前学习到的知识用于实践:动手搭建一个深度学习网络,并且利用CIFAR-10数据集训练之,最终达到识别CIFAR-10数据集中不同物体的目的。在搭建网络的过程中,参考了官方提供的例子。


经过前面的学习,知道了卷积网络特别适合图片识别,因此本节也决定使用卷积网络。首先,对图片进行两层卷积和池化(卷积和池化可参考第四节),然后将结果拉成一维数组,再建立全连接层,将最终结果输出成 10 类。当然,在此之前,要先将 CIFAR-10 数据集读入内存。具体如下:

  • 将上节读入数据的代码写成函数,方便复用
  • 扩大数据集(图片的镜面翻折,调整亮度色调等,不会影响物体类别,但是图片集数量变多了)
  • 将图片以 batch 的形式传送给 tensorflow
  • 每组图片都会经过两层卷积和池化
  • 卷积和池化的结果经过全连接层,输出 10 类结果
  • 通过 tensorflow 的 sparse_softmax_cross_entropy_with_logits 方法计算 loss
  • 通过 tensorflow 的 AdamOptimizer 优化器训练网络

看了一下官方提供的例子,略庞大,但是原理并不复杂。因此决定自己先搭建一个精简一点的网络并训练,测试其性能,最终再将自己实现的网络与官方例程对比,这样既能锻炼自己的动手能力,也能学到官方例程。

python 实战代码


1. 先定义固定变量

图片的深度为 3,长宽都是 32。CIFAR-10 数据集一共有 10 类物体,训练集一共 50000 张图片,测试集一共 10000 张。计划每次训练传送给 tensorflow 的 batch 为 50。

IMAGE_HEIGHT = 32
IMAGE_WIDTH = 32
IMAGE_DEPTH = 3

NUM_CLASSES = 10
NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000
NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000

BATCH_SIZE = 50

2. 将数据集读入

这部分的代码上一节说的非常清楚,其实这里就是将上一节的代码封装成一个函数,方便后面的复用。

def ReadImages(filenames):
    # 使用 tensorflow 将文件名 list 转成队列(queue)
    filename_queue = tf.train.string_input_producer(filenames)
    # 标签占一个字节
    label_bytes = 1
    # 图片尺寸 32x32x3
    height = IMAGE_HEIGHT
    width = IMAGE_WIDTH
    depth = IMAGE_DEPTH 
    # 一张图片字节数
    image_bytes = height * width * depth
    # 一帧数据包含一字节标签和 image_bytes 图片
    record_bytes = label_bytes + image_bytes
    # 创建固定长度的数据 reader
    reader = tf.FixedLengthRecordReader(record_bytes=record_bytes)
    key, value = reader.read(filename_queue)
    # 读出的 value 是 string,现在转换为 uint8 型的向量
    record_bytes = tf.decode_raw(value, tf.uint8)
    # 第一字节表示 标签值,我们把它从 uint8 型转成 int32 型
    label = tf.cast(tf.strided_slice(record_bytes, [0], [label_bytes]), tf.int32)
    # 剩下的就是图片的数据了,我们把它的形状由 [深度*高(长)*宽] 转换成 [深度,高(长),宽] 
    depth_major = tf.reshape(tf.strided_slice(record_bytes, [label_bytes],
                           [label_bytes + image_bytes]),
                            [depth, height, width])                         
    # 将图片从 [深度,高(长),宽] 转成 [高(长),宽, 深度] 形状
    uint8image = tf.transpose(depth_major, [1, 2, 0])

    return label, uint8image

最终返回一个标签值和对应的 uint8 型的图片。

3. 对图片进行一定的加工

这样可以增加数据集的多样性,而且一定程度上可以减小过拟合,代码注释部分说的非常清楚了。

def Distorted_inputs(filenames, batch_size):    
    # 读入数据
    label, uint8image = ReadImages(filenames)
    reshaped_image = tf.cast(uint8image, tf.float32)

    height = IMAGE_SIZE
    width = IMAGE_SIZE
    # 按照 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)
    # 重置形状
    float_image.set_shape([height, width, 3])
    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)
    # 产生一个图片 batch
    num_preprocess_threads = 16
    images, label_batch = tf.train.batch(
        [float_image, label],
        batch_size=batch_size,
        num_threads=num_preprocess_threads,
        capacity=min_queue_examples + 3 * batch_size)

    # 返回的是加工过的图片和标签的batch
    return images, tf.reshape(label_batch, [batch_size])

4. 建立深度学习网络

图片先经过两层卷积,再经过三层全连接层,最终输出 10 类。

  • 第一层卷积,使用 5x5x3 的卷积核,输出结果为 64 维,第二层显然要使用 5x5x64 的卷积核,仍然让输出结果为 64 维,两层都使用最大值池化。
  • 卷积后,将所有数据拉成一维数组,然后利用 3 层全连接层将结果降低为 10 类。

代码虽然有点长,但是很简单,结合前面几节,应该非常清楚。

def NetworkRes(images):
    # 第一层卷积
    kernel = tf.Variable(tf.zeros([5,5,3,64]))
    biases = tf.Variable(tf.random_normal( [64])) 
    conv = tf.nn.conv2d(images, kernel, [1,1,1,1], padding='SAME')
    preAc = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(preAc)
    pool1 = tf.nn.max_pool(conv1, ksize=[1,3,3,1], strides=[1,2,2,1], padding='SAME')
    # 第二层卷积
    kernel = tf.Variable(tf.zeros([5,5,64,64]))
    conv = tf.nn.conv2d(pool1, kernel, [1,1,1,1], padding='SAME')
    preAc = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(preAc)
    pool2 = tf.nn.max_pool(conv1, ksize=[1,3,3,1], strides=[1,2,2,1], padding='SAME')
    # 第一层全连接层
    reshape = tf.reshape(pool2, [BATCH_SIZE, -1])
    dim = reshape.get_shape()[1].value
    weights = tf.Variable(tf.zeros([dim, 384]))
    biases = tf.Variable(tf.random_normal( [384]))
    res1 = tf.nn.relu(tf.matmul(reshape, weights) + biases)
    # 第二层全连接层
    weights = tf.Variable(tf.zeros([384, 192]))
    biases = tf.Variable(tf.random_normal( [192])) 
    res2 = tf.nn.relu(tf.matmul(res1, weights) + biases)
    # 第三层全连接层
    weights = tf.Variable(tf.zeros([192, NUM_CLASSES]))
    biases = tf.Variable(tf.random_normal( [NUM_CLASSES]))
    logits = tf.add(tf.matmul(res2, weights), biases)
    return logits

5. 定义 loss

loss 的定义是参考官方例程的,使用了 tensorflow 的 sparse_softmax_cross_entropy_with_logits 方法。它的原型为:

sparse_softmax_cross_entropy_with_logits(_sentinel=None,  labels=None, logits=None, name=None)
  • _sentinel:本质上是不用的参数,不用填
  • labels:标签值
  • logits:shape为[batch_size,num_classes],type为float32或float64
  • name:操作的名字,可填可不填

它的功能与

tf.nn.softmax_cross_entropy_with_logits(logits, labels, name=None)
  • 第一个参数logits:就是神经网络最后一层的输出,如果有batch的话,它的大小就是[batchsize,num_classes],单样本的话,大小就是num_classes
  • 第二个参数labels:实际的标签,大小同上。

它首先计算 logits 的 softmax,然后计算 logits 的 softmax 与标签值的交叉熵 cross_entropy,返回值是一个向量。如果要求总交叉熵,要做一步 tf.reduce_sum 操作,就是对向量里面所有元素求和。如果求loss,则要做一步 tf.reduce_mean 操作,对向量求均值。

最终,loss 的代码如下:

def Loss(logits, labels):
    # 求交叉熵loss的平均值
    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')
    return cross_entropy_mean

6. 定义训练方式 train

这一步就比较简单了,就是调用上面定义好的函数。然后创建 session,在 session 里开始训练。千万别忘了 tf.train.start_queue_runners(sess=sess),否则网络会阻塞,不会有数据被读入。

def Train():
    # 创建文件名 list
    for i in range(1,6):
        filenames = [os.path.join('cifar10_data/cifar-10-batches-bin', 'data_batch_%d.bin' % i)]

    images, label_batch = Distorted_inputs(filenames, BATCH_SIZE)
    logits = NetworkRes(images)
    loss = Loss(logits, label_batch)

    train_step = tf.train.AdamOptimizer(5e-4).minimize(loss)

    init = tf.global_variables_initializer()
    with tf.Session() as sess:
        sess.run(init)
        threads = tf.train.start_queue_runners(sess=sess)
        for i in range(30001):
            # print 'times: %d' % i
            sess.run(train_step)
            if i%10 == 0:
                print 'loss:', sess.run(loss)

定义好以后,就可以训练了,全部代码如下:

#encoding=utf8
import tensorflow as tf
import os
import scipy.misc
import numpy as np
# import matplotlib.pyplot as plt  
from personalTools import *

IMAGE_HEIGHT = 32
IMAGE_WIDTH = 32
IMAGE_DEPTH = 3

NUM_CLASSES = 10
NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000
NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000

BATCH_SIZE = 50

def ReadImages(filenames):
    # 使用 tensorflow 将文件名 list 转成队列(queue)
    filename_queue = tf.train.string_input_producer(filenames)
    # 标签占一个字节
    label_bytes = 1
    # 图片尺寸 32x32x3
    height = IMAGE_HEIGHT
    width = IMAGE_WIDTH
    depth = IMAGE_DEPTH 
    # 一张图片字节数
    image_bytes = height * width * depth
    # 一帧数据包含一字节标签和 image_bytes 图片
    record_bytes = label_bytes + image_bytes
    # 创建固定长度的数据 reader
    reader = tf.FixedLengthRecordReader(record_bytes=record_bytes)
    key, value = reader.read(filename_queue)
    # 读出的 value 是 string,现在转换为 uint8 型的向量
    record_bytes = tf.decode_raw(value, tf.uint8)
    # 第一字节表示 标签值,我们把它从 uint8 型转成 int32 型
    label = tf.cast(tf.strided_slice(record_bytes, [0], [label_bytes]), tf.int32)
    # 剩下的就是图片的数据了,我们把它的形状由 [深度*高(长)*宽] 转换成 [深度,高(长),宽] 
    depth_major = tf.reshape(tf.strided_slice(record_bytes, [label_bytes],
                           [label_bytes + image_bytes]),
                            [depth, height, width])                         
    # 将图片从 [深度,高(长),宽] 转成 [高(长),宽, 深度] 形状
    uint8image = tf.transpose(depth_major, [1, 2, 0])

    return label, uint8image

IMAGE_SIZE = 24 

def Distorted_inputs(filenames, batch_size):    
    # 读入数据
    label, uint8image = ReadImages(filenames)
    reshaped_image = tf.cast(uint8image, tf.float32)

    height = IMAGE_SIZE
    width = IMAGE_SIZE
    # 按照 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)
    # 重置形状
    float_image.set_shape([height, width, 3])
    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)
    # 产生一个图片 batch
    num_preprocess_threads = 16
    images, label_batch = tf.train.batch(
        [float_image, label],
        batch_size=batch_size,
        num_threads=num_preprocess_threads,
        capacity=min_queue_examples + 3 * batch_size)

    # 返回的是加工过的图片和标签batch
    return images, tf.reshape(label_batch, [batch_size])

def NetworkRes(images):
    # 第一层卷积
    kernel = tf.Variable(tf.zeros([5,5,3,64]))
    biases = tf.Variable(tf.random_normal( [64])) 
    conv = tf.nn.conv2d(images, kernel, [1,1,1,1], padding='SAME')
    preAc = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(preAc)
    pool1 = tf.nn.max_pool(conv1, ksize=[1,3,3,1], strides=[1,2,2,1], padding='SAME')
    # 第二层卷积
    kernel = tf.Variable(tf.zeros([5,5,64,64]))
    conv = tf.nn.conv2d(pool1, kernel, [1,1,1,1], padding='SAME')
    preAc = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(preAc)
    pool2 = tf.nn.max_pool(conv1, ksize=[1,3,3,1], strides=[1,2,2,1], padding='SAME')
    # 第一层全连接层
    reshape = tf.reshape(pool2, [BATCH_SIZE, -1])
    dim = reshape.get_shape()[1].value
    weights = tf.Variable(tf.zeros([dim, 384]))
    biases = tf.Variable(tf.random_normal( [384]))
    res1 = tf.nn.relu(tf.matmul(reshape, weights) + biases)
    # 第二层全连接层
    weights = tf.Variable(tf.zeros([384, 192]))
    biases = tf.Variable(tf.random_normal( [192])) 
    res2 = tf.nn.relu(tf.matmul(res1, weights) + biases)
    # 第三层全连接层
    weights = tf.Variable(tf.zeros([192, NUM_CLASSES]))
    biases = tf.Variable(tf.random_normal( [NUM_CLASSES]))
    logits = tf.add(tf.matmul(res2, weights), biases)
    return logits

def Loss(logits, labels):
    # 求交叉熵loss的平均值
    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')
    return cross_entropy_mean

def Train():
    # 创建文件名 list
    for i in range(1,6):
        filenames = [os.path.join('cifar10_data/cifar-10-batches-bin', 'data_batch_%d.bin' % i)]

    images, label_batch = Distorted_inputs(filenames, BATCH_SIZE)
    logits = NetworkRes(images)
    loss = Loss(logits, label_batch)

    train_step = tf.train.AdamOptimizer(5e-4).minimize(loss)

    init = tf.global_variables_initializer()
    with tf.Session() as sess:
        sess.run(init)
        threads = tf.train.start_queue_runners(sess=sess)
        for i in range(30001):
            # print 'times: %d' % i
            sess.run(train_step)
            if i%10 == 0:
                print 'loss:', sess.run(loss)

if __name__ == "__main__":
    Train()

这里训练 30001 次,每 10 次打印一次结果。开始训练,发现 loss 在不断减小:

$ python train.py 
loss: 139862.4
loss: 75934.01
loss: 73080.2
loss: 65201.547
loss: 57756.45
loss: 53158.58
loss: 44673.254
loss: 40073.754
loss: 39292.81
loss: 42916.02
loss: 41723.44
loss: 35896.89
loss: 38079.15
loss: 33945.703
loss: 32580.35
loss: 29202.9
loss: 23343.062
loss: 24682.982
loss: 36213.305
loss: 29628.717
loss: 24032.852
loss: 22293.295
...

发现 loss 总体是在不断减小的。经过一段时间,loss 更小了:

...
loss: 95.31898
loss: 70.991165
loss: 89.883095
loss: 77.25716
loss: 78.50725
loss: 52.714283
loss: 85.293945
loss: 88.35736
loss: 66.40239
loss: 72.92768
loss: 52.961704
loss: 61.261597
loss: 56.042004
loss: 64.03586
loss: 81.6912
loss: 94.63177
loss: 63.111412
loss: 78.860794
loss: 88.37257
loss: 61.51449
loss: 57.879826
loss: 55.705624
loss: 81.577095
loss: 82.05835
loss: 50.56248
loss: 55.78168
loss: 64.80161
loss: 44.10519
...

loss 不断减小,是网络可以正常工作的必要条件。下一节,将利用 CIFAR-10 的测试集,对我们训练好的网络测试,非常好奇咱们自己建立的网络效果如何。

阅读更多:   tensorflow


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK