【深度学习】使用C++手动搭建一个神经网络

1.前言

 深度学习框架给我们带来了很多方便,但是过于依赖框架反而会让我们不知所以然。本篇博客中,我使用C++语言实现了一个简单的神经网络。

2.原理

神经网络由正向传播和反向传播构成。正向传播指的是输入样本数据x,通过一层层网络的计算后得到结果。反向传播值得是通过预测结果与正确结果的差异来对网络中的参数进行修正的过程。

下面我们分别来介绍正向传播算法和反向传播算法:

首先以一个最简单的神经元单元为例:

其中x1,x2,x3是一个输入样本的三个数据,输出是y =σ( x1*w1 + x2*w2 + x3*w3 + b),或者简洁地写成 y = σ(wx + b). 即表示先进行普通的线性操作后,再通过激活函数。为什么要用激活函数呢?因为类似wx + b这样的操作都是线性的,当神经网络层数增加时并不会增加网络的表达性。举例来说:一层的网络的输出是y1 = w1*x1 + b1,二层网络的输出是y2 = w2*y1 + b2,y1 = w1*x1 + b1,也就是y2 = w2*w1*x1 + w2*b1 + b2,就会发现本质上还是一层的网络。激活函数的种类有很多,比如sigmoid、ReLU、tanh等等。

一个基本的神经元就基本介绍完了,许许多多的神经元以不同方式组合,就能构成当今我们所知道的大多数神经网络。

目前的深度学习神经网络一般都由大量的神经元构成,这些神经元相互链接,需要大量的参数来表达。这些参数一般是存在着最优解的,当然也存在着各种各样的局部最优解。为了使神经网络能够自动收敛到最优解,通常需要我们设置好“超参数”,并通过反向传播算法来更新神经网络的参数。

BP算法(error BackPropagation)是反向传播算法的一种,这种算法是最基础的权重调整算法。得益于这些年算力的提升,这种“老”算法得以发挥它的威力。BP算法的核心是链式求导。即 .  除此之外,BP算法还有很多“参数”可供选择,比如损失函数的选择(MSE、CE等)、更新规则的选择(SGD、BGD、MBGD等)等等。在不同的参数选择下,公式细节上会有差异,但是核心推导步骤都是一样的。

以下图这个神经网络为例:

这个神经网络的激活函数均采用sigmoid,损失函数采用均方误差MSE(Mean-Square Error),更新方式为随机梯度下降SGD(Stochastic Gradient Descent)。当今网络上大多数的推导也都是这种类型,但并不代表其他的推导就是错的,这点需要注意。

下面列出所有用到的公式:

Sigmoid激活函数:

 

均方误差(MSE):

其中,n是样本数,这里取1,因为我们是每一个样本更新一次权重。有的时候为了使求导简便,通常还会乘一个1/2.

随机梯度下降(SGD):

其中,α是学习率,关于学习率的设置也有很多方法,比如:固定学习率衰减、自适应学习率衰减等。

至此,一个最简单的神经网络的基本流程我们已经了解。下面就可以着手代码实现了。

3.代码实现

#include<bits/stdc++.h>
using namespace std;
double getMSEloss(double x1,double x2){
    return (x1 - x2)*(x1 - x2);
}
class NNetwork
{
    private:
    int epoches;
    double learning_rate;
    double w1,w2,w3,w4,w5,w6;
    double b1,b2,b3;
    public:
    NNetwork(int es,double lr);
    double sigmoid(double x);
    double deriv_sigmoid(double x);
    double forward(vector<double> data);
    void train(vector<vector<double>> data,vector<double> label);
    void predict(vector<vector<double>> test_data,vector<double> test_label);
};
NNetwork::NNetwork(int es,double lr):epoches(es),learning_rate(lr){
    // 超参数、参数初始化
    w1=w2=w3=w4=w5=w6=0;
    b1=b2=b3=0;
}
double NNetwork::sigmoid(double x){
    // 激活函数
    return 1/(1+exp(-x));
}
double NNetwork::deriv_sigmoid(double x){
    // 激活函数求导
    double y = sigmoid(x);
    return y*(1-y);
}
double NNetwork::forward(vector<double> data){
    // 前向传播
    double sum_h1 = w1 * data[0] + w2 * data[1] + b1;
    double h1 = sigmoid(sum_h1);
    double sum_h2 = w3 * data[0] + w4 * data[1] + b2;
    double h2 = sigmoid(sum_h2);
    double sum_o1 = w5 * h1 + w6 * h2 + b3;
    return sigmoid(sum_o1);
}
void NNetwork::train(vector<vector<double>> data,vector<double> label){
    for(int epoch=0;epoch<epoches;++epoch){
        int total_n = data.size();
        for(int i=0;i<total_n;++i){
            vector<double> x = data[i];
            double sum_h1 = w1 * x[0] + w2 * x[1] + b1;
            double h1 = sigmoid(sum_h1);
            double sum_h2 = w3 * x[0] + w4 * x[1] + b2;
            double h2 = sigmoid(sum_h2);
            double sum_o1 = w5 * h1 + w6 * h2 + b3;
            double o1 = sigmoid(sum_o1);
            double pred = o1;

            double d_loss_pred = -2 * (label[i] - pred);

            double d_pred_w5 = h1 * deriv_sigmoid(sum_o1);
            double d_pred_w6 = h2 * deriv_sigmoid(sum_o1);
            double d_pred_b3 = deriv_sigmoid(sum_o1);
            
            double d_pred_h1 = w5 * deriv_sigmoid(sum_o1);
            double d_pred_h2 = w6 * deriv_sigmoid(sum_o1);

            double d_h1_w1 = x[0] * deriv_sigmoid(sum_h1);
            double d_h1_w2 = x[1] * deriv_sigmoid(sum_h1);
            double d_h1_b1 = deriv_sigmoid(sum_h1);

            double d_h2_w3 = x[0] * deriv_sigmoid(sum_h2);
            double d_h2_w4 = x[1] * deriv_sigmoid(sum_h2);
            double d_h2_b2 = deriv_sigmoid(sum_h2);

            w1 -= learning_rate * d_loss_pred * d_pred_h1 * d_h1_w1;
            w2 -= learning_rate * d_loss_pred * d_pred_h1 * d_h1_w2;
            b1 -= learning_rate * d_loss_pred * d_pred_h1 * d_h1_b1;
            w3 -= learning_rate * d_loss_pred * d_pred_h2 * d_h2_w3;
            w4 -= learning_rate * d_loss_pred * d_pred_h2 * d_h2_w4;
            b2 -= learning_rate * d_loss_pred * d_pred_h2 * d_h2_b2;
            w5 -= learning_rate * d_loss_pred * d_pred_w5;
            w6 -= learning_rate * d_loss_pred * d_pred_w6;
            b3 -= learning_rate * d_loss_pred * d_pred_b3; 
        }
        if(epoch%10==0){
            double loss = 0;
            for(int i=0;i<total_n;++i){
                double pred = forward(data[i]);
                loss += getMSEloss(pred,label[i]);
            }
            cout<<"epoch "<<epoch<<" loss: "<<loss<<endl;
        }
    }
}
void NNetwork::predict(vector<vector<double>> test_data,vector<double> test_label){
    int n = test_data.size();
    double cnt = 0;
    for(int i=0;i<n;++i){
        double pred = forward(test_data[i]);
        pred = pred>0.5?1:0;
        cnt += (test_label[i]==pred);
    }
    cout<<"correct rate:"<<cnt/n<<endl;
}
int main(){
    vector<vector<double>> data = {{-2,-1},{25,6},{17,4},{-15,-6}};
    vector<double> label = {1,0,0,1};
    NNetwork network = NNetwork(1000,0.1);
    network.train(data,label);
    vector<vector<double>> test_data  = {{-3,-4},{-5,-4},{12,3},{-13,-4},{9,12}};
    vector<double> test_label = {1,1,0,1,0};
    network.predict(test_data,test_label);
    return 0;
}

4.实验结果

经过了990次的训练,损失函数从0.99降到0.0012,准确率为100%。

5.总结

本篇博客中实现的仅仅是一个简单的神经网络,在这篇博客中,我们使用C++简单实现了正向传播、反向传播。得益于C++的良好封装性,我们甚至可以基于此开发一个基于C++的神经网络库(mxnet等),其运算效率势必会优于python。

  • 31
    点赞
  • 206
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
### 回答1: 在 C 语言中实现深度学习模型可以使用一些开源的库,比如 Caffe、TensorFlow 等。这些库都提供了一系列的函数和工具,帮助你定义、训练和测试深度学习模型。 首先,你需要准备好训练数据和测试数据。训练数据用于训练深度学习模型,测试数据用于评估模型的准确性。 然后,你需要使用 C 语言编写程序,使用这些库中的函数来定义深度学习模型的结构。这通常包括确定模型的输入和输出,以及模型中间的各个层的结构。 接下来,你可以使用训练数据来训练模型。这一过程通常包括多次迭代训练数据,并使用各种优化算法来最小化模型的错误率。 最后,你可以使用测试数据来评估模型的准确性。如果模型的表现满意,就可以将它用于实际应用中。 希望这些信息对你有帮助! ### 回答2: 使用C语言实现一个深度学习模型的可执行算法相对来说比较困难。因为深度学习模型通常依赖于大规模的矩阵运算和复杂的神经网络结构,而C语言并不擅长处理这些复杂的计算和数据结构。 C语言主要用于系统级编程和底层开发,它更适合与硬件进行交互以及执行高性能的计算任务。如果想要实现一个深度学习模型,通常会选择使用更适合进行科学计算和机器学习的语言,如Python、C++、Java等。 在C语言中实现一个深度学习模型需要自行处理矩阵运算、激活函数、神经网络的构建等一系列复杂的计算任务,这不仅工作量大而且容易出错。相比之下,使用Python等高级语言编写深度学习模型会更加简洁和方便,因为它们提供了更丰富的科学计算库(如NumPy、TensorFlow、PyTorch)和高级的深度学习框架。 总结来说,使用C语言实现一个深度学习模型可执行算法相对复杂且不够高效,建议选择更适合的高级语言和深度学习框架。 ### 回答3: 使用C语言实现一个深度学习模型的可执行算法是具有一定挑战性的任务。深度学习模型通常使用Python等高级语言实现,因为这些语言提供了丰富的库和框架来简化深度学习的开发和训练过程。然而,对于特定的应用场景,使用C语言来实现深度学习模型也是可行的。 使用C语言来实现深度学习模型,首先需要编写底层的计算和矩阵运算库。这是因为深度学习模型的训练和推理过程主要涉及到大规模的矩阵乘法和非线性激活函数等计算。在编写这些库时,需要考虑高效的内存管理和并行计算等问题,以提高算法的性能。 其次,需要编写深度学习模型的前向传播和反向传播算法。前向传播算法用于将输入数据通过神经网络的各层进行计算,得到输出结果;反向传播算法则用于根据损失函数的梯度更新网络的权重参数。这部分编程涉及到矩阵运算和各种激活函数的计算,需要正确实现这些计算以保证算法的正确性和有效性。 最后,还需要实现一些辅助功能,例如数据预处理、参数初始化和模型保存等。这些功能能够提高算法的鲁棒性和实用性。 由于C语言相对于高级语言来说缺乏一些便利的特性,例如自动内存管理和现成的深度学习库,所以使用C语言实现深度学习模型的算法会更加复杂和繁琐。但是,对于一些嵌入式设备和资源有限的场景,使用C语言来实现深度学习模型是一种合理的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值