使用 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博客 在此基础上,我们认识到神经元本身在做二元分类,是一种非…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...