Skip to content

hx-w/Handwritten-digit-recognition-CPP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

目录

手写体数字识别(c++)


手写体数字识别(c++)

深度学习的"hello world!"

2019-01-03

概况

本项目利用全连接神经网络对手写体数字进行训练与识别。

  • 训练与测试均采用MNIST数据集。
  • 采用全连接神经网络,相关超参数存储在.\config\hyparam.cfg
  • hl_num 隐藏层数
  • hl_nodes_num 隐藏层结点数
  • rate 学习速率
  • 相关算法:
  • 随机梯度下降
  • 反向传播算法

如果不关注实现方法和算法细节,可以直接跳转至测试方法:测试方法

数据结构

待补

算法推导

变量声明

  • 输入数据:

    其中

    为输入层, 为标签

    的最后一项恒为1,为了匹配中的偏置项

    除了最后一层即输出层之外,其他层的最后一项恒为1

  • 输出数据:

  • 隐藏层:

    其中

  • 边值权重:

    其中

    表示前一层的结点i连接下一层的结点j的权重

    n 表示前一层结点的个数-1(不算最后恒定的1)

    m 表示后一层结点的个数-1(不算最后恒定的1)

    img 为偏置项,用来完成感知器中的偏执操作

  • 层间输出:

    即上一层结点i向下层结点j贡献的值

  • 代价函数:

  • 激活函数:

  • 学习速率:

  • 结点误差:

  • 隐藏层数: hl_num

  • 隐藏层结点数: hl_nodes_num

正向传播

最后增加新项,值为1,以下除输出层之外同理。

随机梯度下降

采用随机梯度下降算法优化训练时间,并且可以有效逃离多元函数局部极小值。

由于是输出数据的直接函数,故可将看作所有的多元函数。

在某一维度下求该函数梯度的负值即的调整方向。

如图:

故针对某一边值权重进行更新的方法为:

参考内容

反向传播算法

在随机梯度下降的基础上,每次训练更新所有边值权重,即关键点是求

右半部分:

直接求偏导得:

左半部分:
  • 在输出层

    由各变量定义,可得:

  • 在隐藏层

    定义表示为其下一层的结点net集合

    则根据多元函数全导数法则

思维导图

结论

  • 连接输出层

  • 连接隐藏层

参考内容

训练测试数据

MNIST数据集 下载地址

The MNIST database (Modified National Institute of Standards and Technology database) is a large database of handwritten digits that is commonly used for training various image processingsystems. The database is also widely used for training and testing in the field of machine learning.[3][4] It was created by "re-mixing" the samples from NIST's original datasets. The creators felt that since NIST's training dataset was taken from American Census Bureau employees, while the testing dataset was taken from American high school students, it was not well-suited for machine learning experiments. Furthermore, the black and white images from NIST were normalized to fit into a 28x28 pixel bounding box and anti-aliased, which introduced grayscale levels.

—— From Wikipedia

MNIST数据集不能直接使用,用脚本对原始数据处理,得到两份*.csv文件。

训练数据: mnist_train.csv 60000组

测试数据: minist_test.csv 10000组

每组数据包含项:

  • 第1项,数据标签label,取值[0,9]。

  • 其余784项是图片每个像素的灰度,取值[0,255]。

文件目录

.
├── bin
|    ├── MNIST.zip         # MNIST原始数据集
|    └── convert.py        # py2脚本,可将MNIST原始数据集转化为*.csv格式 (使用时修改路径)
├── config
|    ├── hyparam.cfg       # 超参数配置
|    └── param.cfg         # 训练参数配置(可重新训练)
├── img
|    ├── frame.jpeg        # 展示图1
|    ├── release.jpg       # 效果图1
|    ├── ...               # 效果图2
|    └── show.jpg          # 展示图2
├── params                 # 参与者贡献的数据文件集合
|    ├── ...
|    └── A_B_C.zip         # A:参与者ID   B:隐藏层数   C:隐藏层结点数
|         ├── hyparam.cfg  # 该测试所用的超参数配置
|         ├── param.cfg    # 训练得到的参数配置
|         └── README.txt   # 此次测试的简单说明(包括训练组数,测试组数,准确率以及对源码的改进等)
├── real-test              # 自定义测试(TODO)
|    └── ...
├── release
|    └── Handwriting_digit_rec_MLP.exe    # 可执行程序
├── result
|    └── acc_save.dat      # 测试的结果保存文件(默认) 
├── src
|    ├── Assistant.cpp     # 训练助手类实现
|    ├── Assistant.h       # 训练助手类定义
|    ├── Data_fetch.cpp    # 数据获取类实现
|    ├── Data_fetch.h      # 数据获取类定义
|    ├── MLP_Neural_Network.cpp  # 多层全连接神经网络类实现
|    ├── MLP_Neural_Network.h    # 多层全连接神经网络类定义
|    └── demo.cpp          # 主函数入口,效果展示
├── static
|    └── train_test.zip    # 训练和测试数据(*.csv),使用时解压
└── README.md              # 说明文档

测试方法

该流程仅作演示。

  • 测试环境:windows 10

  • 命令行: cmd

  • 超参数配置: .\config\hyparam.cfg(预置)

    隐藏层数目: 1

    隐藏层结点: 300

    学习速率: 0.35

  • 训练参数配置: .\config\param.cfg(默认)

    训练.\static\train_test\mnist_train.csv 60000组得到

  • 测试结果:测试结果默认保存在.\result\acc_save.dat中,在之前的测试结果后追加

  1. 命令行当前在release目录即.\release\下。

  2. .\Handwriting_digit_rec_MLP.exe运行程序

    re

  3. 进行多个参数设置,其中:

  1. *.Want show every record detail?

    是否显示每组数据的测试结果?

  2. *.Want to train before testing or test directly with existing parameters?

    输入yes,选择重新训练一组新的参数用于测试

    输入no,选择直接用现有的参数进行测试

  3. *.Input the path of train_data

    输入用于训练的数据文件路径<输入'D'默认为../static/train_test/mnist_train.csv>(问题2 回答yes后出现)

  4. *.Input the records num want to be trained(max 60000)

    输入训练的数据组数,最大为60000(问题2 回答yes后出现)

  5. *.Input the file name of trained parameters

    输入用于保存训练后得到的参数的文件路径<输入'D'默认为../config/param.cfg>(问题2 回答yes后出现)

  6. *.Input the path of parameters_data

    输入现有的参数文件路径<输入'D'默认为../config/param.cfg>(问题2 回答no后出现)

  7. *.Input the path of test_data

    输入用于测试的数据文件路径<输入'D'默认为../static/train_test/mnist_test.csv>

  8. *.Input the records num want to be tested(max 10000)

    输入测试的数据组数,最大为10000

  9. *.Input the file name of accuracy rate result

    输入存放测试结果的文件路径<输入'D'默认为../result/acc_save.dat>

这里演示一种情况,这使用默认路径下的param.cfg,见下图

  • 不显示每组数据的测试结果
  • 选择现有的训练参数进行测试
  • 训练参数在默认路径: ../config/param.cfg
  • 测试数据在默认路径: ../static/train_test/mnist_test.csv
  • 测试数据10000组
  • 测试结果保存在默认路径: ../result/acc_save.dat

测试结果

上一部分演示的结果为:

其中每部分:

  • Start time: mon-day hour:min:sec # 程序开始的本地时间
  • Param: ../config/param.cfg # 训练参数的路径
  • Test_num: 10000 # 测试组数
  • Accuracy 0.9536 # 识别准确率 (95.36%)
  • Finish time: mon-day hour:min:sec # 程序结束的本地时间

可把.\params\中的压缩包解压,其中param.cfghyparam.cfg复制到.\config\中作为新配置文件进行测试。

一般测试数据组数应尽量大(10000)。

项目参与

