opencv2.4.9中ann_mlp.cpp学习

多层感知机的结构

来自<a href=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-范数归一化权值。
公式如下:
Nguyen-Widrow权值初始化公式

三.使用MLP来分类

正如http://blog.youkuaiyun.com/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.youkuaiyun.com/CHIERYU 未经允许请勿用于商业用途

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值