Home Искусственный интеллект Нейронная сеть с нуля — часть 1 | DeepTech

Нейронная сеть с нуля — часть 1 | DeepTech

0
Нейронная сеть с нуля — часть 1
 | DeepTech

Полностью подключенная нейронная сеть

Давайте создадим библиотеку нейронной сети с нуля. Я имею в виду, почему бы и нет? Вы можете сказать: Пфф… Большое дело.. С Python и Numpy это всего лишь вопрос часов. Что, если я скажу вам, что буду использовать C++. Нет, я шучу. Я собираюсь использовать С.

Причина этого в том, что я хочу обучить свою сеть на GPU, а GPU не понимают ни Python, ни даже C++. Мой план состоит в том, чтобы использовать OpenCL вместе с C++ для создания полнофункциональной библиотеки для создания собственной нейронной сети и ее обучения. И чтобы немного оживить его, почему бы не реализовать свёрточную нейронную сеть вместо простой, скучной полносвязной НС. Но обо всем по порядку.

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

OpenCL::initialize_OpenCL();

std::vector<std::vector<float> > inputs, targets;

std::vector<std::vector<float> > testinputs;

std::vector<float> testtargets;

ConvNN m_nn;

std::vector<int> netVec;

netVec = { 1024,10 };

m_nn.createFullyConnectedNN(netVec, 1, 32);

m_nn.trainFCNN(inputs, targets, testinputs, testtargets, 50000);

m_nn.trainingAccuracy(testinputs, testtargets, 2000, 1);

Хорошо, это обычный процесс каждого конвейера машинного обучения с той разницей, что вместо функций Sklearn или Tensorflow здесь у нас есть C++. Вполне достижение! Верно?

Все идет нормально. У нас есть базовая версия нашего программного обеспечения. Теперь пришло время разработать фактическую структуру нейронной сети. Базовым объектом любой NN является узел, и множество узлов, сложенных вместе, образуют слой. Вот оно:

Узел и слой

typedef struct Node {

int numberOfWeights;

float weights(1200);

float output;

float delta;

}Node;

typedef struct Layer {

int numOfNodes;

Node nodes(1200);

}Layer;

Так как это простой C, мы не можем использовать std::vector и нам нужен простой C, потому что приведенный выше код будет скомпилирован и выполнен реальным GPU. Но мы приближаемся к этому. Обратите внимание, что лучше, чем массив с предопределенной длиной, каждый раз выделять необходимое пространство в памяти, но это в другой раз.

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

Нейронная сеть

h_netVec = newNetVec;

Layer *inputLayer = layer(h_netVec(0), 0);

h_layers.push_back(*inputLayer);

for (unsigned int i = 1; i <h_netVec.size(); i++)

{

Layer *hidlayer = layer(h_netVec(i), h_netVec(i - 1));

h_layers.push_back(*hidlayer);

}

Вот оно. Наша простая нейронная сеть написана на C++. По сути, это не более чем вектор слоев, где каждый слой является вектором узлов. Вы можете подумать, что наша работа здесь сделана. Ха-ха! Мы даже не близки. Мы должны обучить нашу сеть на реальных данных. Это время, когда OpenCL вступает в игру.

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

Буферы OpenCL

d_InputBuffer = cl::Buffer(OpenCL::clcontext, CL_MEM_READ_WRITE, sizeof(float)*inpdim*inpdim);

tempbuf = cl::Buffer(OpenCL::clcontext, CL_MEM_READ_WRITE, sizeof(Node)*h_layers(0).numOfNodes);

(OpenCL::clqueue).enqueueWriteBuffer(tempbuf,CL_TRUE,0,sizeof(Node)*h_layers(0).numOfNodes,h_layers(0).nodes);

d_layersBuffers.push_back(tempbuf);

for (int i = 1; i<h_layers.size(); i++) {

tempbuf = cl::Buffer(OpenCL::clcontext, CL_MEM_READ_WRITE, sizeof(Node)*h_layers(i).numOfNodes);

(OpenCL::clqueue).enqueueWriteBuffer(tempbuf, CL_TRUE,0, sizeof(Node)*h_layers(i).numOfNodes, h_layers(i).nodes);

d_layersBuffers.push_back(tempbuf);

}

Не запутайтесь во всех этих “cl::”, “clqueue” и “context”. Это вещи OpenCL. Логика остается неосязаемой.

Прежде чем мы погрузимся в захватывающую часть, мы должны сделать еще одну вещь. Мы должны определить ядра OpenCL. Ядра — это фактический код, который выполняется графическим процессором. Всего нам нужно 3 ядра:

  • Один для прямого распространения
  • Один для обратного распространения в выходном слое
  • Один для отсталых в скрытом слое

ядро

compoutKern = cl::Kernel(OpenCL::clprogram, "compout");

backpropoutKern = cl::Kernel(OpenCL::clprogram, "backpropout");

bakckprophidKern = cl::Kernel(OpenCL::clprogram, "backprophid");

Ты угадал. Настала очередь GPU. Я не буду вдаваться в подробности о том, как работает OpenCL и как GPU обрабатывает данные, но кое-что нужно помнить:

  1. Графические процессоры имеют много ядер, поэтому они подходят для распараллеливания.
  2. Мы считаем, что каждое ядро ​​запускает код для одного узла слоя.
  3. Когда вычисления слоя завершены, мы переходим к следующему слою и так далее.

Обратное распространение

Имейте это в виду, теперь мы можем легко понять следующий фрагмент:

kernel void compout( global Node* nodes,global Node * prevnodes,int softflag)

{

const int n = get_global_size(0);

const int i = get_global_id(0);

float t = 0;

for ( int j = 0; j < nodes(i).numberOfWeights; j++)

t += nodes(i).weights(j) * prevnodes(j).output;

t+=0.1;

nodes(i).output =sigmoid(t);

}

А для обратного распространения имеем:

kernel void backprophid(global Node* nodes,global Node * prevnodes,global Node *nextnodes,int nextnumNodes,float a)

{

const int n = get_global_size(0);

const int i = get_global_id(0);

float delta = 0;

for (int j = 0; j !=nextnumNodes; j++)

delta += nextnodes(j).delta * nextnodes(j).weights(i);

delta *= devsigmoid(nodes(i).output);break;

nodes(i).delta = delta;

for (int j = 0; j != nodes(i).numberOfWeights; j++)

nodes(i).weights(j) -= a*delta*prevnodes(j).output;

}

kernel void backpropout(global Node* nodes,global Node * prevnodes,global float* targets,float a,int softflag )

{

const int n = get_global_size(0);

const int i = get_global_id(0);

float delta=0;

delta = (nodes(i).output-targets(i))*devsigmoid(nodes(i).output);

for (int j = 0; j !=nodes(i).numberOfWeights; j++)

nodes(i).weights(j) -= a*delta*prevnodes(j).output;

nodes(i).delta=delta;

}

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


Уравнения

Теперь все имеет смысл, верно?

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

Для получения полного кода посетите мой репозиторий github: Библиотека нейронных сетей

В следующей части мы расширим библиотеку, включив в нее сверточные нейронные сети. Следите за обновлениями…

Книга «Глубокое обучение в производстве» 📖

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

Узнать больше

* Раскрытие информации: Обратите внимание, что некоторые из приведенных выше ссылок могут быть партнерскими ссылками, и без дополнительной оплаты для вас мы будем получать комиссию, если вы решите совершить покупку после перехода по ссылке.

LEAVE A REPLY

Please enter your comment!
Please enter your name here