神经网络初步完成


前一段时间根据该教程Neural networks and deep learning)使用c++编写了一个简单的神经网络程序,实现了简单的推理和反向传播。本文以记录一些经验。

我编写的神经网络主体分为三大主要结构,初始化函数,核心函数(正向传播和反向传播),调试函数(用于展示内部状态和保存模型),当然同样包含一些辅助函数。当前编写的仅仅为最初版本,未加入多线程或cuda优化,性能羸弱。

虽然本人写的代码也不优雅,不过确实是将神经网络生成出来了。使用MNIST手写数字数据集,进行一次全量训练,测试集可以达到93% 的正确率。虽然与顶尖模型存在巨大差距,但是基础功能已经实现了。

我后面将稍微讲解我的实现思路和方法,如果真的有兴趣的人可以自己动手尝试一下用自己熟悉的语言编写自己的版本。

头文件定义:

namespace NN {
    class NNcore {
        int size;
        double studyRate;
        bool addDropout;
        double dropOutRate;

        std::vector<std::vector<double> > layers;
        std::vector<std::vector<double> > layersZ;
        std::vector<int> layerSize;

        std::vector<std::vector<double> > b;

        std::vector<std::vector<std::vector<double> > > w;

    public:
        double train(std::vector<std::vector<double> > inNums, std::vector<int> correctOut, bool getAcc = false); //最后实现

        double test(std::vector<std::vector<double> > inNums, std::vector<int> correctOut); //最后实现

        std::vector<double> forward(std::vector<double> inNums, bool printRes = false);//最先关注!

        double backpropagation(std::vector<double> correctOut);//最先关注!

        double CalCost(std::vector<double> correctOut);//最先关注!

        void changeStudyRate(double rate);

        void changeDropOutRate(double rate);

        void dropSome();

        int choice();

        void printLayers();

        static void printLayers(const NNcore &nn);

        void printW(int layerNumberToPrint);

        static void printW(const NNcore &nn, int layerNumberToPrint);

        static void save(const NNcore &nn, std::string path);

        void init(const std::string &path, double studyRate, double drRate = -1);//最后实现

        void init(const std::vector<int> &LayerS, double studyR, double drRate = -1);//最先关注!
    };
}

参数定义部分:


      int size;
      
      double studyRate;
      bool addDropout;
      double dropOutRate;

      std::vector<std::vector<double> > layers;
      std::vector<std::vector<double> > layersZ;
      std::vector<int> layerSize;
      
      std::vector<std::vector<double> > b;
      std::vector<std::vector<std::vector<double> > > w;
定义部分超参数:

    studyRate: 学习率,最基础的超参数,用于表示步进大小。数值越大拟合越快。但是,在将要完成收敛时容易“抖动”,无法达到模型最大值。

    dropoutRateaddDropOut: dropout,一种超参数,随机失活一些节点,避免过拟合。(目前版本实现不够优雅 会造成极大的资源浪费 且参数范围未找到)

    模型参数定义:

    size: 用来表示模型层数
    layerSize: 表示每层大小
    layerS:节点权重,正向传播与反向传播时更新,计算中间值,对最终模型无用
    layersZ:激活函数后的节点权重,计算中间值,方便反向传播
    b: 偏置,与节点绑定,将某一数值加到对应节点值上,使得对称中心偏离原点,更好的预测大部分模型
    w: 边的权重,神经网络重点调整对象

    我编写的基础的神经网络模型就是由以上参数决定。结果表明,以上参数足够进行训练。在这里使用Vector是为了让模型大小可变,并且由于Vector自带size()函数,方便部分参数的获取。

    函数定义

    此处包含的函数不少,但是其中最为重要的是forward, backpropagation, 使用layer size初始化的init函数。实现以上代码后,其他的都是对于他们的补充!我最开始编写的版本,所有函数均在一个源文件中,同样可以实现基础功能!

    在这里就不解释具体代码了,思路与上文章节1、2中的思路完全相同,理解起来并不复杂,进需要部分微积分知识就可以独自完成!如果想要code,可以访问github自取。其中同样包含第一版的内容。Talentjoe/SimpleNeuralNetwork

    训练数据

    有的时候,编写并不困难,困难的是知道你的代码对不对,是debug的过程。刚完成初步编写的时候,我就苦于没有合适的训练数据而不知道自己编写的是否正确。其实并非网上没有,而是使用C++编写,读取外部数据并且格式化成需要的格式并不容易(我懒)。一方面也是数据量较少的话无法完成对于模型的收敛,同样无法验证是否预测准确。因此我最开始采用的是鸢尾花数据集,鸢尾花数据集理论上其实并不适合使用NN进行预测,属于费力不讨好的行为。K-NN等方式能以更快更有理论依据的方式完成其工作。但是NN却也能够完成该任务,并且很适合作为小范围验证。此时问题又出现了,鸢尾花数据集大概只有几百个样本以及对应的标签,数量不足够完成NN的训练,因此我找到了GPT帮我生成了一些样本。虽然确实很怪,不过结果却也是好的。

    在完成了对我代码框架的验证之后,我选择了机器学习上避不开的MNIST数据集进行更大范围的练手,或是说,更为实际的情况。不过,对于C++,并没有想python的pytorch那样,对部分数据集只需要一行数据就可以下载引用调试的方便事,因此只能从其官网下载并自己手动分析导入。MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges。官网上给出了文件的定义方式,十分详细。不过如果你是英特尔用户,还要对位进行翻转。如果你想省事,也可以直接使用我github仓库中的readData这个库,或者参考他进行编写。

    一些仍需解决的问题

    准确率一直就卡在了93-95左右,就算提升模型大小也无济于事,这可能是由于模型过拟合而无法寻找更优解。因此尝试添加了dropout这个参数。不过当前效果仍然不明显且增加了很多计算负担,仍需要调整。

    运行效率不高,虽然使用的c++,但是没有并行优化,进行一次两个隐含层,大小为128,64 的训练仍然需要将近五分钟。下一步大概是加入些并行,或者一步到位,直接上cuda

    无法指定激活函数,其实这个问题应该很好解决,将激活函数指定为一个函数指针即可,但是奈何学艺不精,加上懒,就留到之后解决吧!


    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注