Простейшая искусственная нейронная сеть

открытый источник GitHub Нейронные сети
Простейшая искусственная нейронная сеть

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

Оригинальный адрес:Simplest artificial neural network

Оригинальный автор: Гиви Одикадзе (уполномоченный)

Переводчик и корректор: HelloGitHub — Bear & Braised Egg

предисловие

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

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

Содержание разделено на две части:

  • первая часть:Простейшая искусственная нейронная сеть
  • Вторая часть:Самый простой алгоритм обратного распространения

Искусственная нейронная сеть — это основа искусственного интеллекта, и только закрепив ее, мы сможем играть в магию ИИ!

温馨提示: Формул хоть и много, но выглядят они как блеф, на самом деле понять их несложно, если внимательно их прочитать. Начинается следующий текст!

Простейшая искусственная нейронная сеть

Простейшая искусственная нейронная сеть объяснена и продемонстрирована с помощью теории и кода.

Образец кода:GitHub.com/okadina/love-…

теория

смоделированные нейроны

Вдохновленные тем, как работает человеческий мозг, искусственные нейронные сети имеют взаимосвязанные аналоговые нейроны, которые хранят шаблоны и взаимодействуют друг с другом. В своей простейшей форме аналоговый нейрон имеет одно или несколько входных значений.xix_iи выходное значениеyy, каждый, из которыхxix_iимеет весwiw_i.

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

y=i=1nwixiy = \sum_{i=1}^{n}{w_i}{x_i}

простой пример

Роль сети заключается в передаче нескольких параметровwwСмоделируйте сложную функцию, чтобы при заданном ряде входных значенийxxполучить конкретное выходное значение, когдаyy, и эти параметрыwwОбычно нам трудно сформулировать себя.

Предположим, у нас теперь есть сеть с двумя входными значениямиx1=0.2x_1 = 0.2,x2=0.4x_2 = 0.4, они соответствуют двум значениям весаw1w_1иw2w_2.

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

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

В этом случае мы получаем

y=x1w1+x2w2=0.2*1+0.4*1=0.6y = x_1w_1 + x_2w_2 = 0.2 * 1 + 0.4 * 1 = 0.6

разница

Если выходное значениеyyЕсли оно не соответствует ожидаемому выходному значению, возникает ошибка.

Например, если мы хотим, чтобы целевое значение былоt=0.5t = 0.5, тогда разница здесь

yt=0.60.5=0.1y - t = 0.6 - 0.5 = 0.1

Обычно мы используем дисперсию (то есть функцию стоимости) для измерения ошибки:

E=12(yt)2E = \frac{1}{2}(y - t)^2

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

E=12n(yiti)2E = \frac{1}{2n}(y_i - t_i)^2

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

Чтобы исправить ошибку, нам нужно настроить значения веса так, чтобы результат был близок к нашему целевому значению. В нашем примереw1w_1Снижение с 1,0 до 0,5 приведет к цели, потому что

y=t=0.2*0.5+0.4*1.0=0.5y = t = 0.2 * 0.5 + 0.4 * 1.0 = 0.5

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

градиентный спуск

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

Что такое градиент?

Градиент — это, по сути, вектор, указывающий на максимальный наклон функции. Мы используем{\triangledown}для представления градиента, который является просто векторной формой частной производной функциональной переменной.

Для функции двух переменных она принимает вид:

f(x,y)=[fx,fy]=[Дf(x,y)Дx,Дf(x,y)Дy]\triangledown f(x,y) = \left[f_x, f_y\right] = \left[\frac{\eth f(x,y)}{\eth_x},\frac{\eth f(x,y)}{\eth_y}\right]

Давайте смоделируем простой пример с некоторыми числами. Предположим, у нас есть функция, котораяf(x,y)=2x2+3y3f(x,y) = 2x^2 + 3y^3, то градиент будет

f(x,y)=[4x,9y2]\triangledown f(x,y) = [4x, 9y^2]

Что такое градиентный спуск?

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

Мы используемскорость обученияпостоянная для представления этого небольшого шага в противоположном направлении, в формуле, которую мы используемε\varepsilonохарактеризовать.

еслиε\varepsilonЕсли значение слишком велико, можно напрямую пропустить минимум, но если значение слишком мало, то нашей сети потребуется больше времени для обучения, и она также может попасть в неглубокий локальный минимум.

Для двух значений веса в нашем примереw1w_1иw2w_2, нам нужно найти градиент этих двух весовых значений относительно функции ошибок

wE=<ДEДw1,ДEДw2>\triangledown_wE = <\frac{\eth_E}{\eth_{w_1}},\frac{\eth_E}{\eth_{w_2}}>

Помните нашу формулу вышеE=12(yt)2E = \frac{1}{2}(y - t)^2иy=x1w1+x2w2y = x_1w_1 + x_2w_2? заw1w_1иw2w_2, мы можем взять его и вычислить его градиент отдельно по цепному правилу вывода в исчислении

ДEДwi=ДEДyДyДwi=ДДy(12(yt)2)ДДwi(xiwi)=(yt)xi\frac{\eth_E}{\eth_{w_i}} = \frac{\eth_E}{\eth_{y}}\frac{\eth_y}{\eth_{w_i}}=\frac{\eth}{\eth_{y}}(\frac{1}{2}(y-t)^2)\frac{\eth}{\eth_{w_i}}(x_iw_i) = (y - t)x_i

Для краткости далее будем использоватьдельта\deltaэтот термин для обозначенияДEДy=yt\frac{\eth_E}{\eth_{y}} = y - t.

Как только у нас будет градиент, возьмите предложенную скорость обучения.ε\varepsilonВнесите, вы можете обновить значение веса следующими способами:

w1=w1εw1E=w1εдельтаx1w_1 = w_1 - \varepsilon \triangledown_{w_1}E = w_1 - \varepsilon \delta{x_1}
w2=w2εw2E=w2εдельтаx2w_2 = w_2 - \varepsilon \triangledown_{w_2}E = w_2 - \varepsilon \delta{x_2}

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

пример кода

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

x=[1.01.01.00.0]y'=[0.01.0]x = \left[ \begin{matrix} 1.0 & 1.0 \\ 1.0 & 0.0 \end{matrix} \right] \quad y{'}= [ 0.0\quad 1.0 ]

После обучения сеть будет выводить ~0 при вводе двух единиц и ~1 при вводе 1 и 0.

Как это работает?

Go

PS D:\github\ai-simplest-network-master\src> go build -o bin/test.exe
PS D:\github\ai-simplest-network-master\bin> ./test.exe

err:  1.7930306267024234
err:  1.1763080417089242
……
err:  0.00011642621631266815
err:  0.00010770190838306002
err:  9.963134967988221e-05
Finished after 111 iterations

Results ----------------------
[1 1] => [0.007421243532258703]
[1 0] => [0.9879921757260246]

Docker

docker build -t simplest-network .
docker run --rm simplest-network

Во-вторых, самый простой алгоритм обратного распространения

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

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

Образец кода:GitHub.com/okadina/love-…

теория

Введение в персептрон

Персептрон — это процессор, принимающий входные данные.xx, используя функцию активацииffпреобразовать его и вывести результатyy.

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

xj=i=1Ixiwij+bix_j = \sum_{i=1}^{I}{x_i}{w_{ij}} + b_i

Если мы рассматриваем ошибку как еще один узел в слое с константой -1, то мы можем упростить эту формулу до

xj=i=1I+1xiwijx_j = \sum_{i=1}^{I+1}{x_i}{w_{ij}}

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

Зачем нужны функции активации? Если нет, то вывод каждого из наших узлов будет линейным, так что вся нейронная сеть будет выводом линейной операции на основе входного значения. Поскольку комбинация линейных функций по-прежнему является линейной, необходимо ввести нелинейные функции, чтобы нейронные сети отличались от моделей линейной регрессии.

противx=xwx=\sum{\vec x}{\vec w}​, типичная функция активации имеет следующий вид:

Сигмовидная функция:y=11+exy = \frac {1} {1+e^{-x}}

Функция линейного выпрямления:y=max(0,x)y = max(0,x)

тан функция:y=tanh(x)y = tanh(x)

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

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

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

срок

  • xi,xj,xkx_i, x_j, x_k— входные значения узлов слоя I, J, K соответственно.
  • yi,yj,yky_i, y_j, y_kявляются выходными значениями узлов в слоях I, J и K соответственно.
  • yk'y^{'}_{k}является ожидаемым выходным значением выходного узла K.
  • wij,wjkw_{ij}, w_{jk}- значения веса слоев от I до J и слоев от J до K соответственно.
  • ttПредставляет текущий набор ассоциаций в наборе T ассоциаций.

В приведенном ниже примере мы применим следующие функции активации к различным узлам слоя:

  • входной слой -> функция идентификации
  • Скрытый слой -> Сигмовидная функция
  • выходной слой -> функция идентификации

The forward pass

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

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

xj=i=1Iwijyix_j = \sum_{i=1}^{I}{w_{ij}}{y_i}

Поскольку функция активации скрытого слоя является сигмовидной, вывод будет:

yj=fj(xj)=11+exjy_j = f_j(x_j) = \frac {1}{1 + e^{-x_j}}

Точно так же входное значение выходного слоя равно

xk=j=1Jwjkyjx_k = \sum_{j=1}^{J}{w_{jk}}{y_j}

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

yk=fk(xk)=xky_k = f_k(x_k) = x_k

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

