《神经网络与深度学习--Nilsen》+BP学习笔记

来源:互联网 发布:以后淘宝开店收税吗 编辑:程序博客网 时间:2024/06/05 21:14

《神经网络与深度学习--Nilsen》

第一章:使用神经网络识别手写数字

1.1 感知器:先w1x1+w2x2+w3x3+b得到正负数,再阶跃函数(但在0点处感知器输出0)输出0或1。

      其中w权值表示某项输入的重要性,b表示是否容易成立,b越大越容易成立)

      因为感知器可以描述与非门,而与非门可以描述任何逻辑电路,故理论上感知器可描述任何逻辑功能。

1.2 S型神经元:。。。。。。。。。。。。。。,再送入S函数输出具体小数(0~1范围内)。

      其中S函数为1/(1 + exp(-z)), z = w1x1+w2x2+w3x3+b

      比感知器优点:更精确到小数,当权值w和偏置b变化时,输出变化更容易(输出的变化 = k1 * w1的变化 + k2 * w2的变化)。

1.3 神经网络的架构:中间的叫隐藏层

1.4 一个简单的分类手写数字的网络:

      先解决识别,再解决分割问题。

      输出10个比输出4个二进制更符合常识,当然都可以。

疑问:1 识别10个数字输出4个结果就够了,为什么要输出10个结果?(P13)

回答:其实4个和10个都行。但10个与我们识别10个数字的目标更一致、更形象。如果4个输出结果的话,把数字的最高有效位与数字的形状要素联系起来并不直观、并不简单。

      隐藏层:负责识别各个局部区域,如隐藏层的15个神经元分别识别"数字0"的15个部分(通过对"对应局部区域的像素"赋予较大权值)。

      练习1:将3层的10个神经元,添加一层输出为4个神经元。怎么做?(P13)

1.5 使用梯度下降法进行学习

    设输入样本为28*28的图像,则输入x为784维的向量。期望输出y = (0,0,0,...1,0)为10维的向量。

    为了量化如何实现目标,定义了代价函数C(w,b)(方程6):表示当输入x时,期望输出y与实际输出a(即wx+b)的差距。

    故训练算法的目的:即为找到能最小化二次代价函数C(w; b)的权重w和偏置b

    如果求C(w; b)的全局极小值的话,因为网络的w和b(函数的自变量)太多了,用微积分计算量太大,故不可行。因此通过“梯度下降算法”使上述C函数最小。(因为神经网络变量w和b太多,用微积分偏导求极小值计算量太大)

    梯度下降法具体如下:

    v为位置,deltaV的正负为方向,deltaV的大小为步长(学习速率也叫步长)(取负梯度方向,系数为学习速率), 则deltaC为负,即代价函数正在变小。(方程9、10)

    实质:通过重复改变位置v(deltaV与梯度相关,通过重复计算不同位置的梯度得到)来找到代价函数C的最小值。(方程15)

    因为yita的大小代表了代价函数C(w; b)走向最小值过程的快慢,故yita表示了学习速率的快慢。 实际情况是学习速率不可太大(越过谷底),不可太小(代价函数C变化太慢).。

    w和b的更新规则,如式16、17,与学习率和梯度有关。通过不断更新w和b,能得到最终的训练好的模型。

    小技巧:因为全体数据量太大,计算梯度计算量太大,因此随机取小批量数据估算整体数据的梯度,减小计算量。如果按照"小批量"(mini-batch)(比如10个)的形式把"全部数据(比如60000个)"走一遍的话,成为完成了一个"训练迭代期"(epoch)。---这种方法叫随机梯度下降法。(抽样调查比全民选举容易,也有很大的代表性)

    几何含义:点击打开链接   练习2:一元函数的梯度下降法就是沿切线走吧

    练习:点击打开链接           

    区别: 取全部点、取一部分点、取一个点

                点击打开链接           

    回答:比如从山顶道坡底需要走垂直高度10m,若取全部点则每次都是走的最抖的方向下山最快,需要走5次,最终走到离坡底最近;若取一部分点,每次的迭代速率快,但因为梯度不是下降最快的,可能要走8次;取一个点,每次的迭代速率最快,但因为梯度随机性太大了,故迭代次数最多。折中的话肯定是批量(mini_batch)最好了吧!!

1.6 实现我们的网络来分类数字

      a' = S(w * a + b). 其中a是输入层的784维的矩阵,w是权重矩阵(其中Wjk是连接第二层第k个神经元和第三次第j个神经元的权重),b是偏执矩阵。最终对结果矩阵内的每个元素求S函数,得到输出层的矩阵。(也就是说全部是矩阵运算。)(P21)

     函数分析:(P22)

    sigmoid(z): 对矩阵每个元素计算S函数。

    feedforward(self, a): 前向方向,根据输入计算输出。a' = (w * a + b).

    SGD(self, training_data, epoches, mini_batchsize, eta, test_data=None): 打乱训练数据并分批,对每批训练(调用update_mini_batch(mini_batch_eta)),测试(调用evaluate(test_data)。

    update_mini_batch(self, mini_batch, eta): 应用SGD算法,计算代价函数C(W,b)对于w和b的梯度(调用backprop(x, y)),利用得到的梯度对w和b更新。

    完整代码分析(74行):(P23)

    python调用书中的代码,加载MNIST数据集,训练30epoches并测试30epoches:(P26),如下图

    

    1. 首先我把nilsen的代码放在了C:\Users\Administrator\Desktop\neural-networks-and-deep-learning-master路径里。

    2. 打开cmd,通过进入上述路径内的src文件夹内(代码在这里)。

    3. 打开python,在python命令行里输入import mnist_data即可打开该src文件夹内的mnist_data.py文件,然后就可以调用其中的mnist_loader.load_data_wrapper()函数。.同理,import network(详见书P26)即可训练数据了。

    注:第三步中,import mnist_data 成功后,就会生成mnist_data.pyc文件,pyc文件就是py文件经过编译后二进制文件

   

练习(P28),import一次xxx.py后,后面就不需要再import了。没有隐藏层,训练也快多了,结果也挺好的。如下图。



其他算法:瞎猜、暗度、SVM,但目前对MNIST识别来说,神经网络是最好的。

1.7 迈向深度学习:

    启发式的方法:左上角有眼睛吗-》有睫毛吗、有眉毛吗、有虹膜吗-》有直线、三角形图形的像素点吗。即把复杂的问题分解在了单个像素点层级上。

    这也是CNN等深度神经网络的一种直观理解。

疑问:2为了使算法正常工作,公式 (4) 中的学习率 η 要尽可能的小,不然最终可能 ΔC>0。同时学习率不能过小,不然会导致每一次迭代中 Δv 过小,算法工作会非常慢。


第二章:BP如何工作

2.1 热身:神经网络中使用矩阵计算输出的方法。

表示:Wjk表示从第(l-1)层的第k个神经元,到第l层的第j个神经元,的权重。也是权重矩阵第j行、第j列的下标。

          Bj  表示第l层的第j个神经元,的偏置。

           Aj   表示第l层的第j个神经元,的激活值。

   定义权重矩阵W.偏置矩阵B.激活矩阵A.

2.2 关于代价函数的两个假设

第一个:将所有样本x的代价函数Cx的均值,作为代价函数C的结果。

第二个:代价函数C是实际输出y的函数(当然还有理想输出yi)

2.4反向传播的4个过程

对调皮鬼的导数,即C对zj的导数,是神经元误差的度量。
1. 输出层的误差

2. 使用下一层的误差(沿着网络反向传播得到)当前层的误差

3. 误差 = 偏导数

4. 得到,"代价函数"关于"任何一个权重"的改变量

总结:如果输入神经元激活值低,或输出神经元已经饱和(激活值过高或过低),则权重学习会变缓慢。

2.5 推导上述公式

2.7 代码:Network类中的update_mini_batchbackprop 方法。

2.8 反向传播只计算了一次反向传播过程就算出了梯度,计算量小。

2.9 反向传播:全局观

细致地追踪一个w的微小变化如何导致C中的变化值 可以看示意图很形象:当前层某神经元的变化会影响下一层的所有神经元。


第三章:改进神经网络的方法

3.1 交叉熵代价函数

通过动画可以看到:随迭代期增加,代价函数C在变小,w和b最终也变了。(找到了使C最小的w和b)

3.1.1 引入交叉熵代价函数(来代替二次代价函数

特性:交叉熵是非负的,在神经元达到很好的正确率的时候会接近0,避免了学习速率下降

优势:更大的误差,就有更快的学习速度,这很好。(从第二张动态图可以看到)

什么时候用:当激活函数为S型神经元时,交叉熵都比二次好。

3.1.2 交叉熵应用python代码,效果好。

3.1.3 交叉熵直觉含义:表征了学习到实际输出y的正确值的不确定性。如果输出我们的期望结果,不确定性就小,代价函数就小。是信息论里熵的定义。

3.1.4柔性最大值层softmax:一种新式的输出层(最后一层),BP里没有,CNN里有。

通过对倒数第二层的每个节点的输出,应用一个柔性最大值函数,当倒数第二层某个节点变大时,相应的softmax输出变大,其他节点的softmax输出变小。(softmax层所有节点的输出相加,概率为1)所以最后一层的softmax输出可以看做网络估计正确数字分类的概率

学习缓慢问题:对数似然代价函数(log-likelihood),和交叉熵代价函数效果差不多,比二次代价函数效果好。

本章后面使用:S型输出层和交叉熵代价的组合 。

3.2 过度拟合和规范化

过拟合:对已有的数据很适应,但对新数据预测不准。(只是单纯记忆了训练数据,但没有理解出它的抽象内涵)

检测过拟合(hold_out方法:跟踪,测试数据(实际是验证数据validation_data,从60000训练集里分出来的10000张图片),上的准确率变化。(如果验证数据准确率不再提升,就提前停止训练,改超参数(学习率、迭代次数、网络架构)然后重新训练)。可以把验证集看作一种特殊的训练集,它能帮助我们学到更好的超参数。当然最好的办法就是增大训练样本的量,但实际情况肯定不可能无限增大。

3.2.1 规范化:“寻找小权重最小化原始代价函数之间的折中。(从公式85、86、87可以看出)(因为权重向量比较大时,梯度下降带来的变化只会在那个方向引起微小的变化)

3.2.2 为何规范化可以减轻过拟合

直观理解:小权重,意味着更低的复杂性,对数据有更简单但更强大的解释,应该优先选择。

举例子(如图):线性模型加噪声比多项式模型更容易,更不易被干扰,因此对未知数据的预测能力更强。

总结:规范化网络就是权重比较小,所以输入改变不会对输出影响太多,所以学习局部噪声更困难,能抵抗训练数据中噪声的特性影响。

实情:但规范化比非规范化的网络效果好,只是一种实验事实发现效果确实很好,上述结论只是人们猜想的(大家也不知道),并没有很多的理论依据

3.2.3 规范化的其他技术

L2规范化:权重衰减、偏置减不减都行

L1规范化:和L2的权重衰减的方式不同。

弃权(drop-out):弃权相当于不同的网络,以不同的方式过度拟合,投票选出大家公认的好的效果,这样弃权就可以减轻过拟合。

人为扩展训练数据:如旋转、转换、扭曲图像、手部肌肉颤抖的弹性扭曲。通过增加背景噪声来扩展训练数据。

好的算法和好、大的训练数据都会对效果有直接提升。

3.3 权重初始化

高斯分布(均值0,方差1)-》可以改进:还是高斯分布,但标准差变了,这样效果更好

我们会使用均值为0标准差为1/根号n的高斯随机分布初始化这些权重。也就是说,我们会向下挤压高斯分布,让我们的神经元更不可能饱和。

我们会继续使用均值为0标准差为1的高斯分布来对偏置进行初始化。

3.4 再看手写体识别代码

使用了交叉熵代价函数、规范化、权重初始化的改进。152行。

default_weight_initializer(self):新式的权重初始化方法。

QuadraticCost类计算网络输出a与目标输出y的差值,和误差表达式。


3.5 如何选择神经网络的超参数

宽的策略:简化问题(先识别0和1)、缩小训练数据(80%)

学习速率:步长不要太大,也不要太小。

使用early stopping 来确定训练的迭代期数量:防止过拟合。

学习速率调整:单一常量或递减。

规范化参数:lmda

小批量数据大小:折衷。太小了,你不会用上很好的矩阵库的快速计算。太大了,你是不能足够频繁地更新权重的。

自动技术:有人提出了。

总结:没有最好,只有更好的方法。


3.6 其他技术:

3.6.1 随机梯度下降的变化形式

Hessian技术:比SGD跟快,但矩阵太大运算不便。不仅考虑梯度,还有梯度如何变化的信息。

基于momentum的梯度下降:梯度改变速度,间接改变位置。引入摩擦力逐步减小速度。

3.6.2 人工神经元的其他模型

tanh替换S型函数

RLU修正线型神经元

3.6.3 有关神经网络的故事

启发性的探索和严格的推导证明,同时存在,效果才能进步。


第四章:神经网络可以计算任何函数的可视化证明

第五章:深度神经网络为何很难训练

第六章:深度学习

CNN:局部感受野、共享权重、混合。

1. 局部感受野:一(第一层的5*5的一块小区域)对一(隐藏层的一个点)。见下(P149)图。其中5*5区域内的25个点各自有一个权重w,25个点共有1个偏置b。

.

28*28的图片,跨距为1的话,得到的隐藏层为(28-5+1)*(28-5+1) = 24 *24个节点的隐藏层。隐藏层如上图右侧矩阵,也是以矩阵形式表示。


2. 共享权重和偏置

前面提到:每个隐藏层神经元具有连接到他的局部感受野的5*5的权重w一个偏置b

共享的含义是:对24*24个隐藏层神经元中的每一个使用相同的w和b。即对于上图来说,“输入层第1个5*5的小区域与隐藏层的第1个神经元的连接所用的5*5个w和1个b”、与“输入层第2个5*5的小区域与隐藏层的第2个神经元的连接所用的5*5个w和1个b”、与“输入层第24*24个5*5的小区域与隐藏层的第24*24个神经元的连接所用的5*5个w和1个b”是一样的。这样一套相同“5*5个w和1个b”成为一个卷积核或滤波器。也体现了卷积核的平移不变性(图像中数字的上下左右偏移也能识别出)。


若总共有3套卷积核,就有3套不同“5*5个w和1个b”(如下图P150)



下面是20套不同的卷积核,其中5*5个不同的黑白块代表5*5个不同的权重w,黑的代表w大,白的代表w小。b在图中没有表示出来。(P151图)



参数的减少:对于每个特征映射我们需要25 = 5 × 5个共享权重,加上1个共享偏置。所以每个特征映射需要26个参数。如果我们有20个特征映射,那么总共有20×26 = 520 个参数来定义卷积层。作为对比,假设我们有1个全连接的第1层,具有784 = 28× 28 个输出神经元,和1个相对适中的30 个隐藏神经元,正如我们在本书之前的很多例子中使用的。总共有784×30 个权重,加上额外的30 个偏置,共有 23550个参数。换句话说,这个全连接的层有多达40倍于卷基层的参数。


3. 混合层Pooling: 简化凝缩信息。2*2个点-》1个点,减少了参数。


4. 全连接层:Pooling之后的12*12矩阵的每个点 连接到10个(代表0~9)输出结果上。如下图P153



5. 网络训练的目标:用训练数据得到 固定的好的 w和b(即卷积核),可以对 测试数据 进行识别。


6.2 CNN在实际中的应用

network3.py的运行。安装theano.(书P154)

cmd里输入代码如下,调用network3.py进行训练:



上图代码输入后,输出错误信息如下:




代码解析:

使用了规范化、柔性最大值的输出层、对数似然代价函数。

P155的代码还是有错,如下图:


















































本代码里,把一个卷积+混合层作为1个大层。引入了第2个大层。

改进:

激活函数:换为修正线性单元。

扩展训练数据

插入一个额外的全连接层+弃权

多网络投票:训练多个神经网络,看哪个效果好就用哪个


6.3 CNN代码分析

theano和caffe一样,都是CNN的开源实现,直接调用theano的函数,代码很简单。因为CNN的工作已经在theano中编好了。


6.4 图像处理近期进展

6.5 其他深度学习模型

6.6 神经网络的未来








BP其他资料:

2.  概述性质介绍该看什么资料 


3.   BP入门

  (1)首先有两种S函数(单极性、双极性),曲线图形基本一样

  (2)逐层输出:a = f(z); z = w1x1 + w2x2 + b;  原理和Python实现。

  (3)代价函数(损失函数)的定义。

  后面输出层权值调整、隐藏层权值调整、偏置调整、BP步骤就看不懂了(全是数学推导)


4. 对BP神经网络、RNN、监督非监督等有了直观了解 


5. BP  BP网络训练时通过先前向传播,再根据误差反向传播时修正w和b,得到训练好的网络。  测试时,只需要前向传播,利用已有的网络得到输出结果即可。(文中有数学推导--如何根据误差修正w和b)


6. 神经网络简洁的介绍、完整的公式和MATLAB程序 

    能运行的C++程序    下面是对其C++程序的注释:

BP.h

#ifndef _BP_H_  #define _BP_H_     #include <vector>     #define LAYER    3        //三层神经网络  #define NUM      10       //每层的最多节点数     #define A        30.0  #define B        10.0     //A和B是S型函数的参数  #define ITERS    1000     //最大训练次数  #define ETA_W    0.0035   //权值调整率  #define ETA_B    0.001    //阀值调整率  #define ERROR    0.002    //单个样本允许的误差  #define ACCU     0.005    //每次迭代允许的误差     #define Type double  #define Vector std::vector     struct Data  {      Vector<Type> x;       //输入数据      Vector<Type> y;       //输出数据  };     class BP{     public:         void GetData(const Vector<Data>);      void Train();      Vector<Type> ForeCast(const Vector<Type>);     private:         void InitNetWork();         //初始化网络      void GetNums();             //获取输入、输出和隐含层节点数      void ForwardTransfer();     //正向传播子过程      void ReverseTransfer(int);  //逆向传播子过程      void CalcDelta(int);        //计算w和b的调整量      void UpdateNetWork();       //更新权值和阀值      Type GetError(int);         //计算单个样本的误差      Type GetAccu();             //计算所有样本的精度      Type Sigmoid(const Type);   //计算Sigmoid的值     private:      int in_num;                 //输入层节点数      int ou_num;                 //输出层节点数      int hd_num;                 //隐含层节点数         Vector<Data> data;          //输入输出数据         Type w[LAYER][NUM][NUM];    //BP网络的权值      Type b[LAYER][NUM];         //BP网络节点的阀值             Type x[LAYER][NUM];         //每个神经元的值经S型函数转化后的输出值,输入层就为原值      Type d[LAYER][NUM];         //记录delta学习规则中delta的值  };     #endif  //_BP_H_  

BP.cpp

// http://blog.csdn.net/acdreamers/article/details/44657439##include <string.h>  #include <stdio.h>  #include <math.h>  #include <assert.h>  #include "BP.h"    //获取训练所有样本数据  void BP::GetData(const Vector<Data> _data)  {      data = _data;  }    //开始进行训练  void BP::Train()  {      printf("Begin to train BP NetWork!\n");      GetNums();// 输入3、隐含7、输出1    InitNetWork();// 权值w = 0、偏置b = 0    int num = data.size();// 41个训练样本      for(int iter = 0; iter <= ITERS; iter++)  // 迭代1000次    {          for(int cnt = 0; cnt < num; cnt++)    // 41个训练样本         {              //第一层输入节点赋值  : x[0][0] = {0, 0, 0};            for(int i = 0; i < in_num; i++)                  x[0][i] = data.at(cnt).x[i];                while(1)                          // 每个训练样本反复反向传播,直到误差足够小            {                  ForwardTransfer();  // 正向传播                if(GetError(cnt) < ERROR)     // 如果误差比较小,则针对单个样本跳出循环                      break;                  ReverseTransfer(cnt);         // 反向传播            }          }          printf("This is the %d th trainning NetWork !\n", iter);            Type accu = GetAccu();          printf("All Samples Accuracy is %lf\n", accu);          if(accu < ACCU) break;      }      printf("The BP NetWork train End!\n");  }    //根据训练好的网络来预测输出值  Vector<Type> BP::ForeCast(const Vector<Type> data)  {      int n = data.size();      assert(n == in_num);      for(int i = 0; i < in_num; i++)          x[0][i] = data[i];            ForwardTransfer();      Vector<Type> v;      for(int i = 0; i < ou_num; i++)          v.push_back(x[2][i]);      return v;  }    //获取网络节点数  void BP::GetNums()  {      in_num = data[0].x.size();                         //获取输入层节点数      ou_num = data[0].y.size();                         //获取输出层节点数      hd_num = (int)sqrt((in_num + ou_num) * 1.0) + 5;   //获取隐含层节点数      if(hd_num > NUM) hd_num = NUM;                     //隐含层数目不能超过最大设置  }    //初始化网络  void BP::InitNetWork()  {      memset(w, 0, sizeof(w));      //初始化权值和阀值为0,也可以初始化随机值      memset(b, 0, sizeof(b));  }    //工作信号正向传递子过程  void BP::ForwardTransfer()  {      // 计算隐含层各个节点的输出值  :输入->隐含// 结果为x[1][0]~x[1][6]共7个隐含层输出    for(int j = 0; j < hd_num; j++)// j: 隐含层有7个    {          Type t = 0;          for(int i = 0; i < in_num; i++) // i: 输入层有3个            t += w[1][i][j] * x[0][i];  //     w * x        t += b[1][j];//    (w * x) + b        x[1][j] = Sigmoid(t);// f( (w * x) + b )    }        // 计算输出层各节点的输出值  :隐含->输出  // 结果为x[2][0]共1个输出层输出    for(int j = 0; j < ou_num; j++)     // j: 输入层有3个    {          Type t = 0;          for(int i = 0; i < hd_num; i++) // i: 输出层有1个            t += w[2][i][j] * x[1][i];  //     w * x        t += b[2][j];                   //    (w * x) + b        x[2][j] = Sigmoid(t);           // f( (w * x) + b )    }  }    //计算单个样本的误差  Type BP::GetError(int cnt)  {      Type ans = 0;      for(int i = 0; i < ou_num; i++)  // x[2][i]为实际输出、data.at(cnt).y[i]为期望输出        ans += 0.5 * (x[2][i] - data.at(cnt).y[i]) * (x[2][i] - data.at(cnt).y[i]);      return ans;  }    //误差信号反向传递子过程  void BP::ReverseTransfer(int cnt)  {      CalcDelta(cnt);         UpdateNetWork();  }    //计算所有样本的精度  Type BP::GetAccu()  {      Type ans = 0;      int num = data.size();      for(int i = 0; i < num; i++)      {          int m = data.at(i).x.size();          for(int j = 0; j < m; j++)              x[0][j] = data.at(i).x[j];          ForwardTransfer();          int n = data.at(i).y.size();          for(int j = 0; j < n; j++)              ans += 0.5 * (x[2][j] - data.at(i).y[j]) * (x[2][j] - data.at(i).y[j]);      }      return ans / num;  }    //计算调整量  void BP::CalcDelta(int cnt)  {      //计算输出层的delta值      for(int i = 0; i < ou_num; i++)          d[2][i] = (x[2][i] - data.at(cnt).y[i]) * x[2][i] * (A - x[2][i]) / (A * B);      //计算隐含层的delta值      for(int i = 0; i < hd_num; i++)      {          Type t = 0;          for(int j = 0; j < ou_num; j++)              t += w[2][i][j] * d[2][j];          d[1][i] = t * x[1][i] * (A - x[1][i]) / (A * B);      }  }    //根据计算出的调整量对BP网络进行调整  void BP::UpdateNetWork()  {      //隐含层和输出层之间权值和阀值调整      for(int i = 0; i < hd_num; i++)      {          for(int j = 0; j < ou_num; j++)              w[2][i][j] -= ETA_W * d[2][j] * x[1][i];       }      for(int i = 0; i < ou_num; i++)          b[2][i] -= ETA_B * d[2][i];        //输入层和隐含层之间权值和阀值调整      for(int i = 0; i < in_num; i++)      {          for(int j = 0; j < hd_num; j++)              w[1][i][j] -= ETA_W * d[1][j] * x[0][i];      }      for(int i = 0; i < hd_num; i++)          b[1][i] -= ETA_B * d[1][i];  }    //计算Sigmoid函数的值  Type BP::Sigmoid(const Type x)  {      return A / (1 + exp(-x / B));  }  

test.cpp

#include <iostream>  #include <string.h>  #include <stdio.h>     #include "BP.h"     using namespace std;     double sample[41][4]=   {       {0,0,0,0     },       {5,1,4,19.020},   // 输入5,1,4。 输出19.020    {5,3,3,14.150},       {5,5,2,14.360},       {5,3,3,14.150},       {5,3,2,15.390},       {5,3,2,15.390},       {5,5,1,19.680},       {5,1,2,21.060},       {5,3,3,14.150},       {5,5,4,12.680},       {5,5,2,14.360},       {5,1,3,19.610},       {5,3,4,13.650},       {5,5,5,12.430},       {5,1,4,19.020},       {5,1,4,19.020},       {5,3,5,13.390},       {5,5,4,12.680},       {5,1,3,19.610},       {5,3,2,15.390},       {1,3,1,11.110},       {1,5,2,6.521},       {1,1,3,10.190},       {1,3,4,6.043},       {1,5,5,5.242},       {1,5,3,5.724},       {1,1,4,9.766},       {1,3,5,5.870},       {1,5,4,5.406},       {1,1,3,10.190},       {1,1,5,9.545},       {1,3,4,6.043},       {1,5,3,5.724},       {1,1,2,11.250},       {1,3,1,11.110},       {1,3,3,6.380},       {1,5,2,6.521},       {1,1,1,16.000},       {1,3,2,7.219},       {1,5,3,5.724}   };      int main()  {      Vector<Data> data;  // 训练数据格式整理/*比如原来是    {5,1,4,19.020},         {5,3,3,14.150}, 则现在是 data共有41个元素,每个元素由“输入x(3个数)” 和 “输出y(1个数)”组成。 struct Data  {  Vector<Type> x;       //输入数据  {5,1,4}Vector<Type> y;       //输出数据  {19.020}};  */    for(int i = 0; i < 41; i++)      {          Data t;          for(int j = 0; j < 3; j++)              t.x.push_back(sample[i][j]);          t.y.push_back(sample[i][3]);          data.push_back(t);      }  // 获得训练数据并训练    BP *bp = new BP();      bp->GetData(data);      bp->Train();     // 测试:输入3个数据,得到1个输出数据    while(1)      {          Vector<Type> in;          for(int i = 0; i < 3; i++)          {              Type v;              scanf("%lf", &v);              in.push_back(v);          }          Vector<Type> ou;          ou = bp->ForeCast(in);// 根据实际输入 + 训练好的网络,得到输出        printf("%lf\n", ou[0]);      }      return 0;  }  

7. 手写体识别 

http://blog.csdn.net/gzlaiyonghao/article/details/7109898   图像预处理 + python实现(无源代码)

https://github.com/luhantao/BP-Hand-Writing                        MNIST数据集 + C++实现

http://www.lyyyuna.com/2016/06/25/handwritten-neural-net02/神经网络电子书的内容 + python实现(有源代码),python确实比C++易懂多了,此文值得看


下面是一篇课设的论文+程序

// https://github.com/luhantao/BP-Hand-Writing// 还有一篇文档#include <iostream>#include <fstream>#include <vector>#include <stdio.h>#include <stdlib.h>#include <math.h>#include <string.h>#include <cmath>#include <time.h>using namespace std;const int FIRST  = 784;// 输入 28*28 = 784维 输入层(代表784个像素点是0还是1:1代表数字的笔画着色部分,0则代表空白)const int SECOND = 100;// 隐藏层const int THIRD  = 10;// 输出层const double LearningRate = 0.35;// 学习率yitaint    input[FIRST];// 输入的训练样本int    target[THIRD];// 输入的训练样本的标准值double weight1[FIRST][SECOND];// 输入层和隐藏层之间的权值double weight2[SECOND][THIRD];// 隐藏层和输出层之间的权值double output1[SECOND];// 隐藏层(NO.2)输出double output2[THIRD];// 输出层(NO.3)输出double delta1[SECOND];double delta2[THIRD];double b1[SECOND];// 隐藏层(NO.2)偏置double b2[THIRD];// 输出层(NO.3)偏置double test_num = 0.0;double test_success_count = 0.0;//-----------------------------------------// S函数(激活函数)//-----------------------------------------double f_(double x){return 1.0 / (1.0 + exp(-x));}//-----------------------------------------// 对NO.2(隐藏层)的每个元素,分别计算输出//-----------------------------------------void op1_(){for (int j = 0; j < SECOND; j++)// 对NO.2的每个元素j,分别计算输出{double sigma = 0;// 求和的初值for (int i = 0; i < FIRST; i++){sigma += input[i] * weight1[i][j]; }double x = sigma + b1[j];output1[j] = f_(x);// S(w * x + b)}}//-----------------------------------------// 对NO.3(输出层)的每个元素,分别计算输出//-----------------------------------------void op2_(){for (int k = 0; k < THIRD; k++){double sigma = 0;for (int j = 0; j < SECOND; j++){sigma += output1[j] * weight2[j][k];}double x = sigma + b2[k];output2[k] = f_(x);}}//-----------------------------------------// delta(ij):输出层(NO.3)的delta误差//-----------------------------------------void dt2_(){for (int k = 0; k < THIRD; k++){delta2[k] = (output2[k]) * (1.0 - output2[k]) * (output2[k] - target[k]);}}//-----------------------------------------// delta(ki):隐藏层(NO.2)的delta误差//-----------------------------------------void dt1_(){for (int j = 0; j < SECOND; j++){double sigma = 0;for (int k = 0; k < THIRD; k++){sigma += weight2[j][k] * delta2[k];// 计算"NO.2的delta误差"时用到了"NO.3的delta误差"}delta1[j] = (output1[j]) * (1.0 - output1[j]) * sigma;}}//-----------------------------------------// 反向传播NO.3 -> NO.2 :根据误差修正权值&偏置//-----------------------------------------void feedback_SECOND(){for (int j = 0; j < SECOND; j++){b1[j] = b1[j] - LearningRate * delta1[j];for (int i = 0; i < FIRST; i++){weight1[i][j] = weight1[i][j] - LearningRate * input[i] * delta1[j];}}}//-----------------------------------------// 反向传播NO.2 -> NO.1 :根据误差修正权值&偏置 //-----------------------------------------void feedback_THIRD(){for (int k = 0; k < THIRD; k++){b2[k] = b2[k] - LearningRate * delta2[k];for (int j = 0; j < SECOND; j++){weight2[j][k] = weight2[j][k] - LearningRate * output1[j] * delta2[k];}}}//-----------------------------------------// 权值、偏置随机赋初值//-----------------------------------------void initialize(){srand((int)time(0) + rand());for (int i = 0; i < FIRST; i++){for (int j = 0; j < SECOND; j++){weight1[i][j] = rand()%1000 * 0.001 - 0.5;}}for (int j = 0; j < SECOND; j++){for (int k = 0; k < THIRD; k++){weight2[j][k] = rand()%1000 * 0.001 - 0.5;}}for (int j = 0; j < SECOND; j++){b1[j] = rand()%1000 * 0.001 - 0.5;}for (int k = 0; k < THIRD; k++){b2[k] = rand()%1000 * 0.001 - 0.5;}}//-----------------------------------------// 读取训练数据,并训练(正向传播->计算误差->反向传播修正w&b)//-----------------------------------------void training(){FILE *image_train;FILE *image_label;image_train = fopen("../tc/train-images.idx3-ubyte", "rb");// 60000张训练image_label = fopen("../tc/train-labels.idx1-ubyte", "rb");// 10000张测试if (image_train == NULL || image_label == NULL){cout << "can't open the file!" << endl;exit(0);}unsigned char image_buf[784];unsigned char label_buf[10];int useless[1000];// 没什么用,只是为了跳过训练样本的开头的无用字节// fread: http://c.biancheng.net/cpp/html/2516.htmlfread(useless, 1, 16, image_train);// 跳过开头16个字节:从image_train读取1*16个位到useless fread(useless, 1, 8,  image_label);// 跳过开头8 个字节:int cnt = 0;cout << "Start training..." << endl;//60000 timeswhile (!feof(image_train) && !feof(image_label)){memset(image_buf, 0, 784);// image_buf清0memset(label_buf, 0, 10);// label_buf清0fread(image_buf, 1, 784, image_train);// 读取,并填满image_buffread(label_buf, 1, 1, image_label);// 读取,并填满label_buf// 二值化:initialize the input by 28 x 28 (0,1)matrix of the imagesfor (int i = 0; i < 784; i++){if ((unsigned int)image_buf[i] < 128)input[i] = 0;elseinput[i] = 1;}//initialize the target output// 局部变量:实际没用int target_value = (unsigned int)label_buf[0];  // 训练样本的"期望输出值"for (int k = 0; k < THIRD; k++)// 将NO.3输出层的"期望输出值"写入target[k] = 0;target[target_value] = 1;//get the output and start trainingop1_();// 计算输出(正向传播)op2_();dt2_();// 计算误差dt1_();feedback_SECOND();// 反向传播NO.3 -> NO.2 :根据误差修正权值&偏置feedback_THIRD();// 反向传播NO.3 -> NO.2 :根据误差修正权值&偏置cnt ++;if (cnt % 1000 == 0)// 每1000训练1000个样本打印一次{cout << "training image: " << cnt << endl;}}cout << endl;}//-----------------------------------------// 测试数据 + 已训练好的网络 -> 得出识别率//-----------------------------------------void testing(){FILE *image_test;FILE *image_test_label;image_test = fopen("../tc/t10k-images.idx3-ubyte", "rb");image_test_label = fopen("../tc/t10k-labels.idx1-ubyte", "rb");if (image_test == NULL || image_test_label == NULL){cout << "can't open the file!" << endl;exit(0);}unsigned char image_buf[784];unsigned char label_buf[10];int useless[1000];// 跳过样本开头无用字节fread(useless, 1, 16, image_test);fread(useless, 1, 8, image_test_label);while (!feof(image_test) && !feof(image_test_label))// 循环10000次{memset(image_buf, 0, 784);// 读取测试样本memset(label_buf, 0, 10);fread(image_buf, 1, 784, image_test);fread(label_buf, 1, 1, image_test_label);// 二值化:initialize the input by 28 x 28 (0,1)matrix of the imagesfor (int i = 0; i < 784; i++){if ((unsigned int)image_buf[i] < 128)input[i] = 0;elseinput[i] = 1;}//initialize the target output// 记录测试样本的"期望输出值(即正确值)"for (int k = 0; k < THIRD; k++){target[k] = 0;}int target_value = (unsigned int)label_buf[0];target[target_value] = 1;//get the ouput and compare with the targeop1_();// 正向传播op2_();double max_value = -99999;int max_index = 0;for (int k = 0; k < THIRD; k++){if (output2[k] > max_value)// output2[k]是正向传播的实际输出结果{max_value = output2[k];// 比较NO.3层中哪一种(0~9共10种)结果输出结果最大,网络输出结果就是哪一个max_index = k;// max_index即为识别结果(0~9共10种中的一种),如"5"}}//output == targetif (target[max_index] == 1)// 比较实际识别的输出数字"5",是否对应,正确(期望)的输出{test_success_count ++;// 若是,则识别成功,已识别个数+1}test_num ++;// 已测试个数+1if ((int)test_num % 1000 == 0)// 每1000次,汇报一次识别个数。{cout << "test num: " << test_num << "  success: " << test_success_count << endl;}}cout << endl;cout << "The success rate: " << test_success_count / test_num << endl; // 最终汇报10000次总体的识别率}//-----------------------------------------// //-----------------------------------------int main(){initialize();// 初始化w和b(权值、偏置随机赋初值)training();// 读取训练数据,并训练(正向传播->计算误差->反向传播修正w&b)testing();// 测试数据 + 已训练好的网络 -> 得出识别率system("pause");}



0 0
原创粉丝点击