希望本篇文章对你有所帮助 ~ (●’◡’●) ~ 本次部分代码数据以1维呈现(如有需要对应修改即可)
根据c语言特点去设置参数
由于c语言本身可选择的数据类型有限,在为了满足可扩展相同结构的数据以及方便后期修改参数,所以选用结构体创建一个类别的数据。
比如常用的输入层参数结构体
struct input { int numbers; //数据个数:该层神经元(数据)个数 double* values; //数据指针:传入的数据 double* restart; //指针起点:保留数据指针的起始地址,为后边的操作保留返回操作 }; //创建一个输入层数据结构 struct input *input_set = (struct input*)malloc(sizeof(struct input));//记得分配指针地址,否则是垃圾地址c语言中,要实现传入数据的修改无非就是使用指针传入,进而修改地址上的数据;当然返回值也可以,但是在多个数据时,返回的方法就不适用了。
这里主要涉及两种指针
数据指针——作为实参和形参函数指针——传入方法:如激活函数等比如常见的sigmoid函数和数据指针传入函数中:
/*****************************************/ //函数名称:sigmoid 激活函数 //函数原型: 1/(1 + exp(-u)) //输入参数: 一个double变量 //input_elem: double y_input //返回值:double /*****************************************/ double sigmoid_f(double y_input) { y_input = 1 + exp(-y_input); return 1/y_input; } /*****************************************/ //函数名称:输入层(input_layer)函数 //输入参数: 输入数据数组, 数据个数(神经元个数), 激活函数, 输入元素结构体 //input_elem: double* input_data, int data_lenth, double (*f)(), struct input *input_struct //返回值:无 /*****************************************/ void input_layer(double* input_data, int data_lenth,double (*f)(), struct input* input_struct); //部分使用过程 #define input_size 12 //定义输入层参数个数_即神经网络输入层神经元个数 struct input *input_set = (struct input*)malloc(sizeof(struct input)); double input[input_size]={0,}; // 输入数据的存储空间 input_layer(input, input_size,sigmoid_f, input_set);//将数据传入函数进行输入层的数据处理操作根据网络需要去分类需要的函数
在c语言中,数据的输入有两种,文件读入再转换为数值;或者键入文件。
这部分函数很容易构思,不过需要提到的是,如果对于读取的数据精度要求很高,不妨使用大数算法来进行字符数值计算(如大数相乘等),不过这样使用的话,应该别忘了最好所有数据都转换为大数类型,不然后期数据处理会比较麻烦。
这里就举例键入文件数据(假设数据个数在最多是65535个,单个数据精度不超过double)
/*****************************************/ //函数名称:输入值键入函数 //输入参数: 一个double数组/指针变量 一维数组大小(数据个数) //input_elem: double x_input int n //返回值:无 /*****************************************/ void key_input_data(double* x_input,int n) { double* temp = x_input; // 保留初始地址 for(int i = 0; i<n; i++) // 往地址向下一个一个的写数据 { scanf("%lf",x_input); x_input++; } x_input = temp; // 还原初始地址——重新指向 }c语言在函数构建方面还是很方便的,所以对于激活函数这种就可以直接进行构建。
比如激活函数和恒等y=x:
/*****************************************/ //函数名称:sigmoid 激活函数 //函数原型: 1/(1 + exp(-u)) //输入参数: 一个double变量 //input_elem: double y_input //返回值:double /*****************************************/ double sigmoid_f(double y_input) { y_input = 1 + exp(-y_input); return 1/y_input; } /*****************************************/ //函数名称:恒等函数 //函数原型: y = x //输入参数: 一个double变量 //input_elem: double y_input //返回值:double /*****************************************/ double y_x_f(double y_input) { return y_input; }阶层函数——也就是其它语言中的网络层函数,只不过,在这些网络层中的关联参数——如:权重,偏置等需要根据需要设置为全局变量,这样保证传入函数后可修改保存。
提示: 进行权重和偏置传入时,在训练完成后应该将这些数据进行保存。
比如全连接输入层函数:
/*****************************************/ //函数名称:输入层(input_layer)函数 //输入参数: 输入数据数组, 数据个数(神经元个数), 激活函数, 输入元素结构体 //input_elem: double* input_data, int data_lenth, double (*f)(), struct input *input_struct //返回值:无 /*****************************************/ void input_layer(double* input_data, int data_lenth,double (*f)(), struct input* input_struct) { int counts_data = 0; // 记录有用的数据个数,以方便后期打印数据 input_struct->restart = input_data; // 将输入层的参数结构体的restart指向a输入数据数组 input_struct->values = input_data; // 将输入层的参数结构体的values指向a输入数据数组 input_struct->numbers = data_lenth; // 将元素个数——即输入层神经元个数传给结构体的numbers counts_data = input_struct->numbers; // 赋给数据个数 printf("输入层参数分配完毕:(输入神经元个数为%d)\n",counts_data); while(counts_data--) // 处理并打印数据 { *(input_struct->values) = f(*(input_struct->values)); // 使输入数据经过激活函数 if(counts_data % 5 == 0) printf("%lf\n", *(input_struct->values)); else printf("%lf\t", *(input_struct->values)); input_struct->values++; //移到下一个元素 } input_struct->values = input_struct->restart; // 将values在出函数前重新指向数据第一位 printf("数据已传输到输入层!\n\n\n"); }以及简单的全连接中间层、输出层等:
/*****************************************/ //函数名称:中间层(hidden_layer)函数 //输入参数: 输入数据数组, 输入层权重数据, 偏置, 激活函数, 中间层暂存数组, 中间层元素结构体 //input_elem: struct input *input_struct, struct weight* weight_input, double bias_input, double (*f)(), double* hidden_data, struct hidden *hidden_struct //返回值:无 /*****************************************/ void hidden_layer(struct input *input_struct,struct weight* weight_input,double bias_input,double (*f)(),double* hidden_data,struct hidden* hidden_struct); /*****************************************/ //函数名称:输出层(output_layer)函数 //输入参数: 输入数据数组, 输入层权重数据, 偏置, 激活函数, 中间层暂存数组, 中间层元素结构体 //input_elem: struct input *hidden_struct, struct weight* weight_hidden, double bias_hidden, double (*f)(), double* hidden_data, struct hidden *output_struct //返回值:无 /*****************************************/ void output_layer(struct hidden* hidden_struct, struct weight* weight_hidden, double bias_hidden,double (*f)(), double* output_data, struct output* output_struct);如果你发现这部分的输入层中没有权重感到疑惑,那么说明你看得很仔细。 我在构写这部分代码时,考虑到输入层仅仅作为将输入数据直接通过激活函数后传入到输入层的神经元上即可。 并且,我对于数据处理的习惯通常是在传入input_layer之前进行数据的清洗和处理的。 每个人有每个人的想法,你只需要按照自己的思路去大胆尝试,趁年轻去犯犯错,得到更多的成长。
模块是大型设计中必不可少的部分,因为对于一个完整的项目,单独对需要的模块进行设计,然后再组合模块即可。
这里的话暂且建议使用结构体实现吧,维度不同的结构体怎么实现,这个就直接看代码吧。
创建不同维度的数据类型(结构体):1~4D
struct One_Dim { int numbers; double* values; double* restart; } struct Two_Dim { int numbers; double** values; double** restart; } struct Three_Dim { int numbers; double*** values; double*** restart; } struct Four_Dim { int numbers; double**** values; double**** restart; }除了创建结构体外,我们还应该在使用该类型的数据前,定义好对应的维度数据。(定义方式大致如下)
// 创建3d数据类型——千万记得在定义指针时要开辟一段确定的存储空间 struct Three_Dim* tensor_3d = (struct Three_Dim*)malloc(sizeof(struct Three_Dim)); //创建3d数组——存储缓存3d数据 double input_3d_data[10][10][5]={0,}; //将3d数据类型指向当前的数据缓存空间 tensor_3d->values = tensor_3d->restart = input_3d_data; //传入3d数据的个数 tensor_3d->numbers = sizeof(input_3d_data) / sizeof(double );这里对于连续层的定义是——完成类似tensorflow中的线性层一般进行可增可减的结构。
对于这个部分,如今由于部分限制因素暂无法提供参考的代码。
大致思路如下: 1.使用c语言中的不定长参数库,构建函数的入口为不定长参数——假设该函数为:layer_set(vargs layers……) 2.把需要的网络层依次放入layer_set中,然后在layer_set内部进行参数解析和功能实现 3.比如:layer_set(input_layer, hidden_layer,gidden_layer……),进行网络传入
对于以上文章内容,我希望的是将神经网络的C语言搭建说得简单清晰一些,但是也可能使得一些知识点遗漏的。有需要和问题可以留言,但是源码暂时因为一些特殊原因无法公开。
到这里,本篇博客就结束了。 我打算在后面一些时间对部分算法和网络层的搭建进行说明,不过因为一些原因,更新可能比较慢,但是我会尽力在每篇博客中将需要表现的主要思想和思路阐述,以供大家探讨。 也希望大家能够感受到,如果只是使用神经网络、深度学习这些AI技术,其实不难,但是也需要你静下心好好的领悟它的组成和原理。 不过分深究,不过分简略——相信你可以将已有的技术好好地掌握,成为一位优秀的工程师。