Как написать нейросеть на c
Перейти к содержимому

Как написать нейросеть на c

  • автор:

Простенькая нейронная сеть на C++

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

У каждой нейронной сети есть слои, в нашем случае три слоя: входной (input layer), скрытый (hidden layer) и выходной (output layer). Как понятно из названия, на первый слой подаются некоторые данные, выходной отдаёт обработанные. Но что же делает скрытый слой и почему он так называется? Дело в том, что этот слой недоступен пользователю, но очень важен, потому что без него наша сеть будет глупой. Каждый слой имеет по несколько нейронов (а может и довольно много, к примеру, нейросеть, обрабатываются изображения, обязана иметь очень много входных нейронов, та же картинка 1920×1080 содержит 2,073,600 пикселей), связанных с нейронами следующего слоя. По этим связам «передаётся» сигнал, при том эти связи ещё и имеют некоторый коэффициент. Рассмотрим поближе:

Каждый входной нейрон связан с нейроном h₁, я отметил эти связи специально красным. На вход этого нейрона идут сигналы со всего предудущего слоя, так же и со вторым. В итоге этот нейрон обрабатывает полученные данные, прогоняет через функцию активации и выдаёт. Но как он их обрабатывает?

Суть в том, что каждая связь имеет некоторый коэффициент, называемый «весом» (weight), он, грубо говоря, определяет важность такого-то нейрона и именно он подбирается при обучении. При том этот коэффициент принадлежит каждому из связей, как показано ниже:

Выглядит непонятно? Да, тут скорее моя вина, но сейчас разъясню: первое число — номер данного нейрона, второе — нейрон, к которому идёт. А в случае связей между скрытым и выходным, думаю, понятно. Итак, как же мы обрабатываем сигналы? Сигналы со всех нейронов перемножаются на собственные веса и складываются. Пример:

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

Но в таком виде это лишь вход на скрытый нейрон, а как же получить выход? Дело в том, что настоящие нейроны должны «возбудиться» от сигнала, потому нам потребуется функция активации и мы будем использовать самую популярную из них — сигмоиду, так выглядит её график:

А так она сама (формула прямиком из википедии):

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

На этом моменте я буду приводить примеры кода на C++, используя библиотеку boost, код на гитхабе

Подключаем заголовные файлы матриц, векторов и их вывода:

#include #include #include 

Затем для удобства мы воспользуемся директивой using:

using boost::numeric::ublas::matrix; using boost::numeric::ublas::vector; using boost::numeric::ublas::prod;

boost::numeric::ublas::prod — умножение матриц.

Это матрица весов входных нейронов к нейронам скрытого слоя. Каждая строка ведёт к каждому нейрону скрытого слоя от конкретного нейрона входного слоя. Теперь вектор входных сигналов:

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

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

matrix weights_itoh(2, 3, 0); vector inputs(3, 0);

Матрица весов от входных нейронов к скрытым. Что не так с размерностью? Ну, дело в том, что матрицы считают по столбцам и строкам, а не наоборот. В нашей матрице 2 столбца и 3 строки и заполняем её нулями (для эстетики). Вектор же имеет три компоненты и тоже заполняется нулями.

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

Сразу же за этим я создаю матрицу весов скрытого слоя к выходному и инициализирую вектор сигналов:

matrix weights_htoo(1, 2, 0); vector outputs_hidden = sigmoid(prod(weights_itoh, inputs));

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

vector output = sigmoid(prod(weights_htoo, outputs_hidden));

Можем его вывести через std::cout, потому что мы подключили заголовочный файл вывода матриц и векторов.

И что, всё? Ну да, я здесь не обучаю её, потому что статья и так получилось массивной (по моим ощущениям, ведь писал целый день) и у меня ещё мало материала, потому оставлю ниже ссылки на полезные ресурсы для новичков.

