基于cifar10数据集的cnn图片分类模型

数据集下载地址(python版):
https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
该数据集分成了几部分/批次(batches)。CIFAR-10 数据集包含 5 个部分,名称分别为 `data_batch_1`、`data_batch_2`,以此类推。每个部分都包含以下某个类别的标签和图片:

* 飞机
* 汽车
* 鸟类
* 猫
* 鹿
* 狗
* 青蛙
* 马
* 船只
* 卡车
import的helper是一个自己写的工具包
在我这篇随笔里:helper工具包——基于cifar10数据集的cnn分类模型的模块,把内容复制下来用python编辑器写成py文件,名字为helper,放到下载的数据集一个路径下,即可
代码大部分我都仔仔细细的注释过了,希望大家认真看,一定可以看懂的。
import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import matplotlib as mpl
import helper
import pickle
import tensorflow as tf
import random

# 设置字符集,防止中文乱码
mpl.rcParams['font.sans-serif'] = [u'simHei']
mpl.rcParams['axes.unicode_minus'] = False

# 0、定义模型超参数。
learning_rate = 0.01  #学习率,梯度下降时,走的步长
batch_size = 256  #批量大小,将数据集分为好几批,一批批的输入神经网络中,每一批256条数据,一批批的执行梯度下降
keep_probability = 0.7  #dropout流程的保留比例,比如某一隐藏层神经节点参与运算时,使其百分之70为0
image_shape = [32, 32, 3]  # 输入图片的尺寸 [32, 32, 3]
n_classes = 10    #数据集的类别数量
epochs = 2000   #训练过程中,所有数据将被前向传播反向传播更新轮多少次,轮的次数越多,模型越准确,但容易过拟合,比如:
# 训练集有1000个样本,batchsize(批量大小)=10,那么: 训练完整个样本集需要: 100次iteration(迭代次数),1次epoch。
every_save_model = 2 # 每多少个epoch保存1次模型

'''
神经网络中常用的超参数
1. 学习率 η,2. 正则化参数 λ,3. 神经网络的层数 L,4. 每一个隐层中神经元的个数 j,5. 学习的回合数Epochs,6. 小批量数据 minibatch 的大小,7. 输出神经元的编码方式,8. 代价函数的选择,9. 权重初始化的方法,10. 神经元激活函数的种类,11.参加训练模型数据的规模 
'''

"""
网络结构图。
input_x              [-1, 32, 32, 3]    输入层,意为输入图片数据是32*32*3的尺寸,长、宽与通道数,-1意为bantch_size(批量)大小,此处设置为自动
w1                   [5, 5, 3, 32]      权重,意为5*5*3的卷积核,32个,意为提取32张特征图,即32个特征
conv1                [-1, 32, 32, 32]   卷积层一,经过input_x与滤波器w1进行卷积运算后,得到:批量大小自动,长、宽、通道数为32*32*32的尺寸     
池化1(步幅为2)       [-1, 32/2, 32/2, 32]   池化层一:压缩数据,缩小图像,减小参数的数量和计算,意为,批量大小自动,执行步幅为2的平均池化或最                                            大池化,此步完成后得到的是16*16*32尺寸的数据
w2                   [5, 5, 32, 128]    权重,意为5*5*32的卷积核,128个,卷积核数量通常都是上一次卷积核数量的倍数增加,意为提取128张特征图,即                                        128个特征 
conv2                [-1, 16, 16, 128]  卷积层二,经过池化一后的数据与滤波器w2进行卷积运算后,得到:批量大小自动,长、宽、通道数为16*16*128的                                         尺寸,此次卷积相当于在第一次卷积提取出的特征的基础上,将第一次提取出来的特征的一些特征组合也提取出                                          来,相较于第一次卷积结果,因为随着网络的加深,feature map的长宽尺寸缩小(池化),本卷积层的每个map提                                       取的特征越具有代表性(精华部分),所以后一层卷积层需要增加feature map的数量,才能更充分的提取出前一层                                        的特征,一般是成倍增加 
池化2(步幅为2)       [-1, 16/2, 16/2, 128] 
拉平层               [N, 8, 8, 128] --> [N, 8*8*128]
FC1(权重)            [8*8*128, 1024]
logits(权重)         [1024, 10]
预测概率值             -----> 使用softmax激活
"""
#vgg16,resnet50这种层数的计算:层数只包含有参数的层,像池化层啊,relu激活层啊,loss层啊这些,都不计数
# 构建模型图 1、创建变量
graph = tf.Graph() #complete
with graph.as_default():
    weights = {
        'conv1': tf.get_variable('w1', shape=[5, 5, 3, 32], initializer=tf.truncated_normal_initializer(stddev=0.1)),
    #    tf.truncated_normal_initializer从截断的正态分布中输出随机值。生成的值服从具有指定平均值和标准偏差的正态分布,如果生成的值大于平均值2        个标准偏差的值则丢弃重新选择。
        'conv2': tf.get_variable('w2', shape=[5, 5, 32, 128], initializer=tf.truncated_normal_initializer(stddev=0.1)),
        'fc1': tf.get_variable('w3', shape=[8*8*128, 1024], initializer=tf.truncated_normal_initializer(stddev=0.1)),
        'fc2': tf.get_variable('w4', shape=[1024, n_classes], initializer=tf.truncated_normal_initializer(stddev=0.1))
    }
    biases = {
        'conv1': tf.get_variable('b1', shape=[32], initializer=tf.zeros_initializer()),
        #tf.zeros_initializer 全0初始化
        'conv2': tf.get_variable('b2', shape=[128], initializer=tf.zeros_initializer()),
        'fc1': tf.get_variable('b3', shape=[1024], initializer=tf.zeros_initializer()),
        'fc2': tf.get_variable('b4', shape=[n_classes], initializer=tf.zeros_initializer())
    }

cifar10_dataset_folder_path = '../datas/cifar-10-batches-py'
if os.path.exists(cifar10_dataset_folder_path):
    #os.path.exists 判断括号里的文件是否存在的意思,括号内的可以是文件路径。存在输出Ture,不存在输出False
    print('yes')

def explore_data(): #探索一下数据,第五批次中第1001个样本的信息
    batch_id = 5  #批次编号
    sample_id = 1001  #样本编号
    nums = helper.display_stats(cifar10_dataset_folder_path, batch_id, sample_id)
    # epochs = nums // batch_id

def normalize(images,training=True): #complete
    """
    归一化图片数据。将其缩放到(0,1)之间
    :param images: 图片数据,图片的shape =[32, 32, 3]
    :return: 归一化以后的numpy的数据
    """
    return tf.layers.batch_normalization(images, training=True)
    #tf.layers下封装了一些函数,其中包括此函数,批归一化的函数,参数很多不一一列举,,return的这两个参数分别是输入的图片,是否参与模型的训练,此处为参与,注意,当模型训练好后,用在验证数据上时,不再归一化,所以测试数据时,应为False.

def one_hot_encode(x):  #complete
    """
    对输入的列表(真实类别标签),转换为one-hot形式
    :param x: 标签的list。
    :return: one-hot编码后的结果,是一个numpy数组。
    """
    return np.eye(10)[x.reshape(-1)].T #np.eye()意为生成多少行多少列的单位矩阵,因为one_hot编码,即是哪个类别,则第几位为1,其他几位都为0
    #x.reshape(-1)将标签构成的数组x横向平铺成0123456789,

def preprocess_data_and_save():
    # 预处理训练,验证、测试数据集。
    helper.preprocess_and_save_data(cifar10_dataset_folder_path, normalize, one_hot_encode)


# todo 检查点。若预处理数据已经完成,并保存到本地磁盘,那么每次可以从这里开始运行(之前的代码不用再执行了)
valid_features, valid_labels = pickle.load(
    open('../datas/cifar10/preprocess_validation.p', mode='rb'))
# print(len(valid_features))
#pickle我翻译为腌咸菜模块,作用是把python运行中得到的一些列表字典之类永久保存下来,其有两个方法,dump与load,dump(obj(对象), file(文件夹), [protocol可以为012,0是文本形式,1是老二进制,2是新二进制]),protocol意为协议
# load(文件夹),保存为python文件到文件夹中
#open函数 打开一个文件,如果不存在则创建。rb是以二进制读模式打开
# load(文件夹),保存为python文件到文件夹中


def cnn_net_input(image_shape, n_classes):
    """
    定义 input_x, input_y ,keep_prob等占位符。
    :param image_shape:  最原始的输入图片的尺寸
    :param n_classes:     类别数量。
    :return:
    """
    input_x = tf.placeholder(tf.float32, [None, image_shape[0], image_shape[1], image_shape[2]], name='input_x')
    #创建占位符,参数依然是 批量大小,长、宽、通道数
    input_y = tf.placeholder(tf.float32, [None, n_classes], name='input_y')
    change_learning_rate = tf.placeholder(tf.float32, shape=None, name='change_learning_rate')
    #学习率  无形状,标量
    keep_probab = tf.placeholder(tf.float32, shape=None, name='keep_probab')
    #保留比例
    return input_x, input_y, change_learning_rate, keep_probab


def conv2d(input_tensor, filter_w, filter_b, strides=[1, 1, 1, 1]):  #complete
    """
    实现 1、卷积 + 2、偏置项相加 + 3、激活
    :param x:
    :param filter_w:
    :param filter_b:
    :param strides:
    :return:
    """
    # 1、卷积
    conv = tf.nn.conv2d(
        input=input_tensor, filter=filter_w, strides=strides, padding='SAME'
    )
    #tf.nn是tensorflow一个内置的十分丰富的函数大集锦,其中就包括了conv2d这个函数,计算给定4-D输入和滤波器张量的2-D卷积
    #以上四个参数分别是;输入数据;滤波器;strides=[1, 1, 1, 1]是设置的滤波器的步长,steides四个1分别是N H W C,样本数,高度,宽度,通道数,第  一个和最后一个1是官方规定的必须是1,第二个和第三个分别是水平步长和垂直方向步长;当输入数据的    矩阵不够卷积核扫描时是否在四周填充0,使输   入图片和卷积后的图片长宽尺寸一样。若是设置valid(合理的)则不会填充,从而有可能形状变小
    # 2、偏置项相加
    conv = tf.nn.bias_add(conv, filter_b)
    #将偏置项bias的向量加到value的矩阵上,是向量与矩阵的每一行进行相加,得到的结果和value矩阵大小相同
    # 3、激活
    conv = tf.nn.relu(conv)
    return conv


def maxpool2d(input_tensor, k=2):  #complete
    kernel_size = [1, k, k, 1]
    #池化也是用滤波器来达到计算目的的,也叫池化核,此为池化核大小,四个参数同上面的strides,N,H,W C 样本数,高,宽,通道数
    strides = [1, k, k, 1]
    #池化步幅为2,原矩阵会缩小一半,步幅为1 时原矩阵尺寸不变
    maxpool_out = tf.nn.max_pool(
        value=input_tensor, ksize=kernel_size, strides=strides, padding='SAME'
    )
    #最大池化操作,padding=same为矩阵周边填充0
    return maxpool_out


def flatten(input_tensor): #complete
    """
    flatten层,实现特征图 维度从 4-D  重塑到 2-D形状 [Batch_size, 列维度]
    :param input:
    :return:
    """
    shape = input_tensor.get_shape()  # [N, 8, 8, 128]
    flatten_shape = shape[1] * shape[2] * shape[3]
    flatted = tf.reshape(input_tensor, shape=[-1, flatten_shape])
    #tf.reshape 改变指定数据的形状
    return  flatted


def fully_connect(input_tensor, weights, biases, activation=tf.nn.relu): #complete
    """
    实现全连接 或者  输出层。
    :param input_tensor:
    :param num_outputs: 输出的隐藏层节点数量。
    :return:
    """
    #卷积后要激活,池化不要激活,最后一步是全连接时得到output,要激活才能得到预测值
    fc = tf.matmul(input_tensor, weights) + biases
    if activation:
        fc = activation(fc)
        return fc
    else:
        # 这里是为了返回最终输出的logits。
        return fc


def model_net(input_x, weights, biases, keep_prob, istrain): #complete
    """
    构建模型
    :param input_x:   原始图片的占位符
    :param keep_prob: 定义的keep_prob的占位符。
    :return:  logits
    """

    """
    网络结构图。
    input_x              [-1, 32, 32, 3]
    w1                   [5, 5, 3, 32]
    conv1                [-1, 32, 32, 32]      
    池化1(步幅为2)       [-1, 32/2, 32/2, 32] 
    w2                   [5, 5, 32, 128]   
    conv2                [-1, 16, 16, 128] 
    池化2(步幅为2)       [-1, 16/2, 16/2, 128] 
    拉平层               [N, 8, 8, 128] --> [N, 8*8*128]
    FC1(权重)            [8*8*128, 1024]
    logits(权重)         [1024, 10]
    预测概率值             -----> 使用softmax激活
    """
    # conv1--dropout(可选)--池化1--conv2--dropout(可选)--池化2--拉平层--全连接层*N--输出层 得到logits
    with tf.variable_scope('Network'):

        # 卷积1  [N, 32, 32, 3]  --> [N, 32, 32, 32]
        conv1 = conv2d(
            input_tensor=input_x, filter_w=weights['conv1'], filter_b=biases['conv1'])
        conv1 = tf.nn.dropout(conv1, keep_prob=keep_probability)
        if istrain:
            conv1 = normalize(conv1)
        # 池化1 [N, 32, 32, 32]  -->[N, 16, 16, 32]
        pool1 = maxpool2d(conv1)
        # 卷积2  [N, 16, 16, 32]  --> [N, 16, 16, 128]
        conv2 = conv2d(
            input_tensor=pool1, filter_w=weights['conv2'], filter_b=biases['conv2'])
        conv2 = tf.nn.dropout(conv2, keep_prob=keep_probability)
        if istrain:
            conv2 = normalize(conv2)
        # 池化2 [N, 16, 16, 128]  -->[N, 8, 8, 128]
        pool2 = maxpool2d(conv2)

        # 拉平层 [N, 8, 8, 128] ---> [N, 8*8*128]
        shape = pool2.get_shape()  # [N, 8, 8, 128]
        flatten_shape = shape[1] * shape[2] * shape[3]
        flatted = tf.reshape(pool2, shape=[-1, flatten_shape])

        # 全连接层1  [N, 8*8*128] ---> [N, 1024]
        fc1 = fully_connect(
            input_tensor=flatted, weights=weights['fc1'], biases=biases['fc1'])
        fc1 = tf.nn.dropout(fc1, keep_prob=keep_prob)

        # 全连接层2(输出层)   [N, 1024] --->  [N, 10]
        logits = fully_connect(
            input_tensor=fc1, weights=weights['fc2'], biases=biases['fc2'], activation=None
        )
        return logits


# todo 自己定义两个执行会话环节需要使用的辅助函数。
def train_session(sess, train_opt, input_x, input_y, batch_x, batch_y, keep_prob, keep_probability, change_learning_rate, learning_rate):
    """
    执行的跑 模型优化器的函数
    :param sess:       会话的实例对象
    :param train_opt:  优化器对象
    :param keep_probability:  实数,保留概率
    :param batch_x:    当前的批量的images数据
    :param batch_y:    当前批量的标签数据。
    :return: 仅仅是执行优化器,无需返回值。
    """
    feed = {input_x: batch_x, input_y: batch_y, keep_prob: keep_probability, change_learning_rate: learning_rate}
    sess.run(train_opt, feed_dict=feed)  # 执行模型训练


def print_stats(sess, loss, accuracy, input_x, input_y,  batch_x, batch_y, keep_probab, keep_probability, change_learning_rate, learning_rate):
    """
    使用sess跑loss和 Accuracy,并打印出来
    :param sess:  会话的实例对象
    :param batch_x: 当前的批量的images数据
    :param batch_y: 当前批量的标签数据。
    :param loss:   图中定义的loss tensor对象
    :param accuracy: 图中定义的accuracy tensor对象
    :return:  仅仅是打印模型,无需返回值。
    """
    feed = {input_x: batch_x, input_y: batch_y, keep_probab: keep_probability, change_learning_rate: learning_rate}
    change_loss, change_acc = sess.run(
        [loss, accuracy], feed)
    loss_ = change_loss
    accuracy_ = change_acc
    print('Loss:{:.5f} - Valid Accuracy:{:.4f}'.format(loss_, accuracy_))


