数据集下载地址(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()