Содержание статьи
Как сделать свою нейросеть за 10 минут на Python
Как можно улучшить нейронную сеть
Больше мощностей. Нейронные сети работают с матрицами, так что если нейронов много, вычисления получаются очень ресурсоемкие. Известные нейросети вроде Midjourney или ChatGPT — это сложные и «тяжелые» системы, для их работы нужны сервера с мощным «железом». Так что написать собственный DALL-E на домашнем компьютере не получится. Но есть сервисы для аренды мощностей: ими как раз пользуются инженеры машинного обучения, чтобы создавать, обучать и тестировать модели.
Дабы найти значение ошибки E, надо найти сумму квадратов разности векторных значений, которые были выданы нейронной сетью в виде ответа, а также вектора, который ожидается увидеть при обучении. Еще надо будет найти дельту каждого слоя и учесть, что для последнего слоя дельта будет равняться векторной разности фактического и ожидаемого результатов, покомпонентно умноженной на векторное значение производных последнего слоя:
Когда мы узнаем дельту последнего слоя, мы сможем найти дельты и всех предыдущих слоев. Чтобы это сделать, нужно будет лишь перемножить для текущего слоя транспонированную матрицу с дельтой, а потом перемножить результат с вектором производных функции активации предыдущего слоя:
Конечно, после сигмоида мы никогда не получим такие значения, но и результат после обучения нашей нейронки сложно назвать хоть чем-то хотя бы близко похожим на то, что нам нужно. И происходит это потому, что метод обратного распространения подразумевает многократное обучение нейронной сети. В коде которого будет производиться регулирование в соответствии с уже известными алгоритмами выравнивания весов.
И, конечно же, не забудем про генетический алгоритм, или же Genetic Algorithm. Тем не менее, основу основ обучения всех нейронок сегодня составляет именно метод обратного распространения. Именно им мы сейчас и воспользуемся. В коде этот метод будет выглядеть следующим образом. Итак, мы помним, что верными решениями для нас являются значения 0110.
В учебных целях очень часто применяют самую простейшую из них, линейную. Ее еще называют единичный скачок или жесткая пороговая функция. Выглядит в коде она следующим образом. Мы же будем применять более адекватную и подходящую функцию активатора, а именно сигмоид.
Лучше обучение. Искусственные нейронные сети обучаются примерно по тому же принципу, что живые существа. Когда человек часто повторяет одни и те же действия, он учится: ездить на велосипеде, рисовать или набирать текст. Это происходит, потому что веса между нейронами в мозгу меняются: нервные клетки наращивают новые связи, по-новому начинают воспринимать сигналы и правильнее их передают. Нейронная сеть тоже изменяет веса при обучении — чем оно объемнее, тем сильнее она «запомнит» какую-то закономерность.
Последовательность нейрослоев часто применяют для более глубокого обучения нейронной сети и большей формализации имеющихся данных. Именно поэтому, чтобы получить итоговый выходной вектор, нужно проделать вышеописанную операцию пару раз подряд по направлению от одного слоя к другому. В результате для 1-го слоя входным вектором будет являться X, а для последующих входом будет выход предыдущего слоя. То есть нейронная сеть может выглядеть следующим образом:
Функция активации
И позже я об этом еще покажу и расскажу в коде. Сам подход к обучению нейросети, то есть подразумевает старт с непрозрачностью, правильной позиции в поисках правильной. При этом довольно важен тот факт, что начальные веса не могут быть полностью одинаковыми, иначе они так одинаковыми в процессе обучения и останутся. Ну а раз мы все равно не знаем, какие должны быть веса, и нельзя делать их одинаковыми, то идея взять случайные веса в общем случае выглядит очень даже хорошо. И как я уже ранее сказал, именно благодаря весам нейрон будет определять результат.
Человеческий мозг состоит из ста миллиардов клеток, которые называются нейронами. Они соединены между собой синапсами. Если через синапсы к нейрону придет достаточное количество нервных импульсов, этот нейрон сработает и передаст нервный импульс дальше. Этот процесс лежит в основе нашего мышления. Мы можем смоделировать это явление, создав нейронную сеть с помощью компьютера. Нам не нужно воссоздавать все сложные биологические процессы, которые происходят в человеческом мозге на молекулярном уровне, нам достаточно знать, что происходит на более высоких уровнях. Для этого мы используем математический инструмент — матрицы, которые представляют собой таблицы чисел. Чтобы сделать все как можно проще, мы смоделируем только один нейрон, к которому поступает входная информация из трех источников и есть только один выход. 3 входных и 1 выходной сигнал Наша задача — научить нейронную сеть решать задачу, которая изображена в ниже. Первые четыре примера будут нашим тренировочным набором. Получилось ли у вас увидеть закономерность? Что должно быть на месте вопросительного знака — 0 или 1?
И все это нужно будет повторить, например, 20 тысяч раз. В коде этот алгоритм у нас будет выглядеть следующим образом. Если вы хотите более подробно на математическом уровне узнать о том, как именно устроен данный алгоритм обучения нейросети, то я в описании оставлю ссылку на статью, которая на русском языке понятно объясняет, как это все работает. Ну а теперь мы запустим код, и как видите, после запуска мы получаем результат, больше похожий на правду. И по сути уже сейчас наша нейронная сеть обучена. Она сама научилась выявлять взаимосвязь между входными и выходными данными. Давайте проверим, как она справится в какой-то новой для себя ситуации.
Другие методы и формулы. Чтобы нейроны обучались, нужно задать формулу корректировки весов — мы говорили про это выше. Если нейронов много, то формулу нужно как-то распространить на все из них. Для этого используется метод градиентного спуска: рассчитывается градиент по весам, а потом от него делается шаг в меньшую сторону. Звучит сложно, но на самом деле для этого есть специальные формулы и функции.
Иными словами, вес синопса не может быть меньше минус 1 и не может быть больше 1. На текущем этапе вам важно понимать то, что веса мы, грубо говоря, взяли с потолка. Это значит, что столь важные для нас веса синапса, выявляющие взаимосвязь между входными данными и результатом, сейчас непригодны к использованию. А значит, мы должны эту ситуацию как-то исправить. Я сейчас говорю о том, чтобы провести нашу нейронку через так называемый процесс обучения нейросети. Это позволит нам приблизить веса к более верным значениям, а значит, правильно выявлять взаимосвязь. В данное время существуется сразу несколько методов обучения нейросети. Например, это знаменитый метод обратного распространения, на английском Backpropagation. Еще есть метод упругого распространения, или же Resilient Propagation.
Вторая переменная это Training Outputs. Она хранит в себе массив 1 на 4, и это наши ожидаемые выходные данные. Также не забываем транспонировать вторую переменную, чтобы ее содержание поменялось и было 4 на 1. Дальше нам надо инициализировать веса. Ранее я уже говорил, что мы будем это делать при помощи генератора случайных чисел. Чтобы и у вас, и у меня получались одинаковые случайные числа, давайте договоримся и укажем сид генератора в значении 1.
Как написать это на Python
Например, на вход поступает картинка. Чтобы нейросеть могла понять, что на ней изображено, она должна выделить разные элементы из картинки, распознать их и подумать, что означает сочетание этих элементов. Примерно так работает зрительная кора в головном мозге. Это несколько задач, их не смогут решить одинаковые нейроны. Поэтому нужно несколько слоев, где каждый делает что-то свое. Для распознавания часто используют так называемые сверточные нейросети. Они состоят из комбинации сверточных и субдискретизирующих слоев, каждый из которых решает свою задачу.
Все, что сейчас произошло у вас в мозгу, мы называем мышлением. Мозг принял входные данные, увидел выходные, вычислил взаимосвязь и впоследствии научился контролировать как их распознавать? Причем уже без необходимости в повторном вычислении взаимосвязи входных и выходных данных. Ну а нам осталось только воспроизвести этот же процесс в коде, чем мы сейчас и займемся. Но сначала давайте наглядно увидим, как будет выглядеть наш перцептрон. У нас будут входные данные, будет сам нейрон, конечно же результат и синапсы. Как уже понятно, синапсы это некая связь между . входными данными и тем, что попадет в нейрон. Соответственно, у нас есть какие-то входные данные, это будут нолики и единицы, своего рода аналог true и false в булевом типе данных.
Для уменьшения ошибки нейронной сети надо поменять весовые коэффициенты, причем послойно. Каким же образом это осуществить? Ничего сложного в этом нет: надо воспользоваться методом градиентного спуска. То есть нам надо рассчитать градиент по весам и сделать шаг от полученного градиента в отрицательную сторону. Давайте вспомним, что на этапе прямого распространения мы запоминали входные сигналы, а во время обратного распространения ошибки вычисляли дельты, причем послойно. Как раз ими и надо воспользоваться в целях нахождения градиента. Градиент по весам будет равняться не по компонентному перемножению дельт и входного вектора. Дабы обновить весовые коэффициенты, снизив таким образом ошибку нейросети, нужно просто вычесть из матрицы весов итог перемножения входных векторов и дельт, помноженный на скорость обучения. Все вышеперечисленное можно записать в следующем виде:
Давайте поймем почему формула имеет такой вид. Сначала нам нужно учесть то, что мы хотим скорректировать вес пропорционально размеру ошибки. Далее ошибка умножается на значение, поданное на вход нейрона, что, в нашем случае, 0 или 1. Если на вход был подан 0, то вес не корректируется. И в конце выражение умножается на градиент сигмоиды. Разберемся в последнем шаге по порядку:
Но ее легко получить путем увеличения количества нейронов. Давайте попробуем реализовать обучение с тремя нейронами в скрытом слое и одним выходным (выход ведь у нас только один). Чтобы все получилось, создадим массив X и Y, имеющий обучающие данные и саму нейронную сеть:
И теперь, когда мы понимаем общий принцип действия, давайте перейдем к написанию кода, чтобы более наглядно увидеть реализацию всего, что я только что рассказывал. Но сначала в Python нам нужно установить модуль numpy. Он нам понадобится для легкой и высокопроизводительной работы с многомерными массивами. В описании я оставлю ссылку на репозиторий numpy в PyPy. Устанавливается он, как и любой другой модуль в Python, без каких-либо проблем. Теперь в коде начнем с импорта numpy, а затем объявим функцию sigmoid для реализации нашей функции активатора. Уже здесь нам пригождается numpy.
А начнем из простого и распространенного примера. Допустим, у нас есть какая-то проблема, которую наша нейронка должна уметь решать. Во всех учебниках и уроках по нейронкам обычно можно встретить вот эту таблицу. Здесь вы видите набор входных и выходных данных. Можете попробовать поставить видео на паузу и вычислить самостоятельно связь между этими данными. Хотя здесь нет ничего сложного, я думаю, уже видно, что в выходном столбце оказываются значения из первого столбца входного массива. На данном этапе ваш мозг, при помощи своих нейронных связей, синапсов и кое-чего еще, уже смог решить данную проблему и научиться, как ее решать впоследствии.
Допустим, передадим ей значение 1.1.0. Как вы помните, во входных тренировочных данных такого значения у нас не было. В коде задействовать нашу уже обученную нейросеть можно также очень просто. Запускаем получившийся код и видим результат. Наша нейронная сеть прекрасно справилась и поняла, что очень большая вероятность того, что на выходе должна быть цифра 1. И то, что мы сейчас с вами запрограммировали, это простейшая нейронная сеть, которая называется Перцептрон. Но даже с такой простой нейронкой уже можно решить решать какие-то более-менее реальные задачи.