首页 文章

在C中从头开始使用简单的神经网络

提问于
浏览
24

我一直试图让一个简单的双XOR神经网络工作,我遇到了反向传播训练一个非常简单的前馈神经网络的问题 .
我一直试图在获得神经网络时遵循this指南,但最多只能制作以极慢的速度学习的程序 .

据我了解神经网络:

  • 通过从该神经元的所有输入的总和中获取sigmoid函数的结果来计算值 . 然后使用每个神经元的重量将其送入下一层

  • 在运行结束时,计算输出神经元的误差,然后使用权重,通过简单地将值乘以然后在每个神经元处求和,将误差反馈回传播 .

  • 当计算所有误差时,权重由delta =连接的权重* sigmoid的导数(神经元权重的值将要去)调整连接的神经元的值神经元的误差输出误差的量神经元进入 beta(学习率有些常数)

This是我目前正在努力工作的代码 . 我有很多其他的尝试有点混合,但我试图工作的主要反向传播功能是在Net.cpp的第293行

4 回答

  • 4

    看看15 Steps to implement a Neural Network,它应该让你入门 .

  • 21

    我写了一个简单的“教程”,您可以在下面查看 .

    它是感知器模型的简单实现 . 你可以将感知器想象成只有一个神经元的神经网络 . 你可以测试我用C编写的诅咒代码 . 我一步一步地完成代码,所以你不应该有任何问题 .

    虽然感知器并不是真正的“神经网络”,但如果你想要开始并且可能有助于你更好地理解完整的神经网络是如何工作的,那么它真的很有帮助 .

    希望有所帮助!干杯! ^ _ ^



    在这个例子中,我将在C中完成感知器模型的实现,以便您可以更好地了解它的工作原理 .

    首先,首先写下一个我们想要做的简单算法是一个好习惯 .

    算法:

    • 为权重创建一个向量并将其初始化为0(不要忘记添加偏差项)

    • 继续调整权重,直到我们得到0错误或错误计数低 .

    • 对看不见的数据做出预测 .

    编写了一个超级简单的算法后,我们现在编写一些我们需要的函数 .

    • 我们需要一个函数来计算网络的输入(e.i x * wT 乘以输入时间的权重)

    • 一个阶跃函数,以便我们得到1或-1的预测

    • 和一个找到权重理想值的函数 .

    所以没有进一步的努力让我们进入它 .

    让我们通过创建一个感知器类来开始:

    class perceptron
    {
    public:
    
    private:
    
    };
    

    现在让我们添加我们需要的功能 .

    class perceptron
    {
    public:
        perceptron(float eta,int epochs);
        float netInput(vector<float> X);
        int predict(vector<float> X);
        void fit(vector< vector<float> > X, vector<float> y);
    private:
    
    };
    

    注意函数 fit 如何将向量<float>的向量作为参数 . 那是因为我们的训练数据集是一个输入矩阵 . 基本上我们可以想象矩阵作为一对矢量 x 将一个堆叠在另一个上面,并且该矩阵的每一列都是一个特征 .

    最后,让我们添加我们的类需要具有的值 . 例如矢量 w 来保持权重, epochs 的数量表示我们将对训练数据集进行的传球次数 . 并且常量 eta 这是我们将每个权重更新相乘的学习速率,以便通过拨打此值来使训练过程更快或者如果 eta 太高我们可以将其调低以获得理想的结果(对于大多数应用程序)感知器我会建议_ 0.127663_值为0.1) .

    class perceptron
    {
    public:
        perceptron(float eta,int epochs);
        float netInput(vector<float> X);
        int predict(vector<float> X);
        void fit(vector< vector<float> > X, vector<float> y);
    private:
        float m_eta;
        int m_epochs;
        vector < float > m_w;
    };
    

    现在我们的课程设置 . 是时候编写每个函数了 .

    我们将从构造函数开始( perceptron(float eta,int epochs);

    perceptron::perceptron(float eta, int epochs)
    {
        m_epochs = epochs; // We set the private variable m_epochs to the user selected value
        m_eta = eta; // We do the same thing for eta
    }
    

    正如您所看到的,我们将要做的事情非常简单 . 那么让我们继续讨论另一个简单的函数 . 预测函数( int predict(vector X); ) . 请记住,所有 predict 函数执行的操作是获取净输入,如果 netInput 大于0且返回-1,则返回值1 .

    int perceptron::predict(vector<float> X)
    {
        return netInput(X) > 0 ? 1 : -1; //Step Function
    }
    

    请注意,我们使用内联if语句来使我们的生活更轻松 . 以下是内联if语句的工作原理:

    condition ? if_true : else

    到现在为止还挺好 . 让我们继续实现 netInput 函数( float netInput(vector X);

    netInput做了以下; multiplies the input vector by the transpose of the weights vector

    x * wT

    换句话说,它将输入向量 x 的每个元素乘以权重向量的相应元素 w ,然后获取它们的和并添加偏差 .

    (x1 * w1 + x2 * w2 + ... + xn * wn) + bias

    bias = 1 * w0

    float perceptron::netInput(vector<float> X)
    {
        // Sum(Vector of weights * Input vector) + bias
        float probabilities = m_w[0]; // In this example I am adding the perceptron first
        for (int i = 0; i < X.size(); i++)
        {
            probabilities += X[i] * m_w[i + 1]; // Notice that for the weights I am counting
            // from the 2nd element since w0 is the bias and I already added it first.
        }
        return probabilities;
    }
    

    好吧所以我们现在已经做了很多事我们需要做的就是编写修改权重的 fit 函数 .

    void perceptron::fit(vector< vector<float> > X, vector<float> y)
    {
        for (int i = 0; i < X[0].size() + 1; i++) // X[0].size() + 1 -> I am using +1 to add the bias term
        {
            m_w.push_back(0); // Setting each weight to 0 and making the size of the vector
            // The same as the number of features (X[0].size()) + 1 for the bias term
        }
        for (int i = 0; i < m_epochs; i++) // Iterating through each epoch
        {
            for (int j = 0; j < X.size(); j++) // Iterating though each vector in our training Matrix
            {
                float update = m_eta * (y[j] - predict(X[j])); //we calculate the change for the weights
                for (int w = 1; w < m_w.size(); w++){ m_w[w] += update * X[j][w - 1]; } // we update each weight by the update * the training sample
                m_w[0] = update; // We update the Bias term and setting it equal to the update
            }
        }
    }
    

    所以这基本上就是这样 . 只有3个函数,我们现在有一个工作感知器类,我们可以使用它来进行预测!

    如果您想复制粘贴代码并尝试它 . 这是整个类(我添加了一些额外的功能,例如打印权重向量和每个时期的错误,以及添加导入/导出权重的选项 . )

    这是代码:

    类 Headers :

    class perceptron
    {
    public:
        perceptron(float eta,int epochs);
        float netInput(vector<float> X);
        int predict(vector<float> X);
        void fit(vector< vector<float> > X, vector<float> y);
        void printErrors();
        void exportWeights(string filename);
        void importWeights(string filename);
        void printWeights();
    private:
        float m_eta;
        int m_epochs;
        vector < float > m_w;
        vector < float > m_errors;
    };
    

    类.cpp文件的功能:

    perceptron::perceptron(float eta, int epochs)
    {
        m_epochs = epochs;
        m_eta = eta;
    }
    
    void perceptron::fit(vector< vector<float> > X, vector<float> y)
    {
        for (int i = 0; i < X[0].size() + 1; i++) // X[0].size() + 1 -> I am using +1 to add the bias term
        {
            m_w.push_back(0);
        }
        for (int i = 0; i < m_epochs; i++)
        {
            int errors = 0;
            for (int j = 0; j < X.size(); j++)
            {
                float update = m_eta * (y[j] - predict(X[j]));
                for (int w = 1; w < m_w.size(); w++){ m_w[w] += update * X[j][w - 1]; }
                m_w[0] = update;
                errors += update != 0 ? 1 : 0;
            }
            m_errors.push_back(errors);
        }
    }
    
    float perceptron::netInput(vector<float> X)
    {
        // Sum(Vector of weights * Input vector) + bias
        float probabilities = m_w[0];
        for (int i = 0; i < X.size(); i++)
        {
            probabilities += X[i] * m_w[i + 1];
        }
        return probabilities;
    }
    
    int perceptron::predict(vector<float> X)
    {
        return netInput(X) > 0 ? 1 : -1; //Step Function
    }
    
    void perceptron::printErrors()
    {
        printVector(m_errors);
    }
    
    void perceptron::exportWeights(string filename)
    {
        ofstream outFile;
        outFile.open(filename);
    
        for (int i = 0; i < m_w.size(); i++)
        {
            outFile << m_w[i] << endl;
        }
    
        outFile.close();
    }
    
    void perceptron::importWeights(string filename)
    {
        ifstream inFile;
        inFile.open(filename);
    
        for (int i = 0; i < m_w.size(); i++)
        {
            inFile >> m_w[i];
        }
    }
    
    void perceptron::printWeights()
    {
        cout << "weights: ";
        for (int i = 0; i < m_w.size(); i++)
        {
            cout << m_w[i] << " ";
        }
        cout << endl;
    }
    

    此外,如果您想尝试一个示例,这是我做的一个例子:

    main.cpp中:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <fstream>
    #include <string>
    #include <math.h> 
    
    #include "MachineLearning.h"
    
    using namespace std;
    using namespace MachineLearning;
    
    vector< vector<float> > getIrisX();
    vector<float> getIrisy();
    
    int main()
    {
        vector< vector<float> > X = getIrisX();
        vector<float> y = getIrisy();
        vector<float> test1;
        test1.push_back(5.0);
        test1.push_back(3.3);
        test1.push_back(1.4);
        test1.push_back(0.2);
    
        vector<float> test2;
        test2.push_back(6.0);
        test2.push_back(2.2);
        test2.push_back(5.0);
        test2.push_back(1.5);
        //printVector(X);
        //for (int i = 0; i < y.size(); i++){ cout << y[i] << " "; }cout << endl;
    
        perceptron clf(0.1, 14);
        clf.fit(X, y);
        clf.printErrors();
        cout << "Now Predicting: 5.0,3.3,1.4,0.2(CorrectClass=-1,Iris-setosa) -> " << clf.predict(test1) << endl;
        cout << "Now Predicting: 6.0,2.2,5.0,1.5(CorrectClass=1,Iris-virginica) -> " << clf.predict(test2) << endl;
    
        system("PAUSE");
        return 0;
    }
    
    vector<float> getIrisy()
    {
        vector<float> y;
    
        ifstream inFile;
        inFile.open("y.data");
        string sampleClass;
        for (int i = 0; i < 100; i++)
        {
            inFile >> sampleClass;
            if (sampleClass == "Iris-setosa")
            {
                y.push_back(-1);
            }
            else
            {
                y.push_back(1);
            }
        }
    
        return y;
    }
    
    vector< vector<float> > getIrisX()
    {
        ifstream af;
        ifstream bf;
        ifstream cf;
        ifstream df;
        af.open("a.data");
        bf.open("b.data");
        cf.open("c.data");
        df.open("d.data");
    
        vector< vector<float> > X;
    
        for (int i = 0; i < 100; i++)
        {
            char scrap;
            int scrapN;
            af >> scrapN;
            bf >> scrapN;
            cf >> scrapN;
            df >> scrapN;
    
            af >> scrap;
            bf >> scrap;
            cf >> scrap;
            df >> scrap;
            float a, b, c, d;
            af >> a;
            bf >> b;
            cf >> c;
            df >> d;
            X.push_back(vector < float > {a, b, c, d});
        }
    
        af.close();
        bf.close();
        cf.close();
        df.close();
    
        return X;
    }
    

    我导入虹膜数据集的方式并不是很理想,但我只想要一些有用的东西 .

    可以找到数据文件here.

    我希望你发现这有用!

    注意:上面的代码仅作为示例 . 正如juzzlin所指出的那样,使用 const vector<float> &X 并且通常通过引用传递 vector / vector<vector> 对象非常重要,因为数据可能非常大并且按值传递它将使其复制(这是低效的) .

  • 6

    对我来说听起来就像你正在努力使用backprop,你上面描述的内容并不完全符合我理解它的工作原理,你的描述有点含糊不清 .

    您计算输出误差项以反向传播为预测值与实际值之间的差异乘以传递函数的导数 . 然后是您向后传播的错误值 . sigmoid的导数非常简单地计算为y(1-y),其中y是您的输出值 . 网上有很多可用的证明 .

    对于内层上的节点,将该输出误差乘以两个节点之间的权重,并将所有这些乘积相加作为外层传播到内层节点的总误差 . 然后将与内部节点相关联的误差乘以应用于原始输出值的传递函数的导数 . 这是一些伪代码:

    total_error = sum(output_errors * weights)
    node_error = sigmoid_derivative(node_output) * total_error
    

    然后,该错误以相同的方式向后传播通过输入层权重 .

    使用这些误差项和节点的输出值来调整权重

    weight_change = outer_error * inner_output_value
    

    学习率很重要,因为计算输入数据中每个模式/行/观察的权重变化 . 您希望调整每行的权重更改,以便权重不会被任何单行进行过度更改,因此所有行都会对权重产生影响 . 学习率为您提供,并通过乘以它来调整体重变化

    weight_change = outer_error * inner_output_value * learning_rate
    

    在纪元(迭代)之间记住这些变化并将其中一小部分添加到变化中也是正常的 . 添加的分数称为动量,并且应该通过误差表面的区域加速,在那里没有太大的变化,并且在有细节的地方减速 .

    weight_change = (outer_error*inner_output_value*learning_rate) + (last_change*momentum)
    

    随着训练的进行,存在用于调整学习速率和动量的算法 .

    然后通过添加更改来更新权重

    new_weight = old_weight + weight_change
    

    我查看了你的代码,但不是纠正它并发布我认为最好为你描述支持你所以你可以自己编写代码 . 如果您了解它,您也可以根据自己的情况调整它 .

    HTH,祝你好运 .

  • 9

    这个开源代码怎么样?它定义了一个简单的1隐藏层网(2个输入,2个隐藏,1个输出)并解决了XOR问题:

    http://www.sylbarth.com/mlp.php

相关问题