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

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

H Нейронные сети. Математика #1 в черновиках Tutorial

Введение


Всем привет. Я начинаю цикл статей, в которых буду объяснять элементарную линейную алгебру, статистику и т.п. Этот цикл подойдёт как для школьников, так и для студентов, которые начинают изучать нейронные сети или вообще только знакомятся с машинным обучением. Примеры я буду показывать на языке Python с использованием библиотеки NumPy. Данная библиотека позволяет работать с линейной алгеброй во много раз быстрее, чем без неё.



Нейрон


Нейрон — единица нейронных сетей, выполняющая роль сумматора. Он имеет входы, специальные весовые коэффициенты(синапсы), функцию активации, выход. В университетах или в другой любой статье нейрон описывают такой формулой:

Значок сигмы(буква похожая на Е) предназначен для того, чтобы упростить запись суммы. X — входной значение, W — весовой коэффициент. Если бы мы не использовали значок суммирования(сигму), то мы могли бы увидеть такую запись: $y = (x_0 * w_0) +(x_1*w_1)$. Это на случай, когда у нас два входа. Из этого всего исходит, что количество весовых коэффициентов(w), зависит от количества входов(x). И получается, что математическое строение нейрона — суммарный результат произведений весовых коэффициентов на входные значения.

На Python с библиотекой NumPy это выглядит так:
import numpy as np
X = np.array([[3,5],[6,1]])
W = np.array([[0.3,0.4],[1,0.5]])
print(X.dot(W))


Функция np.dot перемножает массивы(матрицы) и суммирует полученные результаты. Как раз то что нам нужно, а функция np.array позволяет нам создать массив. print выводит информацию.

Входные значения

Прежде чем подавать данные на нейронную сеть, их нужно нормализовать к виду от 0 до 1. Таким образом мы получим с вами вещественное число(десятичную дробь). Это делают из-за того, что нейронные сети не способны понять наши единицы измерения, поэтому данные приводят к одному виду. Чтобы произвести нормализацию данных — нужно наши входные значения делить на максимальное из всех наших входных значений. Например, у нас есть такие входы: 5, 3, 9, 1. Самое максимальное здесь — 9. Мы берём 5 и делим на 9, берём 3 и делим на 9 и т.д

import numpy as np
X = np.array([[100, 24],[51, 1003]])
X = X / np.amax(X, axis = 0)
print(X)


Функция np.amax позволяет нам вытащить самое максимальное число из двумерного массива(матрицы).

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

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

Весовые коэффициенты

Это связи, благодаря которым мы получаем какие-либо модифицированные значения. В формулах их обозначают буквой W. Они берут входное значение и умножают на своё весовое значение.

Чтобы поставить правильные весовые коэффициенты — мы должны обучить нашу нейронную сеть.

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

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

Перед обучением весовые коэффициенты нейронной сети задаются случайным образом. Например вот так:
weights = 2 * np.random.random((3,4)) - 1
print(weights)


Мы создали двумерный массив 3 * 4 cо случайными значениями.

Самый распространённый метод обучения нейронных сетей — обратное распространение ошибки. В следующих статьях мы рассмотрим метод градиентного спуска.

Обратное распространение ошибки

После того как мы получили выходной сигнал нейрона, мы должны вычислить ошибку нейронной сети. Ошибка — разность между правильным ответом(полученным из обучающей выборки) и ответом нейронной сети. По-другому это можно записать такой формулой: $error = right - output$

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

Дельта правило имеет такую формулу: $weight_i = weight_i + n * input * error_i$

Weight — вес, n — скорость обучения(обычно равная 0.0001, 0.1 или вообще не используется), input — входное значение, либо значение пришедшее со скрытых слоёв; error — модифицированная ошибки(локальная ошибка умноженная на весовой коэффициент), либо локальная ошибки полученная на выходе(разность между правильным ответом и ответом нейронной сети).

Функция активации


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

Пороговая функция

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

Это можно представить таким условием:

$sum.result > T, 1 || sum.result < T, 0$



Логистическая функция

Такая функция преобразует суммарный результат нейрона в число от 0 до 1.
Функция представляет из себя формулу: image

e — экспонента, показательная функция; -x — суммарный результат нейрона.

На графиках функций представляется так:

image

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

Создадим обучающую выборку:
import numpy as np
data_inputs = np.array([[3,5],[5,1],[10,2]])
data_outputs = np.array([[75, 82, 93]]).T


Проведём нормализацию входных и выходных данных. Входные данные нормализуем таким образом: берём каждое значение и делим на максимальное из всех значений.
Выходные данные изменяются в пределах от 0 до 100. Разделим каждое число на 100.

data_inputs = data_inputs / np.amax(data_inputs, axis = 0)
data_outputs = data_outputs / 100


Пускай в скрытом слое будет три нейрона, которые имеют по два синапса каждый. Количество синапсов = количеству входов. И в выходном слое будет один выходной нейрон, у которого три синапса, так как сигнал передают три скрытых нейрона.

syn0 = 2*np.random.random((2,3))-1
syn1 = 2*np.random.random((3,1))-1


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

for j in range(10000):
    inputs = data_inputs
    hidden = 1/(1+np.exp(-(np.dot(inputs,syn0))))
    output = 1/(1+np.exp(-(np.dot(hidden,syn1))))


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

output_delta = (y - output)*(output*(1-output))
hidden_delta = output_delta.dot(syn1.T) * (hidden * (1-hidden))
syn1 += hidden.T.dot(output_delta)
syn0 += inputs.T.dot(hidden_delta)


Теперь остаётся вывести данные с выходного слоя: print(l2 * 100)
Умножаем на 100, так как вначале на 100 делили.

Спасибо за внимание. В следующей статье я расскажу вам всё о линейной алгебре в кратком формате.

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

+1
lair ,  
Функция np.dot перемножает массивы(матрицы) и суммирует полученные результаты.

Стоп-стоп, а где же обещанная линейная алгебра? Уж либо "функция делает поэлементное умножение и суммирует", либо "функция дает скалярное произведение", но не и то, и другое одновременно.


Прежде чем подавать данные на нейронную сеть, их нужно нормализовать к виду от 0 до 1.

Зачем?


Таким образом мы получим с вами вещественное число(десятичную дробь).

… а можем и не получить.


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

Вообще-то, у нас все данные уже в одном виде — числовом. И единицы измерения вы уже убрали (точнее, у вас их нигде и не было).


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

То есть если у меня есть два входа, на одном значения из интервала 0.0001-0.0003, а на другом — 100000-500000, то мне все надо поделить на 500000?


Ну и да, к каждой вашей статье про нейронные сети всегда можно задать один и тот же вопрос: где bias?

–2
EmeraldSoft ,  
О нем я расскажу потом.
+1
lair ,  

О ком? О скалярном произведении? О bias? О нормализации?


Это ваше "потом" никогда не случается.

0
devpony ,  
Опять?
В следующей статье я расскажу вам всё о линейной алгебре в кратком формате

Не надо, пожалуйста. Лучше сами чего-нибудь почитайте.