基于C++的简单BP神经网络(C++)

需求:在某些无网络的实验机器上,由于某些任务需求,需要拟合特定的函数,因此需要部署基于C++开发的神经网络,本文在不使用外部库的情况下,编写简单的神经网络,实现简单函数的拟合。

一、简介

本文描述了一个用C++编写的反向传播(Backpropagation)神经网络,该网络采用多层感知器(MLP)结构,解决分类和预测问题,使用梯度下降算法优化权重。

二、原理

BP神经网络由输入层、隐藏层和输出层组成。每个层中的节点通过权重连接到下一层的节点。网络接收输入数据,通过激活函数(本文采用Relu)处理并传递到输出层,然后与目标输出进行比较,利用误差进行反向传播,更新权重以最小化误差。
在这里插入图片描述

三、代码详解

  • 首先包含一些头文件,用于导入一些标准库和第三方库,例如iostreamfstreamvectorcmathrandom
    下面展示一些 内联代码片

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <cmath>
    #include <random>
    using namespace std;
    
  • 定义激活函数,用于给神经元的输出增加非线性,本文采用relu 函数,它的定义是

    relu(x)=max(0,x)
    
    double relu(double x) {
    return max(0.0, x);
    }
    
  • 定义一个神经元类,用于封装一个神经元的属性和方法,它包含以下几个成员变量和成员函数:

    • 权重向量,用于存储神经元的输入权重weights

    • 偏置值,用于增加神经元的灵活性bias

    • 输出值,用于存储神经元的激活值output

    • 构造函数,用于初始化权重和偏置为随机值Neuron(int input_size)

    • 前向传播函数,用于计算神经元的输出feedforward(const vector<double>& inputs)

    • 更新权重和偏置的函数,用于根据梯度下降法更新神经元的参数update(double delta, double learning_rate, const vector<double>& inputs)

      / 定义一个神经元类,包含权重、偏置和输出
      class Neuron {
      public:
          // 构造函数,初始化权重和偏置为随机值
          Neuron(int input_size) {
              random_device rd;
              mt19937 gen(rd());
              normal_distribution<> dis(0.0, 0.1);
              for (int i = 0; i < input_size; i++) {
                  weights.push_back(dis(gen));
              }
              bias = dis(gen);
              output = 0.0;
          }
      
          // 前向传播函数,计算输出
          void feedforward(const vector<double>& inputs) {
              double sum = 0.0;
              for (int i = 0; i < inputs.size(); i++) {
                  sum += inputs[i] * weights[i];
              }
              sum += bias;
              output = relu(sum);
          }
      
          // 返回输出
          double get_output() {
              return output;
          }
      
          // 返回权重
          vector<double> get_weights() {
              return weights;
          }
      
          // 返回偏置
          double get_bias() {
              return bias;
          }
      
          // 更新权重和偏置
          void update(double delta, double learning_rate, const vector<double>& inputs) {
              for (int i = 0; i < weights.size(); i++) {
                  weights[i] -= learning_rate * delta * inputs[i];
              }
              bias -= learning_rate * delta;
          }
          vector<double> weights;
          double bias;
          double output;
      };
      
  • 定义一个神经网络类,用于封装一个神经网络的属性和方法,例如NeuralNetwork类,它包含以下几个成员变量和成员函数:

    • 构造函数,用于初始化神经网络的结构和参数NeuralNetwork(int input_size, int hidden_size, int output_size)

    • 前向传播函数,用于计算神经网络的输出feedforward(const vector<double>& inputs)

    • 反向传播函数,用于根据误差反向传播算法更新神经网络的参数backprop(const vector<double>& inputs, const vector<double>& targets, double learning_rate)

    • 训练函数,本部分核心代码,输入参数包含三个部分(文件名,迭代次数,学习率),首先读取数据文件(本文读取“data.txt”),接着迭代更新神经网络的参数,然后将参数保存到文件train(const string& filename, int epochs, double learning_rate)

    • 预测函数,用于给定输入,输出神经网络的预测值predict(const vector<double>& inputs)

    • 加载函数,有了加载函数,就不用每次都进行训练,下次使用的时候直接将保存的参数文件进行加载,提高效率load(const string& filename)

      class NeuralNetwork {
      public:
          // 构造函数,初始化神经元
          NeuralNetwork(int input_size, int hidden_size, int output_size) {
              input_size_save = input_size;
              hidden_size_save = hidden_size;
              output_size_save = output_size;
              for (int i = 0; i < hidden_size; i++) {
                  hidden_layer.push_back(Neuron(input_size));
              }
              for (int i = 0; i < output_size; i++) {
                  output_layer.push_back(Neuron(hidden_size));
              }
          }
      
          // 前向传播函数,计算输出
          void feedforward(const vector<double>& inputs) {
              for (int i = 0; i < hidden_layer.size(); i++) {
                  hidden_layer[i].feedforward(inputs);
              }
              vector<double> hidden_outputs;
              for (int i = 0; i < hidden_layer.size(); i++) {
                  hidden_outputs.push_back(hidden_layer[i].get_output());
              }
              for (int i = 0; i < output_layer.size(); i++) {
                  output_layer[i].feedforward(hidden_outputs);
              }
          }
          // 反向传播函数,更新权重和偏置
          void backprop(const vector<double>& inputs, const vector<double>& targets, double learning_rate) {
              vector<double> output_errors;
              vector<double> hidden_errors;
              for (int i = 0; i < output_layer.size(); i++) {
                  double output = output_layer[i].get_output();
                  double error = (output - targets[i]) * (output > 0 ? 1 : 0);
                  output_errors.push_back(error);
              }
              for (int i = 0; i < hidden_layer.size(); i++) {
                  double hidden_output = hidden_layer[i].get_output();
                  double error = 0.0;
                  for (int j = 0; j < output_layer.size(); j++) {
                      error += output_errors[j] * output_layer[j].get_weights()[i];
                  }
                  error *= hidden_output > 0 ? 1 : 0;
                  hidden_errors.push_back(error);
              }
              for (int i = 0; i < output_layer.size(); i++) {
                  vector<double> hidden_outputs;
                  for (int j = 0; j < hidden_layer.size(); j++) {
                      hidden_outputs.push_back(hidden_layer[j].get_output());
                  }
                  output_layer[i].update(output_errors[i], learning_rate, hidden_outputs);
              }
              for (int i = 0; i < hidden_layer.size(); i++) {
                  hidden_layer[i].update(hidden_errors[i], learning_rate, inputs);
              }
          }
          // 训练函数,读取数据文件,进行迭代
          void train(const string& filename, int epochs, double learning_rate) {
              ifstream fin(filename);
              if (!fin) {
                  cout << "无法打开数据文件" << endl;
                  return;
              }
              vector<vector<double>> data;
              while (!fin.eof()) {
                  vector<double> row;
                  for (int i = 0; i < input_size_save+ output_size_save; i++) {
                      double x;
                      fin >> x;
                      row.push_back(x);
                  }
                  data.push_back(row);
              }
              fin.close();
              for (int e = 0; e < epochs; e++) {
                  double total_error = 0.0;
                  for (int i = 0; i < data.size(); i++) {
                      vector<double> inputs(data[i].begin(), data[i].begin() + 2);
                      vector<double> targets(data[i].begin() + 2, data[i].end());
                      feedforward(inputs);
                      backprop(inputs, targets, learning_rate);
                      for (int j = 0; j < output_layer.size(); j++) {
                          double output = output_layer[j].get_output();
                          double error = 0.5 * pow(output - targets[j], 2);
                          total_error += error;
                      }
                  }
                  cout << "Epoch " << e + 1 << ": Error = " << total_error << endl;
              }
              // 保存神经网络的参数到文件
              ofstream fout("net_params.txt");
              if (!fout) {
                  cout << "无法打开参数文件" << endl;
                  return;
              }
              // 保存输入层、隐藏层和输出层的大小
              int input_size= input_size_save; // 输入层的神经元数量
              int hidden_size = hidden_size_save; // 隐藏层的神经元数量
              int output_size= output_size_save; // 输出层的神经元数量
              fout << input_size << " " << hidden_size << " " << output_size << endl;
              // 保存隐藏层的权重和偏置
              for (int i = 0; i < hidden_layer.size(); i++) {
                  vector<double> weights = hidden_layer[i].get_weights();
                  double bias = hidden_layer[i].get_bias();
                  for (int j = 0; j < weights.size(); j++) {
                      fout << weights[j] << " ";
                  }
                  fout << bias << endl;
              }
              // 保存输出层的权重和偏置
              for (int i = 0; i < output_layer.size(); i++) {
                  vector<double> weights = output_layer[i].get_weights();
                  double bias = output_layer[i].get_bias();
                  for (int j = 0; j < weights.size(); j++) {
                      fout << weights[j] << " ";
                  }
                  fout << bias << endl;
              }
              fout.close();
              cout << "神经网络的参数已保存到net_params.txt文件" << endl;
          }
      
          // 预测函数,给定输入,输出预测值
          void predict(const vector<double>& inputs) {
              feedforward(inputs);
              for (int i = 0; i < output_layer.size(); i++) {
                  cout << "Output " << i + 1 << ": " << output_layer[i].get_output() << endl;
              }
          }
          // 加载函数,从文件中读取神经网络的参数
          void load(const string& filename) {
              ifstream fin(filename);
              if (!fin) {
                  cout << "无法打开参数文件" << endl;
                  return;
              }
              // 读取输入层、隐藏层和输出层的大小
              int input_size, hidden_size, output_size;
              fin >> input_size >> hidden_size >> output_size;
              // 重新初始化神经网络
              hidden_layer.clear();
              output_layer.clear();
              for (int i = 0; i < hidden_size; i++) {
                  hidden_layer.push_back(Neuron(input_size));
              }
              for (int i = 0; i < output_size; i++) {
                  output_layer.push_back(Neuron(hidden_size));
              }
              // 读取隐藏层的权重和偏置
              for (int i = 0; i < hidden_layer.size(); i++) {
                  vector<double> weights(input_size);
                  double bias;
                  for (int j = 0; j < input_size; j++) {
                      fin >> weights[j];
                  }
                  fin >> bias;
                  // 用读取的值覆盖原来的随机值
                  hidden_layer[i].weights = weights;
                  hidden_layer[i].bias = bias;
              }
              // 读取输出
              // 读取输出层的权重和偏置
              for (int i = 0; i < output_layer.size(); i++) {
                  vector<double> weights(hidden_size);
                  double bias;
                  for (int j = 0; j < hidden_size; j++) {
                      fin >> weights[j];
                  }
                  fin >> bias;
                  // 用读取的值覆盖原来的随机值
                  output_layer[i].weights = weights;
                  output_layer[i].bias = bias;
              }
              fin.close();
              cout << "神经网络的参数已从net_params.txt文件加载" << endl;
          }
      
      private:
          vector<Neuron> hidden_layer;
          vector<Neuron> output_layer;
          int input_size_save;
          int hidden_size_save;
          int output_size_save;
      };
      

四、主函数实现

最核心的部分:主函数

  • 创建神经网络对象nn,输入层大小为2,隐藏层大小为8,输出层大小为1,构建一个两输入,单输出的网络
  • 训练函数nn.train("data.txt", 2000, 0.001),数据来源来自data.txt文件,本文采用的是拟合y=x1+x2,训练2000次,学习率为0.001,学习完以后将参数保存为net_params.txt文件
  • 加载语句,用于调用神经网络的加载函数,例如nn.load("net_params.txt"),这里被注释掉了,表示不执行。如果之前将模型训练好了,可以将nn.train("data.txt", 2000, 0.001)进行注释,执行本语句,直接加载训练好的模型参数
  • 测试输入向量,用于存储测试输入的值,例如test_input = { 4, 5 }test_input = { 8, 8 }
  • 预测语句,用于调用神经网络的预测函数,例如nn.predict(test_input)
int main() {
    NeuralNetwork nn(2, 8, 1); // 输入层大小为2,隐藏层大小为8,输出层大小为1
    nn.train("data.txt", 2000, 0.001); // 数据文件为data.txt,迭代次数为100,学习率为0.01
   // nn.load("net_params.txt"); // 从文件中加载神经网络的参数
    vector<double> test_input = { 4, 5 }; // 测试输入
    nn.predict(test_input); // 输出预测值
    test_input = { 8, 8 }; // 测试输入
    nn.predict(test_input); // 输出预测值
    return 0;
}

可以检测输出结果为:
训练结果
可以看到,输出误差基本为0,神经网络的参数被保存,测试输出为9和16,完美预测!

附上完整代码(包含代码,训练数据,模型参数数据):
https://download.csdn.net/download/weixin_44346182/88628514

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/244163.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我常用的几个经典Python模块

Python常用的模块非常多&#xff0c;主要分为内置模块和第三方模块两大类&#xff0c;且不同模块应用场景不同又可以分为文本类、数据结构类、数学运算类、文件系统类、爬虫类、网络通讯类等多个类型。 大家常用的内置模块比如&#xff1a;math、re、datetime、urllib、os、ra…

CountDownLatch用法、详解

目录 ​编辑 概述&#xff1a; 应用场景&#xff1a; 优点&#xff1a; 缺点&#xff1a; 主要方法&#xff1a; 1. CountDownLatch(int count)&#xff1a; 2. void await()&#xff1a; 3. boolean await(long timeout, TimeUnit unit)&#xff1a; 4. void countDo…

【Python】计算一年内的总天数(还有跨年日期)

花了一段时间才找到Python中求一年中总日数&#xff08;total day of the Year&#xff09;的格式代码&#xff0c;所以也把计算方法记录下来。 基本 首先&#xff0c;简单地找出一年中的总天数&#xff0c; strftime() 和 strptime() 的格式代码是 %j ↓看这里 使用 strft…

基于当前实时云渲染的特点,用户体验主要受哪些因素影响?

在回答这个问题之前我们首先需要理解什么是实时云渲染&#xff1f; 点量实时云渲染是一种基于云计算低延迟传输&#xff0c;实现各种轻终端便捷使用云端大型软件和3D应用的一种云技术解决方案。这一技术解决方案通过将应用程序或内容置于云端服务器上运行&#xff0c;然后以视…

测试用例设计方法六脉神剑——第四剑:石破天惊,功能图法攻阵

1 引言 前面几篇文章为我们讲述了因果图、判定表、正交试验等几种方法&#xff0c;主要是针对于不同条件输入输出的组合进行测试&#xff0c;但在实际需求中&#xff0c;我们也常会遇到需要对被测对象的状态流转进行验证的情况&#xff0c;此时前面几种方法将不再适用&#xf…

测试用例设计方法:功能图

1 引言 前面几篇文章为我们讲述了因果图、判定表、正交试验等几种方法&#xff0c;主要是针对于不同条件输入输出的组合进行测试&#xff0c;但在实际需求中&#xff0c;我们也常会遇到需要对被测对象的状态流转进行验证的情况&#xff0c;此时前面几种方法将不再适用&#xf…

OpenHarmony 鸿蒙系统之开发环境安装

一、首先在下方链接网址中下载DevEco Studio的安装包。 DevEco Studio历史版本下载-HarmonyOS应用开发官网

Linux CentOS7 Docker安装Jenkins

1 sudo yum update #确保yum包更新到最新 service network restart #重启网络 2、查询镜像 docker search jenkins 3、拉取镜像 docker pull jenkins/jenkins #拉取镜像 4、创建Jenkins工作目录&#xff0c;并将容器内目录挂载到此目录…

23.12.10日总结

周总结 这周三的晚自习&#xff0c;学姐讲了一下git的合作开发&#xff0c;还有懒加载&#xff0c;防抖&#xff0c;节流 答辩的时候问了几个问题&#xff1a; 为什么在js中0.10.2!0.3? 在js中进行属性运算时&#xff0c;会出现0.10.20.300000000000000004js遵循IEEE754标…

CSS伪元素的特殊机制

概念 伪元素是CSS中的一种特殊机制&#xff0c;用于在元素的特定位置插入虚拟的内容。它们不是实际存在于HTML文档中的元素&#xff0c;而是通过CSS样式来创建和控制的。 伪元素使用双冒号&#xff08;::&#xff09;作为标识符&#xff0c;用于区分伪类选择器&#xff08;使…

Linux Shell——基本语法(变量、流程控制)

shell基本语法 一、变量二、流程控制 总结 最近学习了shell脚本&#xff0c;记录一下相关语法 一、变量 变量是很重要的&#xff0c;是用于存储数据值的容器 变量名要遵循以下规则&#xff1a; &#xff08;1&#xff09;只能包含字母、数字和下划线 &#xff08;2&#xff09…

鸿蒙开发组件之Web

一、加载一个url myWebController: WebviewController new webview.WebviewControllerbuild() {Column() {Web({src: https://www.baidu.com,controller: this.myWebController})}.width(100%).height(100%)} 二、注意点 2.1 不能用Previewer预览 Web这个组件不能使用预览…

Android camera的metadata

一、实现 先看一下metadata内部是什么样子&#xff1a; 可以看出&#xff0c;metadata 内部是一块连续的内存空间。 其内存分布大致可概括为&#xff1a; 区域一 &#xff1a;存 camera_metadata_t 结构体定义&#xff0c;占用内存 96 Byte 区域二 &#xff1a;保留区&#x…

Linux install manual 1Panel

前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。1Panel 的功能和优势包括: 快速建站:深度集成 Wordpress 和 Halo,域名绑定、SSL 证书配置等一键搞定;高效管理:通过 Web 端轻松管理 Linux 服务器,包括主机监控、文件管理、数据库管理、容器管理等;安全可…

Qt图像处理-Qt中配置OpenCV打开本地图片

本文讲解Qt中配置OpenCV过程并用实例展示如何使用OpenCV打开图片(windows环境下) 一、下载OpenCv 本文使用版本OpenCV-MinGW-Build-OpenCV-3.4.5 下载地址: https://codeload.github.com/huihut/OpenCV-MinGW-Build/zip/refs/heads/OpenCV-3.4.5 点击Code-local-Downlo…

大模型的实践应用13-量化后的通义千问Qwen的18亿参数在CPU上的部署,最小2GB显存可跑,并利用两种文本流式方式输出

大家好,我是微学AI,今天给大家介绍大模型的实践应用13-量化后的通义千问Qwen的18亿参数在CPU上的部署,最小2GB显存可跑,并利用两种文本流式方式输出。Qwen-1_8B-Chat是阿里云研发的通义千问大模型系列的18亿参数规模的模型。Qwen-1.8B是基于Transformer的大语言模型, 在超大…

LabVIEW进行癌症预测模型研究

LabVIEW进行癌症预测模型研究 癌症是一种细胞异常增生的疾病。随着年龄的增长&#xff0c;细胞分裂速度放缓&#xff0c;但癌细胞会失去控制地不断分裂&#xff0c;形成可能良性或恶性的肿瘤。 2012年的国际癌症数据显示&#xff0c;新发癌症病例和癌症相关死亡人数有所增加。…

.NET 开发人员,迎接高薪的挑战,你准备好了吗?

我发现我对编程的热情深深植根于我对逻辑的偏好。加入CSDN,标志着进入 .NET 开发人员世界的激动人心的旅程的开始。下面我与您分享我的故事。 编程之路 我大学是主修通信计算机创新&#xff0c;各种各样的选修课程&#xff0c;从平面设计、UX/UI 设计、数字营销到电影&#x…

在Node.js中MongoDB删除数据的方法

本文主要介绍在Node.js中MongoDB删除数据的方法。 目录 Node.js中MongoDB删除数据使用mongodb库删除数据使用Mongoose库删除数据 Node.js中MongoDB删除数据 在Node.js中&#xff0c;可以使用mongodb和Mongoose库来连接和操作MongoDB数据库。 下面是分别使用这两个库在MongoDB中…

双端队列和优先级队列

文章目录 前言dequedeque底层设计迭代器设计 priority仿函数数组中的第k个最大元素优先级队列模拟实现pushpop调整仿函数存储自定义类型 前言 今天要介绍比较特殊的结构&#xff0c;双端队列。 还有一个适配器&#xff0c;优先级队列。 deque 栈的默认容器用了一个deque的东西…