TF中的CNN实现CIFAR10分类

CIFAR-10与FC

数据集cifar-10包括约70000张图片,每张图片为32X32X3的格式,总共分为10各类别的数据。

实现对于cifar数据集的分类,仅仅利用fc层是不现实的,以一个500个神经元的隐含层为例,共需要32X32X3X500约150万个参数,但是如此简单的结构肯定是无法达到所需的识别能力,所以总体需要的参数量可能是上亿甚至更多。

过多的参数往往会带来两个问题:第一是计算量过大,可能会超过计算机的承载能力,并带来极长的训练时间;第二是过多参数会引起过拟合。

为解决上述问题,将采用卷积神经网络进行cifar-10的识别。

CNN

卷积神经网络,其整体结构与之前所述的单层神经网络大体一致,都有输入层、fc层以及输出层,不同的是在cnn中又加入了卷积层、降采样层以及随机失活层等。下文对于额外的层进行进一步的描述。

1.卷积层

卷积在信号处理中,是用于增强信号与消除噪声;在对于图像的处理上,一个卷积相当于一个模板,对图像处理进行滑动与点积的计算,最终得到一个特征图(feature map);卷积的模板性体现在其本质在于对图像进行某一种特征的提取,图像对于此特征的近似程度就是最终卷积的值,也就是特征图。

卷积层最大的两个优点就是局部连接与参数共享。

局部连接是对应着fc层的全连接而言的,在卷积层处理数据时,不会对图像的每一个点进行处理,而是以一定的大小区块划分整图,处理每一区块得到的卷积结果即为最终的特征图;局部连接划分的区块取决于卷积核大小以及步长与是否补零,而对于一个卷积核或者神经元来说,其参数量取决于卷积核自身的尺寸而与输入的数据尺寸无关。

参数共享,就是指一个卷积核或者说神经元,对一张图像做滑动卷积时,其内部的值是不变的;这在物理意义上表示为一个卷积核就是一个模板,处理图像时就是利用这一模板提取相似性结果,所以模板固定也就是参数固定。

2.降采样层

降采样即实现数据的压缩,进而减少参数量,降低计算量,防止过拟合;一般采用池化的方式进行降采样,但是也有网络使用步长大于一的卷积层实现降采样,所以注意池化只是降采样的一种形式而非唯一形式。

池化就是对于卷积得到的feature map进行尺寸缩减,主要分为最大池化与平均池化,分别可以达到抽取主要特征与抽取平均特征的目的,池化就是要尽量保存原有特征,并达到降采样的效果。

3.随机失活层

即dropout层,主要是在非输出层的fc层之后使用,目的也是为了提高模型的泛化能力,防止过拟合;其思想是将每个神经元当作一个开关,开关的开与关具有一定的随机性;利用这一机制,在每次训练时只有部分神经元打开,参与训练,得到一组权重参数,或者说是当前开关形式下的模型,最终将所有的模型组合,形成一个最终的模型,即为训练的结果;利用上述训练过程,每次不完全训练,参数量较小,并且最终融合的模型相当于多角度描述特征,所以有更好的泛化能力。

TF中的卷积相关函数

1.二维卷积

tf.nn.conv2d(input,filter,strides,padding,name=None)
# input 是输入数据,其格式是[batch, width, height, channel]这样的四维格式,并且batch常写作-1
  要求输入数据应为浮点类型,即tf.float32或tf.float64
# filter 是卷积层,格式为 [filter_width, filter_height,channel]的三维格式,其中前两项表示本层每            个神经元的尺寸,最后的参数表示本层有多少个神经元,一个神经元得到一个特征图,最终得到的特征图数就是本层的神经元数目,所以这个参数也叫做输出维度。
# stride 是步长,即一个卷积核载图像上做滑动点积时,每次滑动的步长;其格式为[1,2,2,1]类型,即4维格式,每一个参数表示在不同维度上的步长,且一般首末两个参数为1.
# padding 即补零,有VAlID与SAME两个可选参数,表示是否进行补零来保证卷积前后尺寸是否变化

2.池化

tf.nn.average_pool(value,ksize,stride,padding,name=None)
tf.nn.max_pooling(同上)
# value 即卷积之后的值,所以格式也是[batch,width,height,channel]
# ksize 即池化窗口大小,因为不会在batch与channel上做池化,所以其格式为[1,2,2,1]的4维格式但是首末值为1.
#stride padding的意义与二维卷积一致

TF载入CIFAR-10数据集

import urllib.request
import os
import tarfile
import pickle as p
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import OneHotEncoder
import tensorflow as tf
from time import time

url = 'http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'  # 下载链接
filepath = 'data/cifar-10-python.tar.gz'  # 下载好的数据集应在的路径

