BP神经网络的C代码分析

去年在学习Stanford的ML课程的时候整理过一篇BP神经网络原理的解析,链接地址,不过没有对它的code实现作太多的研究,只是用MATLAB的工具箱做了实验。

Jeremy Lin

具体的原理性资料可以参考:

[1] BP神经网络解析          地址

[2] Tom M.Mitchell 机器学习教程  地址


BP网络算法流程:


从上面的算法流程可以看出来,BP神经网络的步骤并不多,如果你之前就了解BP神经网络,那么上面的流程应该还是可以很顺利地看懂的,如果之前没学习过,那么我推荐你好好参看我上面推荐的两个Ref。


BPNN的结构体

要开始具体编写BP神经网络(以下简称BPNN)的步骤之前,你需要定义好BPNN的结构,这里面用结构体实现,自然你完全可以用类实现。

typedef struct 
{
  int input_n;                  // 网络输入个数
  int hidden_n;                 // 隐藏层单元个数
  int output_n;                 // 网络输出个数

  double *input_units;          // 网络输入单元
  double *hidden_units;         // 隐藏层单元
  double *output_units;         // 网络输出单元

  double *hidden_delta;         // 隐藏层单元error
  double *output_delta;         // 网络输出单元error

  double *target;               // 目标输出单元

  double **input_weights;       // 从输入层到隐藏层的权值向量
  double **hidden_weights;      // 从隐藏层到输出层的权值向量
  
                                // 冲量项    
  double **input_prev_weights;  // 从输入层到隐藏层的冲量项向量
  double **hidden_prev_weights; // 从隐藏层到输出层的冲量项向量
} BPNN;

创建BPNN

在上面的算法流程中的第一步是创建n_in个输入,n_hidden个隐藏层单元,n_out个输出单元的网络。然后是初始化网络的权值为较小的随机数。

在讲如何创建网络之前,我更愿意谈谈如何生成随机数。嘿,那不是直接用rand()吗?Yes,当然是用这个函数,但是,我们可以花点时间谈下rand()是如何生成随机数。在Meitu实习的末尾,我突然想了解随机数到底是如何生成的。那时候我第一步是自然是Google了,结果搜索关键词不够cool,得到的结果自然是用rand()生成了,哈,可是我想要的是随机数生成的原理啊。当然,最后知道了,那就是用线性同余法生成随机数。大家如果如果有兴趣的话,可以参考我提供的Refs[3],具体我不想在这里说了。

[3] 计算机程序设计艺术(卷2)  Chapter 3 Random Numbers 

现在进入原题:

BPNN *bpnn_create(int n_in, int n_hidden, int n_out)
{
  // 创建BPNN
  BPNN *newnet;
  newnet = bpnn_internal_create(n_in, n_hidden, n_out);
  
  // 初始化BPNN的网络权值
  bpnn_randomize_weights(newnet->input_weights, n_in, n_hidden);
  bpnn_randomize_weights(newnet->hidden_weights, n_hidden, n_out);
  // 初始化BPNN的冲量
  bpnn_zero_weights(newnet->input_prev_weights, n_in, n_hidden);
  bpnn_zero_weights(newnet->hidden_prev_weights, n_hidden, n_out);

  return (newnet);
}

在这里,我们主要使用bpnn_internal_create()函数创建BPNN:

BPNN *bpnn_internal_create(int n_in, int n_hidden, int n_out)
{
  BPNN *newnet;

  newnet = (BPNN *) malloc (sizeof (BPNN));
  if (newnet == NULL) {
    printf("BPNN_CREATE: Couldn't allocate neural network\n");
    return (NULL);
  }

  newnet->input_n = n_in;
  newnet->hidden_n = n_hidden;
  newnet->output_n = n_out;
  newnet->input_units = alloc_1d_dbl(n_in + 1);
  newnet->hidden_units = alloc_1d_dbl(n_hidden + 1);
  newnet->output_units = alloc_1d_dbl(n_out + 1);

  newnet->hidden_delta = alloc_1d_dbl(n_hidden + 1);
  newnet->output_delta = alloc_1d_dbl(n_out + 1);
  newnet->target = alloc_1d_dbl(n_out + 1);

  newnet->input_weights = alloc_2d_dbl(n_in + 1, n_hidden + 1);
  newnet->hidden_weights = alloc_2d_dbl(n_hidden + 1, n_out + 1);

  newnet->input_prev_weights = alloc_2d_dbl(n_in + 1, n_hidden + 1);
  newnet->hidden_prev_weights = alloc_2d_dbl(n_hidden + 1, n_out + 1);

  return (newnet);
}

在上面各个单元的内存分配主要是通过如下两个函数实现的:

// 一维数组空间分配
double *alloc_1d_dbl(int n)
{
  double *newA;

  newA = (double *) malloc ((unsigned) (n * sizeof (double)));
  if (newA == NULL) 
  {
    printf("ALLOC_1D_DBL: Couldn't allocate array of doubles\n");
    return (NULL);
  }
  return (newA);
}


// 二维数组空间分配
double **alloc_2d_dbl(int m, int n)
{
  int i;
  double **newA2;

  newA2 = (double **) malloc ((unsigned) (m * sizeof (double *)));
  if (newA2 == NULL) 
  {
    printf("ALLOC_2D_DBL: Couldn't allocate array of dbl ptrs\n");
    return (NULL);
  }

  for (i = 0; i < m; i++) 
  {
    newA2[i] = alloc_1d_dbl(n);
  }
  return (newA2);
}

那么接下来就是BPNN的网络权值初始化了:

#define BIGRND 0x7fffffff

double drnd()
{
  return ((double) rand() / (double) BIGRND);
}

double dpn1()
{
  return ((drnd() * 2.0) - 1.0);
}
void bpnn_randomize_weights(double **w, int m, int n)
{
  int i, j;

  for (i = 0; i <= m; i++) 
  {
    for (j = 0; j <= n; j++) 
  {
      w[i][j] = dpn1();
    }
  }
}


BPNN的训练

好了,前戏已做,现在开始训练:

void bpnn_train(BPNN *net, double eta, double momentum, double *eo, double *eh)
{
  int in, hid, out;
  double out_err, hid_err;

  in  = net->input_n;
  hid = net->hidden_n;
  out = net->output_n;

  // 前向传播
  bpnn_layerforward(net->input_units, net->hidden_units,
      net->input_weights, in, hid);
  bpnn_layerforward(net->hidden_units, net->output_units,
      net->hidden_weights, hid, out);

  // 计算误差
  bpnn_output_error(net->output_delta, net->target, net->output_units,
      out, &out_err);
  bpnn_hidden_error(net->hidden_delta, hid, net->output_delta, out,
      net->hidden_weights, net->hidden_units, &hid_err);
  *eo = out_err;
  *eh = hid_err;

  // 调整权值向量
  bpnn_adjust_weights(net->output_delta, out, net->hidden_units, hid,
      net->hidden_weights, net->hidden_prev_weights, eta, momentum);
  bpnn_adjust_weights(net->hidden_delta, hid, net->input_units, in,
      net->input_weights, net->input_prev_weights, eta, momentum);
}

下面是上面各个函数的具体实现:

// 激活函数
double squash(double x)
{
  return (1.0 / (1.0 + exp(-x)));
}

// 前向函数
void bpnn_layerforward(double *l1,double *l2, double **conn, int n1, int n2)
{
  double sum;
  int j, k;

  // 设置偏置
  l1[0] = 1.0;

  for (j = 1; j <= n2; j++) 
  {
    sum = 0.0;
    for (k = 0; k <= n1; k++) 
    {
      sum += conn[k][j] * l1[k];
    }
    l2[j] = squash(sum);
  }
}

void bpnn_output_error(double *delta, double *target, double *output, int nj, double *err)
{
  int j;
  double o, t, errsum;

  errsum = 0.0;
  for (j = 1; j <= nj; j++) 
  {
    o = output[j];
    t = target[j];
    delta[j] = o * (1.0 - o) * (t - o);
    errsum += ABS(delta[j]);
  }
  *err = errsum;
}


void bpnn_hidden_error(double *delta_h, int nh, double *delta_o, int no, double **who, double *hidden, double *err)
{
  int j, k;
  double h, sum, errsum;

  errsum = 0.0;
  for (j = 1; j <= nh; j++) 
  {
    h = hidden[j];
    sum = 0.0;
    for (k = 1; k <= no; k++) 
    {
      sum += delta_o[k] * who[j][k];
    }
    delta_h[j] = h * (1.0 - h) * sum;
    errsum += ABS(delta_h[j]);
  }
  *err = errsum;
}


void bpnn_adjust_weights(double *delta, int ndelta, double *ly, int nly, double **w,
           double **oldw, double eta,double momentum)
{
  double new_dw;
  int k, j;

  ly[0] = 1.0;
  for (j = 1; j <= ndelta; j++) 
  {
    for (k = 0; k <= nly; k++) 
    {
      new_dw = ((eta * delta[j] * ly[k]) + (momentum * oldw[k][j]));
      w[k][j] += new_dw;
      oldw[k][j] = new_dw;
    }
  }
}

void bpnn_feedforward(BPNN *net)
{
  int in, hid, out;

  in = net->input_n;
  hid = net->hidden_n;
  out = net->output_n;

  bpnn_layerforward(net->input_units, net->hidden_units,
      net->input_weights, in, hid);
  bpnn_layerforward(net->hidden_units, net->output_units,
      net->hidden_weights, hid, out);
}

到现在为止,BPNN的主要函数算是介绍完毕了。

不过,还有一个东西还没说,那就是所谓的终止条件。从推导BP的过程来说,我们很容易想到的一种终止条件自然是:直到对训练样例的误差E降低到某个预定义的阈值之下。但是,这其实并不是一个好的想法,因为BP算法容易过度拟合训练样例,降低对于其他实例的泛化精度。而为解决这个问题一个最常用的方法是在训练数据外再为算法提供一个验证数据集(validation dataset)。算法在使用训练数据集合驱动梯度下降搜索的同时,监视对于这个验证数据集合的误差。


本文地址:http://blog.csdn.net/linj_m/article/details/40679085

更加资源请 关注 博客:LinJM-机器视觉微博:林建民-机器视觉

  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: BP神经网络是一种常用的人工神经网络模型,它基于反向传播算法,用于解决分类和回归问题。下面给出一个简单的BP神经网络的C语言代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <math.h> #define INPUT_SIZE 2 #define HIDDEN_SIZE 3 #define OUTPUT_SIZE 1 #define LEARNING_RATE 0.5 #define EPOCHS 1000 // 定义神经网络结构 typedef struct { double input[INPUT_SIZE]; double hidden[HIDDEN_SIZE]; double output[OUTPUT_SIZE]; double weights_ih[INPUT_SIZE][HIDDEN_SIZE]; double weights_ho[HIDDEN_SIZE][OUTPUT_SIZE]; } NeuralNetwork; // 初始化神经网络 void init(NeuralNetwork* nn) { // 初始化输入层到隐藏层的权重 for (int i = 0; i < INPUT_SIZE; i++) { for (int j = 0; j < HIDDEN_SIZE; j++) { nn->weights_ih[i][j] = (double)rand() / RAND_MAX; } } // 初始化隐藏层到输出层的权重 for (int i = 0; i < HIDDEN_SIZE; i++) { for (int j = 0; j < OUTPUT_SIZE; j++) { nn->weights_ho[i][j] = (double)rand() / RAND_MAX; } } } // 前向传播 void forward(NeuralNetwork* nn) { for (int i = 0; i < HIDDEN_SIZE; i++) { double sum = 0; for (int j = 0; j < INPUT_SIZE; j++) { sum += nn->input[j] * nn->weights_ih[j][i]; } nn->hidden[i] = 1 / (1 + exp(-sum)); } for(int i = 0; i < OUTPUT_SIZE; i++) { double sum = 0; for(int j = 0; j < HIDDEN_SIZE; j++) { sum += nn->hidden[j] * nn->weights_ho[j][i]; } nn->output[i] = 1 / (1 + exp(-sum)); } } // 反向传播 void backward(NeuralNetwork* nn, double target) { double delta_o = (target - nn->output[0]) * nn->output[0] * (1 - nn->output[0]); for(int i = 0; i < HIDDEN_SIZE; i++) { double delta_h = delta_o * nn->weights_ho[i][0] * nn->hidden[i] * (1 - nn->hidden[i]); for(int j = 0; j < INPUT_SIZE; j++) { nn->weights_ih[j][i] += LEARNING_RATE * nn->input[j] * delta_h; } nn->weights_ho[i][0] += LEARNING_RATE * nn->hidden[i] * delta_o; } } // 主函数 int main() { NeuralNetwork nn; init(&nn); double input[INPUT_SIZE] = {0.1, 0.2}; double target = 0.3; for (int epoch = 0; epoch < EPOCHS; epoch++) { for (int i = 0; i < INPUT_SIZE; i++) { nn.input[i] = input[i]; } forward(&nn); backward(&nn, target); } printf("Output: %lf\n", nn.output[0]); return 0; } ``` 上述代码实现了一个具有一个输入层,一个隐藏层和一个输出层的BP神经网络,并使用随机权重和输入进行训练。代码中使用sigmoid函数作为激活函数,并通过反向传播算法更新权重以减少误差。主函数中通过迭代训练多个周期来提高模型的准确性,最后输出预测结果。 ### 回答2: BP神经网络是一种经典的神经网络模型,在C语言中可以通过编写代码来实现。下面是一个简单的BP神经网络的C语言代码: ```c #include <stdio.h> #include <stdlib.h> #include <math.h> #define INPUTS 2 #define HIDDEN 3 #define OUTPUTS 1 #define LEARNING_RATE 0.1 #define MAX_ITERATIONS 10000 #define THRESHOLD 0.05 // 隐含层和输出层神经元的权值和阈值 double hidden[HIDDEN]; double output[OUTPUTS]; double hiddenBias[HIDDEN]; double outputBias[OUTPUTS]; // 输入层与隐含层之间的权值和阈值 double inputHiddenWeights[INPUTS][HIDDEN]; double inputHiddenBias[HIDDEN]; void initializeWeights() { // 初始化权值和阈值 for (int i = 0; i < INPUTS; i++) { for (int j = 0; j < HIDDEN; j++) { inputHiddenWeights[i][j] = rand() / (double)RAND_MAX; } } for (int i = 0; i < HIDDEN; i++) { hiddenBias[i] = rand() / (double)RAND_MAX; } for (int i = 0; i < OUTPUTS; i++) { outputBias[i] = rand() / (double)RAND_MAX; } } double sigmoid(double x) { // sigmoid激活函数 return 1.0 / (1.0 + exp(-x)); } void feedForward(double inputs[]) { // 前向传播 for (int i = 0; i < HIDDEN; i++) { hidden[i] = 0.0; for (int j = 0; j < INPUTS; j++) { hidden[i] += inputs[j] * inputHiddenWeights[j][i]; } hidden[i] += inputHiddenBias[i]; hidden[i] = sigmoid(hidden[i]); } for (int i = 0; i < OUTPUTS; i++) { output[i] = 0.0; for (int j = 0; j < HIDDEN; j++) { output[i] += hidden[j] * hiddenOutputWeights[j][i]; } output[i] += outputBias[i]; output[i] = sigmoid(output[i]); } } void backPropagate(double inputs[], double targets[]) { // 反向传播 double deltaHidden[HIDDEN] = {0}; double deltaOutput[OUTPUTS] = {0}; // 更新输出层权值和阈值 for (int i = 0; i < OUTPUTS; i++) { deltaOutput[i] = (targets[i] - output[i]) * output[i] * (1.0 - output[i]); for (int j = 0; j < HIDDEN; j++) { hiddenOutputWeights[j][i] += LEARNING_RATE * deltaOutput[i] * hidden[j]; } outputBias[i] += LEARNING_RATE * deltaOutput[i]; } // 更新隐含层权值和阈值 for (int i = 0; i < HIDDEN; i++) { double sum = 0.0; for (int j = 0; j < OUTPUTS; j++) { sum += deltaOutput[j] * hiddenOutputWeights[i][j]; } deltaHidden[i] = sum * hidden[i] * (1.0 - hidden[i]); for (int j = 0; j < INPUTS; j++) { inputHiddenWeights[j][i] += LEARNING_RATE * deltaHidden[i] * inputs[j]; } hiddenBias[i] += LEARNING_RATE * deltaHidden[i]; } } int train(double inputs[][INPUTS], double targets[][OUTPUTS], int numPatterns) { int iterations = 0; double error; do { error = 0.0; for (int p = 0; p < numPatterns; p++) { feedForward(inputs[p]); backPropagate(inputs[p], targets[p]); error += 0.5 * ((targets[p][0] - output[0]) * (targets[p][0] - output[0])); } iterations++; if (iterations > MAX_ITERATIONS) { break; } } while (error > THRESHOLD); return iterations; } int main() { // 训练样本和目标设置 double inputs[4][INPUTS] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}}; double targets[4][OUTPUTS] = {{0}, {1}, {1}, {0}}; initializeWeights(); int iterations = train(inputs, targets, 4); printf("训练完成,迭代次数:%d\n", iterations); return 0; } ``` 以上是一个简单的BP神经网络的C语言代码。通过定义输入层、隐含层和输出层的神经元数量,设定学习速率、最大迭代次数和误差阈值,并实现了初始化权值和阈值、sigmoid激活函数、前向传播、反向传播以及训练函数。在main函数中,设定了训练样本和目标,并调用了训练函数进行训练。最后输出训练完成后的迭代次数。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值