弹性反向传播

本文介绍了弹性反向传播(Rprop)算法,解决了反向传播中的学习率选择难题和梯度弥散问题。Rprop为每个权重分配独立的学习率,权重更新不受误差函数梯度大小影响,仅受梯度符号影响,决定更新方向。同时,学习率会根据梯度符号变化动态调整,以优化搜索最小值的精度。

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

弹性反向传播算法学习笔记

Resilient backppropagation

正常使用的反向传播算法有两个缺点待解决,其一为学习过程中学习率的选择较难,一旦学习率选择不当会造成学习效果不好;其二为反向传播算法的梯度弥散作用,即距离输出层越远的神经元学习的速度越慢。
Martin Riedmiller也因此提出了弹性反向传播算法(Rprop)

1、学习率

反向传播算法中的学习率为用户提前设定的固定的η,并在整个网络中使用单一的η,因此就会出现学习率选择的问题,而在Rprop中,每一个可优化的权重都对应着一个单独的η,不同的权重的学习率不同,并且这些学习率并不是由用户指定,而是由程序自动的设定,这些学习率在程序执行过程中也并不是静态不变的,而是每一个学习时间点学习率都在不断地更新,即enter image description here

2、权重的更新

在一般的反向传播算法中,学习过程中权重的改变量是由误差函数对该权重的偏导(即梯度)所决定,直观上很有道理,However,We incorporate every jagged feature of the error surface,It is at least questionable.这句看不懂,总之有问题就是了啦啦啦而在Rprop中,权重的变化量enter image description here直接等于学

参考以上文件,用C语言神经网络模型,编写代码,预测成熟度。改成一下几种特征firmness hue saturation brightness color_category sound_db weight_g size_cm3 最后成熟度有5种程度 #include <iostream> #include "rapidcsv.h" #include <opencv2/opencv.hpp> using namespace std; using namespace cv; using namespace cv::ml; typedef struct { double x1,x2,x3,x4; //嘴峰长度、嘴峰深度、脚掌长度、 体重 int x5_0,x5_1; //是否male(1:male,0:female);是否famale(1:female,0:male) int label; //类别标签, 0:Adelie,1:Gentoo,2: Chinstrap } DataPoint; int ReadCsv(const char* fileName, DataPoint data[]) { rapidcsv::Document doc(fileName, rapidcsv::LabelParams(), rapidcsv::SeparatorParams(),rapidcsv::ConverterParams(true));//解析csv文件 int j=0;//样本计数 for (int i=0;i<doc.GetRowCount();i++){ //每次处理一行 double bill_length_mm = doc.GetCell<double>("bill_length_mm", i);//嘴峰长度 double bill_depth_mm = doc.GetCell<double>("bill_depth_mm", i);//嘴峰深度 double flipper_length_mm = doc.GetCell<double>("flipper_length_mm", i);//脚掌长度 double body_mass_g = doc.GetCell<double>("body_mass_g", i);//体重 std::string sex = doc.GetCell<std::string>("sex", i); //性别 std::string type_name = doc.GetCell<std::string>("species", i); //种族 if ( !std::isnan(bill_length_mm) && !std::isnan(bill_depth_mm) && !std::isnan(flipper_length_mm) && !std::isnan(body_mass_g) && sex!="NA" ){//处理缺失值 data[j].x1 = bill_length_mm;data[j].x2 = bill_depth_mm; data[j].x3 = flipper_length_mm;data[j].x4 = body_mass_g; data[j].x5_0 = sex=="male"?1:0; data[j].x5_1 = sex=="female"?1:0; if (type_name == "Adelie") data[j].label=0; else if (type_name == "Gentoo") data[j].label=1; else data[j].label=2; j++;//剔除缺失值后的样本数目 } } return j; } int main() { DataPoint data[500]; int n = ReadCsv("penguins.csv", data);//从文件读入数据 //将数据拷贝到Mat矩阵中 , 75%训练数据,25%测试数据 int n1=n*3/4, n2=n-n1; Mat train_samples(n1, 6, CV_32F); //训练数据集 Mat train_labels(n1, 3, CV_32F);//训练数据的标签(独热编码) Mat test_samples(n2, 6, CV_32F); //测试数据集 Mat test_labels(n2, 1, CV_32S);//测试数据的标签 int i=0, j=0, k=0; for(i = 0; i<n; i++){ if (i%4==0){//每3个训练数据搭配1个测试数据 test_samples.at<float>(j, 0) = data[i].x1;test_samples.at<float>(j, 1) = data[i].x2; test_samples.at<float>(j, 2) = data[i].x3;test_samples.at<float>(j, 3) = data[i].x4; test_samples.at<float>(j, 4) = data[i].x5_0;test_samples.at<float>(j, 5) = data[i].x5_1; test_labels.at<int>(j, 0) = data[i].label; j++; }else{ train_samples.at<float>(k, 0) = data[i].x1;train_samples.at<float>(k, 1) = data[i].x2; train_samples.at<float>(k, 2) = data[i].x3;train_samples.at<float>(k, 3) = data[i].x4; train_samples.at<float>(k, 4) = data[i].x5_0;train_samples.at<float>(k, 5) = data[i].x5_1; if (data[i].label==0) {//标签转换为独热编码 train_labels.at<float>(k, 0)=1;train_labels.at<float>(k, 1)=0;train_labels.at<float>(k, 2)=0; }else if (data[i].label==1) { train_labels.at<float>(k, 0)=0;train_labels.at<float>(k, 1)=1;train_labels.at<float>(k, 2)=0; }else{ train_labels.at<float>(k, 0)=0;train_labels.at<float>(k, 1)=0;train_labels.at<float>(k, 2)=1; } k++; } } cout<<"n="<<n<<",n1="<<n1<<",n2="<<n2<<endl; Ptr<ANN_MLP> nn = ANN_MLP::create(); //创建神经网络模型 nn->setLayerSizes(vector<int>{6, 10, 3});//输入层6个节点,隐藏层10个节点,输出层3个节点(假设3类企鹅) nn->setActivationFunction(ANN_MLP::SIGMOID_SYM);//激活函数目前只支持Sigmoid nn->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 10000, 1e-5));//设置迭代次数与收敛条件 nn->setTrainMethod(ANN_MLP::RPROP); //弹性反向传播算法 nn->train(train_samples, ROW_SAMPLE, train_labels);//训练模型 Mat predictions; //预测结果 nn->predict(test_samples,predictions);//对测试集进行预测 int correct = 0; //正确分类的次数 for (int i = 0; i < predictions.rows; i++){ Point max_loc; // 找到预测值中的最大索引,即预测的类别 minMaxLoc(predictions.row(i), nullptr, nullptr, nullptr, &max_loc); cout<<"predictions:"<<predictions.row(i)<<", max_loc.x:"<<max_loc.x<<", label:"<<test_labels.at<int>(i, 0)<<endl; if (max_loc.x == test_labels.at<int>(i, 0)) correct++; } double accuracy = (double)correct / predictions.rows; //计算准确率 cout<<"Accuracy: " << accuracy * 100 << "%" << endl; return 0; }
最新发布
06-13
<think>好的,用户希望用C语言实现一个神经网络模型,根据多个特征预测5种成熟度等级。需要包含数据读取、预处理、模型训练和预测的完整代码。之前已经给过一个简单的MLP示例,现在需要扩展输入特征和输出类别,并处理更复杂的数据预处理。首先,用户提到的特征有firmness、hue、saturation、brightness、color_category、sound_db、weight_g、size_cm3,共8个。输出是5个成熟度等级,所以输出层需要5个神经元,使用softmax激活函数和交叉熵损失函数。而之前的例子是回归问题,输出层只有一个神经元,现在需要改为多分类,这会影响损失函数和反向传播的计算。 数据预处理部分需要读取CSV文件,处理数值特征和类别特征。例如,color_category可能需要做独热编码。数值特征需要归一化到0-1之间。需要编写数据读取和预处理的函数,这在C语言中可能比较繁琐,需要处理文件读取、字符串分割等操作。模型结构方面,输入层8个节点,隐藏层可能需要更大的结构,比如16个节点,输出层5个节点。激活函数方面,隐藏层可以使用ReLU来缓解梯度消失,输出层用softmax。损失函数改用交叉熵,但要注意数值稳定性,可能需要对softmax的输出做调整。反向传播部分需要重新计算梯度,特别是softmax和交叉熵的组合导数。这需要数学推导,确保梯度计算正确。同时,C语言中需要手动实现这些计算,可能会比较复杂,容易出错。代码结构方面,需要将数据预处理、模型初始化、前向传播、反向传播、训练循环和预测部分整合起来。考虑到C语言没有动态数组,可能需要使用固定大小的数组或动态内存分配,但动态内存管理在C中容易出错,特别是对于新手来说。另外,用户可能需要保存和加载模型权重,以便后续使用。但当前示例可能没有实现,需要添加文件读写功能。还需要考虑训练数据的批量处理,之前的例子是逐个样本训练,效率较低。可以改为mini-batch,但会增加内存使用,对于嵌入式设备可能不太友好,需要权衡。在编写代码时,需要注意数值精度和计算溢出问题,特别是在计算指数函数时,softmax可能导致数值不稳定,需要减去最大值来处理。 最后,测试部分需要验证模型的准确率,计算预测结果与真实标签的匹配情况,并输出混淆矩阵或分类报告,但C语言中实现这些可能需要额外代码。 总结下来,步骤包括:数据读取和预处理,模型结构调整,损失函数和激活函数的更改,反向传播的调整,以及训练过程的优化。需要确保每一步在C语言中正确实现,并处理可能的错误和异常。</think>### C语言实现多特征成熟度分类神经网络 根据您的要求,我将提供一个基于8输入特征、5输出分类的神经网络实现方案。该方案包含数据预处理、网络结构设计、训练逻辑和预测模块,支持多分类任务(5种成熟度等级)。 --- ### 一、系统架构设计 $$ \text{系统流程} = \text{数据读取} \rightarrow \text{特征标准化} \rightarrow \text{独热编码} \rightarrow \text{前向传播} \rightarrow \text{交叉熵计算} \rightarrow \text{反向传播} $$ #### 网络结构参数: - 输入层:8个节点(对应8个特征) - 隐藏层:16个节点(使用ReLU激活) - 输出层:5个节点(使用Softmax分类) - 损失函数:交叉熵损失$L = -\sum_{i=1}^5 y_i \log(p_i)$[^1] --- ### 二、完整代码实现 ```c #include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #define INPUT_SIZE 8 // 输入特征数 #define HIDDEN_SIZE 16 // 隐藏层节点数 #define OUTPUT_SIZE 5 // 成熟度等级数 #define MAX_SAMPLES 1000 // 最大样本容量 #define EPOCHS 5000 #define LR 0.01 // 数据结构体 typedef struct { double features[INPUT_SIZE]; int category; // color_category int label; // 成熟度等级0-4 } Sample; // 激活函数 double relu(double x) { return x > 0 ? x : 0; } double relu_deriv(double x) { return x > 0 ? 1 : 0; } void softmax(double *x, int size) { double max = x[0]; for(int i=1; i<size; i++) if(x[i]>max) max=x[i]; double sum = 0; for(int i=0; i<size; i++) { x[i] = exp(x[i]-max); // 数值稳定性处理 sum += x[i]; } for(int i=0; i<size; i++) x[i] /= sum; } // 神经网络结构 typedef struct { // 权重矩阵 double w1[HIDDEN_SIZE][INPUT_SIZE]; double w2[OUTPUT_SIZE][HIDDEN_SIZE]; // 偏置向量 double b1[HIDDEN_SIZE]; double b2[OUTPUT_SIZE]; } NeuralNetwork; // 数据预处理 void preprocess(Sample *samples, int count) { // 数值特征归一化(示例,需根据实际数据调整) for(int i=0; i<count; i++) { samples[i].features[1] /= 360.0; // hue归一化到[0,1] samples[i].features[5] /= 100.0; // sound_db假设最大100dB } } // 前向传播 void forward(NeuralNetwork *net, double *input, double *output) { double hidden[HIDDEN_SIZE] = {0}; // 输入层 -> 隐藏层 for(int h=0; h<HIDDEN_SIZE; h++) { for(int i=0; i<INPUT_SIZE; i++) hidden[h] += input[i] * net->w1[h][i]; hidden[h] = relu(hidden[h] + net->b1[h]); } // 隐藏层 -> 输出层 for(int o=0; o<OUTPUT_SIZE; o++) { output[o] = 0; for(int h=0; h<HIDDEN_SIZE; h++) output[o] += hidden[h] * net->w2[o][h]; output[o] += net->b2[o]; } softmax(output, OUTPUT_SIZE); } // 训练函数 void train(NeuralNetwork *net, Sample *data, int samples) { for(int epoch=0; epoch<EPOCHS; epoch++) { double total_loss = 0; for(int n=0; n<samples; n++) { double input[INPUT_SIZE]; memcpy(input, data[n].features, sizeof(double)*INPUT_SIZE); // 处理类别特征(color_category) input[4] = data[n].category / 5.0; // 假设有5种颜色分类 // 前向传播 double output[OUTPUT_SIZE]; forward(net, input, output); // 计算交叉熵损失 double target[OUTPUT_SIZE] = {0}; target[data[n].label] = 1.0; for(int o=0; o<OUTPUT_SIZE; o++) total_loss += -target[o] * log(output[o]+1e-8); // 反向传播 double delta2[OUTPUT_SIZE]; for(int o=0; o<OUTPUT_SIZE; o++) delta2[o] = output[o] - target[o]; // 更新输出层权重 for(int o=0; o<OUTPUT_SIZE; o++) { for(int h=0; h<HIDDEN_SIZE; h++) net->w2[o][h] -= LR * delta2[o] * hidden[h]; net->b2[o] -= LR * delta2[o]; } // 计算隐藏层梯度 double delta1[HIDDEN_SIZE] = {0}; for(int h=0; h<HIDDEN_SIZE; h++) { for(int o=0; o<OUTPUT_SIZE; o++) delta1[h] += delta2[o] * net->w2[o][h]; delta1[h] *= relu_deriv(hidden[h]); } // 更新输入层权重 for(int h=0; h<HIDDEN_SIZE; h++) { for(int i=0; i<INPUT_SIZE; i++) net->w1[h][i] -= LR * delta1[h] * input[i]; net->b1[h] -= LR * delta1[h]; } } if(epoch % 500 == 0) printf("Epoch %d Loss: %.4f\n", epoch, total_loss/samples); } } // 主函数示例 int main() { // 初始化网络 NeuralNetwork net; // 权重初始化代码(类似前例,使用随机数初始化) // 读取数据(需实现CSV解析函数) Sample dataset[MAX_SAMPLES]; int count = load_csv("data.csv", dataset); // 数据预处理 preprocess(dataset, count); // 训练网络 train(&net, dataset, count); // 预测示例 double test_features[INPUT_SIZE] = {/* 测试数据 */}; double prediction[OUTPUT_SIZE]; forward(&net, test_features, prediction); return 0; } ``` --- ### 三、关键实现说明 1. **数据预处理**: - 数值特征归一化:例如将hue(色相)从$[0,360]$缩放到$[0,1]$ - 类别特征编码:`color_category`转换为数值型$0 \leq x \leq 1$ - 数据标准化公式:$x_{\text{norm}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}$ 2. **网络初始化**: - 使用He初始化方法:权重初始值范围$[-k, k]$,其中$k = \sqrt{\frac{2}{n_{\text{input}}}}$[^2] - 偏置初始化为0 3. **性能优化**: - 使用ReLU激活函数加速收敛:$f(x) = \max(0,x)$ - Softmax计算时减去最大值避免数值溢出:$x_i = e^{z_i - \max(z)}$ --- ### 四、使用指南 1. **数据文件格式**: ```csv firmness,hue,saturation,brightness,color_category,sound_db,weight_g,size_cm3,label 3.2,120,0.6,80,2,45,150,300,1 ... ``` 2. **编译运行**: ```bash gcc -O3 -lm nn.c -o nn # 启用编译器优化 ./nn ``` 3. **效果评估**: - 添加验证集准确率计算 - 输出混淆矩阵分析分类效果 --- ### 五、扩展建议 1. **动态内存管理**:使用`malloc`实现可变长度数据集 2. **批处理训练**:实现mini-batch梯度下降加速训练 3. **模型保存**:添加权重保存/加载函数
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值