一个类精通B-P网络的精髓

本文通过一个具体的神经网络实现案例,介绍了如何从头构建一个多层全连接神经网络,并使用反向传播算法进行训练。该网络应用于异或(XOR)问题的解决,详细展示了网络结构定义、权重初始化、前向传播、误差反向传播等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于机器学习库的神经网络代码已经非常精简了,对于用户来说都是黑盒子,其内部到底怎么运作的呢?原理虽然简单,但真正要操刀书写,估计一时半会也不好弄,幸好大牛们开源了、只需要一个类就能明明白白!!!


backprop.h

//////////////////////////////////////////////
//	Fully connected multilayered feed		//
//	forward	artificial neural network using	//
//	Backpropogation	algorithm for training.	//
//////////////////////////////////////////////

// 基于反向传播算法的全连接多层前向人工神经网络

#ifndef backprop_h
#define backprop_h

#include <assert.h>
#include <iostream>
#include <stdio.h>
#include <math.h>

using namespace std;


//B-P网络
class CBackProp{

//	output of each neuron
	double **out;

//	delta error value for each neuron
	double **delta;

//	vector of weights for each neuron
	double ***weight;

//	no of layers in net
//	including input layer
	int numl;

//	vector of numl elements for size 
//	of each layer
	int *lsize;

//	learning rate //学习率
	double beta;

//	momentum parameter //动量参数
	double alpha;

//	storage for weight-change made //
//	in previous epoch
	double ***prevDwt;

//	squashing function //压缩函数
	double sigmoid(double in);

public:

	~CBackProp();

//	initializes and allocates memory
	CBackProp(int nl,int *sz,double b,double a);

//	backpropogates error for one set of input //反向传播
	void bpgt(double *in,double *tgt);

//	feed forwards activations for one set of inputs //前向激活
	void ffwd(double *in);

//	returns mean square error of the net //均方误差
	double mse(double *tgt) const;	
	
//	returns i'th output of the net //输出
	double Out(int i) const;
};

#endif


backprop.cpp

#include "backprop.h"
#include <time.h>
#include <stdlib.h>


//	initializes and allocates memory on heap
CBackProp::CBackProp(int nl,int *sz,double b,double a)
	: beta(b)
	, alpha(a)
{
	int i=0;

	//	set no of layers and their sizes //网络层数和节点数
	numl = nl;
	lsize = new int[numl];

	for(int i=0;i<numl;i++)
	{
		lsize[i] = sz[i];
	}

	//	allocate memory for output of each neuron
	out = new double*[numl];
	for( i=0;i<numl;i++)
	{
		out[i] = new double[lsize[i]];
	}

	//	allocate memory for delta
	delta = new double*[numl];
	for(i=1;i<numl;i++)
	{
		delta[i] = new double[lsize[i]];
	}

	//	allocate memory for weights
	weight = new double**[numl];
	for(i=1;i<numl;i++)
	{
		weight[i] = new double*[lsize[i]];
	}
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			weight[i][j] = new double[lsize[i-1]+1];
		}
	}

	//	allocate memory for previous weights
	prevDwt = new double**[numl];
	for(i=1;i<numl;i++)
	{
		prevDwt[i] = new double*[lsize[i]];
	}
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			prevDwt[i][j] = new double[lsize[i-1]+1];
		}
	}

	//	seed and assign random weights
	srand((unsigned)(time(NULL)));

	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			for(int k=0;k<lsize[i-1]+1;k++)
			{
				weight[i][j][k] = (double)(rand())/(RAND_MAX/2) - 1;//32767
			}
		}
	}

	//	initialize previous weights to 0 for first iteration
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			for(int k=0;k<lsize[i-1]+1;k++)
			{
				prevDwt[i][j][k] = (double)0.0;
			}
		}
	}

// Note that the following variables are unused,
//
// delta[0]
// weight[0]
// prevDwt[0]

//  I did this intentionaly to maintains consistancy in numbering the layers.
//  Since for a net having n layers, input layer is refered to as 0th layer,
//  first hidden layer as 1st layer and the nth layer as output layer. And 
//  first (0th) layer just stores the inputs hence there is no delta or weigth
//  values corresponding to it.
}



CBackProp::~CBackProp()
{
	int i=0;
	//	free out
	for(i=0;i<numl;i++)
	{
		delete[] out[i];
	}
	delete[] out;

	//	free delta
	for(i=1;i<numl;i++)
	{
		delete[] delta[i];
	}
	delete[] delta;

	//	free weight
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			delete[] weight[i][j];
		}
	}
	for(i=1;i<numl;i++)
	{
		delete[] weight[i];
	}
	delete[] weight;

	//	free prevDwt
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			delete[] prevDwt[i][j];
		}
	}
	for(i=1;i<numl;i++)
	{
		delete[] prevDwt[i];
	}
	delete[] prevDwt;

	//	free layer info
	delete[] lsize;
}

//	sigmoid function //激活函数
double CBackProp::sigmoid(double in)
{
	return (double)(1/(1+exp(-in)));
}

//	mean square error //均方差
double CBackProp::mse(double *tgt) const
{
	double mse=0;
	for(int i=0;i<lsize[numl-1];i++)
	{
		mse += (tgt[i]-out[numl-1][i])*(tgt[i]-out[numl-1][i]);
	}
	return mse/2;
}


//	returns i'th output of the net //输出网络结果
double CBackProp::Out(int i) const
{
	return out[numl-1][i];
}

// feed forward one set of input //对输入进行前向传播计算
void CBackProp::ffwd(double *in)
{
	double sum;
	int i=0;

	//	assign content to input layer
	for(i=0;i<lsize[0];i++)
	{
		out[0][i]=in[i];  // output_from_neuron(i,j) Jth neuron in Ith Layer
	}

	//	assign output(activation) value 
	//	to each neuron usng sigmoid func
	for(i=1;i<numl;i++)
	{// For each layer
		for(int j=0;j<lsize[i];j++)
		{// For each neuron in current layer
			sum = 0.0;
			for(int k=0;k<lsize[i-1];k++)
			{// For input from each neuron in preceeding layer
				sum += out[i-1][k]*weight[i][j][k];	// Apply weight to inputs and add to sum
			}
			sum += weight[i][j][lsize[i-1]];		// Apply bias
			out[i][j] = sigmoid(sum);				// Apply sigmoid function
		}
	}
}


//	backpropogate errors from output
//	layer uptill the first hidden layer //反向传播误差直到第一个隐含层
void CBackProp::bpgt(double *in,double *tgt)
{
	double sum;
	int i=0;

	//	update output values for each neuron
	ffwd(in);

	//	find delta for output layer
	for(i=0;i<lsize[numl-1];i++)
	{
		delta[numl-1][i] = out[numl-1][i]*
			(1-out[numl-1][i])*(tgt[i]-out[numl-1][i]);
	}

	//	find delta for hidden layers	
	for(i=numl-2;i>0;i--)
	{
		for(int j=0;j<lsize[i];j++)
		{
			sum = 0.0;
			for(int k=0;k<lsize[i+1];k++)
			{
				sum += delta[i+1][k]*weight[i+1][k][j];
			}
			delta[i][j] = out[i][j]*(1-out[i][j])*sum;
		}
	}

	//	apply momentum ( does nothing if alpha=0 )
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			for(int k=0;k<lsize[i-1];k++)
			{
				weight[i][j][k] += alpha*prevDwt[i][j][k];
			}
			weight[i][j][lsize[i-1]] += alpha*prevDwt[i][j][lsize[i-1]];
		}
	}

	//	adjust weights usng steepest descent	
	for(i=1;i<numl;i++)
	{
		for(int j=0;j<lsize[i];j++)
		{
			for(int k=0;k<lsize[i-1];k++)
			{
				prevDwt[i][j][k] = beta*delta[i][j]*out[i-1][k];
				weight[i][j][k] += prevDwt[i][j][k];
			}
			prevDwt[i][j][lsize[i-1]] = beta*delta[i][j];
			weight[i][j][lsize[i-1]] += prevDwt[i][j][lsize[i-1]];
		}
	}
}

main.cpp

#include "BackProp.h"
// NeuralNet.cpp : Defines the entry point for the console application.
//


//异或运算
int main(int argc, char* argv[])
{
	// prepare XOR traing data //训练数据,最后一列为结果
	double data[][4] = {
				0,	0,	0,	0,
				0,	0,	1,	1,
				0,	1,	0,	1,
				0,	1,	1,	0,
				1,	0,	0,	1,
				1,	0,	1,	0,
				1,	1,	0,	0,
				1,	1,	1,	1 };

	// prepare test data //测试数据
	double testData[][3]={
				0,  0,  0, //0
                0,  0,  1, //1
                0,  1,  0, //1
                0,  1,  1, //0
                1,  0,  0, //1
                1,  0,  1, //0
                1,  1,  0, //0
                1,  1,  1 };//1

	
	// defining a net with 4 layers having 3,3,3, and 1 neuron respectively,
	// the first layer is input layer i.e. simply holder for the input parameters
	// and has to be the same size as the no of input parameters, in out example 3
	int numLayers = 4, lSz[4] = {3,3,2,1}; //网络层数、每层节点个数

	
	// Learing rate - beta //学习率
	// momentum - alpha //动量
	// Threshhold - thresh (value of target mse, training stops once it is achieved) //精度阈值
	double beta = 0.3, alpha = 0.1, Thresh =  0.00001;

	
	// maximum no of iterations during training
	long num_iter = 2000000; //最大轮数

	
	// Creating the net
	CBackProp *bp = new CBackProp(numLayers, lSz, beta, alpha);
	
	long i=0;
	cout<< endl <<  "Now training the network...." << endl;	
	for (i=0; i<num_iter; i++)//训练轮数
	{
		
		bp->bpgt(data[i%8], &data[i%8][3]);//反向传播调整网络权重

		if( bp->mse(&data[i%8][3]) < Thresh )//达到精度,停止训练
		{
			cout << endl << "Network Trained. Threshold value achieved in " << i << " iterations." << endl;
			cout << "MSE:  " << bp->mse(&data[i%8][3]) 
				 <<  endl <<  endl;
			break;
		}
		if ( i%(num_iter/10) == 0 )//每10轮输出训练结果
		{
			cout<<  endl <<  "MSE:  " << bp->mse(&data[i%8][3]) 
				<< "... Training..." << endl;
		}
	}
	
	if ( i == num_iter )
	{
		cout << endl << i << " iterations completed..." 
		<< "MSE: " << bp->mse(&data[(i-1)%8][3]) << endl;
	}

	cout<< "Now using the trained network to make predctions on test data...." << endl << endl;	
	for ( i = 0 ; i < 8 ; i++ )
	{
		bp->ffwd(testData[i]);//对测试数据进行预测
		cout << testData[i][0]<< "  " << testData[i][1]<< "  "  << testData[i][2]<< "  " << bp->Out(0) << endl;
	}

	return 0;
}



结果很好:



back propagation算法

https://www.zhihu.com/question/27239198#answer-31377317

<think>嗯,用户让我写一个从入门到精通的C语言学习笔记。首先,我需要确定用户的需求。他们可能是一个刚开始学习编程的新手,或者有一定基础想深入掌握C语言的人。用户可能希望有一个结构化的学习路径,涵盖基础到高级的内容,并且需要详细的例子和注意事项。 接下来,我得考虑如何组织内容。C语言的知识点很多,应该分阶段来介绍。入门部分需要包括环境搭建、基础语法、流程控制等。中级部分可以涉及函数、数组、指针这些核心概念。高级部分可能包括结构体、文件操作、动态内存管理等。最后,精通部分可能需要深入内存管理、多文件编程、预处理指令和底层开发知识。 然后,要确保符合用户提到的格式要求。所有的数学表达式和代码示例需要正确使用$和$$符号。比如,变量名需要用$包裹,独立公式如指针声明示例需要用$$。同时,回答需要用中文,结构清晰,分步骤讲解。 还要检查LaTeX语法是否正确,避免格式错误。比如,在指针部分,声明int *p = &a;需要用$$包裹,并且正确换行。代码示例部分可能需要使用代码块,但用户没有特别说明,所以可能用中文描述更合适。 另外,用户可能希望学习笔记不仅包含语法,还有常见错误和调试技巧。比如,在指针部分强调初始化和野指针的问题,或者数组越界的注意事项。这些实用技巧能帮助用户避免常见陷阱,提升代码质量。 还需要考虑是否覆盖实际应用,比如文件操作和数据结构。用户提到“精通”,可能对底层开发和项目实践有兴趣,所以包括内存布局、位运算、系统调用等内容是必要的。同时,推荐经典书籍和练习方法,帮助用户持续提升。 最后,总结部分要简明扼要,强调坚持和实践的重要性。整个思考过程需要逻辑清晰,逐步深入,确保用户能够跟随学习路径,从基础到高级系统性地掌握C语言。</think>以下是为您整理的C语言学习笔记(入门→精通),采用结构化框架+核心要点+实践技巧的形式: # C语言系统学习笔记 ## 一、入门阶段 ### 1. 开发环境搭建 - 编译器选择:GCC(Linux/Mac)、MinGW(Windows)、Visual Studio -一个程序: ```c #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } ``` - 编译命令:`gcc -o hello hello.c` ### 2. 基础语法 - 数据型: $$ \text{基本型} = \{int, float, double, char\} $$ $$ \text{修饰符} = \{short, long, signed, unsigned\} $$ - 运算符优先级:算术运算符 > 关系运算符 > 逻辑运算符(`&&`优先级高于`||`) ### 3. 流程控制 - 循环结构对比: ```c // for循环(已知次数) for(int i=0; i<10; i++){...} // while循环(条件优先) while(condition){...} // do-while(至少执行一次) do{...} while(condition); ``` ## 二、中级阶段 ### 1. 函数与模块化 - 函数声明 vs 定义: ```c // 声明 int add(int a, int b); // 定义 int add(int a, int b) { return a + b; } ``` - 递归实现示例(斐波那契数列): $$ f(n) = \begin{cases} 0 & n=0 \\ 1 & n=1 \\ f(n-1)+f(n-2) & n>1 \end{cases} $$ ### 2. 数组与指针 - 指针核心概念: $$ \text{指针变量} = \&变量 \Rightarrow \text{存储地址的变量} $$ ```c int a = 10; int *p = &a; // p指向a的地址 *p = 20; // 通过指针修改值 ``` - 数组与指针关系: $$ arr[i] \equiv *(arr + i) $$ ### 3. 结构体与联合 - 结构体内存对齐原则: $$ \text{对齐量} = \max(\text{成员大小}) $$ ```c struct Student { char name[20]; // 20字节 int age; // 4字节(对齐到4的倍数) }; // 总大小24字节 ``` ## 三、高级阶段 ### 1. 文件操作 - 文件模式对比: | 模式 | 描述 | 文件存在 | 文件不存在 | |------|-----------------------|----------|------------| | "r" | 只读 | 打开 | 错误 | | "w" | 写入(清空) | 创建 | 创建 | | "a" | 追加 | 打开 | 创建 | ### 2. 动态内存管理 - 内存操作函数: ```c int *arr = (int*)malloc(10 * sizeof(int)); // 申请 arr = realloc(arr, 20 * sizeof(int)); // 扩容 free(arr); // 释放 ``` - 常见错误: $$ \text{内存泄漏} \Rightarrow \text{忘记free} $$ $$ \text{悬垂指针} \Rightarrow \text{释放后继续使用} $$ ## 四、精通阶段 ### 1. 底层开发进阶 - 位运算技巧: $$ \text{奇偶判断} \rightarrow x \& 1 $$ $$ \text{交换变量} \rightarrow a \hat{}= b; b \hat{}= a; a \hat{}= b; $$ ### 2. 多文件编程 - 头文件规范: ```c #ifndef MYHEADER_H #define MYHEADER_H // 声明内容 #endif ``` ## 五、学习建议 1. 经典教材:《C程序设计语言》(K&R) 2. 练习平台:LeetCode、PTA 3. 调试工具:GDB、Valgrind(内存检测) 4. 项目实践:实现小型计算器、文件压缩工具 > 注:学习周期建议(每周10小时): > $$ \text{入门}(1个月) \rightarrow \text{进阶}(2个月) \rightarrow \text{精通}(6个月) $$ 附:常见错误排查表 | 错误现象 | 可能原因 | |----------------------|------------------------| | 段错误 | 空指针/数组越界 | | 输出乱码 | 字符串未正确终止 | | 程序卡死 | 死循环/未释放资源 | 通过系统性学习+项目实践,配合调试工具的使用,可以逐步掌握C语言的精髓。建议在Linux环境下深入学习,更能体会C语言与操作系统的紧密联系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值