关闭

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

标签: 神经网络深度学习BP手写体识别神经网络与深度学习
864人阅读 评论(0) 收藏 举报
分类:

《神经网络与深度学习--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;	// 学习率yita

int    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.html
	fread(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 times
	while (!feof(image_train) && !feof(image_label))
	{
		memset(image_buf, 0, 784);						// image_buf清0
		memset(label_buf, 0, 10);						// label_buf清0
		fread(image_buf, 1, 784, image_train);			// 读取,并填满image_buf
		fread(label_buf, 1, 1, image_label);			// 读取,并填满label_buf

		// 二值化:initialize the input by 28 x 28 (0,1)matrix of the images
		for (int i = 0; i < 784; i++)
		{
			if ((unsigned int)image_buf[i] < 128)
				input[i] = 0;
			else
				input[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 training
		op1_();											// 计算输出(正向传播)
		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 images
		for (int i = 0; i < 784; i++)
		{
			if ((unsigned int)image_buf[i] < 128)
				input[i] = 0;
			else
				input[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 targe
		op1_();													// 正向传播
		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 == target
		if (target[max_index] == 1)								// 比较实际识别的输出数字"5",是否对应,正确(期望)的输出
		{
			test_success_count ++;								// 若是,则识别成功,已识别个数+1
		}
		
		test_num ++;											// 已测试个数+1

		if ((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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场