Как я в нейросети полез

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости

Новостная лента форума ailab.ru


Граф нейросети

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

Человеческий мозг — это очень большая нейросеть, в ней очень много синаптических связей. Биологические нейронные сети состоят из двух простых вещей: нейронов и синаптических связей, с помощью которых нейроны связаны друг с другом. Каждая такая связь имеет свою силу и влияние на конечный результат. Искусственные нейронные сети (далее ИНС) — это некоторая математическая модель, в виде набора некоторых чисел, структура и взаимодействие этих чисел была «скопирована» из биологических нейросетей, но по факту ей далеко до неё, в общем — всего лишь пародия. Нейроны в таких сетях группируются по слоям, между которыми есть связи. Самой простой архитектурой нейросети является перцептрон Розенблатта.

Схема элементарного перцептрона

Для начала разберем из чего состоит элементарный перцептрон Розенблатта — это три уровня элементов: S-, A-, R-элементы. S-элементы — это сенсорные элементы, на них подаются какие-либо данные. A-элементы — это ассоциативные элементы, они и играют ключевую роль в обобщении некоторых признаков входного слоя. И последние R-элементы, это реагирующие элементы, то есть можно сказать выходные элементы перцептрона.

В элементарном перцептроне, описанном Розенблаттом предполагается, что связи между S- и A-элементами могут быть -1, +1 и 0. 0 — означает отсутствие связи между элементами. Веса между A- и R- элементами могут быть любыми целыми числами. Здесь важно начать понимать, что такое перцептрон на уровне их создателей, потому что так-же есть перцептроны описанные другими людьми и может возникнуть путаница в терминологии, а в частности их названиях.

Так же стоит упомянуть, что такое активный и неактивный нейрон. Всё просто, если на выходе любого элемента мы получаем 1, то считается что элемент активен (возбуждён). Также у нейронов есть их функция активации, с помощью которой, по входным данным нейрон выдаёт что-либо на свой выход. В данном, конкретном случае это пороговая функция. Эта функция выдаёт +1, -1 или 0 в зависимости от поступивших в неё данных. Обычно этими данными является сумма выходов всех нейронов предыдущего уровня (далее слоя). Для S- и A-элементов пороговая функция может выдавать только 0 или 1. Если входной сигнал превышает некоторый порог ?, на выходе элемента получаем +1, в противном случае — 0. Для R-элементов пороговая функция выдаёт -1, +1 или 0, при этом считается, что 0 — это неопределенный результат, в идеале такого быть не должно — либо +1, либо -1. В общем случае, для R-элементов пороговую функцию можно записать следующим образом

здесь ? — это некоторый порог, который и определяет какой знак будет на выходе. Как можно догадаться x — это выход A-элемента, а w вес связи между R-элементом и A-элементом.

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

Самая главная фишка ИНС — это возможность их обучения. Для начала о типах обучения: их три вида, с учителем, без учителя, с подкреплением. Коротко о каждом:

С учителем — вид обучения, который мы и будем рассматривать. Его суть заключается в том, что мы кормим нейронке какие-либо данные и ответную реакцию на них, то что мы хотим от неё получить.

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

С подкреплением — этот метод довольно интересный и его можно использовать для обучения ИНС прохождения каких либо игр. Агент (нейросеть) находится в определенной среде и эта среда выдаёт ему какое либо количество очков за его успехи, поощряет его, либо наказывает за его неудачи, отбирая очки. Тем самым ИНС поймет, чаво от неё хотят и родит результат, который зачастую превосходит человеческие способности :)

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

  1. Случайным образом выбираем пороги для A-элементов и устанавливаем связи S—A (далее они изменяться не будут — это важно).
  2. Начальные коэффициенты Wi (связи между A- и R-элементами) полагаем равными нулю.
  3. Предъявляем обучающую выборку: объекты (например, круги либо квадраты) с указанием класса, к которым они принадлежат.
  4. Показываем перцептрону объект первого класса. При этом некоторые A-элементы возбудятся. Коэффициенты Wi, соответствующие этим возбуждённым элементам, увеличиваем на 1.
  5. Предъявляем объект второго класса и коэффициенты Wi тех A-элементов, которые возбудятся при этом показе, уменьшаем на 1.
  6. Теперь повторим шаги 4 и 5 для всех остальных элементов обучающей выборки, в результате повторения которых сформируются веса Wi.

