说明一下环境,该代码由C++实现,但是矩阵运算在C++中没有实现,自己造轮子既浪费时间又无必要,因此采用了Eigen库来进行矩阵操作,其他功能的代码均为自己实现。

一、环境配置

Eigen矩阵操作方便,本文在VS2015的环境下配置Eigen,具体的Eigen库配置过程不再细说,不了解怎么配置Eigen的话可以看这篇文章https://blog.csdn.net/panpan_jiang1/article/details/79649452,环境就是这样,其余的工作就是新建一个工程,开始编写代码,本文使用Eigen3.3.9版本。

二、网络结构

网络共有输入层、隐含层、输出层3层,输入层输入维度数10,隐含层输出维度为5,输出层为1,大概结构如下图所示:

就下来就上具体代码部分:
    最开始是固定的参数部分:

#define InputShape 10
#define Layer1_OutShape 5
#define Layer2_OutShape 1
//数据量
#define DataNum 100
//训练轮数
#define Epcho   2000

首先,我们定义一个神经网络的类(class)NN,一个神经网络的可训练参数有权值W与阈值B,固定的是学习率(当然有衰减学习率,这里我没有这样做)与输入值与真实(标签)值,因此,在初始化一个神经网络时先将权值阈值随机初始化。
NN的构造函数:

NN::NN(MatrixXf input, MatrixXf y_true, float alph)//初始化权值
{this->input = input;this->y_true = y_true;this->alph = alph;this->W1_T = MatrixXf::Random(Layer1_OutShape, InputShape);this->W2_T = MatrixXf::Random(Layer2_OutShape, Layer1_OutShape);this->B1 = MatrixXf::Zero(Layer1_OutShape,this->input.cols());this->B2 = MatrixXf::Zero(Layer2_OutShape, this->input.cols());
}

随后,建立网络的前向传播

MatrixXf NN::ForWard()
{this->Z1 = this->W1_T * this->input;this->A1 = Leak_ReLu(this->Z1, this->alph);this->Z2 = this->W2_T * this->A1;this->A2 = Leak_ReLu(this->Z2, this->alph);//return MSE(this->y_true, this->A2);return this->A2;
}

前向传播的数学原理描述就比较简单了,
    输入层到隐含层:
                                        Z1=W1T∗inputZ_{1}=W_{1}^{T}*inputZ1​=W1T​∗input,

A1=Leaky_ReLu(Z1)A_{1}=Leaky\_ReLu(Z_{1})A1​=Leaky_ReLu(Z1​)

Leaky_ReLu(Z)={ZZ>=0alph∗ZZ<0Leaky\_ReLu(Z)=\left\{\begin{matrix} Z & Z>=0 & \\ alph*Z & Z<0 & \end{matrix}\right.Leaky_ReLu(Z)={Zalph∗Z​Z>=0Z<0​​
    alph的值默认0.1。
    隐含层到输出层:
                                        Z2=W2T∗A1Z_{2}=W_{2}^{T}*A_{1}Z2​=W2T​∗A1​,

A2=Leaky_ReLu(Z2)A_{2}=Leaky\_ReLu(Z_{2})A2​=Leaky_ReLu(Z2​)

重头戏来了,反向传播过程,先贴代码^_^

float NN::BackWard()
{int rows_temp, cols_temp;//临时行列变量//Abount Layer2 work start!!this->dJ_dA2 = 2 * (this->A2 - this->y_true);this->dA2_dZ2 = MatrixXf::Ones(this->Z2.rows(),this->Z2.cols());for (rows_temp = 0; rows_temp < this->Z2.rows(); ++rows_temp){for (cols_temp = 0; cols_temp < this->Z2.cols(); ++cols_temp){this->dA2_dZ2(rows_temp, cols_temp) = this->Z2(rows_temp, cols_temp) >= 0 ? 1.0 : this->alph;}}this->dZ2_dW2 = this->A1.transpose();this->dW2 = this->dJ_dA2.cwiseProduct(this->dA2_dZ2)*this->dZ2_dW2/DataNum;this->dB2 = this->dJ_dA2.cwiseProduct(this->dA2_dZ2) / DataNum;//Abount Layer2 work end!!//Abount Layer1 work start!!this->dZ2_dA1 = this->W2_T.transpose();this->dA1_Z1 = MatrixXf::Ones(this->Z1.rows(), this->Z1.cols());for (rows_temp = 0; rows_temp < this->Z1.rows(); ++rows_temp){for (cols_temp = 0; cols_temp < this->Z1.cols(); ++cols_temp){this->dA1_Z1(rows_temp, cols_temp) = this->Z1(rows_temp, cols_temp) >= 0 ? 1.0 : this->alph;}}this->dZ1_W1 = this->input.transpose();this->dW1 = this->dA1_Z1.cwiseProduct(this->dZ2_dA1 * this->dJ_dA2.cwiseProduct(this->dA2_dZ2))*this->dZ1_W1 / DataNum;this->dB1 = this->dA1_Z1.cwiseProduct(this->dZ2_dA1 * this->dJ_dA2.cwiseProduct(this->dA2_dZ2)) / DataNum;//Abount Layer1 work end!!//调整学习参数this->W2_T = this->W2_T - this->alph*this->dW2;this->W1_T = this->W1_T - this->alph*this->dW1;this->B2 = this->B2 - this->alph*this->dB2;this->B1 = this->B1 - this->alph*this->dB1;return MSE(this->y_true,this->ForWard());
}

只看代码一定会晕菜,先放一会,让我解释下整体过程:NN(神经网络)初始化好之后,给它喂入数据,NN会给出预测值y_pred,y_pred一定会跟真实值y_true有误差,用简单的均方误差(MSE)公式根据y_pred与y_true会计算出一个损失值loss,NN就是基于loss根据一定算法调整权值与阈值,目标是使得loss变小、趋向于0,这样y_pred就会与y_true变得一样。
    好的,那该如何调整呢?简单的,我们想到了梯度下降法,上图先

简化下问题,我们的目的是调整W使得loss(W)变小,那么,W就可以根据loss函数在W处的梯度调整

W1′=W−alph∗dloss(W)dWW_{1}^{'}=W-alph*\frac{d loss(W)}{dW}W1′​=W−alph∗dWdloss(W)​
这样loss不就小了吗?
所以,W1,W2,B1,B2W_{1} ,W_{2} ,B_{1} ,B_{2}W1​,W2​,B1​,B2​都是这么调整,这里只举一个例子:

W1=W−alph∗dJdW1W_{1}=W-alph*\frac{d J}{dW_{1}}W1​=W−alph∗dW1​dJ​
J是代价函数,注意,这里是代价函数,不是损失函数,代价函数是损失函数在每个样本上的损失值和再求平均。以MSE和本文的数据为例,y_true与y_pred都是1行100列的向量(矩阵),J就为:

J(y_true,y_pred)=∑i=0100−1MSE(y_true(0,i),y_pred(0,i))/100J(y\_true,y\_pred)=\sum_{i=0}^{100 - 1}MSE(y\_true(0,i),y\_pred(0,i))/100J(y_true,y_pred)=∑i=0100−1​MSE(y_true(0,i),y_pred(0,i))/100

J是关于y_pred与y_true的函数。
OK,这里我们就要求dJdW1\frac{d J}{dW_{1}}dW1​dJ​了,这里需要用一点大学知识链式求导法则,或者,你就直接看做分式的化简的反向操作:

dJW2=dJdA2∗dA2dZ2∗dZ2dW2\frac{dJ}{W_{2}}=\frac{dJ}{dA_{2}}*\frac{dA_{2}}{dZ_{2}}*\frac{dZ_{2}}{dW_{2}}W2​dJ​=dA2​dJ​∗dZ2​dA2​​∗dW2​dZ2​​

J/W2 ==>>(J/A2) * (A2/Z2) *(Z2/W2)

A2A_{2}A2​就是y_pred,J又是关于y_pred与y_true的函数,y_true是定值,所以这个求导结果就非常简单了,直接给答案,求导结果是2*(y_pred-y_true)。
A2=Leaky_ReLu(Z2)A_{2}=Leaky\_ReLu(Z_{2})A2​=Leaky_ReLu(Z2​),导数很明显是分段的,Z2>=0Z_{2}>=0Z2​>=0,导数为1,反之,导数为alph。
Z2=W2T∗A1Z_{2}=W_{2}^{T}*A_{1}Z2​=W2T​∗A1​,这个导数学过矩阵求导的话应该知道导数是A1A_{1}A1​的转置。
这样的话dJW2\frac{dJ}{W_{2}}W2​dJ​就求出来了,只要W2=W2−alph∗dJdW2W_{2}=W_{2}-alph*\frac{d J}{dW_{2}}W2​=W2​−alph∗dW2​dJ​这样操作就好。

OK,这样的话关于W1W_{1}W1​的调整正也同理

dJW1=dJdA2∗dA2dZ2∗dZ2dA1∗dA1dZ1∗dZ1dW1\frac{dJ}{W_{1}}=\frac{dJ}{dA_{2}}*\frac{dA_{2}}{dZ_{2}}*\frac{dZ_{2}}{dA_{1}}*\frac{dA_{1}}{dZ_{1}}*\frac{dZ_{1}}{dW_{1}}W1​dJ​=dA2​dJ​∗dZ2​dA2​​∗dA1​dZ2​​∗dZ1​dA1​​∗dW1​dZ1​​

关于阈值B1B_{1}B1​与B2B_{2}B2​,
                                                            dJB2=dJdA2∗dA2dZ2\frac{dJ}{B_{2}}=\frac{dJ}{dA_{2}}*\frac{dA_{2}}{dZ_{2}}B2​dJ​=dA2​dJ​∗dZ2​dA2​​

dJB1=dJdA2∗dA2dZ2∗dZ2dA1∗dA1dZ1\frac{dJ}{B_{1}}=\frac{dJ}{dA_{2}}*\frac{dA_{2}}{dZ_{2}}*\frac{dZ_{2}}{dA_{1}}*\frac{dA_{1}}{dZ_{1}}B1​dJ​=dA2​dJ​∗dZ2​dA2​​∗dA1​dZ2​​∗dZ1​dA1​​

都是在WWW的计算过程计算了,直接拿来用就好。于是,给出NN的定义

class NN
{public:NN(MatrixXf input, MatrixXf y_true, float alph);MatrixXf ForWard();float BackWard();private://神经网络的输入值、输出的真实值、学习率等较为固定的值MatrixXf input;MatrixXf y_true;float alph;//神经网络待学习参数MatrixXf W1_T;MatrixXf B1;MatrixXf W2_T;MatrixXf B2;//神经网络中间参数MatrixXf Z1;       MatrixXf A1;MatrixXf Z2;MatrixXf A2;//==out//反向传播所用参数MatrixXf dW2;MatrixXf dB2;MatrixXf dW1;MatrixXf dB1;//对Layer2所需参数MatrixXf dJ_dA2;MatrixXf dA2_dZ2;MatrixXf dZ2_dW2;MatrixXf dZ2_dB2;//对Layer1所需参数MatrixXf dZ2_dA1;MatrixXf dA1_Z1;MatrixXf dZ1_W1;
};

PS:用的是BGSD(批量梯度下降法)。

三、数据集说明

数据集是用Eigen库的Random随机生成10行100列的数据(Random是生成-1到1的均匀分布数字),每一列是一条数据,真实值(标签)是这样生成的,每一条的数据和>0:标签为1,反之,标签为0.
代码:

MatrixXf input_data,y_true,y_pred;int rows_temp,cols_temp;//行列数临时变量input_data = MatrixXf::Random(InputShape, DataNum);y_true = MatrixXf::Zero(Layer2_OutShape, DataNum);for (cols_temp = 0; cols_temp < DataNum; ++cols_temp){y_true(0, cols_temp) = input_data.col(cols_temp).sum() > 0 ? 1.0:0.0;}

四、运行结果

贴图,有图有真相:
先是训练过程的部分loss变化情况

可以看到,loss在稳步下降。
2000迭代后:

预测结果与真实结果对比,认为正确的判断条件是:y_true是1,对应的y_pred>0.5,反之y_true是0,对应的y_pred<0.5。

最终准确率95%

写了好几天,搞了好几个版本,终于把功能实现了,从来都是知易行难【抱拳】。代码上传到这里了https://github.com/tian0zhi/Neural-Network-By-C-Plus-Plus,使用与转载请注明出处

C++下实现全连接神经网络相关推荐

  1. 【TensorFlow】TensorFlow从浅入深系列之十 -- 教你认识卷积神经网络的基本网路结构及其与全连接神经网络的差异

    本文是<TensorFlow从浅入深>系列之第10篇 TensorFlow从浅入深系列之一 -- 教你如何设置学习率(指数衰减法) TensorFlow从浅入深系列之二 -- 教你通过思维 ...

  2. 【实验记录】EA-MLP(演化算法--全连接神经网络)实验记录

    large scale evaluation net -- MLP全连接实验记录 Ⅰ. Experiment detail Ⅱ. Method Vertex Edge DNA Evolution_po ...

  3. 神经网络 测试集loss不下降_代码实践 | 全连接神经网络回归---房价预测

    学习目录 阿力阿哩哩:深度学习 | 学习目录​zhuanlan.zhihu.com 前面我们介绍了: 阿力阿哩哩:深度学习开端|全连接神经网络​zhuanlan.zhihu.com 4.7代码实践 & ...

  4. [转载] python bp神经网络 mnist_Python利用全连接神经网络求解MNIST问题详解

    参考链接: Python中的单个神经元神经网络 本文实例讲述了Python利用全连接神经网络求解MNIST问题.分享给大家供大家参考,具体如下: 1.单隐藏层神经网络 人类的神经元在树突接受刺激信息后 ...

  5. 全连接神经网络基础——正向传播及损失函数

    全连接神经网络结构 顾名思义,全连接神经网络指的是上一层网络中的所有神经元都与下一层网络中的所有神经元相连,即上一层网络所有神经元的输出都作为下一层网络所有神经元的输入.一个简单的全连接神经网络结果如 ...

  6. 深度之眼Pytorch打卡(十三):Pytorch全连接神经网络部件——线性层、非线性激活层与Dropout层(即全连接层、常用激活函数与失活 )

    前言   无论是做分类还是做回归,都主要包括数据.模型.损失函数和优化器四个部分.数据部分在上一篇笔记中已经基本完结,从这篇笔记开始,将学习深度学习模型.全连接网络MLP是最简单.最好理解的神经网络, ...

  7. Pytorch 实现全连接神经网络/卷积神经网络训练MNIST数据集,并将训练好的模型在制作自己的手写图片数据集上测试

    使用教程 代码下载地址:点我下载 模型在训练过程中会自动显示训练进度,如果您的pytorch是CPU版本的,代码会自动选择CPU训练,如果有cuda,则会选择GPU训练. 项目目录说明: CNN文件夹 ...

  8. 卷积神经网络与全连接神经网络

    1.定义 在全连接神经网络中,每两层之间的节点都有边相连. 卷积神经网络也是通过一层一层的节点组织起来的,对于卷积神经网络,相邻两层之间只有部分节点相连.在卷积神经网络的前几层中,每一层的节点都被组织 ...

  9. 全连接神经网络、卷积神经网络

    全连接神经网络.卷积神经网络 前言 全连接神经网络 介绍 结构 损失函数 梯度下降 链式法则 反向传播 总结 卷积神经网络 背景 结构 卷积(Convolution) 池化(Max Pooling) ...

最新文章

  1. qt 实现窗口局部镂空,并截图显示。
  2. 一小段代码,得到项目决对路径
  3. 如何让网页弹出确定_电脑去除网页上弹窗广告的操作方法
  4. SQL语法之排序查询(进阶3)and常见函数(进阶4)
  5. 行业场景智能应用,解锁边缘计算时代新机遇
  6. python解决https私密连接警告信息
  7. ASP.NET Core 借助 K8S 玩转容器编排
  8. 会议交流 | “数据智能与知识服务”研讨会的专家报告题目已更新!
  9. 《软件需求分析(第二版)》第 16 章——需求链中的联系链 重点部分总结
  10. 用户账号管理基本概念
  11. JMeter之HTTP请求上传文件/上传图片
  12. 【C语言】C语言随机数生成教程,C语言rand和srand用法详解
  13. SPI机制入门、SPI机制原理
  14. day01 继承、抽象类和模板设计模式
  15. 电脑插入U盘后里面的文件变成快捷方式解决办法
  16. Codeforces 1194B+1194D
  17. 百度飞桨-基于CV的工业读表案例(修改读表范围和数值)
  18. 高德地图使用鼠标工具(mouseTool)画覆盖物折线(mouseTool.polyline),光标使用十字架(crosshair)类型,不断出现closehand小手图标干扰
  19. 中首清算质疑偶像演员不适合演抗战剧?《雷霆战将》三大还原引争议
  20. 聚醋酸乙烯酯(PVAc)乳剂市场现状及未来发展趋势

热门文章

  1. 供应科研试剂乙缩醛-琥珀酰亚胺酯,Acetal-NHS (SDMB)
  2. 干货!我被面试官绝地反杀了,附详细答案
  3. 称“开启千元机快充时代” 魅蓝5s将于15日发布
  4. TPMS—胎压监测系统
  5. Tcl/Tk入门(上)
  6. 10个维修中最常见的蓝屏代码,值得收藏!
  7. android手机整体规模,2021年Android手机市场规模
  8. 【扫一扫二维码,传智大礼包带回家】
  9. 微信小程序 非webview分享给好友及生成分享海报
  10. bootstrap-table实现表格编辑