本文均属自己阅读源代码的点滴总结。转账请注明出处谢谢。

欢迎和大家交流。qq:1037701636 email:gzzaigcn2009@163.com

写在前面的闲话:

自我感觉自己应该不是一个非常擅长学习算法的人。过去的一个月时间里由于须要去接触了BP神经网络。在此之前一直都觉得算法界的神经网络、蚁群算法、鲁棒控制什么的都是特别高大上的东西,自己也就听听好了,未曾去触碰与了解过。这次和BP神经网络的邂逅。让我初步掌握到。理解透彻算法的基本原理与公式,转为计算机所能识别的代码流,这应该就是所谓的数学和计算机的完美结合吧,难道这过程也就是所谓的ACM吗?

1.BP神经网络的基本概念和原理

看了网络上非常多关于神经网络相关的资料,了解到BP神经网络是最为简单和通用的,刚好所需完毕的工作也主要是简单的字符识别过程。

BP神经网络的概念:

BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络。是眼下应用最广泛的神经网络模型之中的一个。

BP网络能学习和存贮大量的输入-输出模式映射关系。而无需事前揭示描写叙述这样的映射关系的数学方程。

BP神经网络的基本模型图例如以下所看到的:

 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3p6YWlnY25mb3JldmVy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

BP网络的三个层次:输入层、隐藏层以及输出层。

我们所要做的就是依据自身的需求建立一个属于我们自己的BP神经网络。

 
2.BP神经网络的原理以及相关公式的推导
 
2.1、BP网络的基本思想:
 
BP神经网络学习过程由信息的正向传递与误差的反向传播两个过程组成:
(1) 正向传递:输入样本从输入经隐含层逐层计算传向输出层,若输出层的实际输出和期望输出不符,则计算输出层的误差值。然后转向反向传播过程。

(2) 误差的反向传播:是将输出误差以某种形式通过隐层向输入层逐层反传。并将误差分摊给各层全部单元。从而获得各层单元的误差信号,此误差做为修正该单元的依据。信号正向传递和误差反向传播重复进行,权值不断得到调整的过程。就是网络的学习/训练过程。

当训练达到规定误差或一定训练次数。则结束训练。

 
BP网络训练学习过程可理解成:样本输入时的理想目标Tk与实际输出Ok之间的误差平方Ep不断趋向于0的一个过程:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3p6YWlnY25mb3JldmVy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

依据误差梯度下降法依次修正输出层权值的修正量 Δ wki ,输出层阈值的修正量 Δa k,隐含层权值的修正量 Δ wij ,隐含层阈值的修正量:
                     
上述公式表明网络输入误差是各层权值wjk、vij的函数,因此调整权值可改变误差E。显然,调整权值的原则是使误差不断地减小。因此应使权值的调整量与误差的梯度下降成正比。故而对BP网络算法的直观解释例如以下所看到的:
 
通过上图表明,BP网络算法的核心是在不断调整权值的情况下。使得误差不断的减小。

显然不管是在正向梯度还是负向梯度,在离散情况下都须要不断的将权值往误差极小值的地方调整。而调整的速率值eta(也称权值的步进值)关乎着整个神经网络的训练速度。

 
 
BP神经网络的公式推导能够參考:http://en.wikipedia.org/wiki/Backpropagation,相关推导公式核心内容例如以下:
 
BP网络的算法核心是使用一个刺激函数不断的forward,而这个刺激函数往往使用S型激活函数(logsig)为:
 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3p6YWlnY25mb3JldmVy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

 
 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3p6YWlnY25mb3JldmVy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

 
 
注:上述公式參阅网络资源,这几步流程图,对于理解整个BP神经网络算法的核心思想有非常大的帮助。
 
 

3.BP神经网络算法用C语言的实现

3.1.初始的准备工作,建立三个层的数据结构体:分别表示样本信息、输入层的信息、隐藏层的信息以及输出层的信息,终于归于到一个BP核心算法的结构体bp_alg_core_params;

typedef struct {

    unsigned int img_sample_num; //待训练的图像字符、数字样本个数
unsigned int img_width;
unsigned int img_height;
unsigned char *img_buffer; }img_smaple_params; typedef img_smaple_params* hd_sample_params; typedef struct { unsigned int in_num; //输入层节点数目
double *in_buf; //输入层输出数据缓存
double **weight; //权重,当前输入层一个节点相应到多个隐层
unsigned int weight_size;
double **pri_deltas; //记录先前权重的变化值。用于附加动量
double *deltas; //当前计算的隐层反馈回来的权重矫正 }bp_input_layer_params; typedef bp_input_layer_params* hd_input_layer_params; typedef struct { unsigned int hid_num; //隐层节点数目
double *hid_buf; //隐层输出数据缓存
double **weight; //权重。当前隐层一个节点相应到多个输出层
unsigned int weight_size;
double **pri_deltas; //记录先前权重的变化值,用于附加动量
double *deltas; //当前计算的输出反馈回来的权重矫正值 }bp_hidden_layer_params; typedef bp_hidden_layer_params* hd_hidden_layer_params; typedef struct { unsigned int out_num;//输出层节点数目
double *out_buf; //输出层输出数据缓存
double *out_target; }bp_out_layer_params; typedef bp_out_layer_params* hd_out_layer_params; typedef struct { unsigned int size; //结构体大小
unsigned int train_ite_num; //训练迭代次数
unsigned int sample_num; //待训练的样本个数
double momentum; //BP阈值调整动量
double eta; //训练步进值。学习效率
double err2_thresh; //最小均方误差 hd_sample_params p_sample; //样本集合參数
hd_input_layer_params p_inlayer; //输入层參数
hd_hidden_layer_params p_hidlayer;//隐藏层參数
hd_out_layer_params p_outlayer;//输出层參数 }bp_alg_core_params;

3.2.參数的初始化。主要包含对计算缓存区的分配。

如果这里分别有M,N。K表示输入、隐藏、输出的节点数目。

那么一个输入缓存区大小:分配大小M+1个。同理隐藏层和输出分别分配:N+1,P+1个;数据量大小默认双精度的double类型。

权值的缓冲区大小:一个依据BP网络的算法,一个节点到下一层的节点分别须要具备一一相应。故这是一个二维数组的形式存在。我们分配输入层权值空间大小为(M+1)(N+1)的大小。隐藏权值空间大小为(N+1)(P+1);

当然对于权值的矫正量。其是一个依据节点的输出值向后反馈的一个变量,实际就是多对1的反馈。而通过公式能够看到我们能够仅仅採用一维数组来表示每个节点的反馈矫正值(不基于输入节点的数据。即例如以下的变量:

同理终于隐藏层到输出层的反馈矫正、输出层和隐藏层的反馈矫正都以一个一位变量的形式存在,仅仅是在计算权值时要结合节点的输入数据来进行2维矫正。

完毕二维数组的动态分配过程函数例如以下所看到的:

double** alloc_2d_double_buf(unsigned int m, unsigned int n)
{ unsigned int i;
double **buf = NULL;
double *head; /*分配一个数组指针空间+ 2维数据缓存空间*/
buf = (double **)malloc(sizeof(double *)*m + m*n*sizeof(double));
if(buf == NULL)
{
ERR("malloc error!");
exit(1);
} head = (double *)(buf + m); memset((void *)head, 0x00, sizeof(double)*m*n);//clear 2d buf
for(i = 0; i < m; i++)
{ buf[i] = head + i*n;
DEG("alloc_2d_double_buf, addr = 0x%x", buf[i] );
} return buf; }

3.3 BP神经网络训练过程和不断的权值矫正

依次经过forward向前刺激。权值矫正值计算,权值调整,样本均分误差计算。

以一次样本数全部样本节点计算完后做均方误差,误差满足一定的阈值就说明BP神经网络训练能够基本结束(一般定义可接受的误差在0.001左右):

int bp_train(bp_alg_core_params *core_params)
{
unsigned int i, j, k;
unsigned int train_num, sample_num;
double err2;//均分误差 DEG("Enter bp_train Function"); if(core_params == NULL)
{
ERR("Null point Entry");
return -1;
} train_num = core_params->train_ite_num;//迭代训练次数
sample_num = core_params->sample_num;//样本数 hd_sample_params p_sample = core_params->p_sample; //样本集合參数
hd_input_layer_params p_inlayer = core_params->p_inlayer; //输入层參数
hd_hidden_layer_params p_hidlayer = core_params->p_hidlayer; //隐藏层參数
hd_out_layer_params p_outlayer = core_params->p_outlayer; //输出层參数 DEG("The max train_num = %d", train_num); /*依次依照训练样本数目进行迭代训练*/
for(i = 0; i < train_num; i++)
{
err2 = 0.0; DEG("current train_num = %d", i); for(j = 0 ; j < sample_num; j++)
{ DEG("current sample id = %d", j); memcpy((unsigned char*)(p_inlayer->in_buf+1), (unsigned char*)sample[j], p_inlayer->in_num*sizeof(double));
memcpy((unsigned char*)(p_outlayer->out_target+1), (unsigned char*)out_target[j%10], p_outlayer->out_num*sizeof(double)); /*输入层到隐藏层的向前传递输出*/
bp_layerforward(p_inlayer->in_buf, p_hidlayer->hid_buf, p_inlayer->in_num, p_hidlayer->hid_num, p_inlayer->weight); /*隐藏层到输出层的向前传递输出*/
bp_layerforward(p_hidlayer->hid_buf, p_outlayer->out_buf, p_hidlayer->hid_num, p_outlayer->out_num, p_hidlayer->weight); /*输出层向前反馈错误到隐藏层,即权值矫正值*/
bp_outlayer_deltas(p_outlayer->out_buf, p_outlayer->out_target, p_outlayer->out_num, p_hidlayer->deltas); /*隐藏层向前反馈错误到输入层,权值矫正值依赖于上一层的调整值*/
bp_hidlayer_deltas(p_hidlayer->hid_buf, p_hidlayer->hid_num, p_outlayer->out_num, p_hidlayer->weight, p_hidlayer->deltas, p_inlayer->deltas); /*调整隐藏层到输出层的权值*/
adjust_layer_weight(p_hidlayer->hid_buf, p_hidlayer->weight, p_hidlayer->pri_deltas, p_hidlayer->deltas, p_hidlayer->hid_num,
p_outlayer->out_num, core_params->eta, core_params->momentum); /*调整隐藏层到输出层的权值*/
adjust_layer_weight(p_inlayer->in_buf, p_inlayer->weight, p_inlayer->pri_deltas, p_inlayer->deltas, p_inlayer->in_num,
p_hidlayer->hid_num, core_params->eta, core_params->momentum); err2 += calculate_err2(p_outlayer->out_buf, p_outlayer->out_target, p_outlayer->out_num);//统计全部样本遍历一次后的均分误差 } /*一次样本处理后的均分误差统计*/
err2 = err2/(double)(p_outlayer->out_num*sample_num);
INFO("err2 =%08f\n",err2 ); if(err2 < core_params->err2_thresh)
{
INFO("BP Train Success by costs vaild iter nums: %d\n", i);
return 1;
}
} INFO("BP Train %d Num Failured! need to modfiy core params\n", i);
return 0; }

4.总结

BP神经网络在理解完算法的核心思想后,用代码的形式去实现往往会变得事半功倍,而如果一股脑儿直接拿到code去分析的做法不推荐。由于不了解核心思想的基础下,无法对算法的參数进行部分的改动以及优化,盲目改动往往会造成实验的失败。

本算法应该满足了BP神经网络的基本训练过程。至于不管是识别,预測还是其它,都能够在获取理想样本源和目标源的基础上对BP神经网络进行训练与学习,使得其具备了一定的通用性。后期要做的是将上述浮点的处理过程竟可能的转为定点化的DSP来处理,将其应用到嵌入式设备中去。

注:

由于非常多人向我咨询code,所以上传了基于BP神经网络的简单字符识别算法自小结(C语言版)。本人已不再研究,谢谢。

基于BP神经网络的简单字符识别算法自小结(C语言版)的更多相关文章

  1. 基于BP神经网络的字符识别研究

    基于BP神经网络的字符识别研究 原文作者:Andrew Kirillov. http://www.codeproject.com/KB/cs/neural_network_ocr.aspx 摘要:本文 ...

  2. 基于steam的游戏销量预测 — PART 3 — 基于BP神经网络的机器学习与预测

    语言:c++ 环境:windows 训练内容:根据从steam中爬取的数据经过文本分析制作的向量以及标签 使用相关:无 解释: 就是一个BP神经网络,借鉴参考了一些博客的解释和代码,具体哪些忘了,给出 ...

  3. BP神经网络反向传播之计算过程分解(详细版)

    摘要:本文先从梯度下降法的理论推导开始,说明梯度下降法为什么能够求得函数的局部极小值.通过两个小例子,说明梯度下降法求解极限值实现过程.在通过分解BP神经网络,详细说明梯度下降法在神经网络的运算过程, ...

  4. [纯C#实现]基于BP神经网络的中文手写识别算法

    效果展示 这不是OCR,有些人可能会觉得这东西会和OCR一样,直接进行整个字的识别就行,然而并不是. OCR是2维像素矩阵的像素数据.而手写识别不一样,手写可以把用户写字的笔画时间顺序,抽象成一个维度 ...

  5. 感知机与BP神经网络的简单应用

    感知机与神经元 感知机(Perceptron)由两层神经元组成(输入层.输出层),输入层接收外界输入信号后传递给输出层,输出层是M-P神经元,亦称“阈值逻辑单元”(threshold logic un ...

  6. 基于BP神经网络的手MNIST写数字识别

    import numpy import math import scipy.special#特殊函数模块 import matplotlib.pyplot as plt #创建神经网络类,以便于实例化 ...

  7. 4、BFS算法套路框架——Go语言版

    前情提示:Go语言学习者.本文参考https://labuladong.gitee.io/algo,代码自己参考抒写,若有不妥之处,感谢指正 关于golang算法文章,为了便于下载和整理,都已开源放在 ...

  8. 各种排序算法代码(C语言版)

    选择排序 #include <stdio.h> /* * 选择排序 * 稳定性:不稳定 * 时间复杂度:O(N^2) **/ void select_sort(int a[], int l ...

  9. 专注笔试算法20年(C语言版)

    1.C语言实现链表数据的反转({1,2,3,4}->{4,3,2,1}). int trav(PNode *head){ PNode p_1,p_2,tmp; //判断参数是否有效 if(*he ...

随机推荐

  1. POJ 2311 Cutting Game(SG函数)

    题目描述 意思就是说两个人轮流剪纸片,直到有一个人剪出1*1的方格就算这个人赢了.然后给出纸片的长和宽,求先手会赢还是会输 (1<=n,m<=200) 题解 看了一眼,这不是裸的SG吗 啪 ...

  2. weak和alias

    一.强符号和弱符号 在C语言中,如果多个模块定义同名全局符号时,链接器认为函数和已初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号. 根据这个定义,Linux链接器使用下面的 ...

  3. 2015 Multi-University Training Contest 1 Tricks Device

    Tricks Device Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Tot ...

  4. P2899 [USACO08JAN]手机网络Cell Phone Network

    P2899 [USACO08JAN]手机网络Cell Phone Networ题目描述 Farmer John has decided to give each of his cows a cell ...

  5. 在独立的文件里定义WPF资源

    一.文章概述 本演示介绍怎样在单独的文件里定义WPF资源.并在须要的地方调用相关资源文件. 相关下载(代码.屏幕录像):http://pan.baidu.com/s/1sjO7StB 在线播放:htt ...

  6. ubuntu 14.04 桌面版关闭图形界面

    ubuntu 14.04 桌面版关闭图形界面 问题: 怎样将ubuntu14.04设置为文本模式启动? 解决方式: 改动改GRUB 的配置文件(不建议直接改 grub.conf) $sudo vim ...

  7. Innosetup

    卸载的同时删除日志,卸载的时候判断程序是否正在运行,regsvr32 1.卸载程序的时候如何判断程序是否正在运行 http://bbs.csdn.net/topics/370097914 2.强制删除 ...

  8. android JNI 一维数组、二维数组的访问与使用

    在JNI中访问JAVA类中的整型.浮点型.字符型的数据比较简单,举一个简单的例子,如下: //得到类名 jclass cls = (*env)->GetObjectClass(env, obj) ...

  9. ssh跳板登陆太麻烦,使用expect每次自动登录 利用expect 模拟键盘动作,在闲置时间之内模拟地给个键盘响应

    #!/usr/bin/expect -f #设置超时时间 set timeout #这里设置了跳板机的密码 set password "你的跳板机密码" #连接跳板机 spawn ...

  10. django 笔记5 外键 ForeignKey

    class UsserGroup(models.Model): uid = models.AutoField(primary_key=True) caption = models.CharField( ...