c++, нейронная сеть, перцептрон, xor, ошибка

103
17 марта 2022, 23:10
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<ctime>
using namespace std;
double activation(double x) {
    return 1 / (1 + exp(-x));
}
double answer[4] = {0.3, 0.8, 0.8, 0.3};
double n1[2] = {0, 0};
double n2 = 0;

double count = 20000, A = 0.4, E, E1, E2;
int main() {
    srand(time(NULL));
    double data[4][2] = { {0, 0}, {1, 0}, {0, 1}, {1, 1} };
    double w[2] = {
        double(rand() % 10 - 5)/10, // случайные веса от 
        double(rand() % 10 - 5)/10, // -0.5 до 0.5
    };
    double q = 0.0; // правильные ответы
    int choose;

    while(count > 0) {
        choose = rand() % 3; // случайно выбираю входные данные
        n1[0] = data[choose][0]; // поставляю входные данные в
        n1[1] = data[choose][1]; // нейронах 1 слоя
         /*Умножаю значения нейронов 1 слоя с соответствующими весами и
           пропускаю через функцию активации которая является сигмоидом*/
        n2 = activation(n1[0] * w[0] + n1[1] * w[1]);
        // Получаю ошибку выходного нейрона
        E = (answer[choose] - n2);
        E1 = E* w[0]; // Получаю ошибки нейронов
        E2 = E* w[1]; // первого слоя
        // изменяю веса по формулу w = w + A* Еrror * fx *(1-fx) * INPUT 
        w[0] = w[0] + A* E1* n2 * (1 - n2) * n1[0];
        w[0] = w[0] + A* E2* n2 * (1 - n2) * n1[1];
        // проверяю верна ли ответ перцептрона и если да увеличиваю счетчик
        if((n2 <= 0.3 && answer[choose] == 0.3) || (n2 >= 0.8 && answer[choose] == 0.8)) q++;
        // показываю
        cout << n1[0] << "  " << n1[1] <<" : " << n2 <<" [" << answer[choose] << "] status: " << q/count << endl;
        count--;
    }
    system("pause");
    return 0;
}

Пытаюсь писать простой перцептрон, который имеет только 2 вход и 1 выход и не имеет никаких скрытых слоев. Перцептрон должен выполнять функцию xor. Для обучения пытаюсь использовать метод обратного распространения ошибок, хотя не уверен сделаю ли это правильно.Во время тестирования число правильных ответов остается равным 0. Не могу понять мою ошибку, помогите пожалуйста.

n1 - значения нейронов первого входного слоя

n2 - значение единственного нейрона второго выходного слоя

data - содержит все возможные входные данные

answer - содержит требуемые выходные данные, так что data[i] и answer[i] соответствуют друг другу.

w - массив весов, так что w[i] соответствует нейрону n1[i]

choose - показывает какие входные параметры были выбраны из всех возможных для обучения

A - скорость обучения

Answer 1

Ну начнем с того, что задача XOR не решаема однослойным персептроном, потому что нули и единицы XOR линейно не разделимы.

Но по вашему решению есть замечания и так:

Первое замечание в комментариях - вы изменяете только один вес:

w[0] = w[0] + A* E1* n2 * (1 - n2) * n1[0];
w[0] = w[0] + A* E2* n2 * (1 - n2) * n1[1];

Ну а дальше тогда по порядку:

  1. double answer[4] = {0.3, 0.8, 0.8, 0.3};

    и if((n2 <= 0.3 && answer[choose] == 0.3) || (n2 >= 0.8 && answer[choose] == 0.8)) q++;

    это что за такая пороговая функция, где серая зона идет в промежутке от 0.3 до 0.8 при бинарном ответе?

  2. Почему вы изменяете веса каждый раз при итерации?

    Логика же в том, чтобы дообучать модель только тогда, когда она выдает ошибку. Так как E = (answer[choose] - n2); практически всегда будет у вас отлична от нуля, потому что n2 в очень редком случае выдаст вам ответ 1 в 1 с пороговыми значениями, то вы меняете веса даже при правильных ответах.

