Pull to refresh

Comments 27

У вас есть ряд проблем с качеством кода.
-Неуместное использование макросов вместо constexpr-значания и constexpr-функции.Коротко: макросы хуже из-за того, что компилятор втыкает их в текст чисто механически, в отличие от constexpr, где он будет знать тип выражения и сможет ругаться в случае мест, подозрительных на ошибку и потерю точности.
-malloc и new в одной куче. Так на С++ писать нельзя — обязательно перепутаете пары malloc/free new/delete и засадите в программу неопределенное поведение, которое будет сложно отладить.
-отчасти, прошлый пункт компенсируется тем, что у вас деструкторов и очистки ресурсов просто нет. Да, программа при выходе все сама почистит, однако где гарантия, что ваш код не скопипастят в другую программу? Раз материал с намеком на обучение, не учите сразу плохому, мне после ваших обучалок, условно, джунов на работу брать.
-Использование сырых указателей — в ту же кучу камней — облом писать очистку руками? Заставь компилятор все почистить, благо это просто — RAII + смартпоинтеры называется.
-Куча магических констант по коду. Уж лучше юзать макросы, чем так поступать.
-Хранение индекса массива в переменной типа int — да, болваны-преподаватели любят так писать (благо слово всего из трех букв выходит), и в книжках тоже так пишут. Но это закладывает в программу страшную проблему под названием «несовместима с 64 битными архитектурами при обработке больших массивов». Просто юзайте size_t.
Мне кажется, что человек всю жизнь писал на С и решил пересесть на С++. Может быть поэтому столько «не стыковок»?
Больше похоже на бодрое студенчество на марше. Могу и ошибаться.
Спасибо за анализ, работа над ошибками будет. Согласен, чистоте кода было уделено недостаточно внимания, основная цель была заложить необходимую математику в код, чтобы другие начинающие разработчики не натыкались на использование сторонних библиотек ради пары функций. Повторюсь, весь функционал заложен в структуре на «С», которую я для примера обернул в ++ и qt.
Это один из самых неприятных вариантов развития событий — когда интеллектуально значимый код написан настолько грязно, что его невозможно поддерживать, адаптировать и так далее. В такой ситуации код легко может быть обесценен с формулировкой — «переписать нафиг».
Никнейм как бы намекает :)
А по делу — вопрос автору статьи: где вы брали данные и как подключали к программе? Хотелось бы самому такое пощупать, но, насколько я знаю база данных NNIST имеет размеры 28х28, которые в свою очередь были преобразованы из матрицы 20х20.
Добавил в конце статьи «UPD», применение для MNIST и ссылка на dataset. Откуда преобразовывали я не в курсе, главное знать что из 785 значений в строчке элемент «0» — маркер, далее 28х28 значений пикселей (1+28х28=785). В экспериментах не забывайте, чем больше сеть тем дольше ей обучаться, на глубоком обучении приходится вводить эпохи, менять скорость обучения (0,01-0,05 в данном случае) и корректировать значения начальных весов
Спасибо. Давно искал подобный материал, чтобы без библиотек, «голыми руками» пощупать нейросети. А то в соседней теме опять «хелловорды» на python затеяли.
Кстати, только сейчас заметил, что данные у вас генерируются, теперь разобрался.
На 64-разрядной системе программа вылетает с ошибкой «Segmentation fault». Нашел причину — проблема в выделении памяти. У вас в функции setIO в строке
matrix = (float**) malloc((in+1)*sizeof(float));
нужно указывать не sizeof(float), а sizeof(float*)
Возможно вы тестировали на 32 разрядной системе, там размер указателя совпадает с размером float и равен 4 байта.
На 64 разрядной системе размер указателя 8 байт, со всеми вытекающими последствиями — выделяется недостаточно памяти для массива указателей matrix.

Огромное спасибо, да действительно вы правы. На неделе подправлю, заодно в порядок приведу по советам gbg. В планах уйти от qt и перевести весь код на std. Ну а дальше будут фишки в отдельных ветках на git, типо свёртки и других приёмов. Главное сохранить максимальную простоту проекта

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

Если же речь идет о промышленной реализации нейросети, тогда не стоит делать велосипедной математики, а стоит взять Eigen, BLAS, ATLAS, Vienna CL и использовать их — они в любом случае окажутся быстрее и стабильнее.

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

По математике к вам тоже есть замечание — в виду того, что вы обрезаете число, которое является степенью двойки при помощи остатка от деления (qrand()%98), вы получаете из равномерного распределения (насколько уместен этот термин в контексте ГПСЧ из стандартной библиотеки я не рассматриваю) уже не совсем равномерное.
Чтобы с этим не возиться, возьмите генераторы и преобразователи псевдослучайных величин из C++11

А зачем? Как упражнение, навыки с++ потренировать — понятно. Но вы не используете векторизацию, то есть практического смысла мало.

Зачем что? Что вы понимаете под практическим смыслом? распишите подробнее

Зачем вы реализовали этот проект. Однако ответ я уже увидел выше — вам не понравились сторонние библиотеки и вы реализовали свою. Ваше право.


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

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

Желающие поучаствовать в развитии проекта с возможным преломлением на практике — писать сюда mamkin.itshnik@gmail.com

Посмотрите внимательный. Да немного, но есть, да можно переписать в С, но зачем мне искажать заголовок? Напишите решение где ++ больше или в ассемблер закатайте. Сообщество только выйграет от разнообразия материала. Каждый сам решит что ему ближе. По нейросетям много развелись статей, не хватает наверно DeepLearning на Assembler

Вы могли бы значительно упростить код, если бы вместо C-style массивов с malloc использовали std::vector. Уже это добавило бы плюсов в код, сделало бы его проще, понятнее и надёжнее. Выше в комментариях уже замечали, что в коде есть (была?) ошибка, связанная с неправильным вычислением размера участка памяти. Соответственно, все циклы, которые работают с массивами, могли бы быть преобразованы в range-based циклы, что избавило бы Вас от магических чисел в коде.

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

Изначально vector<vector<....> был! Да удобней с памятью, но грамоздко показалось, вот и решил в С-образно сделать да и в struct загнать. Ну переоценил силы немного, бывает

Если хочется краткости, то можно использовать using:
using MyMatrix = std::vector< std::vector < float > >


Пишется один раз, после чего использование становится намного проще. Единственное что, возвращать по значению из функции не всегда может быть разумно. Но у Вас всё же структура, вполне можно обойтись без get/set методов — они ничего не прячут, да и для примера реализации не нужны.
ну точно… в struct по дефолту все в public. Забавная не состыковка myneuro.h, private:
struct nnLay *list;
в ++ для указателей на структуру нет необходимости указывать struct. Для полного юмора указатель на структуру в «С» виде размещен в привате класса на плюсах. А никто и не заметил.
Плюсанул, хотя практического смысла в разработке нет :) Думаю, скоро автор закинет свой велосипед на чердак, и начнет пользоваться другими инструментами. Но велосипед этот сослужит очень хорошую службу автору — станут хорошо понятны основы.
Вообще, узнаю себя :)
Как только заинтересовался НС, сразу же кинулся их кодировать на C++. Сделал прямое распространение, потом обратное, градиентный спуск. Обучал, и они успешно обучались. Потом столкнулся с переобучением (до регуляризации и дропаута не дошел). По мере углубления в тему забросил свой велосипед, стал экспериментировать в матлабе, потом на питоне :)
С++ очень хороший язык, но что бы эффективно хотя бы перемножить матрицы, нужно, как минимум, знать алгоритмические методы оптимизации этой операции. Они есть, оказывается :) А еще нужно использовать векторные расширения процессоров, например, SSE. И оптимизация может быть разной на разных процессорах. А еще эта операция хорошо параллелится и можно задействовать несколько ядер процессора… А еще есть CUDA…
В общем, хорошие библиотеки считают НС на порядки (!) быстрее подобного кода.
Подобный код можно применить в продакшене только в одном случае — загрузить уже просчитанные другими средствами веса, и скорость не нужна. Но тогда весь код расчета НС поместится на одном-двух экранах, так как это будет простое прямое распространение, да свертки…
Но польза от велосипедов авторам есть! :)
Что-то я не понял…
Вы хотите сказать, что вот эта ваша сеть (та что в архиве) научилась распозновать набор MNIST за 3 секунды?
Сами то в это верите?

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

Sign up to leave a comment.

Articles