C ++ Backpropagation в ANN для проблемы XOr - всегда имеет тенденцию к усреднению результатов? - PullRequest
0 голосов
/ 12 июля 2020

Последние несколько недель я пытался создать нейронную сеть net с использованием обратного распространения, решающего проблему XOr. Пока мне удалось создать однослойный персептрон, который может решить любой 1 из 4 наборов входных данных, но, очевидно, не все 4. Я изо всех сил пытаюсь найти ответы на свои вопросы в Интернете, несмотря на то, что я просмотрел много видео на предмет (Coding Train, 3B1B) и прочитав множество руководств (в основном этот один.)

Сделав несколько версий этого, и все они стремятся к каждому выходу будучи ~ 0,5 (общее для всех 4 всегда было 2), я решил немного разбить проблему и создать сеть с 1 скрытым слоем с 2 нейронами и 1 выходным слоем с одним нейроном, с только входами [0,0] = 0 и [0,1] = 1. Раньше я вычислял 1 ошибку для каждого выхода для каждого нейрона - поэтому в этом примере ^ мой выходной нейрон будет иметь 2 значения ошибки для 2 наборов входов / выходов, и каждый скрытый нейрон также будет иметь 2 значения ошибки. На этот раз я рассчитал только одно значение ошибки для каждого нейрона, независимо от количества входов и выходов.

Я произвольно инициализирую все веса в конструкторе класса Neuron (3xW -> 2 веса, 1 смещение на нейрон). Вот мой .h файл со всем классом, функцией et c. декларации и определения:

(Форматирование сложное, здесь 2 файла, 1-й заканчивается обновлением весов скрытого слоя, 2-й начинается с #include "funcs.h", надеюсь, это понятно.)

#pragma once
#include <iostream>
#include <vector>
#include <cmath>
#include <ctime>

std::vector<double> initWeights(int randIterA, int numWeights);

class Neuron
{
public:
    Neuron(int randIterA, int numWeights) { // Num Ws required for neuron (incl. bias) and random iterator to ensure more robust randomness
        weights = initWeights(randIterA, numWeights);
    }
    std::vector<double> weights;
    std::vector<double> outputs;
    double error;
};

class Layer
{
public:
    std::vector<Neuron> neurons;
    Layer(int randIterA, int numWeights, int numNeurons)
    {
        for (int i = 0; i < numNeurons; i++)
        {
            Neuron nI(randIterA, numWeights);
            neurons.emplace_back(nI);
            randIterA++;
        }
    }
};

std::vector<double> initWeights(int randIterA, int numWeights)
{
    std::vector<double> rands;
    double randV;
    srand((unsigned int)time(NULL));
    for (int i = 0; i < randIterA; i++)
    {
        rand(); // Reseeding rand() to ensure better randomness
    }
    for (int i = 0; i < numWeights; i++)
    {
        randV = rand() % 2000 + 0;
        rands.emplace_back((randV / 2000) - 0.5); // between 0.5 & -0.5
    }
    return rands;
}

double derivative(double x)
{
    return (x * (1.0 - x));
}

double sigmoid(double x) // Activation function
{
    return (1.0 / (1.0 + exp(0.0 - x)));
}

struct masterValues
{
    double inputs[4] = {0.0, 0.0, 0.0, 1.0};
    double perfOuts[2] = {0.0, 1.0};
};

std::vector<double> outOuts(std::vector<double> weights, std::vector<std::vector<double>> inputs) { // Outputs for output layer (1 neuron)
    std::vector<double> outs;
    double dotProd;
    for (int i = 0; i < 2;i++) {
        dotProd = 0;
        for (int j = 0; j < 2;j++) {
            dotProd += inputs[j][i] * weights[j];
        }
        dotProd += weights[2];
        outs.emplace_back(sigmoid(dotProd));
    }
    return outs;
}

std::vector<double> hiddenOuts(std::vector<double> weights, double* inputs) { // Outputs for hidden layer (2 neurons)
    std::vector<double> outs;
    double dotProd;
    for (int i = 0; i < 2; i++) {
        dotProd = 0;
        for (int j = 0; j < 2; j++) {
            dotProd += (weights[j] * inputs[j + (i * 2)]); 
        }
        dotProd += weights[2];
        outs.emplace_back(sigmoid(dotProd));
    }
    return outs;
}

double outErr(std::vector<double> outputs, double* perfOuts) {
    double err = 0;
    for (int i = 0; i < 2;i++) {
        err += (perfOuts[i] - outputs[i]) * derivative(outputs[i]);
    }
    return (err / 2);
}

double hiddenErr(double weight, double error, std::vector<double> outputs) {
    double err = 0;
    for (int i = 0; i < 2; i++) {
        err += (weight * error) * derivative(outputs[i]);
    }
    return (err / 2);
}

std::vector<double> updateOutWs(double lR, std::vector<double> oldWs, double error, std::vector<std::vector<double>> inputs) {
    std::vector<double> newWs;
    double delta; // Not the delta bad var name
    for (int i = 0; i < 2;i++) {
        delta = 0;
        for (int j = 0; j < 2;j++) {
            delta += error * inputs[j][i];
        }
        delta *= lR;
        newWs.emplace_back(oldWs[i] + delta);
    }
    // Now the bias
    newWs.emplace_back(oldWs[2] + (error * lR));
    return newWs;
}

std::vector<double> updateHidWs(double lR, std::vector<double> oldWs, double error, double* inputs) {
    std::vector<double> newWs;
    double delta; // Not the delta bad var name
    for (int i = 0; i < 2; i++) {
        delta = 0;
        for (int j = 0; j < 2; j++) {
            delta += error * inputs[j + (i * 2)];
        }
        delta *= lR;
        newWs.emplace_back(oldWs[i] + delta);
    }
    // Now the bias 
    newWs.emplace_back(oldWs[2] + (error * lR));
    return newWs;
} 

И вот главный файл, который использует эти функции:

#include "funcs.h"

int main() 
{
    masterValues ioputs;
    double lR = 0.05; // Learning rate
    int randIter = 1;
    Layer hidden(randIter, 3, 2);
    randIter += 3;
    Layer output(randIter, 3, 1);
    std::vector<std::vector<double>> ALLhouts;
    int gen = 1;
    int MAXgen = 300000; // A silly high number to check for any changes
    while (gen <= MAXgen) {
        // Start iterations
        // Calc outs
        for (int i = 0; i < 2;i++) {
            hidden.neurons[i].outputs = hiddenOuts(hidden.neurons[i].weights, ioputs.inputs);
            ALLhouts.emplace_back(hidden.neurons[i].outputs); // To use the hidden outputs in functions for the output layer error etc.
        }
        output.neurons[0].outputs = outOuts(output.neurons[0].weights, ALLhouts);
        // Print start
        std::cout << "Outputs are ";
        for (int i = 0; i < 2;i++) {
            std::cout << output.neurons[0].outputs[i] << " ";
        }
        std::cout << "\n";
        // Print end
        // Errors
        output.neurons[0].error = outErr(output.neurons[0].outputs, ioputs.perfOuts);
        for (int i = 0; i < 2;i++) {
            hidden.neurons[i].error = hiddenErr(output.neurons[0].weights[i], output.neurons[0].error, hidden.neurons[i].outputs);
        }
        // Update weights
        output.neurons[0].weights = updateOutWs(lR, output.neurons[0].weights, output.neurons[0].error, ALLhouts);
        for (int i = 0; i < 2;i++) {
            hidden.neurons[i].weights = updateHidWs(lR, hidden.neurons[i].weights, hidden.neurons[i].error, ioputs.inputs);
        }
        gen++;
    }
    return 0;
} 
...