Решением для вас станет изменение пороговой функции, допустим на (>0.5)/(<0.5), добавление дообучения второго веса и обучение только при условии неправильного ответа.

То есть вместо:

// изменяю веса по формулу w = w + A* Еrror * fx *(1-fx) * INPUT 
        w[0] = w[0] + A* E1* n2 * (1 - n2) * n1[0];
        w[0] = w[0] + A* E2* n2 * (1 - n2) * n1[1];
        // проверяю верна ли ответ перцептрона и если да увеличиваю счетчик
        if((n2 <= 0.3 && answer[choose] == 0.3) || (n2 >= 0.8 && answer[choose] == 0.8)) q++;

Вам нужно написать вот так:

// проверяю верна ли ответ перцептрона и если да увеличиваю счетчик
        if ((n2 <= 0.5 && answer[choose] == 0) || (n2 > 0.5 && answer[choose] == 1)) q++;
        else {
            // изменяю веса по формулу w = w + A* Еrror * fx *(1-fx) * INPUT 
            w[0] = w[0] + A * E1 * n2 * (1 - n2) * n1[0];
            w[1] = w[1] + A * E2 * n2 * (1 - n2) * n1[1];
        }

И еще поменять массив answer на double answer[4] = { 0, 1, 1, 0 };.

Ну а дальше добавьте еще один слой и примените все это к двухслойному персептрону:)

Answer 2

