Найдя производную ошибки, вычислив тем самым наклон функции ошибки (подсветив фонариком, подходящий участок для спуска), нам необходимо обновить наш вес в сторону уменьшения ошибки (сделать шаг в сторону подсвеченного фонарем участка). Затем повторяем те же действия, но уже с новыми (обновлёнными) значениями.
Для понимания как мы будем обновлять наши коэффициенты (делать шаги в нужном направлении), прибегнем к помощи так уже нам хорошо знакомой – иллюстрации. Напомню, величина шага зависит от крутизны наклона прямой (tgφ). А значит величина, на которую мы обновляем наши веса, в соответствии со своим входом, и будет величиной производной по функции ошибки:
Вот теперь иллюстрируем:
Из графика видно, что для того чтобы обновить вес в большую сторону, до значения (w2), нужно к старому значению (w1) прибавить дельту (∆w), откуда: (w2 = =w1+∆w). Приравняв (∆w) к производной ошибки (величину которой уже знаем), мы спускаемся на эту величину в сторону уменьшения ошибки.
Так же замечаем, что (E2 – E1 = -∆E) и (w2 – w1 = ∆w), откуда делаем вывод:
∆w = -∆E/∆w
Ничего не напоминает? Это почти то же, что и дельта линейного классификатора (∆А = E/х), подтверждение того что наша эволюция прошла с поэтапным улучшением математического моделирования. Таким же образом, как и с обновлением коэффициента (А = А+∆А), линейного классификатора, обновляем весовые коэффициенты:
новый wij = старый wij -(– ∆E/∆w)
Знак минус, для того чтобы обновить вес в большую сторону, для уменьшения ошибки. На примере графика – от w1 до w2.
В общем виде выражение записывается как:
новый wij = старый wij – dE/dwij
Еще одно подтверждение, постепенного, на основе старого аппарата, хода эволюции, в сторону улучшения классификации искусственного нейрона.
Теперь, зайдем с другой стороны функции ошибки:
Снова замечаем, что (E2 – E1 = ∆E) и (w2 – w1 = ∆w), откуда делаем вывод:
∆w = ∆E/∆w
В этом случае, для обновления весового коэффициента, в сторону снижения функции ошибки, а значит до значения находящееся левее (w1), необходимо от значения (w1) вычесть дельту (∆w):
новый wij = старый wij - ∆E/∆w
Получается, что независимо от того, какого знака производная ошибки от весового коэффициента по входу, вычитая из старого значения – значение этой производной, мы движемся в сторону уменьшения функции ошибки. Откуда можно сделать вывод, что последнее выражение, общее для всех возможных случаев обновления градиента.
Запишем еще раз, обновление весовых коэффициентов в общем виде:
новый wij = старый wij – dE/dwij
Но мы забыли еще об одной важной особенности… Сглаживания! Без сглаживания величины дельты обновления, наши шаги будут слишком большие. Мы подобно кенгуру, будем прыгать на большие расстояния и можем перескочить минимум ошибки! Используем прошлый опыт, чтоб устранить этот недочёт.
Вспоминаем старое выражение при нахождении сглаженного значения дельты линейного классификатора: ∆А = L*(Е/х). Где (L) – скорость обучения, необходимая для того, чтобы мы делали спуск, постепенно, небольшими шашками.
Ну и наконец, давайте запишем окончательный вариант выражения при обновлении весовых коэффициентов:
новый wij = старый wij – L*(dE/dwij)
Еще раз можем убедиться, в постепенном улучшении свойств, в ходе эволюции искусственного нейрона. Много из того что реализовывали ранее остается, лишь небольшая часть подверглась эволюционному улучшению.
Ложный минимум
Если еще раз взглянуть на трехмерную поверхность, можно увидеть, что метод градиентного спуска может привести в другую долину, которая расположена правее, где минимум значения будет меньше относительно той долины, куда попали мы сейчас, т.е. эта долина не является самой глубокой.
На следующей иллюстрации показано несколько вариантов градиентного спуска, один из которых приводит к ложному минимуму.
Поздравляю! Мы прошли самую основу в теории нейронных сетей – метод градиентного спуска. Освоив этот материал, в дальнейшем, изучение теории искусственных нейронных сетей, не будет представлять для вас значимого труда.
Ну вот и настало время проверить практически, все наши умозаключения, касающиеся работы нашего искусственного нейрона, после первой эволюции. Для этого прибегнем к помощи Python, но сначала покажем наш список с данными, с которого мы это всё затеяли:
Если по координатам построить точки на плоскости, то мы заметим, что их значения лежат возле значений графика функции – y = 2x + 2,5.
Программа
import random
# Инициализируем любым числом крутизны наклона прямой w1 = A
w1 = 0.4
w1_vis = w1 # Запоминаем начальное значение крутизны наклона
# Инициализируем параметр w2 = b – отвечающий за точку прохождения прямой через ос Y
w2 = random.uniform(-4, 4)
w2_vis = w2 # Запоминаем начальное значение параметра
# Вывод данных начальной прямой
print('Начальная прямая: ', w1, '* X + ', w2)
# Скорость обучения
lr = 0.001
# Зададим количество эпох
epochs = 3000
# Создадим массив (выборку входных данных) входных данных x1
arr_x1 = [1, 2, 3, 3.5, 4, 6, 7.5, 8.5, 9]
# Значение входных данных второго входа всегда равно 1
x2 = 1
# Создадим массив значений (целевых значений)
arr_y = [4.3, 7, 8.0, 10.1, 11.3, 14.2, 18.5, 19.3, 21.4]
# Прогон по выборке
for e in range(epochs):
for i in range(len(arr_x1)): # len(arr) – функция возвращает длину массива
# Получить x координату точки
x1 = arr_x1[i]
# Получить расчетную y, координату точки
y = w1 * x1 + w2
# Получить целевую Y, координату точки
target_Y = arr_y[i]
# Ошибка E = -(целевое значение – выход нейрона)
E = – (target_Y – y)
# Меняем вес при x, в соответствии с правилом обновления веса
w1 -= lr * E * x1
# Меняем вес при x2 = 1
#w2 -= rate * E * x2 # Т.к. x2 = 1, то этот множитель можно не писать
w2 -= lr * E
# Вывод данных готовой прямой
print('Готовая прямая: ', w1, '* X + ', w2)
Данный код, как и все другие, вы можете скачать по ссылке: https://github.com/CaniaCan/neuralmaster
Опишем код программы:
В самом начале программы импортируем модуль для работы со случайными числами:
import random
При помощи которого, случайным числом, создаем весовой коэффициент параметра (w2 = b) – отвечающий за точку прохождения прямой через ос Y:
w2 = random.uniform(-4, 4)
Метод модуля random – uniform(from, to), генерирует случайное вещественное число от from до to включительно.
В нашей программе, как видно, не так много изменений, по сравнению с той что мы написали до этого. Мы добавили второй вход (х2 = 1), со своим весовым коэффициентом (w2). Коэффициент (А) – переименовали в весовой коэффициент (w1), параметр (b) – в весовой коэффициент (w2). Ну и конечно же, реализовали новую улучшенную функцию ошибки, и обновление весовых коэффициентов по методу градиентного спуска.
В результате чего, наш эволюционировавший нейрон, теперь гораздо лучше справляется с задачей классификации. Теперь он может классифицировать данные по двум входам, тем самым получая линейный классификатор с пересечением прямой по всей оси Y, а не только строго в точке нуля.
Давайте взглянем на результат чтобы убедиться в этом:
Начальная прямая: 0.4 * X + 0.3652477754014445
Готовая прямая: 2.058410130422831 * X + 2.5013583972057263
Вы видите! Как наш искусственный нейрон прекрасно справляется с задачей. Даже еле различимые на глаз данные, он легко смог линейно разделить.
Теперь зададим условие, как это делали ранее. Если данные расположены выше классифицирующий линии, то это вид жирафа, а все что ниже – крокодилы. Будем делать это подавая на входы, значения, которые нейрон до этого не видел и посмотрим, сможет ли обученный нейрон, самостоятельно определить к какому виду они принадлежат.
x1 = input("Введите значение ширины Х: ")
x1 = int(x1)
T = input("Введите значение высоты Y: ")
T = int(T)
y = w1 * x1 + w2
# Условие
if T > y:
print('Это жираф!')
else:
print('Это крокодил!')
После ввода наших значений, следует условие, которое проверяет, какого вида эти данные, жирафы или крокодилы, и возвращает ответ на поставленный вопрос.
Введите значение ширины Х: 4
Введите значение высоты Y: 15
Это жираф!
Резюмируя проделанную работу:
Получив задание, классифицировать два вида животных, по параметрам, определяющим размеры их тела, с некоторой выборкой данных (значений и ответов), мы смогли запрограммировать искусственный нейрон, основываясь на элементарных знаниях математики, а именно линейной функции, проходящей через начало координат (y = Ax). Определив, что, данные лежащие выше прямой относились бы к одному классу, а все точки данных лежащих ниже – к другим. Тем самым мы лишили бы себя утомительной работы по самостоятельному анализу полученных данных, для классификации их на два вида. Говоря иными словами, мы доверили этот процесс искусственному нейрону, который мы создали на основе знания линейного классификатора. Теперь нейрон самостоятельно классифицирует все данные поступившие на его единственный вход. Более того, после процесса обучения, с обученным коэффициентом (А), мы легко можем задать условие, которое по вводимым пользователем значениям, определяло, к какому виду они принадлежат.
Мы полностью автоматизировали процесс классификации! Избавили себя от рутины сейчас и в последующем. И это только на самой простейшей форме “искусственной жизни” нейрона, с одним входом и выходом!
Но биологическая, как и цифровая, природа, не столь однообразна. До этого мы рассматривали “тепличные данные” – (y = Ax). Данные – которые мы могли классифицировать, имея лишь один вход. Во многих случаях классификации обойтись одним коэффициентом (А), линейной функции, невозможно, приходится использовать весь спектр возможности линейной функции. Для использования этих дополнительных возможностей, необходимо эволюционировать искусственный нейрон, добавив к нему еще один вход.
Добавив на второй вход параметр (b), отвечающий за точку прохождения прямой через ось Y, в качестве обучаемого коэффициента, мы получаем весь арсенал возможностей линейной функции (y = Ax+b) при классификации.
Так как у параметра (b), в линейной функции (y = Ax1+b), нет произведения на значение переменной, то на второй вход, в качестве данных, всегда поступает единица (x2 = 1). Откуда на выходе получаем взвешенную сумму: y = Ax1+bx2. При х2 = 1, на выходе получаем y = Ax1+b. И наконец, назвав коэффициенты, при входных данных – весовыми коэффициентами, изменили их обозначение – w1 = А, а w2 = b, в итоге: y = w1x1+w2.
Но обучая наш нейрон, как в первом случае, на выходе мы не получим нужных ответов. Оказалось, всё дело в том, что второй вход, участвует в процессе обучения независимо от первого, и наоборот. Каждый тянет одеяло на себя. Оба входа, как бы мешают друг другу подстроить свои веса. Вследствие чего, при вычислении ошибки, получали непредсказуемый результат для подстройки обоих весовых коэффициентов. И было бы здорово, если бы с каждым последующим обучающим примером, мы смогли уменьшать функцию ошибки.
Для решения этой проблемы, нам пришлось ознакомится с методом градиентного спуска. В ходе рассмотрения этого метода, мы ознакомились с производными, узнали о правилах дифференцирования. В следствии чего, научились обновлять весовые коэффициенты, в сторону уменьшения ошибки по каждому из входов.
Суть метода – обновление весовых коэффициентов на своих входах, в зависимости от функции ошибки, таким образом, чтобы плавно двигаться в сторону её уменьшения. Другими словами, найти на каждом из входов, такое значение веса, чтоб ошибка на выходе, для всех этих весовых коэффициентов, была минимальной и как следствие удовлетворяла их всех.
Получив необходимые выражения, убедились, что изменений в математике функционирования искусственного нейрона, не так уж и много. Подобно биологической эволюции, наша тоже произошла постепенно. Ранее приобретённые навыки для классификации, лишь немногим усовершенствовались, а новые в свою очередь, выходят исходя из старых.
О проекте
О подписке