def create_file_path(path): #complete
    """
    创建文件夹路径函数
    """
    if not os.path.exists(path):
        os.makedirs(path)
        print('成功创建路径:{}'.format(path))
# ____________________________________________________________________________________________________
def train_single_batch():
    """
    先跑 preprocess-batch-1 这个训练数据集,确认模型ok之后,跑所有的数据。
    :return:
    """
    tf.reset_default_graph()
    my_graph = tf.Graph()
    # 一、建图
    with my_graph.as_default():
        # 1、创建占位符(输入图片,输入的标签,dropout)
        input_x, input_y,change_learning_rate, keep_prob = cnn_net_input(image_shape, n_classes)
        # 2、构建cnn图(传入输入图片,获得logits)
        logits = model_net(input_x, weights, biases, keep_probab, True)
        # 3、构建损失函数
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
            logits=logits, labels=input_y
        ))
        # 4、构建优化器。
        update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
        with tf.control_dependencies(update_ops): #保证train_op在update_ops执行之后再执行。
            optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
            train_opt = optimizer.minimize(loss)
        # 5、计算准确率
        correct_pred = tf.equal(tf.argmax(logits, axis=1), tf.argmax(input_y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
        # 二、构建会话
        with tf.Session() as sess:
            # 1、初始化全局变量
            sess.run(tf.global_variables_initializer())
            # 2、构建迭代的循环
            for epoch in range(epochs):
                batch_i = 1
                # 3、构建批量数据的循环
                # 3、构建批量数据的循环
                for batch_x, batch_y in helper.load_preprocess_training_batch(batch_i, batch_size):
                    # 4、跑train_opt
                    train_session(sess, train_opt, input_x, input_y, batch_x, batch_y, keep_probab,
                                  keep_probability, change_learning_rate, learning_rate)
                    print('Epoch {:>2}, CIFAR-10 Batch:{}'.format(epoch + 1, batch_i), end='')
                # 5、跑 模型损失和 准确率,并打印出来。
                    print_stats(sess, loss, accuracy, input_x, input_y, batch_x, batch_y, keep_probab,
                            keep_probability, change_learning_rate, learning_rate)

                # # 执行模型持久化的。
                # if epoch % every_save_model == 0:
                #     save_file = '_{}_model.ckpt'.format(epoch)
                #     save_file = os.path.join(save_path, save_file)
                #     saver.save(sess, save_path=save_file)
                #     print('Model saved to {}'.format(save_file))
# ________________________________________________________________________________________________________

def train_all_batch():
    """
    跑所有的数据。
    """
    # 一、建图
    with graph.as_default():

        # 1、创建占位符(输入图片,输入的标签,dropout)
        input_x, input_y, change_learning_rate, keep_probab = cnn_net_input(image_shape, n_classes)
        # 2、构建cnn图(传入输入图片,获得logits)
        logits = model_net(input_x, weights, biases, keep_probab, True)
        # 3、构建损失函数
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits,
                                                                         labels=input_y))
        # 4、构建优化器。
        optimizer = tf.train.AdamOptimizer(learning_rate = change_learning_rate)
        train_opt = optimizer.minimize(loss)
        # 5、计算准确率
        correct_pred = tf.equal(tf.argmax(logits, axis=1), tf.argmax(input_y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

        # 6、构建持久化模型的对象 并创建 持久化文件保存的路径
        saver = tf.train.Saver(max_to_keep=2)
        save_path = './models/checkpoints'
        create_file_path(save_path)
        # 二、构建会话
        with tf.Session() as sess:
            # 1、初始化全局变量
            ckpt = tf.train.get_checkpoint_state(save_path)
            if ckpt is not None:
                saver.restore(sess, ckpt.model_checkpoint_path)
                saver.recover_last_checkpoints(ckpt.all_model_checkpoint_paths)
                print('从持久化文件中恢复模型')
            else:
                sess.run(tf.global_variables_initializer())
                print('没有持久化文件,从头开始训练!')
            # 2、构建迭代的循环
            print("epochs: {}".format(epochs))
            for epoch in range(epochs):
                # 多加一个循环,遍历所有的训练数据的batch
                n_batches = 5
                for batch_i in range(1, n_batches+1):
                    # 3、构建批量数据的循环
                    for batch_x, batch_y in helper.load_preprocess_training_batch(batch_i, batch_size):
                        # 4、跑train_opt
                        train_session(sess, train_opt, input_x, input_y, batch_x, batch_y, keep_probab,
                                      keep_probability, change_learning_rate, learning_rate)
                    print('Epoch {:>2}, CIFAR-10 Batch:{}'.format(epoch+1, batch_i), end='')
                    # 5、跑 模型损失和 准确率,并打印出来。
                    print_stats(sess, loss, accuracy, input_x, input_y, batch_x, batch_y, keep_probab,
                                        keep_probability, change_learning_rate, learning_rate)

                # 执行模型持久化的。
                if epoch % every_save_model == 0:
                    save_file = '_{}_model.ckpt'.format(epoch)
                    save_file = os.path.join(save_path, save_file)
                    saver.save(sess, save_path=save_file)
                    print('Model saved to {}'.format(save_file))

def gotest_model():
    """
    调用持久化文件跑测试数据集的数据。(要求准确率在60%以上)
    """
    tf.reset_default_graph()
    test_features, test_labels = pickle.load(
        open('../datas/cifar10/preprocess_test.p', mode='rb')
    )
    # 一、建图
    with graph.as_default():

        # 1、创建占位符(输入图片,输入的标签,dropout)
        input_x, input_y, change_learning_rate, keep_probab = cnn_net_input(image_shape, n_classes)
        # 2、构建cnn图(传入输入图片,获得logits)
        logits = model_net(input_x, weights, biases, keep_probab, True)
        # 3、构建损失函数
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits,
                                                                         labels=input_y))
        # 4、构建优化器。
        optimizer = tf.train.AdamOptimizer(learning_rate=change_learning_rate)
        train_opt = optimizer.minimize(loss)
        # 5、计算准确率
        correct_pred = tf.equal(tf.argmax(logits, axis=1), tf.argmax(input_y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

        # 6、构建持久化模型的对象 并创建 持久化文件保存的路径
        saver = tf.train.Saver(max_to_keep=2)
        save_path = './models/checkpoints'
        # 二、构建会话
        with tf.Session() as sess:
            # 2、获取持久化的信息对象
            ckpt = tf.train.get_checkpoint_state(save_path)
            if ckpt is not None:
                saver.restore(sess, ckpt.model_checkpoint_path)
                saver.recover_last_checkpoints(ckpt.all_model_checkpoint_paths)
                print('从持久化文件中恢复模型')
            else:
                sess.run(tf.global_variables_initializer())
                print('没有持久化文件,从头开始训练!')
            # 2、保存每个批次数据的准确率,再求平均值。
            test_acc_total = []
            # 3、构建迭代的循环
            for test_batch_x, test_batch_y in helper.batch_features_labels(test_features, test_labels, batch_size):
                test_dict = {input_x: test_batch_x,
                             input_y: test_batch_y,
                             keep_probab: 1.0}
                test_batch_acc = sess.run(accuracy, test_dict)
                test_acc_total.append(test_batch_acc)
            print('Test Accuracy:{:.5f}'.format(np.mean(test_acc_total)))
            if np.mean(test_acc_total) > 0.6:
                print('恭喜你,通过了Cifar10项目!你已经掌握了CNN网络的基础知识!')


if __name__=='__main__':
    # explore_data()
    # train_all_batch()
    gotest_model()
    # train_single_batch()

 

猜你喜欢

转载自www.cnblogs.com/qianchaomoon/p/12315969.html
今日推荐