Проект сделал на github.Важное в desc.txt. Проект https://github.com/kosta2222/simpleNNforSover . Код:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
float operations(int op, float a, float b, float c, int d, char* str);
//double activation(double x) {
//    return 1 / (1 + exp(-x));
//}
// Байт-код для обучения сети
typedef enum {
    RELU,
    RELU_DERIV,
    SIGMOID,
    SIGMOID_DERIV,
    TRESHOLD_FUNC,
    TRESHOLD_FUNC_DERIV,
    LEAKY_RELU,
    LEAKY_RELU_DERIV,
    INIT_W_HE,
    INIT_W_GLOROT,
    INIT_W_STEPAN_M_TEST,
    DEBUG,
    DEBUG_STR
} OPS;
// Логическое ИЛИИ
double data[4][2] = {
    {0, 0},
    {1, 0},
    {0, 1},
    {1, 1}
};
double answer[4] = {0, 1, 1, 1};
double n1[2] = {0, 0};
double n2 = 0;
double w2[3] = {0, 0, 0};
double n2_dot = 0; // взвешенное состояние нейрона(в больших сетях нейронов)
int count = 0;
double A = 7, E, E1, E2, E3;
int main() {
    int choose = 3;
    int eps = 10;
    double mse = 0;
    // b - верхняя граница, a - нижняя граница  
    w2[1] = operations(INIT_W_STEPAN_M_TEST, 3, -0.0025, 0.0025, 0, ""); // случайные веса от 
    w2[2] = operations(INIT_W_STEPAN_M_TEST, 3, -0.0025, 0.0025, 0, "");
    w2[0] = operations(INIT_W_STEPAN_M_TEST, 3, -0.0025, 0.0025, 0, ""); // биас
    //  double q = 0.0; // правильные ответы
    /*while (count < eps)*/ while (1) {
        printf("epocha %d\n", count);
        //      choose = rand() % 3; // случайно выбираю входные данные
        // Рандомно выбирать неправильно - надо ведь все их обойти
        while (choose >= 0) {
            printf("chose %d\n", choose);
            n1[0] = data[choose][0]; // поставляю входные данные в
            n1[1] = data[choose][1]; // нейронах 1 слоя
            /*Умножаю значения нейронов 1 слоя с соответствующими весами и
              пропускаю через функцию активации которая является сигмоидом*/
            n2_dot = n1[0] * w2[0] + n1[1] * w2[2] + w2[0];
            n2 = operations(SIGMOID, n2_dot, 0.5, 0, 0, "");
            // Получаю ошибку выходного нейрона
            E = (answer[choose] - n2) * operations(SIGMOID_DERIV, n2_dot, 0.5, 0, 0, "");
            mse = pow(answer[choose] - n2, 2);
            printf("mse in train: %f\n", mse);
            if (mse < 0.000100000)
                goto out_bach;
            E1 = E * w2[0];
            E2 = E * w2[1];
            E3 = E * w2[2];
            // изменяю веса по формулу w = w + A* Еrror * fx *(1-fx) * INPUT 
            w2[1] = w2[0] + A * E1 * n1[0];
            w2[2] = w2[1] + A * E2 * n1[1];
            w2[0] = w2[1] + A * E3 * (+1);
            choose--;
        }
        choose = 3;
        count++;
    }
out_bach:
    ;
    // Сеть обучилась - проведем консольную кросс-валидацию
    printf("***Cons Cv - Logic Or***\n");
    choose = 3;
    while (choose >= 0) {
        printf("chose %d\n", choose);
        n1[0] = data[choose][0]; // поставляю входные данные в
        n1[1] = data[choose][1]; // нейронах 1 слоя
        /*Умножаю значения нейронов 1 слоя с соответствующими весами и
          пропускаю через функцию активации которая является сигмоидом*/
        n2_dot = n1[0] * w2[0] + n1[1] * w2[2] + w2[0];
        n2 = operations(SIGMOID, n2_dot, 0.5, 0, 0, "");
        printf("input vector [ %f %f ] ",n1[0], n1[1]);
        if(n2 > 0.5)
            printf("output vector[ %f ]\n",1);
        else
            printf("output vector[ %f ]\n",0);
        choose--;
    }
    system("pause");
    return 0;
}
//-----------------[Операция наподобии виртуальной машины]------------
float operations(int op, float a, float b, float c, int d, char* str) {
    switch (op) {
    case RELU:
    {
        if (a <= 0)
            return 0;
        else
            return a;
    }
    case RELU_DERIV:
    {
        if (a <= 0)
            return 0;
        else
            return 1;
    }
    case TRESHOLD_FUNC:
    {
        if (a < 0)
            return 0;
        else
            return 1;
    }
    case TRESHOLD_FUNC_DERIV:
    {
        return 0;
    }
    case LEAKY_RELU:
    {
        if (a < 0)
            return b * a;
        else
            return a;
    }
    case LEAKY_RELU_DERIV:
    {
        if (a < 0)
            return b;
        else
            return 1;
    }
    case SIGMOID:
    {
        return 1.0 / (1 + exp(b * (-a)));
    }
    case SIGMOID_DERIV:
    {
        return(b * 1.0 / (1 + exp(b * (-a))))*(1 - 1.0 / (1 + exp(b * (-a))));
    }
    case DEBUG:
    {
        printf("%s : %f\n", str, a);
        break;
    }
        // Лучше использовать Гауссовое распределение,я его из Python получил
        //  case INIT_W_HE:
        //  {
        //      PyObject * pVal;
        //      float r = 0;
        //      pVal = PyObject_CallMethod(pInstanceRandom, "gauss", "ii", 0, 1);
        //      if (pVal != NULL) r = PyFloat_AsDouble(pVal), clear_pyObj(pVal), printf("r he:%f\n", r);
        //      else PyErr_Print();
        //      decr(pVal);
        //      return r * sqrt(2 / a);
        //  }
    case INIT_W_STEPAN_M_TEST:
    {
        srand(42);
        // а - cумма нейронов(входные + выходные),b и c - диапазон  
        return float((rand() / RAND_MAX) * (b / a - c / a)) + b / a;
    }
    case DEBUG_STR:
    {
        printf("%s\n", str);
    }
    }
}
//-----------------[Операция наподобии виртуальной машины]------------
READ ALSO
Теорема о пробном частном C++

Теорема о пробном частном C++

Помогите разобраться в теореме Дональда Кнута: теорема о пробном частном

81
Получение COUNT(*) из двух таблиц одним запросом

Получение COUNT(*) из двух таблиц одним запросом

такой вопрос уже есть, но ответа на него нет

95