深度篇——神经网络(七) 细说 DNN神经网络手写数字代码演示

返回主目录

返回神经网络目录

上一章:深度篇——神经网络(六)  细说 数据增强与fine-tuning

本小节,细说 神经网络手写数字代码演示

github 上项目下载:mnist_pro 项目

6. 代码项目演示

(1). 前言

虽然前面我们学了很多 深度神经网络的理论,大概,也知道,是那样训练和测试,但是,项目要怎么做呢?估计,还是有不少人一脸懵逼的。懵逼于如何将理论转成代码,去构建项目。下面,我将用一个简单的 手写数字项目,去为大家简单说明一下。

(2). 首先,要明确项目需求

项目需求,就是想把 手写 的 0 ~ 9 的阿拉伯数字图片识别出来。比方说,发票上面的数字(第一个做手写数字识别的,是1989年,美国的一家银行 聘请大佬写的,当时是用卷积神经网络技术 LeNet-5 写的,它的识别率比深度神经DNN 要好些。后面,会为大家讲解卷积神经网络的。当时这个项目是用来识别支票上面签约的数字 )。训练网络,当然离不开数据,所以,我们先下载数据,数据为已经为大家上传到百度云盘:链接:https://pan.baidu.com/s/13OokGc0h3F5rGrxuSLYj9Q   提取码:qfj6  。
 

(3). 构建项目

项目结构如下:

上面的模型,是我随意训练 10 个 epoch 得到的精度:0.9614。我之前跑100 个 epoch,精度上到 0.98 多。

 

(4). 依赖环境和 README.md

依赖环境:

pip install numpy==1.16
pip install easydict
conda install tensorflow-gpu==1.13.1 # 建议不要用 2.0 版本的 tf,坑多

tensorflow 的安装,我前面的博客有详细解说:碎点篇——tensorflow gpu 版本安装  如果不会安装的,可以查看如何安装。

 

README.md 文件

# mnist_pro
DNN 手写数字预测 2020-02-06
- 项目下载地址:https://github.com/wandaoyi/mnist_pro
- 请到百度云盘下载项目所需要的训练数据:
- 链接:https://pan.baidu.com/s/13OokGc0h3F5rGrxuSLYj9Q   提取码:qfj6 

## 参数设置
- 在训练或预测之前,我们要先进行参数设置
- 打开 config.py 文件,对其中的参数或路径进行设置。

## 训练模型
- 运行 mnist_train.py ,简单操作,右键直接 run
- 训练效果如下:
- acc_train: 0.90625
- y_perd: [7 2 1 0 4]
- y_true: [7 2 1 0 4]
- epoch: 10, acc_test: 0.9613999724388123
- epoch: 10, acc_test_2: 0.9606000185012817
- 下面是随意训练的效果,如果想效果好,可以多训练多点epoch
- 也可以自己添加 early-stopping 进去,不麻烦的

## 预测
- 运行 mnist_test.py ,简单操作,右键直接 run
- 运行后,部分预测结果会打印在控制台上
- 预测效果如下:
- 预测值: [7 2 1 0 4]
- 真实值: [7 2 1 0 4]

## tensorboard 日志
- 使用 tensorboard 的好处是,这个日志是实时的,可以一边训练一边看效果图。
- 在 cmd 命令窗口,输入下面命令:
- tensorboard --logdir=G:\work_space\python_space\pro2018_space\wandao\mnist_pro\logs\mnist_log_train --host=localhost
![image](./docs/images/open_tensorboard.png)
- 在 --logdir= 后面是日志的文件夹路径,
- 在 --host= 是用来指定 ip 的,如果不写,则只能电脑的地址,而不能使用 localhost
- 在 谷歌浏览器 上打开 tensorboard 日志: http://localhost:6006/
![image](./docs/images/tensorboard_acc.png)
- 
![image](./docs/images/tensorboard_image.png)
- 
![image](./docs/images/tensorboard_graph.png)
- 
![image](./docs/images/tensorboard_param.png)
- 
![image](./docs/images/tensorboard_histograms.png)
- 
- 测试日志也是这样打开来看,就不详细去说了。
- 
- 关于其他的 ROC 曲线 或 mAP 等,这里就没做这些操作。以后的项目,再操作一番就是了。

 

下面的文件或代码,里面,都有注释