# 下载数据集
if not os.path.isfile(filepath):
    result = urllib.request.urlretrieve(url, filepath)
    print('download: ', result)
else:
    print('Data file already exist')

# 解压数据集
if not os.path.exists("data/cifar-10-batches-py"):
    tfile = tarfile.open('data/cifar-10-python.tar.gz', 'r:gz')
    result = tfile.extractall('data/')
    print('Extracted to ./data/cifar-10-batches-py/')
else:
    print('Directory already exist')


# 批次读取数据 的函数
def load_CIFAR_batch(filename):
    with open(filename,'rb') as f:
        data_dict = p.load(f,encoding='bytes')
        images = data_dict[b'data']
        labels = data_dict[b'labels']
        images = images.reshape(10000, 3, 32, 32)
        images = images.transpose(0, 2, 3, 1)
        labels = np.array(labels)
        return images, labels


# 完整读取数据的函数,通过多次调用批次读取数据的函数实现
def load_CIFAR_data(data_dir):
    images_train =[]
    labels_train = []
    for i in range(5):
        f = os.path.join(data_dir, 'data_batch_%d'%(i+1))
        print('loading', f)
        image_batch, label_batch = load_CIFAR_batch(f)
        images_train.append(image_batch)
        labels_train.append(label_batch)
        Xtrain = np.concatenate(images_train)
        Ytrian = np.concatenate(labels_train)
        del image_batch, label_batch
    Xtest, Ytest = load_CIFAR_batch(os.path.join(data_dir, 'test_batch'))
    return Xtrain, Ytrian, Xtest, Ytest


data_dir = 'data/cifar-10-batches-py'  # 解压抽取数据集后的路径
Xtrain, Ytrain, Xtest, Ytest = load_CIFAR_data(data_dir)  # 获取训练集测试集相关数据与标签

对数据进行预处理

# 对数据进行预处理

# 对图像进行数值标准化,注意标准化之前要先类型转化为浮点型
Xtrain_normalize = Xtrain.astype('float32')/255.0
Xtest_normalize = Xtest.astype('float32')/255.0


# 标签进行独热编码转换
encoder = OneHotEncoder(sparse=False)
yy = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]
encoder.fit(yy)
Ytrain_reshape = Ytrain.reshape(-1, 1)
Ytrain_onehot = encoder.transform(Ytrain_reshape)  # 50000,1->50000,10
Ytest_reshape = Ytest.reshape(-1, 1)
Ytest_onehot = encoder.transform(Ytest_reshape)
# 使用独热编码是要用于对预测结果和标签值的类欧氏距离,来表征预测的偏差程度

定义网络构造辅助函数

# 定义 权重 构造函数 使用截断随机初始化
# 输入为权重的尺寸,即卷积核的尺寸与通道数
def weight(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1), name="W")


# 定义 偏置 构造函数
# 偏置的输入是一个以一维数组,大小等于卷积核数目
def bias(shape):
    return tf.Variable(tf.constant(0.1, shape=shape), name="b")


# 定义 2维卷积 构造函数,卷积前后尺寸不变
# 输入为图像或特征图 与 权重
# 输出 再加上偏置 就得到卷积结果
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')


# 定义 最大池化 构造函数,池化不改变通道数,但是将图像的长宽各减一半
# 输入为卷积结果
def max_pool_2X2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME

构造网络

# 定义网络结构
# 使用了tf的命名空间

# 输入层
with tf.name_scope("input_layer"):
    # cifar数据集的图像格式为32X32X3,批次为待定参数,用于批次读取数据
    x = tf.placeholder(tf.float32, [None, 32, 32, 3], name="x")

# 第一层
with tf.name_scope("conv_1"):
    w1 = weight([3, 3, 3, 32])  # 尺寸为3X3的卷积核,输入通道是3,输出是32
    b1 = bias([32])  # 维度与输出通道数目一致即可
    conv_1 = conv2d(x, w1)+b1  # 进行卷积计算并加上偏置
    conv_1 = tf.nn.relu(conv_1)  # 对卷积结果进行激活  输出为 batch,32,32,32
    # print(conv_1.shape)

# 第一池化层
with tf.name_scope("pool1"):
    pool1 = max_pool_2X2(conv_1)  # 对第一层激活结果池化,不改变通道数,
                                  # 改变尺寸为 batch,16,16,32
    # print(pool1.shape)

