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

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

欢迎和大家交流。qq:1037701636 email:[email protected]

写在前面的闲话:

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

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

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

BP神经网络的概念:

BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络。是目前应用最广泛的神经网络模型之一。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。BP神经网络的基本模型图如下所示:

BP网络的三个层次:输入层、隐藏层以及输出层。我们所要做的就是根据自身的需求建立一个属于我们自己的BP神经网络。

2.BP神经网络的原理以及相关公式的推导

2.1、BP网络的基本思想:

BP神经网络学习过程由信息的正向传递与误差的反向传播两个过程组成:

(1) 正向传递:输入样本从输入经隐含层逐层计算传向输出层,若输出层的实际输出和期望输出不符,则计算输出层的误差值,然后转向反向传播过程。

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

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

根据误差梯度下降法依次修正输出层权值的修正量 Δ wki ,输出层阈值的修正量 Δa k,隐含层权值的修正量 Δ wij ,隐含层阈值的修正量:

上述公式表明网络输入误差是各层权值wjk、vij的函数,因此调整权值可改变误差E。显然,调整权值的原则是使误差不断地减小,因此应使权值的调整量与误差的梯度下降成正比,故而对BP网络算法的直观解释如下所示:

通过上图表明,BP网络算法的核心是在不断调整权值的情况下,使得误差不断的减小。显然无论是在正向梯度还是负向梯度,在离散情况下都需要不断的将权值往误差极小值的地方调整。而调整的速率值eta(也称权值的步进值)关乎着整个神经网络的训练速度。

BP神经网络的公式推导可以参考:http://en.wikipedia.org/wiki/Backpropagation,相关推导公式核心内容如下:

BP网络的算法核心是使用一个刺激函数不断的forward,而这个刺激函数往往使用S型激活函数(logsig)为:

注:上述公式参阅网络资源,这几步流程图,对于理解整个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来处理,将其应用到嵌入式设备中去。

基于BP神经网络的简单字符识别算法自小结(C语言版),布布扣,bubuko.com

时间: 2024-10-10 10:07:26

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

ECG信号读取,检测QRS,P,T 波(基于小波去噪与检测),基于BP神经网络的身份识别

这学期选了神经网络的课程,最后作业是处理ECG信号,并利用神经网络进行识别. 1  ECG介绍与读取ECG信号 1)ECG介绍  具体ECG背景应用就不介绍了,大家可以参考百度 谷歌.只是简单说下ECG的结构: 一个完整周期的ECG信号有 QRS P T 波组成,不同的人对应不用的波形,同一个人在不同的阶段波形也不同.我们需要根据各个波形的特点,提取出相应的特征,对不同的人进行身份识别. 2)ECG信号读取 首先需要到MIT-BIH数据库中下载ECG信号,具体的下载地址与程序读取内容介绍可以参考

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

一.BP神经网络原理及结构 本部分先介绍神经网络基本单元神经元的结构和作用,再主要介绍BP神经网络的结构和原理. 1.神经元 神经元作为神经网络的基本单元,对于外界输入具有简单的反应能力,在数学上表征为简单的函数映射.如下图是一个神经元的基本结构,  神经元结构 图中是神经元的输入,是神经元输入的权重,是神经元的激活函数,y是神经元的输出,其函数映射关系为 激活函数来描述层与层输出之间的关系,从而模拟各层神经元之间的交互反应.激活函数必须满足处处可导的条件.常用的神经元函数有四种,分别是线性函数

题外:分类篇(音乐风格分类)基于BP神经网络

语音特征参数MFCC的提取及识别 (2012-09-07 20:24:03) 转载▼ 耳蜗实质上相当于一个滤波器组,耳蜗的滤波作用是在对数频率尺度上进行的,在1000HZ下,人耳的感知能力与频率成线性关系:而在1000HZ以上,人耳的感知能力与频率不构成线性关系,而更偏向于对数关系,这就使得人耳对低频信号比高频信号更敏感.Mel频率的提出是为了方便人耳对不同频率语音的感知特性的研究.频率与Mel频率的转换公式为: MFCC在一定程度上模拟了人耳对语音的处理特点,应用了人耳听觉感知方面的研究成果,

基于人工神经网络的数字字符识别系统demo(一):字符去噪、分割

最近尝试利用神经网络做数字字符识别,大概做了一下.整体很简陋,就是先对测试图片做下预处理,然后通过重采样提取特征,最后通过神经网络进行训练和识别.感兴趣的可以点击这里 了解一下,欢迎多多指教. 这里我大概介绍一下怎样将一副包含多个字符的图片进行去噪处理进而分割出单个字符,关于opencv中怎样使用ANN详见Opencv中ANN神经网络使用示例. 原始图: 效果图: 下面介绍一下处理步骤, 1.载入图像并作灰度化 2.二值化并作反色处理 3.提取外轮廓 4.根据轮廓大小去除噪声 5.再次查找轮廓并

基于BP神经网络的数字识别

一.BP神经网络 BP(Back Propagation)表示反向传播.BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程.它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小.BP神经网络模型拓扑结构包括输入层(input).隐层(hidden layer)和输出层(output layer). 图1 三层BP网 如图所示,最下面一层为输入层,中间为隐藏层,最上面为输出层.其中X={x1,x2.......xn},

基于cocos2dx的RPG简单实用算法之一 - 角色的移动

最近自己写RPG,发现在角色对象运动上面还是可以运动到不少的以前数学知识(经理各种纠结的脑补),好久没有写博客了,趁热总结一下算法思路,免得自己过两天又忘了. 已知角色速度和目的地,求每帧位置 已经知道了一个角色 bodyA 速度为 像素/秒 float speed  = 5 目的地为  Point  destination 当前地点为 Point currentPosition = bodyA.getPosition() 那么帧循环里面应该怎样计算角色的 当前位置呢? 方案1. 计算量小但是不

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

感知机与神经元 感知机(Perceptron)由两层神经元组成(输入层.输出层),输入层接收外界输入信号后传递给输出层,输出层是M-P神经元,亦称“阈值逻辑单元”(threshold logic unit). 输入层只接受输入而没有权重以及阈值,输出层的神经元有阈值,两层间连接有权重. 把阈值当做是第n+1个权重,第n+1个输入是-1,那么就可以把阈值放进权重里了.那么只需要做权重的学习. 根据错误程度进行调整. 这样两层神经元只能处理线性问题,而非线性问题则需要多层网络. 输入输出层之间的叫做

基于cocos2dx的RPG简单实用算法之3 - 多角色跟随阵型移动

1. 确定到一个阵型中心对象. 也许是一个英雄,也可以是一个隐藏的对象.也就是下文种的 GridCenter 2. 预先计算号每个阵型"槽" 相对中心对象的 向量. void GameControlManager::startGridMode() { if(m_MainScene->heroList.empty()) return; m_IsStartGridMode = true; Point GridCenter = findGridCenter(); if(memberNu

基于cocos2dx的RPG简单实用算法之2 - 角色跟随移动

1. 最朴素的思路: 如果让 B 跟随 A ,并且两者保持距离X(什么,你要让B和A重叠?那你真的不需要看下去了) . 每一帧 检查 B 与A 的距离, 如果距离 > X ,那么就让B 想A 移动一步. 但是当B的速度 > A 的速度,问题来了,B跟随A 的时候抖得很厉害. 2. 解决抖动 这里有一个比较水的小技巧来解决抖动:lerp 插值 m_heroFollowList 为一角色列表,通过每帧调用playHeroFollow()来实现跟随 void MapTileLayer::playHe