(5). 配置文件 config.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/05 13:51
# @Author   : WanDaoYi
# @FileName : config.py
# ============================================

from easydict import EasyDict as edict
import os


__C = edict()

cfg = __C

# common options 公共配置文件
__C.COMMON = edict()
# windows 获取文件绝对路径, 方便 windows 在黑窗口 运行项目
__C.COMMON.BASE_PATH = os.path.abspath(os.path.dirname(__file__))
# # 获取当前窗口的路径, 当用 Linux 的时候切用这个,不然会报错。(windows也可以用这个)
# __C.COMMON.BASE_PATH = os.getcwd()

__C.COMMON.DATA_PATH = os.path.join(__C.COMMON.BASE_PATH, "dataset")

# 隐藏层的输出节点数
__C.COMMON.HIDDEN_1 = 300
__C.COMMON.HIDDEN_2 = 100
__C.COMMON.HIDDEN_3 = 50


# 训练配置
__C.TRAIN = edict()

# 学习率
__C.TRAIN.LEARNING_RATE = 0.01
# batch_size
__C.TRAIN.BATCH_SIZE = 32
# 迭代次数
__C.TRAIN.N_EPOCH = 10

# 模型保存路径, 使用相对路径,方便移植
__C.TRAIN.MODEL_SAVE_PATH = "./checkpoint/model_"
# dropout 的持有量,0.7 表示持有 70% 的节点。
__C.TRAIN.KEEP_PROB_DROPOUT = 0.7


# 测试配置
__C.TEST = edict()

# 测试模型保存路径
__C.TEST.CKPT_MODEL_SAVE_PATH = "./checkpoint/model_acc=0.961400.ckpt-10"


# 日志配置
__C.LOG = edict()
# 日志保存路径,后面会接上  train 或 test: 如 mnist_log_train
__C.LOG.LOG_SAVE_PATH = "./logs/mnist_log_"

(6). 公共代码 common.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/05 14:09
# @Author   : WanDaoYi
# @FileName : common.py
# ============================================

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from config import cfg
import numpy as np


class Common(object):

    def __init__(self):
        # 数据路径
        self.data_file_path = cfg.COMMON.DATA_PATH

        self.hidden_01 = cfg.COMMON.HIDDEN_1
        self.hidden_02 = cfg.COMMON.HIDDEN_2
        self.hidden_03 = cfg.COMMON.HIDDEN_3

        pass

    # 读取数据
    def read_data(self):
        # 数据下载地址: http://yann.lecun.com/exdb/mnist/
        mnist_data = input_data.read_data_sets(self.data_file_path, one_hot=True)
        train_image = mnist_data.train.images
        train_label = mnist_data.train.labels
        _, n_feature = train_image.shape
        _, n_label = train_label.shape

        return mnist_data, n_feature, n_label

    # bn 操作
    def layer_bn(self, input_data, is_training, name, momentum=0.999, eps=1e-3):
        """
        :param inputdata: 输入数据
        :param is_training: 是否是训练 ,True 为训练
        :param name: 名字
        :param momentum: 动量因子
        :param eps:
        :return:
        """

        return tf.layers.batch_normalization(inputs=input_data, training=is_training,
                                             name=name, momentum=momentum,
                                             epsilon=eps)

    # dropout 处理
    def deal_dropout(self, hidden_layer, keep_prob):
        with tf.name_scope("dropout"):
            tf.summary.scalar('dropout_keep_probability', keep_prob)
            dropped = tf.nn.dropout(hidden_layer, keep_prob)
            tf.summary.histogram('dropped', dropped)
            return dropped
        pass

    # 神经网络层
    def neural_layer(self, x, n_neuron, name, activation=None):
        # 包含所有的计算节点对于这一层, name_scope 可写可不写
        with tf.name_scope(name=name):
            n_input = int(x.get_shape()[1])
            stddev = 2 / np.sqrt(n_input)

            # 这层里面的w可以看成是二维数组,每个神经元对于一组w参数
            # truncated normal distribution 比 regular normal distribution的值小
            # 不会出现任何大的权重值,确保慢慢的稳健的训练
            # 使用这种标准方差会让收敛快
            # w参数需要随机,不能为0,否则输出为0,最后调整都是一个幅度没意义
            with tf.name_scope("weights"):
                init_w = tf.truncated_normal((n_input, n_neuron), stddev=stddev)
                w = tf.Variable(init_w, name="weight")
                self.variable_summaries(w)

            with tf.name_scope("biases"):
                b = tf.Variable(tf.zeros([n_neuron]), name="bias")
                self.variable_summaries(b)
            with tf.name_scope("wx_plus_b"):
                z = tf.matmul(x, w) + b
                tf.summary.histogram('pre_activations', z)

            if activation == "relu":
                activation_result = tf.nn.relu(z)
                tf.summary.histogram('activation_result', activation_result)
                return activation_result
            else:
                return z

    def dnn_layer(self, x, n_label, keep_prob):
        # 隐藏层
        with tf.name_scope("dnn"):
            # 这里要注意矩阵匹配
            x_scale = self.layer_bn(x, is_training=True, name="x_bn")
            hidden_1 = self.neural_layer(x_scale, self.hidden_01, "hidden_01", activation="relu")
            dropped_hidden_1 = self.deal_dropout(hidden_1, keep_prob)

            hidden_scale_1 = self.layer_bn(dropped_hidden_1, is_training=True, name="hidden_bn_1")
            hidden_2 = self.neural_layer(hidden_scale_1, self.hidden_02, "hidden_02", activation="relu")
            dropped_hidden_2 = self.deal_dropout(hidden_2, keep_prob)

            hidden_scale_2 = self.layer_bn(dropped_hidden_2, is_training=True, name="hidden_bn_2")
            hidden_3 = self.neural_layer(hidden_scale_2, self.hidden_03, "hidden_03", activation="relu")
            dropped_hidden_3 = self.deal_dropout(hidden_3, keep_prob)

            hidden_scale_3 = self.layer_bn(dropped_hidden_3, is_training=True, name="hidden_bn_3")
            logits = self.neural_layer(hidden_scale_3, n_label, name="logits")

            return logits

    # 定义Variable变量的数据汇总函数,我们计算出变量的mean、stddev、max、min
    # 对这些标量数据使用tf.summary.scalar进行记录和汇总
    # 使用tf.summary.histogram直接记录变量var的直方图数据
    def variable_summaries(self, param):
        with tf.name_scope('summaries'):
            mean = tf.reduce_mean(param)
            tf.summary.scalar('mean', mean)
            with tf.name_scope('stddev'):
                stddev = tf.sqrt(tf.reduce_mean(tf.square(param - mean)))
            tf.summary.scalar('stddev', stddev)
            tf.summary.scalar('max', tf.reduce_max(param))
            tf.summary.scalar('min', tf.reduce_min(param))
            tf.summary.histogram('histogram', param)

(7). 训练代码mnist_train.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/05 13:52
# @Author   : WanDaoYi
# @FileName : mnist_train.py
# ============================================

from datetime import datetime
import tensorflow as tf
from config import cfg
from core.common import Common
import numpy as np


