Последние несколько недель я пытался создать нейронную сеть 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;
}