編寫C語言版本的卷積神經網路CNN之一:前言與Minst資料集

tostq發表於2016-06-29
原創文章
轉載請註冊來源http://blog.csdn.net/tostq

前言
       卷積神經網路是深度學習的基礎,但是學習CNN卻不是那麼簡單,雖然網路上關於CNN的相關程式碼很多,比較經典的是tiny_cnn(C++)、DeepLearnToolbox(Matlab)等等,但通過C語言來編寫CNN的卻比較少,本人因為想在多核DSP下執行CNN,所以便嘗試通過C語言來編寫,主要參考的程式碼是DeepLearnToolbox的內容,DeepLearnToolbox是用Matlab指令碼編寫,是我看過的最為簡單的CNN程式碼,程式碼清晰,閱讀方便,非常適合新手入門學習。
       本文的CNN程式碼是一個最基本的卷積網路,主要用於手寫數字的識別,選擇的訓練測試是資料庫是Minst手寫數字庫,主要是包括了一個基本的多層卷積網路框架、卷積層、Pooling層、及全連線的單層神經網路輸出層,不過CNN其他重要的概念如Dropout、ReLu等暫時沒有涉及,但是個人對於新手,學習卷積網路的基本結構及其誤差反向傳播方法是完全足夠。
       這裡要注意的是,本文的方法並不是深度學習之父Yann. LeCun在1998年就已經提出的成熟演算法LeNet-5卷積網路,而只是DeepLearnToolbox內的cnn程式碼的c語言實現,不過我們會比較二者之間的區別,因為二者的基本原理是相似的。另外,為了不使部落格篇幅過長,所以部落格中貼的程式碼並不完整,完整程式碼請見附件。
       這篇部落格總共分為四節:
       第一節:前言,介紹專案結構及Minst資料集測試訓練資料集
       第二節:主要介紹CNN的網路結構、相關資料結構
       第三節:重點介紹CNN學習訓練過程的誤差反向傳播方法,採用的是線上訓練方式
       第四節:CNN的學習及測試結果的比較

論文參考文獻:
Y. LeCun, L. Bottou, Y. Bengio and P. Haffner: Gradient-Based Learning Applied to Document Recognition, Proceedings of the IEEE, 86(11):2278-2324, November 1998

一、程式碼結構
       本文的CNN程式碼是通過標準C編寫,也不需要呼叫任何三方庫檔案,附件共享的檔案是通過VS2010編譯的專案檔案(這裡雖然是.cpp檔案,但實際上完全是用C編寫的,直接改成.c檔案是完全可以使用的),當然也可以直接將相關的原始檔匯入到其他IDE上,也是能夠執行的。
       檔案結構:
       cnn.cpp cnn.h 存在關於CNN網路的函式、網路結構等
       minst.cpp minst.h 處理Minst資料庫的函式、資料結構等
       mat.cpp mat.h 一些關於矩陣的函式,如卷積操作、180度翻轉操作等
       main.cpp 主函式和測試函式

二、MINST資料庫
       MINST資料庫是由Yann提供的手寫數字資料庫檔案,其官方下載地址http://yann.lecun.com/exdb/mnist/
       這個裡面還包含了對這個資料庫進行識別的各類演算法的結果比較及相關演算法的論文

       資料庫的裡的影像都是28*28大小的灰度影像,每個畫素的是一個八位位元組(0~255)

       這個資料庫主要包含了60000張的訓練影像和10000張的測試影像,主要是下面的四個檔案

       上述四個檔案直接解壓就可以使用了,雖然資料未壓縮,但是我們還是需要將影像提取出來,方便我們進行操作
(1)儲存影像資料的相關資料結構:
       單張影像結構及儲存影像的連結串列
       
       影像實際數字標號(0~9),資料庫裡是用一個八位位元組來表示,不過為了方便學習,需要用10位來表示。
       這裡的10位表示網路輸出層的10個神經元,某位為1表示數字標號即為該位。
       
(2)讀入影像資料的相關函式
ImgArr read_Img(const char* filename) // 讀入影像
{
    FILE  *fp=NULL;
    fp=fopen(filename,"rb");
    if(fp==NULL)
        printf("open file failed\n");
    assert(fp);

    int magic_number = 0;  
    int number_of_images = 0;  
    int n_rows = 0;  
    int n_cols = 0;  
    //從檔案中讀取sizeof(magic_number) 個字元到 &magic_number  
    fread((char*)&magic_number,sizeof(magic_number),1,fp); 
    magic_number = ReverseInt(magic_number);  
    //獲取訓練或測試image的個數number_of_images 
    fread((char*)&number_of_images,sizeof(number_of_images),1,fp);  
    number_of_images = ReverseInt(number_of_images);    
    //獲取訓練或測試影像的高度Heigh  
    fread((char*)&n_rows,sizeof(n_rows),1,fp); 
    n_rows = ReverseInt(n_rows);                  
    //獲取訓練或測試影像的寬度Width  
    fread((char*)&n_cols,sizeof(n_cols),1,fp); 
    n_cols = ReverseInt(n_cols);  
    //獲取第i幅影像,儲存到vec中 
    int i,r,c;

    // 影像陣列的初始化
    ImgArr imgarr=(ImgArr)malloc(sizeof(MinstImgArr));
    imgarr->ImgNum=number_of_images;
    imgarr->ImgPtr=(MinstImg*)malloc(number_of_images*sizeof(MinstImg));

    for(i = 0; i < number_of_images; ++i)  
    {  
        imgarr->ImgPtr[i].r=n_rows;
        imgarr->ImgPtr[i].c=n_cols;
        imgarr->ImgPtr[i].ImgData=(float**)malloc(n_rows*sizeof(float*));
        for(r = 0; r < n_rows; ++r)      
        {
            imgarr->ImgPtr[i].ImgData[r]=(float*)malloc(n_cols*sizeof(float));
            for(c = 0; c < n_cols; ++c)
            { 
                // 因為神經網路用float型計算更為精確,這裡我們將影像畫素轉為浮點型
                unsigned char temp = 0;  
                fread((char*) &temp, sizeof(temp),1,fp); 
                imgarr->ImgPtr[i].ImgData[r][c]=(float)temp/255.0;
            }  
        }    
    }

    fclose(fp);
    return imgarr;
}

(3)讀入影像資料標號
LabelArr read_Lable(const char* filename)// 讀入影像
{
    FILE  *fp=NULL;
    fp=fopen(filename,"rb");
    if(fp==NULL)
        printf("open file failed\n");
    assert(fp);

    int magic_number = 0;  
    int number_of_labels = 0; 
    int label_long = 10;

    //從檔案中讀取sizeof(magic_number) 個字元到 &magic_number  
    fread((char*)&magic_number,sizeof(magic_number),1,fp); 
    magic_number = ReverseInt(magic_number);  
    //獲取訓練或測試image的個數number_of_images 
    fread((char*)&number_of_labels,sizeof(number_of_labels),1,fp);  
    number_of_labels = ReverseInt(number_of_labels);    

    int i,l;

    // 影像標記陣列的初始化
    LabelArr labarr=(LabelArr)malloc(sizeof(MinstLabelArr));
    labarr->LabelNum=number_of_labels;
    labarr->LabelPtr=(MinstLabel*)malloc(number_of_labels*sizeof(MinstLabel));

    for(i = 0; i < number_of_labels; ++i)  
    {
        // 資料庫內的影像標記是一位,這裡將影像標記變成10位,10位中只有唯一一位為1,為1位即是影像標記  
        labarr->LabelPtr[i].l=10;
        labarr->LabelPtr[i].LabelData=(float*)calloc(label_long,sizeof(float));
        unsigned char temp = 0;  
        fread((char*) &temp, sizeof(temp),1,fp); 
        labarr->LabelPtr[i].LabelData[(int)temp]=1.0;    
    }

    fclose(fp);
    return labarr;    
}

 專案程式碼地址:https://github.com/tostq/DeepLearningC/tree/master/CNN

相關文章