Программируем свою нейронную сеть

Программируем свою нейронную сеть

Нейронные сети являются своего рода трендом в области развития computer science. Появляется все больше софта, которое использует machine learning алгоритмы внутри. На мой взгляд бизнес в большей степени сейчас решает задачи на основе классических алгоритмов машинного обучения (т.е. без использования нейронный сетей). Однако есть ряд задач, где нейронные сети хорошо себя зарекомендовали:

  • обработка изображений
  • обработка видео
  • голосовые ассистенты и помощники
  • обработка и распознавание текста
  • self-driving машины
  • разведка ландшафта дронами

Эта статья преследует цель глубже понять строение нейронной сети, дать возможность написать свою нейронную сеть без использования deep learning библиотек.

Проблема

Нейросети как правило решают какую-то проблему. Поэтому придумаем для себя следующую задачу.

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

PersonSmokingObesityExerciseDiabetic
Person 10101
Person 20010
Person 31000
Person 41101
Person 51111
простой датасет

Человек может курить (smoking), страдать ожирением (obesity), заниматься спортом (exercise), быть диабетиком (diabetic). Если в ячейке стоит 1 – это истина, 0 – ложь. Например, Person 1 – страдает ожирением и является диабетиком.

Мы хотим на новых данных научиться предсказывать эти характеристики.

Создадим простую нейронную сеть с одним слоем на вход и одним слоем на выход.

Краткое введение в нейронные сети.

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

Сначала нейронная сеть действует практически наугад. Затем получившиеся данные она сравниваем с ответом (который у нас есть на обучающей выборке) и вычисляем разницу между получившимся результатом и правильными данными. Это функция называется функцией стоимости (cost function), или по-другому функцией потерь (loss function). Под стоимостью/потерей подразумевается ошибка. И наша цель заключается в минимизации этой ошибки.

simple neural network

xi представляют собой независимые переменные (или характеристики), wi представляют собой веса или коэффициенты каждой характеристики. Выходной слой является взвешенной суммой.

Основные составляющие нейронной сети

Слои

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

Входной слой

Входной слой – это слой куда приходят наши данные на начальном этапе. Это первый слой нейронной сети.

Выходной слой

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

Скрытый слой

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

Нейроны

Каждый слой состоит из нейронов. Нейроны представляют наши данные. Каждый нейрон содержит одно числовое значение. Например, если вы передаете картинку для анализа размерами 640х480 пикселей, значит вам потребуется 640х480=307200 нейронов на входном слое.

Связанные слои

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

Веса

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

Bias

Или отклонение, является некой константой. Служит для более точной настройки нейронной сети.

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

Функция, которая применяется к взвешенной сумме нейрона. Наиболее распространенные фукнции:

  • RELU
  • Гиперболический тангенс
  • Сигмоида
Обратное распространение

Является ключевым алгоритмом в тренировке нейронной сети. Именно он отвечает за подборку весов нейронов и bias.

Работа нейронной сети происходит в два этапа.

Прямое распространение

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

  • курение
  • ожирение
  • занятия спортом

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

  • не курит
  • не страдает ожирением
  • не занимается спортом

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

X.W=x1w1+x2w2+x3w3+b

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

Если у вас установлен numpy и matplotlib, можете нарисовать эту функцию

input = np.linspace(-10, 10, 100)
def sigmoid(x):
    return 1/(1+np.exp(-x))
from matplotlib import pyplot as plt
plt.plot(input, sigmoid(input), c="r")

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

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

В качестве функции потерь возьмем среднеквадратичную ошибку (MSE)

MSE=1/n∑n i=1(predicted−observed)2

n – количество наблюдений.

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

Реализация при помощи numpy

Запишем наши характеристики и метки

import numpy as np

features = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]])
labels = np.array([[1, 0, 0, 1, 1]])
labels = labels.reshape(5, 1)

Определим веса и bias

np.random.seed(123)
weights = np.random.rand(3, 1)
bias = np.random.rand(1)
lr = 0.05

Тренируем нашу сеть

for epoch in range(20000):
    # feedforward
    XW = np.dot(features, weights) + bias
    output = sigmoid(XW)

    # backpropagation step 1
    error = output - labels
    print(error.sum())

    # backpropagation step 2
    dcost_dpred = error
    dpred_dz = sigmoid_der(output)

    output_delta = dcost_dpred * dpred_dz

    inputs = features.T
    weights -= lr * np.dot(inputs, output_delta)

    for num in output_delta:
        bias -= lr * num

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

d_cost/dw=d_cost/d_pred*d_pred/dz*dz/dw

dcost/dpred может быть посчитано как

2(predicted−observed)

2 является константой, поэтому можно убрать. Поэтому получаем просто error.

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

dz/dw является по сути входными характеристиками.

Вместо того чтобы проходиться по каждой записи и умножать на output_delta можно транспонировать матрицу характеристик и матрично перемножить с output_delta. И в конце умножаем на lr (learning rate) скорость обучения нейронной сети.

В начале ошибка довольно большая – 0,83. Однако в конце она уменьшается до

0.001682051587058541
0.0016819688044010135
0.0016818860299710938
0.001681803263767337
0.0016817205057886135
0.0016816377560336122
0.0016815550145013198
0.001681472281190189
0.0016813895560991061
0.0016813068392270755
0.0016812241305727664
0.001681141430134642
0.0016810587379115956
0.0016809760539027597
0.001680893378106809

Теперь предскажем новые данные

new_value = np.array([0, 1, 0])
result = sigmoid(np.dot(new_value, weights) + bias)

print("result is {:.10f}".format(result[0]))

Результат будет 0.9983763058, т.е. почти 100% вероятность что человек будет диабетиком, если он не курит, страдает ожирением и не занимается спортом.

Полный код доступен на github.

P.S. Там же можно посмотреть и пример реализации на tensorflow.

miholeus

Leave a Reply

No Comment

Leave a Reply

Your email address will not be published. Required fields are marked *