Tensorflow---基于CNN的手写数字的代码实现

Tensorflow—基于CNN的手写数字的代码实现

全局来看

在这里插入图片描述

局部来看

一、外部参数

# 定义外部传入的参数
tf.app.flags.DEFINE_bool(flag_name="is_train",
                         default_value=True,
                         docstring="给定是否是训练操作,True表示训练,False表示预测!!")
tf.app.flags.DEFINE_string(flag_name="checkpoint_dir",
                           default_value="./models",
                           docstring="给定模型存储的文件夹,默认为./models")
tf.app.flags.DEFINE_string(flag_name="logdir",
                           default_value="./graph",
                           docstring="给定模型日志存储的路径,默认为./graph")
tf.app.flags.DEFINE_integer(flag_name="batch_size",
                            default_value=16,
                            docstring="给定训练的时候每个批次的样本数目,默认为16.")
tf.app.flags.DEFINE_integer(flag_name="store_per_batch",
                            default_value=100,
                            docstring="给定每隔多少个批次进行一次模型持久化的操作,默认为100")
tf.app.flags.DEFINE_integer(flag_name="validation_per_batch",
                            default_value=100,
                            docstring="给定每隔多少个批次进行一次模型的验证操作,默认为100")
tf.app.flags.DEFINE_float(flag_name="learning_rate",
                          default_value=0.01,
                          docstring="给定模型的学习率,默认0.01")
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_传入参数的数据格式(flag_name,
									default_value,
									docstring)
这个参数的作用:可以在外部运行代码的时候传入参数,代码内部可以使用,调用方式:
tf.app.flags.FLAGS.flag_name,例如:
									'''tf.app.flags.FLAGS.learning_rate'''

二、文件夹存在验证函数

def create_dir_with_not_exits(dir_path):
    """
    如果文件的文件夹路径不存在,直接创建
    :param dir_path:
    :return:
    """
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)

三、模型的构建(重点)

def create_model(input_x):
    """
    构建模型
    :param input_x: 占位符,格式为[None, 784]
    :return:
    """
    # 定义一个网络结构: input -> conv -> relu -> pooling -> conv -> relu -> pooling -> FC -> relu -> FC
    with tf.variable_scope("net"):
        with tf.variable_scope("Input"):
            # 这里定义一些图像的处理方式,包括:格式转换、基础处理(大小、剪切...)
            net = tf.reshape(input_x, shape=[-1, 28, 28, 1])
            print(net.get_shape())

            # 可视化图像
            tf.summary.image(name='image', tensor=net, max_outputs=5)
        with tf.variable_scope("Conv1"):
            """
            def conv2d(input, filter, strides, padding, use_cudnn_on_gpu=True, data_format="NHWC", name=None):
                input:输入的tensor对象,会对该值进行卷积操作。默认形状为:[batch_size, height, width, channels], batch_size表示一个批次中的样本数目,height表示一个feature map的高度,width表示一个feature map的宽度,channels表示一个图像的feature map的数量。一般简写为NHWC。当data_format的格式为NHWC的时候,使用默认的形状,如果给给data_format给定位NCHW,那么input的格式为:[batch_size, channels, height, width]
                filter: 卷积核Tensor对象(w),格式要求是一个4维的Tensor对象(一般为变量), 格式为: [height, width, in_channels, out_channels],height和width表示窗口的高度宽度,in_channels就是输入对象的通道数目,out_channels给定当前卷积之后的通道数目,也就是我们理论中所说的卷积核的数目。
                strides: 给定卷积窗口的滑动步长,格式和data_format有关,是一个数组。格式: [batch, in_height, in_width, in_channels]或者[batch, in_channels, in_height, in_width], 其中batch和in_channels必须为1,in_height和in_width分别给定在高度和宽度上的滑动窗口大小。
                padding:是否进行数据的填充操作,可选值:SAME表示进行最小填充,VALID表示不进行填充,直接删除多余的像素值。TensorFlow中有一个特征的值,当步长为1,padding为SAME的时候,卷积是不改变的feature map大小的。
                data_format:给定数据格式是NHWC还是NCHW,默认是NHWC
            """
            filter = tf.get_variable(name='w', shape=[3, 3, 1, 10])
            bias = tf.get_variable(name='b', shape=[10])
            net = tf.nn.conv2d(input=net, filter=filter, strides=[1, 1, 1, 1], padding='SAME')
            net = tf.nn.bias_add(net, bias)
            print(net.get_shape())

            # 对于卷积之后的值做一个可视化操作
            shape = net.get_shape()
            for k in range(shape[-1]):
                image_tensor = tf.reshape(net[:, :, :, k], shape=[-1, shape[1], shape[2], 1])
                tf.summary.image(name='image', tensor=image_tensor, max_outputs=5)
        with tf.variable_scope("Relu1"):
            net = tf.nn.relu(net)
            print(net.get_shape())

            # 对于卷积之后的值做一个可视化操作
            shape = net.get_shape()
            for k in range(shape[-1]):
                image_tensor = tf.reshape(net[:, :, :, k], shape=[-1, shape[1], shape[2], 1])
                tf.summary.image(name='image', tensor=image_tensor, max_outputs=5)
        with tf.variable_scope("Pooling1"):
            """
            def max_pool(value, ksize, strides, padding, data_format="NHWC", name=None):
                value: 给定需要进行池化的tensor对象, 默认形状为:[batch_size, height, width, channels], batch_size表示一个批次中的样本数目,height表示一个feature map的高度,width表示一个feature map的宽度,channels表示一个图像的feature map的数量。一般简写为NHWC。当data_format的格式为NHWC的时候,使用默认的形状,如果给给data_format给定位NCHW,那么input的格式为:[batch_size, channels, height, width]
                ksize: 包含四个整数的数组, 形状为: data_format为NHWC[batch, in_height, in_width, in_channels]或者data_format值为NCHW[batch, in_channels, in_height, in_width]; 要求batch和in_channels必须为1,in_height和in_width指定的就是池化窗口的高度和宽度
                strides: 给定卷积窗口的滑动步长,格式和data_format有关,是一个数组。格式: [batch, in_height, in_width, in_channels]或者[batch, in_channels, in_height, in_width], 其中batch和in_channels必须为1,in_height和in_width分别给定在高度和宽度上的滑动窗口大小。
                padding:是否进行数据的填充操作,可选值:SAME表示进行最小填充,VALID表示不进行填充,直接删除多余的像素值。TensorFlow中有一个特征的值,当步长为1,padding为SAME的时候,池化是不改变的feature map大小的。
                data_format:给定数据格式是NHWC还是NCHW,默认是NHWC
            """
            # ksize:池化的窗口形状;strides:窗口的滑动大小。
            net = tf.nn.max_pool(value=net, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
            print(net.get_shape())

            # 对于卷积之后的值做一个可视化操作
            shape = net.get_shape()
            for k in range(shape[-1]):
                image_tensor = tf.reshape(net[:, :, :, k], shape=[-1, shape[1], shape[2], 1])
                tf.summary.image(name='image', tensor=image_tensor, max_outputs=5)
        with tf.variable_scope("Conv2"):
            filter = tf.get_variable(name='w', shape=[3, 3, 10, 20])
            bias = tf.get_variable(name='b', shape=[20])
            net = tf.nn.conv2d(input=net, filter=filter, strides=[1, 1, 1, 1], padding='SAME')
            net = tf.nn.bias_add(net, bias)
            print(net.get_shape())

            # 对于卷积之后的值做一个可视化操作
            shape = net.get_shape()
            for k in range(shape[-1]):
                image_tensor = tf.reshape(net[:, :, :, k], shape=[-1, shape[1], shape[2], 1])
                tf.summary.image(name='image', tensor=image_tensor, max_outputs=5)
        with tf.variable_scope("Relu2"):
            net = tf.nn.relu(net)
            print(net.get_shape())

            # 对于卷积之后的值做一个可视化操作
            shape = net.get_shape()
            for k in range(shape[-1]):
                image_tensor = tf.reshape(net[:, :, :, k], shape=[-1, shape[1], shape[2], 1])
                tf.summary.image(name='image', tensor=image_tensor, max_outputs=5)
        with tf.variable_scope("Pooling2"):
            net = tf.nn.max_pool(value=net, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
            print(net.get_shape())

            # 对于卷积之后的值做一个可视化操作
            shape = net.get_shape()
            for k in range(shape[-1]):
                image_tensor = tf.reshape(net[:, :, :, k], shape=[-1, shape[1], shape[2], 1])
                tf.summary.image(name='image', tensor=image_tensor, max_outputs=5)
        with tf.variable_scope("FC1"):
            shape = net.get_shape()
            dim_size = shape[1] * shape[2] * shape[3]
            net = tf.reshape(net, shape=[-1, dim_size])
            w = tf.get_variable(name='w', shape=[dim_size, 500])
            b = tf.get_variable(name='b', shape=[500])
            net = tf.matmul(net, w) + b
            net = tf.nn.relu(net)
        with tf.variable_scope("FC2"):
            w = tf.get_variable(name='w', shape=[500, 10])
            b = tf.get_variable(name='b', shape=[10])
            logits = tf.matmul(net, w) + b
        with tf.variable_scope("Prediction"):
            # 每行的最大值对应的下标就是当前样本的预测值
            predictions = tf.argmax(logits, axis=1)

    return logits, predictions
注意点:
1.可视化图像:tf.summary.image(name='image', tensor=net, max_outputs=5)
2.tf.nn.bias_add:一个叫bias的向量加到一个叫value的矩阵上,是向量与矩阵的每一行进行相加,得到的结果和value矩阵大小相同。
3.tf.nn,tf.layers, tf.contrib这三个库的区别:
++tf.nn :提供神经网络相关操作的支持,包括卷积操作(conv)、池化操作(pooling)、归一化、loss、分类操作、embedding、RNN、Evaluation。
++tf.layers:主要提供的高层的神经网络,主要和卷积相关的,tf.nn会更底层一些。
++tf.contrib:tf.contrib.layers提供够将计算图中的 网络层、正则化、摘要操作、是构建计算图的高级操作,但是tf.contrib包含不稳定和实验代码,有可能以后API会改变。

四、损失函数的构建

def create_loss(labels, logits):
    """
    基于给定的实际值labels和预测值logits进行一个交叉熵损失函数的构建
    :param labels:  是经过哑编码之后的Tensor对象,形状为[n_samples, n_class]
    :param logits:  是神经网络的最原始的输出,形状为[n_samples, n_class], 每一行最大值那个位置对应的就是预测类别,没有经过softmax函数转换。
    :return:
    """
    with tf.name_scope("loss"):
        # loss = tf.reduce_mean(-tf.log(tf.reduce_sum(labels * tf.nn.softmax(logits))))
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits))
        tf.summary.scalar('loss', loss)
    return loss
