Получение простой нейронной сети для работы с нуля в C ++ - PullRequest
26 голосов
/ 07 января 2010

Я пытался заставить работать простую двойную нейронную сеть XOR, и у меня возникли проблемы с обратным распространением для обучения действительно простой нейронной сети с прямой связью.
Я в основном пытался следовать этому руководство в получении нейронной сети, но в лучшем случае сделал программы, которые учатся с очень низкой скоростью.

Как я понимаю, нейронные сети:

  1. Значения вычисляются путем взятия результата сигмоидальной функции из суммы всех входов в этот нейрон.Затем он подается на следующий слой с использованием веса для каждого нейрона
  2. . В конце работы вычисляется ошибка для выходных нейронов, затем с использованием весов ошибка возвращается обратно, просто умножая значения изатем суммирование в каждом нейроне
  3. Когда все ошибки вычислены, веса корректируются на дельта = вес соединения * производная сигмоида (значение веса нейрона собирается) * значение нейрона, что соединение является* ошибка нейрона * количество ошибок вывода нейрона, переходящего в * бета (некоторая константа для скорости обучения)

Это - моя текущая куча кода, которую я пытаюсьначать работать.У меня есть много других попыток, которые несколько смешаны, но основная функция обратного распространения, которую я пытаюсь заставить работать, находится на линии 293 в Net.cpp

Ответы [ 4 ]

21 голосов
/ 07 января 2010

Взгляните на 15 шагов по внедрению нейронной сети , она должна помочь вам начать.

12 голосов
/ 29 сентября 2016

Я написал простое «Учебное пособие», которое вы можете проверить ниже.

Это простая реализация модели персептрона. Вы можете представить персептрон как нейронную сеть с одним нейроном. Существует проклятый код, который вы можете проверить, который я написал на C ++. Я пошагово прошёл код, чтобы у вас не возникало проблем.

Хотя персептрон на самом деле не является «нейронной сетью», он действительно полезен, если вы хотите начать работу и может помочь вам лучше понять, как работает полноценная нейронная сеть.

Надеюсь, это поможет! Ура! ^ _ ^



В этом примере я рассмотрю реализацию модели персептрона в C ++, чтобы вы могли лучше понять, как она работает.

Перво-наперво, хорошая практика - записать простой алгоритм того, что мы хотим сделать.

Алгоритм:

  1. Создайте вектор для весов и инициализируйте его 0 (не забудьте добавить термин смещения)
  2. Продолжайте корректировать веса, пока мы не получим 0 ошибок или небольшое количество ошибок.
  3. Создание прогнозов по невидимым данным.

Написав супер простой алгоритм, давайте теперь напишем некоторые функции, которые нам понадобятся.

  • Нам понадобится функция для расчета входных данных сети (например, * 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 принимает в качестве аргумента вектор вектора . Это потому, что наш обучающий набор данных является матрицей входных данных. По сути, мы можем представить, что матрица представляет собой пару векторов x , расположенных один над другим, и каждый столбец этой матрицы является элементом.

Наконец, давайте добавим значения, которые должен иметь наш класс. Например, вектор w для удержания весов, число эпох , которое указывает количество проходов, которые мы сделаем над учебный набор данных. И константа эта , которая является скоростью обучения, на которую мы будем умножать каждое обновление веса, чтобы ускорить процедуру тренировки, набрав это значение или, если Эта слишком высока, мы можем набрать его, чтобы получить идеальный результат (для большинства применений персептрона я бы предложил Эта значение 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;
};

Теперь с нашим набором классов. Пришло время написать каждую из функций.

Начнем с конструктора ( персептрон (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 предсказание (вектор X); ). Помните, что все, что делает функция предсказывать , берет чистый ввод и возвращает значение 1, если netInput больше 0 и -1 в остальном.

int perceptron::predict(vector<float> X)
{
    return netInput(X) > 0 ? 1 : -1; //Step Function
}

Обратите внимание, что мы использовали встроенное выражение if, чтобы сделать нашу жизнь проще. Вот как работает встроенный оператор if:

состояние? if_true: else

Пока все хорошо. Давайте перейдем к реализации функции netInput ( float netInput (вектор 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;
}

Способ, которым я импортировал набор данных iris, не совсем идеален, но я просто хотел что-то, что сработало.

Файлы данных можно найти здесь .

Я надеюсь, что вы нашли это полезным!

Примечание: приведенный выше код приведен только в качестве примера. Как отметил juzzlin, важно использовать const vector<float> &X и вообще передавать объекты vector / vector<vector> по ссылке, потому что данные могут быть очень большими, а передача их по значению сделает их копию (что неэффективно ).

6 голосов
/ 07 января 2010

Мне кажется, что вы боретесь с backprop, и то, что вы описываете выше, не совсем соответствует тому, как я понимаю, как это работает, и ваше описание немного двусмысленно.

Вы вычисляете выходной член ошибки для обратного распространения как разность между прогнозом и фактическим значением, умноженную на производную передаточной функции. Это то значение ошибки, которое вы затем распространяете в обратном направлении. Производная сигмоида вычисляется довольно просто как y (1-y), где y - ваше выходное значение. В Интернете есть множество доказательств этого.

Для узла на внутреннем уровне вы умножаете эту выходную ошибку на вес между двумя узлами и суммируете все эти произведения как общую ошибку от внешнего слоя, распространяемого к узлу во внутреннем слое. Ошибка, связанная с внутренним узлом, затем умножается на производную передаточной функции, примененную к исходному выходному значению. Вот некоторый псевдокод:

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 и удачи.

4 голосов
/ 26 сентября 2013

Как насчет этого открытого исходного кода.Он определяет простую сеть из 1 скрытого слоя (2 входа, 2 скрытых, 1 выход) и решает проблему XOR:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...