adaboost訓練——強分類器訓練原理

轉自:http://blog.csdn.net/lanxuecc/article/details/52712085

opencv中adaboost訓練強分類器源碼。

adaboost訓練強分類器的基本流程: 
1、初始化訓練樣本的類別與權重分佈。 
2、迭代循環訓練弱分類器。 
3、將每次循環訓練成的弱分類器與已經存在的弱分類器組成的強分類器。 
4、根據當前強分類器計算正樣本置信度,根據傳入參數minhitrate來取得強分類器閾值。 
5、用當前強分類器與上步計算得到閾值,分類負樣本,如果分類的最大檢率小於maxfalsealarm。跳出循環,強分類器訓練完成。

下面結合一般的adaboost的算法原理、opencv源碼、記錄下我對adaboost的算法流程的詳細理解: 
給定一個訓練數據集 ,其中屬於標記樣本是正樣本還是負樣本的標記集合{-1,+1},一般-1表示負樣本,+1表示正樣本。在人臉分類器的訓練中,是計算出來的正樣本人臉圖片與負樣本非人臉圖片的某個haar特徵的集合。Adaboost的目的就是從訓練數據集T中學習一系列弱分類器(特徵表述+特徵閾值),然後將這些弱分類器組合成一個強分類器(弱分類器+閾值)來儘可能準確的分類xi以達到能夠分類一個新的樣本是正樣本還是負樣本。

在opencv中函數訓練強分類器的函數是:icvCreateCARTStageClassifier。這裏要特別說明下,在opencv中爲訓練強分類器提供了四種方法分別是Discrete AdaBoost(DAB)、Real AdaBoost(RAB)、Logit Boost(LB)和Gentle AdaBoost(GAB)算法。我們通常使用的較多的都是GAB訓練算法,也是最簡單的,這裏是幾種訓練算法的區別介紹::http://www.cnblogs.com/jcchen1987/p/4581651.html

這裏就DAB算法邏輯做一個介紹::::::::::::::::::

1、首先,初始化訓練數據的權值分佈。每一個訓練樣本最開始時都被賦予相同的權值:1/N。 
這裏寫圖片描述

opencv源碼是在函數cvBooWstStartTraining中對訓練的樣本數據集進行權重的初始化,以及標識每個樣本來初始化數據集

2、進入循環,進行多輪迭代,把每次迭代記做m

A、通過函數 cvTrimWeights來剔除小權值的樣本:對實際存在的樣本按權重的大小排序,找到權重高於總權重的weightfraction倍的樣本保留下來,用來訓練接下來的弱分類器。

B、使用前面一步保留下來的權值分佈爲Dm樣本集學習訓練,得到一個弱分類器,在函數cvCreateCARTClassifier中實現,記公式如下:

這裏寫圖片描述

具體如何訓練弱分類,見前面幾個博客筆記:cvCreateCARTClassifier

C、用上步訓練出來的弱分類器Gm(x)計算每個樣本的分類置信度(具體調用的是函數icvEvalCARTHaarClassifier這裏源碼中用的是函數指針有點難找到,具體可以看下面我貼出的代碼中的註釋),將計算出來的每個樣本的置信度傳入cvBoostNextWeakClassifier函數計算該弱分類器Gm(X)在數據集上的分類誤差em,這裏四種不同的分類器訓練方法有不同的計算方式,其中DAB的計算方法如下,在函數icvBoostNextWeakClassifierDAB中實現: 
這裏寫圖片描述

D、在上述函數icvBoostNextWeakClassifierDAB中緊接着用公式 
這裏寫圖片描述 
計算了當前弱分類器Gm(X)在當前強分類器中的重要程序,這個公式意味着分類誤差率越小的弱分類器在強分類器中的權重越高,作用越大。

E、在函數icvBoostNextWeakClassifierDAB中接着遍歷所有樣本,更新訓練樣本的數據集的權值分佈得到Dm+1用於下輪迭代來訓練弱分類器,公式如下: 
這裏寫圖片描述 
其中:Zm是一個規一化因子: 
這裏寫圖片描述

通俗的說就是:遍歷每個樣本更新其權重Wm+1,i=Wm,i*exp(…) , 同時累加每個樣本計算得到的權重Wm+1,i得到Zm,然後規一化下。通過公式可以看出當Gm(xi)分類這個樣本正確時,則計算得到的Wm+1,i比Wm,i大,反之,Wm+1,i比Wm,i小,這樣就實現了將本次分類正確的樣本權值減小,本次分類錯誤的樣本權值增大,這樣在下次訓練弱分類器裏更多的聚焦於本次被分錯的樣本。

F、從函數icvBoostNextWeakClassifierDAB返回後,得到了當前弱分類器的權重。將該弱分類器以如下公式的方式加入到當前強分類器中: 
這裏寫圖片描述

G、緊接着,在函數icvCreateCARTStageClassifier中分開處理正負樣本,遍歷所有正樣本,用當前強分類器中已經訓練出來的弱分類器來計算每個正樣本的置信度,累加得到置信度累加和,將這些每個樣本的置信度累加和排序,根據minhitrate來計算當前強分類器的閾值threshold。

H、再遍歷所有負樣本,用當前強分類器中已經訓練出來的弱分類器來計算每個負樣本的置信度累加和,再用前面計算得到的threshold來判斷每個負樣本的類別,統計負樣本的分類總數,得到負樣本的誤檢率falsealarm,如果誤檢率小於輸入的參數maxfalsealarm,則跳出迭代循環,當前強分類器訓練完成!!!!

下面上源碼和註釋(結合代碼與原理更容易理解)::::

static
CvIntHaarClassifier* icvCreateCARTStageClassifier( CvHaarTrainingData* data,   // 全部訓練樣本
                                                   CvMat* sampleIdx,       // 實際訓練樣本序列
                                                   CvIntHaarFeatures* haarFeatures,// 全部HAAR特徵
                                                   float minhitrate,// 最小正檢率(用於確定強分類器閾值)
                                                   float maxfalsealarm,// 最大誤檢率(用於確定是否收斂)
                                                   int   symmetric,// HAAR是否對稱 
                                                   float weightfraction,// 樣本剔除比例(用於剔除小權值樣本)
                                                   int numsplits,// 每個弱分類器特徵個數(一般爲1) 
                                                   CvBoostType boosttype,// adaboost類型 
                                                   CvStumpError stumperror,// Discrete AdaBoost中的閾值計算方式
                                                   int maxsplits ) // 弱分類器最大個數  
{

#ifdef CV_COL_ARRANGEMENT
    int flags = CV_COL_SAMPLE;
#else
    int flags = CV_ROW_SAMPLE;
#endif

    CvStageHaarClassifier* stage = NULL;// 強分類器
    CvBoostTrainer* trainer;// 臨時訓練器,用於更新樣本權值
    CvCARTClassifier* cart = NULL;// 弱分類器
    CvCARTTrainParams trainParams;// 訓練參數 
    CvMTStumpTrainParams stumpTrainParams;// 弱分類器參數
    //CvMat* trainData = NULL;
    //CvMat* sortedIdx = NULL;
    CvMat eval;// 臨時矩陣
    int n = 0;// 特徵總數
    int m = 0;// 總樣本個數 
    int numpos = 0;// 正樣本個數
    int numneg = 0; // 負樣本個數
    int numfalse = 0; // 誤檢樣本個數
    float sum_stage = 0.0F;// 置信度累積和
    float threshold = 0.0F;// 強分類器閾值
    float falsealarm = 0.0F; // 誤檢率

    //CvMat* sampleIdx = NULL;
    CvMat* trimmedIdx; // 剔除小權值之後的樣本序列
    //float* idxdata = NULL;
    //float* tempweights = NULL;
    //int    idxcount = 0;
    CvUserdata userdata;// 訓練數據

    int i = 0;
    int j = 0;
    int idx;
    int numsamples;// 實際樣本個數
    int numtrimmed;// 剔除小權值之後的樣本個數

    CvCARTHaarClassifier* classifier;
    CvSeq* seq = NULL;
    CvMemStorage* storage = NULL;
    CvMat* weakTrainVals;
    float alpha;
    float sumalpha;
    int num_splits; /* total number of splits in all weak classifiers */

#ifdef CV_VERBOSE
    printf( "+----+----+-+---------+---------+---------+---------+\n" );
    printf( "|  N |%%SMP|F|  ST.THR |    HR   |    FA   | EXP. ERR|\n" );
    printf( "+----+----+-+---------+---------+---------+---------+\n" );
#endif /* CV_VERBOSE */

    n = haarFeatures->count;
    m = data->sum.rows;
    numsamples = (sampleIdx) ? MAX( sampleIdx->rows, sampleIdx->cols ) : m;

    //樣本與HAAR特徵  
    userdata = cvUserdata( data, haarFeatures );

    /* 弱分類參數設置 */  
    stumpTrainParams.type = ( boosttype == CV_DABCLASS )
        ? CV_CLASSIFICATION_CLASS : CV_REGRESSION;  // 分類或者回歸
    stumpTrainParams.error = ( boosttype == CV_LBCLASS || boosttype == CV_GABCLASS )
        ? CV_SQUARE : stumperror;// 弱分類器閾值計算方式 
    stumpTrainParams.portion = CV_STUMP_TRAIN_PORTION;// 每組特徵個數  
    stumpTrainParams.getTrainData = icvGetTrainingDataCallback;// 計算樣本的haar值,函數指針
    stumpTrainParams.numcomp = n; // 特徵個數
    stumpTrainParams.userdata = &userdata;
    stumpTrainParams.sortedIdx = data->idxcache;// 特徵-樣本序號矩陣(排序之後)  

    // 由於參數衆多,所以創建參數結構體 
    trainParams.count = numsplits;// 弱分類器特徵樹
    trainParams.stumpTrainParams = (CvClassifierTrainParams*) &stumpTrainParams;// 弱分類參數
    trainParams.stumpConstructor = cvCreateMTStumpClassifier;// 篩選最優弱分類器,函數指針
    trainParams.splitIdx = icvSplitIndicesCallback; // CART節點分裂函數
    trainParams.userdata = &userdata;

    //臨時向量,用於存放樣本haar特徵值  
    eval = cvMat( 1, m, CV_32FC1, cvAlloc( sizeof( float ) * m ) );

    storage = cvCreateMemStorage();
    //最優弱分類器存儲序列  
    seq = cvCreateSeq( 0, sizeof( *seq ), sizeof( classifier ), storage );
  
    // 樣本類別,只有logitboost纔會用到  
    weakTrainVals = cvCreateMat( 1, m, CV_32FC1 );
    // 初始化樣本類別與權重,weakTrainVals爲{-1, 1},權重都一樣,LB的權重初值與其他不一樣
    trainer = cvBoostStartTraining( &data->cls, weakTrainVals, &data->weights,
                                    sampleIdx, boosttype );
    num_splits = 0;
    sumalpha = 0.0F;
    do   /*這裏每次循環創建一個弱分類器,一個弱分類器可以兇括幾個特徵,但是一般只有一個特徵*/
    {     

        #ifdef CV_VERBOSE
        int v_wt = 0;
        int v_flipped = 0;
        #endif /* CV_VERBOSE */

        //剔除小權值樣本 
        trimmedIdx = cvTrimWeights( &data->weights, sampleIdx, weightfraction );
        numtrimmed = (trimmedIdx) ? MAX( trimmedIdx->rows, trimmedIdx->cols ) : m; // 實際樣本總數 

        #ifdef CV_VERBOSE
        v_wt = 100 * numtrimmed / numsamples;
        v_flipped = 0;
        #endif /* CV_VERBOSE */

        //重要函數,創建CART樹的同時,當前最優弱分類器出爐,一般只有根節點~因爲一般一個弱分類器只有一個特徵由numsplits決定
        cart = (CvCARTClassifier*) cvCreateCARTClassifier( data->valcache,
                                                           flags,
                                                           weakTrainVals, 
                                                           0, 
                                                           0, 
                                                           0, 
                                                           trimmedIdx,
                                                           &(data->weights),
                                                           (CvClassifierTrainParams*) &trainParams );

        //創建弱分類器,按分類器的結構體分配一個內存   這個函數指定了eval/save/release三個變量調用的實際函數
        classifier = (CvCARTHaarClassifier*) icvCreateCARTHaarClassifier( numsplits );
        // 將CART樹轉化爲弱分類器  
        icvInitCARTHaarClassifier( classifier, cart, haarFeatures );

        num_splits += classifier->count;

        /*這裏又把釋放了*/
        cart->release( (CvClassifier**) &cart );

        /*必需要在對稱的前提下*/
        if( symmetric && (seq->total % 2) )
        {
            float normfactor = 0.0F;
            CvStumpClassifier* stump;

            /* flip haar features */
            for( i = 0; i < classifier->count; i++ )
            {
                if( classifier->feature[i].desc[0] == 'h' )
                {
                    for( j = 0; j < CV_HAAR_FEATURE_MAX &&
                                    classifier->feature[i].rect[j].weight != 0.0F; j++ )
                    {
                        classifier->feature[i].rect[j].r.x = data->winsize.width - 
                            classifier->feature[i].rect[j].r.x -
                            classifier->feature[i].rect[j].r.width;                
                    }
                }
                else
                {
                    int tmp = 0;

                    /* (x,y) -> (24-x,y) */
                    /* w -> h; h -> w    */
                    for( j = 0; j < CV_HAAR_FEATURE_MAX &&
                                    classifier->feature[i].rect[j].weight != 0.0F; j++ )
                    {
                        classifier->feature[i].rect[j].r.x = data->winsize.width - 
                            classifier->feature[i].rect[j].r.x;
                        CV_SWAP( classifier->feature[i].rect[j].r.width,
                                 classifier->feature[i].rect[j].r.height, tmp );
                    }
                }
            }
            icvConvertToFastHaarFeature( classifier->feature,
                                         classifier->fastfeature,
                                         classifier->count, data->winsize.width + 1 );

            stumpTrainParams.getTrainData = NULL;
            stumpTrainParams.numcomp = 1;
            stumpTrainParams.userdata = NULL;
            stumpTrainParams.sortedIdx = NULL;

            for( i = 0; i < classifier->count; i++ )   /*按特徵模版循環*/
            {
                for( j = 0; j < numtrimmed; j++ )  /*計算每個樣本在這個特徵模版下的具體特徵值*/
                {
                    idx = icvGetIdxAt( trimmedIdx, j );

                    eval.data.fl[idx] = cvEvalFastHaarFeature( &classifier->fastfeature[i],
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step) );
                    normfactor = data->normfactor.data.fl[idx];  /*縮放因子*/
                    eval.data.fl[idx] = ( normfactor == 0.0F )
                        ? 0.0F : (eval.data.fl[idx] / normfactor);
                }
                                                         /*cvCreateMTStumpClassifier*/
                stump = (CvStumpClassifier*) trainParams.stumpConstructor( &eval,
                    CV_COL_SAMPLE,
                    weakTrainVals, 0, 0, 0, trimmedIdx,
                    &(data->weights),
                    trainParams.stumpTrainParams );

                classifier->threshold[i] = stump->threshold;
                if( classifier->left[i] <= 0 )
                {
                    classifier->val[-classifier->left[i]] = stump->left;
                }
                if( classifier->right[i] <= 0 )
                {
                    classifier->val[-classifier->right[i]] = stump->right;
                }

                stump->release( (CvClassifier**) &stump );        

            }

            stumpTrainParams.getTrainData = icvGetTrainingDataCallback;
            stumpTrainParams.numcomp = n;
            stumpTrainParams.userdata = &userdata;
            stumpTrainParams.sortedIdx = data->idxcache;

            #ifdef CV_VERBOSE
            v_flipped = 1;
            #endif /* CV_VERBOSE */

        } /* if symmetric */

        if( trimmedIdx != sampleIdx )
        {
            cvReleaseMat( &trimmedIdx );
            trimmedIdx = NULL;
        }

        //調用icvEvalCARTHaarClassifier函數,計算每個樣本的當前最優弱分類器置信度
        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            eval.data.fl[idx] = classifier->eval( (CvIntHaarClassifier*) classifier,  /*這是本級強分類器的弱分類器列表,每次新得到一個弱分類器都加進來 */
                (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                data->normfactor.data.fl[idx] );
        }

        //更新樣本權重,如果是LogitBoost,也會更新weakTrainVals,函數返回的是弱分類器權重 
        /*這裏四種不同的adaboost提升方法*/
        alpha = cvBoostNextWeakClassifier( &eval, &data->cls, weakTrainVals,
                                           &data->weights, trainer );
        //這個變量沒什麼用 
        sumalpha += alpha;

        for( i = 0; i <= classifier->count; i++ )
        {
            if( boosttype == CV_RABCLASS )   /*RAB額外再log一次*/
            {
                classifier->val[i] = cvLogRatio( classifier->val[i] );
            }
            classifier->val[i] *= alpha;   /*更新分類器權重DAB是依照常見公式,GAB始終是1也就是每個弱分類器的權重相同*/
        }

        //添加弱分類器,classifier是弱分類器
        cvSeqPush( seq, (void*) &classifier );

        //遍歷sampleIdx中所有樣本,計算每個樣本的弱分類器置信度和  
        numpos = 0;
        for( i = 0; i < numsamples; i++ )
        {
            // 獲得樣本序號
            idx = icvGetIdxAt( sampleIdx, i );

            // 如果樣本爲正樣本
            if( data->cls.data.fl[idx] == 1.0F )
            {
                //初始化置信度值 
                eval.data.fl[numpos] = 0.0F;

                // 遍歷seq中所有弱分類器
                for( j = 0; j < seq->total; j++ )
                {
                    // 獲取弱分類器  
                    classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));

                    // 累積當前正樣本的弱分類器置信度和  
                    //eval == CV_INT_HAAR_CLASSIFIER_FIELDS==icvEvalCARTHaarClassifier
                    eval.data.fl[numpos] += classifier->eval((CvIntHaarClassifier*) classifier,
                                                             (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                                                             (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                                                             data->normfactor.data.fl[idx] );
                }
                /* eval.data.fl[numpos] = 2.0F * eval.data.fl[numpos] - seq->total; */
                numpos++;
            }
        }

        // 對弱分類器輸出置信度和進行排序  
        icvSort_32f( eval.data.fl, numpos, 0 );

        // 計算閾值,應該是大於threshold則爲正類,小於threshold則爲負類 
        threshold = eval.data.fl[(int) ((1.0F - minhitrate) * numpos)];

        // 遍歷所有樣本,統計錯分負樣本個數  
        numneg = 0;
        numfalse = 0;
        for( i = 0; i < numsamples; i++ )
        {
            idx = icvGetIdxAt( sampleIdx, i );

            // 如果樣本爲負樣本  
            if( data->cls.data.fl[idx] == 0.0F )
            {
                 // 遍歷seq中所有弱分類器
                numneg++;
                sum_stage = 0.0F;
                for( j = 0; j < seq->total; j++ )
                {
                   classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));

                   // 累積當前負樣本的分類器輸出結果  
                   sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }

                // 因爲小於threshold爲負類,所以下面是分類錯誤的情況  
                /* sum_stage = 2.0F * sum_stage - seq->total; */
                if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
                {
                    numfalse++;
                }
            }
        }
        // 因爲小於threshold爲負類,所以下面是分類錯誤的情況  
        falsealarm = ((float) numfalse) / ((float) numneg);

        #ifdef CV_VERBOSE
        {
            // 正樣本檢出率  
            float v_hitrate    = 0.0F;
            // 負樣本誤檢率
            float v_falsealarm = 0.0F;

            /* expected error of stage classifier regardless threshold */
            float v_experr = 0.0F;

            // 遍歷所有樣本  
            for( i = 0; i < numsamples; i++ )
            {
                idx = icvGetIdxAt( sampleIdx, i );

                sum_stage = 0.0F;

                // 遍歷seq中所有弱分類器  
                for( j = 0; j < seq->total; j++ )
                {
                    classifier = *((CvCARTHaarClassifier**) cvGetSeqElem( seq, j ));
                    sum_stage += classifier->eval( (CvIntHaarClassifier*) classifier,
                        (sum_type*) (data->sum.data.ptr + idx * data->sum.step),
                        (sum_type*) (data->tilted.data.ptr + idx * data->tilted.step),
                        data->normfactor.data.fl[idx] );
                }

                /* sum_stage = 2.0F * sum_stage - seq->total; */
                // 只需要判斷單一分支即可 
                if( sum_stage >= (threshold - CV_THRESHOLD_EPS) )
                {
                    if( data->cls.data.fl[idx] == 1.0F )
                    {
                        v_hitrate += 1.0F;
                    }
                    else
                    {
                        v_falsealarm += 1.0F;
                    }
                }

                 // 正類樣本的sum_stage必須大於0
                if( ( sum_stage >= 0.0F ) != (data->cls.data.fl[idx] == 1.0F) )
                {
                    v_experr += 1.0F;
                }
            }
            v_experr /= numsamples;
            printf( "|%4d|%3d%%|%c|%9f|%9f|%9f|%9f|\n",
                seq->total, v_wt, ( (v_flipped) ? '+' : '-' ),
                threshold, v_hitrate / numpos, v_falsealarm / numneg,
                v_experr );
            printf( "+----+----+-+---------+---------+---------+---------+\n" );
            fflush( stdout );
        }
        #endif /* CV_VERBOSE */

    } while( falsealarm > maxfalsealarm && (!maxsplits || (num_splits < maxsplits) ) );

    cvBoostEndTraining( &trainer );

    if( falsealarm > maxfalsealarm )
    {
        stage = NULL;
    }
    else
    {
        /*這裏創建輸出的強分類器*/
        stage = (CvStageHaarClassifier*) icvCreateStageHaarClassifier( seq->total, threshold );

        cvCvtSeqToArray( seq, (CvArr*) stage->classifier );
    }

    /* CLEANUP */
    cvReleaseMemStorage( &storage );
    cvReleaseMat( &weakTrainVals );
    cvFree( &(eval.data.ptr) );

    return (CvIntHaarClassifier*) stage;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418

感謝:::http://www.cnblogs.com/harvey888/p/5505511.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章