找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

565

积分

0

好友

67

主题
发表于 前天 07:11 | 查看: 8| 回复: 0

要深入理解现代AI的奥秘,与其空谈概念,不如亲手实践。今天,我们就抛开复杂的框架,使用纯C++从零实现一个简单的神经网络,并核心剖析其学习的关键——反向传播算法。理解了这个过程,你才能真正明白模型是如何“学会”的。

神经网络:从生物大脑到数学模型

在动手写代码之前,我们先搞清楚几个基本概念。

什么是神经网络?

想象一下,你的大脑里有数百亿个神经元,它们通过突触相互连接,传递电信号。当你看到一张猫的照片,视觉信号会从眼睛进入大脑,经过一系列神经元的处理,最终你认出了这是一只猫。

人工神经网络就是对这个过程的数学模拟。它由多个层组成:

  • 输入层:接收原始数据(比如图片的像素值)
  • 隐藏层:进行特征提取和变换
  • 输出层:给出预测结果

每个神经元接收输入,加权求和,然后通过激活函数产生输出。这就像一个团队协作的工厂:每个工人(神经元)负责一部分工作,最终完成整个生产任务。

神经网络反向传播核心逻辑示意图

图:神经网络中信息流动与权重更新的反向传播过程

前向传播:从输入到预测

前向传播就是数据从输入层到输出层的过程。让我们用C++来实现一个最简单的神经元。

#include<iostream>
#include<vector>
#include<cmath>
#include<random>

// 激活函数:Sigmoid
double sigmoid(double x){
    return 1.0 / (1.0 + std::exp(-x));
}

// Sigmoid的导数
double sigmoid_derivative(double x){
    return x * (1.0 - x);
}

// 神经元类
class Neuron {
private:
    std::vector<double> weights;  // 权重
    double bias;                  // 偏置
    double output;                // 输出值

public:
    Neuron(int num_inputs) {
        // 随机初始化权重和偏置
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_real_distribution<> dis(-1.0, 1.0);

        for (int i = 0; i < num_inputs; i++) {
            weights.push_back(dis(gen));
        }
        bias = dis(gen);
    }

    // 前向传播:计算输出
    double forward(const std::vector<double>& inputs){
        double sum = bias;
        for (size_t i = 0; i < weights.size(); i++) {
            sum += weights[i] * inputs[i];
        }
        output = sigmoid(sum);
        return output;
    }

    // Getter方法
    double get_output() const{ return output; }
    std::vector<double> get_weights() const{ return weights; }
    double get_weight(int index) const{ return weights[index]; }
};

这段代码实现了一个完整的神经元。forward方法做了三件事:

  1. 加权求和sum += weights[i] * inputs[i]
  2. 加上偏置sum += bias
  3. 激活函数output = sigmoid(sum)

反向传播:从错误中学习

现在来到了最关键的部分——反向传播

如果说前向传播是做预测,那反向传播就是从错误中学习。它的核心思想很简单:当预测结果和真实结果不一致时,我们需要找到是谁“错”了,然后修正它。

反向传播基于微积分中的链式法则。让我们用数学语言来描述:假设损失函数为L,神经元的输出为a,加权和为z,权重为w,那么:

$$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} \cdot \frac{\partial z}{\partial w}$$

其中:

  • $\frac{\partial L}{\partial a}$:损失函数对输出的梯度
  • $\frac{\partial a}{\partial z}$:激活函数对加权和的梯度
  • $\frac{\partial z}{\partial w}$:加和对权重的梯度

通过链式法则,我们可以将最终的误差(损失)一层层反向传递,计算出网络中每个参数(权重和偏置)应该如何调整才能减少误差。这个反向传播过程是几乎所有现代深度学习模型训练的核心。

C++实现反向传播

让我们用一个完整的例子来说明。我们构建一个简单的网络:2个输入 → 3个隐藏神经元 → 1个输出。

class NeuralNetwork {
private:
    std::vector<Neuron> hidden_layer;
    Neuron output_neuron;
    double learning_rate = 0.1;

public:
    NeuralNetwork() {
        // 隐藏层:3个神经元,每个接收2个输入
        for (int i = 0; i < 3; i++) {
            hidden_layer.push_back(Neuron(2));
        }
        // 输出层:1个神经元,接收3个隐藏层的输出
        output_neuron = Neuron(3);
    }

    // 前向传播
    std::vector<double> forward(const std::vector<double>& inputs){
        std::vector<double> hidden_outputs;
        for (auto& neuron : hidden_layer) {
            hidden_outputs.push_back(neuron.forward(inputs));
        }
        double output = output_neuron.forward(hidden_outputs);
        return {output};
    }

    // 反向传播并更新权重
    void train(const std::vector<double>& inputs, double target){
        // 前向传播
        std::vector<double> hidden_outputs;
        for (auto& neuron : hidden_layer) {
            hidden_outputs.push_back(neuron.forward(inputs));
        }
        double output = output_neuron.forward(hidden_outputs);

        // 计算输出层误差
        double output_error = target - output;
        double output_delta = output_error * sigmoid_derivative(output);

        // 反向传播误差到隐藏层
        std::vector<double> hidden_errors(3, 0.0);
        for (size_t i = 0; i < 3; i++) {
            hidden_errors[i] = output_delta * output_neuron.get_weight(i);
        }

        std::vector<double> hidden_deltas(3);
        for (size_t i = 0; i < 3; i++) {
            hidden_deltas[i] = hidden_errors[i] * sigmoid_derivative(hidden_outputs[i]);
        }

        // 更新输出层权重
        for (size_t i = 0; i < 3; i++) {
            double weight = output_neuron.get_weight(i);
            double new_weight = weight + learning_rate * output_delta * hidden_outputs[i];
            // 注意:实际代码中需要提供更新权重的方法
        }

        // 更新隐藏层权重
        for (size_t i = 0; i < 3; i++) {
            for (size_t j = 0; j < 2; j++) {
                double weight = hidden_layer[i].get_weight(j);
                double new_weight = weight + learning_rate * hidden_deltas[i] * inputs[j];
                // 注意:实际代码中需要提供更新权重的方法
            }
        }
    }
};

代码完整示例与使用

为了让代码真正可运行,我们需要完善权重更新的机制。下面是一个完整的、可以编译运行的版本:

#include<iostream>
#include<vector>
#include<cmath>
#include<random>

class Neuron {
private:
    std::vector<double> weights;
    double bias;
    double output;

public:
    Neuron(int num_inputs) {
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_real_distribution<> dis(-0.5, 0.5);

        for (int i = 0; i < num_inputs; i++) {
            weights.push_back(dis(gen));
        }
        bias = dis(gen);
        output = 0.0;
    }

    double forward(const std::vector<double>& inputs){
        double sum = bias;
        for (size_t i = 0; i < weights.size(); i++) {
            sum += weights[i] * inputs[i];
        }
        output = 1.0 / (1.0 + std::exp(-sum));
        return output;
    }

    void update_weights(const std::vector<double>& inputs, double delta, double lr){
        for (size_t i = 0; i < weights.size(); i++) {
            weights[i] += lr * delta * inputs[i];
        }
        bias += lr * delta;
    }

    double get_output() const{ return output; }
    double get_weight(size_t index) const{ return weights[index]; }
};

class NeuralNetwork {
private:
    std::vector<Neuron> hidden_layer;
    Neuron output_neuron;
    double learning_rate = 0.5;

public:
    NeuralNetwork() {
        for (int i = 0; i < 3; i++) {
            hidden_layer.emplace_back(2);
        }
        output_neuron = Neuron(3);
    }

    double forward(const std::vector<double>& inputs){
        std::vector<double> hidden_outputs;
        for (auto& neuron : hidden_layer) {
            hidden_outputs.push_back(neuron.forward(inputs));
        }
        return output_neuron.forward(hidden_outputs);
    }

    void train(const std::vector<double>& inputs, double target){
        std::vector<double> hidden_outputs;
        for (auto& neuron : hidden_layer) {
            hidden_outputs.push_back(neuron.forward(inputs));
        }
        double output = output_neuron.forward(hidden_outputs);

        // 输出层反向传播
        double output_error = target - output;
        double output_delta = output_error * output * (1 - output);

        // 隐藏层反向传播
        std::vector<double> hidden_deltas(3);
        for (size_t i = 0; i < 3; i++) {
            double error = output_delta * output_neuron.get_weight(i);
            hidden_deltas[i] = error * hidden_outputs[i] * (1 - hidden_outputs[i]);
        }

        // 更新权重
        for (size_t i = 0; i < 3; i++) {
            output_neuron.update_weights(
                std::vector<double>{hidden_outputs[0], hidden_outputs[1], hidden_outputs[2]},
                output_delta,
                learning_rate
            );
            hidden_layer[i].update_weights(inputs, hidden_deltas[i], learning_rate);
        }
    }
};

int main(){
    NeuralNetwork nn;

    // 训练数据:XOR问题
    std::vector<std::vector<double>> training_inputs = {
        {0, 0}, {0, 1}, {1, 0}, {1, 1}
    };
    std::vector<double> training_targets = {0, 1, 1, 0};

    // 训练10000轮
    for (int epoch = 0; epoch < 10000; epoch++) {
        for (size_t i = 0; i < training_inputs.size(); i++) {
            nn.train(training_inputs[i], training_targets[i]);
        }

        if (epoch % 1000 == 0) {
            double total_error = 0;
            for (size_t i = 0; i < training_inputs.size(); i++) {
                double pred = nn.forward(training_inputs[i]);
                total_error += std::pow(training_targets[i] - pred, 2);
            }
            std::cout << "Epoch " << epoch << ", Error: " << total_error << std::endl;
        }
    }

    // 测试
    std::cout << "\n测试结果:\n";
    for (size_t i = 0; i < training_inputs.size(); i++) {
        double pred = nn.forward(training_inputs[i]);
        std::cout << "Input: [" << training_inputs[i][0] << ", " << training_inputs[i][1]
                  << "] -> Predicted: " << pred
                  << ", Target: " << training_targets[i] << std::endl;
    }

    return 0;
}

这段代码实现了一个能解决XOR问题的神经网络。XOR问题是神经网络领域的“Hello World”,因为它无法被线性分类器解决,必须使用非线性激活函数(如Sigmoid)。

为什么用C++而不是Python?

你可能会问:现在有PyTorch、TensorFlow这么好用的框架,为什么还要用C++手搓神经网络?

实际上,理解框架的底层原理,才能更好地使用框架。当你用PyTorch写loss.backward()时,背后发生的就是我们今天讨论的这些数学运算。

此外,C++在某些场景下确实有优势:

  1. 性能优化:C++的执行效率更高,适合对性能要求极高的场景
  2. 部署环境:嵌入式系统、实时系统等可能无法支持Python环境
  3. 深度定制:当你需要实现一些特殊的网络结构或优化算法时,C++提供了更大的灵活性

希望通过这个从零开始的C++实现,你能对神经网络和反向传播有更深刻、更直观的理解。真正的掌握,始于亲手实践。欢迎在云栈社区分享你的实现心得或提出更多技术问题。




上一篇:20个运维岗位细分详解:从基础运维到云原生,你的职业方向指南
下一篇:JSON Web Token (JWT) 详解:无状态认证的原理、优势与适用场景
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 00:28 , Processed in 0.256397 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表