Обсуждение статьи "Нейросети — это просто (Часть 9): Документируем проделанную работу"

 

Опубликована статья Нейросети — это просто (Часть 9): Документируем проделанную работу:

Мы уже проделали довольно большой путь и код нашей библиотеке сильно разрастается. Становится сложно отслеживать все связи и зависимости. И конечно, перед продолжением развития проекта нам нужно задокументировать уже проделанную работу и актуализировать документацию на каждом последующем шаге. Правильно подготовленная документация поможет нам увидеть целостность нашей работы.

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



Автор: Dmitriy Gizlyk

 

Отличный и полезный материал статьи

Спасибо!

 
Круто! Документация есть - можно и в СБ. Хотя еще надо бы LSTM на кёрнелы прикрутить, планируется?
 
Aleksey Mavrin:
Круто! Документация есть - можно и в СБ. Хотя еще надо бы LSTM на кёрнелы прикрутить, планируется?

Да, планируется :)

 
Dmitriy Gizlyk:

Да, планируется :)

Дмитрий, подскажите, сходу не въехал, зачем синус и косинус прибавляются к входным значениям

neuron.setOutputVal(inputVals.At(i)+(i%2==0 ? sin(i) : cos(i)) );

Ещё совет нужен - стоит ли попробовать для своих задач каким-либо образом нормализовать входные данные?

сейчас в примерах как я понял подаётся всё "как есть". Но даже в примерах с фракталами какие то осцилляторы от 0 до 1, а цены могут быть в зависимости от инструмента и гораздо больше 1.

Не создаёт ли это изначальный перекос при обучении на ненормализованных входах?

 
Aleksey Mavrin:

Дмитрий, подскажите, сходу не въехал, зачем синус и косинус прибавляются к входным значениям

Ещё совет нужен - стоит ли попробовать для своих задач каким-либо образом нормализовать входные данные?

сейчас в примерах как я понял подаётся всё "как есть". Но даже в примерах с фракталами какие то осцилляторы от 0 до 1, а цены могут быть в зависимости от инструмента и гораздо больше 1.

Не создаёт ли это изначальный перекос при обучении на ненормализованных входах?

Это для тайм эмбединга. Подробнее расскажу в следующей статье.
 
Dmitriy Gizlyk:
Это для тайм эмбединга. Подробнее расскажу в следующей статье.

Усиленно пытаюсь понять смысл)

Входные значения арифметически корректируются на константную матрицу от 0 до 1, причём вне зависимости от того, что из себя представляют входные данные и их абсолютных значений.

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

Ок, это понятно, видимо не важно что  колебания входных данных каждого бара с разной фазой, или это фича.

Но тогда вопрос про нормализацию тем более становиться актуальным. Совсем разная значимость получается к примеру для EURUSD и SP500.

И видимо правильно этот тайм-эмбединг вынести из библы в функцию Train.

 
Про позиционные эмбединги интересно почитать было бы. Но есть сомнения, что они сильно необходимы. Можно же использовать волатильность, она сама как синусоида. Но понимаю что это общая практика для временных рядов. Может там ещё какие-то «ноу-хау» напридуманы.
 

@Dmitriy Gizlyk, вопрос возник при изучении и работе с библиотекой:

в методе вычисления градиента для скрытых слоёв прибавляете outputVal

это для последующей компенсации его значения в дальнейшем в методе calcOutputGradients для универсальности, верно?

Также вы добавили нормализацию градиента

bool CNeuron::calcHiddenGradients(CLayer *&nextLayer)
  {
   double targetVal=sumDOW(nextLayer)+outputVal;
   return calcOutputGradients(targetVal);
  }
//+------------------------------------------------------------------+
bool CNeuron::calcOutputGradients(double targetVal)
  {
   double delta=(targetVal>1 ? 1 : targetVal<-1 ? -1 : targetVal)-outputVal;
   gradient=(delta!=0 ? delta*activationFunctionDerivative(outputVal) : 0);
   return true;
  }

Вопрос собственно - не правильнее ли будет нормализовать не таргет, а итоговую дельту  вот так 

double delta=targetVal-outputVal;
delta=delta>1?1:delta<-1?-1:delta;

почему? Пример: Если outputVal близка к 1, а суммарный взвешенный градиент следующего слоя тоже высок и положительный, то сейчас получаем в итоге дельту близкую к нулю, что видится неверным.

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

Также этот же момент и для OCL кода

__kernel void CalcHiddenGradient(__global double *matrix_w,
                                 __global double *matrix_g,
                                 __global double *matrix_o,
                                 __global double *matrix_ig,
                                 int outputs, int activation)
  {
..............   
switch(activation)
     {
      case 0:
        sum=clamp(sum+out,-1.0,1.0)-out;
        sum=sum*(1-pow(out==1 || out==-1 ? 0.99999999 : out,2));
 
Aleksey Mavrin:

@Dmitriy Gizlyk, вопрос возник при изучении и работе с библиотекой:

в методе вычисления градиента для скрытых слоёв прибавляете outputVal

это для последующей компенсации его значения в дальнейшем в методе calcOutputGradients для универсальности, верно?

Также вы добавили нормализацию градиента

Вопрос собственно - не правильнее ли будет нормализовать не таргет, а итоговую дельту  вот так 

почему? Пример: Если outputVal близка к 1, а суммарный взвешенный градиент следующего слоя тоже высок и положительный, то сейчас получаем в итоге дельту близкую к нулю, что видится неверным.

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

Также этот же момент и для OCL кода

Не совсем так. Мы проверяем значения таргета, как и в скрытых слоях прибавляем к градиенту outpuVal для получения таргета и проверки его значения. Дело в том, что сигмоиды имеют ограниченный диапазон результатов: логистическая функция от 0 до 1, tanh - от -1 до 1. Если мы будем неограниченно наказывать нейрон за отклонение и увеличивать весовой коэффициент, то придем к переполнению веса. Ведь если мы в какой-то пришли к значению нейрона равному 1, а последующий слой передавая ошибку говорит что надо нарастить значение до 1,5. Нейрон будет послушно на каждой итерации увеличивать веса, а функция активации будет обрезать значения на уровне 1. Поэтому, я ограничиваю значения таргета диапазонам допустимых значений функции активации. А корректировку за пределами диапазона оставляю на веса последующего слоя.

 
Dmitriy Gizlyk:

Не совсем так. Мы проверяем значения таргета, как и в скрытых слоях прибавляем к градиенту outpuVal для получения таргета и проверки его значения. Дело в том, что сигмоиды имеют ограниченный диапазон результатов: логистическая функция от 0 до 1, tanh - от -1 до 1. Если мы будем неограниченно наказывать нейрон за отклонение и увеличивать весовой коэффициент, то придем к переполнению веса. Ведь если мы в какой-то пришли к значению нейрона равному 1, а последующий слой передавая ошибку говорит что надо нарастить значение до 1,5. Нейрон будет послушно на каждой итерации увеличивать веса, а функция активации будет обрезать значения на уровне 1. Поэтому, я ограничиваю значения таргета диапазонам допустимых значений функции активации. А корректировку за пределами диапазона оставляю на веса последующего слоя.

Думаю я понял. Но всё же интересно насколько это верный подход, такой пример:

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

Допустим в предыдущем слое есть нейрон, который выдавал +0,6, т.е. он же выдавал значение верное - его вес должен увеличиться в плюс. А этой нормировкой мы урезаем изменение его веса.

получится норм(1,6)=1.  1-0,6=0,4, а если нормировать как я предложил, то будет 1. В этом случае мы тормозим усиление правильного "нейрона".

Как считаете?

Насчёт бесконечного увеличения весов, что-то вроде слышал что это бывает в случае "плохой функции ошибок", когда много локальных минимумов и нет выраженного глобального, или функция невыпуклая, что-то такое, я не супер спец, просто полагаю что бороться с бесконечными весами можно и нужно и другими методами.

Проситься эксперимент протестить оба варианта. Если додумаюсь как сформулировать тест )