Меню

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

О чём статья

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

Дайте код!

X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ])
y = np.array([[0,1,1,0]]).T
syn0 = 2*np.random.random((3,4)) - 1
syn1 = 2*np.random.random((4,1)) - 1
for j in xrange(60000):
    l1 = 1/(1+np.exp(-(np.dot(X,syn0))))
    l2 = 1/(1+np.exp(-(np.dot(l1,syn1))))
    l2_delta = (y - l2)*(l2*(1-l2))
    l1_delta = l2_delta.dot(syn1.T) * (l1 * (1-l1))
    syn1 += l1.T.dot(l2_delta)
    syn0 += X.T.dot(l1_delta)

Слишком сжато? Давайте разобьём его на более простые части.

Часть 1: Небольшая игрушечная нейросеть

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

Вход  	 		Выход
0 	0 	1 	0
1 	1 	1 	1
1 	0 	1 	1
0 	1 	1 	0

Предположим, нам нужно предсказать, как будет выглядеть колонка «выход» на основе входных данных. Эту задачу можно было бы решить, подсчитав статистическое соответствие между ними. И мы бы увидели, что с выходными данными на 100% коррелирует левый столбец.

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

Нейросеть в два слоя

import numpy as np

# Сигмоида 
def nonlin(x,deriv=False):
    if(deriv==True):
        return f(x)*(1-f(x))
    return 1/(1+np.exp(-x))
    
# набор входных данных
X = np.array([  [0,0,1],
                [0,1,1],
                [1,0,1],
                [1,1,1] ])
    
# выходные данные            
y = np.array([[0,0,1,1]]).T

# сделаем случайные числа более определёнными
np.random.seed(1)

# инициализируем веса случайным образом со средним 0
syn0 = 2*np.random.random((3,1)) - 1

for iter in xrange(10000):

    # прямое распространение
    l0 = X
    l1 = nonlin(np.dot(l0,syn0))

    # насколько мы ошиблись?
    l1_error = y - l1

    # перемножим это с наклоном сигмоиды 
    # на основе значений в l1
    l1_delta = l1_error * nonlin(l1,True) # !!!

    # обновим веса
    syn0 += np.dot(l0.T,l1_delta) # !!!

print "Выходные данные после тренировки:"
print l1
Выходные данные после тренировки:
[[ 0.00966449]
 [ 0.00786506]
 [ 0.99358898]
 [ 0.99211957]]

Переменные и их описания.

X — матрица входного набор данных; строки – тренировочные примеры
y – матрица выходного набора данных; строки – тренировочные примеры
l0 – первый слой сети, определённый входными данными
l1 – второй слой сети, или скрытый слой
syn0 – первый слой весов, Synapse 0, объединяет l0 с l1.
«*» — поэлементное умножение – два вектора одного размера умножают соответствующие значения, и на выходе получается вектор такого же размера
«-» – поэлементное вычитание векторов
x.dot(y) – если x и y – это вектора, то на выходе получится скалярное произведение. Если это матрицы, то получится перемножение матриц. Если матрица только одна из них – это перемножение вектора и матрицы.

И это работает! Рекомендую перед прочтением объяснения поиграться немного с кодом и понять, как он работает. Он должен запускаться прямо как есть, в ipython notebook. С чем можно повозиться в коде:

  • сравните l1 после первой итерации и после последней
  • посмотрите на функцию nonlin.
  • посмотрите, как меняется l1_error
  • разберите строку 36 – основные секретные ингредиенты собраны тут (отмечена !!!)
  • разберите строку 39 – вся сеть готовится именно к этой операции (отмечена !!!)

Разберём код по строчкам

import numpy as np

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

def nonlin(x,deriv=False):

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

image

if(deriv==True):

Эта функция также умеет выдавать производную сигмоиды (deriv=True). Это одно из её полезных свойств. Если выход функции – это переменная out, тогда производная будет out * (1-out). Эффективно.

X = np.array([  [0,0,1], …

Инициализация массива входных данных в виде numpy-матрицы. Каждая строка – тренировочный пример. Столбцы – это входные узлы. У нас получается 3 входных узла в сети и 4 тренировочных примера.

y = np.array([[0,0,1,1]]).T

Инициализирует выходные данные. «.T» – функция переноса. После переноса у матрицы y есть 4 строки с одним столбцом. Как и в случае входных данных, каждая строка – это тренировочный пример, и каждый столбец (в нашем случае один) – выходной узел. У сети, получается, 3 входа и 1 выход.

np.random.seed(1)

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

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

Матрица весов сети. syn0 означает «synapse zero». Так как у нас всего два слоя, вход и выход, нам нужна одна матрица весов, которая их свяжет. Её размерность (3, 1), поскольку у нас есть 3 входа и 1 выход. Иными словами, l0 имеет размер 3, а l1 – 1. Поскольку мы связываем все узлы в l0 со всеми узлами l1, нам требуется матрица размерности (3, 1).

Заметьте, что она инициализируется случайным образом, и среднее значение равно нулю. За этим стоит достаточно сложная теория. Пока просто примем это как рекомендацию. Также заметим, что наша нейросеть – это и есть эта самая матрица. У нас есть «слои» l0 и l1, но они представляют собой временные значения, основанные на наборе данных. Мы их не храним. Всё обучение хранится в syn0.

for iter in xrange(10000):

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

l0 = X

Первый слой, l0, это просто данные. В X содержится 4 тренировочных примера. Мы обработаем их все и сразу – это называется групповой тренировкой [full batch]. Итого мы имеем 4 разных строки l0, но их можно представить себе как один тренировочный пример – на этом этапе это не имеет значения (можно было загрузить их 1000 или 10000 без всяких изменений в коде).

l1 = nonlin(np.dot(l0,syn0))

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

В строке содержится два шага. Первый делает матричное перемножение l0 и syn0. Второй передаёт вывод через сигмоиду. Размерности у них следующие:

(4 x 3) dot (3 x 1) = (4 x 1)

Матричные умножения требуют, чтобы в середине уравнения размерности совпадали. Итоговая матрица имеет количество строк, как у первой, а столбцов – как у второй.

Мы загрузили 4 тренировочных примера, и получили 4 догадки (матрица 4х1). Каждый вывод соответствует догадке сети для данного ввода.

l1_error = y - l1

Поскольку в l1 содержатся догадки, мы можем сравнить их разницу с реальностью, вычитая её l1 из правильного ответа y. l1_error – вектор из положительных и отрицательных чисел, характеризующий «промах» сети.

 l1_delta = l1_error * nonlin(l1,True)

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

Первая часть: производная

nonlin(l1,True)

l1 представляет три этих точки, а код выдаёт наклон линий, показанных ниже. Заметьте, что при больших значениях вроде x=2.0 (зелёная точка) и очень малые, вроде x=-1.0 (фиолетовая) линии имеют небольшой уклон. Самый большой угол у точки х=0 (голубая). Это имеет большое значение. Также отметьте, что все производные лежат в пределах от 0 до 1.

image

Полное выражение: производная, взвешенная по ошибкам

l1_delta = l1_error * nonlin(l1,True)

Математически существуют более точные способы, но в нашем случае подходит и этот. l1_error – это матрица (4,1). nonlin(l1,True) возвращает матрицу (4,1). Здесь мы поэлементно их перемножаем, и на выходе тоже получаем матрицу (4,1), l1_delta.

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

syn0 += np.dot(l0.T,l1_delta)

Мы готовы к обновлению сети. Рассмотрим один тренировочный пример. В нём мы будем обновлять веса. Обновим крайний левый вес (9.5)

image

weight_update = input_value * l1_delta

Для крайнего левого веса это будет 1.0 * l1_delta. Предположительно, это лишь незначительно увеличит 9.5. Почему? Поскольку предсказание было уже достаточно уверенным, и предсказания были практически правильными. Небольшая ошибка и небольшой наклон линии означает очень небольшое обновление.

image

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

Понаблюдав за обновлением сети, вернёмся к нашим тренировочным данным. Когда и вход, и выход равны 1, мы увеличиваем вес между ними. Когда вход 1, а выход – 0, мы уменьшаем вес.

Вход              	Выход
0 	0 	1 	0
1 	1 	1 	1
1 	0 	1 	1
0 	1 	1 	0

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

Часть 2: задачка посложнее

Вход 	        Выход
0 	0 	1 	0
0 	1 	1 	1
1 	0 	1 	1
1 	1 	1 	0

Попробуем предсказать выходные данные на основе трёх входных столбцов данных. Ни один из входных столбцов не коррелирует на 100% с выходным. Третий столбец вообще ни с чем не связан, поскольку в нём всю дорогу содержатся единицы. Однако и тут можно увидеть схему – если в одном из двух первых столбцов (но не в обоих сразу) содержится 1, то результат также будет равен 1.

Это нелинейная схема, поскольку прямого соответствия столбцов один к одному не существует. Соответствие строится на комбинации входных данных, столбцов 1 и 2.

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

image
image

Стратегия

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

Вход  (l0) 	Скрытые веса (l1)	Выход (l2)
0 	0 	1 	0.1 	0.2 	0.5 	0.2 	0
0 	1 	1 	0.2 	0.6 	0.7 	0.1 	1
1 	0 	1 	0.3 	0.2 	0.3 	0.9 	1
1 	1 	1 	0.2 	0.1 	0.3 	0.8 	0

Случайным образом назначив веса, мы получим скрытые значения для слоя №1. Интересно, что у второго столбца скрытых весов уже есть небольшая корреляция с выходом. Не идеальная, но есть. И это тоже является важной частью процесса тренировки сети. Тренировка будет только усиливать эту корреляцию. Она будет обновлять syn1, чтобы назначить её соответствие выходным данным, и syn0, чтобы лучше получать данные со входа.

Нейросеть в три слоя

import numpy as np

def nonlin(x,deriv=False):
	if(deriv==True):
           return f(x)*(1-f(x))

	return 1/(1+np.exp(-x))
    
X = np.array([[0,0,1],
            [0,1,1],
            [1,0,1],
            [1,1,1]])
                
y = np.array([[0],
			[1],
			[1],
			[0]])

np.random.seed(1)

# случайно инициализируем веса, в среднем - 0
syn0 = 2*np.random.random((3,4)) - 1
syn1 = 2*np.random.random((4,1)) - 1

for j in xrange(60000):

	# проходим вперёд по слоям 0, 1 и 2
    l0 = X
    l1 = nonlin(np.dot(l0,syn0))
    l2 = nonlin(np.dot(l1,syn1))

    # как сильно мы ошиблись относительно нужной величины?
    l2_error = y - l2
    
    if (j% 10000) == 0:
        print "Error:" + str(np.mean(np.abs(l2_error)))
        
    # в какую сторону нужно двигаться?
    # если мы были уверены в предсказании, то сильно менять его не надо
    l2_delta = l2_error*nonlin(l2,deriv=True)

    # как сильно значения l1 влияют на ошибки в l2?
    l1_error = l2_delta.dot(syn1.T)
    
    # в каком направлении нужно двигаться, чтобы прийти к l1?
    # если мы были уверены в предсказании, то сильно менять его не надо
    l1_delta = l1_error * nonlin(l1,deriv=True)

    syn1 += l1.T.dot(l2_delta)
    syn0 += l0.T.dot(l1_delta)
Error:0.496410031903
Error:0.00858452565325
Error:0.00578945986251
Error:0.00462917677677
Error:0.00395876528027
Error:0.00351012256786

Переменные и их описания

X — матрица входного набор данных; строки – тренировочные примеры
y – матрица выходного набора данных; строки – тренировочные примеры
l0 – первый слой сети, определённый входными данными
l1 – второй слой сети, или скрытый слой
l2 – финальный слой, это наша гипотеза. По мере тренировки должен приближаться к правильному ответу
syn0 – первый слой весов, Synapse 0, объединяет l0 с l1.
syn1 – второй слой весов, Synapse 1, объединяет l1 с l2.
l2_error – промах сети в количественном выражении
l2_delta – ошибка сети, в зависимости от уверенности предсказания. Почти совпадает с ошибкой, за исключением уверенных предсказаний
l1_error – взвешивая l2_delta весами из syn1, мы подсчитываем ошибку в среднем/скрытом слое
l1_delta – ошибки сети из l1, масштабируемые по увеернности предсказаний. Почти совпадает с l1_error, за исключением уверенных предсказаний

Код должен быть достаточно понятным – это просто предыдущая реализация сети, сложенная в два слоя один над другим. Выход первого слоя l1 – это вход второго слоя. Что-то новое есть лишь в следующей строке.

l1_error = l2_delta.dot(syn1.T)

Использует ошибки, взвешенные по уверенности предсказаний из l2, чтобы подсчитать ошибку для l1. Получаем, можно сказать, ошибку, взвешенную по вкладам – мы подсчитываем, какой вклад в ошибки в l2 вносят значения в узлах l1. Этот шаг и называется обратным распространением ошибок. Затем мы обновляем syn0, используя тот же алгоритм, что и в варианте с нейросетью из двух слоёв.


  Перевод


  Ссылка на автора

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

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

В этом руководстве вы узнаете, как реализовать алгоритм обратного распространения с нуля с помощью Python.

После завершения этого урока вы узнаете:

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

Давайте начнем.

  • Обновление ноябрь 2016: Исправлена ​​ошибка в функции activ (). Спасибо Алекс!
  • Обновление январь / 2017: Изменено вычисление fold_size в cross_validation_split (), чтобы оно всегда было целым числом. Исправляет проблемы с Python 3.
  • Обновление январь / 2017: Обновлена ​​небольшая ошибка в update_weights (). Спасибо, Томаш!
  • Обновление апрель / 2018: Добавлена ​​прямая ссылка на набор данных CSV.
  • Обновление Авг / 2018: Протестировано и обновлено для работы с Python 3.6.

Описание

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

Алгоритм обратного распространения

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

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

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

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

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

В задачах классификации наилучшие результаты достигаются, когда сеть имеет один нейрон в выходном слое для каждого значения класса. Например, проблема 2-классовой или двоичной классификации со значениями классов A и B. Эти ожидаемые результаты должны быть преобразованы в двоичные векторы с одним столбцом для каждого значения класса. Например, [1, 0] и [0, 1] для A и B соответственно. Это называется горячим кодированием.

Набор данных семян пшеницы

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

Есть 201 записей и 7 числовых входных переменных. Это проблема классификации с 3 выходными классами. Шкала для каждого числового входного значения варьируется, поэтому может потребоваться некоторая нормализация данных для использования с алгоритмами, которые взвешивают входные данные, такие как алгоритм обратного распространения.

Ниже приведен образец первых 5 строк набора данных.

15.26,14.84,0.871,5.763,3.312,2.221,5.22,1
14.88,14.57,0.8811,5.554,3.333,1.018,4.956,1
14.29,14.09,0.905,5.291,3.337,2.699,4.825,1
13.84,13.94,0.8955,5.324,3.379,2.259,4.805,1
16.14,14.99,0.9034,5.658,3.562,1.355,5.175,1

При использовании алгоритма нулевого правила, который прогнозирует наиболее распространенное значение класса, базовая точность задачи составляет 28,095%.

Вы можете узнать больше и загрузить набор данных семян из UCI Хранилище Машинного Обучения,

Загрузите набор данных seed и поместите его в текущий рабочий каталог с именем файлаseeds_dataset.csv,

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

Обновите, загрузите набор данных в формате CSV напрямую:

  • Скачать набор данных семян пшеницы

Руководство

Этот урок разбит на 6 частей:

  1. Инициализировать сеть.
  2. Вперед Распространять.
  3. Ошибка обратного распространения.
  4. Сеть поездов.
  5. Предсказать.
  6. Пример набора данных семян.

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

1. Инициализировать сеть

Давайте начнем с чего-то простого, создания новой сети, готовой к обучению.

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

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

Мы организуем слои как массивы словарей и будем рассматривать всю сеть как массив слоев.

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

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

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

Вы также можете видеть, что выходной слой, который подключается к скрытому слою, имеетn_outputsнейроны, каждый сn_hidden + 1веса. Это означает, что каждый нейрон в выходном слое соединяется (имеет вес) с каждым нейроном в скрытом слое.

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
	network = list()
	hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
	network.append(hidden_layer)
	output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
	network.append(output_layer)
	return network

Давайте проверим эту функцию. Ниже приведен полный пример, который создает небольшую сеть.

from random import seed
from random import random

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
	network = list()
	hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
	network.append(hidden_layer)
	output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
	network.append(output_layer)
	return network

seed(1)
network = initialize_network(2, 1, 2)
for layer in network:
	print(layer)

Запустив пример, вы можете увидеть, что код распечатывает каждый слой по одному. Вы можете видеть, что скрытый слой имеет один нейрон с 2 входными весами плюс смещение. Выходной слой имеет 2 нейрона, каждый с 1 весом плюс смещение.

[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}]
[{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]

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

2. Вперед Распространение

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

Мы называем это продвижением вперед.

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

Мы можем разбить распространение вперед на три части:

  1. Активация нейронов.
  2. Передача нейронов.
  3. Вперед Распространение.

2.1. Активация нейронов

Первым шагом является вычисление активации одного нейрона с учетом входных данных.

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

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

activation = sum(weight_i * input_i) + bias

кудавесвес сети,входявляется входом,яэто индекс веса или ввода исмещениеэто специальный вес, который не имеет входных данных для умножения (или вы можете думать, что входные данные всегда равны 1,0).

Ниже приведена реализация этого в функции с именемактивировать (), Вы можете видеть, что функция предполагает, что смещение является последним весом в списке весов. Это помогает здесь и позже сделать код легче для чтения.

# Calculate neuron activation for an input
def activate(weights, inputs):
	activation = weights[-1]
	for i in range(len(weights)-1):
		activation += weights[i] * inputs[i]
	return activation

Теперь давайте посмотрим, как использовать активацию нейронов.

2.2. Нейрон Трансфер

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

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

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

Мы можем передать функцию активации с помощью функции сигмоида следующим образом:

output = 1 / (1 + e^(-activation))

кудаеявляется основанием натуральных логарифмов (Номер Эйлера).

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

# Transfer neuron activation
def transfer(activation):
	return 1.0 / (1.0 + exp(-activation))

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

2,3. Прямое распространение

Вперед, распространение входных данных просто.

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

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

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

Функция возвращает выходные данные из последнего слоя, также называемого выходным слоем.

# Forward propagate input to a network output
def forward_propagate(network, row):
	inputs = row
	for layer in network:
		new_inputs = []
		for neuron in layer:
			activation = activate(neuron['weights'], inputs)
			neuron['output'] = transfer(activation)
			new_inputs.append(neuron['output'])
		inputs = new_inputs
	return inputs

Давайте соединим все эти части и протестируем прямое распространение нашей сети.

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

from math import exp

# Calculate neuron activation for an input
def activate(weights, inputs):
	activation = weights[-1]
	for i in range(len(weights)-1):
		activation += weights[i] * inputs[i]
	return activation

# Transfer neuron activation
def transfer(activation):
	return 1.0 / (1.0 + exp(-activation))

# Forward propagate input to a network output
def forward_propagate(network, row):
	inputs = row
	for layer in network:
		new_inputs = []
		for neuron in layer:
			activation = activate(neuron['weights'], inputs)
			neuron['output'] = transfer(activation)
			new_inputs.append(neuron['output'])
		inputs = new_inputs
	return inputs

# test forward propagation
network = [[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
		[{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]]
row = [1, 0, None]
output = forward_propagate(network, row)
print(output)

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

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

[0.6629970129852887, 0.7253160725279748]

3. Ошибка обратного распространения

Алгоритм обратного распространения назван по способу обучения весов.

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

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

Эта часть разбита на две части.

  1. Передача Производная.
  2. Ошибка обратного распространения.

3.1. Производная передача

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

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

derivative = output * (1.0 - output)

Ниже приведена функция с именемtransfer_derivative ()который реализует это уравнение.

# Calculate the derivative of an neuron output
def transfer_derivative(output):
	return output * (1.0 - output)

Теперь посмотрим, как это можно использовать.

3.2. Ошибка обратного распространения

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

Ошибка для данного нейрона может быть рассчитана следующим образом:

error = (expected - output) * transfer_derivative(output)

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

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

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

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

error = (weight_k * error_j) * transfer_derivative(output)

кудаerror_jэто сигнал ошибки отJй нейрон в выходном слое,weight_kэто вес, который соединяетКТретий нейрон к текущему нейрону и выход — это выход для текущего нейрона.

Ниже приведена функция с именемbackward_propagate_error ()который реализует эту процедуру.

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

Вы можете видеть, что сигнал ошибки для нейронов в скрытом слое накапливается от нейронов в выходном слое, где скрыт номер нейронаJтакже индекс веса нейрона в выходном слоенейрон [грузики ‘] [J],

# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
	for i in reversed(range(len(network))):
		layer = network[i]
		errors = list()
		if i != len(network)-1:
			for j in range(len(layer)):
				error = 0.0
				for neuron in network[i + 1]:
					error += (neuron['weights'][j] * neuron['delta'])
				errors.append(error)
		else:
			for j in range(len(layer)):
				neuron = layer[j]
				errors.append(expected[j] - neuron['output'])
		for j in range(len(layer)):
			neuron = layer[j]
			neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])

Давайте соберем все части вместе и посмотрим, как это работает.

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

# Calculate the derivative of an neuron output
def transfer_derivative(output):
	return output * (1.0 - output)

# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
	for i in reversed(range(len(network))):
		layer = network[i]
		errors = list()
		if i != len(network)-1:
			for j in range(len(layer)):
				error = 0.0
				for neuron in network[i + 1]:
					error += (neuron['weights'][j] * neuron['delta'])
				errors.append(error)
		else:
			for j in range(len(layer)):
				neuron = layer[j]
				errors.append(expected[j] - neuron['output'])
		for j in range(len(layer)):
			neuron = layer[j]
			neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])

# test backpropagation of error
network = [[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
		[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095]}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763]}]]
expected = [0, 1]
backward_propagate_error(network, expected)
for layer in network:
	print(layer)

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

[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614], 'delta': -0.0005348048046610517}]
[{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095], 'delta': -0.14619064683582808}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763], 'delta': 0.0771723774346327}]

Теперь давайте использовать обратное распространение ошибки для обучения сети.

4. Сеть поездов

Сеть обучается с использованием стохастического градиентного спуска.

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

Эта часть разбита на две части:

  1. Обновление весов.
  2. Сеть поездов.

4.1. Обновление весов

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

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

weight = weight + learning_rate * error * input

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

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

Скорость обучения определяет, насколько изменить вес, чтобы исправить ошибку. Например, значение 0,1 обновит вес на 10% от суммы, которую он мог бы обновить. Предпочтительными являются малые скорости обучения, которые вызывают более медленное обучение в течение большого количества итераций обучения. Это увеличивает вероятность того, что сеть найдет хороший набор весов на всех уровнях, а не самый быстрый набор весов, которые минимизируют ошибку (так называемая преждевременная сходимость).

Ниже приведена функция с именемupdate_weights ()который обновляет весовые коэффициенты для сети с учетом входной строки данных, скорости обучения и предполагает, что прямое и обратное распространение уже выполнено.

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

# Update network weights with error
def update_weights(network, row, l_rate):
	for i in range(len(network)):
		inputs = row[:-1]
		if i != 0:
			inputs = [neuron['output'] for neuron in network[i - 1]]
		for neuron in network[i]:
			for j in range(len(inputs)):
				neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
			neuron['weights'][-1] += l_rate * neuron['delta']

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

4.2. Сеть поездов

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

Это включает в себя первый цикл для фиксированного числа эпох и в каждой эпохе обновление сети для каждой строки в наборе обучающих данных.

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

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

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

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

# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):
	for epoch in range(n_epoch):
		sum_error = 0
		for row in train:
			outputs = forward_propagate(network, row)
			expected = [0 for i in range(n_outputs)]
			expected[row[-1]] = 1
			sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])
			backward_propagate_error(network, expected)
			update_weights(network, row, l_rate)
		print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))

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

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

X1			X2			Y
2.7810836		2.550537003		0
1.465489372		2.362125076		0
3.396561688		4.400293529		0
1.38807019		1.850220317		0
3.06407232		3.005305973		0
7.627531214		2.759262235		1
5.332441248		2.088626775		1
6.922596716		1.77106367		1
8.675418651		-0.242068655		1
7.673756466		3.508563011		1

Ниже приведен полный пример. Мы будем использовать 2 нейрона в скрытом слое. Это проблема двоичной классификации (2 класса), поэтому в выходном слое будет два нейрона. Сеть будет обучаться в течение 20 эпох со скоростью обучения 0,5, что является высоким показателем, потому что мы готовим так мало итераций.

from math import exp
from random import seed
from random import random

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
	network = list()
	hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
	network.append(hidden_layer)
	output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
	network.append(output_layer)
	return network

# Calculate neuron activation for an input
def activate(weights, inputs):
	activation = weights[-1]
	for i in range(len(weights)-1):
		activation += weights[i] * inputs[i]
	return activation

# Transfer neuron activation
def transfer(activation):
	return 1.0 / (1.0 + exp(-activation))

# Forward propagate input to a network output
def forward_propagate(network, row):
	inputs = row
	for layer in network:
		new_inputs = []
		for neuron in layer:
			activation = activate(neuron['weights'], inputs)
			neuron['output'] = transfer(activation)
			new_inputs.append(neuron['output'])
		inputs = new_inputs
	return inputs

# Calculate the derivative of an neuron output
def transfer_derivative(output):
	return output * (1.0 - output)

# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
	for i in reversed(range(len(network))):
		layer = network[i]
		errors = list()
		if i != len(network)-1:
			for j in range(len(layer)):
				error = 0.0
				for neuron in network[i + 1]:
					error += (neuron['weights'][j] * neuron['delta'])
				errors.append(error)
		else:
			for j in range(len(layer)):
				neuron = layer[j]
				errors.append(expected[j] - neuron['output'])
		for j in range(len(layer)):
			neuron = layer[j]
			neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])

# Update network weights with error
def update_weights(network, row, l_rate):
	for i in range(len(network)):
		inputs = row[:-1]
		if i != 0:
			inputs = [neuron['output'] for neuron in network[i - 1]]
		for neuron in network[i]:
			for j in range(len(inputs)):
				neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
			neuron['weights'][-1] += l_rate * neuron['delta']

# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):
	for epoch in range(n_epoch):
		sum_error = 0
		for row in train:
			outputs = forward_propagate(network, row)
			expected = [0 for i in range(n_outputs)]
			expected[row[-1]] = 1
			sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])
			backward_propagate_error(network, expected)
			update_weights(network, row, l_rate)
		print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))

# Test training backprop algorithm
seed(1)
dataset = [[2.7810836,2.550537003,0],
	[1.465489372,2.362125076,0],
	[3.396561688,4.400293529,0],
	[1.38807019,1.850220317,0],
	[3.06407232,3.005305973,0],
	[7.627531214,2.759262235,1],
	[5.332441248,2.088626775,1],
	[6.922596716,1.77106367,1],
	[8.675418651,-0.242068655,1],
	[7.673756466,3.508563011,1]]
n_inputs = len(dataset[0]) - 1
n_outputs = len(set([row[-1] for row in dataset]))
network = initialize_network(n_inputs, 2, n_outputs)
train_network(network, dataset, 0.5, 20, n_outputs)
for layer in network:
	print(layer)

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

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

>epoch=0, lrate=0.500, error=6.350
>epoch=1, lrate=0.500, error=5.531
>epoch=2, lrate=0.500, error=5.221
>epoch=3, lrate=0.500, error=4.951
>epoch=4, lrate=0.500, error=4.519
>epoch=5, lrate=0.500, error=4.173
>epoch=6, lrate=0.500, error=3.835
>epoch=7, lrate=0.500, error=3.506
>epoch=8, lrate=0.500, error=3.192
>epoch=9, lrate=0.500, error=2.898
>epoch=10, lrate=0.500, error=2.626
>epoch=11, lrate=0.500, error=2.377
>epoch=12, lrate=0.500, error=2.153
>epoch=13, lrate=0.500, error=1.953
>epoch=14, lrate=0.500, error=1.774
>epoch=15, lrate=0.500, error=1.614
>epoch=16, lrate=0.500, error=1.472
>epoch=17, lrate=0.500, error=1.346
>epoch=18, lrate=0.500, error=1.233
>epoch=19, lrate=0.500, error=1.132
[{'weights': [-1.4688375095432327, 1.850887325439514, 1.0858178629550297], 'output': 0.029980305604426185, 'delta': -0.0059546604162323625}, {'weights': [0.37711098142462157, -0.0625909894552989, 0.2765123702642716], 'output': 0.9456229000211323, 'delta': 0.0026279652850863837}]
[{'weights': [2.515394649397849, -0.3391927502445985, -0.9671565426390275], 'output': 0.23648794202357587, 'delta': -0.04270059278364587}, {'weights': [-2.5584149848484263, 1.0036422106209202, 0.42383086467582715], 'output': 0.7790535202438367, 'delta': 0.03803132596437354}]

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

5. Предсказать

Делать прогнозы с помощью обученной нейронной сети достаточно просто.

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

Возможно, было бы более полезно превратить этот вывод в четкое предсказание класса. Мы можем сделать это, выбрав значение класса с большей вероятностью. Это также называется функция arg max,

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

# Make a prediction with a network
def predict(network, row):
	outputs = forward_propagate(network, row)
	return outputs.index(max(outputs))

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

Полный пример приведен ниже.

from math import exp

# Calculate neuron activation for an input
def activate(weights, inputs):
	activation = weights[-1]
	for i in range(len(weights)-1):
		activation += weights[i] * inputs[i]
	return activation

# Transfer neuron activation
def transfer(activation):
	return 1.0 / (1.0 + exp(-activation))

# Forward propagate input to a network output
def forward_propagate(network, row):
	inputs = row
	for layer in network:
		new_inputs = []
		for neuron in layer:
			activation = activate(neuron['weights'], inputs)
			neuron['output'] = transfer(activation)
			new_inputs.append(neuron['output'])
		inputs = new_inputs
	return inputs

# Make a prediction with a network
def predict(network, row):
	outputs = forward_propagate(network, row)
	return outputs.index(max(outputs))

# Test making predictions with the network
dataset = [[2.7810836,2.550537003,0],
	[1.465489372,2.362125076,0],
	[3.396561688,4.400293529,0],
	[1.38807019,1.850220317,0],
	[3.06407232,3.005305973,0],
	[7.627531214,2.759262235,1],
	[5.332441248,2.088626775,1],
	[6.922596716,1.77106367,1],
	[8.675418651,-0.242068655,1],
	[7.673756466,3.508563011,1]]
network = [[{'weights': [-1.482313569067226, 1.8308790073202204, 1.078381922048799]}, {'weights': [0.23244990332399884, 0.3621998343835864, 0.40289821191094327]}],
	[{'weights': [2.5001872433501404, 0.7887233511355132, -1.1026649757805829]}, {'weights': [-2.429350576245497, 0.8357651039198697, 1.0699217181280656]}]]
for row in dataset:
	prediction = predict(network, row)
	print('Expected=%d, Got=%d' % (row[-1], prediction))

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

Это показывает, что сеть достигает 100% точности в этом небольшом наборе данных.

Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=0, Got=0
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1
Expected=1, Got=1

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

6. Набор данных семян пшеницы

В этом разделе применяется алгоритм обратного распространения к набору данных семян пшеницы.

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

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

Мы оценим алгоритм с использованием k-кратной перекрестной проверки с 5-кратным увеличением. Это означает, что 201/5 = 40,2 или 40 записей будут в каждом сгибе. Мы будем использовать вспомогательные функцииevaluate_algorithm ()оценить алгоритм с перекрестной проверкой иaccuracy_metric ()рассчитать точность прогнозов.

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

Полный пример приведен ниже.

# Backprop on the Seeds Dataset
from random import seed
from random import randrange
from random import random
from csv import reader
from math import exp

# Load a CSV file
def load_csv(filename):
	dataset = list()
	with open(filename, 'r') as file:
		csv_reader = reader(file)
		for row in csv_reader:
			if not row:
				continue
			dataset.append(row)
	return dataset

# Convert string column to float
def str_column_to_float(dataset, column):
	for row in dataset:
		row[column] = float(row[column].strip())

# Convert string column to integer
def str_column_to_int(dataset, column):
	class_values = [row[column] for row in dataset]
	unique = set(class_values)
	lookup = dict()
	for i, value in enumerate(unique):
		lookup[value] = i
	for row in dataset:
		row[column] = lookup[row[column]]
	return lookup

# Find the min and max values for each column
def dataset_minmax(dataset):
	minmax = list()
	stats = [[min(column), max(column)] for column in zip(*dataset)]
	return stats

# Rescale dataset columns to the range 0-1
def normalize_dataset(dataset, minmax):
	for row in dataset:
		for i in range(len(row)-1):
			row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0])

# Split a dataset into k folds
def cross_validation_split(dataset, n_folds):
	dataset_split = list()
	dataset_copy = list(dataset)
	fold_size = int(len(dataset) / n_folds)
	for i in range(n_folds):
		fold = list()
		while len(fold) < fold_size:
			index = randrange(len(dataset_copy))
			fold.append(dataset_copy.pop(index))
		dataset_split.append(fold)
	return dataset_split

# Calculate accuracy percentage
def accuracy_metric(actual, predicted):
	correct = 0
	for i in range(len(actual)):
		if actual[i] == predicted[i]:
			correct += 1
	return correct / float(len(actual)) * 100.0

# Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):
	folds = cross_validation_split(dataset, n_folds)
	scores = list()
	for fold in folds:
		train_set = list(folds)
		train_set.remove(fold)
		train_set = sum(train_set, [])
		test_set = list()
		for row in fold:
			row_copy = list(row)
			test_set.append(row_copy)
			row_copy[-1] = None
		predicted = algorithm(train_set, test_set, *args)
		actual = [row[-1] for row in fold]
		accuracy = accuracy_metric(actual, predicted)
		scores.append(accuracy)
	return scores

# Calculate neuron activation for an input
def activate(weights, inputs):
	activation = weights[-1]
	for i in range(len(weights)-1):
		activation += weights[i] * inputs[i]
	return activation

# Transfer neuron activation
def transfer(activation):
	return 1.0 / (1.0 + exp(-activation))

# Forward propagate input to a network output
def forward_propagate(network, row):
	inputs = row
	for layer in network:
		new_inputs = []
		for neuron in layer:
			activation = activate(neuron['weights'], inputs)
			neuron['output'] = transfer(activation)
			new_inputs.append(neuron['output'])
		inputs = new_inputs
	return inputs

# Calculate the derivative of an neuron output
def transfer_derivative(output):
	return output * (1.0 - output)

# Backpropagate error and store in neurons
def backward_propagate_error(network, expected):
	for i in reversed(range(len(network))):
		layer = network[i]
		errors = list()
		if i != len(network)-1:
			for j in range(len(layer)):
				error = 0.0
				for neuron in network[i + 1]:
					error += (neuron['weights'][j] * neuron['delta'])
				errors.append(error)
		else:
			for j in range(len(layer)):
				neuron = layer[j]
				errors.append(expected[j] - neuron['output'])
		for j in range(len(layer)):
			neuron = layer[j]
			neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])

# Update network weights with error
def update_weights(network, row, l_rate):
	for i in range(len(network)):
		inputs = row[:-1]
		if i != 0:
			inputs = [neuron['output'] for neuron in network[i - 1]]
		for neuron in network[i]:
			for j in range(len(inputs)):
				neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
			neuron['weights'][-1] += l_rate * neuron['delta']

# Train a network for a fixed number of epochs
def train_network(network, train, l_rate, n_epoch, n_outputs):
	for epoch in range(n_epoch):
		for row in train:
			outputs = forward_propagate(network, row)
			expected = [0 for i in range(n_outputs)]
			expected[row[-1]] = 1
			backward_propagate_error(network, expected)
			update_weights(network, row, l_rate)

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
	network = list()
	hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
	network.append(hidden_layer)
	output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
	network.append(output_layer)
	return network

# Make a prediction with a network
def predict(network, row):
	outputs = forward_propagate(network, row)
	return outputs.index(max(outputs))

# Backpropagation Algorithm With Stochastic Gradient Descent
def back_propagation(train, test, l_rate, n_epoch, n_hidden):
	n_inputs = len(train[0]) - 1
	n_outputs = len(set([row[-1] for row in train]))
	network = initialize_network(n_inputs, n_hidden, n_outputs)
	train_network(network, train, l_rate, n_epoch, n_outputs)
	predictions = list()
	for row in test:
		prediction = predict(network, row)
		predictions.append(prediction)
	return(predictions)

# Test Backprop on Seeds dataset
seed(1)
# load and prepare data
filename = 'seeds_dataset.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])-1):
	str_column_to_float(dataset, i)
# convert class column to integers
str_column_to_int(dataset, len(dataset[0])-1)
# normalize input variables
minmax = dataset_minmax(dataset)
normalize_dataset(dataset, minmax)
# evaluate algorithm
n_folds = 5
l_rate = 0.3
n_epoch = 500
n_hidden = 5
scores = evaluate_algorithm(dataset, back_propagation, n_folds, l_rate, n_epoch, n_hidden)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

Была построена сеть с 5 нейронами в скрытом слое и 3 нейронами в выходном слое. Сеть была подготовлена ​​для 500 эпох с темпом обучения 0,3. Эти параметры были найдены с небольшой пробой и ошибкой, но вы можете сделать это намного лучше.

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

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

Scores: [92.85714285714286, 92.85714285714286, 97.61904761904762, 92.85714285714286, 90.47619047619048]
Mean Accuracy: 93.333%

расширения

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

  • Параметры алгоритма настройки, Попробуйте большие или меньшие сети, обученные дольше или короче. Посмотрите, сможете ли вы улучшить производительность набора данных seed.
  • Дополнительные методы, Поэкспериментируйте с различными методами инициализации веса (такими как небольшие случайные числа) и различными передаточными функциями (такими как tanh).
  • Больше слоев, Добавьте поддержку для большего количества скрытых слоев, обученных так же, как один скрытый слой, используемый в этом руководстве.
  • регрессия, Измените сеть так, чтобы в выходном слое был только один нейрон, и чтобы было предсказано реальное значение. Выберите регрессионный набор данных для практики. Линейная передаточная функция может использоваться для нейронов в выходном слое, или выходные значения выбранного набора данных могут быть масштабированы до значений между 0 и 1.
  • Пакетный градиентный спуск, Измените процедуру обучения с онлайн на пакетный градиентный спуск и обновляйте веса только в конце каждой эпохи.

Вы пробовали какие-либо из этих расширений?
Поделитесь своим опытом в комментариях ниже.

Обзор

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

В частности, вы узнали:

  • Как переслать распространение входа для расчета выхода сети.
  • Как обратно распространять ошибки и обновлять вес сети.
  • Как применить алгоритм обратного распространения к реальному набору данных.

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

Введение

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

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

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

Эта встроенная интеграция MQL5 в Python открывает для нас много возможностей, которые позволяют построить от простой линейной регрессии до моделей глубокого обучения. Поскольку это язык для профессионального использования, существует множество библиотек, которые берут на себя всю тяжелую работу, связанную с вычислениями.

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

Что такое TensorFlow?

TensorFlow — это библиотека с открытым кодом для быстрой числовой обработки.

Он был создан, поддержан и выпущен Google под лицензией Apache 2.0 с открытым кодом. API виртуально предназначен для языка программирования Python, хотя есть доступ к базовому C++ API.

В отличие от других числовых библиотек, предназначенных для использования в глубоком обучении, таких как Theano, TensorFlow предназначен для использования как в научно-исследовательских, так и в системах разработки и производства, особенно RankBrain в поисковой системе Google и в интересном проекте DeepDream.

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

Что такое Keras?

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

Он охватывает эффективные библиотеки вычислений Theano и TensorFlow и позволяет определять и обучать модели нейронных сетей всего за несколько строк кода.

Руководство

Это руководство разделено на 4 части:

  1. Установка и подготовка среды Python в MetaEditor.
  2. Первые шаги и реконструкция модели (перцептрон и MLP).
  3. Создание простой модели с использованием Keras и TensorFlow.
  4. Как интегрировать MQL5 в Python.

1. Установка и подготовка среды Python.

Начнем с загрузки Python с официального сайта www.python.org/downloads/

Чтобы работать с TensorFlow необходимо установить версию выше 3.3 и ниже 3.8; я использую версию 3.7.

