基於BP神經網絡的簡單字符識別算法自小結(C語言版)


本文均屬自己閱讀源代碼的點滴總結。轉賬請注明出處謝謝。

歡迎和大家交流。qq:1037701636 email:gzzaigcn2009@163.com

 

寫在前面的閑話:

    自我感覺自己應該不是一個非常擅長學習算法的人。過去的一個月時間里由於須要去接觸了BP神經網絡。在此之前一直都覺得算法界的神經網絡、蟻群算法、魯棒控制什么的都是特別高大上的東西,自己也就聽聽好了,未曾去觸碰與了解過。這次和BP神經網絡的邂逅。讓我初步掌握到。理解透徹算法的基本原理與公式,轉為計算機所能識別的代碼流,這應該就是所謂的數學和計算機的完美結合吧,難道這過程也就是所謂的ACM嗎?

 

1.BP神經網絡的基本概念和原理

    看了網絡上非常多關於神經網絡相關的資料,了解到BP神經網絡是最為簡單和通用的,剛好所需完畢的工作也主要是簡單的字符識別過程。

BP神經網絡的概念:

BP(Back Propagation)網絡是1986年由Rumelhart和McCelland為首的科學家小組提出,是一種按誤差逆傳播算法訓練的多層前饋網絡。是眼下應用最廣泛的神經網絡模型之中的一個。

BP網絡能學習和存貯大量的輸入-輸出模式映射關系。而無需事前揭示描寫敘述這樣的映射關系的數學方程。

BP神經網絡的基本模型圖例如以下所看到的:

 
BP網絡的三個層次:輸入層、隱藏層以及輸出層。

我們所要做的就是依據自身的需求建立一個屬於我們自己的BP神經網絡。

 
2.BP神經網絡的原理以及相關公式的推導
 
2.1、BP網絡的基本思想:
 
BP神經網絡學習過程由信息的正向傳遞與誤差的反向傳播兩個過程組成:
(1) 正向傳遞:輸入樣本從輸入經隱含層逐層計算傳向輸出層,若輸出層的實際輸出和期望輸出不符,則計算輸出層的誤差值。然后轉向反向傳播過程。

(2) 誤差的反向傳播:是將輸出誤差以某種形式通過隱層向輸入層逐層反傳。並將誤差分攤給各層全部單元。從而獲得各層單元的誤差信號,此誤差做為修正該單元的依據。 信號正向傳遞和誤差反向傳播重復進行,權值不斷得到調整的過程。就是網絡的學習/訓練過程。

當訓練達到規定誤差或一定訓練次數。則結束訓練。

 
BP網絡訓練學習過程可理解成:樣本輸入時的理想目標Tk與實際輸出Ok之間的誤差平方Ep不斷趨向於0的一個過程:
依據誤差梯度下降法依次修正輸出層權值的修正量 Δ wki ,輸出層閾值的修正量 Δa k,隱含層權值的修正量 Δ wij ,隱含層閾值的修正量:
                      
上述公式表明網絡輸入誤差是各層權值wjk、vij的函數,因此調整權值可改變誤差E。顯然,調整權值的原則是使誤差不斷地減小。因此應使權值的調整量與誤差的梯度下降成正比。故而對BP網絡算法的直觀解釋例如以下所看到的:
 
通過上圖表明,BP網絡算法的核心是在不斷調整權值的情況下。使得誤差不斷的減小。

顯然不管是在正向梯度還是負向梯度,在離散情況下都須要不斷的將權值往誤差極小值的地方調整。而調整的速率值eta(也稱權值的步進值)關乎着整個神經網絡的訓練速度。

 
 
BP神經網絡的公式推導能夠參考: http://en.wikipedia.org/wiki/Backpropagation,相關推導公式核心內容例如以下:
 
BP網絡的算法核心是使用一個刺激函數不斷的forward,而這個刺激函數往往使用 S型激活函數(logsig)為:
 
 
 
 
 
注:上述公式參閱網絡資源,這幾步流程圖,對於理解整個BP神經網絡算法的核心思想有非常大的幫助。
 
 

3.BP神經網絡算法用C語言的實現

3.1.初始的准備工作,建立三個層的數據結構體:分別表示樣本信息、輸入層的信息、隱藏層的信息以及輸出層的信息,終於歸於到一個BP核心算法的結構體bp_alg_core_params;

 

typedef struct {

    unsigned int img_sample_num; //待訓練的圖像字符、數字樣本個數
    unsigned int img_width;
    unsigned int img_height;
    unsigned char *img_buffer;
    
}img_smaple_params;

typedef img_smaple_params* hd_sample_params;


typedef struct {

    unsigned int in_num;  //輸入層節點數目
    double *in_buf; //輸入層輸出數據緩存
    double **weight; //權重,當前輸入層一個節點相應到多個隱層
    unsigned int weight_size;
    double **pri_deltas; //記錄先前權重的變化值。用於附加動量
    double *deltas; //當前計算的隱層反饋回來的權重矯正

}bp_input_layer_params;

typedef bp_input_layer_params* hd_input_layer_params;

typedef struct {

    unsigned int hid_num; //隱層節點數目
    double *hid_buf; //隱層輸出數據緩存
    double **weight; //權重。當前隱層一個節點相應到多個輸出層
    unsigned int weight_size;
    double **pri_deltas; //記錄先前權重的變化值,用於附加動量
    double *deltas; //當前計算的輸出反饋回來的權重矯正值
    
}bp_hidden_layer_params;

typedef bp_hidden_layer_params* hd_hidden_layer_params;


typedef struct {

    unsigned int out_num;//輸出層節點數目
    double *out_buf; //輸出層輸出數據緩存
    double *out_target;

}bp_out_layer_params;

typedef bp_out_layer_params* hd_out_layer_params;


typedef struct {

    unsigned int size; //結構體大小
    unsigned int train_ite_num; //訓練迭代次數
    unsigned int sample_num;  //待訓練的樣本個數
    double momentum;   //BP閾值調整動量
    double eta;                //訓練步進值。學習效率
    double err2_thresh;  //最小均方誤差
 
    hd_sample_params p_sample; //樣本集合參數
    hd_input_layer_params p_inlayer; //輸入層參數
    hd_hidden_layer_params p_hidlayer;//隱藏層參數
    hd_out_layer_params p_outlayer;//輸出層參數
    
}bp_alg_core_params;


3.2.參數的初始化。主要包含對計算緩存區的分配。

如果這里分別有M,N。K表示輸入、隱藏、輸出的節點數目。

那么一個輸入緩存區大小:分配大小M+1個。同理隱藏層和輸出分別分配:N+1,P+1個;數據量大小默認雙精度的double類型。

 

權值的緩沖區大小:一個依據BP網絡的算法,一個節點到下一層的節點分別須要具備一一相應。故這是一個二維數組的形式存在。我們分配輸入層權值空間大小為(M+1)(N+1)的大小。隱藏權值空間大小為(N+1)(P+1);

當然對於權值的矯正量。其是一個依據節點的輸出值向后反饋的一個變量,實際就是多對1的反饋。而通過公式能夠看到我們能夠僅僅採用一維數組來表示每個節點的反饋矯正值(不基於輸入節點的數據。即例如以下的變量:

同理終於隱藏層到輸出層的反饋矯正、輸出層和隱藏層的反饋矯正都以一個一位變量的形式存在,僅僅是在計算權值時要結合節點的輸入數據來進行2維矯正。

完畢二維數組的動態分配過程函數例如以下所看到的:

double** alloc_2d_double_buf(unsigned int m, unsigned int n)
{
    
    unsigned int i;
    double **buf = NULL;
    double *head;
    
    /*分配一個數組指針空間+ 2維數據緩存空間*/
    buf = (double **)malloc(sizeof(double *)*m + m*n*sizeof(double));
    if(buf == NULL)
    {
        ERR("malloc error!");
        exit(1);
    }

    head = (double *)(buf + m);
    
    memset((void *)head, 0x00, sizeof(double)*m*n);//clear 2d buf
    for(i = 0; i < m; i++)
    {
        
        buf[i] = head + i*n;
        DEG("alloc_2d_double_buf,  addr = 0x%x", buf[i] );
    }
    
    return buf;

}


3.3 BP神經網絡訓練過程和不斷的權值矯正

依次經過forward向前刺激。權值矯正值計算,權值調整,樣本均分誤差計算。

以一次樣本數全部樣本節點計算完后做均方誤差,誤差滿足一定的閾值就說明BP神經網絡訓練能夠基本結束(一般定義可接受的誤差在0.001左右):

int bp_train(bp_alg_core_params *core_params)
{
    unsigned int i, j, k;
    unsigned int train_num, sample_num;
    double err2;//均分誤差

    DEG("Enter bp_train Function");

    if(core_params == NULL)
    {
        ERR("Null point Entry");
        return -1;
    }
    
    train_num = core_params->train_ite_num;//迭代訓練次數
    sample_num = core_params->sample_num;//樣本數

    hd_sample_params p_sample = core_params->p_sample;  //樣本集合參數
    hd_input_layer_params p_inlayer = core_params->p_inlayer;  //輸入層參數
    hd_hidden_layer_params p_hidlayer = core_params->p_hidlayer; //隱藏層參數
    hd_out_layer_params p_outlayer = core_params->p_outlayer; //輸出層參數

    DEG("The max train_num = %d", train_num);

    /*依次依照訓練樣本數目進行迭代訓練*/
    for(i = 0; i < train_num; i++)
    {
        err2 = 0.0;

        
        DEG("current train_num = %d", i);
        
        for(j = 0 ; j < sample_num; j++)
        {
            
            DEG("current sample id = %d", j);
            
            memcpy((unsigned char*)(p_inlayer->in_buf+1), (unsigned char*)sample[j], p_inlayer->in_num*sizeof(double));
            memcpy((unsigned char*)(p_outlayer->out_target+1), (unsigned char*)out_target[j%10], p_outlayer->out_num*sizeof(double));

            /*輸入層到隱藏層的向前傳遞輸出*/
            bp_layerforward(p_inlayer->in_buf, p_hidlayer->hid_buf, p_inlayer->in_num, p_hidlayer->hid_num, p_inlayer->weight);

            /*隱藏層到輸出層的向前傳遞輸出*/
            bp_layerforward(p_hidlayer->hid_buf, p_outlayer->out_buf, p_hidlayer->hid_num, p_outlayer->out_num, p_hidlayer->weight);
            
            /*輸出層向前反饋錯誤到隱藏層,即權值矯正值*/
            bp_outlayer_deltas(p_outlayer->out_buf, p_outlayer->out_target, p_outlayer->out_num, p_hidlayer->deltas);
            
            /*隱藏層向前反饋錯誤到輸入層,權值矯正值依賴於上一層的調整值*/
            bp_hidlayer_deltas(p_hidlayer->hid_buf, p_hidlayer->hid_num, p_outlayer->out_num, p_hidlayer->weight, p_hidlayer->deltas, p_inlayer->deltas);

            /*調整隱藏層到輸出層的權值*/
            adjust_layer_weight(p_hidlayer->hid_buf, p_hidlayer->weight, p_hidlayer->pri_deltas, p_hidlayer->deltas, p_hidlayer->hid_num, 
                                                    p_outlayer->out_num, core_params->eta, core_params->momentum);
            
            /*調整隱藏層到輸出層的權值*/
            adjust_layer_weight(p_inlayer->in_buf, p_inlayer->weight, p_inlayer->pri_deltas, p_inlayer->deltas, p_inlayer->in_num, 
                                                    p_hidlayer->hid_num, core_params->eta, core_params->momentum);      

            err2 +=  calculate_err2(p_outlayer->out_buf, p_outlayer->out_target, p_outlayer->out_num);//統計全部樣本遍歷一次后的均分誤差

        }

        /*一次樣本處理后的均分誤差統計*/
        err2 = err2/(double)(p_outlayer->out_num*sample_num);
        INFO("err2 =%08f\n",err2 );
        
        if(err2 < core_params->err2_thresh)
        {
            INFO("BP Train Success by costs vaild iter nums: %d\n", i);
            return 1;
        }
    }

    INFO("BP Train %d Num Failured! need to modfiy core params\n", i);
    return 0;
    
}

 

4.總結

BP神經網絡在理解完算法的核心思想后,用代碼的形式去實現往往會變得事半功倍,而如果一股腦兒直接拿到code去分析的做法不推薦。由於不了解核心思想的基礎下,無法對算法的參數進行部分的改動以及優化,盲目改動往往會造成實驗的失敗。

本算法應該滿足了BP神經網絡的基本訓練過程。至於不管是識別,預測還是其它,都能夠在獲取理想樣本源和目標源的基礎上對BP神經網絡進行訓練與學習,使得其具備了一定的通用性。后期要做的是將上述浮點的處理過程竟可能的轉為定點化的DSP來處理,將其應用到嵌入式設備中去。


注:

由於非常多人向我咨詢code,所以上傳了基於BP神經網絡的簡單字符識別算法自小結(C語言版) 。本人已不再研究,謝謝。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM