web-dev-qa-db-ja.com

シンプルなニューラルネットワークをC ++でゼロから動作させる

私は単純なdouble XOR=ニューラルネットワークを機能させるために努力しており、本当に単純なフィードフォワードニューラルネットワークをトレーニングするためのバックプロパゲーションの取得に問題があります。
私はニューラルネットワークを取得する際に主に this ガイドに従っていますが、せいぜい非常に遅い速度で学習するプログラムを作成しています。

私がニューラルネットワークを理解しているとき:

  1. 値は、そのニューロンへのすべての入力の合計からシグモイド関数の結果を取得することによって計算されます。これは、各ニューロンの重みを使用して次の層に供給されます
  2. 実行の最後に、出力ニューロンのエラーが計算され、次に重みを使用して、値を単純に乗算し、各ニューロンで合計することにより、エラーが逆伝播されます。
  3. すべてのエラーが計算されると、重みはデルタ=接続の重み*シグモイドの導関数(ニューロンの重みの値は)に調整されます*接続に関係するニューロンの値*ニューロンのエラー*出力エラーの量ニューロンは*ベータ(学習率には一定)

これ は、私が動作させようとしている現在のコードです。他にもさまざまな試みが混ざっていますが、私が機能させようとしている主なバックプロパゲーション関数は、Net.cppの293行目です。

31
Matthew FL
21
Gregory Pakosz

以下で確認できる簡単な「チュートリアル」を作成しました。

これは、パーセプトロンモデルの単純な実装です。パーセプトロンは、ニューロンが1つだけのニューラルネットワークと考えることができます。私がC++で記述したテスト可能な呪いのコードがあります。私はコードを一歩一歩順を追って説明するので、問題はありません。

パーセプトロンは実際には「ニューラルネットワーク」ではありませんが、使い始めたい場合に非常に役立ち、完全なニューラルネットワークの仕組みをよりよく理解するのに役立ちます。

お役に立てば幸いです。乾杯! ^ _ ^



この例では、C++でのパーセプトロンモデルの実装について説明します。これにより、それがどのように機能するかをよりよく理解できます。

まず最初に、やりたいことの簡単なアルゴリズムを書き留めておくことをお勧めします。

アルゴリズム:

  1. 重みのベクトルを作成し、0に初期化します(バイアス項を追加することを忘れないでください)
  2. エラーが0またはエラー数が少なくなるまで、重みを調整し続けます。
  3. 目に見えないデータを予測します。

超シンプルなアルゴリズムを作成したら、次に、必要な関数のいくつかを作成しましょう。

  • ネットの入力を計算する関数が必要になります(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がどのようにvector <float>のベクトルを引数として取るかに注意してください。これは、トレーニングデータセットが入力のマトリックスであるためです。基本的に、その行列は、いくつかのベクトルxが別の行列の上に積み重ねられ、その行列の各列が特徴であると想像できます。

最後に、クラスに必要な値を追加しましょう。重みを保持するベクトルwなど、epochsの数は、トレーニングデータセット。そして定数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 predict(vector X);)。 all predict関数が行うことは、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;
}

アイリスデータセットのインポート方法はあまり理想的ではありませんが、機能するものが欲しかっただけです。

データファイルは次の場所にあります here。

この情報がお役に立てば幸いです。

注:上記のコードは一例です。 juzzlinで述べたように、const vector<float> &Xを使用し、一般にvector/vector<vector>オブジェクトを参照で渡すことが重要です。これは、データが非常に大きくなり、値で渡すと、それのコピー(これは非効率的です)。

13
Panos

あなたがバックプロップに苦労しているように聞こえますが、上記で説明した内容は、私がそれを機能させるために理解している方法と完全には一致しておらず、あなたの説明は少しあいまいです。

予測と実際の値の差に伝達関数の導関数を掛けたものとして、バックプロパゲートする出力エラー項を計算します。その後、後方に伝播するのはそのエラー値です。シグモイドの導関数は、y(1-y)として簡単に計算されます。ここで、yは出力値です。Webには、その証明がたくさんあります。

内層のノードの場合、その出力エラーに2つのノード間の重みを掛け、それらすべての積を、外層から内層のノードに伝播される合計エラーとして合計します。次に、内部ノードに関連する誤差に、元の出力値に適用された伝達関数の導関数が乗算されます。ここにいくつかの擬似コードがあります:

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と幸運。

6
Simon

このオープンソースコードはどうですか。単純な1つの非表示レイヤーネット(2つの入力、2つの非表示、1つの出力)を定義し、XOR問題を解決します。

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

4
Oleg Shirokikh