推薦一篇非常詳細(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)元沙绝。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ě)為:
在反向傳播的過(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)值死讹,初始為: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):
常數(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ō):
α和β均為函數(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;