【AI学习笔记7】用C语言实现MNIST识别的FCNN网络 A MNIST recognition FCNN in C language

用C语言实现MNIST识别的FCNN网络 A MNIST recognition FCNN in C language

 

一、准备mnist训练和测试数据集【1】

从 https://www.kaggle.com/datasets 下载mnist手写数字数据集。

  1. 数据集包含60,000个训练数据和10,000个测试数据。
  2. 数据集包含了0-9共10类手写数字图片,每张图片都是28x28大小的灰度图。
  3. MNIST数据集包含四个部分:
    训练集图像:train-images-idx3-ubyte.gz(9.9MB,包含60000个样本)
    训练集标签:train-labels-idx1-ubyte.gz(29KB,包含60000个标签)
    测试集图像:t10k-images-idx3-ubyte.gz(1.6MB,包含10000个样本)
    测试集标签:t10k-labels-idx1-ubyte.gz(5KB,包含10000个标签)

【2】

 

二、设计神经网络

采用三层全连接神经网络FCNN:

  1. 输入层: 一共28x28=784个神经元;
  2. 中间层: 一共512个神经元,激活函数用sigmoid;
  3. 输出层: 一共10个神经元,分别对应0-9数字的概率,激活函数用sigmoid。

 

三、训练和测试神经网络

  1. 用60,000个数据训练:训练过程中先用大的learning_rate,再用小的,提高训练速度;
  2. 用10,000个数据测试。

 

四、C语言代码实现(C code)

 

#include "stdio.h"

#include "stdlib.h"

#include "math.h"

 

#define ROW_PIX_NUM 28

#define COL_PIX_NUM 28

#define TOTAL_PIX_NUM (ROW_PIX_NUM * COL_PIX_NUM)  // 784

 

#define TOTAL_TRAIN_NUM 60000 

#define TOTAL_TEST_NUM 10000

 

#define INPUT_NUM TOTAL_PIX_NUM

#define HIDDEN_NUM 512

#define OUTPUT_NUM 10

 

#define LEARNING_RATE_BIG 0.1

#define LEARNING_RATE_SMALL 0.01

#define EPOCHS 100000

#define MIN_EPOCH 90000

 

static unsigned int epoch;

static unsigned int tmp;

 

static unsigned int train_magic_number1, train_number_of_images, train_number_of_rows, train_number_of_columns;

static unsigned int train_images[TOTAL_TRAIN_NUM][TOTAL_PIX_NUM];

static unsigned int train_magic_number2, train_number_of_items;

static unsigned int train_labels[TOTAL_TRAIN_NUM];

 

static unsigned int test_magic_number1, test_number_of_images, test_number_of_rows, test_number_of_columns;

static unsigned int test_images[TOTAL_TEST_NUM][TOTAL_PIX_NUM];

static unsigned int test_magic_number2, test_number_of_items;

static unsigned int test_labels[TOTAL_TEST_NUM];

 

static float weights0[INPUT_NUM][HIDDEN_NUM];  //input --> hidden

static float weights1[HIDDEN_NUM][OUTPUT_NUM];  //hidden --> output

 

static float hiddens[HIDDEN_NUM];

static float outputs[OUTPUT_NUM];

 

static float errs_output[OUTPUT_NUM], errs_hidden[HIDDEN_NUM];

static float learning_rate = LEARNING_RATE_SMALL;

 

//对应int32大小的成员 的转换 范例

unsigned int swapInt32(unsigned int value)

{

return ((value & 0x000000FF) << 24) |

((value & 0x0000FF00) << 8) |

((value & 0x00FF0000) >> 8) |

((value & 0xFF000000) >> 24) ;

}

 

// sigmoid激活函数

double sigmoid(double x) {

    return 1.0 / (1.0 + exp(-x));

}

 

// softmax分类函数

void softmax(float* output, int n) {

    double sum = 0.0;

    for (int i = 0; i < n; i++) {

        sum += exp(output[i]);

    }

    for (int i = 0; i < n; i++) {

        output[i] = exp(output[i]) / sum;

    }

}

 

// probnorm概率归一化函数

void probnorm(float* outputs, int n) {

    float sum = 0.0;

    float maxprob = 0.0, probsum = 0.0;

    int maxid = 0;

 

    for (int i = 0; i < n; i++) {

        sum += outputs[i];

        if (outputs[i] > maxprob) {

            maxid = i;

            maxprob = outputs[i];

        }

    }

    for (int i = 0; i < n; i++) {

        outputs[i] = outputs[i] / sum;

    }

 

    outputs[maxid] = 0.0;

    probsum = 0.0;

    for (int i = 0; i < n; i++) {

        probsum += outputs[i];

    }

    outputs[maxid] = 1.0 - probsum;

}

 

// 前向传播函数

void forward(unsigned int* inputs) {

    for (int i = 0; i < HIDDEN_NUM; i++) {

        float sum = 0.0;

        for (int j = 0; j < INPUT_NUM; j++) {

            sum += inputs[j] * weights0[j][i];

        }

        hiddens[i] = sigmoid(sum);

    }

 

    for (int i = 0; i < OUTPUT_NUM; i++) {

        float sum = 0.0;

        for (int j = 0; j < HIDDEN_NUM; j++) {

            sum += hiddens[j] * weights1[j][i];

        }

        outputs[i] = sigmoid(sum);

    }

 

    //softmax(outputs, OUTPUT_NUMS);

    probnorm(outputs, OUTPUT_NUM);

}

 

// 反向传播函数

void backward(unsigned int* inputs, unsigned int label) {

    for (int i = 0; i < OUTPUT_NUM; i++) {

        errs_output[i] = ((i == (int)label) ? 1.0 : 0.0) - outputs[i];

        //errs_output[i] *= outputs[i] * (1.0 - outputs[i]);

    }

 

    for (int i = 0; i < HIDDEN_NUM; i++) {

        errs_hidden[i] = 0.0f;

        for (int j = 0; j < OUTPUT_NUM; j++) {

            errs_hidden[i] += errs_output[j]*weights1[i][j];

        }

        //errs_hidden[i] *= hiddens[i] * (1.0 - hiddens[i]);

    }

 

    for (int i = 0; i < INPUT_NUM; i++) {

        for (int j = 0; j < HIDDEN_NUM; j++) {

            weights0[i][j] += learning_rate * errs_hidden[j] * inputs[i];

        }

    }

 

    for (int i = 0; i < HIDDEN_NUM; i++) {

        for (int j = 0; j < OUTPUT_NUM; j++) {

            weights1[i][j] += learning_rate * errs_output[j] * hiddens[i];

        }

    }    

}

 

// 训练函数

void train() {

    for (int i = 0; i < TOTAL_TRAIN_NUM; i++) {

        forward(train_images[i]);

        backward(train_images[i], train_labels[i]);

    }

}

 

// 测试函数

void test() {

    int num_correct = 0;

    for (int i = 0; i < TOTAL_TEST_NUM; i++) {

        forward(test_images[i]);

        for (int k = 0; k < OUTPUT_NUM; k++) {

            printf("o[%d]=%f ", k, outputs[k]);

        }

        printf("\n");

 

        int prediction = 0;

        for (int j = 1; j < OUTPUT_NUM; j++) {            

            if (outputs[j] > outputs[prediction]) {

                prediction = j;

            }

        }

       

        if (prediction == test_labels[i]) {

            num_correct++;

            printf("Prediction = %d, [v] %d\n", prediction, test_labels[i]);

        }

        else

        {

            printf("Prediction = %d, [x] %d\n", prediction, test_labels[i]);

        }

 

    }

    double accuracy = (double)num_correct / TOTAL_TEST_NUM;

 

    printf("Test Accuracy = %f\n", accuracy);

}

 

int main() {

 

    FILE *fp_train_images = NULL;

    FILE *fp_train_labels = NULL;

    FILE *fp_test_images = NULL;

    FILE *fp_test_labels = NULL;

 

    fp_train_images = fopen("./train-images.idx3-ubyte", "rb");

    fp_train_labels = fopen("./train-labels.idx1-ubyte", "rb");

    fp_test_images = fopen("./t10k-images.idx3-ubyte", "rb");

    fp_test_labels = fopen("./t10k-labels.idx1-ubyte", "rb");

    if ( (NULL == fp_train_images) || (NULL == fp_train_labels) || (NULL == fp_test_images) || (NULL == fp_test_labels) ) {

        printf("Error: Can't open file !\n");

    }

 

    printf("1. train-images \n");

 

    fread(&tmp, 4, 1, fp_train_images);

    tmp = swapInt32(tmp);

    printf("train_magic_number1=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_train_images);

    tmp = swapInt32(tmp);

    printf("train_number_of_images=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_train_images);

    tmp = swapInt32(tmp);

    printf("train_number_of_rows=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_train_images);

    tmp = swapInt32(tmp);

    printf("train_number_of_columns=0x%08x \n", tmp);

 

    for (int k=0; k<TOTAL_TRAIN_NUM; k++)

    {

        for (int j=0; j<COL_PIX_NUM; j++)

        {

            for (int i=0; i<ROW_PIX_NUM/4; i++)

            {

                fread(&tmp, 4, 1, fp_train_images);

               

                train_images[k][ROW_PIX_NUM*j + i*4 + 0] = (tmp >> 0) & 0x000000FF;

                train_images[k][ROW_PIX_NUM*j + i*4 + 1] = (tmp >> 8) & 0x000000FF;

                train_images[k][ROW_PIX_NUM*j + i*4 + 2] = (tmp >> 16) & 0x000000FF;

                train_images[k][ROW_PIX_NUM*j + i*4 + 3] = (tmp >> 24) & 0x000000FF;

            }

        }

    }

 

    printf("2. train-labels \n");

 

    fread(&tmp, 4, 1, fp_train_labels);

    tmp = swapInt32(tmp);

    printf("train_magic_number2=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_train_labels);

    tmp = swapInt32(tmp);

    printf("train_number_of_items=0x%08x \n", tmp);

 

    for (int k=0; k<TOTAL_TRAIN_NUM/4; k++)

    {

        fread(&tmp, 4, 1, fp_train_labels);

               

        train_labels[k*4+0] = (tmp >> 0) & 0x000000FF;

        train_labels[k*4+1] = (tmp >> 8) & 0x000000FF;

        train_labels[k*4+2] = (tmp >> 16) & 0x000000FF;

        train_labels[k*4+3] = (tmp >> 24) & 0x000000FF;

    }

 

    printf("3. test-images \n");

 

    fread(&tmp, 4, 1, fp_test_images);

    tmp = swapInt32(tmp);

    printf("test_magic_number1=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_test_images);

    tmp = swapInt32(tmp);

    printf("test_number_of_images=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_test_images);

    tmp = swapInt32(tmp);

    printf("test_number_of_rows=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_test_images);

    tmp = swapInt32(tmp);

    printf("test_number_of_columns=0x%08x \n", tmp);

 

    for (int k=0; k<TOTAL_TEST_NUM; k++)

    {

        for (int j=0; j<COL_PIX_NUM; j++)

        {

            for (int i=0; i<ROW_PIX_NUM/4; i++)

            {

                fread(&tmp, 4, 1, fp_test_images);

               

                test_images[k][ROW_PIX_NUM*j + i*4 + 0] = (tmp >> 0) & 0x000000FF;

                test_images[k][ROW_PIX_NUM*j + i*4 + 1] = (tmp >> 8) & 0x000000FF;

                test_images[k][ROW_PIX_NUM*j + i*4 + 2] = (tmp >> 16) & 0x000000FF;

                test_images[k][ROW_PIX_NUM*j + i*4 + 3] = (tmp >> 24) & 0x000000FF;

            }

        }

    }

 

    printf("4. test-labels \n");

 

    fread(&tmp, 4, 1, fp_test_labels);

    tmp = swapInt32(tmp);

    printf("test_magic_number2=0x%08x \n", tmp);

 

    fread(&tmp, 4, 1, fp_test_labels);

    tmp = swapInt32(tmp);

    printf("test_number_of_items=0x%08x \n", tmp);

 

    for (int k=0; k<TOTAL_TEST_NUM/4; k++)

    {

        fread(&tmp, 4, 1, fp_test_labels);

               

        test_labels[k*4+0] = (tmp >> 0) & 0x000000FF;

        test_labels[k*4+1] = (tmp >> 8) & 0x000000FF;

        test_labels[k*4+2] = (tmp >> 16) & 0x000000FF;

        test_labels[k*4+3] = (tmp >> 24) & 0x000000FF;

    }

 

    // 初始化权重

    for (int i = 0; i < INPUT_NUM; i++) {

        for (int j = 0; j < HIDDEN_NUM; j++) {

            weights0[i][j] = (double)rand() / RAND_MAX - 0.5;

        }

    }

 

    for (int i = 0; i < HIDDEN_NUM; i++) {

        for (int j = 0; j < OUTPUT_NUM; j++) {

            weights1[i][j] = (double)rand() / RAND_MAX - 0.5;

        }

    }

 

    // 训练模型

    for (epoch = 0; epoch < EPOCHS; epoch++) {

 

        if (0 == (epoch%10))

        {

            printf("epoch=%d\n", epoch);

        }

 

        if (epoch < MIN_EPOCH)

        {

            learning_rate = LEARNING_RATE_BIG;

        }

        else

        {

            learning_rate = LEARNING_RATE_SMALL;

        }

 

        train();

    }

 

    // 测试模型

    test();

 

    return 0;

}

 

 

参考文献(References):

【1】 Midas-Zhou 《用C语言构建一个手写数字识别神经网络》

https://blog.csdn.net/Septem_ccn/article/details/132589351

 

【2】GShang  《Mnist数据集简介》

https://www.cnblogs.com/gshang/p/13022239.html

posted on 2025-02-09 18:36  JasonQiuStar  阅读(139)  评论(0)    收藏  举报