在 C 中實現 Perceptron 模型

在這個例子中,我將介紹 C++中感知器模型的實現,以便你可以更好地瞭解它的工作原理。

首先,首先寫一個我們想要做的簡單演算法是一個好習慣。

演算法:

  1. 製作權重的向量並將其初始化為 0(不要忘記新增偏差項)
  2. 繼續調整權重,直到我們得到 0 錯誤或錯誤計數低。
  3. 對看不見的資料做出預測。

編寫了一個超級簡單的演算法之後,我們現在編寫一些我們需要的函式。

  • 我們需要一個函式來計算網路的輸入(ei 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:

};

注意函式擬合如何將向量<float>的向量作為引數。那是因為我們的訓練資料集是一個輸入矩陣。基本上我們可以想象矩陣作為幾個向量 x 將一個疊加在另一個上面,並且該矩陣的每列都是一個特徵。

最後,讓我們新增我們的類需要具有的值。例如用於保持權重的向量 w ,表示我們將在訓練資料集上進行的遍數的時期數。並且常數 eta 是我們將每個重量更新乘以學習速率以通過撥打此值來使訓練過程更快或者如果 eta 過高我們可以將其調低以獲得理想結果(對於大多數應用)我會建議感知器的 eta 值為 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 預測(向量 X); )。請記住,所有預測函式的作用是獲取淨輸入,如果 netInput 大於 0 則返回值 1,其他為 -1。

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

請注意,我們使用內聯 if 語句來簡化我們的生活。以下是內聯 if 語句的工作原理:

條件?if_true:else

到現在為止還挺好。讓我們繼續實現 netInput 函式( float netInput(vector X);

netInput 執行以下操作; 將輸入向量乘以權重向量的轉置

x * wT

換句話說,它將輸入向量 x 的每個元素乘以權重 w 的向量的對應元素,然後獲取它們的和並加上偏差。

(x1 * w1 + x2 * w2 + … + xn * wn)+偏差

偏差= 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 個函式,我們現在有一個工作感知器類,我們可以使用它來進行預測!

如果你想複製貼上程式碼並嘗試它。這是整個類(我新增了一些額外的功能,例如列印權重向量和每個時期的錯誤,以及新增匯入/匯出權重的選項。)

這是程式碼:

類標題:

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;
}

我匯入虹膜資料集的方式並不是很理想,但我只想要一些有用的東西。

資料檔案可以在這裡找到

我希望你發現這有用!