C# — Нейросеть на C#


Содержание

Изучаем нейронные сети за четыре шага

В этот раз я решил изучить нейронные сети. Базовые навыки в этом вопросе я смог получить за лето и осень 2015 года. Под базовыми навыками я имею в виду, что могу сам создать простую нейронную сеть с нуля. Примеры можете найти в моих репозиториях на GitHub. В этой статье я дам несколько разъяснений и поделюсь ресурсами, которые могут пригодиться вам для изучения.

Шаг 1. Нейроны и метод прямого распространения

Так что же такое «нейронная сеть»? Давайте подождём с этим и сперва разберёмся с одним нейроном.

Нейрон похож на функцию: он принимает на вход несколько значений и возвращает одно.

Круг ниже обозначает искусственный нейрон. Он получает 5 и возвращает 1. Ввод — это сумма трёх соединённых с нейроном синапсов (три стрелки слева).

В левой части картинки мы видим 2 входных значения (зелёного цвета) и смещение (выделено коричневым цветом).

22–24 ноября, Москва, от 0 до 25 000 ₽

Входные данные могут быть численными представлениями двух разных свойств. Например, при создании спам-фильтра они могли бы означать наличие более чем одного слова, написанного ЗАГЛАВНЫМИ БУКВАМИ, и наличие слова «виагра».

Входные значения умножаются на свои так называемые «веса», 7 и 3 (выделено синим).

Теперь мы складываем полученные значения со смещением и получаем число, в нашем случае 5 (выделено красным). Это — ввод нашего искусственного нейрона.

Потом нейрон производит какое-то вычисление и выдает выходное значение. Мы получили 1, т.к. округлённое значение сигмоиды в точке 5 равно 1 (более подробно об этой функции поговорим позже).

Если бы это был спам-фильтр, факт вывода 1 означал бы то, что текст был помечен нейроном как спам.

Иллюстрация нейронной сети с Википедии.

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

Я очень рекомендую посмотреть серию видео от Welch Labs для улучшения понимания процесса.

Шаг 2. Сигмоида

После того, как вы посмотрели уроки от Welch Labs, хорошей идеей было бы ознакомиться с четвертой неделей курса по машинному обучению от Coursera, посвящённой нейронным сетям — она поможет разобраться в принципах их работы. Курс сильно углубляется в математику и основан на Octave, а я предпочитаю Python. Из-за этого я пропустил упражнения и почерпнул все необходимые знания из видео.

Сигмоида просто-напросто отображает ваше значение (по горизонтальной оси) на отрезок от 0 до 1.

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

Но на одних видео далеко не уедешь. Для полного понимания я решил закодить её самостоятельно. Поэтому я начал писать реализацию алгоритма логистической регрессии (который использует сигмоиду).

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

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

Шаг 3. Метод обратного распространения ошибки

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

Вкратце: вы оцениваете, насколько сеть ошиблась, и изменяете вес входных значений (синие числа на первой картинке).

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

Скриншот из руководства Мэтта Мазура по методу обратного распространения ошибки.

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

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

Шаг 4. Создание своей нейронной сети

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

Ещё одной полезной статьёй оказалась A Neural Network in 11 lines of Python от IAmTrask. В ней содержится удивительное количество знаний, сжатых до 11 строк кода.

Скриншот руководства от IAmTrask

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

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

После этого можно ознакомиться с руководством Wild ML от Denny Britz, в котором разбираются нейронные сети посложнее.

Скриншот из руководства WildML

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

Для поиска хороших наборов данных можете посетить мой сайт Datasets.co и выбрать там подходящий.

Так или иначе, теперь вам лучше начать свои эксперименты, чем слушать мои советы. Лично я сейчас изучаю Python-библиотеки для программирования нейронных сетей, такие как Theano, Lasagne и nolearn.

Тесты — Регрессия нейронной сети

Цель задачи регрессии — предсказать значение числовой переменной (обычно называемой зависимой) на основе значений одной или более переменной-предикторов (независимых переменных), которые могут быть либо числовыми, либо категориальными. Например, возможно, вы хотите спрогнозировать годовой доход некоей персоны, исходя из ее возраста, пола (мужчина или женщина) и уровня образования.

Самая простая форма регрессии называется линейной (linear regression, LR). LR-уравнение прогнозирования может выглядеть так: доход = 17.53 + (5.11 * возраст) + (–2.02 * мужчина) + (–1.32 * женщина) + (6.09 * образование). Хотя LR полезна для некоторых задач, во многих ситуациях она не эффективна. Но существуют другие распространенные типы регрессии: полиномиальная (polynomial regression), регрессия общей линейной модели (general linear model regression) и регрессия нейронной сети (neural network regression, NNR). Пожалуй, последний тип регрессии является самой мощной формой.

Наиболее распространенный тип нейронной сети (neural network, NN) — тот, что предсказывает значение категориальной переменной. Скажем, вы хотите спрогнозировать политические предпочтения некоей персоны (консерватор, умеренный, либерал) на основе таких факторов, как возраст, доход и пол. NN-классификатор имеет n выходных узлов, где n — количество значений, которое может принимать зависимая переменная. Значения n выходных узлов суммируются до 1.0, и их можно вольно трактовать как вероятности. Поэтому для прогноза политических предпочтений NN-классификатор должен был бы иметь три выходных узла. Если бы значения выходных узлов были (0.24, 0.61, 0.15), NN-классификатор предсказал бы «умеренный», поскольку у среднего узла оказалась самая большая вероятность.

В NN-регрессии NN имеет единственный выходной узел, который хранит спрогнозированное значение зависимой числовой переменной. А значит, для примера, где прогнозируется годовой доход, было бы три входных узла (один для возраста [age], один для пола [sex], где male = –1, а female = +1, и один для количества лет образования) и один выходной узел (годовой доход).

Хороший способ получить представление о том, что такое NN-регрессия, и понять, куда я клоню в этой статье, — взглянуть на демонстрационную программу на рис. 1. Вместо решения реалистичной задачи, чтобы сделать идеи NN-регрессии предельно ясными, целью этой демонстрации является создание NN-модели, которая может прогнозировать значение синусоидальной функции (sine function). На случай, если ваши познания в тригонометрии слегка проржавели, график такой функции показан на рис. 2. Синусоидальная функция принимает единственное вещественное входное значение от отрицательной бесконечности до положительной и возвращает значение между –1.0 и +1.0. Эта функция возвращает 0 при x = 0.0, x = pi (

3.14), x = 2 * pi, x= 3 * pi и т. д. Синусоидальная функция на удивление трудна в моделировании.

Рис. 1. Демонстрация регрессии нейронной сети

Рис. 2. Функция Sin(x)

Graph of sin(x) on [0.0, 6.5] График sin(x) при [0.0, 6.5]
sin(x) sin(x)

Демонстрация начинается с программной генерации 80 элементов данных, которые будут использоваться для обучения NN-модели. Эти 80 обучающих элементов имеют случайное входной значение x между 0 и 6.4 (чуть больше 2 * pi) и соответствующее значение y, равное sin(x).

В демонстрации создается нейронная сеть по формуле «1-12-1», т. е. NN с одним входным узлом (для x), 12 скрытыми узлами обработки (которые фактически определяют уравнение для прогноза) и одним выходным узлом (предсказанный синус x). При работе с нейронными сетями всегда приходится экспериментировать, так что количество скрытых узлов определялось методом проб и ошибок.

NN-классификаторы имеют две функции активации — одну для скрытых узлов, а другую для выходных. Функцией активации выходного узла для классификатора почти всегда является softmax, поскольку она дает значения, сумма которых составляет 1.0. Функция активации скрытого узла для классификатора — это обычно либо функция логистического сигмоида (logistic sigmoid function), либо функция гиперболической тангенсоиды (hyperbolic tangent function) (сокращенно tanh). Но в NN-регрессии есть функция активации скрытого узла, а функции активации выходного узла нет. Демонстрационная нейронная сеть использует функцию tanh для активации скрытых узлов.

Вывод NN определяется ее входными значениями и набором констант, называемых весовыми значениями (weights) и смещениями (biases). Так как смещения на самом деле являются просто особой разновидностью весов, иногда веса и смещения называют одним термином «весовые значения». Нейронная сеть с i входных узлов, j скрытых и k выходных имеет всего (i * j) + j + (j * k) + k весовых значений и смещений. Поэтому демонстрационная NN с формулой «1-12-1» имеет (1 * 12) + 12 + (12 * 1) + 1 = 37 весовых значений и смещений.

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

Существует несколько алгоритмов, которые можно использовать для обучения NN. До сих пор самый популярный подход — применение алгоритма обратного распространения ошибок (back-propagation algorithm). Обратное распространение — это итеративный процесс, в котором значения весов и смещений медленно изменяются, чтобы NN вычисляла более точные выходных значения.

В обратном распространении используются два обязательных параметра (максимальное количество итераций и скорость обучения) и один необязательный параметр (величина импульса [momentum rate]). Параметр maxEpochs задает предел на число итераций алгоритма, параметр learnRate управляет тем, как сильно могут изменяться весовые значения и смещения на каждой итерации. Параметр импульса (momentum parameter) ускоряет обучение, а также помогает предотвратить застревание алгоритма обратного распространения на плохом решении. В демонстрации значение maxEpochs устанавливается равным 10 000, значение learnRate — 0.005, а значение импульса — 0.001. Эти значения определяются методом проб и ошибок.

Существует три вариации алгоритма обратного распространения для обучения нейронной сети. В пакетном обратном распространении (batch back propagation) сначала анализируются все обучающие элементы, а затем подстраиваются все весовые значения и смещения. В стохастическом обратном распространении (также называемом онлайновым обратным распространением [online back propagation]) подстройка всех весовых значений и смещений осуществляется после анализа каждого обучающего элемента. В мини-пакетном обратном распространении подстройка всех весовых значений и смещений осуществляется после анализа указанной части обучающих элементов. Демонстрационная программа использует наиболее распространенный вариант — стохастическое обратное распространение.

Демонстрационная программа отображает меру ошибки через каждую 1000 эпох обучения (итераций). Заметьте, что значения ошибок немного скачут вокруг некоей величины. По окончании обучения программа выводит значения 37 весов и смещений, которые определяют NN-модель. Весовые значения и смещения нейронной сети не имеют очевидной интерпретации, но важны в анализе значений для проверки на плохие результаты, например, когда одно весовое значение чрезвычайно велико, а все остальные близки к нулю.

NN-классификаторы имеют две функции активации — одну для скрытых узлов, а другую для выходных.

Демонстрационная программа завершается оценкой модели нейронной сети. Все предсказанные нейронной сетью значения sin(x) при x = pi, pi / 2 и 3 * pi / 2 укладываются в разброс 0.02 от правильных значений. Предсказанное значение sin(6 * pi) очень сильно отличается от правильного. Но это ожидаемый результат, потому что NN обучалась только для предсказания значений sin(x) при x между 0 и 2 * pi.

В этой статье предполагается, что вы умеете программировать хотя бы на среднем уровне, но ничего не знаете о регрессии нейронной сети. Демонстрационная программа написана на C#, но у вас не должно возникнуть особых проблем, если вы захотите выполнить рефакторинг кода под другой язык вроде Visual Basic или Perl. Демонстрационная программа слишком длинная, чтобы ее можно было представить в статье целиком, но вы можете найти полный исходный код в сопутствующем этой статье пакете кода. Я убрал стандартную обработку ошибок, чтобы по возможности не затуманивать основные идеи.

Общая структура демонстрационной программы

Чтобы создать демонстрационную программу, я запустил Visual Studio, выбрал шаблон C# Console Application из File | New | Project и назвал проект NeuralRegression. Я использовал Visual Studio 2015, но в этой программе нет значимых зависимостей от .NET Framework, поэтому подойдет любая версия Visual Studio.

После загрузки кода шаблона в окно редактора я переименовал в окне Solution Explorer файл Program.cs в более описательный NeuralRegressionProgram.cs, и Visual Studio автоматически переименовала класс Program за меня. В начале кода я удалил все лишние выражения using, оставив только ссылку на пространство имен верхнего уровня System.


Общая структура программы с некоторыми правками для экономии места представлена на рис. 3. Вся управляющая логика программы находится в методе Main. Вся функциональность регрессии нейронной сети содержится в определенном мной классе NeuralNetwork.

Рис. 3. Структура демонстрационной программы

В методе Main обучающие данные создаются этими выражениями:

При работе с нейронными сетями, как правило, чем больше у вас обучающих данных, тем лучше. Для моделирования синусоидальной функции при значениях x между 0 и 2 * pi мне понадобилось минимум 80 элементов, чтобы получить хорошие результаты. Выбор начального (зародышевого) значения 1 для объекта случайных чисел был произвольным. Обучающие данные хранятся в матрице в стиле массив массивов. В реальных сценариях вы, вероятно, считывали бы обучающие данные из текстового файла.

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

Нейронная сеть создается следующими выражениями:

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

NN нужен объект Random для инициализации весовых значений и перетасовки порядка, в котором обрабатываются обучающие данные. Конструктор NeuralNetwork принимает начальное значение для внутреннего объекта Random. Я использовал произвольное значение, равное 0.

Нейронная сеть обучается этими выражениями:

Нейронная сеть крайне чувствительна к значениям обучающего параметра. Даже очень малое изменение может дать кардинально другой результат.

Демонстрационная программа оценивает качество полученной модели NN, прогнозируя результат sin(x) при трех стандартных значениях. Для этого применяются следующие выражения (показаны с некоторыми незначительными правками):

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

Демонстрация завершается предсказанием sin(x) при значении x, которое сильно выходит за диапазон обучающих данных:

В большинстве сценариев с NN-классификатором вы вызываете метод, который вычисляет точность классификации, т. е. количество правильных прогнозов, деленное на общее число прогнозов. Это возможно потому, что категориальное выходное значение либо правильно, либо нет. Но при работе с NN-регрессией стандартного способа определения точности нет. Если вы хотите вычислить какую-то меру точности, она будет зависеть от конкретной задачи. Например, для предсказания значения sin(x) вы могли бы произвольно определить правильный прогноз как тот, который укладывается в разброс 0.01 от правильного значения.

Вычисление выходных значений

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

Этот метод принимает массив, хранящий значения независимых переменных-предикторов. Локальные переменные hSums и oSums являются временными массивами (scratch arrays), которые хранят предварительные значения (до активации) скрытых и выходных узлов.

Далее значения независимой переменной копируются во входные узлы нейронной сети:

Затем предварительные значения скрытых узлов вычисляются умножением каждого входного значения на соответствующее весовое значение «входной-скрытый» и суммируются:

Потом добавляются смещения для скрытых узлов:

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

Предварительные значения выходных узлов вычисляются умножением значения каждого скрытого узла на соответствующее весовое значение «скрытый-выходной» и суммируются:

После этого добавляются смещения для скрытых узлов:

Вплоть до этого момента вычисление значений выходного узла для регрессионной сети совершенно идентично таковому для классифицирующей сети. Но в классификаторе окончательные значения выходного узла вычислялись бы применением функции активации softmax к каждой накопленной сумме. В случае регрессии никакая функция активации не применяется. Поэтому метод ComputeOutputs завершается простым копированием значений из временного массива oSums непосредственно в выходные узлы:

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

При обучении NN-классификатора по алгоритму обратного распространения используются вычисленные производные (calculus derivatives) двух функций активации. Для скрытых узлов код выглядит следующим образом:

Значением локальной переменной derivative является производная функции tanh, которое берется из весьма сложной теории. В NN-классификации вычисления, включающие производную функции активации выходного узла, представляют собой:

Здесь значение локальной переменной derivative — это вычисленная производная функции softmax. Однако, поскольку в NN-регрессии функция активации для выходных узлов не применяется, код выглядит так:

Конечно, умножение на 1.0 не имеет никакого эффекта, поэтому вы могли бы просто отбросить член derivative. Другой способ интерпретации этого заключается в том, что в NN-регрессии функция активации выходного узла является функцией тождества f(x) = x. А производная функции тождества — это константа 1.0.

Заключение

Демонстрационный код и пояснения в этой статье должны быть достаточны, чтобы вы смогли приступить к работе, если хотите исследовать регрессию нейронной сети с одной или более числовыми переменными-предикторами. Если у вас есть категориальная переменная-предиктор, вам понадобится кодировать ее. В случае категориальной переменной-предиктора, принимающей одно из двух возможных значений, таких как пол (мужчина, женщина), вы закодировали бы одно значение как –1, а другое — как +1.

В случае категориальной переменной-предиктора, принимающей три и более возможных значений, вам пришлось бы использовать кодирование по формуле «1 из (N–1)». Например, если переменная-предиктор является цветом, который может принимать одно из четырех возможных значений (красный, синий, зеленый, желтый), то красный кодировался бы как (1, 0, 0), синий как (0, 1, 0), зеленый как (0, 0, 1), желтый как (–1, –1, –1).

Джеймс Маккафри (Dr. James McCaffrey) работает на Microsoft Research в Редмонде (штат Вашингтон). Принимал участие в создании нескольких продуктов Microsoft, в том числе Internet Explorer и Bing. С ним можно связаться по адресу jammc@microsoft.com.

Выражаю благодарность за рецензирование статьи экспертам Microsoft Газу Икбалу (Gaz Iqbal) и Умешу Мадану (Umesh Madan).

C# — Нейросеть на C#

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

Тема данной главы – простейший вид искусственных нейронных сетей – персептроны. Вы узнаете об их устройстве, научитесь их создавать и обучать.

История персептрона

Понятия искусственного нейрона и искусственной нейронной сети появились достаточно давно, еще в 1943 году. Эта была чуть ли не первая статья, в которой предпринимались попытки смоделировать работу мозга. Ее автором был Уоррен Мак-Каллок.

Эти идеи продолжил нейрофизиолог Фрэнк Розенблатт. Он предложил схему устройства, моделирующего процесс человеческого восприятия, и назвал его «персептроном» (от латинского perceptio – восприятие) . В 1960 году Розенблатт представил первый нейрокомпьютер – «Марк-1», который был способен распознавать некоторые буквы английского алфавита.

Таким образом персептрон является одной из первых моделей нейросетей, а «Марк-1» – первым в мире нейрокомпьютером.

Персептроны стали очень активно исследовать. На них возлагали большие надежды. Однако, как оказалось, они имели серьезные ограничения. Был такой ученый Минский, который был сокурсником Розенблатта. Видимо, ему не очень понравилось, как все вокруг боготворили персептроны, и он написал целую книгу (1971 год), в которой провел детальнейший их анализ, попутно показав, что они не так уж много и умеют, да и вообще сильно ограничены.

Розенблатт не успел написать ответ Минскому, так как погиб в свой 43 день рождения при крушении лодки.

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

Теперь подробно разберем, что из себя представляет персептрон.

Персептрон

В основе персептрона лежит математическая модель восприятия информации мозгом. Разные исследователи по-разному его определяют. В самом общем своем виде (как его описывал Розенблатт) он представляет систему из элементов трех разных типов: сенсоров, ассоциативных элементов и реагирующих элементов.

Рассмотрим принцип работы персептрона.

Первыми в работу включаются S-элементы. Они могут находиться либо в состоянии покоя (сигнал равен 0), либо в состоянии возбуждения (сигнал равен 1).

Далее сигналы от S-элементов передаются A-элементам по так называемым S-A связям. Эти связи могут иметь веса, равные только -1, 0 или 1.

Затем сигналы от сенсорных элементов, прошедших по S-A связям попадают в A-элементы, которые еще называют ассоциативными элементами. Стоит заметить, что одному A-элементу может соответствовать несколько S-элементов. Если сигналы, поступившие на A-элемент, в совокупности превышают некоторый его порог ​ \( \theta \) ​, то этот A-элемент возбуждается и выдает сигнал, равный 1. В противном случае (сигнал от S-элементов не превысил порога A-элемента), генерируется нулевой сигнал.

Почему A-элементы назвали ассоциативными? Дело в том, что A-элементы являются агрегаторами сигналов от сенсорных элементов. Например, у нас есть группа сенсоров, каждый из которых распознает кусок буквы «Д» на исследуемой картинке. Однако только их совокупность (то есть когда несколько сенсоров выдали сигнал, равный 1) может возбудить A-элемент целиком. На другие буквы А-элемент не реагирует, только на букву «Д». То есть он ассоциируется с буквой «Д». Отсюда и такое название.

Можно привести и другой пример. На самом деле ваши глаза состоят из невероятного количества S-элементов (сенсоров), улавливающих падающий свет (около 140 000 000). И у вас какой-то A-элемент, который распознает конкретную часть лица. И вот вы увидели на улице человека. Некоторые A-элементы, которые распознали конкретные части лица, возбуждаются.

Далее сигналы, которые произвели возбужденные A-элементы, направляются к сумматору (R-элемент), действие которого вам уже известно. Однако, чтобы добраться до R-элемента, они проходят по A-R связям, у которых тоже есть веса. Однако здесь они уже могут принимать любые значения (в отличие от S-A связей).

Финальный аккорд. R-элемент складывает друг с другом взвешенные сигналы от A-элементов и, если превышен определенный порог, генерирует выходной сигнал, равный 1. Это означает, что в общем потоке информации от глаз мы распознали лицо человека.

Если порог не превышен, то выход персептрона равен -1. То есть мы не выделили лицо из общего потока информации.

Так как R-элемент определяет выход персептрона в целом, его назвали реагирующим.

Теперь вы знаете, что такое персептрон и как он работает. Сформулируем теперь его точное определение:

Выше я рассказал о персептроне в самом общем виде. Есть несколько подвидов персептронов, на некоторых из которых мы и сконцентрируем все внимание.

Классификация персептронов

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

Персептрон с одним скрытым слоем

Я уже приводил общее определение персептрона выше. Теперь рассмотрим его подвид – персептрон с одним скрытым слоем (или элементарный персептрон). Именно этот тип персептронов часто подразумевают, когда говорят о персептронах вообще.

Почему слой именно скрытый? Потому что слой А-элементов расположен между слоями S-элементов и R-элементов.

Обе картинки выше изображают именно персептрон с одним скрытым слоем.

Однослойный персептрон


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

Его ключевая особенность состоит в том, что каждый S-элемент однозначно соответствует одному A-элементу, все S-A связи имеют вес, равный +1, а порог A элементов равен 1.

Объясню подробнее. Возьмем картинку персептрона в общем смысле и преобразуем ее в картинку однослойного персептрона.

Изначально персептрон в общем смысле выглядит так:

Исходя из ключевой особенности однослойного персептрона сенсор может быть однозначно связан только с одним ассоциативным элементом. Посмотрим на белый сенсор на картинке (левый верхний угол). Он передает сигнал салатовому (первому) и серому (четвертому) ассоциативным элементам. Непорядок. Сенсор может передавать сигнал только одному А-элементу. Убираем лишнюю связь. Ту же операцию проводим и с другими сенсорами.

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

Далее, S-A связи всегда имеют вес, равный единице, а порог А-элементов всегда равен +1. С другой стороны нам известно, что сенсоры могут подавать сигнал равный только 0 или 1.

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

Ничего на напоминает? Правильно, не считая сенсорных элементов и S-A связей, мы только что описали схему работы искусственного нейрона. И это неслучайно. Однослойный персептрон действительно представляет собой искусственный нейрон с небольшим отличием. В отличие от искусственного нейрона, у однослойного персептрона входные сигналы могут принимать фиксированные значения: 0 или 1. У искусственного нейрона на вход можно подавать любые значения.

В персептроне R-элементы суммируют взвешенные входные сигналы и, если взвешенная сумма выше некоторого порога, выдают 1. Иначе выходы R-элементов были бы равны -1.

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

Цукерберг рекомендует:  Gamedev - Команда для разработки

