分享

【深度学习系列(二)】:基于c++实现一个简单的神经网络(1)

 码农书馆 2020-04-28

      学习深度学习首先得知道反向传播,这是神经网络能够学习得重要原因,也是深度学习得基石。所以,本系列以此为开篇,着重介绍神经网络得正向/反向传播得流程。哈哈,肯定有人会问为什么用C++实现,python不是更好吗?哈哈,本人严重C++控,好吧后续得一些文章大多是基于c++实现的,所以,最好有一定的c++基础。本文代码得配置要求:

  • C++

  • OpenCV3.4

完整的代码工程可以访问我的github:https://github.com/kingqiuol/ann

一、网络的框架结构

     本文代码主要通过一个NN类来实现,支持设定插入多个隐藏层及每个隐藏层的神经元个数,当然这部分可以跳过,主要让大家有个整体的概念,我一般在实现某一个功能或框架时都会提前做好准备,需要那几个功能/那些模块,并一一列举出来,先写好,具体细节不用管。对于一个神经网络来说,这里包括大多数卷积神经网络,一般包括:模型的训练模型的预测(使用),来说一说搭建该网络具体的流程:

  • 模型训练:加载数据  → 创建模型 → 参数设置/优化器设置 → 模型训练 →保存权重

  • 模型预测:创建模型 → 加载权重 → 传入数据 → 预测结果

  1. #ifndef ANN_H_
  2. #define ANN_H_
  3. #include <iostream>
  4. #include <memory>
  5. #include <fstream>
  6. #include <sstream>
  7. #include <string>
  8. #include <time.h>
  9. #include <stdlib.h>
  10. #include <opencv2/opencv.hpp>
  11. using namespace std;
  12. using namespace cv;
  13. class NN{
  14. public:
  15. NN(size_t classes, size_t input = 0,
  16. float reg = 0.005, float learning_rate = 0.0001,
  17. size_t max_epochs = 5000,size_t batch_size = 500) :
  18. input_(input), classes_(classes),
  19. reg_(reg), learning_rate_(learning_rate),
  20. max_epochs_(max_epochs), batch_size_(batch_size),
  21. data_ptr(nullptr), label_ptr(nullptr){}
  22. ~NN(){}
  23. //加载数据
  24. void data_loader(const string &path);
  25. //添加隐藏层
  26. void add_hidden_layer(const vector<size_t> &num_hiddens = {});
  27. //初始化网络
  28. void initial_networks();
  29. //前向传播
  30. void forward(Mat &X);
  31. //反向传播
  32. void backward();
  33. //计算损失函数
  34. float loss(Mat &y);
  35. //更新权重
  36. void update_weight();
  37. //训练网络
  38. void train(const string &file_path, const vector<size_t> &num_hiddens);
  39. //保存权重
  40. void save_weights(const string &save_path);
  41. //加载权重
  42. void load_weights(const string &load_path);
  43. //预测
  44. Mat predict(Mat &data);
  45. //其他方法
  46. inline float get_learning_rate()const{ return this->learning_rate_; }
  47. inline void set_learning_rate(float learning_rate){ this->learning_rate_ = learning_rate; }
  48. inline float get_reg() const{ return this->reg_; }
  49. inline void set_reg(float reg){ this->reg_ = reg; }
  50. inline size_t get_epoch()const{ return this->max_epochs_; }
  51. private:
  52. /***神经网络相关的函数***/
  53. void get_batch(Mat &batch_X,Mat &batch_y);
  54. void initial_layer(const size_t &input, const size_t &output);//单个层的初始化
  55. void relu(Mat &X);//激励函数
  56. void softmax(Mat &out);//softmax分类器
  57. float L2_regular();//L2正则化
  58. /***其他与矩阵操作相关的方法***/
  59. //计算矩阵行/列方向的和,并进行矩阵增广
  60. Mat mat_sum(const Mat &X, const int &axis, const int &dtype);
  61. //计算矩阵行/列方向的最大值,并进行矩阵增广
  62. Mat mat_max(const Mat &X, const int &axis, const int &dtype);
  63. //求矩阵行对对应的最大值的下标所在的列
  64. int argmax(Mat &row, float &max);//单行对应的下标
  65. Mat argmaxes(Mat &out);
  66. /***常见神经网络参数设置***/
  67. float reg_; //正则化系数
  68. float learning_rate_; //学习率
  69. size_t max_epochs_; //最大训练次数
  70. size_t batch_size_; //批量处理大小
  71. private:
  72. //输入数据、数据标签
  73. shared_ptr<Mat> data_ptr, label_ptr;
  74. size_t input_; //输入个数
  75. size_t classes_; //分类个数
  76. vector<size_t> hiddens_; //各个隐藏层中神经元个数
  77. vector<Mat> W_; //保存权重
  78. vector<Mat> b_; //保存偏置项
  79. vector<Mat> out_; //存储各个层的输出
  80. vector<Mat> dW_; //保存各个层的计算权重梯度
  81. vector<Mat> db_; //保存各个层的偏置项梯度
  82. };
  83. #endif // !ANN_H_

       正如头文件所见,对于整个网络我们需要保存一些中间变量为后续反向传播做准备。我们需要保存的节点有:每一层的输入/输出、当前层的权重、以及反向传播时的梯度,然后还需要定义传播过程中的操作,主要操作有:全连接、激活函数(relu)和分类器(softmax)。对于当前大多数深度学习框架(tensorflow/pytorch/caffe)来说,基本都采用运算图模型,说白了就是一系列的节点+边(操作),节点用于存储中间结果,边用于计算。这里的代码不是很明显,后续讲到深度学习时你就会由此体会。接下来我将对这些流程进行讲解并一一细说其中的一些基础知识。

二、神经网络的训练

1、数据加载

  1. void NN::data_loader(const string &path)
  2. {
  3. ifstream file(path);
  4. //将数据存储到vector中
  5. vector<vector<float>> dataset;
  6. string ss;
  7. while (getline(file, ss)){
  8. vector<float> data;
  9. stringstream w(ss);
  10. float temp;
  11. while (w >> temp){
  12. data.push_back(temp);
  13. }
  14. if (data.size() == 0){
  15. continue;
  16. }
  17. dataset.push_back(data);
  18. }
  19. //随机打乱数据
  20. srand(static_cast<unsigned int>(time(0)));
  21. random_shuffle(dataset.begin(), dataset.end());
  22. //将vector转化为Mat并分别存储到训练集和label中
  23. int rows = static_cast<int>(dataset.size());
  24. int cols = static_cast<int>(dataset[0].size() - 1);
  25. //创建数据集和label矩阵
  26. Mat train_data(rows, cols, CV_32FC1);
  27. Mat labels(rows, 1, CV_32FC1);
  28. //加载数据
  29. auto it = dataset.begin();
  30. for (int i = 0; i < rows; ++i){
  31. float *data = train_data.ptr<float>(i);
  32. float *label = labels.ptr<float>(i);
  33. for (int j = 0; j < cols + 1; ++j){
  34. data[j] = (*it)[j];
  35. if (cols == j){
  36. label[0] = (*it)[j];
  37. }
  38. }
  39. ++it;
  40. }
  41. //将共享指针指向数据
  42. this->data_ptr = make_shared<Mat>(train_data);
  43. this->label_ptr = make_shared<Mat>(labels);
  44. }

      这一部分没啥好说的,每个人可以根据自己的数据形式进行改变,最好在训练前将数据打乱(shuffle)和进行归一化(normal)等预处理,这里我没有进行归一化是因为我的数据在存储时已经进行归一化了。最主要的是最终训练数据将转化为矩阵形式,这也是为什么使用OenCV,有Mat的话更方便进行相关矩阵操作,嘿嘿。注意,假设我们的数据的特征有F维,那么其矩阵形式维1XF,所以对于N个数据集,其矩阵形式如下:

  •   Mat:N*F

接下来,我将介绍在加载数据时进行相关的预处理更多详细的信息。对于常见的数据预处理有以下几种情况:

(1)数据清洗

  • 去除缺失大量特征的数据

  • 去掉样本数据的异常数据。(比如连续型数据中的离群点)

  • 对于数据缺失的特征,可以使用该特征的均值来代替缺失的部分

  • 对于一些类别类型,如:L,R,可以one-hot形式进行编码

(2)数据采样

       在我们采集数据时经常会遇到样本不均衡的问题,我们可以采用上/下采样的方法进行样本补充,其具体为假设正负样本比例1:100,把正样本的数量重复100次,这就叫上采样,也就是把比例小的样本放大。下采样同理,把比例大的数据抽取一部分,从而使比例变得接近于1;1。通过这种方式可以避免样本的不均衡问题,同时我们也要注意在实际特征工程中,均衡问题会极大影响训练的结果。

(3)预处理

       数据归一化,数据标准化的方法有很多,如对于所有的数值特征,我们都会减去均值,除以方差。博主主要工作方向是图像处理领域,在图像处理方面,主要的预处理方法为去均值,这里的均值为训练集的均值,之后再验证/测试集中都是减去这个均值。对于为什么要去均值,有很多的解释,如下:

  1. 对于我们的自然图像其实是一种平稳的数据分布【即图像的每一维都服从相同的分布】。所以通过减去数据对应维度的统计平均值,来消除公共的部分,以凸显个体之间的特征和差异。

  2. 根据求导的链式法则,w的局部梯度是X,当X全为正时,由反向传播传下来的梯度乘以X后不会改变方向,要么为正数要么为负数,也就是说w权重的更新在一次更新迭代计算中要么同时减小,要么同时增大。

      其中,w的更新方向向量只能在第一和第三象限。假设最佳的w向量如蓝色线所示,由于输入全为正,现在迭代更新只能沿着红色路径做zig-zag运动,更新的效率很慢。基于此,当输入数据减去均值后,就会有负有正,会消除这种影响。

      当然,目前讲的一些预处理方法只是我在机器学习方面的常用的方法,对于数据预处理还有很多方法还没试过,有兴趣的小伙伴可以试试。常见的特征工程的处理方法如下图:

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多