使用 C++ 在深度学习中的应用:如何通过 C++20 构建高效神经网络
深度学习已经成为现代人工智能的核心技术,在图像识别、自然语言处理、语音识别等多个领域广泛应用。尽管 Python 因其简便易用和强大的深度学习框架(如 TensorFlow 和 PyTorch)而在这一领域占据主导地位,但 C++ 作为一门高性能语言,仍然在许多高效计算场景中有着不可忽视的优势。
在这篇文章中,我们将介绍如何使用 C++20 构建高效的神经网络。通过结合现代 C++ 特性,我们不仅能提升模型的计算效率,还能充分发挥 C++ 在性能优化方面的优势。
目录
1. C++ 神经网络设计基础
1.1 神经网络的基本结构
1.2 单隐层神经网络的实现
2. 使用现代 C++ 特性优化
2.1 智能指针与资源管理
2.2 并行计算加速
2.2.1 使用 std::for_each 实现并行计算
2.2.2 代码解析
2.2.3 性能提升
2.2.4 注意事项
3. 总结
1. C++ 神经网络设计基础
1.1 神经网络的基本结构
神经网络的核心结构通常包括输入层、隐藏层和输出层。每一层包含若干个神经元,数据通过前向传播(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; // 生成 0 到 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));}}// 更新权重和偏置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);}}
};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;
}
2. 使用现代 C++ 特性优化
2.1 智能指针与资源管理
C++ 引入了智能指针(如 std::unique_ptr 和 std::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 = layers.size() - 1; i >= 0; --i) {layers[i]->backward(input, error);error = layers[i]->error;}}private:std::vector<std::unique_ptr<Layer>> layers;
};
2.2 并行计算加速
在大规模神经网络训练和推理中,矩阵乘法是计算瓶颈之一。C++20 引入了 std::execution 标准库,提供了便捷的并行计算支持,使得我们能够通过并行化矩阵计算来加速深度学习模型的训练。通过将计算任务分配给多个处理器核心,可以显著提升计算速度,尤其是当数据量非常庞大的时候。
std::execution::par 是 C++20 并行算法的一部分,可以通过它使得某些算法(例如 std::for_each)并行执行,从而提高性能。通过这一特性,我们可以轻松地将矩阵乘法的计算并行化,实现显著的加速。
2.2.1 使用 std::for_each 实现并行计算
std::for_each 是一个算法,用于对指定范围的每个元素执行操作。在 C++20 中,我们可以指定 std::execution::par 来告知编译器我们希望对该范围内的元素进行并行处理。
为了实现并行矩阵乘法,我们将 std::for_each 应用于矩阵 result 的每个元素,在计算每个元素时,我们将其对应的行和列进行点积操作,从而计算出矩阵乘法的结果。
下面是一个细化的并行矩阵乘法实现:
#include <execution>
#include <vector>
#include <iostream>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_; }auto begin() { return data_.begin(); }auto end() { return data_.end(); }private:int rows_, cols_;std::vector<std::vector<float>> data_;
};// 并行矩阵乘法函数
void parallel_matrix_multiplication(const Tensor& A, const Tensor& B, Tensor& result) {int rowsA = A.getRows();int colsA = A.getCols();int rowsB = B.getRows();int colsB = B.getCols();if (colsA != rowsB) {std::cerr << "Matrix dimensions do not match for multiplication!" << std::endl;return;}// 使用并行执行计算每个结果元素std::for_each(std::execution::par, result.begin(), result.end(), [&](auto& element) {int row = &element - &result.at(0, 0); // 当前元素所在的行int col = &element - &result.at(0, 0); // 当前元素所在的列// 计算 A 行与 B 列的点积float sum = 0.0f;for (int k = 0; k < colsA; ++k) {sum += A.at(row, k) * B.at(k, col);}result.at(row, col) = sum;});
}int main() {Tensor A(2, 3); // A 为 2x3 矩阵Tensor B(3, 2); // B 为 3x2 矩阵Tensor C(2, 2); // 结果矩阵 C 为 2x2 矩阵// 初始化矩阵 A 和 BA.at(0, 0) = 1.0f; A.at(0, 1) = 2.0f; A.at(0, 2) = 3.0f;A.at(1, 0) = 4.0f; A.at(1, 1) = 5.0f; A.at(1, 2) = 6.0f;B.at(0, 0) = 7.0f; B.at(0, 1) = 8.0f;B.at(1, 0) = 9.0f; B.at(1, 1) = 10.0f;B.at(2, 0) = 11.0f; B.at(2, 1) = 12.0f;// 执行并行矩阵乘法parallel_matrix_multiplication(A, B, C);// 打印结果矩阵std::cout << "Matrix C (Result of A * B):" << std::endl;for (int i = 0; i < C.getRows(); ++i) {for (int j = 0; j < C.getCols(); ++j) {std::cout << C.at(i, j) << " ";}std::cout << std::endl;}return 0;
}
2.2.2 代码解析
- 矩阵表示:我们使用
Tensor类来表示矩阵。矩阵是一个二维数组,我们为每个矩阵元素提供了at()方法来访问其值。 - 并行化矩阵计算:在
parallel_matrix_multiplication函数中,我们使用了std::for_each(std::execution::par, ...)来并行计算result矩阵的每个元素。对于每个元素,我们计算其对应的行和列的点积,并将结果存储到result矩阵中。 - 元素定位:通过
&element - &result.at(0, 0),我们找到了当前元素的行和列索引。这样每个线程都能够独立处理一个矩阵元素,而不会产生数据竞争。 - 矩阵维度检查:在进行矩阵乘法之前,我们检查了矩阵的维度是否符合乘法要求(即
A的列数等于B的行数)。
2.2.3 性能提升
使用 std::execution::par 可以让我们充分利用现代 CPU 的多核架构。在多核处理器上,每个矩阵元素的计算任务都被分配到不同的线程上,从而加速了矩阵乘法的计算。当矩阵的规模很大时,这种并行化带来的加速效果更加明显。
2.2.4 注意事项
- 线程安全:由于每个线程处理矩阵中的不同元素,因此不会发生数据竞争,保证了线程安全。
- 负载均衡:并行算法的效果依赖于负载的均衡。在大规模矩阵计算中,
std::for_each会根据 CPU 核心的数量自动分配任务,从而提升计算效率。
3. 总结
本文通过 C++20 展示了如何从头开始构建一个高效的神经网络,并结合现代 C++ 特性进行优化。在深度学习应用中,C++ 能够提供更高的性能和灵活性,尤其适用于对计算效率要求较高的场景。通过适当使用智能指针、并行计算等技术,我们能够在 C++ 中实现高效的深度学习框架,充分发挥其性能优势。
希望本文能为你提供一个了解如何在 C++ 中实现神经网络的起点,并为你在构建高效深度学习模型的过程中提供有益的帮助。
相关文章:
使用 C++ 在深度学习中的应用:如何通过 C++20 构建高效神经网络
深度学习已经成为现代人工智能的核心技术,在图像识别、自然语言处理、语音识别等多个领域广泛应用。尽管 Python 因其简便易用和强大的深度学习框架(如 TensorFlow 和 PyTorch)而在这一领域占据主导地位,但 C 作为一门高性能语言&…...
当 Facebook 窥探隐私:用户的数字权利如何捍卫?
随着社交平台的普及,Facebook 已经成为全球用户日常生活的一部分。然而,伴随而来的隐私问题也愈发严峻。近年来,Facebook 频频被曝出泄露用户数据、滥用个人信息等事件,令公众对其隐私保护措施产生质疑。在这个信息化时代…...
Spring MVC中HandlerInterceptor和Filter的区别
目录 一、处理阶段 二、功能范围 三、参数访问 四、配置方式 五、使用场景说明 在Spring MVC中,HandlerInterceptor和Filter都是用于拦截请求的重要组件,但它们在多个方面存在显著的差异。本文将详细解析这两种拦截机制的区别,并结合使用…...
Android多语言开发自动化生成工具
在做 Android 开发的过程中,经常会遇到多语言开发的场景,尤其在车载项目中,多语言开发更为常见。对应多语言开发,通常都是在中文版本的基础上开发其他国家语言,这里我们会拿到中-外语言对照表,这里的工作难…...
回首2024,展望2025
2024年,是个充满挑战与惊喜的年份。在这366个日夜里,我站在编程与博客的交汇点,穿越了无数的风景与挑战,也迎来了自我成长的丰收时刻。作为开发者的第十年,我依然步伐坚定,心中始终带着对知识的渴望与对自我…...
Android SystemUI——快捷面板的显示(十五)
上一篇文章我们分析了 QSTileHost 初始化以及快捷设置面板的创建流程,这里我们继续来看一下快捷设置面板显示流程。 一、QS显示 对于界面的显示,我们同样从 Fragment 的 onViewCreated() 方法开始分析。 1、QSFragment 源码位置:/frameworks/base/packages/SystemUI/src/…...
放弃使用Dockerfiles 平替 docker init
您是那种觉得编写 Dockerfile 和 docker-compose.yml 文件很痛苦的人之一吗? 我承认,我就是其中之一。 我总是想知道我是否遵循了 Dockerfile、 docker-compose 文件的最佳编写实践,我害怕在不知不觉中引入了安全漏洞。 但是现在,…...
前端jquery 实现文本框输入出现自动补全提示功能
git仓库:web_study/some-demos/inputAutoFit at main Cong0925/web_study (github.com) 压缩包:已绑定到指定资源 示例图: 实现说明: 1.首先,html部分设置好相关的定位标签如图: 2.主要函数 3.默认数据...
vulfocus/fastjson-cnvd_2017_02833复现
漏洞概述 Fastjson 是阿里巴巴开发的一个高性能的 Java 库,用于将 Java 对象转换成 JSON 格式(序列化),以及将 JSON 字符串转换回 Java 对象(反序列化)。 fastjson在解析json的过程中,支持使用type字段来指…...
华为支付接入规范
为了确保用户获得良好的支付体验,Payment Kit制定了相关接入设计规范,请开发者遵照执行,具体要求(非强制性)如下: 一、支付方式呈现 涉及支付公司名称,请统一使用:花瓣支付ÿ…...
MySQL训练营-慢查询诊断问题
慢查询相关参数和建议配置 slow_query_log long_query_time 日志开关,是否记慢查询日志以及超过多长时间判定为慢查询。 查看参数设置: SHOW VARIABLES LIKE ‘slow_query_log’;SHOW VARIABLES LIKE ‘long_query_time’; 实践建议: …...
如何给自己的域名配置免费的HTTPS How to configure free HTTPS for your domain name
今天有小伙伴给我发私信,你的 https 到期啦 并且随手丢给我一个截图。 还真到期了。 javapub.net.cn 这个网站作为一个用爱发电的编程学习网站,用来存编程知识和面试题等,平时我都用业余时间来维护,并且还自费买了服务器和阿里云…...
.Net Core微服务入门全纪录(六)——EventBus-事件总线
系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…...
1/20赛后总结
1/20赛后总结 T1『讨论区管理员』的旅行 - BBC编程训练营 算法:IDA* 分数:0 damn it! Ac_code走丢了~~(主要是没有写出来)~~ T2华强买瓜 - BBC编程训练营 算法:双向DFS或者DFS剪枝 分数:0 Ac_code…...
PVE 虚拟机安装 Debian 无图形化界面服务器
Debian 安装 Debian 镜像下载 找一个Debian镜像服务器,根据需要的版本和自己硬件选择。 iso-cd/:较小,仅包含安装所需的基础组件,可能需要网络访问来完成安装。有镜像 debian-12.9.0-amd64-netinst.isoiso-dvd/:较…...
第17篇:python进阶:详解数据分析与处理
第17篇:数据分析与处理 内容简介 本篇文章将深入探讨数据分析与处理在Python中的应用。您将学习如何使用pandas库进行数据清洗与分析,掌握matplotlib和seaborn库进行数据可视化,以及处理大型数据集的技巧。通过丰富的代码示例和实战案例&am…...
三天急速通关Java基础知识:Day1 基本语法
三天急速通关JAVA基础知识:Day1 基本语法 0 文章说明1 关键字 Keywords2 注释 Comments2.1 单行注释2.2 多行注释2.3 文档注释 3 数据类型 Data Types3.1 基本数据类型3.2 引用数据类型 4 变量与常量 Variables and Constant5 运算符 Operators6 字符串 String7 输入…...
Python的进程和线程
ref 接受几个设定: 进程是一家almost密不透风的公司,缅甸KK园区 线程里面工作的…人 进程**[园区]**内公共资源对于进程来说,可以共享. 别的园区[进程],一般不能和自己的园区共享人员资源,除非… 好的,现在再接受设定: 单个CPU在任一时刻只能执行单个线程,只有…...
【Mysql】记录锁、间隙锁和临键锁的区别
InnoDB通过MVCCNext-Key Locks,解决了可重复读的事务隔离级别出现的幻读问题。 记录锁 记录锁就是为某行数据进行加锁,它封锁该行的索引记录 SELECT * FROM table WHERE id 1 FOR UPDATE id为1的记录行会被锁住。需要注意的的:id列必须为…...
神经网络|(二)sigmoid神经元函数
【1】引言 在前序学习进程中,我们已经了解了基本的二元分类器和神经元的构成,文章学习链接为: 神经网络|(一)加权平均法,感知机和神经元-CSDN博客 在此基础上,我们认识到神经元本身在做二元分类,是一种非…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
