Нейронная сеть — это модное слово, которое встречается на каждой странице всех технических журналов. Какие они на самом деле? Вопреки распространенному мнению, Бог не сотворил их за шесть дней. Они существуют (или, по крайней мере, их концепция) с 40-х годов, когда Дональд О. Хебб, член Королевского общества, предложил Hebbian Learning. (Вдохновение Тони Старка?) Механический эквивалент, способный обновлять вес с помощью электродвигателей.
Эти модели, которые веками игнорировались, пришли в бешенство с появлением графических процессоров и огромной, хотя и дешевой вычислительной мощности, что привело к машинному обучению. бум.

Блоки Lego: нейроны

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

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

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

F(x1,x2,x3…xn)=g(w1.x1+w2.x2+w3.x3…+w4.xn+b)

где x1,x2,x3,…xn — входные данные, wi — изученный вес нейрона, а g(x) — функция активации нейрона.

В поисках Святого Грааля: нейронные сети

Теперь, когда вы так хорошо знаете об основных строительных блоках нейронных сетей, давайте сразу приступим к их созданию с нуля. Мы будем использовать библиотеку Python numpy (да… именно так), чтобы создать сеть. Но перед этим нам понадобятся данные, на которых нейронная сеть будет учиться. Давайте воспользуемся набором данных Anderson’s Iris, предоставленным Репозиторием машинного обучения UCI. Набор данных имеет формат, аналогичный следующему:

F0–F3 — это функции, а 0–2 — классы. Набор данных Iris содержит 3 класса. Если ваш набор данных не в числовом формате, вы можете легко преобразовать его с помощью Python (или даже текстового редактора). После того, как вы загрузили набор данных и поместили его в подходящий каталог, давайте начнем.

Приведенные выше строки импортируют необходимые библиотеки.

Затем нам нужно импортировать наш набор данных и преобразовать его в пустой массив, оператор np.array(list(iris)) делает именно это.
На следующем шаге мы немного перемешаем строки набора данных… это заключается в обеспечении равномерности распределения.

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

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

Теперь давайте внесем последние изменения в представление, нам понадобятся метки классов в формате One-Hot encoded, для этого мы будем использовать следующий трюк с numpy.

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

Давайте сложим их: слои нейронов

Вспомните эти простые нейронные блоки, способные выполнять простые функции, давайте сложим их в слои и соединим пару этих слоев вместе. Это позволяет сети нейронов вычислять сложные функции, как мы обсуждали ранее. У нас будет простая трехслойная сеть с одним скрытым слоем. Размеры скрытого слоя — это гиперпараметр, и вам придется перебрать пару моделей, чтобы получить то, что работает. Здесь мы сохраним размер скрытого слоя hiddenCount того же размера, что и входной слой. Мы также инициализируем активацию нейронных слоев ai, ah и ao.

Изучаемые параметры: веса

Веса — это параметры, которые наша нейронная сеть будет фактически изучать. Матрица — это естественный способ обозначения полносвязных слоев нейронной сети. Нам нужно инициализировать наши веса из равномерного распределения, это делается для того, чтобы сеть нарушила симметрию, вычисляя разные сигналы, иначе она продолжит выполнять те же вычисления. (Попробуйте сами!!!). Здесь мы используем стратегию инициализации, известную как He Initialization, предложенную в статье He, Kaiming, et al. Эта стратегия используется для того, чтобы веса не были слишком большими или слишком маленькими, что может вызвать проблемы взрывающихся или исчезающих градиентов во время тренировки; вот статья Иманола Шлага, посвященная теме.

Мы будем использовать Gradient Decent Optimizer, известный как Momentum. Это обеспечит более быстрое обучение нашей сети. Подробнее об этом позже, но давайте инициализируем необходимые матрицы здесь.

Функция прямой связи

Функция feedFwd() вычисляет один проход вперед для нейронной сети. Эта функция возвращает вычисленный вывод нейронной сети. Аргумент featureMat может иметь любое количество строк, но количество столбцов должно соответствовать размеру входного слоя, то есть количество объектов + 1 для смещения. Мы будем использовать tanh в качестве функции активации. Первый вывод будет рассчитан с использованием случайных весов, поэтому результаты будут такими же ошибочными, как и сторонники плоской Земли; но наша Сеть скоро начнет учиться.

Следующие строки кода Python вычисляют приведенное ниже уравнение:

Zh=X.Wih Ah=tanh(Zh)

где X — обучающие примеры, а Wih

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

Zh=X.Wih+bih

Но мы уже позаботились об этом, так что не беспокойтесь!

Шаг обучения: обратное распространение

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

E=12∑i(ˆyi−yi)2

где ˆyi и yi — i-я метка истинности и i-я вычисляемая метка соответственно. Наша сеть минимизирует эту функцию стоимости, двигаясь к минимуму функционального пространства. Интуитивно подумайте об этом, как о спуске с холма с завязанными глазами. Лучший способ спуститься вниз — это спуститься в самую низкую точку, на которую вы можете наступить (здесь нет шуток на скалах!). Размер вашего шага аналогичен скорости обучения (а потом Господь сказал, пусть будет еще один гиперпараметр) в градиентном спуске. Если вы будете делать очень маленькие шаги, вам потребуется много времени, чтобы достичь дна, с другой стороны, если у вас рекордно длинные ноги… и я имею в виду Лунга, вы никогда не достигнете дна с комфортом.
Математически это падение может быть достигнуто с помощью волшебства исчисления или более технически; вычисление градиента функции ошибки и соответствующая настройка параметров. Уравнение, представляющее обратное распространение, имеет следующий вид:

∂Ao=Y−ˆY

∂who=1mATh.∂Ao

∂Ah=Ao.wTho∗(1−A2h)

∂wih=1мXT.Ач

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

кто=кто-α.∂кто

wih=wih−α.∂wih

где α — скорость обучения.

Член (1-A2h) в третьем уравнении является результатом использования tanh(x) в качестве нашей функции активации, производная которой равна 1-tanh(x)2. Если вам интересно, как получаются эти уравнения, прочитайте эту отличную статью Брайана Долхански о BackPropagation.

Импульсные оптимизации

Помните, что cih и cho определены для обновлений моментума? Вот где это действительно сияет. Учитывая наш пример спуска; импульс работает, если вы представляете себе, что у вас есть небольшая скорость при спуске, это не позволит вам резко изменить направление во время спуска. Тот же принцип интуитивно применим к обновлениям весов Momentum. Математически это основано на принципе скользящего среднего обновлений веса и может быть представлено как:

Wt=βWt−1+(1−β)θt

Для краткости мы используем только β.θt, что прекрасно работает.

Функция обучения

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

  • Ванильный градиентный спуск: все данные тренировки сразу.
  • Мини-пакетный градиентный спуск: сразу несколько данных, обычно в степени 2, например 16, 32, 64.
  • Стохастический градиентный спуск: сразу один обучающий пример.

Функция train позволяет нам использовать все три, используя параметр batchSize, когда он установлен на M, размер набора данных; он запускает Vanilla Gradient Descent. Установка размера пакета на 1 обучает сеть с использованием стохастического градиентного спуска, любых других значений, и мы можем обучать сеть с использованием мини-пакетного градиентного спуска. Вот отличная статья Энди Томаса на adventuresinmachinelearning.com, в которой подробно обсуждаются все три типа градиентного спуска.

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

α=α∗11+η.α.t

β=β∗11+η.β.t

где η — скорость распада. Этот метод известен как обратное время затухания.

def train(X,Y,iteration=1000,learningRate=0.001,batchSize=1,beta=0.099,decayRate=0.01):
errorTimeline = []

#train it for iteration number of epoch
for epoch in xrange(iteration):

#for each mini batch
for i in xrange(0,X.shape[0],batchSize):
#split the dataset into mini batches
batchSplit = min(i+batchSize,X.shape[0])
XminiBatch = X[i:batchSplit,:]
YminiBatch = Y[i:batchSplit,:]

#calculate a forwasd pass through the network
output = feedFwd(XminiBatch)

#calculate Quadratic error
error = 0.5*np.sum((YminiBatch-output)**2)/batchSize
#print error

#backprop and update weights
backProp(XminiBatch,YminiBatch,output,learningRate,batchSize,beta)

#after every 50 iteration decrease momentum and learning rate
#decreasing momentum helps reduce the chances of overshooting a convergence point
if epoch%50 == 0 and epoch > 0:
learningRate *= 1./(1. + (decayRate * epoch))
beta *= 1./(1. + (decayRate * epoch))
#Store error for ploting graph
errorTimeline.append(error)
print 'Epoch :',epoch,', Error :',error

return errorTimeline

Давайте обучим сеть

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

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

Момент истины: тестирование нашей сети

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

Мы видим, что сеть достигает точности более 97% для тестового набора. Таким образом, мы успешно создали нейронную сеть, которая точно идентифицирует виды ирисов… и это тоже с нуля! Нейронная сеть — это универсальные устройства, способные решать невероятные задачи с поразительной точностью. Попробуйте поэкспериментировать с разными наборами данных и поделитесь своим опытом в комментариях ниже. Полный код вы можете найти здесь. Поделитесь своей любовью, сославшись на эту статью. Следите за новостями, чтобы не пропустить такие статьи!!!