(BP进阶3)详解BP神经网络

推荐一篇非常详细的对BP的解答:http://www.2cto.com/kf/201610/553336.html
这里主要讲解的是昨天未涉及和讲解到的ANN的详细和拓展的部分。
具体涉及到的是:
BACKPROP和RPROP两种训练方式的原理和区别。
实现神经网络时每个函数的具体参数及意义。

BACKPROP

前馈神经网络是神经网络的一种,也是最常用的一种神经网络。它包括一个输入层,一个输出层和若干个隐含层,因此具有该种拓扑结构的神经网络又称为多层感知器(MLP)。如图所示,该MLP包括一个输入层,一个输出层和一个隐含层,其中某一层的神经元只能通过一个方向连接到下一层的神经元。
前馈神经网络

Backprop(backward propagation oferrors,误差的反向传播,简称BP)算法的核心思想是:通过前向通路(箭头的方向)得到误差,再把该误差反向传播实现权值w的修正。

目标值t有J种可能的值,即t={t1, t2,…,tJ},设样本x经过前向通路得到的最终输出为y={y1L,y2L,…,yJL },则该样本的平方误差为:

之所以式中的平方误差函数要除以2,是为了便于后面的求导运算,因为它并不影响误差的变化趋势。
显然,为了减小E,每层神经元的输出又由上层的所有神经元的输出经加权激活后得到,因此可以说误差E是全体权值w的函数,通过改变权值w,就可达到使误差E最小的目的
Backprop算法是一种迭代的方法,也就是我们不必通过一次改变权值w来达到使E最小的目的,我们只需渐进的减小E即可,误差越大,那么权值的变化就也越大,而当权值改变时,误差就要重新计算。这样两者相互作用不断迭代,直到误差小于某个值(即收敛)为止。该方法就是我们常用的梯度下降法。

误差E对权值w的导数为w的变化率,即:


式中,η表示学习效率,它的取值在0和1之间,它起到控制收敛速度和准确性的作用。如果η过大,导致振荡,则很难收敛,如果η过小,则需要更长的时间收敛。为了改变因η选取的不好而带来的问题,又引入了被称为“动量(momentum)”的参数μ,则w的变化率改写为:


式1

在反向传播的过程中,所有权值都经过了上述计算后,就得了更新后的所有权值。用新得到的权值计算下一个样本,因为样本是一个一个的进入MLP,每完成一个样本的计算就更新一次权值。为了增加鲁棒性,在每次迭代之前,可以把全体样本打乱顺序,这样每次迭代的过程中提取样本的顺序就会不相同。

首先要解决的问题是初始化权值,即第一次权值如何选择。一般的做法是随机选择很小的值作为初始权值,但这样做收敛较慢。比较好的方法是采用Nguyen-Widrow算法初始化权值。它的基本思想是每个神经元都有属于自己的一个区间范围,通过初始化权值就可以限制它的区间位置,当改变权值时,该神经元也只是在自己的区间范围内变化,因此该方法可以大大提高收敛速度。

Nguyen-Widrow算法初始化MLP权值的方法为:对于所有连接输出层的权值和偏移量,初始值为一个在正负1之间的随机数。对于中间层的权值,初始为:

其中,vh是一个在正负1之间的随机数,H为第l-1层神经元的数量。而中间层的偏移量初始为:

其中,vk是一个在正负1之间的随机数,K为第l层神经元的数量,而G为:

RPROP

以上我们介绍了经典的Backprop算法,该算法还是有一些不足之处。首先是它的学习效率η是需要我们事先确定好;另外权值的变化是基于误差梯度的变化率,虽然这点乍一看,似乎没有问题,但我们不敢保证它永远正确有效。为此Riedmiller等人提出了RPROP算法(resilient backpropagation),用以改善Backprop算法。
RPROP算法的权值变化率并不是基于误差梯度的变化率,而是基于它的符号:


式2
式3

常数η+必须大于1,常数η-必须在0和1之间
关于Δ(t)的初始值和它的变化范围。Riedmiller等人已经给出了Δ(0)初始化为0.1是比较正确的选择,而Δmax(t)=50,Δmin(t)=10-6可以有效的防止溢出。

函数参数

create函数:MLP模型的构建:
void CvANN_MLP::create( const CvMat* _layer_sizes, int _activ_func,
                        double _f_param1, double _f_param2 )
{
    CV_FUNCNAME( "CvANN_MLP::create" );
 
    __BEGIN__;
    // l_count表示MLP的层数,buf_sz表示开辟存储权值的内存空间的大小
    int i, l_step, l_count, buf_sz = 0;
    int *l_src, *l_dst;
 
    clear();    //清除和初始化一些全局变量
    //判断_layer_sizes的格式、数据类型是否正确,_layer_sizes必须是相量形式,数据类型必须为CV_32SC1,不对则报错
    if( !CV_IS_MAT(_layer_sizes) ||
        (_layer_sizes->cols != 1 && _layer_sizes->rows != 1) ||
        CV_MAT_TYPE(_layer_sizes->type) != CV_32SC1 )
        CV_ERROR( CV_StsBadArg,
        "The array of layer neuron counters must be an integer vector" );
    //调用set_activ_func函数,设置激活函数,该函数在后面给出详细介绍
    CV_CALL( set_activ_func( _activ_func, _f_param1, _f_param2 ));
    //l_count为相量_layer_sizes的维数,即MLP的层数L
    l_count = _layer_sizes->rows + _layer_sizes->cols - 1;
    l_src = _layer_sizes->data.i;    //_layer_sizes的首地址指针
    //_layer_sizes元素的步长
    l_step = CV_IS_MAT_CONT(_layer_sizes->type) ? 1 :
                _layer_sizes->step / sizeof(l_src[0]);
    //创建相量layer_sizes
    CV_CALL( layer_sizes = cvCreateMat( 1, l_count, CV_32SC1 ));
    l_dst = layer_sizes->data.i;    //layer_sizes的首地址指针
 
    max_count = 0;    //表示某层中,最多的神经元的数量
    for( i = 0; i < l_count; i++ )    //遍历MLP的所有层
    {
        int n = l_src[i*l_step];    //得到当前层的神经元的数量
        //满足条件:0 < i && i < l_count-1,说明i为隐含层,该if语句的作用是,如果是隐含层,则神经元的数量一定要大于1,如果是输入层或输出层,则神经元的数量至少应为1,否则程序报错
        if( n < 1 + (0 < i && i < l_count-1))
            CV_ERROR( CV_StsOutOfRange,
            "there should be at least one input and one output "
            "and every hidden layer must have more than 1 neuron" );
        //把当前层的神经元的数量赋值给变量layer_sizes所对应的层
        l_dst[i] = n;
        //记录下MLP层中数量最多的神经元的数量
        max_count = MAX( max_count, n );
        //统计该MLP一共有多少个权值,其中也包括偏移量
        if( i > 0 )
            buf_sz += (l_dst[i-1]+1)*n;
    }
    // l_dst[0]表示输入层神经元的数量,l_dst[l_count-1]表示输出层神经元的数量
    buf_sz += (l_dst[0] + l_dst[l_count-1]*2)*2;
    //创建相量wbuf,用于存储权值
    CV_CALL( wbuf = cvCreateMat( 1, buf_sz, CV_64F ));
    //为weights开辟内存空间
    CV_CALL( weights = (double**)cvAlloc( (l_count+2)*sizeof(weights[0]) ));
    //weights[0]指向wbuf的首地址,它表示输入层规范化所用的系数
    weights[0] = wbuf->data.db;
    //定义weights[1]首地址
    weights[1] = weights[0] + l_dst[0]*2;
    // weights[1]至weights[l_count]表示MLP相应层的所有权值,包括偏移量(即公式中的+ 1),它存放在数组的最后一个位置上
    for( i = 1; i < l_count; i++ )
        weights[i+1] = weights[i] + (l_dst[i-1] + 1)*l_dst[i];
    // weights[l_count]和weights[l_count+1]都表示输出层规范化所用到的系数,训练时用的是weights[l_count+1]内的值,预测时用的是weights[l_count]内的值
    weights[l_count+1] = weights[l_count] + l_dst[l_count-1]*2;
 
    __END__;
}

void CvANN_MLP::set_activ_func( int _activ_func, double _f_param1, double _f_param2 )
{
    CV_FUNCNAME( "CvANN_MLP::set_activ_func" );
 
    __BEGIN__;
    //判断激活函数是否为线性、对称SIGMOR、或高斯中的一种
    if( _activ_func < 0 || _activ_func > GAUSSIAN )
        CV_ERROR( CV_StsOutOfRange, "Unknown activation function" );
 
    activ_func = _activ_func;    //赋值
    //根据不同的激活函数类型,赋予不同的参数
    switch( activ_func )
    {
    case SIGMOID_SYM:    //对称SIGMOID激活函数
        max_val = 0.95; min_val = -max_val;
        max_val1 = 0.98; min_val1 = -max_val1;
        //如果用户定义的对称SIGMOID激活函数的参数过小,则重新赋值
        if( fabs(_f_param1) < FLT_EPSILON )
            _f_param1 = 2./3;
        if( fabs(_f_param2) < FLT_EPSILON )
            _f_param2 = 1.7159;
        break;
    case GAUSSIAN:    //高斯激活函数
        max_val = 1.; min_val = 0.05;
        max_val1 = 1.; min_val1 = 0.02;
        //如果用户定义的高斯激活函数的参数过小,则重新赋值
        if( fabs(_f_param1) < FLT_EPSILON )
            _f_param1 = 1.;
        if( fabs(_f_param2) < FLT_EPSILON )
            _f_param2 = 1.;
        break;
    default:    //线性激活函数
        min_val = max_val = min_val1 = max_val1 = 0.;
        _f_param1 = 1.;
        _f_param2 = 0.;
    }
 
    f_param1 = _f_param1;    //赋值α
    f_param2 = _f_param2;    //赋值β
 
    __END__;
}

总结来说:

Sigmoid函数:

对称Sigmoid函数:

高斯函数:

α和β均为函数的系数。在系统进行构建的时候,不但需要指定激励函数的类型,还要在需要使用参数的函数中初始化参数

 CV_WRAP virtual void create( const cv::Mat& layerSizes,
                        int activateFunc=CvANN_MLP::SIGMOID_SYM,
                        double fparam1=0, double fparam2=0 );
第一个参数是输入层,隐藏层,输出层,的感知器的个数信息
第二个参数是激励函数的类型
第三个和第四个参数分别数系数α和β的值,默认为0,也可以自行设置参数,但是如果参数过小会被重新赋值
CvANN_MLP_TrainParams的初始化:训练参数的确定
CvANN_MLP_TrainParams::CvANN_MLP_TrainParams()
{
    //表示训练迭代的终止条件,默认为迭代次数(大于1000)和权值变化率(小于0.01)
    term_crit = cvTermCriteria( CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 1000, 0.01 );
    //具体应用的MLP算法,默认为RPROP
    train_method = RPROP;
    // bp_dw_scale为式13中的η,bp_moment_scale为式13中的μ
    bp_dw_scale = bp_moment_scale = 0.1;
    //RPROP算法所需的参数(式40和式41),依次为Δ(0)、η+、η-、Δmin(t)、Δmax(t)
    rp_dw0 = 0.1; rp_dw_plus = 1.2; rp_dw_minus = 0.5;
    rp_dw_min = FLT_EPSILON; rp_dw_max = 50.;
}
CvANN_MLP_TrainParams::CvANN_MLP_TrainParams( CvTermCriteria _term_crit,
                                              int _train_method,
                                              double _param1, double _param2 )
{
    term_crit = _term_crit;
    train_method = _train_method;
    bp_dw_scale = bp_moment_scale = 0.1;
    rp_dw0 = 1.; rp_dw_plus = 1.2; rp_dw_minus = 0.5;
    rp_dw_min = FLT_EPSILON; rp_dw_max = 50.;
 
    if( train_method == RPROP )    //RPROP算法
    {
        rp_dw0 = _param1;    //输入参数_param1表示Δ(0)
        if( rp_dw0 < FLT_EPSILON )    //Δ(0)不能太小
            rp_dw0 = 1.;
        rp_dw_min = _param2;    //输入参数_param2表示Δmin(t)
        rp_dw_min = MAX( rp_dw_min, 0 );    //Δmin(t)不能小于0
    }
    else if( train_method == BACKPROP )    //BACKPROP算法
    {
        bp_dw_scale = _param1;    //输入参数_param1表示η
        //确保η在一个合理的范围内
        if( bp_dw_scale <= 0 )
            bp_dw_scale = 0.1;
        bp_dw_scale = MAX( bp_dw_scale, 1e-3 );
        bp_dw_scale = MIN( bp_dw_scale, 1 );
        bp_moment_scale = _param2;    //输入参数_param2表示μ
        //确保μ在一个合理的范围内
        if( bp_moment_scale < 0 )
            bp_moment_scale = 0.1;
        bp_moment_scale = MIN( bp_moment_scale, 1 );
    }
    //如果输入参数_train_method为除了RPROP和BACKPROP以外的值,则程序给出的算法为RPROP
    else
        train_method = RPROP;
}

