(BP进阶2)学习和实现BP神经网络

上午说到了BP神经网络,现在就把神经网络的具体实现做一下简单的解析。
BP的实现原理可以参考这里:http://blog.csdn.net/u013007900/article/details/50118945
在上次的讲解中,我们说到了

可以假设,

那么

yj表示神经元j的输出,函数f称为激活函数 ( Activation Function )或转移函数 ( Transfer Function ) ,net'j(t)称为净激活(net activation)。 若将阈值看成是神经元j的一个输入x0的权重w0j,则上面的式子可以简化为:

接下来开始利用该式子简要论述BP实现的原理:

我们假设一个ANN的拓扑结构是比较常见的三层拓扑

可以看出,该拓扑结构只包含一个输入层,一个输出层,一个隐藏层,可以假设输入层的权值数是Wjk,隐藏层的权值数是Vij,dk为预期的输出值,Ok是实际经计算得出的输出值,当实际计算值与预期期望值不同时,存在误差E,定义如下:

将以上误差定义式展开至隐层,有

进一步展开至输入层,有

由上式可以看出,网络输入误差是各层权值ωjκ、υij的函数,因此调整权值可改变误差 E。 显然,调整权值的原则是使误差不断减小,因此应使权值与误差的梯度下降成正比。
故训练一个BP神经网络,实际上就是调整网络的权重和偏置这两个参数,BP神经网络的训练过程分两部分:
前向传输,逐层波浪式的传递输出值;
逆向反馈,反向逐层调整权重和偏置;

到此,我们已经大致明白了神经网络的训练过程,但是怎么进行训练的实现和网络的使用呢?
在我看来,代码实现的关键步骤只有3步:
1.创建网络(create函数)
2.训练网络(train函数)
3.实现网络(predict函数)

当然,一个网络训练好后可以将其保存为xml文件,下次再次使用时只需要读取该xml文件就可以得到上次训练过的神经网络了,因此,又可以分为两步:
1)网络的创建:
1.创建网络(create函数)
2.训练网络(train函数)
3.保存网络(save函数)
2)网络使用
1.读取网络(load函数)
2.实现网络(predict函数)
当然,如果不想保存该网络的话也可以训练好直接使用的。

实现网络的训练首先需要准备好两组数据,分别是:样本数组和样本标记数组,拿上次的两个数相与的例子来说,样本数组即为:{{0,0},{0,1},{1,0},{1,1}},而样本标记数组则是每个样本的结果分类,共有4组数据,每组数据的结果都有2种可能,四组数据的输出结果是0,0,0,1,那么,标记样本数组则可以初始化为: { {1,0}, {1,0}, {1,0}, {0,1} }需要注意的是,两个样本都是由Mat类型储存,且数据类型都定义为CV_32FC1。

因此如果需要就该题进行BP神经系统的训练,可以初始化样本数组和标记数组如下:

float labels[4][2] = { {1,0}, {1,0}, {1,0}, {0,1} };
Mat labelsMat(4,2, CV_32FC1, labels);
float trainingData[4][2] = { { 0, 0 }, { 0, 1 }, { 1, 0 }, { 1, 1 } };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);

在理解了以上讲解,那么进行神经网络的搭建也就简单得多了,我们还拿这个简单的例子来进行分析:

创建神经网络

首先,这组数据的每组输入数据有两组,故输入层的感知器有两个,输出的数组每组也有两个,故输出层感知器个数为2,隐藏层一般情况下都有一到两个,但是在本题中由于不需要隐藏层就可以解决,故无需用到隐藏层,所以该ANN共有两层:输入层(两个感知器)和输出层(两个感知器),(但是按照逻辑来讲,建立隐藏层以后仍旧是可以得到想要的答案的)创建神经网络的函数可以写为:

CvANN_MLP bp;
Mat layerSizes = (Mat_<int>(1, 2) <<2,2); 
bp.create(layerSizes, CvANN_MLP::SIGMOID_SYM);

CvANN_MLP::SIGMOID_SYM :选用sigmoid作为激励函数,即上次所说的S形函数(包括单极性S形函数和双极性S形函数)
除此之外,BP所使用的激励函数还有:
CvANN_MLP::GAUSSIAN:GAUSS函数
CvANN_MLP::IDENTITY:阶跃函数。

训练神经网络

在进行神经网络的训练时,我们就需要用到实现进行初始化好的样本数组和标记数组了,为了方便在图像中显示,我们将样本数组:{{0,0},{0,1},{1,0},{1,1}},进行扩大:{ { 50, 50 }, { 50, 100 }, { 100, 50 }, { 100, 100 } },扩大后直接调用训练函数:

bp.train(trainingDataMat, labelsMat, Mat(), Mat(), params); 

关于train函数的参数:

int CvANN_MLP::train(constMat& inputs, constMat& outputs, 
constMat& sampleWeights, constMat& sampleIdx=Mat(), 
CvANN_MLP_TrainParams params=CvANN_MLP_TrainParams(), intflags=0 );```
1) inputs:输入矩阵。它存储了所有训练样本的特征。假设所有样本总数为nSamples,而我们提取的特征维数为ndims,
则inputs是一个nSamples∗ndims的矩阵,每个样本的特征占一行。
2) outputs:输出矩阵。我们实际在训练中,我们知道每个样本所属的种类,假设一共有nClass类。那么我们将outputs设置为
一个nSample*nClass列的矩阵,每一行表示一个样本的预期输出结果,该样本所属的那类对应的列设置为1,其他都为0。
比如我们需要识别0-9这10个数字,则总的类数为10类,那么样本数字“3”的预期输出为[0,0,1,0,0,0,0,0,0,0];
3) sampleWeights:一个在使用RPROP方法训练时才需要的数据,如果使用的是BACKPROP方法则不设置,直接设置为Mat()即可。
4) sampleIdx:相当于一个遮罩,它指定哪些行的数据参与训练。如果设置为Mat(),则所有行都参与。
5) params:这个在刚才已经说过了,是训练相关的参数。
其中,params是CvANN_MLP_TrainParams类型的参数,是经过初始化的,训练相关的参数

CvANN_MLP_TrainParams params;  
params.train_method=CvANN_MLP_TrainParams::BACKPROP;  
params.bp_dw_scale=0.1;  
params.bp_moment_scale=0.1;  
//params.train_method=CvANN_MLP_TrainParams::RPROP;  
//params.rp_dw0 = 0.1;   
//params.rp_dw_plus = 1.2;   
//params.rp_dw_minus = 0.5;  
//params.rp_dw_min = FLT_EPSILON;   
//params.rp_dw_max = 50.;  
以上是其初始化参数方法,可以看出,共有两种初始化方法,其中BACKPROP有两个初始化参数,RPROP有五个初始化参数, BACKPROP表示使用back-propagation的训练方法,RPROP即最简单的propagation训练方法。Opencv的神经网络实现了MLP算法,具体为BACKPROP算法和RPROP算法两种,BACKPROP算法使用的是在线方法,RPROP算法使用的是批量方法。

在这里我们使用第一种,即BACKPROP方法。
#保存和加载神经网络
到此神经网络已经搭建完成了,我们可以选择

bp.save("name.xml");

保存神经网络,保存后直接退出或者是继续使用该网络,但是保存后下次使用该神经网络就不必再训练了,只需要使用函数:

bp.load("name.xml");

即可。
#使用神经网络
图像进行特征提取,把它保存在sampleMat里,通过调用predict函数,我们得到一个输出向量,它是一个1*nClass的行向量,(nClass是可能出现的结果种类数目)

其中每一列说明它与该类的相似程度(0-1之间),也可以说是置信度。我们只用对output求一个最大值,就可得到结果。
这个函数的返回值是一个无用的float值,可以忽略。
在本例子中,由于输入层的感知器数目为2,是图像的坐标,那么,在图像的每一个坐标都去进行分类,得到的是 输出层感知器数目的个数个输出矩阵,每个数代表该种结果的符合率
如:将一个坐标 带入后p[0]==0.1,p[1]==0.5,那么,取拟合率较大的,训练结果是1。

for (int i = 0; i < image.rows; ++i){
for (int j = 0; j < image.cols; ++j){
Mat sampleMat = (Mat_<float>(1, 2) << i, j);
Mat responseMat;
bp.predict(sampleMat, responseMat);
float* p = responseMat.ptr<float>(0);
if (p[0] > p[1]){
image.at<Vec3b>(i,j) = green;
}
else{
image.at<Vec3b>(i,j) = blue;
}
}
}

把所有代码总结起来:

include <opencv2/core/core.hpp>

include <opencv2/highgui/highgui.hpp>

include <opencv2/ml/ml.hpp>

include <iostream>

include <string>

using namespace std;
using namespace cv;
int main()
{
CvANN_MLP bp;
CvANN_MLP_TrainParams params;
params.train_method = CvANN_MLP_TrainParams::BACKPROP;
params.bp_dw_scale = 0.1;
params.bp_moment_scale = 0.1;
float labels[4][2] = { {1,0}, {1,0}, {1,0}, {0,1} };
Mat labelsMat(4,2, CV_32FC1, labels);
float trainingData[4][2] = { { 50, 50 }, { 50, 100 }, { 100, 50 }, { 100, 100 } };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat layerSizes = (Mat_<int>(1, 2) <<2,2);
bp.create(layerSizes, CvANN_MLP::SIGMOID_SYM);
bp.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
int width = 150, height =150;
Mat image = Mat::zeros(height, width, CV_8UC3);
Vec3b green(0, 255, 0), blue(255, 0, 0);
// Show the decision regions
for (int i = 0; i < image.rows; ++i){
for (int j = 0; j < image.cols; ++j){
Mat sampleMat = (Mat_<float>(1, 2) << i, j);
Mat responseMat;
bp.predict(sampleMat, responseMat);
float* p = responseMat.ptr<float>(0);
if (p[0] > p[1]){
image.at<Vec3b>(i,j) = green;
}
else{
image.at<Vec3b>(i,j) = blue;
}
}
}
int thickness = -1;
int lineType = 8;
circle(image, Point(50, 50), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(50, 100), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(100, 50), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(100, 100), 5, Scalar(0,0,0), thickness, lineType);
imwrite("result.png", image); // save the image
imshow("BP Simple Example", image); // show it to the user
waitKey(0);
return 0;
}

运行结果:![](http://upload-images.jianshu.io/upload_images/1825077-4e39454dc6c5cc68.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276

推荐阅读更多精彩内容

  • 芙蓉又长出花蕾, 在静候佳人, 我很是欢喜。 蚜虫在花蕾上联欢, 活得很舒坦, 我有些担心!
    panjw阅读 193评论 0 3
  • 给我 一望无垠的田野 我要把家安在那里 除除草 耕耕田 种下春天和夏天 我要把树栽在上面 刻下时间和容颜 我要把你...
    伦小让阅读 220评论 4 3
  • 1 不知道为什么, 这些日子的脾气越来越暴躁,动不动在家就发火, 本来很小的一个事情,小声说不了一两句,马上抬高声...
    清风明月照牡丹阅读 252评论 0 2
  • 转自http://www.mamicode.com/info-detail-877996.html 一、网络各个协...
    在这蓝色天空下阅读 18,086评论 7 48