Что не так с моей реализацией BPTT? - PullRequest
0 голосов
/ 30 апреля 2020

Я пытался внедрить Backpropagation через время вручную, но в итоге сеть не сходилась. Я попытался посмотреть на net описания и курсы по BPTT, и код делает все соответственно:

  • Прямое распространение
  • Ошибка распространения назад
  • Градиент вычисление, основанное на ожидаемых значениях
  • Обновление весов на основе градиента и скорости обучения

Способ, которым я понимаю периодические производные, заключается в том, что в случае периодических нейронных сетей входные данные с предыдущего шага нельзя считать константой. Например: производная от w1 на 3-м шаге зависит не только от ввода текущего шага, но и от предыдущих шагов. Вот почему dw1[1] = net_inputs_train[first_sample_index + 1][0]; неверно, оно должно быть dw1[1] = net_inputs_train[first_sample_index + 1][0] + dw1[0] * w3;.

Предполагается, что все остальное должно быть "только" для обратного распространения в развернутой сети. К сожалению, эта программа просто не работает, ошибка просто скачет без конвергенции net ..

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

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

int main(int argc, char *argv[]){

  srand(time(nullptr));

  /* Manual BPTT with one custom implemented Neuron */
  double number_of_samples = 3;  /* Binary addition dataset */
  vector<vector<double>> net_inputs_train = {  /* 2 inputs in each step */
      {1,1},    {0,0},  {0,0}, /* 100 + 100 = 110 */
      {1,0},    {0,1},  {1,0}, /* 101 + 010 = 111*/
      {1,0},    {1,1},  {0,0}, /* 110 + 010 = 111 */
  };

  vector<vector<double>> expected_output = { /* 1 output in each step */
      {1},      {1},    {0}, /* 110 */
      {1},      {1},    {1}, /* 111 */
      {1},      {1},    {1}, /* 111 */
  };

  double w1 = 0.5;
  double w2 = 0.5;
  double w3 = 0.5;
  double b = 0.0;

  vector<double> neuron_data(3,0);
  vector<double> neuron_deriv(3,0); /* Neuron error value ( partial based on the output )*/

  vector<double> dw1(3,0); /* derivatives for weights for each sequence */
  vector<double> dw2(3,0);
  vector<double> dw3(3,0);
  vector<double> derb(3,0);

  int first_sample_index;
  double manual_error = 1.0;
  double learning_rate = 1e-2;
  while(manual_error > learning_rate){
    for(int mbIter = 0; mbIter < 4; ++mbIter){
      first_sample_index = (rand()%(static_cast<int>(number_of_samples)));

      /* Fill in the data and derviatives */
      neuron_data[0] = (
        net_inputs_train[first_sample_index][0] * w1
        + net_inputs_train[first_sample_index][1] * w2
        + b
      );
      dw1[0] = net_inputs_train[first_sample_index][0];
      dw2[0] = net_inputs_train[first_sample_index][1];
      dw3[0] = 0;
      derb[0] = 1;

      neuron_data[1] = (
        net_inputs_train[first_sample_index + 1][0] * w1
        + net_inputs_train[first_sample_index + 1][1] * w2
        + neuron_data[0] * w3
        + b
      );
      dw1[1] = net_inputs_train[first_sample_index + 1][0] + dw1[0] * w3;
      dw2[1] = net_inputs_train[first_sample_index + 1][1] + dw2[0] * w3;
      dw3[1] = neuron_data[0] + w3 * dw3[0];
      derb[1] = 1 + derb[0] * w3;

      neuron_data[2] = (
        net_inputs_train[first_sample_index + 2][0] * w1
        + net_inputs_train[first_sample_index + 2][1] * w2
        + neuron_data[1] * w3
        + b
      );
      dw1[2] = net_inputs_train[first_sample_index + 2][0] + dw1[1] * w3;
      dw2[2] = net_inputs_train[first_sample_index + 2][1] + dw2[1] * w3;
      dw3[2] = neuron_data[1] + w3 * dw3[1];
      derb[2] = 1 + derb[1] * w3;

      /* Calculate the error and the gradients */
      manual_error = (
        pow((neuron_data[2] - expected_output[first_sample_index + 2][0]),2)/2.0
        +pow((neuron_data[1] - expected_output[first_sample_index + 1][0]),2)/2.0
        +pow((neuron_data[0] - expected_output[first_sample_index + 0][0]),2)/2.0
      );

      neuron_deriv[2] = (
        (-(neuron_data[2] - expected_output[first_sample_index + 2][0])/2.0)
      );
      neuron_deriv[1] = (
        (-(neuron_data[1] - expected_output[first_sample_index + 1][0])/2.0)
        + (w3 * neuron_deriv[2])
      );
      neuron_deriv[0] = (
        (-(neuron_data[0] - expected_output[first_sample_index + 0][0])/2.0)
        + (w3 * neuron_deriv[1])
      );

      w1 += (learning_rate * (
        neuron_deriv[2] * dw1[2]
        + neuron_deriv[1] * dw1[1]
        + neuron_deriv[0] * dw1[0]
      ) / number_of_samples);

      w2 += (learning_rate * (
        neuron_deriv[2] * dw2[2]
        + neuron_deriv[1] * dw2[1]
        + neuron_deriv[0] * dw2[0]
      ) / number_of_samples);

      w3 += (learning_rate * (
        neuron_deriv[2] * dw3[2]
        + neuron_deriv[1] * dw3[1]
        + neuron_deriv[0] * dw3[0]
      ) / number_of_samples);

      b += (learning_rate * (
        neuron_deriv[2] * derb[2]
        + neuron_deriv[1] * derb[1]
        + neuron_deriv[0] * derb[0]
      ) / number_of_samples);
      std::cout << "\r Error: " << manual_error << "                    \n";
    }
  }

  return 0;
}