tf.nn.softmax_cross_entropy_with_logits:只是对同一行进行求和,还需要在前面加一个tf.reduce_mean求损失均值

五、优化器的构建

def create_train_op(loss, learning_rate=0.01, global_step=None):
    """
    基于给定的损失函数构建一个优化器,优化器的目的就是让这个损失函数最小化
    :param loss:
    :param learning_rate:
    :param global_step:
    :return:
    """
    with tf.name_scope("train"):
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
        train_op = optimizer.minimize(loss, global_step=global_step)
    return train_op
注意点:
minimize中global_step的作用:minimize函数中global_step参数值每次迭代都会更新,如果不加,会导致损失一直不下降

六、准确率的计算

def create_accuracy(labels, predictions):
    """
    基于给定的实际值和预测值,计算准确率
    :param labels:  是经过哑编码之后的Tensor对象,形状为[n_samples, n_class]
    :param predictions: 实际的预测类别下标,形状为[n_samples,]
    :return:
    """
    with tf.name_scope("accuracy"):
        # 获取实际的类别下标,形状为[n_samples,]
        y_labels = tf.argmax(labels, 1)
        # 计算准确率
        accuracy = tf.reduce_mean(tf.cast(tf.equal(y_labels, predictions), tf.float32))
        tf.summary.scalar('accuracy', accuracy)
    return accuracy

七、训练操作

def train():
    # 对于文件是否存在做一个检测
    create_dir_with_not_exits(FLAGS.checkpoint_dir)
    create_dir_with_not_exits(FLAGS.logdir)

    with tf.Graph().as_default():
        # 一、执行图的构建
        # 0. 相关输入Tensor对象的构建
        input_x = tf.placeholder(dtype=tf.float32, shape=[None, 784], name='input_x')
        input_y = tf.placeholder(dtype=tf.float32, shape=[None, 10], name='input_y')
        global_step = tf.train.get_or_create_global_step()

        # 1. 网络结构的构建
        logits, predictions = create_model(input_x)
        # 2. 构建损失函数
        loss = create_loss(input_y, logits)
        # 3. 构建优化器
        train_op = create_train_op(loss,
                                   learning_rate=FLAGS.learning_rate,
                                   global_step=global_step)
        # 4. 构建评估指标
        accuracy = create_accuracy(input_y, predictions)

        # 二、执行图的运行/训练(数据加载、训练、持久化、可视化、模型的恢复....)
        with tf.Session() as sess:
            # a. 创建一个持久化对象(默认会将所有的模型参数全部持久化,因为不是所有的都需要的,最好仅仅持久化的训练的模型参数)
            var_list = tf.trainable_variables()
            # 是因为global_step这个变量是不参与模型训练的,所以模型不会持久化,这里加入之后,可以明确也持久化这个变量。
            var_list.append(global_step)
            saver = tf.train.Saver(var_list=var_list)

            # a. 变量的初始化操作(所有的非训练变量的初始化 + 持久化的变量恢复)
            # 所有变量初始化(如果有持久化的,后面做了持久化后,会覆盖的)
            sess.run(tf.global_variables_initializer())
            # 做模型的恢复操作
            ckpt = tf.train.get_checkpoint_state(FLAGS.checkpoint_dir)
            if ckpt and ckpt.model_checkpoint_path:
                print("进行模型恢复操作...")
                # 恢复模型
                saver.restore(sess, ckpt.model_checkpoint_path)
                # 恢复checkpoint的管理信息
                saver.recover_last_checkpoints(ckpt.all_model_checkpoint_paths)

            # 获取一个日志输出对象
            train_logdir = os.path.join(FLAGS.logdir, 'train')
            validation_logdir = os.path.join(FLAGS.logdir, 'validation')
            train_writer = tf.summary.FileWriter(logdir=train_logdir, graph=sess.graph)
            validation_writer = tf.summary.FileWriter(logdir=validation_logdir, graph=sess.graph)
            # 获取所有的summary输出操作
            summary = tf.summary.merge_all()

            # b. 训练数据的产生/获取(基于numpy随机产生<可以先考虑一个固定的数据集>)
            mnist = input_data.read_data_sets(
                train_dir='../datas/mnist',  # 给定本地磁盘的数据存储路径
                one_hot=True,  # 给定返回的数据中是否对Y做哑编码
                validation_size=5000  # 给定验证数据集的大小
            )

            # c. 模型训练
            batch_size = FLAGS.batch_size
            step = sess.run(global_step)
            vn_accuracy_ = 0
            while True:
                # 开始模型训练
                x_train, y_train = mnist.train.next_batch(batch_size=batch_size)
                _, loss_, accuracy_, summary_ = sess.run([train_op, loss, accuracy, summary], feed_dict={
                    input_x: x_train,
                    input_y: y_train
                })
                print("第{}次训练后模型的损失函数为:{}, 准确率:{}".format(step, loss_, accuracy_))
                train_writer.add_summary(summary_, global_step=step)

                # 持久化
                if step % FLAGS.store_per_batch == 0:
                    file_name = 'model_%.3f_%.3f_.ckpt' % (loss_, accuracy_)
                    save_path = os.path.join(FLAGS.checkpoint_dir, file_name)
                    saver.save(sess, save_path=save_path, global_step=step)

                if step % FLAGS.validation_per_batch == 0:
                    vn_loss_, vn_accuracy_, vn_summary_ = sess.run([loss, accuracy, summary],
                                                                   feed_dict={
                                                                       input_x: mnist.validation.images,
                                                                       input_y: mnist.validation.labels
                                                                   })
                    print("第{}次训练后模型在验证数据上的损失函数为:{}, 准确率:{}".format(step,
                                                                    vn_loss_,
                                                                    vn_accuracy_))
                    validation_writer.add_summary(vn_summary_, global_step=step)

                # 退出训练(要求当前的训练数据集上的准确率至少为0.8,然后最近一次验证数据上的准确率为0.8)
                if accuracy_ > 0.99 and vn_accuracy_ > 0.99:
                    # 退出之前再做一次持久化操作
                    file_name = 'model_%.3f_%.3f_.ckpt' % (loss_, accuracy_)
                    save_path = os.path.join(FLAGS.checkpoint_dir, file_name)
                    saver.save(sess, save_path=save_path, global_step=step)
                    break
                step += 1
            # 关闭输出流
            train_writer.close()
            validation_writer.close()
注意点:
1. tf.train.get_or_create_global_step():创建一个计算步数的全局变量,相当于tf.Variable(0,tf.float32)
2. tf.trainable_variables () 和 tf.all_variables()的区别:
	这两个都是保存tf中变量的列表,但是前者是只保存可训练的变量,后者是保存所有的变量
3.

八、main函数

def main(_):
    if FLAGS.is_train:
        # 进入训练的代码执行中
        print("开始进行模型训练运行.....")
        train()
    else:
        # 进入测试、预测的代码执行中
        print("开始进行模型验证、测试代码运行.....")
        prediction()
    print("Done!!!!")
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进我的收藏吃灰吧~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值