Таким образом становится ясно, что часть однослойного персептрона (выделена черным прямоугольником на картинке выше) можно представить в виде искусственного нейрона, но ни в коем случае не путайте два этих понятия. Во-первых, никто не отменял S-элементы, которых в искусственном нейроне просто нет. Во-вторых, в однослойном персептроне S-элементы и A-элементы могут принимать только фиксированные значения 0 и 1, тогда как в искусственном нейроне таких ограничений нет.

Часть однослойного персептрона соответствует модели искусственного нейрона.

Однослойный персептрон может быть и элементарным персептроном, у которого только по одному слою S,A,R-элементов.

Многослойный персептрон

Под многослойным персептроном понимают два разных вида: многослойный персептрон по Розенблатту и многослойный персептрон по Румельхарту.

Многослойный персептрон по Розенблатту содержит более 1 слоя А-элементов.

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

  1. S-A связи могут иметь произвольные веса и обучаться наравне с A-R связями.
  2. Обучение производится по специальному алгоритму, который называется обучением по методу обратного распространения ошибки.

Этот метод является краеугольным камнем обучения всех многослойных ИНС. Во многом благодаря ему возобновился интерес к нейронным сетям. Но обсуждать мы его будем в других главах.

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

Опорная схема

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

Мы разобрались с видами персептронов. Далее в этой главе рассмотрим только однослойный персептрон с одним скрытым слоем.

Говоря про персептрон я буду иметь ввиду именно его.

Какие задачи решает персептрон?

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

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

«Очень хорошо» – понятие растяжимое. Насколько хорошо? Розенблатт доказал несколько теорем, суть которых я попытаюсь донести максимально понятным образом.

Объясняю на пальцах. Под полем сенсоров понимается множество всех S-элементов. Под классификацией – придуманные нами классы (те же кошки и собаки). Под «непустым множеством элементарных персептронов, проводящих успешную классификацию» понимается, что найдется хотя бы один перспетрон, справившийся с классификацией объектов.

Рассмотрим на примере.

Я хочу, чтобы персептрон научился различать кошек и собак. Это задача довольно трудная, но мы ее существенно упростим.

У нас будет 3 сенсора: длина лап, окрас и форма морды. Так как S-элементы могут принимать значения 0 или 1, то условимся, что значения 1 будут соответствовать коротким лапам, смешанному окрасу и округлая морда соответственно. Значения 0 будут означать признак собаки на данном S-элементе (длинные лапы, однотонный окрас и вытянутая морда). Вот мы и получили сенсорное поле. Если хотите, его можно представить в виде множества возможных значений 0 и 1 у каждого S-элемента. Например, абсолютная кошка должна вызвать срабатывание всех S-элементов ​ \( \ <1,1,1\>\) ​.

Идеальной же собаке соответствует следующий набор выходов S-элементов: ​ \( \ <0,0,0\>\) ​.

Сами по себе сенсоры не играют роли. Но добавив к набору выходов сенсоров смысл: кошка или собака, мы тем самым задали некоторую классификацию. Математически это означает, что мы задали некоторую функцию, которая принимает набор выходов S-элементов, а ее значением является 0 или 1 (кошка или собака).

Из приведенной выше теоремы следует, что множество персептронов, правильно проводящих нашу классификацию не является пустым. То есть такие персептроны есть!

Но ведь можно выбрать любой набор S-элементов и любую классификацию. И множество «решений» все равно не будет пустым!

Это означает, что теоретически персептроны способны решать любую задачу на классификацию.

Важное замечание!
1. Речь идет об элементарных персептронах.
2. Объекты классификации должны обладать свойством линейной разделимости (подробнее о ней ниже).

Но есть и вторая теорема, доказанная Розенблаттом:

Под произвольным исходным состоянием тут понимается персептрон с произвольными S-A и A-R весами связей. Под решением в теореме понимается персептрон с определенными весами, успешно решающий нашу задачу на классификацию.

Эта теорема не оставляет задачам на классификацию никаких шансов. Теперь нам известно, что мы всегда сможем решить нашу задачу за конечный промежуток времени. Единственный нюанс заключается в том, что никто не говорит о длительности «конечного промежутка времени». Секунда, минута, час, год, 1 000 лет?

Обе теоремы имеют доказательства, но здесь мы их рассматривать не будем.

Метод коррекции ошибок – один из алгоритмов изменения весов. В этой главе мы его также разберем.

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

Линейная разделимость

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

В этом случае потребуется всего два S-элемента, определяющие размер и прирученность. Так как размер животного и степень его прирученности могут быть разными и иметь промежуточные значения, то давайте немного отойдем от принятого определения S-элемента. Представим, что он может выдавать не только 0 (маленький размер, совсем дикий) или 1 (большой, полностью ручной), но и другие значения.

Так как у нас есть два сенсорных элемента, то их значения можно расположить вдоль двух координатных осей. На получившейся координатной плоскости можно размещать точки, каждая из которых характеризует какой-то вид кошки или собаки. Как вы знаете, у каждой точки есть координаты. В нашем случае их две: размер и прирученность. Так вот, задачей персептрона в данном случае (основанного на двух S-элементах) – провести некоторую прямую, которая максимально точно разделит два множества точек (кошек и собак). На рисунке ниже видно 4 этапа обучения сети на все более большой обучающей выборке.

Естественно, что у нас может быть больше признаков, а значит, и больше сенсорных элементов. В случае трех признаков будет три S-элемента, то есть имеем уже трехмерное пространство. В таком случае, между точками, каждая из которых соответствует определенным значениям всех трех S-элементов, проводилась бы плоскость. И так далее. В общем случае для ​ \( n \) ​ S-элементов в ​ \( n \) ​-мерном пространстве строится так называемая гиперплоскость с размерностью ​ \( n-1 \) ​.

Вы заметили, что в картинках выше в качестве разделителя используется прямая? А ведь мы могли бы в качестве разделителя использовать и любую другую кривую. Но прямая – проще. Именно поэтому в этой главе я рассматриваю задачи на классификацию линейно (от слова линия) разделимых объектов. Именно такие задачи способны решать элементарные персептроны.

Конечно, бывают и такие множества объектов, которые невозможно разделить линией (плоскостью/гиперплоскостью). В общем случае, такие множества называют линейно неразделимыми и в этой главе мы не будем их рассматривать. Теоремы о сходимости элементарных пересептронов на них не распространяются.

Задача на классификацию

Можно ли классифицировать логические функции? Да, и к тому же эта задача отлично проиллюстрирует такую классификацию.

На случай, если вы не знакомы с логическими функциями, ознакомьтесь с математической справкой.

Что такое логические функции? Это функции от какого-то числа переменных. Причем как сами переменные, так и значения логических функций могут принимать только фиксированные (дискретные) значения: 0 или 1.

Начнем с логического «И». Вы отправили Сашу в магазин за продуктами. Ему надо купить хлеб и квас. Если он ничего не купил, вы не пускаете его домой. Если он купил только хлеб или только квас, вы не пускаете его домой. Другими словами, Саша может войти в дом только когда он купил хлеб И квас. Также работает и логическое «И». У нас есть две бинарные переменные (то есть они могут быть равны только 0 или 1). Значением функции логического «И» будет 1 только тогда, когда значения обеих переменных тоже равны 1. Во всех остальных случаях значение этой логической функции равно 0.

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

Вот, например, таблица истинности для логического И.

X1 X2 Значение функции
1
1
1 1 1

А есть еще логическое «ИЛИ». Снова посылаем Сашу в магазин за продуктами. Ему надо купить хлеб и квас. Если он ничего не купил, вы не пускаете его домой. Если он купил только хлеб или только квас, или оба продукта – вы пускаете его домой. Также работает и логическое «ИЛИ». Значением функции логического «ИЛИ» будет 0 только тогда, когда значения обеих переменных тоже равны 0. Во всех остальных случаях значение этой логической функции равно 1.

Таблица истинности для логического ИЛИ выглядит следующим образом.

X1 X2 Значение функции
1 1
1 1
1 1 1

Логические функции очень красиво иллюстрируют идею классификации. Любая такая функция принимает на вход два аргумента. По счастливой случайности точки на плоскости задаются двумя числами (x и y)! Но логические функции могут принимать только дискретные аргументы (0 или 1). В итоге получаем, что для изображения любой логической функции на плоскости достаточно 4 точки (с координатами ​ \( (0,0) \) ​ ​ \( (1,0) \) ​ ​ \( (0,1) \) ​ ​ \( (1,1) \) ​). Вот так это выглядит:

Рассмотрим логическую функцию И. Она равна нулю для любого набора входных аргументов, кроме набора ​ \( (1,1) \) ​.


X1 X2 Логическое И
1
1
1 1 1

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

В случае с логическим И эту прямую, например, можно провести так, как показано на рисунке ниже. Все точки, находящиеся под этой прямой, приводят к 0 значению этой функции. Единственная точка над этой прямой приводит к значению логического И, равному 1.

Похожим образом ведет себя логическое ИЛИ, имеющее следующую таблицу истинности:

X1 X2 Логическое ИЛИ
1 1
1 1
1 1 1

Для такой функции графическое представление будет выглядеть так:

Нетрудно заметить, что данная картинка представляет собой графическое представление логического И, но наоборот (тоже одна точка, но для которой значение функции равно 0 и уже под прямой).

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

Теперь мы переходим к краеугольному камню нейронных сетей — их обучению. Ведь без этого свойства они не имеют никакого смысла.

Обучение персептронов

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

Упрощаем до предела

Начнем обучение наших нейронных сетей с самого простого случая. Для этого мы сильно упростим и без того простой однослойный персептрон с одним скрытым слоем:
1. Будем считать, что его A-R связи могут принимать только целые значения (…, -2, -1, 0, 1, 2, …).
2. Более того, у каждого А-элемента может быть только один S-элемент.
3. И у нас будет только 1 R-элемент.

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

Изначально мы имеем следующий персептрон.

Мы должны упростить его. Теперь A-элементы могут быть соединены только с одним S-элементом. Убираем все лишние связи.

На картинке выше 3 R-элемента. Оставляем только один.

S-A веса и пороги A элементов у нас теперь равны +1. Отмечаем это на рисунке.

В итоге получаем следующую картину.

Однако получается, что у нас слой A-элементов не выполняет никакие функции. Он эквивалентен S-слою. Поэтому мы проводим следующее упрощение. Выбрасываем слой сенсоров. Теперь роль сенсоров у нас будут выполнять ассоциативные элементы (или наоборот, без разницы).

Итак, мы только что ну очень упростили однослойный персептрон с одним скрытым слоем.

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

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

Практика: распознавание цифр

Картинка, как всем известно, состоит из пикселей. Так вот, очень удобно на каждый пиксель выделять один сенсор (для его распознавания).

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

  1. Будем распознавать только черно-белые цифры от 0 до 9.
  2. Цифры будут состоять из черных квадратиков в табличке 3×5 квадратов.
  3. Нейросеть будет распознавать только одну цифру.

Вот как выглядят наши цифры.

В нашей сети будет по 1 S-элементу (он же А-элемент) на каждый квадратик из таблички. Поэтому для распознавания цифры нам потребуется 15 сенсоров. Черный цвет квадрата соответствует возбуждению S-элемента (значение передаваемого сигнала равно 1). Белый цвет – выход соответствующего S-элемента равен 0.

Цифры в строковом формате

Чтобы работать с нейросетью, мы должны на ее входы подавать сигналы в виде чисел (0 или 1). Таким образом изображение цифры мы должны перевести в последовательность сигналов в виде чисел. Это легко сделать, если представить цифры в строковом формате.

Каждая цифра представляет собой всего пятнадцать квадратиков, причем только двух возможных цветов. Как и говорилось в предыдущем разделе, за белый квадратик отвечает 0, а черный квадратик – 1. Поэтому наши десять цифр от 0 до 9 в строковом формате будут выглядеть вот так:

Для записи каждой цифры у нас используется по 5 строк с 3 символами в каждой. Теперь уберем все переносы строк, чтобы получить для каждой цифры от 0 до 9 одну длинную строку длиной в 15 символов.

Цифры в таком строковом формате уже можно использовать для работы с нейросетью.

Еще раз поясню, почему мы не используем картинки, а перешли к строчкам символов. Взгляните на картинку ниже.

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

