用C语言手写一个神经网络

程序员小沃 2024-04-07 03:44:21

该程序是模拟tensflow游乐场写的,实现了基本的神经网络效果并验证通过,不多废话,上代码。

核心代码在nn.c中,包含激活函数和损失函数,前向传播,反向传播以及更新权重与偏执的函数。

#include <stdint.h>

#include <stdlib.h>

#include <math.h>

#include "config.h"

#include "dataset.h"

#include "nn.h"

int networkShape[] = {2, 8, 8, 8, 8, 8, 8, 1};

NODE **network;

double getOutPut()

<{p> return network[sizeof(networkShape) / sizeof(int) - 1][0].output;

}

double square(double output, double target)

<{p> double r = output - target;

return r * r / 2;

}

double squareder(double output, double target)

<{p> return output - target;

}

double activation(double x)

<{p>#if ACTIVATIONFUNCTION == RELU

if (x > 0)

<{p> return x;

}

else

<{p> return 0;

}

#elif ACTIVATIONFUNCTION == TANH

return tanh(x);

#endif

}

double activationder(double x)

<{p>#if ACTIVATIONFUNCTION == RELU

if (x > 0)

<{p> return 1;

}

else

<{p> return 0;

}

#elif ACTIVATIONFUNCTION == TANH

// tanh的倒数

double y = tanh(x);

return 1 - y * y;

#endif

}

double outlayeractivation(double x)

<{p>#if OUTLAYERACTIVATIONFUNCTION == TANH

return tanh(x);

#endif

}

double outlayeractivationder(double x)

<{p>#if OUTLAYERACTIVATIONFUNCTION == TANH

// tanh的倒数

double y = tanh(x);

return 1 - y * y;

#endif

}

void buildNetwork()

<{p> network = (PPNODE)malloc((sizeof(networkShape) / sizeof(int)) * sizeof(PNODE));

// 输入层

network[0] = (PNODE)malloc(networkShape[0] * sizeof(NODE));

// 隐藏层与输出层

for (int i = 1, leni = sizeof(networkShape) / sizeof(int); i < leni; i++)

<{p> network[i] = (PNODE)malloc(networkShape[i] * sizeof(NODE));

int prenodeNum = networkShape[i - 1];

for (int j = 0, lenj = networkShape[i]; j < lenj; j++)

<{p> network[i][j].link = (PLINK)malloc(prenodeNum * sizeof(LINK));

}

}

// 输入层

for (int i = 0; i < networkShape[0]; i++)

<{p> network[0][i].bias = 0.1;

}

// 隐藏层与输出层

for (int i = 1, leni = sizeof(networkShape) / sizeof(int); i < leni; i++)

<{p> for (int j = 0, lenj = networkShape[i]; j < lenj; j++)

<{p> network[i][j].bias = 0.1;

network[i][j].inputDer = 0;

network[i][j].outputDer = 0;

network[i][j].accInputDer = 0;

network[i][j].numAccumulatedDers = 0;

for (int k = 0, lenk = networkShape[i - 1]; k < lenk; k++)

<{p> network[i][j].link[k].weight = (double)rand() / RAND_MAX - 0.5;

network[i][j].link[k].errorDer = 0;

network[i][j].link[k].accErrorDer = 0;

network[i][j].link[k].numAccumulatedDers = 0;

}

}

}

}

void forwardProp(POINT point)

<{p> int outlayerNum = sizeof(networkShape) / sizeof(int) - 1; // 输出层所在层

// 输入层

network[0][0].output = point.x;

network[0][1].output = point.y;

// 隐藏层

for (int i = 1, leni = outlayerNum; i < leni; i++)

<{p> for (int j = 0, lenj = networkShape[i]; j < lenj; j++)

<{p> network[i][j].totalInput = network[i][j].bias;

for (int k = 0, lenk = networkShape[i - 1]; k < lenk; k++)

<{p> network[i][j].totalInput += network[i][j].link[k].weight * network[i - 1][k].output;

}

network[i][j].output = activation(network[i][j].totalInput);

}

}

// 输出层

for (int i = 0, leni = networkShape[outlayerNum]; i < leni; i++)

<{p> network[outlayerNum][i].totalInput = network[outlayerNum][i].bias;

for (int j = 0, lenj = networkShape[outlayerNum - 1]; j < lenj; j++)

<{p> network[outlayerNum][i].totalInput += network[outlayerNum][i].link[j].weight * network[outlayerNum - 1][j].output;

}

network[outlayerNum][i].output = outlayeractivation(network[outlayerNum][i].totalInput);

}

}

void backProp(POINT point)

<{p> // 清空所有节点的outputDer

for (int i = 0, leni = sizeof(networkShape) / sizeof(int); i < leni; i++)

<{p> for (int j = 0; j < networkShape[i]; j++)

<{p> network[i][j].outputDer = 0;

}

}

int outlayerNum = sizeof(networkShape) / sizeof(int) - 1; // 输出层所在层

// 输出层

for (int i = 0, leni = networkShape[outlayerNum]; i < leni; i++)

<{p> network[outlayerNum][i].outputDer = squareder(network[outlayerNum][i].output, point.label); // 目标和结果的差距

network[outlayerNum][i].inputDer = network[outlayerNum][i].outputDer * outlayeractivationder(network[outlayerNum][i].totalInput);

network[outlayerNum][i].accInputDer += network[outlayerNum][i].inputDer;

network[outlayerNum][i].numAccumulatedDers++;

for (int j = 0, lenj = networkShape[outlayerNum]; j < lenj; j++)

<{p> network[outlayerNum][i].link[i].errorDer = network[outlayerNum][i].inputDer * network[outlayerNum - 1][i].output;

network[outlayerNum][i].link[i].accErrorDer += network[outlayerNum][i].link[i].errorDer;

network[outlayerNum][i].link[i].numAccumulatedDers++;

network[outlayerNum - 1][i].outputDer += network[outlayerNum][i].link[i].weight * network[outlayerNum][i].inputDer;

}

}

// 隐藏层

for (int i = outlayerNum; i > 0; i--)

<{p> for (int j = 0; j < networkShape[i]; j++)

<{p> network[i][j].inputDer = network[i][j].outputDer * activationder(network[i][j].totalInput);

network[i][j].accInputDer += network[i][j].inputDer;

network[i][j].numAccumulatedDers++;

for (int k = 0; k < networkShape[i - 1]; k++)

<{p> network[i][j].link[k].errorDer = network[i][j].inputDer * network[i - 1][k].output;

network[i][j].link[k].accErrorDer += network[i][j].link[k].errorDer;

network[i][j].link[k].numAccumulatedDers++;

network[i - 1][k].outputDer += network[i][j].link[k].weight * network[i][j].inputDer;

}

}

}

}

void updateWeights()

<{p> // 隐藏层与输出层

for (int i = 1; i < sizeof(networkShape) / sizeof(int); i++)

<{p> for (int j = 0; j < networkShape[i]; j++)

<{p> if (network[i][j].numAccumulatedDers > 0)

<{p> network[i][j].bias -= LEARNINGRATE * network[i][j].accInputDer / network[i][j].numAccumulatedDers;

network[i][j].accInputDer = 0;

network[i][j].numAccumulatedDers = 0;

}

for (int k = 0; k < networkShape[i - 1]; k++)

<{p> if (network[i][j].link[k].numAccumulatedDers > 0)

<{p> network[i][j].link[k].weight -= LEARNINGRATE * network[i][j].link[k].accErrorDer / network[i][j].link[k].numAccumulatedDers;

network[i][j].link[k].accErrorDer = 0;

network[i][j].link[k].numAccumulatedDers = 0;

}

}

}

}

}

对应头文件为nn.h

#ifndef __NN_H__

#define __NN_H__

#include "config.h"

typedef struct LINK

<{p> double weight;

double errorDer;

double accErrorDer;

int numAccumulatedDers;

} LINK;

typedef LINK *PLINK;

typedef struct NODE

<{p> double bias;

PLINK link;

double output;

double inputDer;

double outputDer;

double accInputDer;

int numAccumulatedDers;

double totalInput;

} NODE;

typedef NODE *PNODE;

typedef PNODE *PPNODE;

double getOutPut();

double square(double output, double target);

double squareder(double output, double target);

double tanhder(double x); // tanh的倒数

double activation(double x);

double activationder(double x);

double outlayeractivation(double x);

double outlayeractivationder(double x);

void buildNetwork();

void forwardProp(POINT point);

void backProp(POINT point);

void updateWeights();

#endif

自动创建与生成训练集与测试集的程序,这里就创建了一个基于半径为5的圆型,圆中间是一部分数据,圆外围是一部分数据。

#include <stdlib.h>

#include <math.h>

#include "config.h"

#include "dataset.h"

POINT points[NUMSAMPLES];

void shuffle()

<{p> for (int i = 0; i < NUMSAMPLES; i++)

<{p> int index = i * ((double)rand() / RAND_MAX);

POINT point = points[i];

points[i] = points[index];

points[index] = point;

}

}

// 创建NUMSAMPLES个参数,按照原型来创建

voidifyCircleData()

<{p> double radius = 5;

// 创建内部圆上的点

for (int i = 0; i < NUMSAMPLES / 2; i++)

<{p> double r = 0.5 * radius * rand() / RAND_MAX; // 生成随机的半径

double angle = 2.0 * M_PI * rand() / RAND_MAX; // 生成随机的角度

points[i].x = r * cos(angle);

points[i].y = r * sin(angle);

points[i].label = 1;

}

// 创建外部圆上的点

for (int i = NUMSAMPLES / 2; i < NUMSAMPLES; i++)

<{p> double r = 0.7 * radius + 0.3 * radius * rand() / RAND_MAX; // 生成随机的半径

double angle = 2.0 * M_PI * rand() / RAND_MAX; // 生成随机的角度

points[i].x = r * cos(angle);

points[i].y = r * sin(angle);

points[i].label = -1;

}

shuffle();

}

对应头文件为dataset.h

#ifndef __DATASET_H__

#define __DATASET_H__

typedef struct

<{p> double x;

double y;

double label;

} POINT;

voidifyCircleData();

#endif

程序配置部分为config.h,定义了数据集大小,学习率以及batchsize大小,还有激活函数,损失函数等应该选什么。

#ifndef __CONFIG_H__ #define __CONFIG_H__ #define NUMSAMPLES 500 // 创建测试点的数量,其中前一半作为训练集,后一半作为测试集 #ifndef __CONFIG_H__

#define __CONFIG_H__

#define NUMSAMPLES 500 // 创建测试点的数量,其中前一半作为训练集,后一半作为测试集

#define LEARNINGRATE 0.03

#define BATCHSIZE 10

#define ACTIVATIONFUNCTION RELU

#define OUTLAYERACTIVATIONFUNCTION TANH

#endif

main.c主要是调用上述函数,初始化网络以及数据集,以及训练。

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

#include "config.h"

#include "dataset.h"

#include "nn.h"

extern POINT points[NUMSAMPLES];

double getLoss(int mode) // 0代表训练集,1代表测试集

<{p> double loss = 0;

if (mode)

<{p> for (int i = NUMSAMPLES / 2; i < NUMSAMPLES; i++)

<{p> forwardProp(points[i]);

loss += square(getOutPut(), points[i].label);

}

}

else

<{p> for (int i = 0; i < NUMSAMPLES / 2; i++)

<{p> forwardProp(points[i]);

loss += square(getOutPut(), points[i].label);

}

}

return loss / (NUMSAMPLES / 2);

}

void training()

<{p> for (int i = 0; i < NUMSAMPLES / 2; i++)

<{p> forwardProp(points[i]);

backProp(points[i]);

if ((i + 1) % BATCHSIZE == 0)

<{p> updateWeights();

}

}

double lossTrain = getLoss(0);

double lossTest = getLoss(1);

printf("lossTrain:%f,lossTest:%f\n", lossTrain, lossTest);

}

int main(int argc, char **argv)

<{p> srand((unsigned)time(NULL));

classifyCircleData();

buildNetwork();

double lossTrain = getLoss(0);

double lossTest = getLoss(1);

printf("lossTrain:%f,lossTest:%f\n", lossTrain, lossTest);

for (int i = 0; i < 100; i++)

<{p> training();

}

return 0;

}

代码完整地址为:

后期可能会根据我学习的深入继续更新这份代码,就不另行通知了。

0 阅读:0

程序员小沃

简介:分享自己技术特点的个人小站。