卷积神经网络(4)-- 实现卷积神经网络的简例

一、卷积神经网络的一般框架

卷积神经网络主要包含5个结构:输入层、卷积层、池化层、全连层、softmax层

  • 输入层:整个神经网络的输入,在图像分类问题中,一般是一张图片的像素矩阵。一般按照batch进行输入,因此是四维数据。

  • 卷积层:该例中有两个卷积层,由卷积核提取图像特征,输出特征图,扫描窗口的大小就是卷积核的大小,输出结果的通道数是卷积核的数量,提取的特征数量越多,输出矩阵就越深

  • 池化层:缩减输出矩阵长度和宽度的作用,不会增加通道数,目的在于减少网络中的参数

  • 全连层:将经由两段卷积层输出的特征矩阵作为输入,接入全连层,将三维的拉成一维,可连接多个隐藏层

  • softmax层:得到输入样例所属种类的概率分布情况

 二、用简单卷积神经网络实现Cifar-10数据集分类

在本节,将会利用之前讲述的内容搭建一个比较简单的卷积神经网络,实现Cifar-10数据集的分类。尽管搭建的卷积神经网络比较简单,但是卷积神经网络的数据输入需要我们自己创建数据的函数和文件,而不像解决MNIST数据集时我们直接调用input_data.py的函数read_data_sets()来进行读取。  

因此在本小节,这个完整的样例程序被分为了两个文件:Cifar10_data.py和CNN_Cifar-10.py    

其中Cifar10_data.py文件负责读取Cifar-10数据集并且对它进行数据增强预处理,而CNN_Cifar-10.py  文件负责构造卷积神经网络的整体结构,实现训练与测试的过程。

 1、Cifar-10数据集的下载存储

文件包含五个训练集,一个测试集,每个文件存放了10000张图片,这里以bin文件的形式存放,每个文件都包含图像数据和标签数据 :

图像数据 : 一个10000x3072 的numpy数组。阵列的每一行存储32x32彩色图像即每一行存储32x32x3=3072个数字信息。前1024个条目包含红色通道值,下一个1024个绿色,最后1024个蓝色。

标签 :范围为0-9的10000个数字的列表。索引i处的数字表示阵列数据中第i个图像的标签。标签的label_names [0] ==“飞机”,label_names [1] ==“汽车”等

  这些文件中的每一个格式如下:
  <1×标签> <3072×像素>
    ...
  <1×标签> <3072×像素  

换句话说,文件中数组的每一行是都是一张图片的信息,每行的第一个字节是图像的标签,它是一个0-9范围内的数字。接下来的3072个字节是图像像素的值。

每个文件都包含10000个这样的3073字节的“行”图像,但没有任何分隔行的限制。因此每个文件应该完全是30730000字节长。


2、Cifar10_data.py文件的编写


程序包含两个内容:Cifar-10数据读取函数与读取之后的图像增强预处理函数


1)、为什么要另外搭建数据读取文件


a. TensorFlow数据读取机制

Tensorflow读取数据的一般方式有下面3种:

  • 直接创建变量:在tensorflow定义图的过程中,创建常量或变量来存储数据

  • feed:在运行程序时,通过feed_dict传入数据

  • 调用reader函数类从储存的文件中读取数据:在tensorflow图开始时,通过一个输入管线(线程)从文件中读取数据

b. 调用阅读器reader的TensorFlow数据读取机制:

假设我们的硬盘中有一个图片数据集0001.jpg,0002.jpg,0003.jpg……我们只需要把它们读取到内存中,然后提供给GPU或是CPU进行计算就可以了。这听起来很容易,但事实远没有那么简单。事实上,我们必须要把数据先读入后才能进行计算,假设读入用时0.1s,计算用时0.9s,那么就意味着每过1s,GPU都会有0.1s无事可做,这就大大降低了运算的效率。

如何解决这个问题?方法就是将读入数据和计算分别放在两个线程中,将数据读入内存的一个队列

线程(英语:thread):是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

读取线程源源不断地将文件系统中的图片读入到一个内存的队列中,而负责计算的是另一个线程,计算需要数据时,直接从内存队列中取就可以了。这样就可以解决GPU因为读取数据而空闲的问题!


而在tensorflow中,为了方便管理,在内存队列前又添加了一层所谓的“文件名队列”。

tensorflow使用文件名队列+内存队列双队列的形式读入文件,可以很好地管理。如下图,还是以数据集A.jpg, B.jpg, C.jpg为例,那么我们就要把A、B、C放入文件名队列中。通过文件列队将数据读入到内存列队当中

2)、读取不同格式文件对应的reader函数

  • tensorflow支持读取的文件格式包括:CSV文件,二进制文件,TFRecords文件,图像文件,文本文件等等。具体使用时,需要根据文件的不同格式,选择对应的文件格式阅读器,再将文件名队列传为参数,传入阅读器的read方法中。方法会返回key与对应的record value。将value交给解析器进行解析,转换成网络能进行处理的tensor。

  • csv文件: class tf.TextLineReader, 默认按行读取

  • 二进制文件: tf.FixedLengthRecordReader(record_bytes) , record_bytes:整型,指定每次读取的字节数

  • TfRecords文件: tf.TFRecordReader

  • 以上3个阅读器有一个相同读取方法:

    read(file_queue):从队列中指定数量内容,返回一个Tensors元组(key, value),这是字符串的储存方式,其中key是文件名字,value是默认的内容


3)、数据增强处理


数据增强处理包括:随机裁剪、随机翻转、随机调整对比度、随机调整亮度以及标准化和归一化操作,能够产生更多训练样本,这有利于网络更好的提取图像特征,并且丰富图像的训练数据集有利于增强模型的泛化能力。

标准化:如果样本的尺度不一致,通过如此的方式可以得到一致的输入数据。譬如,如果采用不同的相机采集的图片,或者是来源不同的图片,由于明暗,对比度等各种问题,会出现尺度不一的图片。有些数值过大会影响收敛的结果。此时进行标准化以后可以加速收敛速度。也在一定程度上可以提高收敛精度。

归一化不会改变图像本身的信息存储,原图与归一化之后的运行结果完全一致,但是取值范围从0~255已经转化为0~1之间了,这个对于后续的神经网络或者卷积神经网络处理有很大的好处,加快训练网络的收敛性,更加便捷快速,应该归到数字信号处理范畴之内

tensorflow官方给出mnist数据集,全部采用了归一化之后的结果作为输入图像数据来演示神经网络与卷积神经网络。


一般训练集需要对图像进行增强处理,测试集不需要进行处理

Cifar10_data.py文件的编写


调用写好的Cifar10_data.py文件,实现一个线程读取数据,一个线程进行图像识别的多线程方式对数据集分类

import Cifar10_data
import tensorflow as tf
import numpy as np
import time
import math

max_steps = 4000
batch_size = 100
num_examples_for_eval = 10000
data_dir1 = ".\cifar-10-batches-py"

#定义权重创建函数,生成权重变量的同时,给权重加了L2的loss,做了L2正则化处理,可以简略之后的步骤
#wl是控制L2 loss大小的参数
def variable_with_weight_loss(shape, stddev, wl):
    var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))
    if wl is not None:

        # multiply()函数原型multiply(x,y,name)
        # tf.add_to_collection将weight loss统一到集合losses当中
        weight_loss = tf.multiply(tf.nn.l2_loss(var), wl, name="weight_loss")
        tf.add_to_collection("losses", weight_loss)
    return var

#导入数据
#对于用于训练的图片数据,distorted参数为True,表示进行数据增强处理
images_train, labels_train = Cifar10_data.inputs(data_dir=data_dir, batch_size=batch_size,distorted=True)

#对于用于训练的图片数据,distorted参数为Nnone,表示不进行数据增强处理
images_test, labels_test = Cifar10_data.inputs(data_dir=data_dir, batch_size=batch_size,distorted=None)


#创建placeholder
x = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
y_ = tf.placeholder(tf.int32, [batch_size])


#第一个卷积层
kernel1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2, wl=0.0)
conv1 = tf.nn.conv2d(x, kernel1, [1, 1, 1, 1], padding="SAME")
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, bias1))
pool1 = tf.nn.max_pool(relu1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],padding="SAME")


#第二个卷积层
kernel2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, wl=0.0)
conv2 = tf.nn.conv2d(pool1, kernel2, [1, 1, 1, 1], padding="SAME")
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, bias2))
pool2 = tf.nn.max_pool(relu2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding="SAME")


# 拉直数据
# reshape()第二个参数用到-1,表示将数据拉成1维结构,对于一个batch有100张图片,reshape之后得到100行的结果
#.get_shape()[1].value表示获取reshape之后的第二个维度的值
reshape = tf.reshape(pool2, [batch_size, -1])
dim = reshape.get_shape()[1].value


#第一个全连层,连接384个神经元,因为产生的权重参数较多,为防止过拟合这里w1有取值
weight1 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, wl=0.004)
fc_bias1 = tf.Variable(tf.constant(0.1, shape=[384]))
fc_1 = tf.nn.relu(tf.matmul(reshape, weight1) + fc_bias1)


#第二个全连层,连接192个神经元,其余和第一层一样
weight2 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, wl=0.004)
fc_bias2 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(fc_1, weight2) + fc_bias2)


#第三个全连层。全连层的输出层,连接是个神经元,输出十个类别的结果。神经元个数较少不做L2正则化
weight3 = variable_with_weight_loss(shape=[192, 10], stddev=1 / 192.0, wl=0.0)
fc_bias3 = tf.Variable(tf.constant(0.0, shape=[10]))
result = tf.add(tf.matmul(local4, weight3), fc_bias3)


#计算损失,包括权重参数的正则化损失和交叉熵损失,选择同第六章一样的交叉熵函数
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=result, labels=tf.cast(y_, tf.int64))

#将所有的L2正则化损失加和
weights_with_l2_loss=tf.add_n(tf.get_collection("losses"))

#计算总损失
loss=tf.reduce_mean(cross_entropy)+weights_with_l2_loss

#定义优化器,学习率为0.001
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)

#函数原型in_top_k(predictions,targets,k,name),用于测试集验证
top_k_op = tf.nn.in_top_k(result, y_, 1)

with tf.Session() as sess:
    tf.global_variables_initializer().run()

    #开启多线程,这里不开启线程,后续步骤无法进行
    tf.train.start_queue_runners()
    
    #开始训练
    for step in range(max_steps):
        start_time = time.time()#返回当前时间的时间戳
        image_batch, label_batch = sess.run([images_train, labels_train])
        loss_value = sess.run([train_op, loss], feed_dict={x: image_batch,y_: label_batch})
        duration = time.time() - start_time

        if step % 100 == 0:
            examples_per_sec = batch_size / duration#每秒钟能训练的样本数量
            sec_per_batch = float(duration)#一个batch的训练时间

            #打印每一轮训练的耗时
            print("step %d, loss = %.2f (%.1f examples/sec; %.3f sec/batch)"%
                     (step, loss_value, examples_per_sec, sec_per_batch))
            #训练结束

 

 #math.ceil()函数用于求整,原型为ceil(x)
    num_batch = int(math.ceil(num_examples_for_eval / batch_size))#num_iter
    true_count = 0
    total_sample_count = num_iter * batch_size
    
    #开始测试
    # 在一个for循环内统计所有预测正确的样例的个数
    for j in range(num_batch):
        image_batch, label_batch = sess.run([images_test, labels_test])
        predictions = sess.run([top_k_op], feed_dict={x: image_batch, y_: label_batch})
        true_count += np.sum(predictions)#sum函数结合布尔型数组来使用,布尔值会被强制转换为0和1,True对应1

    #打印正确率信息
    print("accuracy = %.3f%%" % ((true_count/total_sample_count)*100))

accuracy = 74.6%

尽管这个正确率比MNIST手写数据集识别正确率低了很多,但持续增加max_step有助于提高正确率

当赋予训练迭代轮数一个较大的值时,优化器需要配合使用学习率指数衰减的形式来训练,这样能够达到大约86%的正确率

第八章当中会对卷积神经网络进行更多的优化,使得卷积神经网络进一步提高正确率

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值