# 第二层
with tf.name_scope("conv_2"):
    w2 = weight([3, 3, 32, 64])  # 3X3的卷积核,输入通道是32,输出是64
    b2 = bias([64])  # 维度与输出通道一致即可
    conv_2 = conv2d(pool1, w2)+b2  # 进行卷积计算,注意是对第一池化层数据进行卷积
    conv_2 = tf.nn.relu(conv_2)  # 对卷积结果进行激活 输出为 batch,16,16,64
    # print(conv_2.shape)

# 第二池化层
with tf.name_scope("pool2"):
    pool2 = max_pool_2X2(conv_2)  # 对第二层激活结果池化,不改变通道数,
                                  # 改变尺寸为 batch,8,8,64

# 全连接层 # 注意卷积层向全连接层连接时要先做扁平化
with tf.name_scope("fc"):
     flat = tf.reshape(pool2, [-1, 4096])  # pool2结果是四维的,在传输给全连接层时,要先做扁平化
    w3 = weight([4096, 128])  # 输入维度为4096,输出维度为128,或者说有128个神经元在本fc层
    b3 = bias([128])  # 偏置与输出维度一致
    # 4096 = 8X8X64,即将pool2得到的feature map数据,合并为一个维度
    h = tf.nn.relu(tf.matmul(flat, w3)+b3)  # fc层使用矩阵乘法,并使用relu激活
    h_dropout = tf.nn.dropout(h, keep_prob=0.8)  # 引入随机失活,防止过拟合

# 输出层
with tf.name_scope("output"):
    w4 = weight([128, 10])  # 输出维度即为分类数目
    b4 = bias([10])  # 偏置与输出维度一致
    # 输出层使用矩阵乘法,并使用softmax激活
    preb = tf.nn.softmax(tf.matmul(h_dropout, w4)+b4)  # -1,10

超参与优化器、损失函数的设定

# 构建优化器与损失函数
with tf.name_scope("optimizer"):
    y = tf.placeholder(tf.float32, [None, 10], name="label")
    loss_function = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=preb, labels=y))
    optimizer = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(loss_function)

# 构建准确率,实际上是对于一个批次下所有准确度的均值
with tf.name_scope("evaluation"):
    correct_prediction = tf.equal(tf.argmax(preb, 1), tf.argmax(y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 设置超参
train_epochs = 2500  # 总的训练轮数
batch_size = 50  # 但批次训练数目
total_batch = int(len(Xtrain)/batch_size)  # 一轮训练多少单批次
epoch_list = []  # 论数集合
accuracy_list = []  # 准确度集合
loss_list = []  # 损失值集合
epoch = tf.Variable(0, name="epoch", trainable=False)  # 定义一个不可训练的变量,用于统计训练轮数,以及后续断点续训
start_time = time()  # 计时开始

启动会话设置断点续训

# 启动会话
sess = tf.Session()
sess.run(tf.global_variables_initializer())

# 设置日志目录
ckpt_dir = 'CIFAR_log/'
if not os.path.exists(ckpt_dir):
    os.makedirs(ckpt_dir)  # 没有这个目录,将自动创建

# 生成存储对象,用于后续存储模型
saver = tf.train.Saver(max_to_keep=1)

# 构造断点训练
ckpt = tf.train.latest_checkpoint(ckpt_dir)  # 尝试从日志目录读取存储的模型
if ckpt!= None:
    saver.restore(sess, ckpt)
    print("载入成功")
else:
    print("载入失败,开始训练")

开训练

# 迭代训练


# 首先定义批量获取训练数据的函数,返回值是经过归一化的图像与独热后的标签
def get_train_batch(number, batch_size):
    return Xtrain_normalize[number*batch_size:(number+1)*batch_size], Ytrain_onehot[number*batch_size:(number+1)*batch_size]

# 逐个轮数,逐个批次进行数据训练
for ep in range(start,train_epochs):
    for i in range(total_batch):
        batch_x, batch_y = get_train_batch(i, batch_size)
        sess.run(optimizer, feed_dict={x: batch_x, y: batch_y})
        if i % 100 == 0:
            print("Step{}".format(i), "finished")
    # 每轮训练完成,统计损失与准确率
    loss, acc = sess.run([loss_function, accuracy], feed_dict={x: batch_x, y: batch_y})
    epoch_list.append(ep+1) # 统计轮数
    loss_list.append(loss)  # 统计损失
    accuracy_list.append(acc)  # 统计准确率
    print("Train epoch: ", "%2d" % (sess.run(epoch)+1), "Loss: ","{:.6f}".format(loss),"Accuracy: ", acc)

    saver.save(sess, ckpt_dir+"CIFAR_cnn_model.ckpt", global_step=ep+1)  # 每轮保存模型
    sess.run(epoch.assign(ep+1))  # 更新轮数计数
duration = time()-start_time
print("Train duration: ", duration)  统计时长

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值