После загрузки и начала процесса установки мы активируем опцию «Добавить Python 3.7 в PATH», это гарантирует, что некоторые вещи будут работать без необходимости дополнительной настройки в будущем.

Чтобы запустить скрипт Python прямо из нашего терминала MetaTrader 5, нам нужно только выполнить предыдущую настройку.

  • Установить путь исполняемого файла Python (среда)
  • Установите необходимые зависимости для проекта 

Нам нужно открыть MetaEditor и перейти в Инструменты>Параметры.

В данном этапе нам нужно определить путь, на котором находится наш исполняемый файл Python, и иметь в виду, что после установки он, вероятно, будет содержать путь Python по умолчанию, если по какой-то причине это не так, то введите полный путь к исполняемому файлу, чтобы вы могли запустить скрипты прямо из вашего терминала MetaTrader 5.

1 - Настройка компиляторов

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

Для получения дополнительной информации о пакете venv перейдите по ссылке.

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

Если у вас есть вопросы о том, как установить библиотеки, смотрите руководство по установке модуля.

  • MetaTrader 5
  • TensorFlow
  • Matplotlib
  • Pandas
  • Sklearn

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

Новый > Python script

1 - Новый скрипт

Определите имя для вашего скрипта, мастер создания MetaEditor подсказывает нам автоматически импортировать некоторые библиотеки, это очень интересно и для нашего эксперимента мы выберем вариант numpy.

3 - Новый скрипт II

Теперь давайте создадим простой скрипт, который генерирует синусоидальный график.

# Copyright 2021, Lethan Corp.
# https:

import numpy as np
import matplotlib.pyplot as plt

data = np.linspace(-np.pi, np.pi, 201)
plt.plot(data, np.sin(data))
plt.xlabel('Angle [rad]')
plt.ylabel('sin(data)')
plt.axis('tight')
plt.show()

Чтобы запустить скрипт, просто нажмите на компилировать (F7) или откройте терминал MetaTrader 5 и прикрепите скрипт к графику. После этого результаты будут отображаться во вкладке экспертов в случае, если есть распечатки, как при разработке алгоритма MQL5, в нашем случае откроется окно с графиком функции, которую мы создали выше.

3 - Синусоидальный график

2 — Первые шаги и реконструкция модели (перцептрон и MLP).

Мы будем использовать тот же набор данных, что и пример в MQL5, чтобы было проще.

Затем мы увидим функцию под названием predict(), которая прогнозирует выходное значение для строки с заданным набором весов, в этом случае первым весом всегда будет смещение. А также функция активации.

# Transfer neuron activation
def activation(activation):
    return 1.0 if activation >= 0.0 else 0.0

# Make a prediction with weights
def predict(row, weights):
    z = weights[0]
    for i in range(len(row) - 1):
        z += weights[i + 1] * row[i]
    return activation(z)

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

# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
    weights = [0.0 for i in range(len(train[0]))]  #random.random()
    for epoch in range(n_epoch):
        sum_error = 0.0
        for row in train:
            y = predict(row, weights)
            error = row[-1] - y
            sum_error += error**2
            weights[0] = weights[0] + l_rate * error

            for i in range(len(row) - 1):
                weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
        print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
    return weights

Применение модели MLP:

Это руководство разделено на 5 частей:

  • Запуск сети
  • Распространение (FeedForward)
  • Обратное распространение (BackPropagation)
  • Обучение
  • Прогноз

Запуск сети:

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

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

from random import seed
from random import random

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
    network = list()
    hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
    network.append(hidden_layer)
    output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
    network.append(output_layer)
    return network