class MnistTrain(object):

    def __init__(self):
        # 模型保存路径
        self.model_save_path = cfg.TRAIN.MODEL_SAVE_PATH
        self.log_path = cfg.LOG.LOG_SAVE_PATH

        self.learning_rate = cfg.TRAIN.LEARNING_RATE
        self.batch_size = cfg.TRAIN.BATCH_SIZE
        self.n_epoch = cfg.TRAIN.N_EPOCH

        self.common = Common()
        # 读取数据和 维度
        self.mnist_data, self.n_feature, self.n_label = self.common.read_data()

        # 创建设计图
        with tf.name_scope(name="input_data"):
            self.x = tf.placeholder(dtype=tf.float32, shape=(None, self.n_feature), name="input_data")
            self.y = tf.placeholder(dtype=tf.float32, shape=(None, self.n_label), name="input_labels")

        with tf.name_scope(name="input_shape"):
            # 784维度变形为图片保持到节点
            # -1 代表进来的图片的数量、28,28是图片的高和宽,1是图片的颜色通道
            image_shaped_input = tf.reshape(self.x, [-1, 28, 28, 1])
            tf.summary.image('input', image_shaped_input, self.n_label)

        self.keep_prob_dropout = cfg.TRAIN.KEEP_PROB_DROPOUT
        self.keep_prob = tf.placeholder(tf.float32)

        # 获取最后一层的输出
        self.logits = self.common.dnn_layer(self.x, self.n_label, self.keep_prob_dropout)

        # self.config = tf.ConfigProto()
        # self.config.gpu_options.allow_growth = True
        # self.sess = tf.Session(config=self.config)
        self.sess = tf.InteractiveSession()
        # 保存训练模型
        self.saver = tf.train.Saver()
        pass

    # 灌入数据
    def feed_dict(self, train_flag=True):
        # 训练样本
        if train_flag:
            # 获取下一批次样本
            x_data, y_data = self.mnist_data.train.next_batch(self.batch_size)
            keep_prob = self.keep_prob_dropout
            pass
        # 验证样本
        else:
            x_data, y_data = self.mnist_data.test.images, self.mnist_data.test.labels
            keep_prob = 1.0
            pass
        return {self.x: x_data, self.y: y_data, self.keep_prob: keep_prob}
        pass

    # 训练
    def do_train(self):

        # 计算loss 损失
        with tf.name_scope("train_loss"):
            # softmax_cross_entropy_with_logits 只会给 one-hot 编码
            # sparse_softmax_cross_entropy_with_logits 只会给没有 one-hot 编码,使用的会给0-9分类号
            cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=self.y, logits=self.logits)
            loss = tf.reduce_mean(cross_entropy, name="loss")
            tf.summary.scalar("cross_entropy", loss)
            pass

        # 构建优化器
        with tf.name_scope("optimizer"):
            optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate)
            training_op = optimizer.minimize(loss=loss)

        # 比对正确的率
        # 只处理没 one-hot 编码,获取logits里面最大的那1位和y比较类别好是否相同,返回True或者False一组值
        # correct = tf.nn.in_top_k(logits, y, 1) 处理 one-hot 编码后的 y
        with tf.name_scope("accuracy"):
            correct = tf.equal(tf.argmax(self.logits, 1), tf.argmax(self.y, 1))
            acc = tf.reduce_mean(tf.cast(correct, tf.float32))
            tf.summary.scalar("accuracy", acc)

        # 因为我们之前定义了太多的tf.summary汇总操作,逐一执行这些操作太麻烦,
        # 使用tf.summary.merge_all()直接获取所有汇总操作,以便后面执行
        merged = tf.summary.merge_all()

        # 定义两个tf.summary.FileWriter文件记录器再不同的子目录,分别用来存储训练和测试的日志数据
        # 同时,将Session计算图sess.graph加入训练过程,这样再TensorBoard的GRAPHS窗口中就能展示
        train_writer = tf.summary.FileWriter(self.log_path + 'train', self.sess.graph)
        test_writer = tf.summary.FileWriter(self.log_path + 'test')

        # 构建初始化变量
        init_variable = tf.global_variables_initializer()

        self.sess.run(init_variable)

        test_acc = None

        for epoch in range(self.n_epoch):
            # 获取总样本数量
            batch_number = self.mnist_data.train.num_examples
            # 获取总样本一共几个批次
            size_number = int(batch_number / self.batch_size)

            for number in range(size_number):
                summary, _ = self.sess.run([merged, training_op], feed_dict=self.feed_dict())
                # 第几次循环
                i = epoch * size_number + number + 1
                train_writer.add_summary(summary, i)
                pass

                if number == size_number - 1:
                    # 获取下一批次样本
                    x_batch, y_batch = self.mnist_data.train.next_batch(self.batch_size)
                    acc_train = acc.eval(feed_dict={self.x: x_batch, self.y: y_batch})
                    print("acc_train: {}".format(acc_train))

            # 测试
            output = self.logits.eval(feed_dict={self.x: self.mnist_data.test.images})
            y_perd = np.argmax(output, axis=1)
            print("y_perd: {}".format(y_perd[: 5]))
            y_true = np.argmax(self.mnist_data.test.labels, axis=1)
            print("y_true: {}".format(y_true[: 5]))

            # 验证 方法一
            acc_test = acc.eval(feed_dict={self.x: self.mnist_data.test.images,
                                           self.y: self.mnist_data.test.labels})

            print("epoch: {}, acc_test: {}".format(epoch + 1, acc_test))

            # 验证 方法二 两个方法,随便挑一个都可以的。
            test_summary, acc_test_2 = self.sess.run([merged, acc], feed_dict=self.feed_dict(False))
            print("epoch: {}, acc_test_2: {}".format(epoch + 1, acc_test_2))
            test_writer.add_summary(test_summary, epoch + 1)

            test_acc = acc_test

        save_path = self.model_save_path + "acc={:.6f}".format(test_acc) + ".ckpt"
        # 保存模型
        self.saver.save(self.sess, save_path, global_step=self.n_epoch)

        train_writer.close()
        test_writer.close()
        pass


