首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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

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

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

#include

#include

#include

#include "config.h"

#include "dataset.h"

#include "nn.h"

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

NODE **network;

double getOutPut()

{

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

}

double square(double output, double target)

{

double r = output - target;

return r * r / 2;

}

double squareder(double output, double target)

{

return output - target;

}

double activation(double x)

{

#if ACTIVATIONFUNCTION == RELU

if (x > 0)

{

return x;

}

else

{

return 0;

}

#elif ACTIVATIONFUNCTION == TANH

return tanh(x);

#endif

}

double activationder(double x)

{

#if ACTIVATIONFUNCTION == RELU

if (x > 0)

{

return 1;

}

else

{

return 0;

}

#elif ACTIVATIONFUNCTION == TANH

// tanh的倒数

double y = tanh(x);

return 1 - y * y;

#endif

}

double outlayeractivation(double x)

{

#if OUTLAYERACTIVATIONFUNCTION == TANH

return tanh(x);

#endif

}

double outlayeractivationder(double x)

{

#if OUTLAYERACTIVATIONFUNCTION == TANH

// tanh的倒数

double y = tanh(x);

return 1 - y * y;

#endif

}

void buildNetwork()

{

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++)

{

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

int prenodeNum = networkShape[i - 1];

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

{

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

}

}

// 输入层

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

{

network[0][i].bias = 0.1;

}

// 隐藏层与输出层

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

{

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

{

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++)

{

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)

{

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++)

{

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

{

network[i][j].totalInput = network[i][j].bias;

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

{

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++)

{

network[outlayerNum][i].totalInput = network[outlayerNum][i].bias;

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

{

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)

{

// 清空所有节点的outputDer

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

{

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

{

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

}

}

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

// 输出层

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

{

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++)

{

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--)

{

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

{

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++)

{

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()

{

// 隐藏层与输出层

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

{

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

{

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

{

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++)

{

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

{

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

{

double weight;

double errorDer;

double accErrorDer;

int numAccumulatedDers;

} LINK;

typedef LINK *PLINK;

typedef struct NODE

{

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

#include

#include "config.h"

#include "dataset.h"

POINT points[NUMSAMPLES];

void shuffle()

{

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

{

int index = i * ((double)rand() / RAND_MAX);

POINT point = points[i];

points[i] = points[index];

points[index] = point;

}

}

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

void classifyCircleData()

{

double radius = 5;

// 创建内部圆上的点

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

{

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++)

{

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

{

double x;

double y;

double label;

} POINT;

void classifyCircleData();

#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

#include

#include

#include "config.h"

#include "dataset.h"

#include "nn.h"

extern POINT points[NUMSAMPLES];

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

{

double loss = 0;

if (mode)

{

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

{

forwardProp(points[i]);

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

}

}

else

{

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

{

forwardProp(points[i]);

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

}

}

return loss / (NUMSAMPLES / 2);

}

void training()

{

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

{

forwardProp(points[i]);

backProp(points[i]);

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

{

updateWeights();

}

}

double lossTrain = getLoss(0);

double lossTest = getLoss(1);

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

}

int main(int argc, char **argv)

{

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++)

{

training();

}

return 0;

}

代码完整地址为:https://github.com/worldflyingct/ann

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

  • 发表于:
  • 原文链接https://page.om.qq.com/page/ORLaydLlXV-s1XZ2207V5h0w0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券