Редактировать: Одна интересная вещь, это то, что обучение сходится, если w1 += (learning_rate * (...)/number_of_samples); переключено на w1 += ((...)/number_of_samples);

Ответы [ 2 ]

1 голос
/ 30 апреля 2020

Я предполагаю, что это опечатка:

 w1 += ((
        neuron_deriv[2] * dw1[2]
        + neuron_deriv[1] * dw1[1]
        + neuron_deriv[0] * dw1[0]
      ) / 300.0);                   // why?

, поскольку вы не делаете то же самое для других весов.

Если вы измените его, чтобы оно соответствовало тому, как вы вычисляете другое веса:

 w1 += ((
        neuron_deriv[2] * dw1[2]
        + neuron_deriv[1] * dw1[1]
        + neuron_deriv[0] * dw1[0]
      ) / number_of_samples);       // makes more sense

это сходится .

Возможно, вы намеревались использовать константу 300.0 в расчете b. Независимо от этого, не используйте магический номер c как этот; дать ему имя.

Другие вопросы; не используйте using namespace std;. Сделайте все свои постоянные значения const, или даже лучше, constexpr. Кроме того, разделите код в l oop на несколько именованных функций. например, если вы сделаете это для расчета веса, несоответствие в вашем коде никогда не произойдет.

0 голосов
/ 04 мая 2020

Так с чего мне начать? Помимо некоторых логических ошибок c (например, в строке 43, при настройке last_sample_index), основная проблема заключалась в том, что обратное распространение было смешано между последовательностями.

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

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

Учитывая это, я переработал код в разделите вычисления градиента в последовательностях.

Затем возникает проблема исчезающие / взрывающиеся градиенты . После вышеперечисленных переделок сеть все еще не сошлась из-за этого. После третьего пробоя и некоторых экспериментов, которые я обнаружил, просто наполовину уменьшить градиент, полученный из смещения второй последовательности, решает проблему исчезновения. Градиент отклонения смещен, потому что численно это самый большой из всех весов.

Теперь приведенная ниже программа работает с успешно сходящейся сетью.

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

int main(int argc, char *argv[]){

  srand(time(nullptr));

  /* Manual BPTT with one custom implemented Neuron */
  double sequence_size = 3;
  double number_of_samples = 3;  /* Binary addition dataset */
  double minibatch_size = 4;
  vector<vector<double>> net_inputs_train = {  /* 2 inputs in each step */
      {1,1},    {0,0},  {0,0}, /* 100 + 100 = 110 */
      {1,0},    {0,1},  {1,0}, /* 101 + 010 = 111*/
      {1,0},    {1,1},  {0,0}, /* 110 + 010 = 111 */
  };

  vector<vector<double>> expected_output = { /* 1 output in each step */
      {1},      {1},    {0}, /* 110 */
      {1},      {1},    {1}, /* 111 */
      {1},      {1},    {1}, /* 111 */
  };

  double w1 = 0.5;
  double w2 = 0.5;
  double w3 = 0.5;
  double b = 0.0;

  double gradw1; /* gradients for the weights */
  double gradw2;
  double gradw3;
  double gradb;

  vector<double> neuron_data(3,0);
  double neuron_deriv = 0; /* Neuron error value ( partial based on the expected output and the error function )*/

  vector<double> dw1(3,0); /* derivatives for weights for each sequence */
  vector<double> dw2(3,0);
  vector<double> dw3(3,0);
  vector<double> derb(3,0);

  int first_sample_index;
  double manual_error = 1.0;
  double learning_rate = 1e-2;
  while(manual_error > learning_rate){
    for(int mbIter = 0; mbIter < minibatch_size; ++mbIter){ /* minibatches */
      first_sample_index = sequence_size * (rand()%(static_cast<int>(number_of_samples)));
      gradw1 = 0;
      gradw2 = 0;
      gradw3 = 0;
      gradb = 0;

      /* Fill in the data and derviatives */
      neuron_data[0] = (
        net_inputs_train[first_sample_index][0] * w1
        + net_inputs_train[first_sample_index][1] * w2
        + b
      );
      dw1[0] = net_inputs_train[first_sample_index][0];
      dw2[0] = net_inputs_train[first_sample_index][1];
      dw3[0] = 0;
      derb[0] = 1;

      neuron_data[1] = (
        net_inputs_train[first_sample_index + 1][0] * w1
        + net_inputs_train[first_sample_index + 1][1] * w2
        + neuron_data[0] * w3
        + b
      );
      dw1[1] = net_inputs_train[first_sample_index + 1][0] + w3 * dw1[0];
      dw2[1] = net_inputs_train[first_sample_index + 1][1] + w3 * dw2[0];
      dw3[1] = neuron_data[0] + w3 * dw3[0];
      derb[1] = 1 + derb[0] * w3;

      neuron_data[2] = (
        net_inputs_train[first_sample_index + 2][0] * w1
        + net_inputs_train[first_sample_index + 2][1] * w2
        + neuron_data[1] * w3
        + b
      );
      dw1[2] = net_inputs_train[first_sample_index + 2][0] + w3 * dw1[1];
      dw2[2] = net_inputs_train[first_sample_index + 2][1] + w3 * dw2[1];
      dw3[2] = neuron_data[1] + w3 * dw3[1];
      derb[2] = 1 + derb[1] * w3;

      /* Calculate the error and the gradients */
      manual_error = (
         pow((neuron_data[2] - expected_output[first_sample_index + 2][0]),2)/2.0
        +pow((neuron_data[1] - expected_output[first_sample_index + 1][0]),2)/2.0
        +pow((neuron_data[0] - expected_output[first_sample_index + 0][0]),2)/2.0
      );    

      /* Calculate gradients for sequence 2 */
      neuron_deriv = (
       -(neuron_data[2] - expected_output[first_sample_index + 2][0])
       -w3*(neuron_data[2] - expected_output[first_sample_index + 2][0])
       -w3*(neuron_data[2] - expected_output[first_sample_index + 2][0])
      );
      gradw1 += dw1[2] * neuron_deriv;
      gradw2 += dw2[2] * neuron_deriv;
      gradw3 += dw3[2] * neuron_deriv;
      gradb += derb[2] * neuron_deriv / 2.0;

      /* Calculate gradients for sequence 1 */
      neuron_deriv = (
        -(neuron_data[1] - expected_output[first_sample_index + 1][0])
        -w3*(neuron_data[1] - expected_output[first_sample_index + 1][0])
      );
      gradw1 += dw1[1] * neuron_deriv;
      gradw2 += dw2[1] * neuron_deriv;
      gradw3 += dw3[1] * neuron_deriv;
      gradb += derb[1] * neuron_deriv;

      /* Calculate gradients for sequence 0 */
      neuron_deriv = -(neuron_data[0] - expected_output[first_sample_index + 0][0]);
      gradw1 += dw1[0] * neuron_deriv;
      gradw2 += dw2[0] * neuron_deriv;
      gradw3 += dw3[0] * neuron_deriv;
      gradb += derb[0] * neuron_deriv;

      w1 += (learning_rate * (gradw1) / (sequence_size * minibatch_size));
      w2 += (learning_rate * (gradw2) / (sequence_size * minibatch_size));
      w3 += (learning_rate * (gradw3) / (sequence_size * minibatch_size));
      b += (learning_rate * (gradb) / (sequence_size * minibatch_size));
      std::cout << "\r Error: " << manual_error << "                     ";
    }
  }
  std::cout << std::endl;

  return 0;
}

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

...