【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手写数字数据集。
- 数据集包含60,000个训练数据和10,000个测试数据。
- 数据集包含了0-9共10类手写数字图片,每张图片都是28x28大小的灰度图。
- 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:
- 输入层: 一共28x28=784个神经元;
- 中间层: 一共512个神经元,激活函数用sigmoid;
- 输出层: 一共10个神经元,分别对应0-9数字的概率,激活函数用sigmoid。
三、训练和测试神经网络
- 用60,000个数据训练:训练过程中先用大的learning_rate,再用小的,提高训练速度;
- 用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) 收藏 举报
浙公网安备 33010602011771号