Что под капотом у нейронной сети. Нейросеть c точки зрения математики и программирования

Здравствуйте, меня зовут Александр, я backend-разработчик.

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

Цель данной публикации — комплексное рассмотрение строения искусственных нейронных сетей c точки зрения и математики и программного кода. В данной работе нейронная сеть реализуется на языке Python с использованием библиотеки tensorflow.keras. Статья сосредоточена в основном на строении и функционировании искусственной нейронной сети, поэтому такие этапы как обучение и т.д. в ней не затрагиваются.

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

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

  1. Линейная функция активации который имеет вид f(x,a)=ax

  2. Сигмоид который имеет вид {f(x,a)} = {1\over 1 + e^{-ax}}

  3. ReLu вида f(x,a) = \begin{cases}     x      & \quad \text{x > 0}\\ ax & \quad \text{x <= 0}   \end{cases}

Активационная функция ReLu

Активационная функция ReLu

В расчетах будем использовать активационную функцию ReLu с параметром = 1. Т.к. активационная функция имеет вид f (x) = x, для положительных значений параметров ей можно пренебречь.

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

Схема одного нейрона

Схема одного нейрона

X1Xn — значение входных сигналов, Y — выходной сигнал, W1…Wn — вес определяет, насколько соответствующий вход нейрона влияет на его состояние.

Таким образом нейрон так же представляет из себя некую функцию вида

S_n(X,W) = f({\sum_{i=1}^n(X_iW_i)})

где f — активационная функция.

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

Рассмотрим пример. Исследуется зависимость Y от двух параметров X1 и X2.

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

X1

X2

Y

1

1

2

2

1

3

3

1

4

4

1

5

5

1

6

1

2

3

2

2

4

3

2

5

4

2

6

5

2

7

1

5

6

Не сложно заметить, что зависимостьY(X1,X2) = X1 + X2
Но допустим, мы этого не знаем. Попробуем найти значения Y обучив нейронную сеть.

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

from tensorflow.keras.models import Sequential
model = Sequential()

Для нахождения весов на каждом слое используется команда

for layer in model.layers: 
  print(layer.get_weights())

Рассмотрим две схемы нейронной сети:

В первом случае нейронная сеть имеет два слоя, на каждом слое по одному нейрону (вычисленный вес показан на рисунке).

Схема 1

Схема 1

model.add(Dense(1, input_shape=X.shape[1:], kernel_constraint=non_neg()))
model.add(layers.Activation(keras.activations.relu))
model.add(Dense(1, activation='linear', kernel_constraint=non_neg()))

Выполним проверку, т.е. вычислим многочлен Y = (X1 * W11 + X2 * W12) * W21

Для получения аналогичных данных используется метод predict

model.predict(…)

X1

X2

Y = X1 + X2

Вычисленный результат

1

1

2

(1×0.7717732 + 1×0.7717735)*1.295717 = 2

10

10

20

(10×0.7717732 + 10×0.7717735)*1.295717 = 20

5.5

2.5

8

(5.5×0.7717732 + 2.5×0.7717735)*1.295717 = 8

10.3

2.1

12.4

(10.3×0.7717732 + 2.1×0.7717735)*1.295717 = 12.4

8.3

20.1

28.4

(8.3×0.7717732 + 20.1×0.7717735)*1.295717 = 28.4

Во втором случае нейронная сеть имеет два слоя, на первом слое два нейрона, а на втором они нейрон (вычисленный вес показан на рисунке).

Схема 2

Схема 2

model.add(Dense(1, input_shape=X.shape[1:], kernel_constraint=non_neg()))
model.add(layers.Activation(keras.activations.relu))
model.add(Dense(1, activation='linear', kernel_constraint=non_neg()))

Вычислим многочлен Y = (X1 * W11 + X2 * W12) * W21 + (X1 * W13 + X2 * W14) * W22

X1

X2

Y = X1 + X2

Вычисленный результат

1

1

2

(1×0.75749624 + 1×0.7609735)* 0.67330104 + (1×0.6390331+
1×0.88485044)*0.6438818 = 2

10

10

20

(10×0.75749624 + 10×0.7609735)*0.67330104 +
(10×0.6390331+ 10×0.88485044)*0.6438818 = 20

5.5

2.5

8

(5.5×0.75749624 + 2.5×0.7609735)*0.67330104 +
(5.5×0.6390331+ 2.5×0.88485044)*0.6438818 = 8

10.3

2.1

12.4

(10.3×0.75749624 + 2.1×0.7609735)*0.67330104 +
(10.3×0.6390331+ 2.1×0.88485044)*0.6438818 = 11.76

8.3

20.1

28.4

(8.3×0.75749624 + 20.1×0.7609735)0.67330104 + (8.3 0.6390331+ 20.1×0.88485044)*0.6438818 = 29.4

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

© Habrahabr.ru