Обратите внимание, что здесь мы не обучаем веса связей S-A, как мы их задали, они так и остаются. Розенблатт (и другие) доказали, что элементарный перцептрон обучающийся этим алгоритмом всегда будет иметь правильное решение за конечное число дискретных шагов (теорема сходимости перцептрона). По факту такого перцептрона и метода обучения хватит с головой для решения задачи распознавания рукописных цифр (есть замечательная работа: Э. Куссуль и др. «Перцептроны Розенблатта для распознавания рукописных цифр» (Англ.)). Но мы пойдем немного дальше и будем изучать другие типы перцептронов.

Существует два типа многослойных перцептронов, которые сформировались в ходе изучения темы ИНС разными людьми. Многослойный перцептрон Розенблатта и многослойный перцептрон Руммельхарта. По своей сути они имеют одинаковые недостатки (об этом лучше читать в оригинале) и способы достижения результата, но у перцептрона Руммельхарта есть своя особенность, и она заключается в том, что у последнего появилась возможность использования нелинейной функции активации и переход на область вещественных чисел. Плюс это или минус — я сказать не могу, но одно я скажу наверняка, что использование перцептрона Руммельхарта, для меня лично, показалось намного практичнее, с точки зрения решения задачи распознавания цифр. Основой такого мнения стало то, что я решился использовать изображения 32х32, на которых изображены машинописные цифры различными шрифтами, которые есть у меня на компьютере, что в принципе не предполагает распознавания рукописных цифр, но об этом потом. Также этот перцептрон обучается методом обратного распространения ошибки, это тоже можно сказать большой шаг назад, потому что этот метод приводит к использованию градиентного спуска, что не всегда даёт сходимость (результат может быть не найден никогда для конкретных параметров ИНС). Но выбор мной данного перцептрона обусловлен тем, что у него способность к обобщению намного больше, чем у обычного перцептрона Розенблатта. В любом случае ТЕОРЕТИЧЕСКИ одного слоя достаточно для нахождения линейных решений для практически любых нелинейных входных параметров (работу Э. Куссуля по распознаванию цифр я советовал выше). Но из любопытства и дальнейшему (возможному) переходу на свёрточные нейросети я выбрал именно этот тип.

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

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

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

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

Сигмоид

Функция активации — это способ нормализации входных данных (мы уже говорили об этом ранее, но для перцептрона Розенблатта). То есть, если на входе у вас будет большое число, пропустив его через функцию активации, вы получите выход в нужном вам диапазоне (больше функций активации ищите тут). Важно понимать, что для разных задач, требуются разные функции активации и в некоторых архитектурах ИНС используются сразу несколько функций активаций для разных слоёв, например в свёрточных. Что-ж… Осталось рассказать только про нахождение ошибки и описать алгоритм работы метода обратного распространения этой самой ошибки, ну и соотвественно рассказать как происходит прохождение сигнала вперед ещё раз для закрепления.

Ошибка — это процентная величина, отражающая расхождение между ожидаемым и полученным ответами. Есть три формулы для вычисления ошибки: MSE, Root MSE, Arctan. Здесь нет какого-либо ограничения на использование, как в функции активации, и вы вольны выбрать любой метод, который будет приносить вам наилучший результат. Стоит лишь учитывать, что каждый метод считает ошибки по разному. У Arctan, ошибка, почти всегда, будет больше, так как он работает по принципу: чем больше разница, тем больше ошибка. У Root MSE будет наименьшая ошибка, поэтому, чаще всего, используют MSE, которая сохраняет баланс в вычислении ошибки. MSE (Mean Squared Error — средняя квадратичная ошибка) я и буду использовать.

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

  1. Установка случайных весовых коэффициентов синапсам.
  2. Прохождение тренировочных данных сквозь сеть по алгоритму, описанному выше (прямое прохождение).
  3. Считается ошибка выходного слоя по формуле

4. Далее подсчитывается значение $#120575; по формуле для выходного нейрона

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

для скрытого и входного нейронов $#120575; находится по формуле

5. Находится градиент для каждого исходящего синапса по формуле

6. После того как всё посчитано нужно обновить вес связи

Все обозначения:

  1. $#119864;$#119903;$#119903;$#119900;$#119903;$#119900;$#119906;$#119905;$#119901;$#119906;$#119905; – ошибка выходного нейрона
  2. $#119874;$#119880;$#119879;$#119894;$#119889;$#119890;$#119886;$#119897; – ожидаемый выход нейрона
  3. $#119874;$#119880;$#119879;$#119886;$#119888;$#119905;$#119906;$#119886;$#119897; – выход нейрона нейросети
  4. $#120575;$#119900; – дельта выходного нейрона
  5. $#119891;?($#119868;$#119873;) – производная функции активации от входного значения
  6. $#119874;$#119880;$#119879; – выход нейрона нейросети
  7. $#120575;? - дельта нейрона скрытого слоя
  8. $#119908;$#119894; – вес связи между входным и выходным нейроном
  9. $#120575;$#119894; – дельта предыдущего нейрона с предыдущего слоя
  10. $#119866;$#119877;$#119860;$#119863;ab – градиент синапса
  11. $#120575;$#119861; – дельта нейрона на конце синапса
  12. $#119874;$#119880;$#119879;$#119860; – выход нейрона в начале синапса
  13. ?$#119908;$#119894; – изменение веса связи
  14. $#119908;$#119894;-1 — изменение веса связи на предыдущей итерации
  15. $#119864; (эпсилон) – скорость обучения
  16. ? (альфа) — момент

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

Момент я, кстати, не использовал. Совсем. Хотя стоило бы. Это важная штука, которую лучше не игнорить.

Что такое гиперпараметры? Гиперпараметры — это значения, которые нужно подбирать вручную и зачастую методом проб и ошибок. Среди таких значений можно выделить:

  • Момент и скорость обучения
  • Количество скрытых слоев
  • Количество нейронов в каждом слое
  • Наличие нейронов смещения

Про нейроны смещения как нибудь потом, я их конечно использовал, но они никак не повлияли на результат из-за большого числа нейронов (только увеличили время обучения…)

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

Кстати, здесь есть ошибка, входные данные должны быть нормализованы в диапазон [0; 1] (пункт 2 алгоритма). Можно и -1 туда подавать, но мы используем сигмоиду, поэтому надо, чтобы входные данные были в диапазоне функции активации.

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

Так как операции с наборами весов, выходов, входов и прочим можно реализовывать в матричном виде, то самым первым шагом стала разработка класса Matrix. Я бы конечно мог использовать какие-нибудь готовые классы матриц, но мне больше нравится разрабатывать всё самому. Основным, частоиспользуемым функционалом стала перегрузка операторов умножения.

Затем был разработан класс слоёв, который хранит наборы таких матриц.

После разработки предыдущих двух классов стала возможна разработка основного класса нейросети. Я покажу код только нескольких методов, остальное можно будет найти на гитхабе в репозитории (тык).

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

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

И так же десериализуем эти данные из файла обратно и загружаем в веса

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

Собсна сам скрипт генерит по папкам цифорки, рисует их на 32х32 белом холсте по середине, вот и всё. Собсна выглядит это примерно следующим образом:

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

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

Распаковка и формирование датасета:

Реализация ранее написанных функций на примере обучающей формы:

Выбор датасета для обучения
Выбор весов

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

Собсна я тестировал различные конфигурации, к сожалению графиков обучения у меня не осталось, но вот список того, что я тестировал:

Исходя из результатов представленных выше можно сказать о том, как сильно вырастает время обучения и прохождения сигнала в зависимости от количества слоёв и количества нейронов на них. Так же вероятно во втором тестирование за такое время обучения произошло переобучение ИНС, т.к. график обучения под конец был практически плоским (так же вероятно, попадание в локальный минимум). Метод обратного распространения ошибки не очень быстрый, но подходит для не бинарных входных данных. Самой удачной обученной сетью стала самая 1 попытка, т.к. она довольно неплохо распознает рукописные цифры, хоть в выборке таких вообще не присутствовало, выборка состояла только из машинописных цифр. Также неплохо справляется с задачей нейросеть с 300 нейронами на скрытом слое.

P.S. Уже после всех тестирований было обнаружено неправильный подсчёт СКО, но это уже не играет роли, искомый результат я получил.

Соотвественно код отвечающий за обучение представлен ниже:

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

Цифра от руки с грязью

Честно, для меня стало удивлением, что нейросеть смогла сделать такие обобщения, что рукописные цифры тоже принимает за чистую монету, хотя из похожих к рукописным шрифтам лишь Comic Sans Ms у меня был на компьютере.

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

Список полезных материалов:

  1. Перцептрон
  2. Функция активации
  3. Человек рассказывает о путанице в терминологии
  4. Книга Розенблатта
  5. ИНС
  6. Этот проект на Github
  7. Kussul E., Baidyk T., Kasatkina L., Lukovich V., Перцептроны Розенблатта для распознавания рукописных цифр, 2001. (Англ.)

Источник: m.vk.com

Комментарии: