четверг, 28 июня 2018 г.

Введение в нейронные сети

Эта статья-заметка освещает основные понятия нейронных сетей и как их строить применяя python.

Основное понятие нейронных сетей - регрессия.
Линейная регрессия - предсказание количественной зависимости одного показателя от другого

Простейшим представителем нейронных сетей - является перцептрон, который описывает 1 нейрон

Перцептрон можно описать формулой:


Где
* X - входные активации (факты влияющие на выходное решение)
* W - вектор весов (вес каждого факта в принятии решения)
* b - смещение или порог срабатывания, если перенести его вправо
Исходя из формулы, видно, что перцептрон активируется, если сумма входов * веса > порога срабатывания

Если представить графически, то это будет выглядеть так:

Наша цель заключается в таком подборе весов W, чтобы перцептрон активировался ( > 0 )
* Первоначально веса (W) задаются случайными значениями.
В моем случае это 0
* Чтобы обучить перцептрон, прогоним его по примерам которые мы знаем (tar)
* Если ошиблись в предсказании значения res:
 o Получили 1, вместо 0, то уменьшаем вес на значение наблюдения
 o Получили 0, вместо 1, то наоборот увеличиваем вес, чтобы следующий раз получить больше

Res тут считается по формуле выше = вес * входная активация + смещение, но для перемножения без циулов используются функции numpy для работы с матрицами: * dot - перемножить матрицы * T - транспонирование матрицы - поворот ее на 90 градусов, т.к. перемножаться могут только матрицы у которых число строк у первой = числу столбцов у второй
import numpy as np
w = np.array([0,0,0])
x = np.array([[1,1,0.3],[1,0.4,0.5],[1,0.7,0.8]])
tar = np.array([1,1,0])
 
def activate(sum):
       return 1 if sum > 0 else 0
       
for i in range(3):
       res = activate(w.T.dot(x[i]))
       print(str(i) + ". " + str(res))
       if res != tar[i]:
             if res == 1:
                    w = w - x[i]
             else:
                    w = w + x[i]
       print(w)
#print(w)
#1, 1, 0.3 -> (1, 0.4, 0,15) -> 0, 0.6, -0.2 -> (0, 0.42, -0.16) -> 1, 1.3, 0.6 или -1, -0.1, -1

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

* Линейная (w*x + b)
Просто возвращаем значение этого выражения, без каких либо сравнений

* Перцептрона (f(x,w,b)=1, если w*x+b>0, иначе 0)
Рисунок выше.
Такие функции не всегда удобны, так как от них нельзя найти производную, которая понадобится нам дальше, для вычисления ошибки.
Рассмотрим другие дифференцируемые функции активации:

* сигмоидальная sigma(w*x+b)=1/(1+e**−(w*x+b))

Наиболее распространенная, часто используется поумолчанию.

* тангенс tanh(w*x+b) = 2*sigma(2*x)-1


* улучшенный линейный нейрон ReLU
= greatest(w*x+b, 0)

* Аналитическое прилижение к ReLU (softplus)
=ln(1+e**w*x+b)


Графически активации можно представить на общем графике :

Сигмоидальная активационная функция удобна тем, что ее значения лежат в ограниченном диапазоне от 0 (полная ложь) до 1 (полная правда)


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

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

Функция используется для оценки ошибки нейрона, где:
* Y^ - предсказанное значение
Предсказаное значение считается по тойже формуле перцептрона, но активационная функция чаще всего = сигмойде
* Y - реальное значение

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

Где на плоскости веса, а по оси Z - величина ошибки.

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

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

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


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

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

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

* скорость обучения регулируем константой a
Wновое = Wтекущее - a*J (J - значение градиентного спуска)
+/- изменения константы a (размера шага обучения):
 ** двигаемся большими шагами - можно пропустить минимум
 ** маленькими - можем остановиться в локальном минимуме, вместо глобального

* критерий останова:
 ** изменение w -> 0 или достаточно мало
 ** достигли максимального числа итерации

Градиент для ошибки нейрона
Можно описать как среднее значение приращений (производных) отклоенений ошибок:


Градиент ошибки сигмоидального нейрона:
Посчитанная производная для активации с сигмоидальным нейроном:

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

Пример обучения на Python:
* «J_last» - считаем целевую функцию (отклонения предсказания от реального)
* «J_der» - считаем производную по формуле выше.
Этим определяем направление роста функции.
* «self.w = self.w - J_der * learning_rate» -
Обновляем веса нейрона = вес - производная * скорость обучения.
Минус, т.к. нам нужно двигаться в противоположную сторону от роста функции ошибки.
* «J_new» - пересчитываем целевую функцию еще раз на новых весах
* «(J_last - J_new) < eps»
Если изменение функции ошибки очень мало, значит мы дошли до минимума функции, где изменение очень мало (производная стремится к нулю).
Это значит или что мы дошли до локального минимума или подобрали нужные веса для правильного предсказания:
import numpy as np

def update_mini_batch(self, X, y, learning_rate, eps):
        J_last = J_quadratic(self, X, y)
        J_der = compute_grad_analytically(self, X, y)
self.w = self.w - J_der * learning_rate J_new = J_quadratic(self, X, y) return int((J_last - J_new) < eps)


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

Многослойный перцептрон:

Здесь Z - сумматорная функция (взвешенная сумма входов)
1 слой - это входы
а - активация
w - веса
x - входные примеры
l - номер слоя
J - градиент ошибки
( Если нужно классифицировать несколько классов, то на выходе может быть более 1 нейрона. )
Одной из сложнейших задач многослойного перцептрона является определение влиятения ошибки предыдущего слоя на текущий.


Алгоритм обратного распространения ошибки:
С помощью градиентного спуска мы уже рассмотрели как найти ошибку в выходном слое.
Чтобы определить влияние других слоев на выходную ошибку применяем алгоритм обратного распространения ошибки:

1. Вектор ошибок выходного слоя, зная целевую функцию по активациям
2. Ошибки предыдущем слое, зная ошибку в текущем
3. Производная по смещениям
4. Производная по весам

Общий алгоритм теперь такой:
* Сначала мы вычисляем выходные активации, сохраняем промежуточные активации (они понадобятся при обратном проходе расчета ошибок)
* Используем ф.1, чтобы найти ошибки в выходном слое
* Используем ф.2, чтобы найти ошибки во всех предыдущих слоях
* Используем ф.3 и ф.4 получаем остальные параметры
* Обновляем параметры и повторяем
* Останавливаемся при достижении нужных критериев



На основе stepic курса: "Нейронные сети".
Сертификат о прохождении