//关于CvTermCriteria 的构造函数:
#define CV_TERMCRIT_ITER    1  
#define CV_TERMCRIT_NUMBER  CV_TERMCRIT_ITER  
#define CV_TERMCRIT_EPS     2  
  
typedef struct CvTermCriteria  
{  
    //【1】int type--type of the termination criteria,one of:  
    //【1】int type---迭代算法终止条件的类型,是下面之一:  
            //【1】CV_TERMCRIT_ITER---在完成最大的迭代次数之后,停止算法  
            //【2】CV_TERMCRIT_EPS----当算法的精确度小于参数double epsilon指定的精确度时,停止算法  
            //【3】CV_TERMCRIT_ITER+CV_TERMCRIT_EPS--无论是哪一个条件先达到,都停止算法  
    int    type;  /* may be combination of 
                     CV_TERMCRIT_ITER 
                     CV_TERMCRIT_EPS */  
    //【2】Maximum number of iterations  
    //【2】最大的迭代次数  
    int    max_iter;  
    //【3】Required accuracy  
    //【3】所要求的精确度  
    double epsilon;  
}  

总结来讲,train函数的使用首先需要包含两个参数:
CvTermCriteria类型的term_cri 和int类型的train_method,第一个表示训练迭代的停止条件,第二个则表示使用的训练方法。

如果使用的是 RPROP:

可以根据上面所提到的计算公式式2式3得到,我们需要指明参数值Δ(0)、η+、η-、Δmin(t)、Δmax(t),而这五个参数分别对应的是:
Δ(0): rp_dw0(一般情况下是0.1)
η+ rp_dw_plus (必须大于1)
η-: rp_dw_minus (必须在0和1之间)
Δmin(t): rp_dw_min (一般设为:10^-6 /FLT_EPSILON,不能小于0)
Δmax(t) :rp_dw_max(Riedmiller的结论值50)

如果使用的是 :BACKPROP

只需要指明两个参数即可:
bp_dw_scale, bp_moment_scale ;分别代表式1中的η和μ。用来计算权值的变化率。其取值都应当尽量合理且大于0。

训练MLP模型:
int CvANN_MLP::train( const CvMat* _inputs, const CvMat* _outputs,
                      const CvMat* _sample_weights, const CvMat* _sample_idx,
                      CvANN_MLP_TrainParams _params, int flags )