E=i=1IEt=12Tt=1T(yktykt')2E = \sum_{i=1}^{I}{E_{t}}=\frac{1}{2T}\sum_{t=1}^{T}({y_{kt}} - {y^{'}_{kt}})^2

The backward pass

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

Из первой части обучения мы знаем, что корректировка веса может быть основана на частной производной ошибки по весу, умноженной на скорость обучения, которая имеет вид

Δwjk=εEwjk\Delta w_{jk} = - \varepsilon \frac{\partial E}{\partial w_{jk}}

Градиент ошибки вычисляем по цепному правилу следующим образом:

Ewjk=Exkxkwjk\frac{\partial E}{\partial w_{jk}} = \frac{\partial E}{\partial x_{k}}\frac{\partial x_k}{\partial w_{jk}}
Exk=Eykykxk=yk(12(ykyk')2)xk(xk)=ykyk'=дельтаk\frac{\partial E}{\partial x_{k}} = \frac{\partial E}{\partial y_{k}} \frac{\partial y_k}{\partial x_{k}} = \frac{\partial}{\partial y_{k}}(\frac{1}{2}(y_k - y^{'}_k)^2)\frac{\partial}{\partial x_k}(x_k) = y_k - y^{'}_k = \delta_k
xkwjk=wjk(yjwjk)=yj\frac{\partial x_k}{\partial w_{jk}} = \frac{\partial}{\partial w_{jk}}(y_jw_{jk}) = y_j

Поэтому коррекция весаΔwjk=εдельтаkyj\Delta w_{jk} = - \varepsilon \delta_k y_j

Для нескольких ассоциаций корректировка веса будет суммой значений корректировки веса для каждой ассоциацииΔwjk=εt=1Tдельтаkyjt\Delta w_{jk} = - \varepsilon \sum_{t=1}^{T}\delta_k y_{jt}

Аналогично, для корректировки веса между скрытыми слоями, продолжая пример выше, корректировка веса между входным слоем и первым скрытым слоем

Δwij=εEwij\Delta w_{ij} = - \varepsilon \frac{\partial E}{\partial w_{ij}}
Ewij=Exjxjwij=дельтаjyi\frac{\partial E}{\partial w_{ij}} = \frac{\partial E}{\partial x_{j}}\frac{\partial x_j}{\partial w_{ij}} = \delta_jy_i

Затем корректировка веса на основе всех ассоциаций представляет собой сумму значений корректировки, рассчитанных для каждой ассоциацииΔwij=εt=1Tдельтаjtyit\Delta w_{ij} = - \varepsilon \sum_{t=1}^{T}\delta_{jt}y_{it}

дельтаj\delta_j рассчитать

Мы здесьдельтаj\delta_jДальнейшее исследование может быть сделано. Выше мы видимEwij=Exjxjwij\frac{\partial E}{\partial w_{ij}} = \frac{\partial E}{\partial x_{j}}\frac{\partial x_j}{\partial w_{ij}}.

за первую половинуExj\frac{\partial E}{\partial x_{j}}, мы можем иметь

Exj=Eyjdyjdxj\frac{\partial E}{\partial x_{j}} = \frac{\partial E}{\partial y_{j}}\frac{d_{y_j}}{d_{x_j}}
Eyj=k=1KExkxkyj=k=1Kдельтаkwjk\frac{\partial E}{\partial y_j} = \sum_{k=1}^K \frac {\partial E}{\partial x_k}\frac {\partial x_k}{\partial y_j} = \sum_{k=1}^K \delta_k w_{jk}

на вторую половинуdyjdxj\frac{d_{y_j}}{d_{x_j}}, поскольку мы используем сигмовидную функцию в этом слое, мы знаем, что производная форма сигмоидальной функцииf(x)(1f(x))f(x)(1-f(x)), следовательно, существует

dyjdxj=f'(xj)=f(xj)(1f(xj))=yj(1yj)\frac{d_{y_j}}{d_{x_j}} = f^{'}(x_j) = f(x_j)(1-f(x_j)) = y_j(1-y_j)

В сумме можно получитьдельтаj\delta_j Формула расчета следующая

дельтаj=yj(1yj)k=1Kдельтаkwjk\delta_j = y_j(1-y_j)\sum_{k=1}^K \delta_k w_{jk}

Краткое описание алгоритма

Во-первых, присвойте небольшое случайное значение значению веса сети.

Повторяйте следующие шаги, пока ошибка не станет равной 0:

  • Для каждой ассоциации пройдите через нейронную сеть, чтобы получить выходное значение.
    • Вычислить ошибку для каждого выходного узла (дельтаk=ykyk'\delta_k = y_k - y^{'}_k​)
    • Стекирование вычисляет градиент каждого выходного веса (wjkE=дельтаkyj\triangledown_{w_{jk}}E = \delta_k y_j)
    • Рассчитать значение каждого узла в скрытом слоедельта\delta(дельтаj=yj(1yj)k=1Kдельтаkwjk\delta_j = y_j(1-y_j)\sum_{k=1}^K \delta_k w_{jk})
    • Stacking вычисляет градиент веса каждого скрытого слоя (wijE=дельтаjyj\triangledown_{w_{ij}}E = \delta_j y_j)
  • Обновить все значения веса, сбросить градиент стека (w=wεEw = w - \varepsilon\triangledown E)

Графическое обратное распространение

В этом примере мы моделируем каждый шаг в нейронной сети с реальными данными. Входное значение — [1,0, 1,0], а ожидаемое выходное значение — [0,5]. Для простоты мы установили вес инициализации равным 0,5 (хотя на практике часто используются случайные значения). Для входного, скрытого и выходного слоев мы используем функцию идентичности, сигмовидную функцию и функцию идентичности в качестве функций активации соответственно, а скорость обученияε\varepsilonустановлен на 0,01.

Forward pass

В начале операции мы устанавливаем входное значение узла входного слоя какx1=1.0,x2=1.0x_1 = 1.0, x_2 = 1.0.

Поскольку мы используем функцию идентификации в качестве функции активации для входного слоя, мы имеемyi=xi=1.0y_i = x_i = 1.0.

Затем мы передаем сеть вперед к слоям J по взвешенной сумме предыдущих слоев следующим образом

xj=i=1Iyiwij=1.0*0.5+1.0*0.5=1.0x_j = \sum_{i=1}^Iy_iw_{ij}= 1.0*0.5+1.0*0.5=1.0

Затем мы вводим значение узла слоя J в сигмовидную функцию (f(x)=11+exf(x)=\frac{1}{1+e^{-x}}будетx=1x=1Подставьте, получите 0,731) для активации.

Наконец, мы передаем этот результат в окончательный выходной слой.

xk=0.731*0.5+0.731*0.5=0.731x_k = 0.731*0.5+0.731*0.5 = 0.731

Поскольку функция активации нашего выходного слоя также является функцией тождества, поэтомуyk=xk=0.731y_k = x_k = 0.731

Backward pass

Первым шагом в обратном распространении является вычисление выходного узла.дельта\delta,

дельтаk=ykyk'=0.7310.5=0.231\delta_k = y_k - y'_k = 0.731-0.5=0.231

использоватьдельта\deltaВычислите весовой градиент между узлами в слоях J и K:wjkE=дельтаkyj=0.231*0.731=0.168861\triangledown_{w_{jk}}E = \delta_ky_j=0.231*0.731=0.168861

Затем таким же образом рассчитайте значение каждого скрытого слоя.дельта\deltaЗначения (в данном примере только один скрытый слой):

дельтаj=yj(1yj)k=1Kдельтаkwjk=0.731*(10.731)*(0.5*0.231)0.0227\delta_j = y_j(1-y_j)\sum_{k=1}^K\delta_kw_{jk}=0.731*(1-0.731)*(0.5*0.231)\approx0.0227

Градиенты, рассчитанные для весов узлов на слоях I и J, следующие:

wijE=дельтаjyi=0.0227*1=0.0227\triangledown_{w_{ij}}E=\delta_jy_i = 0.0227*1=0.0227

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

wij=wijεwijE=0.50.01*0.0227=0.499773w_{ij}=w_{ij}-\varepsilon\triangledown_{w_{ij}}E=0.5-0.01*0.0227=0.499773
wjk=wjkεwjkE=0.50.01*0.168861=0.49831139w_{jk}=w_{jk}-\varepsilon\triangledown_{w_{jk}}E=0.5-0.01*0.168861=0.49831139

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

В первый раз мы получилиy1=0.731y_1 = 0.731, рассчитанный с использованием нового значения весаy20.7285y_2\approx0.7285.

таким образом,y1y1'=0.231y_1-y'_1=0.231y2y2'=0.2285y_2-y'_2=0.2285.

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

пример кода

В этом примере сеть 2X2X1 обучена давать эффект оператора XOR.

x=[1.01.00.00.01.00.01.00.0]x= \left[ \begin{matrix} 1.0 & 1.0 & 0.0 & 0.0 \\ 1.0 & 0.0 & 1.0 & 0.0 \end{matrix} \right]
y'=[0.01.01.00.0]y'= \left[ \begin{matrix} 0.0 & 1.0 & 1.0 & 0.0 \end{matrix} \right]

Здесь f — сигмовидная функция активации скрытого слоя.

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

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

Наконец

Поздравляем с прочтением этой статьи! Вы научились этому?


обрати внимание наHelloGitHubОфициальный аккаунт получил обновление в кратчайшие сроки.

Есть больше представлений о проектах с открытым исходным кодом и ценных проектов, которые ждут вас.