[Tensorflow]第三课 搭建简化版LeNet5来训练识别CIFAR-10图片数据

Le-Net5是由Yann LeCun于1998年在论文Gradient-based learning applied to document recognition中提出的,在论文中它主要用于识别MNIST手写数字的识别率。此处我们要来改造一下这个网络,已用于CIFAR-10数据的训练和识别。

此处最大的区别在于,MNIST手写数字是32x32的单色图片,而CIRAR-10则是32x32的RGB图像,使用Le-Net5的网络来训练效果会如何呢? 让我们来尝试一下。

  • Le-Net5模型结构介绍
  • CIFAR-10数据介绍
  • 构建Le-Net5
  • 训练和测试
  • 附录

一. Le-Net5简化版模型结构介绍

这个模型的基本结构如下这张图中所示,在原论文中有大量的细节处理,此处我们主要的目的并非研究模型算法,而是学习如何使用Tensorflow,因此略去了一些细节内容,采用了一个简化的模型。
在这里插入图片描述
输入是一张32x32的单色图片。
第一层卷积层,采用5x5的过滤器,步长为1,输入通道为3, 输出通道为6, 生成数据为28x28x6,激活函数采用sigmoid。
第二层池化层,采用2x2的过滤器,步长为1,生成数据为14x14x6,激活函数采用sigmoid。
第三层卷积层,依旧采用5x5的过滤器, 步长为1,输入通道为6, 输出通道为16, 生成数据为10x10x16,激活函数采用sigmoid。(在原论文中,此处并非全连接,而是采用一种不对称的连接方式,因为实现起来太过复杂,此处改为全连接。)
第四层池化层,采用2x2的过滤器,步长为2,生成数据为5x5x16,激活函数采用sigmoid。
第五层依旧是卷积层,采用5x5的过滤器, 步长为1,输入通道为16, 输出通道为120, 生成数据为1x1x120,激活函数采用tanh。
第五层是展开层,根据上一层的数据量,该层为120= 1x1x120。因为这一层没有对数据做任何改变,仅仅是展开,因此一般不被看做一个有效层。
第六层是全连接层,节点数量是120。
第七层依旧是全连接层,节点数是84。
最后一层采用softmax来输出预测结果。(在原论文中此处采用RBF,而非softmax。)

三. CIFAR-10 数据集

CIFAR-10数据集包含60000张32x32的彩色图片,分别属于10个类别,每个类别分别有6,0000张图片。其中50000张图片被作为训练集,而另外10000张图片则被作为测试集。
该数据有3种格式可供选择,python格式,matlab格式和二进制格式。此处我们使用的是python格式。该数据需要导入pickle模块,并使用pickle.load方法来读取。数据被读取进来后,他的数据结构是一个数据字典。
他的data部分是10000x3072的numpy数组,数据类型为unit8。没一张图片数据中,前1024字节是红色通道的数据,中间1024字节是绿色通道数据,最后的1024字节是蓝色通道数据。
他的label部分是10000个数字,数字范围是0-9分别代表10个类别。
关于CIFAR-10数据集更多信息,可以在神经网络的大牛Alex Krizhevsky的个人网站上找到。 http://www.cs.toronto.edu/~kriz/cifar.html

二. 构建Le-Net5

我们构建一个LeNet5类来封装网络结构。
在论文中有提到,输入数据是经过归一化处理的,值空间大约在-0.1到1.175之间,这样可以加速训练速度,因此,此处加入了归一化处理,使用tensorflow函数l2_normalize。
为了让代码更加易读,我将卷积层,池化层和全连接层的实现代码封装在一个叫tf_general的模块内。tf_general的代码在文章尾部的附注中给出。
另外,根据论文中描述,Le-Net5采用的激活函数是sigmoid而非我们现在最常用的LeRU。
代码如下:

import tensorflow as tf
import tf_general as tfg

class LeNet5(object):
    def __init__(self, x, n_class=10, drop_rate=0):
        self.input = x
        self.n_class = n_class
        self.drop_rate = drop_rate
        self._build_net()

    def _build_net(self):
        with tf.name_scope('norm'):    
            self.x_norm = tf.nn.l2_normalize(tf.cast(self.input, tf.float32),axis=1)
        
        with tf.name_scope('conv_1'):
            self.conv1 = tfg.conv2d(self.x_norm, 5, 1, 6, 'conv1', 'VALID')
            print('conv_1: ', self.conv1.get_shape())
        
        
        with tf.name_scope('pool_1'):
            self.pool1 = tfg.avg_pool(self.conv1, 2, 2, 'pool1', 'VALID')
            print('pool_1: ', self.pool1.get_shape())
        
        
        with tf.name_scope('conv_2'):
            self.conv2 = tfg.conv2d(self.pool1, 5, 1, 16, 'conv2', 'VALID','SIGMOID')
            print('conv_2: ', self.conv2.get_shape())
        
        with tf.name_scope('pool_2'):
            self.pool2 = tfg.avg_pool(self.conv2, 2, 2, 'pool2', 'VALID')
            print('pool_2: ', self.pool2.get_shape())   
            
        with tf.name_scope('conv_3'):
            self.conv3 = tfg.conv2d(self.pool2, 5, 1, 120, 'conv3', 'VALID','TANH')
            print('conv_3:', self.conv3.get_shape())
    
        with tf.name_scope('flat_1'):
            self.flat1, self.flat_dim = tfg.flatten(self.conv3)
            print('flat_1:', self.flat1.get_shape())
        
        with tf.name_scope('fc_2'):
            self.fc2 = tfg.fc_layer(self.flat1, 120, 84, 'fc2')
            print('fc_2 ', self.fc2.get_shape())
        
        with tf.name_scope('fc_3'):
            self.fc3 = tfg.fc_layer(self.fc2, 84, 10, 'fc4')
            print('fc_3: ', self.fc3.get_shape())

        with tf.name_scope('drop_out'):
            self.logits = tfg.drop_out(self.fc3, self.drop_rate, 'drop_out')
            print('drop_out: ', self.logits .get_shape())

        with tf.name_scope('prediction'):
            self.prediction = tf.nn.softmax(self.logits )
            print('prediction: ', self.prediction.get_shape())

四. 训练和测试

在训练开始之前先要定义超参。
此处我们定义了6个超参

  • 训练迭代次数 epoch
  • 每个训练批次的图像数 batch_size
  • 每个测试批次的图像数 test_size
  • 初始的学习率 lr
  • 是否需要开启训练数据图像增强,True表示开启,False表示关闭。这个默认是开启的。
FLAGS = tf.flags.FLAGS
try:
    tf.flags.DEFINE_string('f', '', 'kernel')
    #super parameters
    tf.flags.DEFINE_integer('epoch', 30000, 'epoch')
    tf.flags.DEFINE_integer('batch_size',200, 'batch size')
    tf.flags.DEFINE_integer('test_size', 200, 'test size')
    tf.flags.DEFINE_float('lr', 0.01, 'learning rate')
    tf.flags.DEFINE_float('keep_prob', 0.8, 'keep prob for drop lay')
    tf.flags.DEFINE_boolean('augument', True, 'if image augument is applied')
    #other parameters
    tf.flags.DEFINE_float('ckpt_frequency', 250, 'frequency to save checkpoint')
    tf.flags.DEFINE_boolean('restore', False, 'restore from checkpoint and run test')
    print('parameters were defined.')
except:
    print('parameters have been defined.')

第一步 定义输入,采用供给数据的方式,因此需要定义一个和输入数据结构一样的placeholder叫做x。再定义y_作为标签的placeholder。
此处要注意的是,LeNet5原本是用与单色图片上的,而我们现在的数据却是RGB彩色的,也就是说输入是3通道的而非单通道。因此我们将输入x的结构定义为从原本的[None, 32,32,1]改为[None, 32,32,3], 输入为3通道,至于训练效果如何,让我们拭目以待。

with tf.name_scope('input'):
    x = tf.placeholder(tf.float32, [None, 32,32,3], name='x_input')
    y_ = tf.placeholder(tf.int64, [None], name='labels')

第二步 生成LeNet5对象,x作为输入,y作为他的输出,即预测结果。

with tf.name_scope('prediction'):
    le_net5 = LetNet5(x)
    y = le_net5.logits 

第三步 计算交叉熵做为损失函数。

with tf.name_scope('cross_entropy'):
    cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,
                                                                                  labels=y_, 
                                                                                  name="cross_entropy_per_example"))

第四步 使用AdagradOptimizer进行优化。该算法可以自动变更学习速率, 只是需要设定一个全局的学习速率ϵ,但是这并非是实际学习速率,实际的速率是与以往参数的模之和的开方成反比的。
在这里插入图片描述

with tf.name_scope('train_step'):
    train_step = tf.train.AdagradOptimizer(FLAGS.lr).minimize(cross_entropy)

第五步 计算训练正确率
argmax方法返回指定维度的向量中最大值的索引。y是一个Nx10的numpy数组,第一维度表示是该批次训练数据的个数,第二维度则是softmax的输出,是10个0-1之间的概率值。我们制定对第二维度(索引值为1)求argmax将softmax的输出转换成0-9的数值。然后使用equal将其与数据标签y_比对,相等的值为True即1,不相等的为False即0,。对该结果求平均值,就可以得到预测正确的概率。

prediction =tf.argmax(le_net5.prediction, 1)
accuracy = tf.reduce_mean(tf.cast( tf.equal(prediction,y_), tf.float32))

至此整个训练的图已经完成,接下来我们要读取数据,启动session进行运算了。
在训练过程中,每完成250次训练,我们会保存一下模型,并进行一次测试,测试时不做优化,也不drop参数。由于测试数据量较大,我们依旧需要分批进行,并将测试正确率打印出来。

data = cifar10();
if __name__ == '__main__':
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        saver = tf.train.Saver(max_to_keep=1)
        for i in range(FLAGS.epoch):
            train_image, train_label,_ = data.get_train_batch(FLAGS.batch_size)
            loss, _,accuracy_rate = sess.run([cross_entropy, train_step,accuracy], 
            						feed_dict={x:train_image, y_:train_label})
            if (i+1) % FLAGS.ckpt_frequency == 0:  #保存预测模型
                saver.save(sess,ckpt_dir+'cifar10_'+str(i+1)+'.ckpt',global_step=i+1)
                acc_accuracy = 0
                for j in range(int(10000/FLAGS.test_size)):                    
                    test_image, test_label,test_index = data.get_test_batch(FLAGS.test_size)
                    accuracy_rate, output = sess.run([accuracy,prediction],feed_dict=
                    						{keep_prob: 1, x:test_image, y_:test_label})
                    acc_accuracy += accuracy_rate
                accuracy_rate = acc_accuracy/10000*FLAGS.test_size
                print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + ' iter ' + str(i) + ', Test accuracy:' +str(round(accuracy_rate*100,2))+'%')
    tf.reset_default_graph()

让我们来看一下训练结果如何。此处进行了两次独立的训练,迭代50000次后正确率仅仅为50%,而且趋于平缓。这个结果显然很不理想, 主要是Le-Net5的网络结构过于简单,处理cifar10图像就有点力不从心了。
在这里插入图片描述

附录

1. 深入理解LeNet5的结构

参考 EVA HUA的“Lenet5设计理解——咬文嚼字系列”。
https://blog.csdn.net/whatwho_518/article/details/79724602

2. 源码下载

https://download.csdn.net/download/deecheanW/12494457

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小白的逆袭日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值