//_inputs表示MLP的输入数据,即待训练的样本数据
//_outputs表示MLP的输出数据,即待训练的样本响应值或分类标签
//_sample_weights表示事先定义好的样本的权值
//_sample_idx表示真正被用于训练的样本数据的索引,即有一部分样本不用于训练MLP
//_params表示MLP模型参数
//flags表示控制算法的参数,可以为UPDATE_WEIGHTS、NO_INPUT_SCALE和NO_OUTPUT_SCALE,以及它们的组合,这些变量的含义为:UPDATE_WEIGHTS表示算法需要更新网络的权值;NO_INPUT_SCALE表示算法无需规范化MLP的输入数据,规范化的意思就是使输入样本的特征属性均值为0,标准方差为1;NO_OUTPUT_SCALE表示算法无需归一化MLP的输出数据
//该函数返回迭代次数
{
    const int MAX_ITER = 1000;    //表示最大迭代次数
    const double DEFAULT_EPSILON = FLT_EPSILON;    //定义一个常数
 
    double* sw = 0;    //表示样本的权值
    CvVectors x0, u;    //分别表示MLP的输入数据和输出数据
    int iter = -1;    //表示训练MLP所需的迭代次数
    //初始化首地址指针
    x0.data.ptr = u.data.ptr = 0;
 
    CV_FUNCNAME( "CvANN_MLP::train" );
 
    __BEGIN__;
 
    int max_iter;
    double epsilon;
 
    params = _params;    //MLP模型参数
 
    // initialize training data
    //调用prepare_to_train函数,为MLP模型的训练准备参数,该函数在后面给出详细的介绍
    CV_CALL( prepare_to_train( _inputs, _outputs, _sample_weights,
                               _sample_idx, &x0, &u, &sw, flags ));
 
    // ... and link weights
    //如果没有定义UPDATE_WEIGHTS,则需要调用init_weights函数进行权值的初始化,该函数在后面给出了详细的介绍
    if( !(flags & UPDATE_WEIGHTS) )
        init_weights();
    //得到最大迭代次数
    max_iter = params.term_crit.type & CV_TERMCRIT_ITER ? params.term_crit.max_iter : MAX_ITER;
    max_iter = MAX( max_iter, 1 );    //最大迭代次数必须大于等于1
    //得到用前后两次误差之差来判断终止迭代的系数
    epsilon = params.term_crit.type & CV_TERMCRIT_EPS ? params.term_crit.epsilon : DEFAULT_EPSILON;
    epsilon = MAX(epsilon, DBL_EPSILON);
    //重新定义终止MLP训练的条件
    params.term_crit.type = CV_TERMCRIT_ITER + CV_TERMCRIT_EPS;
    params.term_crit.max_iter = max_iter;
    params.term_crit.epsilon = epsilon;
    //如果是BACKPROP算法,则调用train_backprop函数,如果是RPROP算法,则调用train_rprop函数,这个两个函数在后面有详细的介绍
    if( params.train_method == CvANN_MLP_TrainParams::BACKPROP )
    {
        CV_CALL( iter = train_backprop( x0, u, sw ));
    }
    else
    {
        CV_CALL( iter = train_rprop( x0, u, sw ));
    }
 
    __END__;
    //释放内存空间
    cvFree( &x0.data.ptr );
    cvFree( &u.data.ptr );
    cvFree( &sw );
 
    return iter;    //返回迭代次数
}
//为训练MLP模型准备参数:
bool CvANN_MLP::prepare_to_train( const CvMat* _inputs, const CvMat* _outputs,
            const CvMat* _sample_weights, const CvMat* _sample_idx,
            CvVectors* _ivecs, CvVectors* _ovecs, double** _sw, int _flags )
{
    bool ok = false;    //该函数正确返回的标识
    CvMat* sample_idx = 0;    //表示样本数据的索引
    CvVectors ivecs, ovecs;    //分别表示输入层和输出层的数据
    double* sw = 0;    //表示样本的权值
    int count = 0;
 
    CV_FUNCNAME( "CvANN_MLP::prepare_to_train" );
 
    ivecs.data.ptr = ovecs.data.ptr = 0;    //初始化为0
    assert( _ivecs && _ovecs );    //确保输入参数_ivecs和_ovecs有效
 
    __BEGIN__;
 
    const int* sidx = 0;    //该指针指向sample_idx
    // sw_type和sw_count分别表示样本权值数据的类型和数量
    int i, sw_type = 0, sw_count = 0;
    int sw_step = 0;    //示样本权值数据的步长
    double sw_sum = 0;    //表示样本权值的累加和
    //通过判断layer_sizes是否被正确赋值,来确保MLP模型是否被构建好
    if( !layer_sizes )
        CV_ERROR( CV_StsError,
        "The network has not been created. Use method create or the appropriate constructor" );
    //判断输入参数_inputs是否正确
    if( !CV_IS_MAT(_inputs) || (CV_MAT_TYPE(_inputs->type) != CV_32FC1 &&
        CV_MAT_TYPE(_inputs->type) != CV_64FC1) || _inputs->cols != layer_sizes->data.i[0] )
        CV_ERROR( CV_StsBadArg,
        "input training data should be a floating-point matrix with"
        "the number of rows equal to the number of training samples and "
        "the number of columns equal to the size of 0-th (input) layer" );
    //判断输入参数_outputs是否正确
    if( !CV_IS_MAT(_outputs) || (CV_MAT_TYPE(_outputs->type) != CV_32FC1 &&
        CV_MAT_TYPE(_outputs->type) != CV_64FC1) ||
        _outputs->cols != layer_sizes->data.i[layer_sizes->cols - 1] )
        CV_ERROR( CV_StsBadArg,
        "output training data should be a floating-point matrix with"
        "the number of rows equal to the number of training samples and "
        "the number of columns equal to the size of last (output) layer" );
    //确保样本的输入和输出的数量一致,即每个样本都必须有一个响应值或分类标签
    if( _inputs->rows != _outputs->rows )
        CV_ERROR( CV_StsUnmatchedSizes, "The numbers of input and output samples do not match" );
    //如果定义了_sample_idx,则需要掩码一些样本数据
    if( _sample_idx )
    {
        //得到真正用于训练的样本
        CV_CALL( sample_idx = cvPreprocessIndexArray( _sample_idx, _inputs->rows ));
        sidx = sample_idx->data.i;    //指针赋值
        count = sample_idx->cols + sample_idx->rows - 1;    //得到训练样本的数量
    }
    else
        count = _inputs->rows;    //得到训练样本的数量
 
    if( _sample_weights )    //如果定义了_sample_weights
    {
        if( !CV_IS_MAT(_sample_weights) )    //确保_sample_weights格式正确
            CV_ERROR( CV_StsBadArg, "sample_weights (if passed) must be a valid matrix" );
 
        sw_type = CV_MAT_TYPE(_sample_weights->type);    //数据类型
        sw_count = _sample_weights->cols + _sample_weights->rows - 1;    //数量
        //判断sw_type格式是否正确,sw_count是否与样本的数量一致
        if( (sw_type != CV_32FC1 && sw_type != CV_64FC1) ||
            (_sample_weights->cols != 1 && _sample_weights->rows != 1) ||
            (sw_count != count && sw_count != _inputs->rows) )
            CV_ERROR( CV_StsBadArg,
            "sample_weights must be 1d floating-point vector containing weights "
            "of all or selected training samples" );
 
        sw_step = CV_IS_MAT_CONT(_sample_weights->type) ? 1 :
            _sample_weights->step/CV_ELEM_SIZE(sw_type);    //得到步长
 
        CV_CALL( sw = (double*)cvAlloc( count*sizeof(sw[0]) ));    //为sw分配空间
    }
    //为MLP的输入和输出数据开辟一块内存空间
    CV_CALL( ivecs.data.ptr = (uchar**)cvAlloc( count*sizeof(ivecs.data.ptr[0]) ));
    CV_CALL( ovecs.data.ptr = (uchar**)cvAlloc( count*sizeof(ovecs.data.ptr[0]) ));
 
    ivecs.type = CV_MAT_TYPE(_inputs->type);    //指定类型
    ovecs.type = CV_MAT_TYPE(_outputs->type);    //指定类型
    ivecs.count = ovecs.count = count;    //相量的长度,即维数
 
    for( i = 0; i < count; i++ )    //遍历所有的待训练样本数据
    {
        int idx = sidx ? sidx[i] : i;    //表示样本索引值
        //给MLP的输入和输出数据赋值
        ivecs.data.ptr[i] = _inputs->data.ptr + idx*_inputs->step;
        ovecs.data.ptr[i] = _outputs->data.ptr + idx*_outputs->step;
        if( sw )    //如果sw被定义
        {
            int si = sw_count == count ? i : idx;    //得到样本索引值
            double w = sw_type == CV_32FC1 ?    //得到当前样本的权值
                (double)_sample_weights->data.fl[si*sw_step] :
                _sample_weights->data.db[si*sw_step];
            sw[i] = w;    //赋值
            if( w < 0 )    //权值不能小于0
                CV_ERROR( CV_StsOutOfRange, "some of sample weights are negative" );
            sw_sum += w;    //权值累加
        }
    }
 
    // normalize weights
    if( sw )    //如果sw被定义,归一化样本权值
    {
        sw_sum = sw_sum > DBL_EPSILON ? 1./sw_sum : 0;    //倒数
        for( i = 0; i < count; i++ )
            sw[i] *= sw_sum;    //归一化
    }
    //调用calc_input_scale和calc_output_scale函数,依据_flags分别对输入数据(样本值)和输出数据(样本响应值)进行规范化处理,这两个函数在后面给出详细的介绍
    calc_input_scale( &ivecs, _flags );
    CV_CALL( calc_output_scale( &ovecs, _flags ));
 
    ok = true;    //标识函数返回值
 
    __END__;
 
    if( !ok )    //没有正确对训练过程进行预处理,则清空一些变量
    {
        cvFree( &ivecs.data.ptr );
        cvFree( &ovecs.data.ptr );
        cvFree( &sw );
    }
 
    cvReleaseMat( &sample_idx );    //释放空间
    *_ivecs = ivecs;    //赋值
    *_ovecs = ovecs;    //赋值
    *_sw = sw;    //赋值
 
    return ok;
}