Такой простой пример не стоит таких больших усилий. Поэтому мы пропустим первые два этапа, указанные на картинке, и будем сразу подавать на вход нейросети цифры в строковом формате.

Постановка задачи

Мы хотим создать программу, которая из всех 10 цифр будет распознавать нужную нам цифру. Например, пусть это будет цифра 5 (можно и любую другую). Только и всего.

Нашей обучающей выборкой будут все цифры от 0 до 9. Когда нейросеть обучится безошибочно распознавать нужную нам цифру (5), тогда мы проверим ее «интеллект» уже на тестовой выборке. Она будет уже похитрее: на вход будут подаваться уже искаженные изображения пятерки.

Посмотрим, сможет ли обученная нейросеть с высокой точностью распознавать пятерку?

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

Алгоритм обучения

Наконец-то мы дошли до главного: как обучать сеть. В общих чертах процесс понятен. Мы будем случайным образом выбирать цифру и прогонять ее через сеть, модифицируя ее веса. Но как их модифицировать?

Мы знаем, что важность тем или иным входам (в нашем случае – S-элементам) придают веса, связывающие их с R-элементом. Таким образом, чем сильнее повлиял какой-то вес связи на результат, тем сильнее надо его изменить.

Следовательно, мы должны учесть следующие важные моменты:

  • Если наша нейросеть правильно распознала/отвергла цифру 5, то мы ничего не предпринимаем (все ведь замечательно!).
  • Если нейросеть ошиблась ираспозналаневерную цифру как 5, то мы должны ее наказать – мы уменьшаем веса тех связей, через которые прошел сигнал. Другими словами веса, связанные с возбудившимися входами, уменьшаются.
  • Если нейросеть ошиблась и не распознала цифру 5, то мы должны увеличить все веса, через которые прошел сигнал. Таким образом мы как бы говорим сети, что такие связи, а значит и связанные с ними входы – правильные.

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

  1. Подать на входы нейросети цифру в строковом формате.
  2. Если цифра распознана/отвергнута верно, то перейти к шагу 1.
  3. Если сеть ошиблась и распознала неверную цифру как 5, то вычесть из всех связей, связанных с возбудившимися S-элементами единицу.
  4. Если сеть ошиблась и отвергла цифру 5, то добавить единицу ко всем связям, связанным с возбудившимися S-элементами.

Почему в алгоритме мы прибавляем или отнимаем именно 1. На самом деле эту величину можно задать любой. Понятно, что эта величина влияет на эффективность обучения. Какой шаг обучения выбрать – это отдельный вопрос, который мы рассмотрим позднее.

Программа

Если вы не знакомы с языком программирования Python и/или не знаете, как приступить к работе, то рекомендую ознакомиться (можно бегло) с самым популярным самоучителем по Python.

Совершенно не обязательно писать нейросети на Python! Искусственные нейросети – математическая модель, и их можно запрограммировать с помощью любого языка. Так что если вам больше по душе Java, C, C#… то можете реализовывать сети на них. Никаких принципиальных различий нет. Алгоритм один и тот же. Реализация на разных языках разная.

Для начала импортируем модуль для работы со случайными числами.

Теперь давайте запишем все цифры от 0 до 9. Просто записывайте цифру в 5 строк по 3 символа, а затем удаляйте переносы строк (выше мы это уже рассматривали).

Функция list(*) позволяет нам создать список (массив), состоящий из отдельных символов, на которые разбивается длинная строка.

Далее, для простоты добавим все эти 10 цифр в список (для быстрого доступа к ним).

Теперь запишем 6 видов искаженной пятерки в строковом формате.

Теперь нам необходимо создать список весов. Помните теоремы об обучении персептронов? Там сказано, что сеть из любого состояния может обучиться правильно отвечать. Так что, чтобы не загадывать, пусть все веса вначале у нас будут равны 0. Так как у нас 15 входов и все они сразу соединены с одним R-элементом, то нам потребуется 15 связей.

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

Так как мы используем пороговую функцию активации, то нам необходимо установить какое-то число (порог). Если взвешенная сумма будет больше или равна ему, то сеть выдаст 1 (что означает, что она считает предоставленную цифру пятеркой). Порог можно выбрать любой. Пусть будет равен 7.

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


Результатом работы выражения return net >= bias этой функции может быть True (Правда/Да), что означает 1 или False (Ложь/Нет), что означает 0.

Теперь определим еще две вспомогательные функции.

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

Я использовал функцию int(number[i]) для преобразования символа ‘1’ в цифру 1. Если бы я этого не сделал, то возникла бы ошибка здесь if int(number[i]) == 1: , так как Python не умеет сравнивать символы (текст) с цифрами.

Вторая функция вызывается, если сеть не смогла распознать цифру пять (выход равен 0 при демонстрации 5) и увеличивает на единицу все веса, связанные с возбужденными входами.

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

Осталось только вывести результаты обучения. Выводить их мы будем следующим образом. Сначала выведем результирующие значения весов. Героев надо знать в лицо. После этого еще раз прогоним сеть по всем цифрам от 0 до 9, но уже без обучения. Просто чтобы удостоверится, что она обучилась. А в конце опробуем сеть на нашей тестовой выборке.

Если у вас отображаются какие-то ошибки или вы просто запутались в структуре, то вот полный код программы:

Готовы? Запускайте! Пробуйте! Должно сработать.

Мои результаты — сеть стала распознавать пятерку во все случаях только
с 3 попытки. Картинка кликабельна.

Не обязательно сеть научится распознавать пятерки из тестовой выборки с первой попытки. Если результат программы вас не устраивает (не вся тестовая выборка успешно прошла проверку), то просто запустите программу снова. Все будет сброшено и начнется с чистого листа.

Но почему сеть не обучается с первого раза?

При любом запуске программы 10 тысяч обучающих цифр генерятся случайным образом. Таким образом при каждом запуске программы процесс обучения сети уникален.

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

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

  1. Добиться равномерности показа всех обучающих цифр.
  2. Увеличить общее количество шагов обучения (50 тысяч или 100 тысяч).

Давайте поближе рассмотрим созданного нами монстра!
Что еще за Скайнет?

Рассмотрим мой результат. Начнем с первой строчки (это веса сети):

[1, 1, 1, 3, 0, -8, 1, 2, 1, -8, 0, 1, 1, 1, 1]

Теперь расположим эти цифры в виде «цифры».

1 1 1
3 0 -8
1 2 1
-8 0 1
1 1 1

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

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

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

Получается, что сеть сама выделила хорошие/противные области, нужные ей для распознавания. Невероятно, да?

Но это еще не все. Дальше идет 9 строк обучающей выборки.

0 это 5? False
1 это 5? False
2 это 5? False
3 это 5? False
4 это 5? False
6 это 5? False
7 это 5? False
8 это 5? False
9 это 5? False

Все предложенные цифры наша нейросеть смело отвергает, клеймя их False.

Наконец, итоговый и самый желанный результат программы – тестовая выборка. Как вы видите, сеть прекрасно распознала и цифру 5, и шесть ее искаженных вариаций.

Узнал 5? True
Узнал 5 — 1? True
Узнал 5 — 2? True
Узнал 5 — 3? True
Узнал 5 — 4? True
Узнал 5 — 5? True
Узнал 5 — 6? True

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

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

1 Правило. Если сигнал персептрона неверен и равен 0, то необходимо увеличить веса тех входов, на которые была подана единица.

2 Правило. Если сигнал персептрона неверен и равен 1, то необходимо уменьшить веса тех входов, на которые была подана единица.

Правила Хебба часто встречается в литературе, как обучение с коррекцией ошибки.

Дельта-правило

Теперь попробуем обобщить правила Хебба на произвольные (не только 0/1) входы и связи (не только целые числа).

Пусть мы заранее знаем правильный выход нашей сети. Обозначим его за ​ \( d \) ​. Однако в процессе обучения сеть ошибается. Обозначим ответ сети за ​ \( y \) ​. Тогда мы можем получить ошибку/погрешность сети как разницу правильного и реального ответов:

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

\[ w_i(t+1)=w_i(t)+\delta x_i \eta \]

Разберем эту формулу. Сначала в общих чертах.

Для отображения количества шагов обучения сети я использую переменную ​ \( t \) ​. Очевидно, что наша цель – получить из старого значения веса связи ​ \( w_i(t) \) ​ новое значение ​ \( w_i(t+1) \) ​.

Для этого мы, как и в правилах Хебба, должны прибавить какое-то число к весу связи. Как раз эта добавка вычисляется по формуле ​ \( \delta x_i \eta \) ​. Разберем эту формулу.

Переменная ​ \( \delta \) ​ есть ошибка нейросети. Заметим сходство с правилами Хебба. Если нейросеть ответила правильно, то ожидаемый и реальный результаты равны и ​ \( \delta = 0 \) ​, а значит и вся добавка к весу связи равна ​ \( 0 \) ​. Вес не изменился.

В случае, если ​ \( \delta > 0 \) ​, а значит ​ \( d > y \) ​, то значение добавки к весу будет положительное. Вес связи увеличится (1 правило Хебба). Это соответствует случаю, когда сеть получила на вход 5, но не узнала ее.

В случае, если ​ \( \delta ​, а значит ​ \( d ​, то значение добавки к весу будет отрицательное. Вес связи уменьшится (2 правило Хебба). Это соответствует случаю, когда сеть неверно посчитала данное число за 5.

С ​ \( \delta \) ​ разобрались. Теперь перейдем к ​ \( x_i \) ​. Это значение, которое пришло на ​ \( i \) ​-ый вход сети. Опять же, чем более сильный сигнал поступил на вход, тем сильнее изменится вес, с этим входом связанный. Это логично. Ведь если на вход вообще не поступило сигнала (​ \( x_i = 0 \) ​), то и соответствующий вес не должен изменится (добавка будет равна нулю).

А теперь самое интересное: Скорость обучения ​ \( \eta \) ​.

Скорость обучения

Если с первыми двумя членами формулы добавки к весу ​ \( \delta x_i \eta \) ​ все просто и понятно, то постоянный коэффициент ​ \( \eta \) ​, называемый коэффициентом скорости обучения, вызывает вопросы. Зачем он нужен?

Результат сети зависит от ее весов. Но раз зависит результат сети, то от весов зависит и ее ошибка (как разность постоянного «правильного» значения и результата сети). Представим, что погрешность нашей сети ​ \( \delta \) ​ зависит от какого-то веса сети ​ \( w_i \) ​ следующим образом.

Положение кенгуру означает конкретный вес связи. Так в чем же фишка? А фишка в том, что кенгуру надо попасть в низину, так как именно в ней ошибка сети будет минимальна. Однако наш кенгуру может только прыгать. И именно за «длину прыжка» и отвечает коэффициент ​ \( \eta \) ​ в формуле добавки к весу.

Пусть коэффициент ​ \( \eta \) ​ маленький. Тогда наш кенгуру будет маленькими прыжками продвигаться к низине. Это будет очень доооооолго…

Ок, сделаем коэффициент ​ \( \eta \) ​ большим. Ведь большими прыжками мы быстрее доскачем до низины? Нет. При большом значении скорости обучения есть опасность так и не доскакать до самого низа из-за того, что вы постоянно будете прыгать вправо-влево оставаясь на одной и той же высоте.

Поэтому подбирать коэффициент скорости обучения надо с умом. Иногда его представляют в виде некоторой функции, которая постепенно уменьшается. То есть ​ \( \eta \) ​ уменьшается (а значит уменьшается и длина прыжка) по мере приближения к низине.

Цукерберг рекомендует:  Html - Как сверстать шапку

Вот мы и разобрались в предназначении коэффициента скорости обучения. Мы можем изменять вес связи только скачками. Этот коэффициент определяет величину скачков.

Практика: линейная аппроксимация

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

Пусть у нас имеется какой-то набор точек на плоскости.

X Y
1 2
2 4.2
2.5 5
3.8 7.9
4 9
6 10.2
6.6 13
7.2 15.3
8 17.1
8.5 19.5

Теперь давайте представим эти данные в виде картинки:

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

Например, у нас нет данных о Y координате точки с X = 3. Задача аппроксимации – с наибольшей вероятностью предсказать, какой будет Y для этой точки.

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

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


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

Коэффициент ​ \( k \) ​ отвечает за крутизну наклона прямой, а ​ \( c \) ​ указывает точку на оси Y, через которую проходит эта прямая.

Почему ​ \( k \) ​ отвечает за крутизну наклона?

Крутизну наклона прямой мы определяем по величине угла ​ \( \phi \) ​ между прямой и осью X. Пусть для простоты наша прямая проходит через начало координат. Возьмем любую точку на этой прямой. Ее координаты равны ​ \( (x_0, y_0) \) ​. Теперь возьмем произвольную точку на прямой. Ее координаты нам неизвестны ​ \( (x,y) \) ​. Наша цель – получить зависимость ​ \( y \) ​ от ​ \( x \) ​.

Можно заметить, что мы имеем два подобных прямоугольных треугольника. Они подобны по двум углам (по углу ​ \( \phi \) ​ и по прямому углу из точки на прямой к оси X).

В подобных треугольниках отношение сходственных сторон равно между собой. Следовательно можно составить следующую пропорцию:

Видим, что отношение ​ \( \frac \) ​ равно тангенсу угла ​ \( \phi \) ​, как отношение противолежащего катета (​ \( y_0 \) ​) к прилежащему (​ \( x_0 \) ​). А чем больше угол ​ \( \phi \) ​, тем больше и его тангенс. Обозначим тангенс за ​ \( k \) ​.

А значит, коэффициент при ​ \( x \) ​ отвечает за крутизну наклона прямой к оси X.

Почему ​ \( c \) ​ отвечает за точку пересечения оси Y и прямой?

Пусть ​ \( x=0 \) ​. В этом случае прямая пересекает ось Y. При ​ \( x=0 \) ​ имеем ​ \( y=0 \) ​. Подставляем ​ \( x=0 \) ​ в общее уравнение прямой:

Получаем, что ​ \( y=c \) ​. Это означает, что прямая пересекает ось Y «на высоте» ​ \( с \) ​.

Мы ищем уравнение прямой, аппроксимирующее наши данные. Значит ​ \( y \) ​ у нас будет выходом сети. Теперь определимся со входами. Совершенно точно, что одним входом будет являться переменная ​ \( x \) ​. Однако в уравнении прямой фигурирует еще одно слагаемое: ​ \( c \) ​. О нем тоже нельзя забывать. ​ \( c \) ​ – постоянная величина. Поэтому мы добавим в нашу сеть второй вход, на который всегда будет подаваться единица. Таким образом произведение этого входа на вес всегда будет равно только этому весу, вне зависимости от входа (он всегда равен 1).

Функции активации у нас не будет. Взвешенная сумма и будет являться выходом нашей сети.

Вот так графически выглядит наш персептрон:

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

Это была запись в общем виде. А для нашего случая имеем:

Ничего эта запись вам не напоминает? Да это же уравнение прямой линии, где ​ \( out=y, \ w_2=k, \ w_1=c \) ​! Мы построили персептрон так, что в процессе обучения его весовые коэффициенты станут коэффициентами прямой, которую мы ищем!

Программа

Для начала вновь импортируем модуль для работы со случайными числами.

Далее мы создаем переменную, являющуюся весом связи при входе x (а по совместительству и коэффициентом крутизны наклона прямой).

Функция uniform(from, to) генерирует случайное вещественное число от from и до to включительно. Заметьте, что мы уже не работаем с целыми числами.

Далее создаем переменную, являющуюся весом связи при всегда единичном входе (а по совместительству отвечающую за свободный член в уравнении прямой).

Его значение опять же выбирается случайным образом.

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

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

Теперь задаем данные о наборе точек.

Здесь я использую не список, а словарь. Это практически тоже самое, что и список, только используются не числовые индексы, а собственноручно заданные имена – ключи. В данном словаре ключи это X, а соответствующие им значения это Y.

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

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

Теперь нужно создать функцию, высчитывающую ответ нашего персептрона:

Как видите, никакой функции активации тут нет. Ответ нашей сети есть взвешенная сумма, где ​ \( k \) ​ – вес связи при x, а ​ \( c \) ​ – вес связи при входе, всегда равном единице.

Теперь надо обучить нашу сеть. Чем больше шагов заложим, тем лучше. Возьмем, например, 100 000 шагов обучения. Хотя, возможно, для получения результата подошло бы и меньшее количество шагов. Тут есть простор для экспериментирования.

На протяжении 100 000 раз мы выбираем из нашего словаря случайный ключ (так как ключ у меня это и есть значение X).

После этого создается переменная true_result , в которой хранится правильный Y для соответствующего X.

Далее, в переменную out мы помещаем ответ нашей сети для данного X.

После этого мы высчитываем ошибку, и, используя дельта-правило, меняем оба веса связи.

Осталось только вывести данные о новой прямой.

Привожу полный код программы:

Можно запускать! Корректные результаты должны получиться уже с первого раза. Как понять, что ваши результаты корректные? Значение ​ \( k \) ​ у вас должно быть в районе 2, а значение ​ \( c \) ​ — чем ближе к 0, тем лучше.

Вот, что получилось у меня.

а самом деле я намеренно задал уравнение вида ​ \( y=2x \) ​ и, как видно по результатам, сеть практически полностью повторила его.

Цифры это конечно хорошо, но на графиках выглядит еще лучше.

Здесь синие точки – это набор исходных данных. Коричневая прямая – это случайно полученная в начале программы прямая. Она может проходить как угодно и где угодно.

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

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

И ваша, и моя прямые, в свою очередь, практически идеально совпадают с первоначальной прямой (​ \( y=2x \) ​), из которой я намеренным искажением получил точки набора.

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

Выводы

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

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

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

Затем вы ознакомились с правилами Хебба и создали первую искусственную нейросеть, которая не только умеет отличать цифру 5 от всех остальных, но еще и умудряется распознавать ее в искаженных картинках. Это был пример использования персептрона как классификатора.

Последние разделы главы посвящены дельта-правилу, как обобщению правил Хебба, и созданию персептрона, реализующего линейную аппроксимацию.

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

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

Другие языки программирования

Здесь приведены исходные коды программ для различных языков программирования. (Код для C# и PHP предоставлен пользователем под ником Geograph, код для Java написал poslannik).

Истинная реализация нейросети с нуля на языке программирования C#

Здравствуй! Данная статья предназначена для тех, кто приблизительно шарит в математических принципах работы нейронных сетей и в их сути вообще, поэтому советую ознакомиться с этим перед прочтением. Хоть как-то понять, что происходит можно сначала здесь, потом тут.

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

На самом деле, несмотря на обилие математики, она не такая уж и запредельно сложная. Понять сатанистские каракули и письмена этого пособия сможет среднестатистический 11-классник товарищ-физмат или 1

2-курсник технарьской шараги. Помимо этого, пусть книга достаточно объёмная и трудная для восприятия, но вещи, написанные в ней, реально объясняют, что «твориться у тачки под капотом». Как вы поняли я крайне рекомендую(ни в коем случае не рекламирую) «Нейронные сети. Полный курс» Саймона Хайкина к прочтению в том случае, если вам придётся столкнуться с применением/написанием/разработкой нейросетей и прочего подобного stuff’а. Хотя в ней нет материала про новомодные свёрточные сети, никто не мешает загуглить лекции от какого-нибудь харизматичного работника Yandex/Mail.ru/etc. никто не мешает.

Конечно, осознав устройство сеток, я не мог просто остановиться, так как впереди предстояло написание кода. В связи со своим параллельным занятием, заключающемся в создани игр на Unity, языком реализации оказался ламповый и няшный шарпей 7 версии(ибо она последняя актуальная). Именно в этот момент, оказавшись на просторах интернета, я понял, что число внятных туториалов по написанию нейросетей с нуля(без ваших фреймворков) на шарпе бесконечно мало. Ладно. Я мог использовать, всякие Theano и Tensor Flow, НО под капотом моей смерть-машины в моём ноутбуке стоит «красная» видеокарта без особой поддержки API, через которые обращаются к мощи GPU(ведь именно их и используют Theano/Tensor Flow/etc.).

Моя видеокарта называется ATI Radeon HD Mobility 4570. И если кто знает, как обратиться к её мощностям для параллелизации нейросетевых вычислений, пожалуйста, напишите в комментарии. Тогда вы поможете мне, и возможно у этой статьи появится продолжение. Не осуждается предложение других ЯП.

Просто, как я понял, она настолько старая, что нифига не поддерживает. Может быть я не прав.

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


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

Прежде чем начать программировать это, можно нужно нарисовать на бумаге, дабы облегчить представление структуры и работы нейронки. Моё воображение вылилось в следующую картинку. И да, кстати, это консольное приложение в Visual Studio 2020, с .NET Framework версии 4.7.

Многослойный полносвязный персептрон.
Один скрытый слой.
4 нейрона в скрытом слое(на этом количестве персептрон сошёлся).
Алгоритм обучения — backpropagation.
Критерий останова — преодоление порогового значения среднеквадратичной ошибки по эпохе.(0.001)
Скорость обучения — 0.1.
Функция активации — логистическая сигмоидальная.

Потом надо осознать, что нам нужно куда-то записывать веса, проводить вычисления, немного дебажить, ну и кортежи поюзать(но для них юзинг мне не нужен). Соответственно, using’и у нас такие.

В папке release||debug этого прожекта располагаются файлы(на каждый слой по одному) по имени типа (fieldname)_memory.xml сами знаете для чего. Они создаются заранее с учётом общего количества весов каждого слоя. Знаю, что XML — это не лучший выбор для парсинга, просто времени было немного на это дело.

Также вычислительные нейроны у нас двух типов: скрытые и выходные. А веса могут считываться или записываться в память. Реализуем сию концепцию двумя перечислениями.

Всё остальное будет происходить внутри пространства имён, которое я назову просто: Neural Network.

Прежде всего, важно понимать, почему нейроны входного слоя я изобразил квадратами. Ответ прост. Они ничего не вычисляют, а лишь улавливают информацию из внешнего мира, то есть получают сигнал, который будет пропущен через сеть. Вследствие этого, входной слой имеет мало общего с остальными слоями. Вот почему стоит вопрос: делать для него отдельный класс или нет? На самом деле, при обработке изображений, видео, звука стоит его сделать, лишь для размещения логики по преобразованию и нормализации этих данных к виду, подаваемому на вход сети. Вот почему я всё-таки напишу класс InputLayer. В нём находиться обучающая выборка организованная необычной структурой. Первый массив в кортеже — это сигналы-комбинации 1 и 0, а второй массив — это пара результатов этих сигналов после проведения операций XOR и XAND(сначала XOR, потом XAND).

Теперь реализуем самое важное, то без чего ни одна нейронная сеть не станет терминатором, а именно — нейрон. Я не буду использовать смещения, потому что просто не хочу. Нейрон будет напоминать модель МакКаллока-Питтса, но иметь другую функцию активации(не пороговую), методы для вычисления градиентов и производных, свой тип и совмещенные линейные и нелинейные преобразователи. Естественно без конструктора уже не обойтись.

Ладно у нас есть нейроны, но их необходимо объединить в слои для вычислений. Возвращаясь к моей схеме выше, хочу объяснить наличие чёрного пунктира. Он разделяет слои так, чтобы показать, что они содержат. То есть один вычислительный слой содержит нейроны и веса для связи с нейронами предыдущего слоя. Нейроны объединяются массивом, а не списком, так как это менее ресурсоёмко. Веса организованы матрицей(двумерным массивом) размера(нетрудно догадаться) [число нейронов текущего слоя X число нейронов предыдущего слоя]. Естественно, слой инициализирует нейроны, иначе словим null reference. При этом эти слои очень похожи друг на друга, но имеют различия в логике, поэтому скрытые и выходной слои должны быть реализованы наследниками одного базового класса, который кстати оказывается абстрактным.

Класс Layer — это абстрактный класс, поэтому нельзя создавать его экземпляры. Это значит, что наше желание сохранить свойства «слоя» выполняется путём наследования родительского конструктора через ключевое слово base и пустой конструктор наследника в одну строчку(ибо вся логика конструктора определена в базовом классе, и её не надо переписывать).

Теперь непосредственно классы-наследники: Hidden и Output. Сразу два класса в цельном куске кода.

В принципе, всё самое важное я описал в комментариях. У нас есть все компоненты: обучающие и тестовые данные, вычислительные элементы, их «конгламераты». Теперь настало время всё связать обучением. Алгоритм обучения — backpropagation, следовательно критерий останова выбираю я, и выбор мой — есть преодоление порогового значения среднеквадратичной ошибки по эпохе, которое я выбрал равным 0.001. Для поставленной цели я написал класс Network, описывающий состояние сети, которое принимается в качестве параметра многих методов, как вы могли заметить.

Итого, путём насилования мозга несложных манипуляций, мы получили основу работающей нейронной сети. Для того, чтобы заставить её делать что-либо другое, достаточно поменять класс InputLayer и подобрать параметры сети для новой задачи. Через время(какое конкретно не знаю) напишу продолжение этой статьи с руководством по созданию с нуля свёрточной нейронной сети на C# и здесь сделаю апдейт этой с ссылками на MLP-рекогнитор картинок MNIST( но это не точно ) и код статьи на Python( точно, но дольше ждать ).

Тесты — Регрессия нейронной сети

Цель задачи регрессии — предсказать значение числовой переменной (обычно называемой зависимой) на основе значений одной или более переменной-предикторов (независимых переменных), которые могут быть либо числовыми, либо категориальными. Например, возможно, вы хотите спрогнозировать годовой доход некоей персоны, исходя из ее возраста, пола (мужчина или женщина) и уровня образования.

Самая простая форма регрессии называется линейной (linear regression, LR). LR-уравнение прогнозирования может выглядеть так: доход = 17.53 + (5.11 * возраст) + (–2.02 * мужчина) + (–1.32 * женщина) + (6.09 * образование). Хотя LR полезна для некоторых задач, во многих ситуациях она не эффективна. Но существуют другие распространенные типы регрессии: полиномиальная (polynomial regression), регрессия общей линейной модели (general linear model regression) и регрессия нейронной сети (neural network regression, NNR). Пожалуй, последний тип регрессии является самой мощной формой.

Наиболее распространенный тип нейронной сети (neural network, NN) — тот, что предсказывает значение категориальной переменной. Скажем, вы хотите спрогнозировать политические предпочтения некоей персоны (консерватор, умеренный, либерал) на основе таких факторов, как возраст, доход и пол. NN-классификатор имеет n выходных узлов, где n — количество значений, которое может принимать зависимая переменная. Значения n выходных узлов суммируются до 1.0, и их можно вольно трактовать как вероятности. Поэтому для прогноза политических предпочтений NN-классификатор должен был бы иметь три выходных узла. Если бы значения выходных узлов были (0.24, 0.61, 0.15), NN-классификатор предсказал бы «умеренный», поскольку у среднего узла оказалась самая большая вероятность.

В NN-регрессии NN имеет единственный выходной узел, который хранит спрогнозированное значение зависимой числовой переменной. А значит, для примера, где прогнозируется годовой доход, было бы три входных узла (один для возраста [age], один для пола [sex], где male = –1, а female = +1, и один для количества лет образования) и один выходной узел (годовой доход).

Хороший способ получить представление о том, что такое NN-регрессия, и понять, куда я клоню в этой статье, — взглянуть на демонстрационную программу на рис. 1. Вместо решения реалистичной задачи, чтобы сделать идеи NN-регрессии предельно ясными, целью этой демонстрации является создание NN-модели, которая может прогнозировать значение синусоидальной функции (sine function). На случай, если ваши познания в тригонометрии слегка проржавели, график такой функции показан на рис. 2. Синусоидальная функция принимает единственное вещественное входное значение от отрицательной бесконечности до положительной и возвращает значение между –1.0 и +1.0. Эта функция возвращает 0 при x = 0.0, x = pi (

3.14), x = 2 * pi, x= 3 * pi и т. д. Синусоидальная функция на удивление трудна в моделировании.

Рис. 1. Демонстрация регрессии нейронной сети

Рис. 2. Функция Sin(x)

Graph of sin(x) on [0.0, 6.5] График sin(x) при [0.0, 6.5]
sin(x) sin(x)

Демонстрация начинается с программной генерации 80 элементов данных, которые будут использоваться для обучения NN-модели. Эти 80 обучающих элементов имеют случайное входной значение x между 0 и 6.4 (чуть больше 2 * pi) и соответствующее значение y, равное sin(x).

В демонстрации создается нейронная сеть по формуле «1-12-1», т. е. NN с одним входным узлом (для x), 12 скрытыми узлами обработки (которые фактически определяют уравнение для прогноза) и одним выходным узлом (предсказанный синус x). При работе с нейронными сетями всегда приходится экспериментировать, так что количество скрытых узлов определялось методом проб и ошибок.

NN-классификаторы имеют две функции активации — одну для скрытых узлов, а другую для выходных. Функцией активации выходного узла для классификатора почти всегда является softmax, поскольку она дает значения, сумма которых составляет 1.0. Функция активации скрытого узла для классификатора — это обычно либо функция логистического сигмоида (logistic sigmoid function), либо функция гиперболической тангенсоиды (hyperbolic tangent function) (сокращенно tanh). Но в NN-регрессии есть функция активации скрытого узла, а функции активации выходного узла нет. Демонстрационная нейронная сеть использует функцию tanh для активации скрытых узлов.

Вывод NN определяется ее входными значениями и набором констант, называемых весовыми значениями (weights) и смещениями (biases). Так как смещения на самом деле являются просто особой разновидностью весов, иногда веса и смещения называют одним термином «весовые значения». Нейронная сеть с i входных узлов, j скрытых и k выходных имеет всего (i * j) + j + (j * k) + k весовых значений и смещений. Поэтому демонстрационная NN с формулой «1-12-1» имеет (1 * 12) + 12 + (12 * 1) + 1 = 37 весовых значений и смещений.

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

Существует несколько алгоритмов, которые можно использовать для обучения NN. До сих пор самый популярный подход — применение алгоритма обратного распространения ошибок (back-propagation algorithm). Обратное распространение — это итеративный процесс, в котором значения весов и смещений медленно изменяются, чтобы NN вычисляла более точные выходных значения.

В обратном распространении используются два обязательных параметра (максимальное количество итераций и скорость обучения) и один необязательный параметр (величина импульса [momentum rate]). Параметр maxEpochs задает предел на число итераций алгоритма, параметр learnRate управляет тем, как сильно могут изменяться весовые значения и смещения на каждой итерации. Параметр импульса (momentum parameter) ускоряет обучение, а также помогает предотвратить застревание алгоритма обратного распространения на плохом решении. В демонстрации значение maxEpochs устанавливается равным 10 000, значение learnRate — 0.005, а значение импульса — 0.001. Эти значения определяются методом проб и ошибок.

Существует три вариации алгоритма обратного распространения для обучения нейронной сети. В пакетном обратном распространении (batch back propagation) сначала анализируются все обучающие элементы, а затем подстраиваются все весовые значения и смещения. В стохастическом обратном распространении (также называемом онлайновым обратным распространением [online back propagation]) подстройка всех весовых значений и смещений осуществляется после анализа каждого обучающего элемента. В мини-пакетном обратном распространении подстройка всех весовых значений и смещений осуществляется после анализа указанной части обучающих элементов. Демонстрационная программа использует наиболее распространенный вариант — стохастическое обратное распространение.

Демонстрационная программа отображает меру ошибки через каждую 1000 эпох обучения (итераций). Заметьте, что значения ошибок немного скачут вокруг некоей величины. По окончании обучения программа выводит значения 37 весов и смещений, которые определяют NN-модель. Весовые значения и смещения нейронной сети не имеют очевидной интерпретации, но важны в анализе значений для проверки на плохие результаты, например, когда одно весовое значение чрезвычайно велико, а все остальные близки к нулю.

NN-классификаторы имеют две функции активации — одну для скрытых узлов, а другую для выходных.

Демонстрационная программа завершается оценкой модели нейронной сети. Все предсказанные нейронной сетью значения sin(x) при x = pi, pi / 2 и 3 * pi / 2 укладываются в разброс 0.02 от правильных значений. Предсказанное значение sin(6 * pi) очень сильно отличается от правильного. Но это ожидаемый результат, потому что NN обучалась только для предсказания значений sin(x) при x между 0 и 2 * pi.

В этой статье предполагается, что вы умеете программировать хотя бы на среднем уровне, но ничего не знаете о регрессии нейронной сети. Демонстрационная программа написана на C#, но у вас не должно возникнуть особых проблем, если вы захотите выполнить рефакторинг кода под другой язык вроде Visual Basic или Perl. Демонстрационная программа слишком длинная, чтобы ее можно было представить в статье целиком, но вы можете найти полный исходный код в сопутствующем этой статье пакете кода. Я убрал стандартную обработку ошибок, чтобы по возможности не затуманивать основные идеи.

Общая структура демонстрационной программы

Чтобы создать демонстрационную программу, я запустил Visual Studio, выбрал шаблон C# Console Application из File | New | Project и назвал проект NeuralRegression. Я использовал Visual Studio 2015, но в этой программе нет значимых зависимостей от .NET Framework, поэтому подойдет любая версия Visual Studio.

После загрузки кода шаблона в окно редактора я переименовал в окне Solution Explorer файл Program.cs в более описательный NeuralRegressionProgram.cs, и Visual Studio автоматически переименовала класс Program за меня. В начале кода я удалил все лишние выражения using, оставив только ссылку на пространство имен верхнего уровня System.

Общая структура программы с некоторыми правками для экономии места представлена на рис. 3. Вся управляющая логика программы находится в методе Main. Вся функциональность регрессии нейронной сети содержится в определенном мной классе NeuralNetwork.

Рис. 3. Структура демонстрационной программы

В методе Main обучающие данные создаются этими выражениями:

При работе с нейронными сетями, как правило, чем больше у вас обучающих данных, тем лучше. Для моделирования синусоидальной функции при значениях x между 0 и 2 * pi мне понадобилось минимум 80 элементов, чтобы получить хорошие результаты. Выбор начального (зародышевого) значения 1 для объекта случайных чисел был произвольным. Обучающие данные хранятся в матрице в стиле массив массивов. В реальных сценариях вы, вероятно, считывали бы обучающие данные из текстового файла.

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

Нейронная сеть создается следующими выражениями:

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

NN нужен объект Random для инициализации весовых значений и перетасовки порядка, в котором обрабатываются обучающие данные. Конструктор NeuralNetwork принимает начальное значение для внутреннего объекта Random. Я использовал произвольное значение, равное 0.

Нейронная сеть обучается этими выражениями:

Нейронная сеть крайне чувствительна к значениям обучающего параметра. Даже очень малое изменение может дать кардинально другой результат.

Демонстрационная программа оценивает качество полученной модели NN, прогнозируя результат sin(x) при трех стандартных значениях. Для этого применяются следующие выражения (показаны с некоторыми незначительными правками):

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

Демонстрация завершается предсказанием sin(x) при значении x, которое сильно выходит за диапазон обучающих данных:

В большинстве сценариев с NN-классификатором вы вызываете метод, который вычисляет точность классификации, т. е. количество правильных прогнозов, деленное на общее число прогнозов. Это возможно потому, что категориальное выходное значение либо правильно, либо нет. Но при работе с NN-регрессией стандартного способа определения точности нет. Если вы хотите вычислить какую-то меру точности, она будет зависеть от конкретной задачи. Например, для предсказания значения sin(x) вы могли бы произвольно определить правильный прогноз как тот, который укладывается в разброс 0.01 от правильного значения.

Вычисление выходных значений

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

Этот метод принимает массив, хранящий значения независимых переменных-предикторов. Локальные переменные hSums и oSums являются временными массивами (scratch arrays), которые хранят предварительные значения (до активации) скрытых и выходных узлов.

Далее значения независимой переменной копируются во входные узлы нейронной сети:

Затем предварительные значения скрытых узлов вычисляются умножением каждого входного значения на соответствующее весовое значение «входной-скрытый» и суммируются:

Потом добавляются смещения для скрытых узлов:

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

Предварительные значения выходных узлов вычисляются умножением значения каждого скрытого узла на соответствующее весовое значение «скрытый-выходной» и суммируются:

После этого добавляются смещения для скрытых узлов:

Вплоть до этого момента вычисление значений выходного узла для регрессионной сети совершенно идентично таковому для классифицирующей сети. Но в классификаторе окончательные значения выходного узла вычислялись бы применением функции активации softmax к каждой накопленной сумме. В случае регрессии никакая функция активации не применяется. Поэтому метод ComputeOutputs завершается простым копированием значений из временного массива oSums непосредственно в выходные узлы:

Цукерберг рекомендует:  Как правильно отдыхать программисту

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

При обучении NN-классификатора по алгоритму обратного распространения используются вычисленные производные (calculus derivatives) двух функций активации. Для скрытых узлов код выглядит следующим образом:


Значением локальной переменной derivative является производная функции tanh, которое берется из весьма сложной теории. В NN-классификации вычисления, включающие производную функции активации выходного узла, представляют собой:

Здесь значение локальной переменной derivative — это вычисленная производная функции softmax. Однако, поскольку в NN-регрессии функция активации для выходных узлов не применяется, код выглядит так:

Конечно, умножение на 1.0 не имеет никакого эффекта, поэтому вы могли бы просто отбросить член derivative. Другой способ интерпретации этого заключается в том, что в NN-регрессии функция активации выходного узла является функцией тождества f(x) = x. А производная функции тождества — это константа 1.0.

Заключение

Демонстрационный код и пояснения в этой статье должны быть достаточны, чтобы вы смогли приступить к работе, если хотите исследовать регрессию нейронной сети с одной или более числовыми переменными-предикторами. Если у вас есть категориальная переменная-предиктор, вам понадобится кодировать ее. В случае категориальной переменной-предиктора, принимающей одно из двух возможных значений, таких как пол (мужчина, женщина), вы закодировали бы одно значение как –1, а другое — как +1.

В случае категориальной переменной-предиктора, принимающей три и более возможных значений, вам пришлось бы использовать кодирование по формуле «1 из (N–1)». Например, если переменная-предиктор является цветом, который может принимать одно из четырех возможных значений (красный, синий, зеленый, желтый), то красный кодировался бы как (1, 0, 0), синий как (0, 1, 0), зеленый как (0, 0, 1), желтый как (–1, –1, –1).

Джеймс Маккафри (Dr. James McCaffrey) работает на Microsoft Research в Редмонде (штат Вашингтон). Принимал участие в создании нескольких продуктов Microsoft, в том числе Internet Explorer и Bing. С ним можно связаться по адресу jammc@microsoft.com.

Выражаю благодарность за рецензирование статьи экспертам Microsoft Газу Икбалу (Gaz Iqbal) и Умешу Мадану (Umesh Madan).

Истинная реализация нейросети с нуля на языке программирования C#

Здравствуй! Данная статья предназначена для тех, кто приблизительно шарит в математических принципах работы нейронных сетей и в их сути вообще, поэтому советую ознакомиться с этим перед прочтением. Хоть как-то понять, что происходит можно сначала здесь, потом тут.

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

На самом деле, несмотря на обилие математики, она не такая уж и запредельно сложная. Понять сатанистские каракули и письмена этого пособия сможет среднестатистический 11-классник товарищ-физмат или 1

2-курсник технарьской шараги. Помимо этого, пусть книга достаточно объёмная и трудная для восприятия, но вещи, написанные в ней, реально объясняют, что «твориться у тачки под капотом». Как вы поняли я крайне рекомендую(ни в коем случае не рекламирую) «Нейронные сети. Полный курс» Саймона Хайкина к прочтению в том случае, если вам придётся столкнуться с применением/написанием/разработкой нейросетей и прочего подобного stuff’а. Хотя в ней нет материала про новомодные свёрточные сети, никто не мешает загуглить лекции от какого-нибудь харизматичного работника Yandex/Mail.ru/etc. никто не мешает.

Конечно, осознав устройство сеток, я не мог просто остановиться, так как впереди предстояло написание кода. В связи со своим параллельным занятием, заключающемся в создани игр на Unity, языком реализации оказался ламповый и няшный шарпей 7 версии(ибо она последняя актуальная). Именно в этот момент, оказавшись на просторах интернета, я понял, что число внятных туториалов по написанию нейросетей с нуля(без ваших фреймворков) на шарпе бесконечно мало. Ладно. Я мог использовать, всякие Theano и Tensor Flow, НО под капотом моей смерть-машины в моём ноутбуке стоит «красная» видеокарта без особой поддержки API, через которые обращаются к мощи GPU(ведь именно их и используют Theano/Tensor Flow/etc.).

Моя видеокарта называется ATI Radeon HD Mobility 4570. И если кто знает, как обратиться к её мощностям для параллелизации нейросетевых вычислений, пожалуйста, напишите в комментарии. Тогда вы поможете мне, и возможно у этой статьи появится продолжение. Не осуждается предложение других ЯП.

Просто, как я понял, она настолько старая, что нифига не поддерживает. Может быть я не прав.

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

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

Прежде чем начать программировать это, можно нужно нарисовать на бумаге, дабы облегчить представление структуры и работы нейронки. Моё воображение вылилось в следующую картинку. И да, кстати, это консольное приложение в Visual Studio 2020, с .NET Framework версии 4.7.

Многослойный полносвязный персептрон.
Один скрытый слой.
4 нейрона в скрытом слое(на этом количестве персептрон сошёлся).
Алгоритм обучения — backpropagation.
Критерий останова — преодоление порогового значения среднеквадратичной ошибки по эпохе.(0.001)
Скорость обучения — 0.1.
Функция активации — логистическая сигмоидальная.

Потом надо осознать, что нам нужно куда-то записывать веса, проводить вычисления, немного дебажить, ну и кортежи поюзать(но для них юзинг мне не нужен). Соответственно, using’и у нас такие.

В папке release||debug этого прожекта располагаются файлы(на каждый слой по одному) по имени типа (fieldname)_memory.xml сами знаете для чего. Они создаются заранее с учётом общего количества весов каждого слоя. Знаю, что XML — это не лучший выбор для парсинга, просто времени было немного на это дело.

Также вычислительные нейроны у нас двух типов: скрытые и выходные. А веса могут считываться или записываться в память. Реализуем сию концепцию двумя перечислениями.

Всё остальное будет происходить внутри пространства имён, которое я назову просто: Neural Network.

Прежде всего, важно понимать, почему нейроны входного слоя я изобразил квадратами. Ответ прост. Они ничего не вычисляют, а лишь улавливают информацию из внешнего мира, то есть получают сигнал, который будет пропущен через сеть. Вследствие этого, входной слой имеет мало общего с остальными слоями. Вот почему стоит вопрос: делать для него отдельный класс или нет? На самом деле, при обработке изображений, видео, звука стоит его сделать, лишь для размещения логики по преобразованию и нормализации этих данных к виду, подаваемому на вход сети. Вот почему я всё-таки напишу класс InputLayer. В нём находиться обучающая выборка организованная необычной структурой. Первый массив в кортеже — это сигналы-комбинации 1 и 0, а второй массив — это пара результатов этих сигналов после проведения операций XOR и XAND(сначала XOR, потом XAND).

Теперь реализуем самое важное, то без чего ни одна нейронная сеть не станет терминатором, а именно — нейрон. Я не буду использовать смещения, потому что просто не хочу. Нейрон будет напоминать модель МакКаллока-Питтса, но иметь другую функцию активации(не пороговую), методы для вычисления градиентов и производных, свой тип и совмещенные линейные и нелинейные преобразователи. Естественно без конструктора уже не обойтись.

Ладно у нас есть нейроны, но их необходимо объединить в слои для вычислений. Возвращаясь к моей схеме выше, хочу объяснить наличие чёрного пунктира. Он разделяет слои так, чтобы показать, что они содержат. То есть один вычислительный слой содержит нейроны и веса для связи с нейронами предыдущего слоя. Нейроны объединяются массивом, а не списком, так как это менее ресурсоёмко. Веса организованы матрицей(двумерным массивом) размера(нетрудно догадаться) [число нейронов текущего слоя X число нейронов предыдущего слоя]. Естественно, слой инициализирует нейроны, иначе словим null reference. При этом эти слои очень похожи друг на друга, но имеют различия в логике, поэтому скрытые и выходной слои должны быть реализованы наследниками одного базового класса, который кстати оказывается абстрактным.

Класс Layer — это абстрактный класс, поэтому нельзя создавать его экземпляры. Это значит, что наше желание сохранить свойства «слоя» выполняется путём наследования родительского конструктора через ключевое слово base и пустой конструктор наследника в одну строчку(ибо вся логика конструктора определена в базовом классе, и её не надо переписывать).

Теперь непосредственно классы-наследники: Hidden и Output. Сразу два класса в цельном куске кода.

В принципе, всё самое важное я описал в комментариях. У нас есть все компоненты: обучающие и тестовые данные, вычислительные элементы, их «конгламераты». Теперь настало время всё связать обучением. Алгоритм обучения — backpropagation, следовательно критерий останова выбираю я, и выбор мой — есть преодоление порогового значения среднеквадратичной ошибки по эпохе, которое я выбрал равным 0.001. Для поставленной цели я написал класс Network, описывающий состояние сети, которое принимается в качестве параметра многих методов, как вы могли заметить.

Итого, путём насилования мозга несложных манипуляций, мы получили основу работающей нейронной сети. Для того, чтобы заставить её делать что-либо другое, достаточно поменять класс InputLayer и подобрать параметры сети для новой задачи. Через время(какое конкретно не знаю) напишу продолжение этой статьи с руководством по созданию с нуля свёрточной нейронной сети на C# и здесь сделаю апдейт этой с ссылками на MLP-рекогнитор картинок MNIST( но это не точно ) и код статьи на Python( точно, но дольше ждать ).

Hello, C# — Учим Шарп #2

Опубликовано shwan в 08.11.2020 08.11.2020

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

Перед началом данного урока рекомендую ознакомиться с предыдущим Преимущества и недостатки C# — Учим Шарп #1

Как работает компьютер

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

  • Записать значения в память
  • Прочитать значение из памяти
  • Выполнить операции сложения, умножения, отрицания

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

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

Что такое языки программирования

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

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

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

Инструмент создания приложений IDE

Таким образом, мы пришли к тому, что необходимы определенные инструменты, которые позволяют преобразовывать написанный текст на языке высокого уровня в машинные коды, которые будут понятны компьютеру. Такие приложения называются компиляторами. Но сейчас намного большей популярностью пользуются другие приложения, которые по сути являются основным инструментом современного программиста – это интегрированная среда разработки (Integrated Development Environment) IDE. По сути, это компилятор на стероидах. Это приложение, а иногда и набор инструментов, которые позволяют преобразовать исходный код, написанный программистом в готовое приложение, но содержащее в себе еще вагон и маленькую тележку дополнительных приблуд, которые делают жизнь программиста чуточку проще. Это и всевозможные статические анализаторы кода, которые находят ошибки еще до запуска приложения, а также указывают на так называемый код с душком. Это и системы тестирования, анализа и статистики по коду, инструменты для быстрого рефакторинга и выгрузки документации по коду. Короче, целая куча крутых фишек, которыми никто не пользуется.

Visual Studio

Так вот, основным инструментом C# программиста является интегрированная среда разработки Visual Studio 2020 на данный момент. Скачать ее можно по ссылке https://visualstudio.microsoft.com/ru/ Существует 3 основные версии

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

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

— Для всех прочих сценариев использования: в некорпоративных организациях Visual Studio Community может использовать до 5 пользователей. В корпоративных организациях (в которых используется > 250 ПК или годовой доход которых > 1 млн долларов США) использование запрещено.

Таким образом, пока ты зарабатываешь меньше 1 млн долларов в год, ив твоей команде меньше 5 человек, можешь разрабатывать любое ПО в том числе и коммерческое.

Сейчас установка, обновление и изменение компонентов Visual Studio стало достаточно приятных и простым занятием, хотя раньше приносило достаточно много головной боли. Просто скачивай специальное приложение-установщик на свой компьютер и запускай установку. После скачивания необходимых компонентов, откроется окно выбора того, что тебе нужно установить.

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

  • Разработка классических приложений .NET — обязательно
  • ASP.NET и разработка веб-приложений
  • Кроссплатформенная разработка .NET Core

Самые нужные расширения для Visual Studio

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

Для этого открываем меню Средства => Расширения и обновления

Выбрать В сети и в строке поиска ввести названия необходимых расширений

ReSharper – суперкрутой статический анализатор кода. Может практически все, но некоторым (в том числе мне) не нравится, потому что бесит, и является платным. Но в целом очень полезен.

Web Essentials – mast have для веб программистов. Позволяет весьма упростить и ускорить процесс работы в интерфейсной частью приложения, упрощает и ускоряет верстку, но в других случаях он не нужен.

Productivity Power Tools 2020/2020 – целый набор небольших расширения, позволяющих сделать процесс написания кода комфортнее, код чище и красивее. Настоятельно рекомендую устанавливать каждому.

GitHub Extension for Visual Studio – очень удобное взаимодействие с популярным сервисом хранения кода и системой контроля версий. Каждому программисту нужно научиться и постоянно использовать любую подходящую систему контроля версий. А это расширение позволит форкать проекты и выполнять коммиты в пару кликов.

Visual Studio Spell Checker — позволяет избавиться от таких досадных ошибок, как опечатки в коде. Работает примерно как в ворде — подчеркивает красным не правильные слова. При этом он понимает различные нотации и без проблем понимает, что написанные слитно слова, но с заглавными буквами это не ошибка.


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

Создадим Hello World проект на C#

Открываем среду разработки Visual Studio и заходим в меню Файл => Создать => Проект

После этого выбираем Visual C# => Классическое приложение для Windows => Консольное приложение (.NET Framework), вводим имя проекта, указываем его расположение на диске и нажмем ОК.

В открывшимся окне вводим следующий код приложения

После чего запускаем приложение, нажав сочетание клавишь Ctrl+F5 или зеленую клавишу Пуск и получаем следующий результат

Поздравляю, вы создали свое первое приложение!

Подробно, весь процесс установки и настройки Visual Studio, а также создание первого приложения рассмотрен на моем видеоуроке.

Нейросетевое программное обеспечение

В этом разделе находится информация по программному обеспечению по направлению искусственного интеллекта.

Программное обеспечение
К настоящему времени разработано много программных пакетов, реализующих нейронные сети. Вот некоторые, наиболее известные программы-симуляторы нейронных сетей, представленных на рынке программного обеспечения:

  • Alyuda NeuroIntelligence — Нейросетевое программное обеспечение и расширение для Excel, предназначенное для прогнозирования и анализа данных. Поддерживает несколько алгоритмов. Доступна trial-версия.
  • Amygdala — Программное обеспечение с открытым исходным кодом для моделирования нейронных сетей, написанное на C++.
  • Annie — Нейросетевая библиотека для C + + (Windows и Linux) с открытым исходным кодом. Поддерживает MLP, RBF и сети Хопфилда. Наличие интерфейсов для Matlab’s Neural Network Toolbox.
  • Artificial Intelligence Recurrent Asymmetric Networks (NARIA) — Открытый проект о симуляции человеко-подобного интеллекта с помощью нейронных сетей.
  • Cortex — Приложение, реализующее нейронные сети обратного распространения.
  • DELVE — Стандартная среда для оценки эффективности методов обучения. Включает в себя набор данных и архив методов обучения.
  • EasyNN — Нейросетевое программное обеспечение для Windows с числовыми, текстовыми и графичискими функциями.
  • ECANSE — Предоставляет среду разработки для проектирования, моделирования и тестирования нейронных сетей и их применения для производства оптимизированного программного решения.
  • FANN — Нейросетевая библиотека исполняемая в ANSI C. Создает многослойные сети прямого распространения с поддержкой как полносвязных и и сетей со структурными связями. Поддерживает выполнение в фиксированной точке для быстрой работы на системах, как IPAQ.
  • Fann Neural Network for Mathematica — Бесплатная интерактивная среда для Mathematica, включающая распознавание образов и предсказания временных рядов.
  • Genesis — Платформа для моделирования сложных нейронных систем.
  • Java library — Java-библиотека с открытыым кодом, реализующая сети прямого распространения такие как: многослойные персептроны, обобщенные и модульные сети прямого распространения.
  • Joone — (Java Object Oriented Neural Engine) — свободно распространяемый нейросетевой фреймворк на Java. Может быть расширен новыми модулями.
  • libF2N2 — Нейросетевая библиотека с открытым исходным кодом. Реализует нейронные сети прямого распространения на C++ и PHP.
  • Lightweight Neural Network++ — Свободно распространяемое программное обоеспечение. Реализует нейронные сети прямого распространения и неккоторые мтеоды обучения.
  • LTF-Cimulator — Симулятор LTF-C нейронных сетей для решения задач классификации.
  • Netlab — Библиотека MATLAB- функций для моделирования нейросетевых алгоритмов, основанных на книге «Neural Networks for Pattern Recognition» Chris Bishop.
  • NetMaker — Моделирует MLP, RMLP and каскадно-корреляционные сети с динамической подстройкой архитектуры. Включает различные учебные данные, ошибки и активационные функции.
  • Neurak — бесплатная среда для разработки и применения искусственных нейронных сетей.
  • Neural Network Framework — Фреймворк для создания нейронных сетей с произвольной топологией и смешанного типа нейронов. Включает в себя техническую информацию и почтовые рассылки.
  • Neural Network Leaves Recognition — Нейронная сеть, предназначенная для распознования. Написана на Java. Java-Applet также доступен.
  • Neural Network Models in Excel — Бесплатное программное обеспечение, реализующее нейронные сети для решения задач прогнозирования и классификации в Excel. Используется обратное распространение. Может обрабатывать пропущенные значения и категориальные данные.
  • Neural Network Toolbox for MATLAB — среда для исследований нейронных сетей, проектирования и моделирования их в MATLAB.
  • Neural Networks at your Fingertips — Нейросетевой симулятор, реализующий 8 различный архитектур со встроенным примером приложений запрограммированных на ANSI C.
  • NeuralWorks — Professional II/PLUS является средой для разработки нейронных сетей для Windows и Unix. Predict — нейросетевой инструмент для решения задач прогнозирования и классификации для Unix или как надстройка Excel для Windows.
  • NeuroBox -dotNET-библиотека, реализованная на C# для создания, распространения и обучения сложных нейронных сетей прямого распространения.
  • Neuromat — Программное обеспечение для разработки байесовских нейронных сетей.
  • NeuroMine — Нейрсетевые COM+ компонены и среда разработки для предсказания и анализа данных. Поддерживает несколько алгоритмов. Доступна trial-версия.
  • Neuropilot Project — Набор демо java-апплетов, реализующих обученные нейронные сети для пилотирования лунно-посадочного модуля на ландшафты различной сложности.
  • NeuroShell Predictor — Программное обеспечени для прогнозирования и оценки на основе нейронные сетей. Доступна демо-версия.
  • NeuroSolutions — Среда раработки нейронных сетей с графичиским интерфейсом. Поддерживает несколько типо сетей и алгоритмов обучения. Доступна trial-версия.
  • NeuroXL — MS Excel надстройки на основе нейронных сетей. Предназначены для предсказания, классификации и финансового прогнозирования.
  • NNSYS >

Тема: Сверточная нейросеть C#

Опции темы

Сверточная нейросеть C#

Чет не могу нормальный пример для нубцов найти, есть у кого?

бро, ну явно не твое, шел бы в мак и все

Тебе там чтоли одному скучно?

Учи плюсы, не создавай тупые темы.

Вот это понесло человека с модом.

А чем плюсы лучше?

Ну кроме того, что на них примеры я видел.

Последний раз редактировалось had; 04.01.2020 в 18:12 .

А чем плюсы лучше?

Ну кроме того, что на них примеры я видел.

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

мое лицо когда я слышу про нейросети

Я хочу написать свои классы с нуля, чтобы разобраться.
Но не смог нагуглить подходящие гайды, в лучше случае «посоны, я написал классы, ну короч вот качайте, по коментам разберетесь»

Готовые либы для шарпа тоже какие-то есть, мне их пока рано.

Последний раз редактировалось had; 04.01.2020 в 19:16 .

1) Из какого типа нейронов состоит сеть.
2) Какую структуру она имеет.
3) Какое назначение данной сети. Классификация. кластеризация, или что?

А чем плюсы лучше?

Ну кроме того, что на них примеры я видел.

Все очень просто. Нейронные сети это чистая математика. В основном весь математический аппарат написан с помощью функционального счисления. А вовсе не классов.

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

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

Последний раз редактировалось Левингук; 04.01.2020 в 19:23 .

Изучаем нейронные сети: как создать нейросеть за 4 шага?

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

Шаг 1. Поговорим о нейронах и методах прямого распространения

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

Для наилучшего понимания давайте посмотрим на картинку ниже. Круг — это искусственный нейрон. Он получает 5, а возвращает 1. Под вводом понимается сумма трёх синапсов, соединённых с нейроном (это три стрелки слева).

В левой части у нас находятся два входных значения (выделены зелёным цветом) и одно смещение (выделено коричневым цветом).

При этом входные данные могут быть численными представлениями 2-х различных свойств. К примеру, когда создаёшь спам-фильтр, они могут означать наличие больше чем одного слова, написанного прописными буквами, и наличие слова «Виагра».

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

Далее полученные значения складываются со смещением, и получается число 5, которое у нас выделено красным. Это и есть ввод искусственного нейрона.

Идём дальше. Нейрон выполняет вычисление, выдавая выходное значение. Мы получили 1, так как именно единице равно округлённое значение сигмоиды в точке 5. Если, опять же, вспомнить про спам-фильтр, то факт вывода единицы означал бы, что текст был помечен нейроном в качестве спама.

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

Для наилучшего понимания этого процесса посмотрите серию видео на английском, от Welch Labs.

Шаг 2. Сигмоида

Прежде чем приступить к следующему шагу, было бы неплохо ознакомиться с 4-й неделей курса по Machine Learning от Coursera — она как раз посвящена нейронным сетям и помогла бы вам разобраться в особенностях и принципах их работы. Да, этот курс слишком сильно углубляется в математику, плюс основан на Octave, хотя многие предпочитают Python. Но тем не менее там можно почерпнуть много полезных знаний.

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

Если честно, это может занять целый день, причём результат будет далёк от идеального. Вот, к примеру, как с этим справился Per Harald Borgen, англоязычная статья которого стала основой материала, который вы сейчас читаете. Но главное здесь не в том, чтобы сделать всё идеально, а в том, чтобы разобраться, как всё работает. И понять, как устроена сигмоида.

Шаг 3. О методе обратного распространения ошибки

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

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

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

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

Шаг 4. Создание своей нейросети

Читая разные статьи и руководства, вы так или иначе будете создавать небольшие нейросети. И это очень эффективно для обучения в целом.

Пример очень полезной информации можно найти здесь. В этом материале удивительное количество знаний сжато до 11 строк кода.

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

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

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

Кстати, если интересуют хорошие наборы данных, вы можете посетить этот сайт.

Чем раньше вы начнёте свои эксперименты, тем лучше. Будет кстати и изучение Python-библиотек для программирования нейронных сетей: Theano, Lasagne, Nolearn. А ещё лучше — записаться на курс «Нейронные сети на Python» в OTUS. С его помощью вы освоите архитектуру нейронных сетей, узнаете методы их обучения и особенности реализации.

Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих