C语言实现bp神经网络
该程序是gpt生成的,我稍加改动,可以直接运行。
后续会上传到github上。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define N 4 // 输入维度
#define M 4 // 输出维度
#define H 15 // 隐藏层1维度
#define H2 15 // 隐藏层2维度
#define ETA 0.002 // 学习率
#define THETA 0.01 // 误差容限
#define EPOCHS 70000 // 最大训练次数
#define TEST_IN_PATH "D:\\data_save\\c-bpnn-master\\dataset\\at_in_p.txt"
#define TEST_OUT_PATH "D:\\data_save\\c-bpnn-master\\dataset\\at_out_p.txt"
#define SAVE_PARAM_PATH "D:\\data_save\\c-bpnn-master\\bpnn_param.h"
#define BUFFER_SIZE 256
static FILE *in_file = NULL;
static FILE *out_file = NULL;
// 随机数生成器
double rand_double(double min, double max) {
return min + (max - min) * ((double)rand() / RAND_MAX);
}
// sigmoid 函数
double sigmoid(double x) {
return 1.0 / (1.0 + exp(-x));
}
// d_sigmoid 函数
double d_sigmoid(double x)
{
return x * (1 - x);
}
// softmax 函数
void softmax(double *x, int n) {
double sum = 0.0;
int i;
for (i = 0; i < n; i++) {
sum += exp(x[i]);
}
for (i = 0; i < n; i++) {
x[i] = exp(x[i]) / sum;
}
}
// 前向传播
void forward(double *x, double **w1, double *b1, double **w2, double *b2,double *y, double *z) {
int i, j;
//w1[N][H],b1[20],w2[H][N],b2[4]
for (i = 0; i < H; i++) {
z[i] = b1[i];
for (j = 0; j < N; j++) {
z[i] += x[j] * w1[j][i];
}
y[i] = sigmoid(z[i]);
}
for (i = 0; i < M; i++) {
double u = b2[i];
for (j = 0; j < H; j++) {
u += y[j] * w2[j][i];
}
z[H+i] = u;
}
softmax(&z[H], M);
}
// 反向传播,更新模型
void backward(double *x, double *t, double **w1, double *b1, double **w2, double *b2, double *y, double *z) {
int i, j, k;
double delta[M], delta2[H];
for (i = 0; i < M; i++) {
delta[i] = z[H+i] - t[i];
}
for (i = 0; i < H; i++) {
double sum = 0.0;
for (j = 0; j < M; j++) {
sum += delta[j] * w2[i][j];
}
delta2[i] = y[i] * (1.0 - y[i]) * sum;
}
for (i = 0; i < H; i++) {
for (j = 0; j < M; j++) {
w2[i][j] -= ETA * delta[j] * y[i];
}
}
for (i = 0; i < M; i++) {
b2[i] -= ETA * delta[i];
}
for (i =0; i < H; i++) {
for (j = 0; j < N; j++) {
w1[j][i] -= ETA * delta2[i] * x[j];
}
b1[i] -= ETA * delta2[i];
}
}
// 前向传播
void predict(double *x, double **w1, double *b1, double **w2, double *b2, double *y, double *z) {
int i, j;
for (i = 0; i < H; i++) {
z[i] = b1[i];
for (j = 0; j < N; j++) {
z[i] += x[j] * w1[j][i];
}
y[i] = sigmoid(z[i]);
}
for (i = 0; i < M; i++) {
double u = b2[i];
for (j = 0; j < H; j++) {
u += y[j] * w2[j][i];
}
z[i] = u;
}
softmax(z, M);
}
// 训练神经网络
void train(double **x, double **t, int n, double **w1, double *b1, double **w2, double *b2) {
int i, j, k;
double y[H], z[H+M];
for (i = 0; i < EPOCHS; i++) {
double error = 0.0;
for (j = 0; j < n; j++) {
forward(x[j], w1, b1, w2, b2, y, z);
backward(x[j], t[j], w1, b1, w2, b2, y, z);
for (k = 0; k < M; k++) {
error += t[j][k] * log(z[H+k] + 1e-7);
}
}
error /= -n;
if (i % 100 == 0) {
printf("Epoch: %d, Error: %f\n", i, error);
}
if (error < THETA) {
printf("Converged!\n");
break;
}
}
}
//保存参数到 SAVE_PARAM_PATH 文件中
void save_parameter(double **w1, double *b1, double **w2, double *b2) {
FILE *out = NULL;
out = fopen(SAVE_PARAM_PATH, "w+");
if (out == NULL) {
fprintf(stderr, "[BPNN] OPEN FILE %s FAILED.\n", SAVE_PARAM_PATH);
}
fprintf(out, "double w1[N][H]={");
//w1[N][H],b1[20],w2[H][M],b2[4]
for (size_t i = 0; i < N; i++) {
fprintf(out, "{");
for (size_t h = 0; h < H; h++)
{
fprintf(out, "%lf,", w1[i][h]);
}
fprintf(out, "},\n");
}
fprintf(out, "};\n");
fprintf(out, "double b1[H]={");
for (size_t h = 0; h < H; h++)
fprintf(out, "%lf,", b1[h]);
fprintf(out, "};\n");
fprintf(out, "double w2[H][M]={");
for (size_t h = 0; h < H; h++){
fprintf(out, "{");
for (size_t j = 0; j < M; j++)
{
fprintf(out, "%lf,", w2[h][j]);
}
fprintf(out, "},\n");
}
fprintf(out, "};\n");
fprintf(out, "double b2[M]={");
for (size_t j = 0; j < M; j++)
fprintf(out, "%lf,", b2[j]);
fprintf(out, "};\n");
fclose(out);
}
数据集长这样,已经做归一化了:
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
0.2347,0.2671,0.1805,0.2383
标签数据长这样:
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
0,0,0,1
下面的main函数中,TEST_IN_PATH=数据集文件地址,TEST_OUT_PATH=标签数据地址。
int main()
{
static char buffer[BUFFER_SIZE];
int d_len=2000;
int train_len=0,val_len=0;
int i, j;
double max=0;
double tab_max=0;
int tab_flag=0;
int flag=0;
//获取数据
in_file = fopen(TEST_IN_PATH, "r");
if (in_file == NULL) {
fprintf(stderr, "open file %s failed.\n", TEST_IN_PATH);
return 0;
}
out_file = fopen(TEST_OUT_PATH, "r");
if (out_file == NULL) {
fprintf(stderr, "open file %s failed.\n", TEST_OUT_PATH);
return 0;
}
//x是数据
//t是标签
double **x = (double **) malloc(d_len * sizeof(double *));
double **t = (double **) malloc(d_len * sizeof(double *));
if (in_file && out_file) {
for(int m=0;m<d_len;m++)
{
x[m]=(double *) malloc(N * sizeof(double));
t[m]=(double *) malloc(M * sizeof(double));
if (fgets(buffer, BUFFER_SIZE, in_file) != NULL) {
char *token = strtok(buffer, ",");
for (i = 0; i < N; i++) {
if (token == NULL) {
fprintf(stderr, "the format of input is not correct!\n");
}
x[m][i] = strtod(token, NULL);
token = strtok(NULL, ",");
}
}
if (fgets(buffer, BUFFER_SIZE, out_file) != NULL) {
char *token = strtok(buffer, ",");
for (i = 0; i < M; i++) {
if (token == NULL) {
fprintf(stderr, "the format of out is not correct!\n");
}
t[m][i] = strtod(token, NULL);
token = strtok(NULL, ",");
}
}
//printf("in:%f,%f,%f,%f out:%f,%f,%f,%f \n",x[m][0],x[m][1],x[m][2],x[m][3],t[m][0],t[m][1],t[m][2],t[m][3]);
}
}
// 初始化权重和偏置 w1[N][H],b1[20],w2[H][N],b2[4]
double **w1 = (double **) malloc(N * sizeof(double *));
for (i = 0; i < N; i++) {
w1[i] = (double *) malloc(H * sizeof(double));
for (j = 0; j < H; j++) {
w1[i][j] = rand_double(-1.0, 1.0);
}
}
double *b1 = (double *) malloc(H * sizeof(double));
for (i = 0; i < H; i++) {
b1[i] = rand_double(-1.0, 1.0);
}
double **w2 = (double **) malloc(H * sizeof(double *));
for (i = 0; i < H; i++) {
w2[i] = (double *) malloc(M * sizeof(double));
for (j = 0; j < M; j++) {
w2[i][j] = rand_double(-1.0, 1.0);
}
}
double *b2 = (double *) malloc(M * sizeof(double));
for (i = 0; i < M;i++) {
b2[i] = rand_double(-1.0, 1.0);
}
train_len=(int)(d_len*0.8);
val_len=(int)(d_len*0.2);
//训练
train(x, t, train_len, w1, b1, w2, b2);
//保存训练参数
save_parameter( w1, b1, w2, b2);
double *ys = (double *) malloc(H * sizeof(double ));
double *zs = (double *) malloc(M * sizeof(double ));
int correct = 0;
//测试准确率
for (i=train_len;i<d_len;i++)
{
//预测
predict(x[i], w1, b1, w2, b2,ys,zs);
max=0;
flag=0;
tab_max=0;
tab_flag=0;
for (j = 0; j < M; j++) {
if (t[i][j]>tab_max)
{
tab_max=t[i][j];
tab_flag=j;
}
if (zs[j]>max)
{
max=zs[j];
flag=j;
}
}
//预测准确的
if(flag==tab_flag)
{
correct++;
}
}
double accuracy = (double) correct / (d_len-train_len);
printf("Accuracy: %f\n", accuracy);
//x:d_len*N
//t:d_len*M
//ys:H
//zs:M
// 释放内存
for (i = 0; i < d_len; i++) {
free(x[i]);
}
for (i = 0; i < d_len; i++) {
free(t[i]);
}
free(x);
free(t);
for (i = 0; i < N; i++) {
free(w1[i]);
}
free(w1);
free(b1);
for (i = 0; i < H; i++) {
free(w2[i]);
}
free(w2);
free(b2);
free(ys);
free(zs);
}