2015-01-27 2 views
0

У меня есть некоторые проблемы, реализующие backpropagation в нейронной сети. Эта реализация использует идеи из слайдов курса Эндрю Нг по компьютерному обучению от Coursera (вот ссылка https://www.coursera.org/course/ml). Я думаю, что я понял алгоритм, но в коде есть небольшая ошибка.Алгоритм обратного распространения в нейронной сети

Я использую сеть с 1 входным слоем, 1 скрытым слоем и 1 выходным уровнем. У них есть 2 + 1, 2 + 1, 1 нейроны соответственно (+1 для смещения). Когда я пытался реализовать логическое И и логическое ИЛИ все отлично работало, и сеть научилась давать правильные значения. Но затем я попытался реализовать XNOR (XNOR b = NOT (a XOR b)).

Я использовал 4 примера:

Но вдруг, на этой функции градиента Безразлично Никуда. Вначале я инициализирую весы со случайными малыми числами (от -0.01 до 0.01). Выходной сигнал составляет около 0,5. Затем я делаю градиентный спуск. Выход по-прежнему всегда около 0,5 на любом входе.

Я хочу знать, как исправить эту проблему.

Вот код:

#include <iostream> 
#include <fstream> 
#include <vector> 
#include <algorithm> 

// contains matrix and Vector classes. 
// Vector is just like std::valarray, but is compatible with my matrix. 
#include "matrix.hpp" 

size_t L; 
std::vector< Vector<double> > layers; 
std::vector< matrix<double> > theta; 

struct Example 
{ 
    Vector<double> x; 
    Vector<double> y; 
}; 

using TrainingSet = std::vector<Example>; 

TrainingSet examples; 

double g(double x) 
{ 
    return 1/(1 + exp(-x)); 
} 

void forwardPropagate(Vector<double> x) 
{ 
    for (size_t i = 1; i < layers[0].size(); ++i) 
     layers[0][i] = x[i - 1]; 

    for (size_t i = 0; i < L - 1; ++i) 
    { 
     auto z = theta[i] * layers[i]; 
     for (size_t j = 1; j < layers[i + 1].size(); ++j) 
      layers[i + 1][j] = g(z[j - 1]); 
    } 
} 

void backwardPropagate(Vector<double> y, std::vector< matrix<double> >& delta) 
{ 
    auto err = layers.back().slice(1) - y; 

    for (int i = L - 2; i >= 0; --i) 
    { 
     delta[i] += asMatrix(err) * asMatrix(layers[i]).transpose(); 

     auto gdz = layers[i] * (Vector<double>(layers[i].size(), 1.0) - layers[i]); 
     auto tmp = theta[i].transpose() * err * gdz; 
     err = tmp.slice(1); 
    } 
} 

double costFunction(const TrainingSet& examples) 
{ 
    double result = 0.0; 

    for (const auto& example : examples) 
    { 
     std::cout << layers.back()[1] << '\n'; 

     forwardPropagate(example.x); 
     for (size_t k = 1; k < layers.back().size(); ++k) 
     { 
      auto h = layers.back()[k]; 
      auto y = example.y[k - 1]; 
      result += y * log(h) + (1 - y) * log(1 - h); 
     } 
    } 

    return (-result)/examples.size(); 
} 

void computeGradient(std::vector< matrix<double> >& delta, const TrainingSet& examples) 
{ 
    for (auto& m : delta) 
     m.fillWith(0); 

    for (auto example : examples) 
    { 
     forwardPropagate(example.x); 
     backwardPropagate(example.y, delta); 
    } 

    for (auto& m : delta) 
     m /= examples.size(); 
} 

void gradientDescentStep(const std::vector< matrix<double> >& gradient) 
{ 
    const double alpha = 0.01; 

    for (size_t i = 0; i < L - 1; ++i) 
     theta[i] -= alpha/examples.size() * gradient[i]; 
} 

double gradientDescent(const TrainingSet& examples) 
{ 
    const double eps = 0.0000001; 

    double prev, cur; 
    cur = costFunction(examples); 

    size_t iterations = 0; 
    const size_t max_iterations = 200000000; 

    std::vector< matrix<double> > delta; 
    delta.reserve(L - 1); 
    for (size_t i = 0; i < L - 1; ++i) 
     delta.emplace_back(theta[i].rows(), theta[i].cols()); 

    do 
    { 
     prev = cur; 
     computeGradient(delta, examples); 
     gradientDescentStep(delta); 
     cur = costFunction(examples); 

    } while (fabs(cur - prev) >= eps && iterations++ < max_iterations); 

    std::cout << "Made " << iterations << " iterations\n"; 

    return cur; 
} 

int main() 
{ 
    std::ifstream fin("input.txt");  
    std::istream& in = fin;  

    std::cout.sync_with_stdio(false); 

    in >> L; 
    std::vector<size_t> architecture(L); 

    for (size_t i = 0; i < L; ++i) 
     in >> architecture[i]; 

    layers.reserve(L); 
    for (size_t i = 0; i < L; ++i) 
    { 
     layers.emplace_back(1 + architecture[i]); 
     layers.back()[0] = 1; 
    } 

    const double eps = 0.01;  

    theta.reserve(L - 1); 
    for (size_t i = 0; i < L - 1; ++i) 
    { 
     theta.emplace_back(layers[i + 1].size() - 1, layers[i].size()); 
     theta[i].randomInitialize(eps); 
    } 

    size_t number_of_examples; 
    in >> number_of_examples; 

    examples.reserve(number_of_examples); 
    for (size_t i = 0; i < number_of_examples; ++i) 
    { 
     auto x = Vector<double>(architecture.front()); 
     auto y = Vector<double>(architecture.back()); 

     for (size_t j = 0; j < architecture.front(); ++j) 
      in >> x[j]; 

     for (size_t j = 0; j < architecture.back(); ++j) 
      in >> y[j]; 

     examples.emplace_back(Example{x, y}); 
    } 

    for (auto example : examples) 
    { 
     forwardPropagate(example.x); 
     std::cout << layers.back()[1] << '\n'; 
    } 

    for (size_t i = 0; i < theta.size(); ++i) 
     std::cout << "θ[" << i << "] = " << theta[i]; 

    gradientDescent(examples); 

    for (size_t i = 0; i < theta.size(); ++i) 
     std::cout << "θ[" << i << "] = " << theta[i]; 

    std::cout << "\n\n\n"; 

    for (auto example : examples) 
    { 
     forwardPropagate(example.x); 
     std::cout << layers.back()[1] << '\n'; 
    } 

    return 0; 
} 
+0

Я не понимаю C++ достаточно хорошо, чтобы читать ваш код. Вы прошли через это с помощью отладчика и следили за изменением веса? Я ожидаю, что скрытый слой будет изучать функции 'AND' и' NAND'. Кроме того, вы пытались добавить больше данных обучения, даже если они повторяются? – eigenchris

+0

@eigenchris Я контролировал, как меняется вес, на самом деле они меняются очень мало, около 10^(- 6). Кроме того, если я устанавливаю начальные значения в интервале (-ε, ε) и ε = 0,5, то мой спуск градиента составляет около 2000 итераций. Если я задал ε = 0,01, то он не выполняет никаких итераций вообще.Я не пробовал больше данных, даже если они повторяются, но я собираюсь сделать это прямо сейчас. ** Изменить: ** Просто попробовал, ничего не меняется. – justanothercoder

+0

Я не уверен, что еще предложить. Я знаю, что Эндрю Нг предлагает реализовать функцию проверки градиента, которая численно принимает градиент, чтобы убедиться, что ваша функция градиента работает правильно. Если у вас нет других идей, стоит попробовать. – eigenchris

ответ

0

Наконец я понял, что это не так. Проблема не в самом коде. Дело в том, что функция стоимости при такой конфигурации сети с XOR имеет локальный минимум. Итак, я пришел туда и застрял.

Решение должно сделать шаг в случайном направлении, пока вы не достигнете его локального минимума. Это позволяет вам быстро добраться до глобального минимума.

0

Обычно для случайной инициализации весы находятся в диапазоне ~ [-3, 3]. Первоначальное большее количество ошибок (с большим весом) помогает «прыгать» весом в сближение с соответствующими областями. Да, проблема XOR имеет локальные минимумы, но ваша сеть должна легко сходиться к правильному ответу только с несколькими скрытыми узлами.

Вы не должны «нуждаться» в том, чтобы предпринимать шаги из локальных минимумов, они должны легко сходиться к правильным оптимумам. Проблема XOR с сетевой структурой, являющейся [2,2,1], имеет такое небольшое количество весов, что вы могли бы существенно «случайную инициализацию» ваших весов до правильной оптики относительно быстро. (Потому что пространство поиска мало)

примечание/изменение если ваша сеть имеет только 2 скрытых узла, есть хорошие изменения, они застрянут в локальных минимумах. сеть даже размером ~ [2,7,1] должна иметь возможность сходиться без случайных шагов.

Смежные вопросы