Обсуждение статьи "Нейросети — это просто (Часть 5): Многопоточные вычисления в OpenCL"

 

Опубликована статья Нейросети — это просто (Часть 5): Многопоточные вычисления в OpenCL:

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

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

Полносвязный перцептрон

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

Автор: Dmitriy Gizlyk

 

Статья крайне сложна для не посвященного в OpenCL, я в свое пытался понять суть, прочитал статьи которые вы указали у себя и .... ничего не понял, точнее крайне частично.

Вот и ваша статья наверно супер, но слишком сложна, мне кажется нужно рассмотреть сами основы OpenCL, и возможно работу со встроенными библиотеками типа OpenCL.mqh, ведь без них не будет понимания, о чём и говорит отсутствие обсуждения статьи. Мне вот ваш код крайне не понятен, потому как в нем ссылки на множество библиотек о которых я не имею представления. 

Второй вопрос который мне не понятен - это как использовать OpenCL в режиме оптимизации. Что будет если одновременно несколько экземпляров полезут в видеокарту???? Потом есть куча приемов ускорить в разы вычисления даже одного и того же на OpenCL. Ну например избавиться от double в сторону int. Ну т.д. .... много вопросов.

Я предлагаю для начала рассмотреть совсем простой пример в коде многопоточного программирования. Предположим есть ОДИН перцептрон у которого n входов, соответственно нужно написать с использованием OpenCL простейший код расчета функций нормализации в диапазоне [-1,1] и вычисления самого значения нейрона по tanh. Причём сам код в OpenCL надо прокомментировать. И рассмотреть совсем простой торговый эксперт с оптимизацией в тестере на истории по ОДНОМУ индикатору, причём тоже простому то есть выдающему только одно значение ну типа RSI или CCI. Ну то есть все максимально упрощенно, без наворотов, прям совсем максимально просто. В последующем распространить это на множество перцептронов и написать свой эксперт думаю не проблема.

За статью спасибо.

 
Статья что нужно. Спасибо автору. Параллельные вычисления в нейронах понял с первого раза. Просто нужно "дозреть".))
 
Реter Konow:
Статья что нужно. Спасибо автору. Параллельные вычисления в нейронах понял с первого раза. Просто нужно "дозреть".))

ну вот один не дозрел, второй .... а хочется всем наверно 

согласен с тем что статья нужная

я усиленно хочу понять наконец это

может вы напишете то что я просил, раз это для вас так просто
 
Boris Egorov:

ну вот один не дозрел, второй .... а хочется всем наверно 

согласен с тем что статья нужная

я усиленно хочу понять наконец это

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

Многопоточность в работе сети необходима для ускорения обработки слоем своего обьема информации (данных) и передачи эстафеты след.слою. 

Каждый нейрон обрабатывает свой "кусок" пространства значений в своем потоке, не заставляя себя ждать другие нейроны. В этом преимущество использования многопоточности в НС, которая обеспечивается технологией OpenCL.
 
Всё это интересно, хотя и мало вероятно, что повторимо мной, но меня озадачил вопрос - откуда прирост в 10 раз, если при обучении задействовали всего ещё одно ядро?
 
Aleksey Vyazmikin:
Всё это интересно, хотя и мало вероятно, что повторимо мной, но меня озадачил вопрос - откуда прирост в 10 раз, если при обучении задействовали всего ещё одно ядро?

насколько я понял, он при обучении на OpenCL паралелльно делает расчет сразу нескольких индикаторов

 
Aleksey Vyazmikin:
Всё это интересно, хотя и мало вероятно, что повторимо мной, но меня озадачил вопрос - откуда прирост в 10 раз, если при обучении задействовали всего ещё одно ядро?

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

 

Вот некоторые моменты не понятны

__kernel void FeedForward(__global double *matrix_w,
                          __global double *matrix_i,
                          __global double *matrix_o,
                          int inputs, 
                          int activation)
  {
   int i=get_global_id(0); //Что делает эта строка?
   double sum=0.0;
   double4 inp, weight;
   int shift=(inputs+1)*i; //Что делает эта строка?
   for(int k=0; k<=inputs; k=k+4)
     {
      switch(inputs-k)
        {
         case 0:
           inp=(double4)(1,0,0,0); //Что делает эта строка?
           weight=(double4)(matrix_w[shift+k],0,0,0);
           break;
         case 1:
           inp=(double4)(matrix_i[k],1,0,0);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],0,0);
           break;
         case 2:
           inp=(double4)(matrix_i[k],matrix_i[k+1],1,0);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],0);
           break;
         case 3:
           inp=(double4)(matrix_i[k],matrix_i[k+1],matrix_i[k+2],1);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],matrix_w[shift+k+3]);
           break;
         default:
           inp=(double4)(matrix_i[k],matrix_i[k+1],matrix_i[k+2],matrix_i[k+3]);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],matrix_w[shift+k+3]);
           break;
        }
      sum+=dot(inp,weight); //Что делает эта строка?
     }
   switch(activation)
     {
      case 0:
        sum=tanh(sum);
        break;
      case 1:
        sum=pow((1+exp(-sum)),-1);
        break;
     }
   matrix_o[i]=sum;
  }

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

 
Boris Egorov:

Вот некоторые моменты не понятны

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

Добрый день, Борис.
Вы приложили код кернела. Как написал выше Максим, это микропрограмма, которая выполняется на ядрах микропроцессара или видеокарты (зависит от начальной инициализации контекста). Вся суть в том, что таких программ сразу вызывается несколько и они выполняются параллельно на всех ядрах. Каждая работает со своими данными.

int i=get_global_id(0); //Что делает эта строка?

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

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

double4 inp, weight;

Но размер входного массива не всегда будет кратен 4. Поэтому, чтобы избежать неверного обращения к массиву используется swith и недостающие значения заполняются "0". При этом, для каждого нейрона размерность массива весов на 1 элемент больше размерности входных элементов, это элемент используется в качестве байесовского смещения. Ранее мы для этого использовали дополнительный нейрон, в котором корректировали только веса и не пересчитывали выходное значение, которое всегда оставалось равное "1". Здесь, я не стал постоянно дописывать "1" в массив входных данных, а прописал ее прямо в коде, при этом размер входного массива не меняется.

           inp=(double4)(1,0,0,0); //Что делает эта строка?
           weight=(double4)(matrix_w[shift+k],0,0,0);

Функция dot возвращает скалярное произведение двух векторов, т.е. в нашем случае мы одной строчкой считаем сразу сумму 4-х произведений входных значений на веса.

sum+=dot(inp,weight); //Что делает эта строка?
 
Maxim Dmitrievsky:

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

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

Причина обращения: