web-dev-qa-db-ja.com

パーセプトロン学習アルゴリズムが0に収束しない

ANSI Cでのパーセプトロンの実装は次のとおりです。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

float randomFloat()
{
    srand(time(NULL));
    float r = (float)Rand() / (float)Rand_MAX;
    return r;
}

int calculateOutput(float weights[], float x, float y)
{
    float sum = x * weights[0] + y * weights[1];
    return (sum >= 0) ? 1 : -1;
}

int main(int argc, char *argv[])
{
    // X, Y coordinates of the training set.
    float x[208], y[208];

    // Training set outputs.
    int outputs[208];

    int i = 0; // iterator

    FILE *fp;

    if ((fp = fopen("test1.txt", "r")) == NULL)
    {
        printf("Cannot open file.\n");
    }
    else
    {
        while (fscanf(fp, "%f %f %d", &x[i], &y[i], &outputs[i]) != EOF)
        {
            if (outputs[i] == 0)
            {
                outputs[i] = -1;
            }
            printf("%f   %f   %d\n", x[i], y[i], outputs[i]);
            i++;
        }
    }

    system("PAUSE");

    int patternCount = sizeof(x) / sizeof(int);

    float weights[2];
    weights[0] = randomFloat();
    weights[1] = randomFloat();

    float learningRate = 0.1;

    int iteration = 0;
    float globalError;

    do {
        globalError = 0;
        int p = 0; // iterator
        for (p = 0; p < patternCount; p++)
        {
            // Calculate output.
            int output = calculateOutput(weights, x[p], y[p]);

            // Calculate error.
            float localError = outputs[p] - output;

            if (localError != 0)
            {
                // Update weights.
                for (i = 0; i < 2; i++)
                {
                    float add = learningRate * localError;
                    if (i == 0)
                    {
                        add *= x[p];
                    }
                    else if (i == 1)
                    {
                        add *= y[p];
                    }
                    weights[i] +=  add;
                }
            }

            // Convert error to absolute value.
            globalError += fabs(localError);

            printf("Iteration %d Error %.2f %.2f\n", iteration, globalError, localError);

            iteration++;
        }

        system("PAUSE");

    } while (globalError != 0);

    system("PAUSE");
    return 0;
}

私が使用しているトレーニングセット: Data Set

関係のないコードはすべて削除しました。基本的には今何をしているのかtest1.txtファイルを作成し、その値を3つの配列xyoutputsにロードします。

それから パーセプトロン学習アルゴリズム があり、これは何らかの理由で0に収束していません(globalErrorは0に収束する必要があります)。したがって、無限のdo whileループが発生します。

小さいトレーニングセット(5ポイントなど)を使用すると、かなりうまく機能します。問題となる可能性のあるアイデアはありますか?

私はこれに非常によく似たこのアルゴリズムを書きました C#パーセプトロンアルゴリズム


編集:

以下は、より小さいトレーニングセットの例です。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

float randomFloat()
{
    float r = (float)Rand() / (float)Rand_MAX;
    return r;
}

int calculateOutput(float weights[], float x, float y)
{
    float sum = x * weights[0] + y * weights[1];
    return (sum >= 0) ? 1 : -1;
}

int main(int argc, char *argv[])
{
    srand(time(NULL));

    // X coordinates of the training set.
    float x[] = { -3.2, 1.1, 2.7, -1 };

    // Y coordinates of the training set.
    float y[] = { 1.5, 3.3, 5.12, 2.1 };

    // The training set outputs.
    int outputs[] = { 1, -1, -1, 1 };

    int i = 0; // iterator

    FILE *fp;

    system("PAUSE");

    int patternCount = sizeof(x) / sizeof(int);

    float weights[2];
    weights[0] = randomFloat();
    weights[1] = randomFloat();

    float learningRate = 0.1;

    int iteration = 0;
    float globalError;

    do {
        globalError = 0;
        int p = 0; // iterator
        for (p = 0; p < patternCount; p++)
        {
            // Calculate output.
            int output = calculateOutput(weights, x[p], y[p]);

            // Calculate error.
            float localError = outputs[p] - output;

            if (localError != 0)
            {
                // Update weights.
                for (i = 0; i < 2; i++)
                {
                    float add = learningRate * localError;
                    if (i == 0)
                    {
                        add *= x[p];
                    }
                    else if (i == 1)
                    {
                        add *= y[p];
                    }
                    weights[i] +=  add;
                }
            }

            // Convert error to absolute value.
            globalError += fabs(localError);

            printf("Iteration %d Error %.2f\n", iteration, globalError);          
        }

        iteration++;

    } while (globalError != 0);

    // Display network generalisation.
    printf("X       Y     Output\n");
    float j, k;
    for (j = -1; j <= 1; j += .5)
    {
        for (j = -1; j <= 1; j += .5)
        {
            // Calculate output.
            int output = calculateOutput(weights, j, k);
            printf("%.2f  %.2f  %s\n", j, k, (output == 1) ? "Blue" : "Red");
        }
    }

    // Display modified weights.
    printf("Modified weights: %.2f %.2f\n", weights[0], weights[1]);

    system("PAUSE");
    return 0;
}
59
Richard Knop

現在のコードでは、 perceptron が決定境界の方向を正常に学習しますが、それを変換できません

 y y 
 ^ ^ 
 | -+ \\ + | -\\ + + 
 | -+ \\ + + | -\\ + + + 
 | --\\ + | --\\ + 
 | --+ \\ + | --\\ + + 
 ---------------------> x ---------------- ----> x 
このように動かなくなった場合、このようにする必要があります

(誰かが指摘したように、これが より正確なバージョンです

問題は、パーセプトロンにバイアスがないバイアス項、つまり、値1の入力に接続された3番目の重みコンポーネントがあるという事実にあります。

 w0 ----- 
 x ----> | | 
 | f | ---->出力(+ 1/-1)
 y ----> | | 
 w1 ----- 
 ^ w2 
 1(bias)  

以下は私が問題を修正した方法です:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#define LEARNING_RATE    0.1
#define MAX_ITERATION    100

float randomFloat()
{
    return (float)Rand() / (float)Rand_MAX;
}

int calculateOutput(float weights[], float x, float y)
{
    float sum = x * weights[0] + y * weights[1] + weights[2];
    return (sum >= 0) ? 1 : -1;
}

int main(int argc, char *argv[])
{
    srand(time(NULL));

    float x[208], y[208], weights[3], localError, globalError;
    int outputs[208], patternCount, i, p, iteration, output;

    FILE *fp;
    if ((fp = fopen("test1.txt", "r")) == NULL) {
        printf("Cannot open file.\n");
        exit(1);
    }

    i = 0;
    while (fscanf(fp, "%f %f %d", &x[i], &y[i], &outputs[i]) != EOF) {
        if (outputs[i] == 0) {
            outputs[i] = -1;
        }
        i++;
    }
    patternCount = i;

    weights[0] = randomFloat();
    weights[1] = randomFloat();
    weights[2] = randomFloat();

    iteration = 0;
    do {
        iteration++;
        globalError = 0;
        for (p = 0; p < patternCount; p++) {
            output = calculateOutput(weights, x[p], y[p]);

            localError = outputs[p] - output;
            weights[0] += LEARNING_RATE * localError * x[p];
            weights[1] += LEARNING_RATE * localError * y[p];
            weights[2] += LEARNING_RATE * localError;

            globalError += (localError*localError);
        }

        /* Root Mean Squared Error */
        printf("Iteration %d : RMSE = %.4f\n",
            iteration, sqrt(globalError/patternCount));
    } while (globalError > 0 && iteration <= MAX_ITERATION);

    printf("\nDecision boundary (line) equation: %.2f*x + %.2f*y + %.2f = 0\n",
        weights[0], weights[1], weights[2]);

    return 0;
}

...次の出力が表示されます。

Iteration 1 : RMSE = 0.7206
Iteration 2 : RMSE = 0.5189
Iteration 3 : RMSE = 0.4804
Iteration 4 : RMSE = 0.4804
Iteration 5 : RMSE = 0.3101
Iteration 6 : RMSE = 0.4160
Iteration 7 : RMSE = 0.4599
Iteration 8 : RMSE = 0.3922
Iteration 9 : RMSE = 0.0000

Decision boundary (line) equation: -2.37*x + -2.51*y + -7.55 = 0

そして、MATLABを使用した上記のコードの短いアニメーションを次に示します。各反復での---(決定境界 を示しています。

screenshot

158
Amro

randomFloatを呼び出すたびに再シードするのではなく、ランダムジェネレーターのシードをyout mainの先頭に配置すると役立つ場合があります。

float randomFloat()
{
    float r = (float)Rand() / (float)Rand_MAX;
    return r;
}

// ...

int main(int argc, char *argv[])
{
    srand(time(NULL));

    // X, Y coordinates of the training set.
    float x[208], y[208];
6
rsp

私があなたのソースコードで見つけたいくつかの小さなエラー:

int patternCount = sizeof(x) / sizeof(int);

これを次のように変更してください

int patternCount = i;

そのため、適切なサイズにするためにx配列に依存する必要はありません。

Pループ内で反復を増やしますが、元のC#コードはpループ外でこれを行います。 printfとiteration ++をPAUSEステートメントの前のpループの外に移動することをお勧めします。また、PAUSEステートメントを削除するか、次のように変更します。

if ((iteration % 25) == 0) system("PAUSE");

これらすべての変更を行っても、プログラムはデータセットを使用して終了しませんが、出力はより一貫性があり、56〜60のどこかでエラーが発生します。

最後に試すことができるのは、このデータセットで元のC#プログラムをテストすることです。それも終了しない場合は、アルゴリズムに問題があります(データセットが正しいように見えるため、視覚化コメントを参照してください)。

3
schnaader

globalErrorはゼロにならず、converge toゼロになります。つまり、非常に大きくなります小さい。

ループを次のように変更します。

int maxIterations = 1000000; //stop after one million iterations regardless
float maxError = 0.001; //one in thousand points in wrong class

do {
    //loop stuff here

    //convert to fractional error
    globalError = globalError/((float)patternCount);

} while ((globalError > maxError) && (i<maxIterations));

問題に該当するmaxIterationsおよびmaxError値を指定します。

1
jilles de wit