说明

除了仅用该程序做测试,还可以通过改变超参数或输入数据预处理算法来贡献新的训练参数配置。

  1. 更改.\config\hyparam.cfg中的超参数配置 (详情见超参数)

  2. 如果有必要,更改输入数据预处理算法 (详情见输入预处理)

  3. 训练一组新的param.cfg,并且对该训练参数配置进行测试,测试数据数目应为10000(测完)

  4. 训练测试之后进行打包,规则如下:

    • 撰写说明文档README.txt,内容如下

    • 将训练得到的param.cfg,所用超参数配置hyparam.cfg,以及说明文档README.txt放入同一文件夹,并压缩成压缩文件。

    • 该文件夹命名为 "昵称ID_隐藏层数目_隐藏层结点数"。

    • 放入.\params\目录下上传

超参数

hyparam.cfg文件中需要设置3种配置,如下:

  • rate学习速率

    示例值: 0.35

    调整建议:

    学习速率太大容易过优化,太小训练速度慢容易陷入局部极值。

    可以适当调小,或采用动态更新的办法,每次训练根据代价函数的值更新。

  • hl_num隐藏层层数

    示例值: 1

    调整建议:

    由于输入层到输出层为784维到10维的映射,隐藏层数不宜过多。

    可以适当增大隐藏层层数,根据以往测试hl_num的变化对训练速度的影响比hl_nodes_num小。

  • hl_nodes_num隐藏层的维度

    示例值: 300

    调整建议:

    隐藏层的维度对训练速度的影响很大,可以适当减小。

    对隐藏层维度即隐藏层结点个数的取值有以下经验公式:

    其中:

    m: 隐藏层结点数

    n: 输入层结点数

    l: 输出层结点数

    PS: 由于程序设计的问题,所有隐藏层的维度都将是hl_nodes_num,如果有需要可以改进为不同隐藏层的维度不同。

输入预处理

由于输入层元素大小在0到255之间不利于训练,需要进行预处理。

  • 预处理函数: inline void Network::input_prep(std::vector<double> &)

  • 函数实现位置: "MLP_Neural_Network.cpp" >row 300+

  • 预览:

    /**
     * @Brief  Preprocessing 784-dimensional vector input_layer
     * @Date   2019-01-04
     * @Param  inputs: the vector<double> of input value, sizeof(inputs) == 28 * 28
     *                 values in inputs are guaranteed to [0, MAX_VALUE]
     *                 with MAX_VALUE defined as (1 << 8) - 1 at "MLP_Neural_Network.h"
     *
     * @Author Herixth
     * @Algorithm 
     *        // Write down specific processing algorithms:
     *        For each element E in inputs
     *        > if E > (MAX_VALUE >> 1)
     *              E = 1.0
     *        > else
     *              E = 0.0
              So the input_layer can be treated as a zero-one matrix
     */
    inline void Network::input_prep(std::vector<double> &inputs) {
        std::vector<double>::iterator iter = inputs.begin();
        for (; iter != inputs.end(); iter++) {
            (*iter) = ((*iter) > (MAX_VALUE >> 1));
        }
    }
  • 调整建议:

    该方法将输入进行二值化离散处理,可以考虑多分几种情况,把离散的程度增大。

分析总结

对通用的全连接神经网络模型

  • 输入层结点数目:

  • 输出层结点数目:

  • 隐藏层数目:

  • 层隐藏层的结点数目:

  • 训练测试数据组数:

  • 模型边值权重总数:

  • 输入预处理

    计算次数

  • 正向传播

    计算次数:

  • 反向传播

    计算次数:

  • 结果统计

    计算次数:

故此模型算法复杂度为:

可见

在数据组数固定的情况下程序的运行速度与训练参数个数成正比,但是由于常数项很大,导致了反向传播过程中计算缓慢。

全连接神经网络还有很多需要优化的地方,以后继续写,将来可能需要用别的模型来优化。

About

Full connected neural network

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published