seed(1)
network = initialize_network(2, 1, 2)
for layer in network:
    print(layer)

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

    Распространение (FeedForward)

    from math import exp
    
    # Calculate neuron activation for an input
    def activate(weights, inputs):
        activation = weights[-1]
        for i in range(len(weights)-1):
            activation += weights[i] * inputs[i]
        return activation
    
    # Transfer neuron activation
    def transfer(activation):
        return 1.0 / (1.0 + exp(-activation))
    
    # Forward propagate input to a network output
    def forward_propagate(network, row):
        inputs = row
        for layer in network:
            new_inputs = []
            for neuron in layer:
                activation = activate(neuron['weights'], inputs)
                neuron['output'] = transfer(activation)
                new_inputs.append(neuron['output'])
            inputs = new_inputs
        return inputs
    
    # test forward propagation
    network = [[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
            [{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]]
    row = [1, 0, None]
    output = forward_propagate(network, row)
    print(output)

    При выполнении приведенного выше скрипта мы получим результат:

    [0.6629970129852887, 0.7253160725279748]

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

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

    # Calculate the derivative of an neuron output
    def transfer_derivative(output):
        return output * (1.0 - output)
    
    # Backpropagate error and store in neurons
    def backward_propagate_error(network, expected):
        for i in reversed(range(len(network))):
            layer = network[i]
            errors = list()
            if i != len(network)-1:
                for j in range(len(layer)):
                    error = 0.0
                    for neuron in network[i + 1]:
                        error += (neuron['weights'][j] * neuron['delta'])
                    errors.append(error)
            else:
                for j in range(len(layer)):
                    neuron = layer[j]
                    errors.append(expected[j] - neuron['output'])
            for j in range(len(layer)):
                neuron = layer[j]
                neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
    
    # test backpropagation of error
    network = [[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
              [{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095]}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763]}]]
    expected = [0, 1]
    backward_propagate_error(network, expected)
    for layer in network:
        print(layer)
    

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

    [{‘output’: 0.7105668883115941, ‘weights’: [0.13436424411240122, 0.8474337369372327, 0.763774618976614], ‘delta’: -0.0005348048046610517}]

    [{‘output’: 0.6213859615555266, ‘weights’: [0.2550690257394217, 0.49543508709194095], ‘delta’: -0.14619064683582808}, {‘output’: 0.6573693455986976, ‘weights’: [0.4494910647887381, 0.651592972722763], ‘delta’: 0.0771723774346327}]

    Обучение сети

    from math import exp
    from random import seed
    from random import random
    
    # Initialize a network
    def initialize_network(n_inputs, n_hidden, n_outputs):
        network = list()
        hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
        network.append(hidden_layer)
        output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
        network.append(output_layer)
        return network
    
    # Calculate neuron activation for an input
    def activate(weights, inputs):
        activation = weights[-1]
        for i in range(len(weights)-1):
            activation += weights[i] * inputs[i]
        return activation
    
    # Transfer neuron activation
    def transfer(activation):
        return 1.0 / (1.0 + exp(-activation))
    
    # Forward propagate input to a network output
    def forward_propagate(network, row):
        inputs = row
        for layer in network:
            new_inputs = []
            for neuron in layer:
                activation = activate(neuron['weights'], inputs)
                neuron['output'] = transfer(activation)
                new_inputs.append(neuron['output'])
            inputs = new_inputs
        return inputs
    
    # Calculate the derivative of an neuron output
    def transfer_derivative(output):
        return output * (1.0 - output)
    
    # Backpropagate error and store in neurons
    def backward_propagate_error(network, expected):
        for i in reversed(range(len(network))):
            layer = network[i]
            errors = list()
            if i != len(network)-1:
                for j in range(len(layer)):
                    error = 0.0
                    for neuron in network[i + 1]:
                        error += (neuron['weights'][j] * neuron['delta'])
                    errors.append(error)
            else:
                for j in range(len(layer)):
                    neuron = layer[j]
                    errors.append(expected[j] - neuron['output'])
            for j in range(len(layer)):
                neuron = layer[j]
                neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
    
    # Update network weights with error
    def update_weights(network, row, l_rate):
        for i in range(len(network)):
            inputs = row[:-1]
            if i != 0:
                inputs = [neuron['output'] for neuron in network[i - 1]]
            for neuron in network[i]:
                for j in range(len(inputs)):
                    neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
                neuron['weights'][-1] += l_rate * neuron['delta']
    
    # Train a network for a fixed number of epochs
    def train_network(network, train, l_rate, n_epoch, n_outputs):
        for epoch in range(n_epoch):
            sum_error = 0
            for row in train:
                outputs = forward_propagate(network, row)
                expected = [0 for i in range(n_outputs)]
                expected[row[-1]] = 1
                sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])
                backward_propagate_error(network, expected)
                update_weights(network, row, l_rate)
            print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
    
    # Test training backprop algorithm
    seed(1)
    dataset = [[2.7810836,2.550537003,0],
        [1.465489372,2.362125076,0],
        [3.396561688,4.400293529,0],
        [1.38807019,1.850220317,0],
        [3.06407232,3.005305973,0],
        [7.627531214,2.759262235,1],
        [5.332441248,2.088626775,1],
        [6.922596716,1.77106367,1],
        [8.675418651,-0.242068655,1],
        [7.673756466,3.508563011,1]]
    
    n_inputs = len(dataset[0]) - 1
    n_outputs = len(set([row[-1] for row in dataset]))
    network = initialize_network(n_inputs, 2, n_outputs)
    train_network(network, dataset, 0.5, 20, n_outputs)
    for layer in network:
        print(layer)
    

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

    >epoch=13, lrate=0.500, error=1.953

    >epoch=14, lrate=0.500, error=1.774

    >epoch=15, lrate=0.500, error=1.614

    >epoch=16, lrate=0.500, error=1.472

    >epoch=17, lrate=0.500, error=1.346

    >epoch=18, lrate=0.500, error=1.233

    >epoch=19, lrate=0.500, error=1.132

    [{‘weights’: [-1.4688375095432327, 1.850887325439514, 1.0858178629550297], ‘output’: 0.029980305604426185, ‘delta’: -0.0059546604162323625}, {‘weights’: [0.37711098142462157, -0.0625909894552989, 0.2765123702642716], ‘output’: 0.9456229000211323, ‘delta’: 0.0026279652850863837}]

    [{‘weights’: [2.515394649397849, -0.3391927502445985, -0.9671565426390275], ‘output’: 0.23648794202357587, ‘delta’: -0.04270059278364587}, {‘weights’: [-2.5584149848484263, 1.0036422106209202, 0.42383086467582715], ‘output’: 0.7790535202438367, ‘delta’: 0.03803132596437354}]

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

    Прогнозирование

    from math import exp
    
    # Calculate neuron activation for an input
    def activate(weights, inputs):
        activation = weights[-1]
        for i in range(len(weights)-1):
            activation += weights[i] * inputs[i]
        return activation
    
    # Transfer neuron activation
    def transfer(activation):
        return 1.0 / (1.0 + exp(-activation))
    
    # Forward propagate input to a network output
    def forward_propagate(network, row):
        inputs = row
        for layer in network:
            new_inputs = []
            for neuron in layer:
                activation = activate(neuron['weights'], inputs)
                neuron['output'] = transfer(activation)
                new_inputs.append(neuron['output'])
            inputs = new_inputs
        return inputs
    
    # Make a prediction with a network
    def predict(network, row):
        outputs = forward_propagate(network, row)
        return outputs.index(max(outputs))
    
    # Test making predictions with the network
    dataset = [[2.7810836,2.550537003,0],
        [1.465489372,2.362125076,0],
        [3.396561688,4.400293529,0],
        [1.38807019,1.850220317,0],
        [3.06407232,3.005305973,0],
        [7.627531214,2.759262235,1],
        [5.332441248,2.088626775,1],
        [6.922596716,1.77106367,1],
        [8.675418651,-0.242068655,1],
        [7.673756466,3.508563011,1]]
    network = [[{'weights': [-1.482313569067226, 1.8308790073202204, 1.078381922048799]}, {'weights': [0.23244990332399884, 0.3621998343835864, 0.40289821191094327]}],
        [{'weights': [2.5001872433501404, 0.7887233511355132, -1.1026649757805829]}, {'weights': [-2.429350576245497, 0.8357651039198697, 1.0699217181280656]}]]
    for row in dataset:
        prediction = predict(network, row)
        print('Expected=%d, Got=%d' % (row[-1], prediction))

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

    Это показывает, что сеть достигает 100% точности на этом небольшом наборе данных.

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=1, Got=1

    Expected=1, Got=1

    Expected=1, Got=1

    Expected=1, Got=1

    Expected=1, Got=1

    3. Создание простой модели с использованием Keras и TensorFlow.

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

    Начнем с краткого предварительного просмотра данных. Набор данных (dataset) состоит из последних 1000 баров актива EURUSD. Для этого нам понадобятся несколько шагов, например:

    • Импорт библиотек
    • Подключение к MetaTrader
    • Сбор данных
    • Преобразование данных, установка даты
    • Графические данные
    import MetaTrader5 as mt5
    from pandas import to_datetime, DataFrame
    import matplotlib.pyplot as plt
    
    symbol = "EURUSD"
    
    if not mt5.initialize():
        print("initialize() failed")
        mt5.shutdown()
    
    rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 1000)
    mt5.shutdown()
    
    rates = DataFrame(rates)
    rates['time'] = to_datetime(rates['time'], unit='s')
    rates = rates.set_index(['time'])
    
    plt.figure(figsize = (15,10))
    plt.plot(rates.close)
    plt.show()
    

    После запуска кода мы визуализируем данные закрытия на графике с линиями, как показано ниже.

    plot_1

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

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

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

    Первый шаг — разделить загруженную серию на набор для обучения и тестирования. Для этого создадим функцию, которая разделит такую ​​серию на две части; мы будем использовать процентное значение от общего размера серии для отсечения, например, 70% для обучения и 30% для тестов. Для валидации (backtest) у нас есть другие подходы, такие как разделение серии на обучение, тестирование и валидацию. Поскольку мы говорим о финансовых сериях, нам надо будет особо стараться избегать переобучения.

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

    Первый возврат — это весь набор от первой позиции 0 до размера, который представляет размер фактора, а второй ряд — все остальное.

    def train_test_split(values, fator):
        train_size = int(len(values) * fator)
        return values[0:train_size], values[train_size:len(values)]
    

    Модели на Keras можно определить как последовательность слоев.

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

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

    Как узнать количество слоев и их типы?

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

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

    Полносвязные слои определяются с помощью класса Dense. Мы можем указать количество нейронов или узлов в слое в качестве первого аргумента и указать функцию активации с помощью аргумента активации.

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

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

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

    Рассмотрим одномерную последовательность:

    [10, 20, 30, 40, 50, 60, 70, 80, 90]

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

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

    X,                                          y

    10, 20, 30                          40

    20, 30, 40                          50

    30, 40, 50                          60

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

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

    # univariate data preparation
    from numpy import array
     
    # split a univariate sequence into samples
    def split_sequence(sequence, n_steps):
        X, y = list(), list()
        for i in range(len(sequence)):
            # find the end of this pattern
            end_ix = i + n_steps
            # check if we are beyond the sequence
            if end_ix > len(sequence)-1:
                break
            # gather input and output parts of the pattern
            seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
            X.append(seq_x)
            y.append(seq_y)
        return array(X), array(y)
     
    # define input sequence
    raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
    # choose a number of time steps
    n_steps = 3
    # split into samples
    X, y = split_sequence(raw_seq, n_steps)
    # summarize the data
    for i in range(len(X)):
        print(X[i], y[i])
    

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

    [10 20 30] 40

    [20 30 40] 50

    [30 40 50] 60

    [40 50 60] 70

    [50 60 70] 80

    [60 70 80] 90

    Чтобы продолжить, нам нужно разделить выборку на X (feature) и y (target), чтобы мы могли обучить сеть. Для этого мы будем использовать функцию split_sequence(), которую мы создали ранее.

    X_train, y_train = split_sequence(train, 3)
    X_test, y_test = split_sequence(test, 3)
    

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

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

    Мы можем определить MLP для прогнозирования одномерных временных рядов следующим образом.

    # define model
    model = Sequential()
    model.add(Dense(100, activation='relu', input_dim=n_steps))
    model.add(Dense(1))
    model.compile(optimizer='adam', loss='mse')
    

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

    Количество временных шагов в качестве входных данных — это число, которое мы выбираем при подготовке нашего набора данных как аргумент функции split_sequence().

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

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

    [samples, features]

    Функция split_sequence() генерирует X в форме [образцы, характеристики], готовой к использованию.

    Модель обучается с использованием эффективного алгоритма под названием Adam для стохастического градиентного спуска и он оптимизируется с использованием функции потерь среднеквадратичной ошибки, или ‘mse’.

    Как только модель определена, мы можем обучить ее с помощью набора данных.

    model.fit(X_train, y_train, epochs=100, verbose=2)

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

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

    # demonstrate prediction
    x_input = X_test[-1]
    x_input = x_input.reshape((1, n_steps))
    yhat = model.predict(x_input, verbose=0)
    
    print("Valor previsto: ", yhat)
    print("Valor real: ", y_test[-1])
    

    4. Как интегрировать MQL5 в Python.

    Чтобы использовать модель в торговом счете, у нас есть несколько вариантов, один из них — использовать встроенные функции в Python, которые открывают и закрывают позиции, но для этого сценария у нас не будет многих возможностей, которые предлагает нам MQL; по этой причине я выбрал одну интеграцию между Python и средой MQL, что даст нам больше автономии в управлении нашими позициями/ордерами.

    Основываясь на статье MQL5 Соединение MetaTrader 5 и Python: получение и отправка данных, написанной Максимом Дмитриевским, я реализовал этот класс, используя шаблон разработки под названием Singleton, который будет отвечать за создание Socket-клиента для соединения. Этот шаблон гарантирует, что существует только одна копия объекта определенного типа, потому что если программа использует два указателя, относящиеся к одному и тому же объекту, они будут указывать на один и тот же объект.

    class CClientSocket
      {
    private:
       static CClientSocket*  m_socket;
       int                    m_handler_socket;
       int                    m_port;
       string                 m_host;
       int                    m_time_out;
                         CClientSocket(void);
                        ~CClientSocket(void);
    public:
       static bool           DeleteSocket(void);
       bool                  SocketSend(string payload);
       string                SocketReceive(void);
       bool                  IsConnected(void);
       static CClientSocket *Socket(void);
       bool                  Config(string host, int port);
       bool                  Close(void);
      };

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

    static CClientSocket *CClientSocket::Socket(void)
      {
       if(CheckPointer(m_socket)==POINTER_INVALID)
          m_socket=new CClientSocket();
       return m_socket;
      }

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

    Чтобы установить соединение с сервером, необходимо запустить соединение, поэтому мы создали метод IsConnected, чтобы установить соединение и мы можем начать передачу/получение данных.

    bool CClientSocket::IsConnected(void)
      {
       ResetLastError();
       bool res=true;
    
       m_handler_socket=SocketCreate();
       if(m_handler_socket==INVALID_HANDLE)
          res=false;
    
       if(!::SocketConnect(m_handler_socket,m_host,m_port,m_time_out))
          res=false;
    
       return res;
      }

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

    bool CClientSocket::Close(void)
      {
       bool res=false;
       if(SocketClose(m_handler_socket))
         {
          res=true;
          m_handler_socket=INVALID_HANDLE;
         }
       return res;
      }

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

    import socket
    
    class socketserver(object):
        def __init__(self, address, port):
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.address = address
            self.port = port
            self.sock.bind((self.address, self.port))
            
        def socket_receive(self):
            self.sock.listen(1)
            self.conn, self.addr = self.sock.accept()
            self.cummdata = ''
    
            while True:
                data = self.conn.recv(10000)
                self.cummdata+=data.decode("utf-8")
                if not data:
                    self.conn.close()
                    break
                return self.cummdata
        
        def socket_send(self, message):
            self.sock.listen(1)
            self.conn, self.addr = self.sock.accept()
            self.conn.send(bytes(message, "utf-8"))
        
                
        def __del__(self):
            self.conn.close()

    Наш объект простой, в его конструкторе мы получаем адрес и порт, с которым будем создавать свой сервер. Метод socket_received отвечает за принятие нового соединения и проверку наличия отправленных сообщений; если есть сообщения, которые нужно получить, мы запускаем цикл, пока не получим все части сообщения, затем мы закрываем соединение с клиентом и выходим из цикла. С другой стороны, метод socket_send отвечает за отправку сообщений нашему клиенту, но имейте в виду, что эта предлагаемая модель не только позволяет нам отправлять прогнозы нашей модели, а также открывает возможности для нескольких других вещей — всё будет зависеть от вашей креативности и потребности.

    Теперь, когда у нас есть готовая коммуникация, мы должны подумать о двух вещах:

    1. Обучить модель и сохранить ее.
    2. Использовать обученную модель, чтобы делать прогнозы.

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

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

    import MetaTrader5 as mt5
    from numpy.lib.financial import rate
    from pandas import to_datetime, DataFrame
    from datetime import datetime, timezone
    from matplotlib import pyplot
    from sklearn.metrics import mean_squared_error
    from math import sqrt
    import numpy as np
    
    from tensorflow.keras import Sequential
    from tensorflow.keras.layers import Dense
    from tensorflow.keras.callbacks import *
    
    
    symbol = "EURUSD"
    date_ini = datetime(2020, 1, 1, tzinfo=timezone.utc)
    date_end = datetime(2021, 7, 1, tzinfo=timezone.utc)
    period   = mt5.TIMEFRAME_D1
    
    def train_test_split(values, fator):
        train_size = int(len(values) * fator)
        return np.array(values[0:train_size]), np.array(values[train_size:len(values)])
    
    # split a univariate sequence into samples
    def split_sequence(sequence, n_steps):
            X, y = list(), list()
            for i in range(len(sequence)):
                    end_ix = i + n_steps
                    if end_ix > len(sequence)-1:
                            break
                    seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
                    X.append(seq_x)
                    y.append(seq_y)
            return np.array(X), np.array(y)
    
    if not mt5.initialize():
        print("initialize() failed")
        mt5.shutdown()
        raise Exception("Error Getting Data")
    
    rates = mt5.copy_rates_range(symbol, period, date_ini, date_end)
    mt5.shutdown()
    rates = DataFrame(rates)
    
    if rates.empty:
        raise Exception("Error Getting Data")
    
    rates['time'] = to_datetime(rates['time'], unit='s')
    rates.set_index(['time'], inplace=True)
    
    rates = rates.close.pct_change(1)
    rates = rates.dropna()
    
    X, y = train_test_split(rates, 0.70)
    X = X.reshape(X.shape[0])
    y = y.reshape(y.shape[0])
    
    train, test = train_test_split(X, 0.7)
    
    n_steps = 60
    verbose = 1
    epochs  = 50
    
    X_train, y_train = split_sequence(train, n_steps)
    X_test, y_test   = split_sequence(test, n_steps)
    X_val, y_val     = split_sequence(y, n_steps)
    
    # define model
    model = Sequential()
    model.add(Dense(200, activation='relu', input_dim=n_steps))
    model.add(Dense(1))
    model.compile(optimizer='adam', loss='mse')
    
    history = model.fit(X_train
                       ,y_train  
                       ,epochs=epochs
                       ,verbose=verbose
                       ,validation_data=(X_test, y_test))
    
    model.save(r'C:YOUR_PATHMQL5ExpertsYOUR_PATHmodel_train_'+symbol+'.h5')
    
    pyplot.title('Loss')
    pyplot.plot(history.history['loss'], label='train')
    pyplot.plot(history.history['val_loss'], label='test')
    pyplot.legend()
    pyplot.show()
    
    history = list()
    yhat    = list()
    
    for i in range(0, len(X_val)):
            pred = X_val[i]
            pred = pred.reshape((1, n_steps))
            history.append(y_val[i])
            yhat.append(model.predict(pred).flatten()[0])
    
    pyplot.figure(figsize=(10, 5))
    pyplot.plot(history,"*")
    pyplot.plot(yhat,"+")
    pyplot.plot(history, label='real')
    pyplot.plot(yhat, label='prediction')
    pyplot.ylabel('Price Close', size=10)
    pyplot.xlabel('time', size=10)
    pyplot.legend(fontsize=10)
    
    pyplot.show()
    rmse = sqrt(mean_squared_error(history, yhat))
    mse = mean_squared_error(history, yhat)
    
    print('Test RMSE: %.3f' % rmse)
    print('Test MSE: %.3f' % mse)

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

    from tensorflow.keras.models import *
    
    class Model(object):
        def __init__(self, n_steps:int, symbol:str, period:int) -> None:
            super().__init__()
            self.n_steps = n_steps
            self.model = load_model(r'C:YOUR_PATHMQL5ExpertsYOUR_PATHmodel_train_'+symbol+'.h5')
    
        def predict(self, data):
            return(self.model.predict(data.reshape((1, self.n_steps))).flatten()[0])

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

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

    import ast
    import pandas as pd
    from model import Model
    from server_socket import socketserver
    
    host = 'localhost'
    port = 9091 
    n_steps = 60
    TIMEFRAME = 24 | 0x4000
    model   = Model(n_steps, "EURUSD", TIMEFRAME)
    
    if __name__ == "__main__":
        serv = socketserver(host, port)
    
        while True:
            print("<<--Waiting Prices to Predict-->>")
            rates = pd.DataFrame(ast.literal_eval(serv.socket_receive()))
            rates = rates.rates.pct_change(1)
            rates.dropna(inplace=True)
            rates = rates.values.reshape((1, n_steps))
            serv.socket_send(str(model.predict(rates).flatten()[0]))

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

    bool NewBar(void)
      {
       datetime time[];
       if(CopyTime(Symbol(), Period(), 0, 1, time) < 1)
          return false;
       if(time[0] == m_last_time)
          return false;
       return bool(m_last_time = time[0]);
      }

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

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

    void CheckPosition(void)
      {
       buy = false;
       sell  = false;
    
       if(PositionSelect(Symbol()))
         {
          if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA)
            {
             buy = true;
             sell  = false;
            }
          if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA)
            {
             sell = true;
             buy = false;
            }
         }
      }

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

    if(NewBar())
       {
          if(!Socket.IsConnected())
             Print("Error : ", GetLastError(), " Line: ", __LINE__);
        ...
       }

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

    string payload = "{'rates':[";
    for(int i=InpSteps; i>=0; i--)
       {
          if(i>=1)
             payload += string(iClose(Symbol(), Period(), i))+",";
          else
             payload += string(iClose(Symbol(), Period(), i))+"]}";
       }

    Я решил отправить данные в формате {‘rates’:[1,2,3,4]}, потому что таким образом мы просто преобразуем их в pandas-датафрейм, и мы не будем тратить время на конверсии.

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

    void OnTick(void)
      {
          ....
    
          bool send = Socket.SocketSend(payload);
          if(send)
            {
             if(!Socket.IsConnected())
                Print("Error : ", GetLastError(), " Line: ", __LINE__);
    
             double yhat = StringToDouble(Socket.SocketReceive());
    
             Print("Value of Prediction: ", yhat);
    
             if(CopyBuffer(handle, 0, 0, 4, m_fast_ma)==-1)
                Print("Error in CopyBuffer");
    
             if(m_fast_ma[1]>m_fast_ma[2]&&m_fast_ma[2]>m_fast_ma[3])
               {
                if((iClose(Symbol(), Period(), 2)>iOpen(Symbol(), Period(), 2)&&iClose(Symbol(), Period(), 1)>iOpen(Symbol(), Period(), 1))&&yhat<0)
                  {
                   m_trade.Sell(mim_vol);
                  }
               }
    
             if(m_fast_ma[1]<m_fast_ma[2]&&m_fast_ma[2]<m_fast_ma[3])
               {
                if((iClose(Symbol(), Period(), 2)<iOpen(Symbol(), Period(), 2)&&iClose(Symbol(), Period(), 1)<iOpen(Symbol(), Period(), 1))&&yhat>0)
                  {
                   m_trade.Buy(mim_vol);
                  }
               }
            }
            
          Socket.Close();
         }
      }

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

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

    Что будет дальше?

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

    Заключение:

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

    Вы научились:

    1. Настраивать среду разработки Python.
    2. Мы вспоминали и реализовали нейрон перцептрона и сеть MLP в Python.
    3. Мы приготовили одномерные данные для изучения простой сети. 
    4. Мы настраивали архитектуру связи между Python и MQL.

    Расширения:

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

    • Размер входных данных. Примерно изучите количество дней, используемых в качестве входных данных для модели, например три дня, 21 день, 30 дней и т.д.
    • Настройка модели. Изучите различные структуры и гиперпараметры модели и получите среднюю производительность модели.
    • Масштабирование данных. Узнайте, можно ли использовать размер данных, например стандартизацию и нормализацию, для повышения производительности модели.
    • Диагностика обучения. Используйте такую диагностику, как кривые обучения для потери обучения и валидации, и среднеквадратичную ошибку, чтобы помочь настроить структурy и гиперпараметры модели.

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

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

    Что такое алгоритм обратного распространения и зачем он нужен?

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

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

    Примечание: Нейронные сети с прямой передачей обычно являются многослойными нейронными сетями (MLN). Данные проходят путь от входного слоя к скрытому слою и выходному слою.

    Как работает обратное распространение в Python?

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

    • Шаг 1:Входной слой получает входные данные.
    • Шаг 2:Входные данные усредняются по весам.
    • Шаг 3:Каждый скрытый слой обрабатывает выход. Каждый выход называется здесь «ошибкой», которая на самом деле является разницей между фактическим и желаемым выходом.
    • Шаг 4:На этом этапе алгоритм снова возвращается к скрытым слоям, чтобы оптимизировать веса и уменьшить ошибку.

    Типы обратного распространения в Python

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

    Реализация обратного распространения в Python

    Давайте посмотрим, как мы можем реализовать обратное распространение в Python в пошаговом режиме. Прежде всего, нам нужно импортировать все необходимые библиотеки.

    1. Импорт библиотек

     
    import numpy as np import pandas as pd from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt 

    Теперь давайте посмотрим, с каким набором данных мы будем работать.

    2. Загрузка набора данных

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

     
    # Loading dataset data = load_iris() # Dividing the dataset into target variable and features X=data.data y=data.target

    3. Разделение набора данных на обучающий и тестовый

    Теперь мы разделим набор данных на обучающий и тестовый наборы. Для этого воспользуемся функцией train_test_split(). Функция принимает три параметра: признаки, цель и размер тестового набора. На следующем этапе мы должны начать инициализацию гиперпараметров. Мы введем скорость обучения, количество итераций, размер входных данных, количество скрытых слоев и количество выходных слоев.

    3. Инициализация весов

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

     
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=20, random_state=4) 

    Теперь мы начнем строить нашу модель обратного распространения.

     
    learning_rate = 0.1 iterations = 5000 N = y_train.size # Input features input_size = 4 # Hidden layers hidden_size = 2 # Output layer output_size = 3 results = pd.DataFrame(columns=["mse", "accuracy"])

    4. Построение модели обратного распространения в Python

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

     
    for itr in range(iterations): # Implementing feedforward propagation on hidden layer Z1 = np.dot(X_train, W1) A1 = sigmoid(Z1) # Implementing feed forward propagation on output layer Z2 = np.dot(A1, W2) A2 = sigmoid(Z2) # Calculating the error mse = mean_squared_error(A2, y_train) acc = accuracy(A2, y_train) results=results.append({"mse":mse, "accuracy":acc},ignore_index=True ) # Backpropagation phase E1 = A2 - y_train dW1 = E1 * A2 * (1 - A2) E2 = np.dot(dW1, W2.T) dW2 = E2 * A1 * (1 - A1) # Updating the weights W2_update = np.dot(A1.T, dW1) / N W1_update = np.dot(X_train.T, dW2) / N W2 = W2 - learning_rate * W2_update W1 = W1 - learning_rate * W1_update

    Теперь мы построим график среднеквадратичной ошибки и точности с помощью функции pandas plot().

     
    results.mse.plot(title="Mean Squared Error") 

    Download

     
    results.accuracy.plot(title="Accuracy") 

    Download 1 Теперь мы рассчитаем точность модели. Вы можете видеть, что точность модели значительно возросла до 80%.

     
    Z1 = np.dot(X_test, W1) A1 = sigmoid(Z1) Z2 = np.dot(A1, W2) A2 = sigmoid(Z2) acc = accuracy(A2, y_test) print("Accuracy: {}".format(acc))

    Вывод:

    Вы можете видеть, что точность модели значительно возросла до 80%.

    Преимущества метода обратного распространения в Python

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

    Недостатки обратного распространения

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

    Заключение

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

    Алексей

    Меня зовут Алексей Красовский, я ведущий программист, сертифицированный специалист по Python и, одновременно, автор этого блога.

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

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

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

    Теперь, главный
    вопрос: как построить алгоритм, который бы наилучшим образом находил весовые
    коэффициенты. Наилучший – это значит, максимально быстро и с максимально
    близкими выходными значениями для требуемых откликов. В общем случае эта задача
    не решена. Нет универсального алгоритма обучения. Поэтому, лучшее, что мы можем
    сделать – это выбрать тот алгоритм, который хорошо себя зарекомендовал в
    прошлом. Основной «рабочей лошадкой» здесь является алгоритм back propagation (обратного
    распространения ошибки), который, в свою очередь, базируется на алгоритме градиентного
    спуска
    .

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

    Чтобы все лучше
    понять, предположим, что у нас имеется вот такая полносвязная НС прямого
    распространения с весами связей, выбранными произвольным образом в диапазоне от
    [-0.5; 0,5]. Здесь верхний индекс показывает принадлежность к тому или иному слою
    сети. Также, каждый нейрон имеет некоторую активационную функцию :

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

          

    и последнее
    выходное значение y:

    Далее, мы знаем
    требуемый отклик d для текущего вектора ,
    значит для него можно вычислить ошибку работы НС. Она будет равна:

    На данный момент
    все должно быть понятно. Мы на первом занятии подробно рассматривали процесс
    распространения сигнала по НС. И вы это уже хорошо себе представляете. А вот
    дальше начинается самое главное – корректировка весов. Для этого делается
    обратный проход по НС: от последнего слоя – к первому.

    Итак, у нас есть
    ошибка e и некая функция
    активации нейронов .
    Первое, что нам нужно – это вычислить локальный градиент для выходного нейрона.
    Это делается по формуле:

    Этот момент
    требует пояснения. Смотрите, ранее используемая пороговая функция:

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

    или логистическую
    функцию
    :

    Фактически, они
    отличаются только тем, что первая дает выходной интервал [-1; 1], а вторая – [0;
    1]. И мы уже берем ту, которая нас больше устраивает в данной конкретной
    ситуации. Например, выберем логистическую функцию.

    Ее производная
    функции по аргументу x дает очень простое выражение:

    Именно его мы и
    запишем в нашу формулу вычисления локального градиента:

    Но, так как

    то локальный
    градиент последнего нейрона, равен:

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

    Для второй связи
    все то же самое, только входной сигнал берется от второго нейрона:

    Здесь у вас
    может возникнуть вопрос: что такое параметр λ и где его брать? Он
    подбирается самостоятельно, вручную самим разработчиком. В самом простом случае
    можно попробовать следующие значения:

     (Мы подробно о
    нем говорили на занятии по алгоритму градиентного спуска):

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

    А дальше
    действуем по такой же самой схеме, корректируем входные связи по той же
    формуле:

    И для второго
    нейрона:

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

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

    Ну и осталось
    выполнить коррекцию весов первого слоя все по той же формуле:

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

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

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

    Вот так, в целом
    выглядит идея работы алгоритма обучения по методу обратного распространения
    ошибки. Давайте теперь в качестве примера обучим следующую НС:

    В качестве
    обучающего множества выберем все возможные варианты (здесь 1 – это да, -1 – это
    нет):

    Вектор
    наблюдений

    Требуемый
    отклик

    [-1, -1, -1]

    -1

    [-1, -1,
    1]

    1

    [-1, 1, -1]

    -1

    [-1, 1, 1]

    1

    [1, -1, -1]

    -1

    [1, -1, 1]

    1

    [1, 1, -1]

    -1

    [1, 1, 1]

    -1

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

    В качестве
    активационной функции выберем гиперболический тангенс:

    со значением
    производной:

    Программа на Python будет такой:

    lesson 3. Back propagation.py

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

    Видео по теме

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

    Код

    X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ])

    y = np.array([[0,1,1,0]]).T

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

    syn1 = 2*np.random.random((4,1)) 1

    for j in xrange(60000):

        l1 = 1/(1+np.exp((np.dot(X,syn0))))

        l2 = 1/(1+np.exp((np.dot(l1,syn1))))

        l2_delta = (y l2)*(l2*(1l2))

        l1_delta = l2_delta.dot(syn1.T) * (l1 * (1l1))

        syn1 += l1.T.dot(l2_delta)

        syn0 += X.T.dot(l1_delta)

    Слишком сжато? Давайте разобьём его на более простые части.

    Часть 1: Небольшая игрушечная нейросеть

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

    Вход   Выход

    0 0 1 0

    1 1 1 1

    1 0 1 1

    0 1 1 0

    Предположим, нам нужно предсказать, как будет выглядеть колонка «выход» на основе входных данных. Эту задачу можно было бы решить, подсчитав статистическое соответствие между ними. И мы бы увидели, что с выходными данными на 100% коррелирует левый столбец.

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

    Нейросеть в два слоя

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    import numpy as np

    # Сигмоида

    def nonlin(x,deriv=False):

        if(deriv==True):

            return f(x)*(1f(x))

        return 1/(1+np.exp(x))

    # набор входных данных

    X = np.array([  [0,0,1],

                    [0,1,1],

                    [1,0,1],

                    [1,1,1] ])

    # выходные данные            

    y = np.array([[0,0,1,1]]).T

    # сделаем случайные числа более определёнными

    np.random.seed(1)

    # инициализируем веса случайным образом со средним 0

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

    for iter in xrange(10000):

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

        l0 = X

        l1 = nonlin(np.dot(l0,syn0))

        # насколько мы ошиблись?

        l1_error = y l1

        # перемножим это с наклоном сигмоиды

        # на основе значений в l1

        l1_delta = l1_error * nonlin(l1,True) # !!!

        # обновим веса

        syn0 += np.dot(l0.T,l1_delta) # !!!

    print «Выходные данные после тренировки:»

    print l1

    Выходные данные после тренировки:
    [[ 0.00966449]
    [ 0.00786506]
    [ 0.99358898]
    [ 0.99211957]]

    Переменные и их описания.

    X — матрица входного набор данных; строки – тренировочные примеры
    y – матрица выходного набора данных; строки – тренировочные примеры
    l0 – первый слой сети, определённый входными данными
    l1 – второй слой сети, или скрытый слой
    syn0 – первый слой весов, Synapse 0, объединяет l0 с l1.
    «*» — поэлементное умножение – два вектора одного размера умножают соответствующие значения, и на выходе получается вектор такого же размера
    «-» – поэлементное вычитание векторов
    x.dot(y) – если x и y – это вектора, то на выходе получится скалярное произведение. Если это матрицы, то получится перемножение матриц. Если матрица только одна из них – это перемножение вектора и матрицы.

    И это работает! Рекомендую перед прочтением объяснения поиграться немного с кодом и понять, как он работает. Он должен запускаться прямо как есть, в ipython notebook. С чем можно повозиться в коде:

    • сравните l1 после первой итерации и после последней
    • посмотрите на функцию nonlin.
    • посмотрите, как меняется l1_error
    • разберите строку 36 – основные секретные ингредиенты собраны тут (отмечена !!!)
    • разберите строку 39 – вся сеть готовится именно к этой операции (отмечена !!!)

    Разберём код по строчкам

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

    def nonlin(x,deriv=False):

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

    Эта функция также умеет выдавать производную сигмоиды (deriv=True). Это одно из её полезных свойств. Если выход функции – это переменная out, тогда производная будет out * (1-out). Эффективно.

    X = np.array([  [0,0,1],

    Инициализация массива входных данных в виде numpy-матрицы. Каждая строка – тренировочный пример. Столбцы – это входные узлы. У нас получается 3 входных узла в сети и 4 тренировочных примера.

    y = np.array([[0,0,1,1]]).T

    Инициализирует выходные данные. «.T» – функция переноса. После переноса у матрицы y есть 4 строки с одним столбцом. Как и в случае входных данных, каждая строка – это тренировочный пример, и каждый столбец (в нашем случае один) – выходной узел. У сети, получается, 3 входа и 1 выход.

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

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

    Матрица весов сети. syn0 означает «synapse zero». Так как у нас всего два слоя, вход и выход, нам нужна одна матрица весов, которая их свяжет. Её размерность (3, 1), поскольку у нас есть 3 входа и 1 выход. Иными словами, l0 имеет размер 3, а l1 – 1. Поскольку мы связываем все узлы в l0 со всеми узлами l1, нам требуется матрица размерности (3, 1).

    Заметьте, что она инициализируется случайным образом, и среднее значение равно нулю. За этим стоит достаточно сложная теория. Пока просто примем это как рекомендацию. Также заметим, что наша нейросеть – это и есть эта самая матрица. У нас есть «слои» l0 и l1, но они представляют собой временные значения, основанные на наборе данных. Мы их не храним. Всё обучение хранится в syn0.

    for iter in xrange(10000):

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

    Первый слой, l0, это просто данные. В X содержится 4 тренировочных примера. Мы обработаем их все и сразу – это называется групповой тренировкой [full batch]. Итого мы имеем 4 разных строки l0, но их можно представить себе как один тренировочный пример – на этом этапе это не имеет значения (можно было загрузить их 1000 или 10000 без всяких изменений в коде).

    l1 = nonlin(np.dot(l0,syn0))

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

    В строке содержится два шага. Первый делает матричное перемножение l0 и syn0. Второй передаёт вывод через сигмоиду. Размерности у них следующие:

    (4 x 3) dot (3 x 1) = (4 x 1)

    Матричные умножения требуют, чтобы в середине уравнения размерности совпадали. Итоговая матрица имеет количество строк, как у первой, а столбцов – как у второй.

    Мы загрузили 4 тренировочных примера, и получили 4 догадки (матрица 4х1). Каждый вывод соответствует догадке сети для данного ввода.

    Поскольку в l1 содержатся догадки, мы можем сравнить их разницу с реальностью, вычитая её l1 из правильного ответа y. l1_error – вектор из положительных и отрицательных чисел, характеризующий «промах» сети.

    l1_delta = l1_error * nonlin(l1,True)

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

    Первая часть: производная

    l1 представляет три этих точки, а код выдаёт наклон линий, показанных ниже. Заметьте, что при больших значениях вроде x=2.0 (зелёная точка) и очень малые, вроде x=-1.0 (фиолетовая) линии имеют небольшой уклон. Самый большой угол у точки х=0 (голубая). Это имеет большое значение. Также отметьте, что все производные лежат в пределах от 0 до 1.

    image

    Полное выражение: производная, взвешенная по ошибкам

    l1_delta = l1_error * nonlin(l1,True)

    Математически существуют более точные способы, но в нашем случае подходит и этот. l1_error – это матрица (4,1). nonlin(l1,True) возвращает матрицу (4,1). Здесь мы поэлементно их перемножаем, и на выходе тоже получаем матрицу (4,1), l1_delta.

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

    syn0 += np.dot(l0.T,l1_delta)

    Мы готовы к обновлению сети. Рассмотрим один тренировочный пример. В нём мы будем обновлять веса. Обновим крайний левый вес (9.5)

    нейрон

    weight_update = input_value * l1_delta

    Для крайнего левого веса это будет 1.0 * l1_delta. Предположительно, это лишь незначительно увеличит 9.5. Почему? Поскольку предсказание было уже достаточно уверенным, и предсказания были практически правильными. Небольшая ошибка и небольшой наклон линии означает очень небольшое обновление.

    image

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

    Понаблюдав за обновлением сети, вернёмся к нашим тренировочным данным. Когда и вход, и выход равны 1, мы увеличиваем вес между ними. Когда вход 1, а выход – 0, мы уменьшаем вес.
    Вход Выход
    0 0 1 0
    1 1 1 1
    1 0 1 1
    0 1 1 0

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

    Часть 2: задачка посложнее

     
    Вход Выход
    0 0 1 0
    0 1 1 1
    1 0 1 1
    1 1 1 0

    Попробуем предсказать выходные данные на основе трёх входных столбцов данных. Ни один из входных столбцов не коррелирует на 100% с выходным. Третий столбец вообще ни с чем не связан, поскольку в нём всю дорогу содержатся единицы. Однако и тут можно увидеть схему – если в одном из двух первых столбцов (но не в обоих сразу) содержится 1, то результат также будет равен 1.

    Это нелинейная схема, поскольку прямого соответствия столбцов один к одному не существует. Соответствие строится на комбинации входных данных, столбцов 1 и 2.

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

    image
    image

    Стратегия

    Чтобы скомбинировать пиксели в нечто, у чего может появиться однозначное соответствие с выходными данными, нужно добавить ещё один слой. Первый слой комбинирует вход, второй назначает соответствие выходу, используя в качестве входных данных выходные данные первого слоя. Обратите внимание на таблицу.
    Вход (l0) Скрытые веса (l1) Выход (l2)
    0 0 1 0.1 0.2 0.5 0.2 0
    0 1 1 0.2 0.6 0.7 0.1 1
    1 0 1 0.3 0.2 0.3 0.9 1
    1 1 1 0.2 0.1 0.3 0.8 0

    Случайным образом назначив веса, мы получим скрытые значения для слоя №1. Интересно, что у второго столбца скрытых весов уже есть небольшая корреляция с выходом. Не идеальная, но есть. И это тоже является важной частью процесса тренировки сети. Тренировка будет только усиливать эту корреляцию. Она будет обновлять syn1, чтобы назначить её соответствие выходным данным, и syn0, чтобы лучше получать данные со входа.

    Нейросеть в три слоя

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    import numpy as np

    def nonlin(x,deriv=False):

    if(deriv==True):

               return f(x)*(1f(x))

    return 1/(1+np.exp(x))

    X = np.array([[0,0,1],

                [0,1,1],

                [1,0,1],

                [1,1,1]])

    y = np.array([[0],

    [1],

    [1],

    [0]])

    np.random.seed(1)

    # случайно инициализируем веса, в среднем — 0

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

    syn1 = 2*np.random.random((4,1)) 1

    for j in xrange(60000):

    # проходим вперёд по слоям 0, 1 и 2

        l0 = X

        l1 = nonlin(np.dot(l0,syn0))

        l2 = nonlin(np.dot(l1,syn1))

        # как сильно мы ошиблись относительно нужной величины?

        l2_error = y l2

        if (j% 10000) == 0:

            print «Error:» + str(np.mean(np.abs(l2_error)))

        # в какую сторону нужно двигаться?

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

        l2_delta = l2_error*nonlin(l2,deriv=True)

        # как сильно значения l1 влияют на ошибки в l2?

        l1_error = l2_delta.dot(syn1.T)

        # в каком направлении нужно двигаться, чтобы прийти к l1?

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

        l1_delta = l1_error * nonlin(l1,deriv=True)

        syn1 += l1.T.dot(l2_delta)

        syn0 += l0.T.dot(l1_delta)

    Error:0.496410031903
    Error:0.00858452565325
    Error:0.00578945986251
    Error:0.00462917677677
    Error:0.00395876528027
    Error:0.00351012256786

     

    Переменные и их описания

    X — матрица входного набор данных; строки – тренировочные примеры
    y – матрица выходного набора данных; строки – тренировочные примеры
    l0 – первый слой сети, определённый входными данными
    l1 – второй слой сети, или скрытый слой
    l2 – финальный слой, это наша гипотеза. По мере тренировки должен приближаться к правильному ответу
    syn0 – первый слой весов, Synapse 0, объединяет l0 с l1.
    syn1 – второй слой весов, Synapse 1, объединяет l1 с l2.
    l2_error – промах сети в количественном выражении
    l2_delta – ошибка сети, в зависимости от уверенности предсказания. Почти совпадает с ошибкой, за исключением уверенных предсказаний
    l1_error – взвешивая l2_delta весами из syn1, мы подсчитываем ошибку в среднем/скрытом слое
    l1_delta – ошибки сети из l1, масштабируемые по увеернности предсказаний. Почти совпадает с l1_error, за исключением уверенных предсказаний

    Код должен быть достаточно понятным – это просто предыдущая реализация сети, сложенная в два слоя один над другим. Выход первого слоя l1 – это вход второго слоя. Что-то новое есть лишь в следующей строке.

    l1_error = l2_delta.dot(syn1.T)

    Использует ошибки, взвешенные по уверенности предсказаний из l2, чтобы подсчитать ошибку для l1. Получаем, можно сказать, ошибку, взвешенную по вкладам – мы подсчитываем, какой вклад в ошибки в l2 вносят значения в узлах l1. Этот шаг и называется обратным распространением ошибок. Затем мы обновляем syn0, используя тот же алгоритм, что и в варианте с нейросетью из двух слоёв.

    Источник

    Практическая работа №1: Реализация метода обратного распространения ошибки для двухслойной полностью связанной нейронной сети

    Задача

    Требуется вывести расчетные формулы и спроектировать программную реализацию метода обратного распространения ошибки для двухслойной полносвязной нейронной сети. Обучение и тестирование сети происходит на наборе данных MNIST, функция активации скрытого слоя – relu, функция активации выходного слоя – softmax, функция ошибки – кросс-энтропия.

    Математическая модель

    Модель нейрона описывается следующими уравнениями:

    где – входной сигнал, – синаптический вес сигнала , – функция активации, – смещение

    Прямой ход

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

    где – выход скрытого слоя, – функция активации скрытого слоя (relu), – вход сети, – выход сети, – функция активации выходного слоя (softmax),

    Метод обратного распространения ошибки

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

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

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

    2. Вычисление значений целевой функции и её производной.

      Целевая функция – кросс-энтропия, вычисляется как

      где – ожидаемый выход (метки)

      Производную целевой функции по весам можно вывести следующим образом:

      По весам второго слоя:

      из условия получаем

      По весам первого слоя

    3. Обратный проход нейронной сети и корректировка весов

    4. Повторение этапов 1-3 до выполнения условия останова

    Описание программной реализации

    Network.py

    Содержит реализацию нейронной сети

    Класс NN содержит данные и методы для работы с сетью

    Поля класса NN:

    _input_size – размер входного слоя

    _hidden_size – размер скрытого слоя

    _output_size – размер выходного слоя

    _w1, _b1 – массивы для хранения весов и смещений первого слоя

    _w2, _b2 – массивы для хранения весов и смещений второго слоя

    Методы класса NN:

    _forward(input) – прямой проход сети. Возвращает выходной сигнал первого и второго слоя

    _calculate_dE(input, label, output1, output2) – вычисление градиента функции ошибки. Возвращает градиент функции по весам и биасам первого и второго слоёв

    _backprop(learning_rate, size, dEb1, dEb2) – корректировка весов сети при помощи посчитанных градиентов

    init_weights() – инициализация весов нормальным распределением с дисперсией 1/10

    fit(input, label, validate_data = None, batch_size = 100, learning_rate = 0.1, epochs = 100) – пакетное обучение сети на epochs эпохах, скоростью обучения learning_rate, размером пакета batch_size. Выводит точность и значение целевой функции на каждой эпохе

    predict(input) – получение предсказания сети

    utils.py:

    Содержит вспомогательные функции.

    relu(X) – функция relu

    reluD(X) – производная функции relu

    calcilate_E(predict, label) – подсчёт функции ошибки на основании предсказания сети и верной разметки

    calculate_acc(prediction ,label) – посчёт точности на основании предсказания сети и верной разметки

    main.py

    Обучает сеть из класса NN на MNIST с параметрами из аргументов запуска. Измеряет время обучения.

    Аргументы:

    • —hidden – количество нейронов в скрытом слое

    • —epochs – количество эпох обучения

    • —lr – скорость обучения

    • —batch – размер пакета

    Как вызывать:

        python main.py --hidden 30 --epochs 20 --lr 0,1 --batch 100
    

    (в примере указаны параметры по умолчанию)

    Эксперименты

    Размер скрытого слоя: 30, эпох: 20, скорость обучения: 0,1, размер пакета: 100

    train accuracy: 0.9729 train error: 0.0913
    validate accuracy: 0.9613 validate error: 0.1238

    Time: 28.309726 seconds

    Размер скрытого слоя: 10, эпох: 20, скорость обучения: 0,1, размер пакета: 100

    train accuracy: 0.9469 train error: 0.1805
    validate accuracy: 0.9404 validate error: 0.2013

    Time: 18.23942 seconds

    Размер скрытого слоя: 10, эпох: 20, скорость обучения: 0,1, размер пакета: 100

    train accuracy: 0.9874 train error: 0.0467
    validate accuracy: 0.9721 validate error: 0.0878

    Time: 58.233283 seconds

    Размер скрытого слоя: 30, эпох: 50, скорость обучения: 0,1, размер пакета: 100

    train accuracy: 0.9875 train error: 0.0418
    validate accuracy: 0.9678 validate error: 0.1216

    Time: 70.004101 seconds

    Размер скрытого слоя: 30, эпох: 20, скорость обучения: 0,5, размер пакета: 100

    train accuracy: 0.9766 train error: 0.0732
    validate accuracy: 0.9622 validate error: 0.1614

    Time: 26.213141 seconds

    Размер скрытого слоя: 30, эпох: 30, скорость обучения: 0,05, размер пакета: 100

    train accuracy: 0.971 train error: 0.0998
    validate accuracy: 0.9625 validate error: 0.1225

    Time: 40.577051 seconds

    Размер скрытого слоя: 30, эпох: 20, скорость обучения: 0,1, размер пакета: 10

    train accuracy: 0.9724 train error: 0.0928
    validate accuracy: 0.9533 validate error: 0.2423

    Time: 65.179444 seconds

    Размер скрытого слоя: 30, эпох: 20, скорость обучения: 0,1, размер пакета: 1000

    train accuracy: 0.9268 train error: 0.2588
    validate accuracy: 0.9264 validate error: 0.2576

    Time: 25.455479 seconds

    Background

    Backpropagation is a common method for training a neural network. There is no shortage of papers online that attempt to explain how backpropagation works, but few that include an example with actual numbers. This post is my attempt to explain how it works with a concrete example that folks can compare their own calculations to in order to ensure they understand backpropagation correctly.

    Backpropagation in Python

    You can play around with a Python script that I wrote that implements the backpropagation algorithm in this Github repo.

    Backpropagation Visualization

    For an interactive visualization showing a neural network as it learns, check out my Neural Network visualization.

    Additional Resources

    If you find this tutorial useful and want to continue learning about neural networks, machine learning, and deep learning, I highly recommend checking out Adrian Rosebrock’s new book, Deep Learning for Computer Vision with Python. I really enjoyed the book and will have a full review up soon.

    Overview

    For this tutorial, we’re going to use a neural network with two inputs, two hidden neurons, two output neurons. Additionally, the hidden and output neurons will include a bias.

    Here’s the basic structure:

    neural_network (7)

    In order to have some numbers to work with, here are the initial weights, the biases, and training inputs/outputs:

    neural_network (9)

    The goal of backpropagation is to optimize the weights so that the neural network can learn how to correctly map arbitrary inputs to outputs.

    For the rest of this tutorial we’re going to work with a single training set: given inputs 0.05 and 0.10, we want the neural network to output 0.01 and 0.99.

    The Forward Pass

    To begin, lets see what the neural network currently predicts given the weights and biases above and inputs of 0.05 and 0.10. To do this we’ll feed those inputs forward though the network.

    We figure out the total net input to each hidden layer neuron, squash the total net input using an activation function (here we use the logistic function), then repeat the process with the output layer neurons.

    Total net input is also referred to as just net input by some sources.

    Here’s how we calculate the total net input for h_1:

    net_{h1} = w_1 * i_1 + w_2 * i_2 + b_1 * 1

    net_{h1} = 0.15 * 0.05 + 0.2 * 0.1 + 0.35 * 1 = 0.3775

    We then squash it using the logistic function to get the output of h_1:

    out_{h1} = frac{1}{1+e^{-net_{h1}}} = frac{1}{1+e^{-0.3775}} = 0.593269992

    Carrying out the same process for h_2 we get:

    out_{h2} = 0.596884378

    We repeat this process for the output layer neurons, using the output from the hidden layer neurons as inputs.

    Here’s the output for o_1:

    net_{o1} = w_5 * out_{h1} + w_6 * out_{h2} + b_2 * 1

    net_{o1} = 0.4 * 0.593269992 + 0.45 * 0.596884378 + 0.6 * 1 = 1.105905967

    out_{o1} = frac{1}{1+e^{-net_{o1}}} = frac{1}{1+e^{-1.105905967}} = 0.75136507

    And carrying out the same process for o_2 we get:

    out_{o2} = 0.772928465

    Calculating the Total Error

    We can now calculate the error for each output neuron using the squared error function and sum them to get the total error:

    E_{total} = sum frac{1}{2}(target - output)^{2}

    Some sources refer to the target as the ideal and the output as the actual.

    The frac{1}{2} is included so that exponent is cancelled when we differentiate later on. The result is eventually multiplied by a learning rate anyway so it doesn’t matter that we introduce a constant here [1].

    For example, the target output for o_1 is 0.01 but the neural network output 0.75136507, therefore its error is:

    E_{o1} = frac{1}{2}(target_{o1} - out_{o1})^{2} = frac{1}{2}(0.01 - 0.75136507)^{2} = 0.274811083

    Repeating this process for o_2 (remembering that the target is 0.99) we get:

    E_{o2} = 0.023560026

    The total error for the neural network is the sum of these errors:

    E_{total} = E_{o1} + E_{o2} = 0.274811083 + 0.023560026 = 0.298371109

    The Backwards Pass

    Our goal with backpropagation is to update each of the weights in the network so that they cause the actual output to be closer the target output, thereby minimizing the error for each output neuron and the network as a whole.

    Output Layer

    Consider w_5. We want to know how much a change in w_5 affects the total error, aka frac{partial E_{total}}{partial w_{5}}.

    frac{partial E_{total}}{partial w_{5}} is read as “the partial derivative of E_{total} with respect to w_{5}“. You can also say “the gradient with respect to w_{5}“.

    By applying the chain rule we know that:

    frac{partial E_{total}}{partial w_{5}} = frac{partial E_{total}}{partial out_{o1}} * frac{partial out_{o1}}{partial net_{o1}} * frac{partial net_{o1}}{partial w_{5}}

    Visually, here’s what we’re doing:

    output_1_backprop (4)

    We need to figure out each piece in this equation.

    First, how much does the total error change with respect to the output?

    E_{total} = frac{1}{2}(target_{o1} - out_{o1})^{2} + frac{1}{2}(target_{o2} - out_{o2})^{2}

    frac{partial E_{total}}{partial out_{o1}} = 2 * frac{1}{2}(target_{o1} - out_{o1})^{2 - 1} * -1 + 0

    frac{partial E_{total}}{partial out_{o1}} = -(target_{o1} - out_{o1}) = -(0.01 - 0.75136507) = 0.74136507

    -(target - out) is sometimes expressed as out - target

    When we take the partial derivative of the total error with respect to out_{o1}, the quantity frac{1}{2}(target_{o2} - out_{o2})^{2} becomes zero because out_{o1} does not affect it which means we’re taking the derivative of a constant which is zero.

    Next, how much does the output of o_1 change with respect to its total net input?

    The partial derivative of the logistic function is the output multiplied by 1 minus the output:

    out_{o1} = frac{1}{1+e^{-net_{o1}}}

    frac{partial out_{o1}}{partial net_{o1}} = out_{o1}(1 - out_{o1}) = 0.75136507(1 - 0.75136507) = 0.186815602

    Finally, how much does the total net input of o1 change with respect to w_5?

    net_{o1} = w_5 * out_{h1} + w_6 * out_{h2} + b_2 * 1

    frac{partial net_{o1}}{partial w_{5}} = 1 * out_{h1} * w_5^{(1 - 1)} + 0 + 0 = out_{h1} = 0.593269992

    Putting it all together:

    frac{partial E_{total}}{partial w_{5}} = frac{partial E_{total}}{partial out_{o1}} * frac{partial out_{o1}}{partial net_{o1}} * frac{partial net_{o1}}{partial w_{5}}

    frac{partial E_{total}}{partial w_{5}} = 0.74136507 * 0.186815602 * 0.593269992 = 0.082167041

    You’ll often see this calculation combined in the form of the delta rule:

    frac{partial E_{total}}{partial w_{5}} = -(target_{o1} - out_{o1}) * out_{o1}(1 - out_{o1}) * out_{h1}

    Alternatively, we have frac{partial E_{total}}{partial out_{o1}} and frac{partial out_{o1}}{partial net_{o1}} which can be written as frac{partial E_{total}}{partial net_{o1}}, aka delta_{o1} (the Greek letter delta) aka the node delta. We can use this to rewrite the calculation above:

    delta_{o1} = frac{partial E_{total}}{partial out_{o1}} * frac{partial out_{o1}}{partial net_{o1}} = frac{partial E_{total}}{partial net_{o1}}

    delta_{o1} = -(target_{o1} - out_{o1}) * out_{o1}(1 - out_{o1})

    Therefore:

    frac{partial E_{total}}{partial w_{5}} = delta_{o1} out_{h1}

    Some sources extract the negative sign from delta so it would be written as:

    frac{partial E_{total}}{partial w_{5}} = -delta_{o1} out_{h1}

    To decrease the error, we then subtract this value from the current weight (optionally multiplied by some learning rate, eta, which we’ll set to 0.5):

    w_5^{+} = w_5 - eta * frac{partial E_{total}}{partial w_{5}} = 0.4 - 0.5 * 0.082167041 = 0.35891648

    We can repeat this process to get the new weights w_6, w_7, and w_8:

    w_6^{+} = 0.408666186

    w_7^{+} = 0.511301270

    w_8^{+} = 0.561370121

    We perform the actual updates in the neural network after we have the new weights leading into the hidden layer neurons (ie, we use the original weights, not the updated weights, when we continue the backpropagation algorithm below).

    Hidden Layer

    Next, we’ll continue the backwards pass by calculating new values for w_1, w_2, w_3, and w_4.

    Big picture, here’s what we need to figure out:

    frac{partial E_{total}}{partial w_{1}} = frac{partial E_{total}}{partial out_{h1}} * frac{partial out_{h1}}{partial net_{h1}} * frac{partial net_{h1}}{partial w_{1}}

    Visually:

    nn-calculation

    We’re going to use a similar process as we did for the output layer, but slightly different to account for the fact that the output of each hidden layer neuron contributes to the output (and therefore error) of multiple output neurons. We know that out_{h1} affects both out_{o1} and out_{o2} therefore the frac{partial E_{total}}{partial out_{h1}} needs to take into consideration its effect on the both output neurons:

    frac{partial E_{total}}{partial out_{h1}} = frac{partial E_{o1}}{partial out_{h1}} + frac{partial E_{o2}}{partial out_{h1}}

    Starting with frac{partial E_{o1}}{partial out_{h1}}:

    frac{partial E_{o1}}{partial out_{h1}} = frac{partial E_{o1}}{partial net_{o1}} * frac{partial net_{o1}}{partial out_{h1}}

    We can calculate frac{partial E_{o1}}{partial net_{o1}} using values we calculated earlier:

    frac{partial E_{o1}}{partial net_{o1}} = frac{partial E_{o1}}{partial out_{o1}} * frac{partial out_{o1}}{partial net_{o1}} = 0.74136507 * 0.186815602 = 0.138498562

    And frac{partial net_{o1}}{partial out_{h1}} is equal to w_5:

    net_{o1} = w_5 * out_{h1} + w_6 * out_{h2} + b_2 * 1

    frac{partial net_{o1}}{partial out_{h1}} = w_5 = 0.40

    Plugging them in:

    frac{partial E_{o1}}{partial out_{h1}} = frac{partial E_{o1}}{partial net_{o1}} * frac{partial net_{o1}}{partial out_{h1}} = 0.138498562 * 0.40 = 0.055399425

    Following the same process for frac{partial E_{o2}}{partial out_{h1}}, we get:

    frac{partial E_{o2}}{partial out_{h1}} = -0.019049119

    Therefore:

    frac{partial E_{total}}{partial out_{h1}} = frac{partial E_{o1}}{partial out_{h1}} + frac{partial E_{o2}}{partial out_{h1}} = 0.055399425 + -0.019049119 = 0.036350306

    Now that we have frac{partial E_{total}}{partial out_{h1}}, we need to figure out frac{partial out_{h1}}{partial net_{h1}} and then frac{partial net_{h1}}{partial w} for each weight:

    out_{h1} = frac{1}{1+e^{-net_{h1}}}

    frac{partial out_{h1}}{partial net_{h1}} = out_{h1}(1 - out_{h1}) = 0.59326999(1 - 0.59326999 ) = 0.241300709

    We calculate the partial derivative of the total net input to h_1 with respect to w_1 the same as we did for the output neuron:

    net_{h1} = w_1 * i_1 + w_3 * i_2 + b_1 * 1

    frac{partial net_{h1}}{partial w_1} = i_1 = 0.05

    Putting it all together:

    frac{partial E_{total}}{partial w_{1}} = frac{partial E_{total}}{partial out_{h1}} * frac{partial out_{h1}}{partial net_{h1}} * frac{partial net_{h1}}{partial w_{1}}

    frac{partial E_{total}}{partial w_{1}} = 0.036350306 * 0.241300709 * 0.05 = 0.000438568

    You might also see this written as:

    frac{partial E_{total}}{partial w_{1}} = (sumlimits_{o}{frac{partial E_{total}}{partial out_{o}} * frac{partial out_{o}}{partial net_{o}} * frac{partial net_{o}}{partial out_{h1}}}) * frac{partial out_{h1}}{partial net_{h1}} * frac{partial net_{h1}}{partial w_{1}}

    frac{partial E_{total}}{partial w_{1}} = (sumlimits_{o}{delta_{o} * w_{ho}}) * out_{h1}(1 - out_{h1}) * i_{1}

    frac{partial E_{total}}{partial w_{1}} = delta_{h1}i_{1}

    We can now update w_1:

    w_1^{+} = w_1 - eta * frac{partial E_{total}}{partial w_{1}} = 0.15 - 0.5 * 0.000438568 = 0.149780716

    Repeating this for w_2, w_3, and w_4

    w_2^{+} = 0.19956143

    w_3^{+} = 0.24975114

    w_4^{+} = 0.29950229

    Finally, we’ve updated all of our weights! When we fed forward the 0.05 and 0.1 inputs originally, the error on the network was 0.298371109. After this first round of backpropagation, the total error is now down to 0.291027924. It might not seem like much, but after repeating this process 10,000 times, for example, the error plummets to 0.0000351085. At this point, when we feed forward 0.05 and 0.1, the two outputs neurons generate 0.015912196 (vs 0.01 target) and 0.984065734 (vs 0.99 target).

    If you’ve made it this far and found any errors in any of the above or can think of any ways to make it clearer for future readers, don’t hesitate to drop me a note. Thanks!

    And while I have you…

    In addition to dabbling in data science, I run Preceden timeline maker, the best timeline maker software on the web. If you ever need to create a high level timeline or roadmap to get organized or align your team, Preceden is a great option.

    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии

    А вот еще интересные материалы:

  1. Яшка сломя голову остановился исправьте ошибки
  2. Ясность цели позволяет целеустремленно добиваться намеченного исправьте ошибки
  3. Ясность цели позволяет целеустремленно добиваться намеченного где ошибка
  4. Обратное распространение ошибки habr
  5. Обратно робин бежал еще быстрее есть ли грамматическая ошибка