if __name__ == "__main__":
    # 代码开始时间
    start_time = datetime.now()
    print("开始时间: {}".format(start_time))

    demo = MnistTrain()
    demo.do_train()

    # 代码结束时间
    end_time = datetime.now()
    print("结束时间: {}, 训练模型耗时: {}".format(end_time, end_time - start_time))

(8). 测试代码 mnist_test.py

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
# ============================================
# @Time     : 2020/02/05 13:52
# @Author   : WanDaoYi
# @FileName : mnist_test.py
# ============================================

from datetime import datetime
import tensorflow as tf
import numpy as np
from config import cfg
from core.common import Common


class MnistTest(object):

    def __init__(self):

        self.common = Common()
        # 读取数据和 维度
        self.mnist_data, self.n_feature, self.n_label = self.common.read_data()

        # ckpt 模型
        self.test_ckpt_model = cfg.TEST.CKPT_MODEL_SAVE_PATH
        print("test_ckpt_model: {}".format(self.test_ckpt_model))

        # tf.reset_default_graph()
        # 创建设计图
        with tf.name_scope(name="input"):
            self.x = tf.placeholder(dtype=tf.float32, shape=(None, self.n_feature), name="input_data")
            self.y = tf.placeholder(dtype=tf.float32, shape=(None, self.n_label), name="input_labels")

        # 获取最后一层的输出
        self.logits = self.common.dnn_layer(self.x, self.n_label, 1)

    # 使用 ckpt 模型测试
    def do_ckpt_test(self):

        saver = tf.train.Saver()

        with tf.Session() as sess:
            # 重载模型
            saver.restore(sess, self.test_ckpt_model)

            # 预测
            output = self.logits.eval(feed_dict={self.x: self.mnist_data.test.images})

            # 将 one-hot 预测值转为 数字
            y_perd = np.argmax(output, axis=1)
            print("预测值: {}".format(y_perd[: 5]))

            # 真实值
            y_true = np.argmax(self.mnist_data.test.labels, axis=1)
            print("真实值: {}".format(y_true[: 5]))

        pass


if __name__ == "__main__":
    # 代码开始时间
    start_time = datetime.now()
    print("开始时间: {}".format(start_time))

    demo = MnistTest()
    # 使用 ckpt 模型测试
    demo.do_ckpt_test()

    # 代码结束时间
    end_time = datetime.now()
    print("结束时间: {}, 训练模型耗时: {}".format(end_time, end_time - start_time))

(9). 日志操作

训练生成有日志之后,用 cmd 打开命令窗口。

输入命令: tensorboard --logdir=日志文件夹的路径 --host=localhost

在 tensorboard 里面,需要 定义 --host=localhost 在网页才能用 localhost ip 打开,不然,只能拷贝 命令窗口 展示的 机器 ip 打开。tensorboard 的端口号是 6006,正好是 "goog" 谷歌单词反过来。说到这里,tensorboard 需要用 Google 浏览器打开,我试过 火狐和 搜狗,都无法打开。

我自己训练得到的 tensorboard 日志打开后如下:

tensorboard --logdir=G:\work_space\python_space\pro2018_space\wandao\mnist_pro\logs\mnist_log_train --host=localhost

把  http://localhost:6006 在 Google 浏览器打开

在本项目,我没有用到 early-stopping 和 fine-tuning,这两个,以后有机会再用上。并且,到后面的项目,还会用 mAP 等计算 精度。如果在座的有兴趣对本项目进行改进玩玩,也是可以自己先搞起来的。

不足的地方,请大佬多多指教。

                

返回主目录

返回神经网络目录

上一章:深度篇——神经网络(六)  细说 数据增强与fine-tuning

发布了42 篇原创文章 · 获赞 15 · 访问量 2764

猜你喜欢

转载自blog.csdn.net/qq_38299170/article/details/104119085
今日推荐