Тарик Рашид — «Создаём нейронную сеть» (правда, книга про питон)

  • https://habr.com/ru/post/312450/
  • https://habr.com/ru/post/313216/
  • https://habr.com/ru/post/425717/

Как написать свою первую нейронную сеть на C++? [закрыт]

Закрыт. На этот вопрос невозможно дать объективный ответ. Ответы на него в данный момент не принимаются.

Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы на него можно было дать ответ, основанный на фактах и цитатах.

Закрыт 2 года назад .

Я перепрочитал многие статьи, знаю, что такое сигмоид и другие функции активации, знаю метод обратного распространения и другие формулы. Я не могу лишь начать. Я читал коды других, но они были просто ужасны и без подробных объяснении. Имеются основные знания по C++, владение STL, многотопочностью и немного QT так же есть. Задачи решал. Пожалуйста, скажите, что мне, человеку, который прочитал лишь статьи на хабре по этой теме, делать.

Отслеживать
задан 21 дек 2020 в 12:47
45 7 7 бронзовых знаков

Напиши самую простую нейронную сеть. Где все через многомерные массивы (например трехмерный массив под веса) и самые стандартные функции forward , findError ну типа такого. Потом протести свою нейронку на каком нибудь максимально простом примере. например xor . А дальше попробуй использовать ООП. И задачу например распознание цифр. Обучающую выборку можно взять тут. Если хотите можем связаться, могу показать свою минимальную нейронку.

21 дек 2020 в 12:52

в большинстве случаев, нейронные сети — это (ну если супер грубо) просто умножение матриц. и все. Другое дело, что их нужно посчитать (называется «обучить»). Тут чуточку больше умножений, но все равно те же умножения.

21 дек 2020 в 14:27

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Да много я годков потратил чтобы сварганить следующий код на Python.4-5.Начиная анализы с numpy.Вы можете конвертировать в С, главное организовать статические массивы-они быстрее, for row in range(n) это всегда for(int row=0;row.Итак)

import math import numpy as np import matplotlib.pyplot as plt import math import random from serial_deserial import to_file, deserialization import sys TRESHOLD_FUNC = 0 TRESHOLD_FUNC_DERIV = 1 SIGMOID = 2 SIGMOID_DERIV = 3 RELU = 4 RELU_DERIV = 5 TAN = 6 TAN_DERIV = 7 INIT_W_MY = 8 INIT_W_RANDOM = 9 LEAKY_RELU = 10 LEAKY_RELU_DERIV = 11 INIT_W_CONST = 12 INIT_W_RANDN = 13 SOFTMAX = 14 SOFTMAX_DERIV = 15 PIECE_WISE_LINEAR = 16 PIECE_WISE_LINEAR_DERIV = 17 TRESHOLD_FUNC_HALF = 18 TRESHOLD_FUNC_HALF_DERIV = 19 MODIF_MSE = 20 NOOP = 0 ACC_GRAD = 1 APPLY_GRAD = 2 class Dense: def __init__(self): # конструктор self.in_ = None # количество входов слоя self.out_ = None # количество выходов слоя self.matrix = [0] * 10 # матрица весов self.biases = [0] * 10 self.cost_signals = [0] * 10 # вектор взвешенного состояния нейронов self.act_func = RELU self.hidden = [0] * 10 # вектор после функции активации self.errors = [0] * 10 # вектор ошибок слоя self.with_bias = False for row in range(10): # создаем матрицу весов # подготовка матрицы весов,внутренняя матрица self.inner_m = list([0] * 10) self.matrix[row] = self.inner_m ################### Функции обучения ###################### class NetCon: def __init__(self, alpha_sigmoid=1, alpha_tan=1, beta_tan=1): self.net = [None] * 2 # Двойной перпецетрон self.alpha_sigmoid = alpha_sigmoid self.alpha_tan = alpha_tan self.beta_tan = beta_tan for l_ind in range(2): self.net[l_ind] = Dense() self.sp_d = -1 # алокатор для слоев self.nl_count = 0 # количество слоев self.ready = False def make_hidden(self, layer_ind, inputs: list): layer = self.net[layer_ind] for row in range(layer.out_): tmp_v = 0 for elem in range(layer.in_): tmp_v += layer.matrix[row][elem] * inputs[elem] if layer.with_bias: tmp_v += layer.biases[row] layer.cost_signals[row] = tmp_v val = self.operations(layer.act_func, tmp_v) layer.hidden[row] = val def get_hidden(self, objLay: Dense): return objLay.hidden def feed_forwarding(self, inputs): self.make_hidden(0, inputs) j = self.nl_count for i in range(1, j): inputs = self.get_hidden(self.net[i - 1]) self.make_hidden(i, inputs) last_layer = self.net[j-1] return self.get_hidden(last_layer) def cr_lay(self, in_=0, out_=0, act_func=None, with_bias=False, init_w=INIT_W_RANDOM): self.sp_d += 1 layer = self.net[self.sp_d] layer.in_ = in_ layer.out_ = out_ layer.act_func = act_func if with_bias: layer.with_bias = True else: layer.with_bias = False for row in range(out_): for elem in range(in_): layer.matrix[row][elem] = self.operations( init_w, 0) layer.biases[row] = self.operations( init_w, 0) self.nl_count += 1 # Различные операции по числовому коду def operations(self, op, x): alpha_leaky_relu = 1.7159 alpha_sigmoid = 2 alpha_tan = 1.7159 beta_tan = 2/3 if op == RELU: if (x 0): return 1 else: return 0 elif op == TRESHOLD_FUNC_HALF: if x >= 1/2: return 1 else: return 0 elif op == TRESHOLD_FUNC_HALF_DERIV: return 1 elif op == PIECE_WISE_LINEAR: if x >= 1/2: return 1 elif x < 1/2 and x >-1/2: return x elif x None: fig: plt.Figure = None ax: plt.Axes = None fig, ax = plt.subplots() ax.plot(epochs, errors, label="learning", ) plt.xlabel('Эпоха обучения') plt.ylabel('loss') ax.legend() plt.savefig(_file) print("Graphic saved") plt.show() train_inp = ((1, 1), (0, 0), (0, 1), (1, 0)) # Логическое И train_out = ([1], [0], [0], [0]) def main(): epochs = 1000 l_r = 0.1 errors_y = [] epochs_x = [] # Создаем обьект параметров сети net = NetCon() # Создаем слои net.cr_lay(2, 3, PIECE_WISE_LINEAR, True, INIT_W_MY) net.cr_lay(3, 1, PIECE_WISE_LINEAR, True, INIT_W_MY) for ep in range(epochs): # Кол-во повторений для обучения gl_e = 0 for single_array_ind in range(len(train_inp)): inputs = train_inp[single_array_ind] output = net.feed_forwarding(inputs) e = net.calc_diff(output, train_out[single_array_ind]) gl_e += net.get_err(e) net.backpropagate(train_out[single_array_ind], train_inp[single_array_ind], l_r) #gl_e /= 2 print("error", gl_e) print("ep", ep) print() errors_y.append(gl_e) epochs_x.append(ep) if gl_e == 0: break plot_gr('gr.png', errors_y, epochs_x) # пост оценка - evaluate() for single_array_ind in range(len(train_inp)): inputs = train_inp[single_array_ind] output_2_layer = net.feed_forwarding(inputs) equal_flag = 0 out_net = net.net[1].out_ for row in range(out_net): elem_net = output_2_layer[row] elem_train_out = train_out[single_array_ind][row] if elem_net > 0.5: elem_net = 1 else: elem_net = 0 print("elem:", elem_net) print("elem tr out:", elem_train_out) if elem_net == elem_train_out: equal_flag = 1 else: equal_flag = 0 break if equal_flag == 1: print('-vecs are equal-') else: print('-vecs are not equal-') print("========") # to_file(nn_params, nn_params.net, loger, 'wei1.my') main() Graphic saved elem: 1 elem tr out: 1 -vecs are equal- ======== elem: 0 elem tr out: 0 -vecs are equal- ======== elem: 0 elem tr out: 0 -vecs are equal- ======== elem: 0 elem tr out: 0 -vecs are equal- ======== 

Используем модель нейросети на C++

Большинство туториалов по созданию и использованию моделей нейросетей написаны на Python. Однако для какого-нибудь проекта рано или поздно может понадобится использовать более быстрый и надежный язык для этих задач, например, C++. Эта статья о том, как на C++ можно использовать модель нейросети на примере модели YOLOv8 для детектирования лиц и библиотеки PyTorch.

Библиотеки

PyTorch предоставляет C++-версию своего модуля — библиотеку LibTorch. Её можно скачать (cxx11 ABI) и расположить рядом с будущей программой:

wget https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.0.0%2Bcpu.zip unzip libtorch-cxx11-abi-shared-with-deps-2.0.0%2Bcpu.zip

Для примера также понадобится библиотека OpenCV. Её стоит скачать и установить:

wget https://github.com/opencv/opencv/archive/4.7.0.zip unzip opencv-4.7.0.zip && cd opencv-4.7.0 mkdir build && cd build cmake .. cmake --build . sudo make install

Веса

Обычный формат весов модели PyTorch необходимо экспортировать в формат TorchScript . Для большинства моделей достаточно написать следующее:

# model_export.py import torch MODEL_PATH = 'model.pt' MODEL_INPUT_SHAPE = (1,3,640,640) model = torch.load(MODEL_PATH) example = torch.randn(MODEL_INPUT_SHAPE) model(example) # model warmup model_traced = torch.jit.trace(model, example) model_traced.save('model_traced.torchscript')

Однако для примера я использую модель YOLOv8 для детектирования лиц от Ultralytics, её можно экспортировать с помощью готовых модулей:

pip3 install ultralytics pip3 install gdown gdown 1jG8_C_P0SbnzYROZORe7CiJO6oMgp7eZ # yolov8n-face.pt
# model_export_yolov8face.py import torch from ultralytics import YOLO model = YOLO('yolov8n-face.pt') model.export(format='torchscript')

Использование

Инклюды, либы и линковка

Для компилятора будущей программы необходимо указать путь до icnlude- и lib-файлов библиотек LibTorch и OpenCV, а также линковать необходимые lib-файлы (они понадобятся во время компиляции; для автоматизации стоит использовать IDE, например, Visual Studio Code):

# . -I/usr/local/include/opencv4 -I/path/to/libtorch/include -L/path/to/libtorch/lib # . -o main.o # . -lopencv_core -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_videoio -ltorch -ltorch_cpu -lc10

Загрузка модели

Загрузить модель достаточно просто:

// main.cpp #include #include int main()

Загрузка данных на вход модели

LibTorch принимает на вход моделей вектор из torch::jit::IValue и возвращает значения torch::jit::IValue , которые затем необходимо экспортировать в привычные типы (тензор .toTensor() , кортеж .toTuple() или любой другой доступный).

Модель-пример принимает на вход тензор с формой (B, 3, 640, 640) нормализованного изображения, а возвращает тензор с формой (1, 5, 8400) , где 5 тензоров содержат 8400 целых значений cx, cy, w, h и вещественный conf для каждого бокса детектирования соответственно. Реализуем загрузку данных на вход модели и получение выходов:

// main.cpp // . cv::Mat img = cv::imread(source); cv::cvtColor(img, img, cv::COLOR_BGR2RGB); cv::normalize(img, img, 0.0, 1.0, cv::NORM_MINMAX, CV_32F); vector inputs = < torch::from_blob( imgNorm.data, , torch::kFloat32 ).permute().unsqueeze(0) >; at::Tensor outputs = model.forward(inputs).toTensor(); // . 

Детектирование лиц

Далее реализуем использование конкретно модели-примера.

Создадим класс Box для боксов детектирования:

// detect.h // . class Box < public: int x1, y1, x2, y2; float conf; Box(int x1, int y1, int x2, int y2, float conf) < this->x1 = x1; this->y1 = y1; this->x2 = x2; this->y2 = y2; this->conf = conf; > >; // . 

Создадим функцию getBoxes , которая будет возвращать боксы отфильтрованные по уверенности conf и с помощью будущего Non Maximum Supression (NMS):

// detect.h // . vector getBoxes( at::Tensor &outputs, float confThres = 0.25, float iouThres = 0.15 ) < vectorcandidates; for (unsigned short ibatch = 0; ibatch < outputs.sizes()[0]; ibatch++) < for (unsigned short ibox = 0; ibox < outputs.sizes()[2]; ibox++) < float conf = outputs[ibatch][4][ibox].item(); if (conf >= confThres) < unsigned short cx = outputs[ibatch][0][ibox].item(), cy = outputs[ibatch][1][ibox].item(), w = outputs[ibatch][2][ibox].item(), h = outputs[ibatch][3][ibox].item(); unsigned short x1 = cx - w / 2, y1 = cy - h / 2, x2 = cx + w / 2, y2 = cy + h / 2; candidates.push_back(Box(x1,y1,x2,y2,conf)); > > > sort(candidates.begin(), candidates.end(), [](Box b1, Box b2) b2.conf;>); vector boxes = nms(candidates, iouThres); return boxes; > // . 

Создадим функцию nms , которая будет фильтровать боксы-кандитаты и сопутствующую iou , которая будет вычислять для них метрику IoU (Intersection Over Union):

// detect.h // . float iou(Box &fb, Box &sb) < float inter = max(min(fb.x2, sb.x2) - min(fb.x1, sb.x1), 0) * max(min(fb.y2, sb.y2) - min(fb.y1, sb.y1), 0); float union_ = (fb.x2-fb.x1)*(fb.y2-fb.y1) + (sb.x2-sb.x1)*(sb.y2-sb.y1) - inter; return inter / union_; >vector nms(vector &boxes, float iouThres) < vectorsupBoxes; for (Box box: boxes) < bool valid = true; for (Box supBox: supBoxes) < if (iou(box, supBox) >iouThres) < valid = false; break; >> if (valid == true) < supBoxes.push_back(box); >> return supBoxes; > // getBoxes

Наконец, получим боксы детектирования:

// main.cpp // . vector boxes = getBoxes(outputs); // . 

А также напишем процедуру для выделения боксов на оригинальном изображении:

// detect.h // . void highlightBoxes(cv::Mat &img, vector &boxes) < cv::Scalar rectColor(0,192,0); unsigned short fontScale = 2, confPrecis = 2; for (Box box: boxes) < string text = to_string(box.conf); cv::rectangle(img, , , rectColor, 2); cv::rectangle( img, , , rectColor, -1 ); cv::putText(img, text, , cv::FONT_HERSHEY_PLAIN, fontScale, , 2); > > // . 

И выведем результат на экран:

// main.cpp // . highlightBoxes(img, boxes); cv::imshow("Result", img); // return

Пример результата

Заключение

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

Пример простой нейросети на С/C++

Решил поделиться простым и ёмким на мой взгляд решением нейронной сети на С++.

Почему эта информация должна быть интересна?

Ответ: я старался в минимальном наборе запрограммировать работу многослойного перцептрона, да так, чтобы его можно было настраивать как душе угодно всего в нескольких строчках кода, а реализация основных алгоритмов работы на «С» позволит с лёгкостью переносить на «С» ориентированные языки(в прочем и на любые другие) без использования сторонних библиотек!

Прошу взглянуть на то, что из этого вышло

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

Исходный код вы найдёте в конце статьи, а пока по порядку.

Начнём разбор

1) Архитектура и технические подробности

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

пример конфигурации

myNeuero.cpp

 inputNeurons = 100; //ширина входного слоя outputNeurons =2; //ширина выходного слоя nlCount = 4; //количество слоёв ( по факту их 3, указываемое число намеренно увеличено на 1 list = (nnLay*) malloc((nlCount)*sizeof(nnLay)); inputs = (float*) malloc((inputNeurons)*sizeof(float)); targets = (float*) malloc((outputNeurons)*sizeof(float)); list[0].setIO(100,20); //установка ширины INPUTS/OUTPUTS для каждого слоя list[1].setIO(20,6); // -//- list[2].setIO(6,3); // -//- list[3].setIO(3,2); // -//- выходной слой 

Обратите внимание, что установка ширины входа и выхода для каждого слоя выполняется по определённому правилу — вход текущего слоя = выходу предыдущего. Исключением является входной слой.

Таким образом, вы имеете возможность настраивать любую конфигурацию вручную или по заданному правилу перед компиляцией или после компиляции считывать данные из source файлов.

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

 #define learnRate 0.1 

— установка начальных весов

 #define randWeight (( ((float)qrand() / (float)RAND_MAX) - 0.5)* pow(out,-0.5)) 

Примечание: если слоёв больше трёх (nlCount > 4), то pow(out,-0.5) необходимо увеличивать, чтобы при прямом прохождении сигнала его энергия не сводилась к 0. Пример pow(out,-0.2)

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

Структура слоя

 struct nnLay < int in; int out; float** matrix; float* hidden; float* errors; int getInCount()int getOutCount() float **getMatrix() void updMatrix(float *enteredVal) < for(int ou =0; ou < out; ou++) < for(int hid =0; hid < in; hid++) < matrix[hid][ou] += (learnRate * errors[ou] * enteredVal[hid]); >matrix[in][ou] += (learnRate * errors[ou]); > >; void setIO(int inputs, int outputs) < in=inputs; out=outputs; hidden = (float*) malloc((out)*sizeof(float)); matrix = (float**) malloc((in+1)*sizeof(float)); for(int inp =0; inp < in+1; inp++) < matrix[inp] = (float*) malloc(out*sizeof(float)); >for(int inp =0; inp < in+1; inp++) < for(int outp =0; outp < out; outp++) < matrix[inp][outp] = randWeight; >> > void makeHidden(float *inputs) < for(int hid =0; hid < out; hid++) < float tmpS = 0.0; for(int inp =0; inp < in; inp++) < tmpS += inputs[inp] * matrix[inp][hid]; >tmpS += matrix[in][hid]; hidden[hid] = sigmoida(tmpS); > >; float* getHidden() < return hidden; >; void calcOutError(float *targets) < errors = (float*) malloc((out)*sizeof(float)); for(int ou =0; ou < out; ou++) < errors[ou] = (targets[ou] - hidden[ou]) * sigmoidasDerivate(hidden[ou]); >>; void calcHidError(float *targets,float **outWeights,int inS, int outS) < errors = (float*) malloc((inS)*sizeof(float)); for(int hid =0; hid < inS; hid++) < errors[hid] = 0.0; for(int ou =0; ou < outS; ou++) < errors[hid] += targets[ou] * outWeights[hid][ou]; >errors[hid] *= sigmoidasDerivate(hidden[hid]); > >; float* getErrors() < return errors; >; float sigmoida(float val) < return (1.0 / (1.0 + exp(-val))); >float sigmoidasDerivate(float val) < return (val * (1.0 - val)); >; >; 
2) Применение

Тестирование проекта с набором mnist произошло удачно, удалось добиться условной вероятности распознавания рукописного текста 0,9795 (nlCount = 4, learnRate = 0.03 и несколько эпох). Основная цель теста была в проверке работоспособности нейронной сети, с чем она справилась.

Ниже мы рассмотрим работу на «условной задаче».

Исходные данные:

-2 случайных входных вектора размером в 100 значений
-нейросеть со случайной генерацией весов
-2 заданные цели

Код в функции main()

 < //. ________ ДЛЯ ВЫВОДА ВМЕСТО qDebug() можете использовать std::cout или std::cerr myNeuro *bb = new myNeuro(); //----------------------------------INPUTS----GENERATOR------------- /! создаём 2 случайнозаполненных входных вектора qsrand((QTime::currentTime().second())); float *abc = new float[100]; for(int i=0; i<100;i++) < abc[i] =(qrand()%98)*0.01+0.01; >float *cba = new float[100]; for(int i=0; i <100;i++) < cba[i] =(qrand()%98)*0.01+0.01; >//---------------------------------TARGETS----GENERATOR------------- // создаем 2 цели обучения float *tar1 = new float[2]; tar1[0] =0.01; tar1[1] =0.99; float *tar2 = new float[2]; tar2[0] =0.99; tar2[1] =0.01; //--------------------------------NN---------WORKING--------------- // первичный опрос сети bb->query(abc); qDebug()query(cba); // обучение int i=0; while(i<100000) < bb->train(abc,tar1); bb->train(cba,tar2); i++; > //просмотр результатов обучения (опрос сети второй раз) qDebug()query(abc); qDebug()query(cba); >

Результат работы нейронной сети

image

Итоги

Как вы видите, вызов функции query(inputs) до обучения для каждого из векторов не даёт нам судить об их отличиях. Далее, вызывая функцию train(input, target), для обучения с целью расстановки весовых коэффициентов так, чтобы нейросеть в последующем могла различать входные вектора.

После завершения обучения наблюдаем, что попытка сопоставить вектору «abc» — «tar1», а «cba» — «tar2» удалась.

Вам предоставляется возможность используя исходные коды самостоятельно протестировать работоспособность и поэкспериментировать с конфигурацией!

P.S.: данный код писался из QtCreator, надеюсь «заменить вывод» вам не составит труда, оставляйте свои замечания и комментарии.

P.P.S.: если кому интересен детальный разбор работы struct nnLay<> пишите, будет новый пост.

P.P.P.S.: надеюсь кому нибудь пригодится «С» ориентированный код для переноса на другие инструменты.

Исходники

#include #include #include #include "myneuro.h" int main(int argc, char *argv[]) < QCoreApplication a(argc, argv); myNeuro *bb = new myNeuro(); //----------------------------------INPUTS----GENERATOR------------- qsrand((QTime::currentTime().second())); float *abc = new float[100]; for(int i=0; i<100;i++) < abc[i] =(qrand()%98)*0.01+0.01; >float *cba = new float[100]; for(int i=0; i <100;i++) < cba[i] =(qrand()%98)*0.01+0.01; >//---------------------------------TARGETS----GENERATOR------------- float *tar1 = new float[2]; tar1[0] =0.01; tar1[1] =0.99; float *tar2 = new float[2]; tar2[0] =0.99; tar2[1] =0.01; //--------------------------------NN---------WORKING--------------- bb->query(abc); qDebug()query(cba); int i=0; while(i<100000) < bb->train(abc,tar1); bb->train(cba,tar2); i++; > qDebug()query(abc); qDebug()query(cba); qDebug()
#include "myneuro.h" #include myNeuro::myNeuro() < //--------многослойный inputNeurons = 100; outputNeurons =2; nlCount = 4; list = (nnLay*) malloc((nlCount)*sizeof(nnLay)); inputs = (float*) malloc((inputNeurons)*sizeof(float)); targets = (float*) malloc((outputNeurons)*sizeof(float)); list[0].setIO(100,20); list[1].setIO(20,6); list[2].setIO(6,3); list[3].setIO(3,2); //--------однослойный--------- // inputNeurons = 100; // outputNeurons =2; // nlCount = 2; // list = (nnLay*) malloc((nlCount)*sizeof(nnLay)); // inputs = (float*) malloc((inputNeurons)*sizeof(float)); // targets = (float*) malloc((outputNeurons)*sizeof(float)); // list[0].setIO(100,10); // list[1].setIO(10,2); >void myNeuro::feedForwarding(bool ok) < list[0].makeHidden(inputs); for (int i =1; ireturn; > else < // printArray(list[3].getErrors(),list[3].getOutCount()); backPropagate(); >> void myNeuro::backPropagate() < //-------------------------------ERRORS-----CALC--------- list[nlCount-1].calcOutError(targets); for (int i =nlCount-2; i>=0; i--) list[i].calcHidError(list[i+1].getErrors(),list[i+1].getMatrix(), list[i+1].getInCount(),list[i+1].getOutCount()); //-------------------------------UPD-----WEIGHT--------- for (int i =nlCount-1; i>0; i--) list[i].updMatrix(list[i-1].getHidden()); list[0].updMatrix(inputs); > void myNeuro::train(float *in, float *targ) < inputs = in; targets = targ; feedForwarding(true); >void myNeuro::query(float *in) < inputs=in; feedForwarding(false); >void myNeuro::printArray(float *arr, int s) < qDebug()>
#ifndef MYNEURO_H #define MYNEURO_H #include #include #include #include #define learnRate 0.1 #define randWeight (( ((float)qrand() / (float)RAND_MAX) - 0.5)* pow(out,-0.5)) class myNeuro < public: myNeuro(); struct nnLay< int in; int out; float** matrix; float* hidden; float* errors; int getInCount()int getOutCount() float **getMatrix() void updMatrix(float *enteredVal) < for(int ou =0; ou < out; ou++) < for(int hid =0; hid < in; hid++) < matrix[hid][ou] += (learnRate * errors[ou] * enteredVal[hid]); >matrix[in][ou] += (learnRate * errors[ou]); > >; void setIO(int inputs, int outputs) < in=inputs; out=outputs; hidden = (float*) malloc((out)*sizeof(float)); matrix = (float**) malloc((in+1)*sizeof(float)); for(int inp =0; inp < in+1; inp++) < matrix[inp] = (float*) malloc(out*sizeof(float)); >for(int inp =0; inp < in+1; inp++) < for(int outp =0; outp < out; outp++) < matrix[inp][outp] = randWeight; >> > void makeHidden(float *inputs) < for(int hid =0; hid < out; hid++) < float tmpS = 0.0; for(int inp =0; inp < in; inp++) < tmpS += inputs[inp] * matrix[inp][hid]; >tmpS += matrix[in][hid]; hidden[hid] = sigmoida(tmpS); > >; float* getHidden() < return hidden; >; void calcOutError(float *targets) < errors = (float*) malloc((out)*sizeof(float)); for(int ou =0; ou < out; ou++) < errors[ou] = (targets[ou] - hidden[ou]) * sigmoidasDerivate(hidden[ou]); >>; void calcHidError(float *targets,float **outWeights,int inS, int outS) < errors = (float*) malloc((inS)*sizeof(float)); for(int hid =0; hid < inS; hid++) < errors[hid] = 0.0; for(int ou =0; ou < outS; ou++) < errors[hid] += targets[ou] * outWeights[hid][ou]; >errors[hid] *= sigmoidasDerivate(hidden[hid]); > >; float* getErrors() < return errors; >; float sigmoida(float val) < return (1.0 / (1.0 + exp(-val))); >float sigmoidasDerivate(float val) < return (val * (1.0 - val)); >; >; void feedForwarding(bool ok); void backPropagate(); void train(float *in, float *targ); void query(float *in); void printArray(float *arr,int s); private: struct nnLay *list; int inputNeurons; int outputNeurons; int nlCount; float *inputs; float *targets; >; #endif // MYNEURO_H

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *