СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Нейронные сети. Краткое введение в черновиках Tutorial

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

image

Нейронные сети решают огромное количество задач. Самые основные — классификация и прогнозирование.

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

Если бы мы хотели узнать «буквенную оценку» за тест — это бы называлось классификацией.

Строение нейронных сетей

Любая искусственная нейронная сеть состоит из слоёв. Первый слой — входной, последний — выходной.

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


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

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

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



Стрелочки передающие сигналы — синапсы. Они выполняют простую функцию — умножают входной сигнал на синаптический вес. Нейроны выполняют более сложную функцию — складывают результаты синапсов и выводят через функцию активации. Мы будем использовать самую основную — сигмоиду. Она преобразует суммарный результат нейрона в вид от 0 до 1.
Выглядит она так: image

e — это экспонента(показательная функция, по-другому «exp»), -as — суммарный результат нейрона.

Таким образом мы получаем такую модель нейрона:


Прежде чем подать данные на нейронную сеть — нужно их нормализовать. Это делается из-за того, что нейронная сеть не способна различать единицы измерения.
Данные приводятся к виду от 0 до 1. Это делается простым способом. Мы берём каждое входное число, которое мы подадим на сеть, и делим его на максимальное число из входных данных.

Чтобы нормализовать выходные данные — мы делим каждое выходное значение на максимальное в его единицах измерения. Допустим, данные идут от 0 до 100, в таком случае мы делим наше выходное значение на 100. Нормализация выходных данных проводится при обучении.
Чтобы перевести выходное число обратно в наши единицы измерения, нужно умножить выходное число на то число, на которое мы делили. В нашем случае на 100.

Обучение нейронных сетей — достаточно сложная часть. Рассмотрим на задаче «Оценка за тест».
Чтобы обучить нейронную сеть, мы должны иметь уже готовую таблицу входных и выходных данных. Такая таблица называется обучающей выборкой.
Обучение — процесс, при котором изменяются синаптические веса, чтобы нейронная сеть выдавала более правильное значение.

Обратное распространение ошибки — самый лучший метод. Сначала мы вычисляем ошибку, затем распространяем её от выхода по синапсам к скрытым слоям. То есть ошибка умножается на синаптические веса. Модифицированные ошибки хранятся в каких-либо переменных или массивах. Чтобы начать менять веса благодаря этим ошибкам, применяют формулу дельта-правила. $weight += n * error * input$
Где n — скорость обучения(часто не используют), error — ошибка, input — входное значение.

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

Практика

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

Составим обучающую выборку входных данных и выходных данных для того, чтобы обучить нейронную сеть.
import numpy as np
X = np.array([[3,5],[5,1],[10,2]])
# обучающая выборка входных и выходных данных
y = np.array([[75, 82, 93]]).T


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

X = X / np.amax(X, axis = 0)
y = y / 100


Теперь создадим синапсы нашей нейронной сети. Так как у нас три скрытых нейрона и два входа, то у каждого скрытого нейрона будет по 2 синапса, в общем количестве — 6. Для этого составим двумерную матрицу, заполнив её случайными числами от 0 до 1. У выходного нейрона три синапса, так как три скрытых нейрона передают ему сигнал. Тоже создадим матрицу со случайными значениями.

synapses_hidden = 2 * np.random.random((2,3)) - 1
synapses_output = 2 * np.random.random((3,1)) - 1


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

Подаём данные и вычисляем ответ. np.dot перемножает матрицы и затем полученные значения.
np.exp — та самая экспонента для функции активации.
for j in range(10000):
    l0 = X
    # Входной слой ( 2 входа )
    l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
    # Скрытый слой ( 3 скрытых нейрона )
    l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
    # Выходной слой ( 1 выходной нейрон )


Продолжаем выполнять в том же цикле. Тут delta правило немного выглядит по-другому. Но принцип остаётся тем же.
    l2_delta = (y - l2) * (l2 * (1 - l2))
    # вычисляем ошибку и используем дельта-правило
    l1_delta = l2_delta.dot(synapses_output.T) * (l1 * (1 - l1))
    # получаем ошибку на скрытом слое и используем дельта-правило
    synapses_output += l1.T.dot(l2_delta)
    # корректируем веса
    synapses_hidden += l0.T.dot(l1_delta)
    # корректируем веса от входов к скрытым нейронам


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

print(l2 * 100)


В итоге мы получаем такие вот ответы:
[[ 75.00483985]
[ 82.53359502]
[ 92.09079581]]

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

Спасибо за внимание. Скоро выпущу обещанную статью о рекуррентных нейронных сетях.

В этой статье использовались примеры из видео Neural Networks Demystified, так же был использован пример нейронной сети из статьи «Нейросеть в 11 строчек на Python»

комментарии (13)

0
ubobrov ,  
Есть 2 вопроса:
1. Как на этапе проектирования сети понять сколько нам нужно слоёв?
2. Как понять сколько нам нужно нейронов на каждом слое?
0
EmeraldSoft ,  
В зависимости от сложности задачи. Если нейронов не хватает, то сама программа у вас зависнет. Я например устанавливаю количество нейронов равное количеству обучающих входных значений в обучающей подборке.
+1
kahi4 ,  

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

0
ubobrov ,  
А как определить «сложность задачи»? Вот в Вашем примере задача сложная или нет? Есть ли какая-нибудь шкала «сложности» задачи или мат аппарат, позволяющий просчитать эту сложность и порекомендовать нужное количество нейронов? И не понятно про количество слоёв, сколько их надо? и почему именно столько?
0
EmeraldSoft ,  
Количество нейронов, слоёв подбирается на самом деле. Подбиранием, вы выясняете точность ответа самой сети.
0
EmeraldSoft ,  
Нет, таких аппаратов не существует.
0
zolotykh ,  
Как использовать полученный результат на новых данных?
0
EmeraldSoft ,  
В смысле обученную сеть?

Создайте новую матрицу, например с такими данными
inputs = np.array([[5,6],[3,2]])

Распространяйте сигнал таким же методом, как при обучении, только без циклов и обучения.
l1 = 1 / (1 + np.exp(-(inputs.dot(synapses_hidden))))
l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))

Выведите результат:
print(l2)
0
zolotykh ,  
Спасибо, еще вопрос, как добавить в обучение новые данные, повторить цикл?
0
zolotykh ,  
UPD: разобрался, создать новый набор данных и повторить действия из тела цикла, правильно?

X = np.array([[0,0],[30,225],[0,0]])
l0 = X / np.amax(X, axis = 0)
l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))


Т.е. это обучение прошло на одном наборе данных, а если несколько?
0
EmeraldSoft ,  
Чтобы добавить новые данные для обучения, достаточно увеличить число данных в X, в самом начале. Ну или как вы назвали эту матрицу.
+1
kahi4 ,  
Любая искусственная нейронная сеть состоит из слоёв. Первый слой — входной, последний — выходной.

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

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


Есть же сети Кохонена, где все немного не так, а еще есть сети АРТ, где все сильно не так (особенно в всяких там fuzzy-ART-map), есть сети с нечеткой логикой, которые похожие на обычный перцептрон, но в деталях различаются (оперируют вероятностями принадлежности).


Обратное распространение ошибки — самый лучший метод. Сначала мы вычисляем ошибку, затем распространяем её от выхода по синапсам к скрытым слоям.

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


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

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


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


P.S. А вот о рекурентных нейронных сетях было бы интересно почитать.

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

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

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

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

Последняя картинка не понятная. Если бы я первый раз видел сетки, то точно не понял бы что к чему.

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