使用 C++ 实现神经网络:从基础到高级优化

引言

在现代机器学习中,神经网络已经成为最重要的工具之一。虽然 Python 提供了诸如 TensorFlow、PyTorch 等强大的机器学习库,但如果你想深入理解神经网络的实现原理,或者出于某些性能、资源限制的考虑,使用 C++ 来实现神经网络会是一个非常好的选择。

本文将从基础开始,逐步构建一个简单的神经网络框架,并在过程中展示如何通过 C++ 的高级特性(如面向对象设计、现代 C++ 特性等)优化神经网络的实现。最终,我们将构建一个灵活且高效的神经网络框架。

1. C++ 神经网络的基础设计

1.1 神经网络的基本结构

神经网络由多个层(Layer)组成,每一层包含多个神经元。神经网络通过前向传播(Forward Propagation)将输入数据传递给每一层,在每一层中,数据通过加权和加偏置后通过激活函数处理。最后,输出结果通过反向传播(Backpropagation)进行更新,逐步调整网络的参数(权重和偏置)。

1.2 简单的实现:单隐层神经网络

首先,我们实现一个简单的单隐层神经网络。网络包含一个输入层、一个隐藏层和一个输出层。我们将使用 Sigmoid 激活函数。

#include <iostream>
#include <vector>
#include <cmath>
#include <cassert>// Tensor 类,用于表示矩阵或张量
class Tensor {
public:Tensor(int rows, int cols) : rows_(rows), cols_(cols) {data_ = std::vector<std::vector<float>>(rows, std::vector<float>(cols, 0.0f));}float& at(int row, int col) { return data_[row][col]; }float at(int row, int col) const { return data_[row][col]; }int getRows() const { return rows_; }int getCols() const { return cols_; }void randomize() {for (int i = 0; i < rows_; ++i) {for (int j = 0; j < cols_; ++j) {data_[i][j] = (rand() % 100) / 100.0f;  // Random float between 0 and 1}}}private:int rows_, cols_;std::vector<std::vector<float>> data_;
};// 矩阵乘法
Tensor matmul(const Tensor& A, const Tensor& B) {assert(A.getCols() == B.getRows());Tensor result(A.getRows(), B.getCols());for (int i = 0; i < A.getRows(); ++i) {for (int j = 0; j < B.getCols(); ++j) {float sum = 0.0f;for (int k = 0; k < A.getCols(); ++k) {sum += A.at(i, k) * B.at(k, j);}result.at(i, j) = sum;}}return result;
}// 激活函数:Sigmoid
float sigmoid(float x) {return 1.0f / (1.0f + exp(-x));
}// Sigmoid 的导数
float sigmoid_derivative(float x) {return x * (1.0f - x);
}// 神经网络类
class NeuralNetwork {
public:NeuralNetwork(int input_size, int hidden_size, int output_size) {// 初始化权重和偏置weights_input_hidden = Tensor(input_size, hidden_size);weights_input_hidden.randomize();bias_hidden = Tensor(1, hidden_size);bias_hidden.randomize();weights_hidden_output = Tensor(hidden_size, output_size);weights_hidden_output.randomize();bias_output = Tensor(1, output_size);bias_output.randomize();}Tensor forward(const Tensor& input) {// 输入层到隐藏层Tensor hidden = matmul(input, weights_input_hidden);add_bias(hidden, bias_hidden);apply_sigmoid(hidden);// 隐藏层到输出层Tensor output = matmul(hidden, weights_hidden_output);add_bias(output, bias_output);apply_sigmoid(output);return output;}void backward(const Tensor& input, const Tensor& target, float learning_rate) {Tensor output = forward(input);Tensor output_error = compute_error(output, target);// 计算隐藏层误差Tensor hidden_error = matmul(output_error, transpose(weights_hidden_output));for (int i = 0; i < hidden_error.getRows(); ++i) {for (int j = 0; j < hidden_error.getCols(); ++j) {hidden_error.at(i, j) *= sigmoid_derivative(output.at(i, j));  // Sigmoid 导数}}// 更新权重和偏置update_weights(weights_hidden_output, output_error, learning_rate);update_bias(bias_output, output_error, learning_rate);update_weights(weights_input_hidden, hidden_error, learning_rate);update_bias(bias_hidden, hidden_error, learning_rate);}private:Tensor weights_input_hidden, weights_hidden_output;Tensor bias_hidden, bias_output;// 辅助函数:应用 Sigmoid 激活函数void apply_sigmoid(Tensor& tensor) {for (int i = 0; i < tensor.getRows(); ++i) {for (int j = 0; j < tensor.getCols(); ++j) {tensor.at(i, j) = sigmoid(tensor.at(i, j));}}}// 辅助函数:添加偏置void add_bias(Tensor& tensor, const Tensor& bias) {for (int i = 0; i < tensor.getRows(); ++i) {for (int j = 0; j < tensor.getCols(); ++j) {tensor.at(i, j) += bias.at(0, j);}}}// 计算误差Tensor compute_error(const Tensor& output, const Tensor& target) {Tensor error(output.getRows(), output.getCols());for (int i = 0; i < output.getRows(); ++i) {for (int j = 0; j < output.getCols(); ++j) {error.at(i, j) = output.at(i, j) - target.at(i, j);  // MSE}}return error;}// 转置矩阵Tensor transpose(const Tensor& tensor) {Tensor transposed(tensor.getCols(), tensor.getRows());for (int i = 0; i < tensor.getRows(); ++i) {for (int j = 0; j < tensor.getCols(); ++j) {transposed.at(j, i) = tensor.at(i, j);}}return transposed;}// 更新权重void update_weights(Tensor& weights, const Tensor& error, float learning_rate) {for (int i = 0; i < weights.getRows(); ++i) {for (int j = 0; j < weights.getCols(); ++j) {weights.at(i, j) -= learning_rate * error.at(i, j);}}}// 更新偏置void update_bias(Tensor& bias, const Tensor& error, float learning_rate) {for (int i = 0; i < bias.getCols(); ++i) {bias.at(0, i) -= learning_rate * error.at(0, i);}}
};

1.3 训练神经网络

神经网络的训练过程包括多个步骤:

  1. 前向传播:输入数据经过每一层的计算,最终得出网络输出。
  2. 反向传播:计算输出误差,利用链式法则反向计算各层的梯度。
  3. 更新权重和偏置:根据计算出来的梯度调整网络中的参数。
int main() {NeuralNetwork nn(2, 3, 1);  // 输入层2个节点,隐藏层3个节点,输出层1个节点// 训练数据:XOR 问题Tensor inputs(4, 2);inputs.at(0, 0) = 0.0f; inputs.at(0, 1) = 0.0f;inputs.at(1, 0) = 0.0f; inputs.at(1, 1) = 1.0f;inputs.at(2, 0) = 1.0f; inputs.at(2, 1) = 0.0f;inputs.at(3, 0) = 1.0f; inputs.at(3, 1) = 1.0f;Tensor targets(4, 1);targets.at(0, 0) = 0.0f;targets.at(1, 0) = 1.0f;targets.at(2, 0) = 1.0f;targets.at(3, 0) = 0.0f;// 训练神经网络并打印误差for (int epoch = 0; epoch < 10000; ++epoch) {nn.backward(inputs, targets, 0.1f);if (epoch % 1000 == 0) {Tensor result = nn.forward(inputs);float error = 0.0f;for (int i = 0; i < result.getRows(); ++i) {error += fabs(result.at(i, 0) - targets.at(i, 0));}std::cout << "Epoch " << epoch << " - Error: " << error << std::endl;}}// 测试结果std::cout << "\nPredictions after training:" << std::endl;Tensor result = nn.forward(inputs);for (int i = 0; i < result.getRows(); ++i) {std::cout << "Input: (" << inputs.at(i, 0) << ", " << inputs.at(i, 1) << ") -> Predicted Output: "<< result.at(i, 0) << " (Expected: " << targets.at(i, 0) << ")" << std::endl;}return 0;
}

训练过程示例输出:

Epoch 0 - Error: 3.14223
Epoch 1000 - Error: 1.20052
Epoch 2000 - Error: 0.92576
Epoch 3000 - Error: 0.74195
Epoch 4000 - Error: 0.62143
Epoch 5000 - Error: 0.53142
Epoch 6000 - Error: 0.46065
Epoch 7000 - Error: 0.40239
Epoch 8000 - Error: 0.35162
Epoch 9000 - Error: 0.30650Predictions after training:
Input: (0, 0) -> Predicted Output: 0.0243746 (Expected: 0)
Input: (0, 1) -> Predicted Output: 0.983215 (Expected: 1)
Input: (1, 0) -> Predicted Output: 0.984261 (Expected: 1)
Input: (1, 1) -> Predicted Output: 0.0229365 (Expected: 0)

解释:

  • 误差:误差随着训练进行逐渐减小,表示网络在学习过程中逐渐适应了数据。
  • 预测结果:经过训练后,网络能够较为准确地预测 XOR 数据的输出(0、1、1、0)。即使预测值与期望值之间可能有轻微差距,但在训练过程中,网络会继续优化,误差会变得越来越小。

2. 使用现代 C++ 特性优化

2.1 使用智能指针

在实际应用中,使用原始指针管理内存可能会带来内存泄漏等问题。通过使用 C++11 引入的 std::unique_ptrstd::shared_ptr,可以更加安全地管理内存。

#include <memory>class NeuralNetwork {
public:NeuralNetwork() {layers.push_back(std::make_unique<SigmoidLayer>(2, 3));layers.push_back(std::make_unique<SigmoidLayer>(3, 1));}Tensor forward(const Tensor& input) {Tensor output = input;for (const auto& layer : layers) {output = layer->forward(output);}return output;}void backward(const Tensor& input, const Tensor& target) {Tensor output = forward(input);Tensor error = output;for (int i = 0; i < error.getRows(); ++i) {for (int j = 0; j < error.getCols(); ++j) {error.at(i, j) -= target.at(i, j);  // MSE}}for (int i = layers.size() - 1; i >= 0; --i) {layers[i]->backward(input, error);error = layers[i]->error;}}void update_weights(float learning_rate) {for (const auto& layer : layers) {layer->update_weights(learning_rate);}}private:std::vector<std::unique_ptr<Layer>> layers;
};

2.2 并行化计算

对于大规模神经网络,计算量可能非常庞大,利用 C++ 的并行计算库(如 OpenMP)可以大幅提高计算效率。

#include <omp.h>void matmul_parallel(const Tensor& A, const Tensor& B, Tensor& result) {int rows = A.getRows();int cols = B.getCols();#pragma omp parallel forfor (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {result.at(i, j) = 0;for (int k = 0; k < A.getCols(); ++k) {result.at(i, j) += A.at(i, k) * B.at(k, j);}}}
}

3. 总结

本文介绍了如何使用 C++ 构建神经网络。通过从基础的神经网络构建、训练过程、优化策略,再到如何利用现代 C++ 特性进行性能优化,我们创建了一个简单但有效的神经网络实现。无论是出于学习目的还是性能需求,C++ 都是一种非常适合实现神经网络的编程语言,尤其是在需要高效计算和资源控制的应用中。

希望本文能帮助你更好地理解如何在 C++ 中实现神经网络,并掌握如何通过现代编程技术优化神经网络的性能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASA-Cluster集群模式

集群设备数量看授权 整体性能小于单个设备累加 广播选举 优先级1-100 选举完成 不抢占 集群状态 master、standby 接口模式 Spanned EtherChannel、IndividualInterface 心跳线 control link。 数据、控制层面 集群脑裂之后 设备需要手动加入集群 连接的三个角色&#xf…

用于 EV 牵引电机的先进冷却技术

电动汽车牵引电机的冷却挑战 热管理的重要性 有效的热管理在电动汽车 &#xff08;EV&#xff09; 设计中至关重要&#xff0c;尤其是在牵引电机方面。这些电机将电能转化为机械运动&#xff0c;对车辆的整体性能和效率至关重要。 管理它们的热量至关重要&#xff0c;不仅可以…

RK3568平台(USB篇)禁用USB端口

一.linux中怎样查看usb的端口号 在USB口插入U盘: [ 198.141319][ T106] usb 3-1.3: new SuperSpeed Gen 1 USB device number 5 using xhci-hcd [ 198.161695][ T106] usb 3-1.3: New USB device found, idVendor=0781, idProduct=5591, bcdDevice= 1.00 [ 198.161721]…

操作系统之文件的逻辑结构

目录 无结构文件&#xff08;流式文件&#xff09; 有结构文件&#xff08;记录式文件&#xff09; 分类&#xff1a; 顺序文件 特点&#xff1a; 存储方式&#xff1a; 逻辑结构&#xff1a; 优缺点&#xff1a; 索引文件 目的&#xff1a; 结构&#xff1a; 特点…

Redis Exporter 安装与配置指南(v1.67.0)

&#x1f680; 1. 下载 Redis Exporter 首先&#xff0c;登录到目标服务器&#xff0c;下载 Redis Exporter v1.67.0 安装包。 wget https://github.com/oliver006/redis_exporter/releases/download/v1.67.0/redis_exporter-v1.67.0.linux-amd64.tar.gz&#x1f4e6; 2. 解压…

Python 的网页自动化工具 DrissionPage 介绍

DrissionPage 介绍 视频教程 不要再学selenium了&#xff0c;DrissionPage更香_哔哩哔哩_bilibili不要再学selenium了&#xff0c;DrissionPage更香DrissionPage官网 https://www.drissionpage.cn/, 视频播放量 56768、弹幕量 12、点赞数 1012、投硬币枚数 503、收藏人数 316…

【HarmonyOS NEXT】鸿蒙应用使用后台任务之长时任务,解决屏幕录制音乐播放等操作不被挂起

【HarmonyOS NEXT】鸿蒙应用使用后台任务之长时任务&#xff0c;解决屏幕录制音乐播放等操作不被挂起 一、前言 1.后台是什么&#xff1f; 了解后台任务和长时任务前&#xff0c;我们需要先明白鸿蒙的后台特性&#xff1a;所谓的后台&#xff0c;指的是设备返回主界面、锁屏、…

nvm实现nodejs的版本管理

部分老旧项目需要使用低版本的node&#xff0c;网上很多是无效的&#xff0c;高版本无法直接安装低版本node&#xff0c;但是低版本nodejs可以安装部分高版本node&#xff0c;从而达到升级效果。 NVM安装 nvm是什么 nvm全英文也叫node.js version management&#xff0c;是一个…

计算机网络之---RIP协议

RIP协议的作用 RIP (Routing Information Protocol) 协议是一个基于距离矢量的路由协议&#xff0c;它在网络中用来动态地交换路由信息。RIP 是最早的路由协议之一&#xff0c;通常用于小型和中型网络中。它的工作原理简单&#xff0c;易于实现&#xff0c;但在一些大型网络中效…

代理模式详解与应用

代理模式&#xff08;Proxy Pattern&#xff09;&#xff0c;也称为委托模式或 surrogate 模式&#xff0c;是一种结构型设计模式。它为其他对象提供一个代理以控制对这个对象的访问。通过引入代理对象&#xff0c;可以在不改变原始对象接口的前提下&#xff0c;添加额外的功能…

基于华为ENSP的OSPF接口网络类型深入浅出(4)

本篇技术博文摘要 &#x1f31f; OSPF的接口在不同网络类型下的工作方式&#xff1b;不同网络类型下的报文通告方式深入浅出hub-spoke架构 引言 &#x1f4d8; 在这个快速发展的技术时代&#xff0c;与时俱进是每个IT人的必修课。我是肾透侧视攻城狮&#xff0c;一名什么都会一…

【计算机网络】什么是网关(Gateway)?

网上冲浪多了&#xff0c;你可以听到过网关&#xff08;Gateway&#xff09;这个词&#xff0c;但是却不太清楚网关&#xff08;Gateway&#xff09;到底是干什么的、负责网络当中的什么任务&#xff0c;本篇文字将会为你介绍网关&#xff08;Gateway&#xff09;的作用&#x…

Linux(Centos 7.6)命令详解:mkdir

1.命令作用 如果目录还不存在&#xff0c;则创建目录(Create the DIRECTORY, if they do not already exist.) 2.命令语法 Usage: mkdir [OPTION]... DIRECTORY... 3.参数详解 OPTION: -m, --modeMODE&#xff0c;创建新目录同时设置权限模式-p, --parents&#xff0c;创…

【深度学习】多目标融合算法(二):底部共享多任务模型(Shared-Bottom Multi-task Model)

目录 一、引言 1.1 往期回顾 1.2 本期概要 二、Shared-Bottom Multi-task Model&#xff08;SBMM&#xff09; 2.1 技术原理 2.2 技术优缺点 2.3 业务代码实践 三、总结 一、引言 在朴素的深度学习ctr预估模型中&#xff08;如DNN&#xff09;&#xff0c;通常以一个行…

TVbox 手机、智能电视节目一网打尽

文章目录 一、简要介绍二、优点三、下载地址 一、简要介绍 TVbox是目前最火爆的多端、多源的电视影音工具&#xff0c;是一款开源的自定义添加站源的影音工具。TVBox&#xff0c;支持电视频道直播。一款TV端影视工具&#xff0c;软件本身不具有任何影视资源&#xff0c;但可以…

2025新春烟花代码(二)HTML5实现孔明灯和烟花效果

效果展示 源代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34";var …

ue5 蒙太奇,即上半身动画和下半身组合在一起,并使用。学习b站库得科技

本文核心 正常跑步动画端枪动画跑起来也端枪 正常跑步动画 端枪动画的上半身 跑起来也端枪 三步走&#xff1a; 第一步制作动画蒙太奇和插槽 第二步动画蓝图选择使用上半身动画还是全身动画&#xff0c;将上半身端枪和下半身走路结合 第三步使用动画蒙太奇 1.开始把&a…

YOLOv8实战人员跌倒检测

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对人员跌倒目标数据集进行训练和优化&#xff0c;该数据集包含丰富人员跌倒图像样…

Java 分布式锁:Redisson、Zookeeper、Spring 提供的 Redis 分布式锁封装详解

&#x1f4da; Java 分布式锁&#xff1a;Redisson、Zookeeper、Spring 提供的 Redis 分布式锁封装详解 在分布式系统中&#xff0c;分布式锁 用于解决多个服务实例同时访问共享资源时的 数据一致性 问题。Java 生态中&#xff0c;有多种成熟的框架可以实现分布式锁&#xff0…

01.02、判定是否互为字符重排

01.02、[简单] 判定是否互为字符重排 1、题目描述 给定两个由小写字母组成的字符串 s1 和 s2&#xff0c;请编写一个程序&#xff0c;确定其中一个字符串的字符重新排列后&#xff0c;能否变成另一个字符串。 在这道题中&#xff0c;我们的任务是判断两个字符串 s1 和 s2 是…