(BP進(jìn)階3)詳解BP神經(jīng)網(wǎng)絡(luò)

推薦一篇非常詳細(xì)的對(duì)BP的解答:http://www.2cto.com/kf/201610/553336.html
這里主要講解的是昨天未涉及和講解到的ANN的詳細(xì)和拓展的部分幻锁。
具體涉及到的是:
BACKPROP和RPROP兩種訓(xùn)練方式的原理和區(qū)別号坡。
實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)時(shí)每個(gè)函數(shù)的具體參數(shù)及意義稿械。

BACKPROP

前饋神經(jīng)網(wǎng)絡(luò)是神經(jīng)網(wǎng)絡(luò)的一種踏幻,也是最常用的一種神經(jīng)網(wǎng)絡(luò)啃沪。它包括一個(gè)輸入層浪漠,一個(gè)輸出層和若干個(gè)隱含層冗澈,因此具有該種拓?fù)浣Y(jié)構(gòu)的神經(jīng)網(wǎng)絡(luò)又稱(chēng)為多層感知器(MLP)誉帅。如圖所示纳猪,該MLP包括一個(gè)輸入層,一個(gè)輸出層和一個(gè)隱含層氏堤,其中某一層的神經(jīng)元只能通過(guò)一個(gè)方向連接到下一層的神經(jīng)元沙绝。
前饋神經(jīng)網(wǎng)絡(luò)

Backprop(backward propagation oferrors脚祟,誤差的反向傳播为黎,簡(jiǎn)稱(chēng)BP)算法的核心思想是:通過(guò)前向通路(箭頭的方向)得到誤差铭乾,再把該誤差反向傳播實(shí)現(xiàn)權(quán)值w的修正。

目標(biāo)值t有J種可能的值娃循,即t={t1, t2,…,tJ}炕檩,設(shè)樣本x經(jīng)過(guò)前向通路得到的最終輸出為y={y1L,y2L,…,yJL },則該樣本的平方誤差為:

之所以式中的平方誤差函數(shù)要除以2捌斧,是為了便于后面的求導(dǎo)運(yùn)算笛质,因?yàn)樗⒉挥绊懻`差的變化趨勢(shì)。
顯然捞蚂,為了減小E妇押,每層神經(jīng)元的輸出又由上層的所有神經(jīng)元的輸出經(jīng)加權(quán)激活后得到,因此可以說(shuō)誤差E是全體權(quán)值w的函數(shù)姓迅,通過(guò)改變權(quán)值w敲霍,就可達(dá)到使誤差E最小的目的
Backprop算法是一種迭代的方法俊马,也就是我們不必通過(guò)一次改變權(quán)值w來(lái)達(dá)到使E最小的目的,我們只需漸進(jìn)的減小E即可肩杈,誤差越大柴我,那么權(quán)值的變化就也越大,而當(dāng)權(quán)值改變時(shí)扩然,誤差就要重新計(jì)算艘儒。這樣兩者相互作用不斷迭代,直到誤差小于某個(gè)值(即收斂)為止与学。該方法就是我們常用的梯度下降法彤悔。

誤差E對(duì)權(quán)值w的導(dǎo)數(shù)為w的變化率嘉抓,即:


式中索守,η表示學(xué)習(xí)效率,它的取值在0和1之間抑片,它起到控制收斂速度和準(zhǔn)確性的作用卵佛。如果η過(guò)大,導(dǎo)致振蕩敞斋,則很難收斂截汪,如果η過(guò)小,則需要更長(zhǎng)的時(shí)間收斂植捎。為了改變因η選取的不好而帶來(lái)的問(wèn)題衙解,又引入了被稱(chēng)為“動(dòng)量(momentum)”的參數(shù)μ,則w的變化率改寫(xiě)為:


式1

在反向傳播的過(guò)程中焰枢,所有權(quán)值都經(jīng)過(guò)了上述計(jì)算后蚓峦,就得了更新后的所有權(quán)值。用新得到的權(quán)值計(jì)算下一個(gè)樣本济锄,因?yàn)闃颖臼且粋€(gè)一個(gè)的進(jìn)入MLP暑椰,每完成一個(gè)樣本的計(jì)算就更新一次權(quán)值。為了增加魯棒性荐绝,在每次迭代之前一汽,可以把全體樣本打亂順序,這樣每次迭代的過(guò)程中提取樣本的順序就會(huì)不相同低滩。

首先要解決的問(wèn)題是初始化權(quán)值召夹,即第一次權(quán)值如何選擇。一般的做法是隨機(jī)選擇很小的值作為初始權(quán)值恕沫,但這樣做收斂較慢监憎。比較好的方法是采用Nguyen-Widrow算法初始化權(quán)值。它的基本思想是每個(gè)神經(jīng)元都有屬于自己的一個(gè)區(qū)間范圍昏兆,通過(guò)初始化權(quán)值就可以限制它的區(qū)間位置枫虏,當(dāng)改變權(quán)值時(shí)妇穴,該神經(jīng)元也只是在自己的區(qū)間范圍內(nèi)變化,因此該方法可以大大提高收斂速度隶债。

Nguyen-Widrow算法初始化MLP權(quán)值的方法為:對(duì)于所有連接輸出層的權(quán)值和偏移量腾它,初始值為一個(gè)在正負(fù)1之間的隨機(jī)數(shù)。對(duì)于中間層的權(quán)值死讹,初始為:

其中瞒滴,vh是一個(gè)在正負(fù)1之間的隨機(jī)數(shù),H為第l-1層神經(jīng)元的數(shù)量赞警。而中間層的偏移量初始為:

其中妓忍,vk是一個(gè)在正負(fù)1之間的隨機(jī)數(shù),K為第l層神經(jīng)元的數(shù)量愧旦,而G為:

RPROP

以上我們介紹了經(jīng)典的Backprop算法世剖,該算法還是有一些不足之處。首先是它的學(xué)習(xí)效率η是需要我們事先確定好笤虫;另外權(quán)值的變化是基于誤差梯度的變化率旁瘫,雖然這點(diǎn)乍一看,似乎沒(méi)有問(wèn)題琼蚯,但我們不敢保證它永遠(yuǎn)正確有效酬凳。為此Riedmiller等人提出了RPROP算法(resilient backpropagation),用以改善Backprop算法遭庶。
RPROP算法的權(quán)值變化率并不是基于誤差梯度的變化率宁仔,而是基于它的符號(hào):


式2
式3

常數(shù)η+必須大于1,常數(shù)η-必須在0和1之間
關(guān)于Δ(t)的初始值和它的變化范圍峦睡。Riedmiller等人已經(jīng)給出了Δ(0)初始化為0.1是比較正確的選擇翎苫,而Δmax(t)=50,Δmin(t)=10-6可以有效的防止溢出赐俗。

函數(shù)參數(shù)

create函數(shù):MLP模型的構(gòu)建:
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的層數(shù)拉队,buf_sz表示開(kāi)辟存儲(chǔ)權(quán)值的內(nèi)存空間的大小
    int i, l_step, l_count, buf_sz = 0;
    int *l_src, *l_dst;
 
    clear();    //清除和初始化一些全局變量
    //判斷_layer_sizes的格式、數(shù)據(jù)類(lèi)型是否正確阻逮,_layer_sizes必須是相量形式粱快,數(shù)據(jù)類(lèi)型必須為CV_32SC1,不對(duì)則報(bào)錯(cuò)
    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" );
    //調(diào)用set_activ_func函數(shù)叔扼,設(shè)置激活函數(shù)事哭,該函數(shù)在后面給出詳細(xì)介紹
    CV_CALL( set_activ_func( _activ_func, _f_param1, _f_param2 ));
    //l_count為相量_layer_sizes的維數(shù),即MLP的層數(shù)L
    l_count = _layer_sizes->rows + _layer_sizes->cols - 1;
    l_src = _layer_sizes->data.i;    //_layer_sizes的首地址指針
    //_layer_sizes元素的步長(zhǎng)
    l_step = CV_IS_MAT_CONT(_layer_sizes->type) ? 1 :
                _layer_sizes->step / sizeof(l_src[0]);
    //創(chuàng)建相量layer_sizes
    CV_CALL( layer_sizes = cvCreateMat( 1, l_count, CV_32SC1 ));
    l_dst = layer_sizes->data.i;    //layer_sizes的首地址指針
 
    max_count = 0;    //表示某層中瓜富,最多的神經(jīng)元的數(shù)量
    for( i = 0; i < l_count; i++ )    //遍歷MLP的所有層
    {
        int n = l_src[i*l_step];    //得到當(dāng)前層的神經(jīng)元的數(shù)量
        //滿(mǎn)足條件:0 < i && i < l_count-1鳍咱,說(shuō)明i為隱含層,該if語(yǔ)句的作用是与柑,如果是隱含層谤辜,則神經(jīng)元的數(shù)量一定要大于1蓄坏,如果是輸入層或輸出層,則神經(jīng)元的數(shù)量至少應(yīng)為1丑念,否則程序報(bào)錯(cuò)
        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" );
        //把當(dāng)前層的神經(jīng)元的數(shù)量賦值給變量layer_sizes所對(duì)應(yīng)的層
        l_dst[i] = n;
        //記錄下MLP層中數(shù)量最多的神經(jīng)元的數(shù)量
        max_count = MAX( max_count, n );
        //統(tǒng)計(jì)該MLP一共有多少個(gè)權(quán)值涡戳,其中也包括偏移量
        if( i > 0 )
            buf_sz += (l_dst[i-1]+1)*n;
    }
    // l_dst[0]表示輸入層神經(jīng)元的數(shù)量,l_dst[l_count-1]表示輸出層神經(jīng)元的數(shù)量
    buf_sz += (l_dst[0] + l_dst[l_count-1]*2)*2;
    //創(chuàng)建相量wbuf脯倚,用于存儲(chǔ)權(quán)值
    CV_CALL( wbuf = cvCreateMat( 1, buf_sz, CV_64F ));
    //為weights開(kāi)辟內(nèi)存空間
    CV_CALL( weights = (double**)cvAlloc( (l_count+2)*sizeof(weights[0]) ));
    //weights[0]指向wbuf的首地址渔彰,它表示輸入層規(guī)范化所用的系數(shù)
    weights[0] = wbuf->data.db;
    //定義weights[1]首地址
    weights[1] = weights[0] + l_dst[0]*2;
    // weights[1]至weights[l_count]表示MLP相應(yīng)層的所有權(quán)值,包括偏移量(即公式中的+ 1)推正,它存放在數(shù)組的最后一個(gè)位置上
    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]都表示輸出層規(guī)范化所用到的系數(shù)恍涂,訓(xùn)練時(shí)用的是weights[l_count+1]內(nèi)的值,預(yù)測(cè)時(shí)用的是weights[l_count]內(nèi)的值
    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__;
    //判斷激活函數(shù)是否為線性植榕、對(duì)稱(chēng)SIGMOR再沧、或高斯中的一種
    if( _activ_func < 0 || _activ_func > GAUSSIAN )
        CV_ERROR( CV_StsOutOfRange, "Unknown activation function" );
 
    activ_func = _activ_func;    //賦值
    //根據(jù)不同的激活函數(shù)類(lèi)型,賦予不同的參數(shù)
    switch( activ_func )
    {
    case SIGMOID_SYM:    //對(duì)稱(chēng)SIGMOID激活函數(shù)
        max_val = 0.95; min_val = -max_val;
        max_val1 = 0.98; min_val1 = -max_val1;
        //如果用戶(hù)定義的對(duì)稱(chēng)SIGMOID激活函數(shù)的參數(shù)過(guò)小内贮,則重新賦值
        if( fabs(_f_param1) < FLT_EPSILON )
            _f_param1 = 2./3;
        if( fabs(_f_param2) < FLT_EPSILON )
            _f_param2 = 1.7159;
        break;
    case GAUSSIAN:    //高斯激活函數(shù)
        max_val = 1.; min_val = 0.05;
        max_val1 = 1.; min_val1 = 0.02;
        //如果用戶(hù)定義的高斯激活函數(shù)的參數(shù)過(guò)小产园,則重新賦值
        if( fabs(_f_param1) < FLT_EPSILON )
            _f_param1 = 1.;
        if( fabs(_f_param2) < FLT_EPSILON )
            _f_param2 = 1.;
        break;
    default:    //線性激活函數(shù)
        min_val = max_val = min_val1 = max_val1 = 0.;
        _f_param1 = 1.;
        _f_param2 = 0.;
    }
 
    f_param1 = _f_param1;    //賦值α
    f_param2 = _f_param2;    //賦值β
 
    __END__;
}

總結(jié)來(lái)說(shuō):

Sigmoid函數(shù):

對(duì)稱(chēng)Sigmoid函數(shù):

高斯函數(shù):

α和β均為函數(shù)的系數(shù)汞斧。在系統(tǒng)進(jìn)行構(gòu)建的時(shí)候夜郁,不但需要指定激勵(lì)函數(shù)的類(lèi)型,還要在需要使用參數(shù)的函數(shù)中初始化參數(shù)

 CV_WRAP virtual void create( const cv::Mat& layerSizes,
                        int activateFunc=CvANN_MLP::SIGMOID_SYM,
                        double fparam1=0, double fparam2=0 );
第一個(gè)參數(shù)是輸入層粘勒,隱藏層竞端,輸出層,的感知器的個(gè)數(shù)信息
第二個(gè)參數(shù)是激勵(lì)函數(shù)的類(lèi)型
第三個(gè)和第四個(gè)參數(shù)分別數(shù)系數(shù)α和β的值庙睡,默認(rèn)為0事富,也可以自行設(shè)置參數(shù),但是如果參數(shù)過(guò)小會(huì)被重新賦值
CvANN_MLP_TrainParams的初始化:訓(xùn)練參數(shù)的確定
CvANN_MLP_TrainParams::CvANN_MLP_TrainParams()
{
    //表示訓(xùn)練迭代的終止條件乘陪,默認(rèn)為迭代次數(shù)(大于1000)和權(quán)值變化率(小于0.01)
    term_crit = cvTermCriteria( CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 1000, 0.01 );
    //具體應(yīng)用的MLP算法统台,默認(rèn)為RPROP
    train_method = RPROP;
    // bp_dw_scale為式13中的η,bp_moment_scale為式13中的μ
    bp_dw_scale = bp_moment_scale = 0.1;
    //RPROP算法所需的參數(shù)(式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;    //輸入?yún)?shù)_param1表示Δ(0)
        if( rp_dw0 < FLT_EPSILON )    //Δ(0)不能太小
            rp_dw0 = 1.;
        rp_dw_min = _param2;    //輸入?yún)?shù)_param2表示Δmin(t)
        rp_dw_min = MAX( rp_dw_min, 0 );    //Δmin(t)不能小于0
    }
    else if( train_method == BACKPROP )    //BACKPROP算法
    {
        bp_dw_scale = _param1;    //輸入?yún)?shù)_param1表示η
        //確保η在一個(gè)合理的范圍內(nèi)
        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;    //輸入?yún)?shù)_param2表示μ
        //確保μ在一個(gè)合理的范圍內(nèi)
        if( bp_moment_scale < 0 )
            bp_moment_scale = 0.1;
        bp_moment_scale = MIN( bp_moment_scale, 1 );
    }
    //如果輸入?yún)?shù)_train_method為除了RPROP和BACKPROP以外的值,則程序給出的算法為RPROP
    else
        train_method = RPROP;
}

//關(guān)于CvTermCriteria 的構(gòu)造函數(shù):
#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---迭代算法終止條件的類(lèi)型流部,是下面之一:  
            //【1】CV_TERMCRIT_ITER---在完成最大的迭代次數(shù)之后,停止算法  
            //【2】CV_TERMCRIT_EPS----當(dāng)算法的精確度小于參數(shù)double epsilon指定的精確度時(shí)戚绕,停止算法  
            //【3】CV_TERMCRIT_ITER+CV_TERMCRIT_EPS--無(wú)論是哪一個(gè)條件先達(dá)到,都停止算法  
    int    type;  /* may be combination of 
                     CV_TERMCRIT_ITER 
                     CV_TERMCRIT_EPS */  
    //【2】Maximum number of iterations  
    //【2】最大的迭代次數(shù)  
    int    max_iter;  
    //【3】Required accuracy  
    //【3】所要求的精確度  
    double epsilon;  
}  

總結(jié)來(lái)講,train函數(shù)的使用首先需要包含兩個(gè)參數(shù):
CvTermCriteria類(lèi)型的term_cri 和int類(lèi)型的train_method枝冀,第一個(gè)表示訓(xùn)練迭代的停止條件舞丛,第二個(gè)則表示使用的訓(xùn)練方法耘子。

如果使用的是 RPROP:

可以根據(jù)上面所提到的計(jì)算公式式2式3得到,我們需要指明參數(shù)值Δ(0)球切、η+拴还、η-、Δmin(t)欧聘、Δmax(t)片林,而這五個(gè)參數(shù)分別對(duì)應(yīng)的是:
Δ(0): rp_dw0(一般情況下是0.1)
η+ rp_dw_plus (必須大于1)
η-: rp_dw_minus (必須在0和1之間)
Δmin(t): rp_dw_min (一般設(shè)為:10^-6 /FLT_EPSILON,不能小于0)
Δmax(t) :rp_dw_max(Riedmiller的結(jié)論值50)

如果使用的是 :BACKPROP

只需要指明兩個(gè)參數(shù)即可:
bp_dw_scale怀骤, bp_moment_scale ;分別代表式1中的η和μ费封。用來(lái)計(jì)算權(quán)值的變化率。其取值都應(yīng)當(dāng)盡量合理且大于0蒋伦。

訓(xùn)練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的輸入數(shù)據(jù)弓摘,即待訓(xùn)練的樣本數(shù)據(jù)
//_outputs表示MLP的輸出數(shù)據(jù),即待訓(xùn)練的樣本響應(yīng)值或分類(lèi)標(biāo)簽
//_sample_weights表示事先定義好的樣本的權(quán)值
//_sample_idx表示真正被用于訓(xùn)練的樣本數(shù)據(jù)的索引痕届,即有一部分樣本不用于訓(xùn)練MLP
//_params表示MLP模型參數(shù)
//flags表示控制算法的參數(shù)韧献,可以為UPDATE_WEIGHTS、NO_INPUT_SCALE和NO_OUTPUT_SCALE研叫,以及它們的組合锤窑,這些變量的含義為:UPDATE_WEIGHTS表示算法需要更新網(wǎng)絡(luò)的權(quán)值;NO_INPUT_SCALE表示算法無(wú)需規(guī)范化MLP的輸入數(shù)據(jù)嚷炉,規(guī)范化的意思就是使輸入樣本的特征屬性均值為0渊啰,標(biāo)準(zhǔn)方差為1;NO_OUTPUT_SCALE表示算法無(wú)需歸一化MLP的輸出數(shù)據(jù)
//該函數(shù)返回迭代次數(shù)
{
    const int MAX_ITER = 1000;    //表示最大迭代次數(shù)
    const double DEFAULT_EPSILON = FLT_EPSILON;    //定義一個(gè)常數(shù)
 
    double* sw = 0;    //表示樣本的權(quán)值
    CvVectors x0, u;    //分別表示MLP的輸入數(shù)據(jù)和輸出數(shù)據(jù)
    int iter = -1;    //表示訓(xùn)練MLP所需的迭代次數(shù)
    //初始化首地址指針
    x0.data.ptr = u.data.ptr = 0;
 
    CV_FUNCNAME( "CvANN_MLP::train" );
 
    __BEGIN__;
 
    int max_iter;
    double epsilon;
 
    params = _params;    //MLP模型參數(shù)
 
    // initialize training data
    //調(diào)用prepare_to_train函數(shù)申屹,為MLP模型的訓(xùn)練準(zhǔn)備參數(shù)绘证,該函數(shù)在后面給出詳細(xì)的介紹
    CV_CALL( prepare_to_train( _inputs, _outputs, _sample_weights,
                               _sample_idx, &x0, &u, &sw, flags ));
 
    // ... and link weights
    //如果沒(méi)有定義UPDATE_WEIGHTS,則需要調(diào)用init_weights函數(shù)進(jìn)行權(quán)值的初始化哗讥,該函數(shù)在后面給出了詳細(xì)的介紹
    if( !(flags & UPDATE_WEIGHTS) )
        init_weights();
    //得到最大迭代次數(shù)
    max_iter = params.term_crit.type & CV_TERMCRIT_ITER ? params.term_crit.max_iter : MAX_ITER;
    max_iter = MAX( max_iter, 1 );    //最大迭代次數(shù)必須大于等于1
    //得到用前后兩次誤差之差來(lái)判斷終止迭代的系數(shù)
    epsilon = params.term_crit.type & CV_TERMCRIT_EPS ? params.term_crit.epsilon : DEFAULT_EPSILON;
    epsilon = MAX(epsilon, DBL_EPSILON);
    //重新定義終止MLP訓(xùn)練的條件
    params.term_crit.type = CV_TERMCRIT_ITER + CV_TERMCRIT_EPS;
    params.term_crit.max_iter = max_iter;
    params.term_crit.epsilon = epsilon;
    //如果是BACKPROP算法嚷那,則調(diào)用train_backprop函數(shù),如果是RPROP算法杆煞,則調(diào)用train_rprop函數(shù)魏宽,這個(gè)兩個(gè)函數(shù)在后面有詳細(xì)的介紹
    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__;
    //釋放內(nèi)存空間
    cvFree( &x0.data.ptr );
    cvFree( &u.data.ptr );
    cvFree( &sw );
 
    return iter;    //返回迭代次數(shù)
}
//為訓(xùn)練MLP模型準(zhǔn)備參數(shù):
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;    //該函數(shù)正確返回的標(biāo)識(shí)
    CvMat* sample_idx = 0;    //表示樣本數(shù)據(jù)的索引
    CvVectors ivecs, ovecs;    //分別表示輸入層和輸出層的數(shù)據(jù)
    double* sw = 0;    //表示樣本的權(quán)值
    int count = 0;
 
    CV_FUNCNAME( "CvANN_MLP::prepare_to_train" );
 
    ivecs.data.ptr = ovecs.data.ptr = 0;    //初始化為0
    assert( _ivecs && _ovecs );    //確保輸入?yún)?shù)_ivecs和_ovecs有效
 
    __BEGIN__;
 
    const int* sidx = 0;    //該指針指向sample_idx
    // sw_type和sw_count分別表示樣本權(quán)值數(shù)據(jù)的類(lèi)型和數(shù)量
    int i, sw_type = 0, sw_count = 0;
    int sw_step = 0;    //示樣本權(quán)值數(shù)據(jù)的步長(zhǎng)
    double sw_sum = 0;    //表示樣本權(quán)值的累加和
    //通過(guò)判斷l(xiāng)ayer_sizes是否被正確賦值,來(lái)確保MLP模型是否被構(gòu)建好
    if( !layer_sizes )
        CV_ERROR( CV_StsError,
        "The network has not been created. Use method create or the appropriate constructor" );
    //判斷輸入?yún)?shù)_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" );
    //判斷輸入?yún)?shù)_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" );
    //確保樣本的輸入和輸出的數(shù)量一致索绪,即每個(gè)樣本都必須有一個(gè)響應(yīng)值或分類(lèi)標(biāo)簽
    if( _inputs->rows != _outputs->rows )
        CV_ERROR( CV_StsUnmatchedSizes, "The numbers of input and output samples do not match" );
    //如果定義了_sample_idx湖员,則需要掩碼一些樣本數(shù)據(jù)
    if( _sample_idx )
    {
        //得到真正用于訓(xùn)練的樣本
        CV_CALL( sample_idx = cvPreprocessIndexArray( _sample_idx, _inputs->rows ));
        sidx = sample_idx->data.i;    //指針賦值
        count = sample_idx->cols + sample_idx->rows - 1;    //得到訓(xùn)練樣本的數(shù)量
    }
    else
        count = _inputs->rows;    //得到訓(xùn)練樣本的數(shù)量
 
    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);    //數(shù)據(jù)類(lèi)型
        sw_count = _sample_weights->cols + _sample_weights->rows - 1;    //數(shù)量
        //判斷sw_type格式是否正確,sw_count是否與樣本的數(shù)量一致
        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);    //得到步長(zhǎng)
 
        CV_CALL( sw = (double*)cvAlloc( count*sizeof(sw[0]) ));    //為sw分配空間
    }
    //為MLP的輸入和輸出數(shù)據(jù)開(kāi)辟一塊內(nèi)存空間
    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);    //指定類(lèi)型
    ovecs.type = CV_MAT_TYPE(_outputs->type);    //指定類(lèi)型
    ivecs.count = ovecs.count = count;    //相量的長(zhǎng)度瑞驱,即維數(shù)
 
    for( i = 0; i < count; i++ )    //遍歷所有的待訓(xùn)練樣本數(shù)據(jù)
    {
        int idx = sidx ? sidx[i] : i;    //表示樣本索引值
        //給MLP的輸入和輸出數(shù)據(jù)賦值
        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 ?    //得到當(dāng)前樣本的權(quán)值
                (double)_sample_weights->data.fl[si*sw_step] :
                _sample_weights->data.db[si*sw_step];
            sw[i] = w;    //賦值
            if( w < 0 )    //權(quán)值不能小于0
                CV_ERROR( CV_StsOutOfRange, "some of sample weights are negative" );
            sw_sum += w;    //權(quán)值累加
        }
    }
 
    // normalize weights
    if( sw )    //如果sw被定義娘摔,歸一化樣本權(quán)值
    {
        sw_sum = sw_sum > DBL_EPSILON ? 1./sw_sum : 0;    //倒數(shù)
        for( i = 0; i < count; i++ )
            sw[i] *= sw_sum;    //歸一化
    }
    //調(diào)用calc_input_scale和calc_output_scale函數(shù),依據(jù)_flags分別對(duì)輸入數(shù)據(jù)(樣本值)和輸出數(shù)據(jù)(樣本響應(yīng)值)進(jìn)行規(guī)范化處理唤反,這兩個(gè)函數(shù)在后面給出詳細(xì)的介紹
    calc_input_scale( &ivecs, _flags );
    CV_CALL( calc_output_scale( &ovecs, _flags ));
 
    ok = true;    //標(biāo)識(shí)函數(shù)返回值
 
    __END__;
 
    if( !ok )    //沒(méi)有正確對(duì)訓(xùn)練過(guò)程進(jìn)行預(yù)處理凳寺,則清空一些變量
    {
        cvFree( &ivecs.data.ptr );
        cvFree( &ovecs.data.ptr );
        cvFree( &sw );
    }
 
    cvReleaseMat( &sample_idx );    //釋放空間
    *_ivecs = ivecs;    //賦值
    *_ovecs = ovecs;    //賦值
    *_sw = sw;    //賦值
 
    return ok;
}

在這里我們只需要拿出最關(guān)鍵的信息即可:

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的輸入數(shù)據(jù)鸭津,即待訓(xùn)練的樣本數(shù)據(jù)
//_outputs表示MLP的輸出數(shù)據(jù),即待訓(xùn)練的樣本響應(yīng)值或分類(lèi)標(biāo)簽
//_sample_weights表示事先定義好的樣本的權(quán)值
//_sample_idx表示真正被用于訓(xùn)練的樣本數(shù)據(jù)的索引肠缨,即有一部分樣本不用于訓(xùn)練MLP
//_params表示MLP模型參數(shù)
//flags表示控制算法的參數(shù)逆趋,可以為UPDATE_WEIGHTS、NO_INPUT_SCALE和NO_OUTPUT_SCALE晒奕,以及它們的組合闻书,這些變量的含義為:UPDATE_WEIGHTS表示算法需要更新網(wǎng)絡(luò)的權(quán)值;NO_INPUT_SCALE表示算法無(wú)需規(guī)范化MLP的輸入數(shù)據(jù)脑慧,規(guī)范化的意思就是使輸入樣本的特征屬性均值為0魄眉,標(biāo)準(zhǔn)方差為1;NO_OUTPUT_SCALE表示算法無(wú)需歸一化MLP的輸出數(shù)據(jù)
//該函數(shù)返回迭代次數(shù)

在進(jìn)行訓(xùn)練的時(shí)候闷袒,
第一個(gè)表示樣本數(shù)組:格式為樣本個(gè)數(shù)每個(gè)樣本的輸入的數(shù)據(jù)個(gè)數(shù)坑律,數(shù)據(jù)類(lèi)型格式是CV_32FC1
第二個(gè)表示樣本標(biāo)記數(shù)組:格式為樣本的個(gè)數(shù)
每個(gè)樣本的結(jié)果可能性個(gè)數(shù)/輸出的數(shù)據(jù)個(gè)數(shù),數(shù)據(jù)類(lèi)型格式是CV_32FC1
第三個(gè)表示樣本權(quán)值:格式為單行或單列數(shù)組囊骤,通常是單行晃择,列數(shù)為(真正需要訓(xùn)練的)輸入樣本的樣本個(gè)數(shù),數(shù)據(jù)類(lèi)型格式為CV_32FC1或者CV_64FC1(這個(gè)參數(shù)通常在使用RPROP訓(xùn)練方法時(shí)使用)
第四個(gè)表示真正被用于訓(xùn)練的樣本數(shù)據(jù)的索引也物,其格式通常是單行宫屠,列數(shù)是樣本個(gè)數(shù),數(shù)據(jù)類(lèi)型格式是 CV_8UC1焦除, CV_8UC3激况,通常情況下每個(gè)輸入的樣本都需要被訓(xùn)練,所以這里一般不需設(shè)置膘魄。
第五個(gè)表示上面構(gòu)造好的訓(xùn)練參數(shù)
最后一個(gè)表示控制算法的參數(shù),一般不進(jìn)行設(shè)置竭讳。

使用模型:進(jìn)行數(shù)據(jù)預(yù)測(cè)
CV_WRAP virtual float predict( const cv::Mat& inputs, CV_OUT cv::Mat& outputs ) const;
//inputs创葡,一個(gè)測(cè)試用例,單行绢慢,列數(shù)為一個(gè)輸入樣本的輸入數(shù)據(jù)個(gè)數(shù)灿渴,數(shù)據(jù)類(lèi)型格式為CV_32FC1
//outputs,一個(gè)輸出樣本胰舆,單行骚露,列數(shù)為一個(gè)檢測(cè)結(jié)果的可能數(shù)目,其內(nèi)容是每種可能的擬合率

圖像進(jìn)行特征提取缚窿,把它保存在inputs里棘幸,通過(guò)調(diào)用predict函數(shù),我們得到一個(gè)輸出向量倦零,它是一個(gè)1*nClass的行向量误续,
其中每一列說(shuō)明它與該類(lèi)的相似程度(0-1之間)吨悍,也可以說(shuō)是置信度。我們只用對(duì)output求一個(gè)最大值蹋嵌,就可得到結(jié)果育瓜。
這個(gè)函數(shù)的返回值是一個(gè)無(wú)用的float值,可以忽略栽烂。
由于該結(jié)果是單行的躏仇,通常在得到后尋找最大比率的列數(shù),也就是相對(duì)應(yīng)的得到的結(jié)果最可能的下標(biāo)數(shù)(應(yīng)與樣本標(biāo)記數(shù)組的相對(duì)應(yīng))腺办,查找最大值方法:

        Point maxLoc;
        double maxVal;
//minMaxLoc函數(shù)的參數(shù)為:
//1.輸入的源圖像
//2.原圖像中最小的值
//3.原圖像中最大的值
//4.原圖像中最小的值所在坐標(biāo)
//5.原圖像中最大的值所在坐標(biāo)
        minMaxLoc(output, 0, &maxVal, 0, &maxLoc);//找到最大的相似度
        //maxVal是最大的相似度钙态,maxLoc是該像素的坐標(biāo),由于是行向量菇晃,所以y均為1册倒,取的x即可
         int result = maxLoc.x;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市磺送,隨后出現(xiàn)的幾起案子驻子,更是在濱河造成了極大的恐慌,老刑警劉巖估灿,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崇呵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡馅袁,警方通過(guò)查閱死者的電腦和手機(jī)域慷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汗销,“玉大人犹褒,你說(shuō)我怎么就攤上這事〕谡耄” “怎么了叠骑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)削茁。 經(jīng)常有香客問(wèn)我宙枷,道長(zhǎng),這世上最難降的妖魔是什么茧跋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任慰丛,我火速辦了婚禮,結(jié)果婚禮上瘾杭,老公的妹妹穿的比我還像新娘诅病。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布睬隶。 她就那樣靜靜地躺著锣夹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苏潜。 梳的紋絲不亂的頭發(fā)上银萍,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音恤左,去河邊找鬼贴唇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛飞袋,可吹牛的內(nèi)容都是我干的戳气。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼巧鸭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瓶您!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纲仍,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤呀袱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后郑叠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體夜赵,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年乡革,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寇僧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沸版,死狀恐怖嘁傀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情推穷,我是刑警寧澤心包,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站馒铃,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痕惋。R本人自食惡果不足惜区宇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望值戳。 院中可真熱鬧议谷,春花似錦、人聲如沸堕虹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至逼裆,卻和暖如春郁稍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胜宇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工耀怜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桐愉。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓财破,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親从诲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子左痢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容