在这里我们只需要拿出最关键的信息即可:

int CvANN_MLP::train( const CvMat* _inputs, const CvMat* _outputs,
                      const CvMat* _sample_weights, const CvMat* _sample_idx,
                      CvANN_MLP_TrainParams _params, int flags )
//_inputs表示MLP的输入数据,即待训练的样本数据
//_outputs表示MLP的输出数据,即待训练的样本响应值或分类标签
//_sample_weights表示事先定义好的样本的权值
//_sample_idx表示真正被用于训练的样本数据的索引,即有一部分样本不用于训练MLP
//_params表示MLP模型参数
//flags表示控制算法的参数,可以为UPDATE_WEIGHTS、NO_INPUT_SCALE和NO_OUTPUT_SCALE,以及它们的组合,这些变量的含义为:UPDATE_WEIGHTS表示算法需要更新网络的权值;NO_INPUT_SCALE表示算法无需规范化MLP的输入数据,规范化的意思就是使输入样本的特征属性均值为0,标准方差为1;NO_OUTPUT_SCALE表示算法无需归一化MLP的输出数据
//该函数返回迭代次数

在进行训练的时候,
第一个表示样本数组:格式为样本个数每个样本的输入的数据个数,数据类型格式是CV_32FC1
第二个表示样本标记数组:格式为样本的个数
每个样本的结果可能性个数/输出的数据个数,数据类型格式是CV_32FC1
第三个表示样本权值:格式为单行或单列数组,通常是单行,列数为(真正需要训练的)输入样本的样本个数,数据类型格式为CV_32FC1或者CV_64FC1(这个参数通常在使用RPROP训练方法时使用)
第四个表示真正被用于训练的样本数据的索引,其格式通常是单行,列数是样本个数,数据类型格式是 CV_8UC1, CV_8UC3,通常情况下每个输入的样本都需要被训练,所以这里一般不需设置。
第五个表示上面构造好的训练参数
最后一个表示控制算法的参数,一般不进行设置。

使用模型:进行数据预测
CV_WRAP virtual float predict( const cv::Mat& inputs, CV_OUT cv::Mat& outputs ) const;
//inputs,一个测试用例,单行,列数为一个输入样本的输入数据个数,数据类型格式为CV_32FC1
//outputs,一个输出样本,单行,列数为一个检测结果的可能数目,其内容是每种可能的拟合率

图像进行特征提取,把它保存在inputs里,通过调用predict函数,我们得到一个输出向量,它是一个1*nClass的行向量,
其中每一列说明它与该类的相似程度(0-1之间),也可以说是置信度。我们只用对output求一个最大值,就可得到结果。
这个函数的返回值是一个无用的float值,可以忽略。
由于该结果是单行的,通常在得到后寻找最大比率的列数,也就是相对应的得到的结果最可能的下标数(应与样本标记数组的相对应),查找最大值方法:

        Point maxLoc;
        double maxVal;
//minMaxLoc函数的参数为:
//1.输入的源图像
//2.原图像中最小的值
//3.原图像中最大的值
//4.原图像中最小的值所在坐标
//5.原图像中最大的值所在坐标
        minMaxLoc(output, 0, &maxVal, 0, &maxLoc);//找到最大的相似度
        //maxVal是最大的相似度,maxLoc是该像素的坐标,由于是行向量,所以y均为1,取的x即可
         int result = maxLoc.x;

推荐阅读更多精彩内容