您的位置:首页 > 编程语言 > C语言/C++

opencv2.4.9中ann_mlp.cpp学习

2015-11-22 17:09 459 查看

多层感知机的结构

http://www.ieee.cz/knihovna/Zhang/Zhang100-ch03.pdf” title=”” />

理论上已经证明,单层感知机无法拟合XOR等非线性函数,而两层感知机(hidden layer,output layer,但不包括input layer)可以拟合任意连续函数。该理论很好,但是对应一个待拟合的函数,却无法给出每层到底需要多少个神经元(neuron)。

理解opencv2.4.9中ann_mlp.cpp的难点之二,一是RPROP算法,二是权值初始化方法

一.RPROP算法

rprop全称是“resilient propagation.在BP算法中对权值更新推导过程中有如下公式:



ann_mlp.cpp中关于各个参数的取值大都参考了文献Riedmiller M, Braun H. A direct adaptive method for faster backpropagation learning: The RPROP algorithm[C]//Neural Networks, 1993., IEEE International Conference on. IEEE, 1993: 586-591.

在此,贴出公式方便理解代码。

二.权值初始化方法—Nguyen-Widrow algorithm

网上有人说:Nguyen & Widrow in their paper assume that the inputs are between -1 and +1.

Nguyen Widrow initialization is valid for any activation function which is finite in length. Again in their paper they are only talking about a 2 layer NN, not sure about a 5 layer one.

但是ann_mlp.cpp中将Nguyen-Widrow algorithm应用到了任意层数。

使用Nguyen-Widrow algorithm的2个步骤:

1.给与hidden layer的neuron连接权值随机赋予初值。

若前一层的hidden node数是n1,当前层的hidden node数是n2,则总连接数数是n1*n2.

权值w的个数=(n1+1)*(n2+1),所有w均服从(-1,1)之间的均匀分布。“+1”的原因是在将WX输入到sigmoid前有个偏置bias。这个连接关系可以用一个矩阵来表示,矩阵值代表w。

2.对每个hidden node,采用1-范数归一化权值。

公式如下:



三.使用MLP来分类

正如http://blog.csdn.net/xiaowei_cqu/article/details/9004331所说:“由于ml模型实现的算法都继承自统一的CvStatModel基类,其训练和预测的接口都是train(),predict()。”

训练数据采用mushroom.cpp中所用到的agaricus-lepiota.data,输入数据是22维的,输出有毒还是无毒标签。

1. 设计MLP

假设采用两层感知机,input layer node number = 22,output layer node

number = 1,由于d

维感知机的VC维是d+1,证明请参照http://www.douban.com/note/323572623/,因此hidden

layer node

number=21,因此这个两层感知机必定可以将数据完全分开,我认为当感知机的层数增加时,每层的neurons个数应该要减少,在此为了简便,只采用两层感知机。实验室采用for循环选择mse最小时的hidden layer node number。

2.数据处理

由于agaricus-lepiota.data中有属性缺失问题,但是为了简单,不像CvDTree那样考虑代理分裂的之类的处理了,所有缺失属性‘?’都变成了-1。

代码如下:

#include <iostream>
#include<opencv2/opencv.hpp>
#include <opencv2/ml/ml.hpp>

using namespace cv;
using namespace std;

static int mushroom_read_database(const char* filename, CvMat** data, CvMat** missing, CvMat** responses)
{
const int M = 1024;
FILE* f = fopen(filename, "rt");
CvMemStorage* storage;
CvSeq* seq;
char buf[M + 2], *ptr;
float* el_ptr;
CvSeqReader reader;
int i, j, var_count = 0;
if (!f)
return 0;

// read the first line and determine the number of variables
if (!fgets(buf, M, f))
{
fclose(f);
return 0;
}

for (ptr = buf; *ptr != '\0'; ptr++)
var_count += *ptr == ',';
assert(ptr - buf == (var_count + 1) * 2);

// create temporary memory storage to store the whole database
el_ptr = new float[var_count + 1];
storage = cvCreateMemStorage();
seq = cvCreateSeq(0, sizeof(*seq), (var_count + 1)*sizeof(float), storage);

for (;;)
{
for (i = 0; i <= var_count; i++)
{
int c = buf[i * 2];
el_ptr[i] = c == '?' ? -1.f : (float)c;//'?' is replaced by -1.f
}
if (i != var_count + 1)
break;
cvSeqPush(seq, el_ptr);
if (!fgets(buf, M, f) || !strchr(buf, ','))
break;
}
fclose(f);

// allocate the output matrices and copy the base there
*data = cvCreateMat(seq->total, var_count, CV_32F);
*missing = cvCreateMat(seq->total, var_count, CV_8U);
*responses = cvCreateMat(seq->total, 1, CV_32F);

cvStartReadSeq(seq, &reader);

for (i = 0; i < seq->total; i++)
{
const float* sdata = (float*)reader.ptr + 1;
float* ddata = data[0]->data.fl + var_count*i;
float* dr = responses[0]->data.fl + i;
uchar* dm = missing[0]->data.ptr + var_count*i;

for (j = 0; j < var_count; j++)
{
ddata[j] = sdata[j];
dm[j] = sdata[j] < 0;
}
*dr = sdata[-1];
CV_NEXT_SEQ_ELEM(seq->elem_size, reader);
}

cvReleaseMemStorage(&storage);
delete[] el_ptr;
return 1;
}

int main(int argc,char **argv){
CvMat *data = 0, *missing = 0, *responses = 0;
const char* base_path = argc >= 2 ? argv[1] : "C:/opencv2.4.9/sources/samples/c/agaricus-lepiota.data";
if (!mushroom_read_database(base_path, &data, &missing, &responses))
{
printf("\nUnable to load the training database\n\n");
return -1;
}
Mat X = Mat(data, true);
X.convertTo(X, CV_32FC1);
Mat Y = Mat(responses, true);
Y.convertTo(Y, CV_32FC1);
cvReleaseMat(&data);
cvReleaseMat(&missing);//missing is not used in this program.
cvReleaseMat(&responses);
int SAMPLES = X.rows;
float SPLIT = 0.8f;
Mat X_train = X(Range(0, (int)(SAMPLES*SPLIT)), Range::all());
Mat Y_train = Y(Range(0, (int)(SAMPLES*SPLIT)), Range::all());

Mat X_test = X(Range((int)(SAMPLES*SPLIT), SAMPLES), Range::all());
Mat Y_test = Y(Range((int)(SAMPLES*SPLIT), SAMPLES), Range::all());

CvANN_MLP_TrainParams params(
cvTermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 1000, 0.000001),
CvANN_MLP_TrainParams::BACKPROP,
0.1,
0.1);

float min_mse = 1e8f;
int min_nnode = 0, min_err = X_test.rows;
for (int nnode = 4; nnode <= X_train.cols-1; nnode++){
Mat layers = (Mat_<int>(3, 1) << X_train.cols, nnode, 1);

CvANN_MLP net(layers, CvANN_MLP::SIGMOID_SYM, 0, 0);
net.train(X_train, Y_train, Mat(), Mat(), params);

Mat predictions(Y_test.size(), CV_32F);
net.predict(X_test, predictions);

//cout << predictions << endl;
Mat error = predictions - Y_test;
int err=0;
for (int k = 0; k < error.cols; k++){
if (fabs((float)*(error.data + k)) > FLT_EPSILON)//the same threshold as CvDTree
err++;
}
multiply(error, error, error);
float mse = (float)(sum(error)[0] / error.rows);
if (mse < min_mse){
min_mse = mse;
min_nnode = nnode;
min_err = err;
}
}
cout << "MSE: " << min_mse << endl;
cout << " nnode:" << min_nnode << endl;
cout << "accuracy:" << (1 - min_err / X_test.rows) * 100 << "%\n";
system("pause");
return 0;
}


代码借鉴了http://stackoverflow.com/questions/25748423/opencv-mlp-with-sigmoid-neurons-output-range。此处贴出全部代码一是方便备忘,二是方便他人重复试验。试验中发现accuracy总是100%?
另外说明:opencv2.4.9中RPROP和权值初始化时一样的,但是train函数不一样。


训练的过程中采用的训练方法是BACKPROP而不是RPROP,发现RPROP的速度随着hidden layer number的增加变得非常慢。

文中诸多不完善的地方,后续有了新的体会,再更新。。。

转载请注明作者和出处http://blog.csdn.net/CHIERYU 未经允许请勿用于商业用途
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多层感知机 opencv