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

news2025/1/10 16:28:14

引言

在现代机器学习中,神经网络已经成为最重要的工具之一。虽然 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.30650

Predictions 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 for
    for (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.coloradmin.cn/o/2274436.html

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

相关文章

RIS智能无线电反射面:原理、应用与MATLAB代码示例

一、引言 随着无线通信技术的快速发展,人们对通信系统的容量、覆盖范围、能效以及安全性等方面的要求日益提高。传统的无线通信系统主要通过增加基站数量、提高发射功率和优化天线阵列等方式来提升性能,但这些方法面临着资源有限、能耗高和成本上升等挑战。因此,探索新的无线…

合并模型带来的更好性能

研究背景与问题提出 在人工智能领域&#xff0c;当需要处理多个不同任务时&#xff0c;有多种方式来运用模型资源。其中&#xff0c;合并多个微调模型是一种成本效益相对较高的做法&#xff0c;相较于托管多个专门针对不同任务设计的模型&#xff0c;能节省一定成本。然而&…

城市生命线安全综合监管平台

【落地产品&#xff0c;有需要可留言联系&#xff0c;支持项目合作或源码合作】 一、建设背景 以关于城市安全的重要论述为建设纲要&#xff0c;聚焦城市安全重点领域&#xff0c;围绕燃气爆炸、城市内涝、地下管线交互风险、第三方施工破坏、供水爆管、桥梁坍塌、道路塌陷七…

Flink系列知识讲解之:网络监控、指标与反压

Flink系列知识之&#xff1a;网络监控、指标与反压 在上一篇博文中&#xff0c;我们介绍了 Flink 网络协议栈从高层抽象到底层细节的工作原理。本篇博文是网络协议栈系列博文中的第二篇&#xff0c;在此基础上&#xff0c;我们将讨论如何监控网络相关指标&#xff0c;以识别吞…

生物医学信号处理--随机信号的数字特征

前言 概率密度函数完整地表现了随机变量和随机过程的统计性质。但是信号经处理后再求其概率密度函数往往较难&#xff0c;而且往往也并不需要完整地了解随机变量或过程的全部统计性质只要了解其某些特定方面即可。这时就可以引用几个数值来表示该变量或过程在这几方面的特征。…

计算机网络 (31)运输层协议概念

一、概述 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。运输层的一个核心功能是提供从源端主机到目的端主机的可靠的、与实际使用的网络无关的信息传输。它向高层用…

深度学习张量的秩、轴和形状

深度学习张量的秩、轴和形状 秩、轴和形状是在深度学习中我们最关心的张量属性。 秩轴形状 秩、轴和形状是在深度学习中开始使用张量时我们最关心的三个属性。这些概念相互建立&#xff0c;从秩开始&#xff0c;然后是轴&#xff0c;最后构建到形状&#xff0c;所以请注意这…

积分与签到设计

积分 在交互系统中&#xff0c;可以通过看视频、发评论、点赞、签到等操作获取积分&#xff0c;获取的积分又可以参与排行榜、兑换优惠券等&#xff0c;提高用户使用系统的积极性&#xff0c;实现引流。这些功能在很多项目中都很常见&#xff0c;关于功能的实现我的思路如下。 …

vue实现虚拟列表滚动

<template> <div class"cont"> //box 视图区域Y轴滚动 滚动的是box盒子 滚动条显示的也是因为box<div class"box">//itemBox。 一个空白的盒子 计算高度为所有数据的高度 固定每一条数据高度为50px<div class"itemBox" :st…

IEC61850遥控-增强安全选控是什么?

摘要&#xff1a;遥控服务是IEC61850协议中非常重要的一项服务&#xff0c;其通常会被应用在电源开关、指示灯、档位调节等器件的操作。 遥控是一类比较特殊的操作&#xff0c;其通过远程方式操作指定的设备器件&#xff0c;在一些重要的场景中需要有严谨的机制来进行约束&…

【Uniapp-Vue3】创建自定义页面模板

大多数情况下我们都使用的是默认模板&#xff0c;但是默认模板是Vue2格式的&#xff0c;如果我们想要定义一个Vue3模板的页面就需要自定义。 一、我们先复制下面的模板代码&#xff08;可根据自身需要进行修改&#xff09;&#xff1a; <template><view class"…

如何操作github,gitee,gitcode三个git平台建立镜像仓库机制,这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈

如何操作github&#xff0c;gitee&#xff0c;gitcode三个git平台建立镜像仓库机制&#xff0c;这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈 问题背景 由于我司最早期19年使用的是gitee&#xff0c;因此大部分仓库都在gitee有几百个库的代码&#xff0c;…

QThread多线程详解

本文结构如下 文章目录 本文结构如下 1.概述2.开始多线程之旅2.1应该把耗时代码放在哪里&#xff1f;2.2再谈moveToThread() 3.启动线程前的准备工作3.1开多少个线程比较合适&#xff1f;3.2设置栈大小 4.启动线程/退出线程4.1启动线程4.2优雅的退出线程 5.操作运行中的线程5.1…

深度学习数据集有没有规范或指导意见,数据集的建立都需要做哪些研究工作?

一、数据集的核心原则是什么&#xff1f; 数据集的目标&#xff1a;它需要回答“你要解决什么问题&#xff1f;” 在构建数据集之前&#xff0c;最重要的不是去采集数据&#xff0c;而是明确目标&#xff1a; 你的模型是要做图像分类&#xff0c;还是目标检测&#xff1f;是要…

前端for循环遍历——foreach、map使用

title: 前端不同类型的for循环遍历——foreach、map date: 2025-01-04 11:02:17 tags: javascript 前端不同类型的for循环遍历 场景&#xff1a;很多时候后端发来的数据是不能够完全契合前端的需求的&#xff0c;比如你要一个数据对象中的值&#xff0c;但是这个值却作为了ke…

MR30分布式 IO 在物流分拣线的卓越应用

在当今物流行业高速发展的时代&#xff0c;物流分拣线的高效与精准运作至关重要&#xff0c;而其中对于货物点数较多情况下的有效控制更是一大关键环节。明达技术MR30分布式 IO 系统凭借其独特的优势&#xff0c;在物流分拣线中大放异彩&#xff0c;为实现精准的点数控制提供了…

5 分布式ID

这里讲一个比较常用的分布式防重复的ID生成策略&#xff0c;雪花算法 一个用户体量比较大的分布式系统必然伴随着分表分库&#xff0c;分机房部署&#xff0c;单体的部署方式肯定是承载不了这么大的体量。 雪花算法的结构说明 如下图所示: 雪花算法组成 从上图我们可以看…

Android wifi常见问题及分析

参考 Android Network/WiFi 那些事儿 前言 本文将讨论几个有意思的网络问题&#xff0c;同时介绍 Android 上常见WiFi 问题的分析思路。 网络基础Q & A 一. 网络分层缘由 分层想必大家很熟悉&#xff0c;是否想过为何需要这样分层&#xff1f; 网上大多都是介绍每一层…

音视频入门基础:MPEG2-PS专题(6)——FFmpeg源码中,获取PS流的视频信息的实现

音视频入门基础&#xff1a;MPEG2-PS专题系列文章&#xff1a; 音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;1&#xff09;——MPEG2-PS官方文档下载 音视频入门基础&#xff1a;MPEG2-PS专题&#xff08;2&#xff09;——使用FFmpeg命令生成ps文件 音视频入门基础…

读书笔记:分布式系统原理介绍

写在前面 已经大概三个月左右没有更新博客了&#xff0c;哈哈哈哈&#xff1b; 此博客是笔者在对《分布式系统原理介绍》进行概述&#xff0c;对于整个分布式系统协议的理解基于一些量化的指标考虑了数据的分布副本协议&#xff08;中心化/去中心化&#xff09;进行了总结&…