Функции активации

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

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

Пороговая (ступенчатая) функция активации

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

  • возможно только два состояния (активирован или нет);
  • нейрон активируется при достижении порогового значения θ.

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

При θ=0 функция имеет нижеследующий график.

Пороговая функция активации

Пороговая (ступенчатая) функция активации

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

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

const double theta 0;
//———
double ActStep(double x)
  {
   return (>= theta ? 1 : 0);
  }

На Python реализация так же легка.

theta 0
def ActStep (x):
  return 1 if >= theta else 0

Линейная функции активации

Линейная функция активации задается линейной функцией:

где:

  • а определяет угол наклона линии;
  • b смещение линии по вертикале.

Как частный случай линейной функции активации, при a=1 и b=0 функция имеет вид .

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

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

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

График линейной функции

График линейной функции

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

const double 1.0;
const double 0.0;
//———
double ActLinear(double x)
  {
   return (b);
  }

На Python реализация аналогична.

1.0
0.0
def ActLinear (x):
  return b

Логистическая функция активации (Сигмоида) #

Логистическая функция активации, наверное, самая распространенная S-образная функция. Значения функции находятся в диапазоне от 0 до 1, они асимметричны относительно точки [0, 0.5]. График функции напоминает пороговую функцию, но с плавным переходом между состояниями.

Математическая формула функции имеет вид:

Данная функция позволяет нормализовать выходные значений функции в диапазоне [0, 1]. Благодаря этому свойству использование логистической функции вводит понятие вероятности в практику нейронных сетей. Это свойство широко эксплуатируется в нейронах выходного слоя при решении задач классификации, когда количество нейронов выходного слоя равно количеству классов, а отнесение объекта к тому или иному классу определяется по максимальной вероятности (максимальному значению выходного нейрона).

Функция дифференцируема на всем промежутке допустимых значений. Значение производной легко рассчитывается через значение функции по формуле:

График логистической функции (Сигмоида)

График логистической функции (Сигмоида)

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

где:

  • a — растягивает диапазон значений функции от 0 до a;
  • b — по аналогии с линейной функцией смещает результирующее значения.

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

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

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

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

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

const double 1.0;
const double 0.0;
//———
double ActSigmoid(double x)
  {
   return (/ (exp(-x)) - b);
  }

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

import math
1.0
0.0
def ActSigmoid (x):
  return a / (1 + math.exp(-x)) - b

Гиперболический тангенс (tanh) #

Альтернативой логистической функции активации является гиперболический тангенс (Tanh). Он так же, как и логистическая функция, имеет S-образный график, а значения функции нормализованы. Но они принадлежат диапазону от −1 до 1, и изменение состояния нейрона осуществляется в 2 раза быстрее. График функции также асимметричен, но в отличие от логистической функции центр асимметрии находится в центре координат.

Функция дифференцируема на всем промежутке допустимых значений. Значение производной легко считается через значение функции по формуле:

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

График функции гиперболический тангенс (TANH)

График функции гиперболического тангенса (TANH)

 

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

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

double ActTanh(double x)
  {
   return tanh(x);
  }

И аналогичная реализации на Python.

import math
def ActTanh (x):
  return math.tanh(x)

Выпрямленный линейный блок (ReLU) #

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

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

График функции ReLU

График функции ReLU

 

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

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

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

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

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

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

LReLU

Leaky ReLU — ReLU с утечкой

a = 0,01

PReLU

Parametric ReLU — параметрическая ReLU

Параметр a подбирается в процессе обучения нейронной сети

RReLU

Randomized ReLU — рандомизированный ReLU

Параметр a задается случайным образом при создании нейронной сети

Классический вариант ReLU удобно реализовать с помощью функции max(). Реализация ее вариантов потребует создания константы или переменной a. Вариант инициализации будет зависеть от выбранной функции (LReLU / PReLU / RReLU). А внутри нашей функции активации создадим логическое разветвление, в зависимости от значение получаемого параметра.

const double 0.01;
//———
double ActPReLU(double x)
  {
   return (>= 0 ? x : a * x);
  }

На Python реализация аналогична.

0.01
def ActPReLU (x):
  return x if >= 0 else a * x

Softmax #

Если выше рассмотренные функции рассчитывались на данных исключительно отдельно взятого нейрона, то функция Softmax применяется ко всем нейронам отдельного слоя сети (как правило, последнего). Наряду с сигмоидой, вводится понятие вероятности в нейронные сети. Диапазон значений функции лежит между 0 и 1, а сумма всех выходных значений нейронов взятого слоя равна 1.

Математическая формула функции имеет вид:

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

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

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

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

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

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

В теле функции сначала проверяем размер массива исходных данных X. Полученный массив должен быть не нулевой длины. Затем изменяем размер массива для записи результатов Y. При неудачном выполнении какой-либо из операций выходим из функции с результатом false.

bool SoftMax(doubleX[], doubleY[])
  {
   uint total = X.Size();
   if(total == 0)
      return false;
   if(ArrayResize(Ytotal) <= 0)
      return false;

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

//--- Расчет экспоненты для каждого элемента массива
   double sum = 0;
   for(uint i = 0i < totali++)
      sum += Y[i] = exp(X[i]);

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

//--- Нормализация данных в массиве
   for(uint i = 0i < totali++)
      Y[i] /= sum;
//---
   return true;
  }

На Python реализация выглядит намного проще, т.к. функция Softmax уже реализована в библиотеке Scipy.

from scipy.special import softmax
def ActSoftMax (X):
  return softmax(X)

Swish #

В октябре 2017 года команда исследователей из Google Brain провела работу по автоматическому поиску функций активации. Результаты этой работы были представлены в статье Searching for Activation Functions. В статье приведены результаты тестирования целого ряда функций в сравнении с ReLU. Наилучшие показатели были достигнуты в нейросетях с функцией активации Swish. Только замена ReLU на Swish (без переобучения) позволила улучшить показатели нейронных сетей.

График функции Swish

График функции Swish

Математическая формула функции имеет вид:

Параметр β влияет на нелинейность функции и может быть принят константой при проектировании сети или подбираться в процессе обучения. При β=0 функция сводится к масштабируемой линейно функции.

При β=1 график функции Swish приближается к ReLU. Но в отличие от последней, функция дифференцируема на всем промежутке значений.

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

Реализация функции в программном коде MQL5 аналогична представленной выше Сигмоиде. Параметр a заменяется полученным значением взвешенной суммы и добавляется параметр нелинейности β.  

const double b=1.0;
//———
double ActSwish(double x)
  {
   return (x / (exp(-b * x)));
  }

Аналогична реализация на Python.

import math
b=1.0
def ActSwish (x):
  return x / (1 + math.exp(-b * x))

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

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

bool vector::Activation(
  vector&                   vect_out,      // вектор для получения значений
  ENUM_ACTIVATION_FUNCTION  activation     // тип функции
   );
 
 
bool matrix::Activation(
  matrix&                   matrix_out,    // матрица для получения значений
  ENUM_ACTIVATION_FUNCTION  activation     // тип функции
   );

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

Идентификатор

Описание

AF_ELU

Экспоненциальная линейная единица

AF_EXP

Экспоненциальная

AF_GELU

Линейная единица ошибки Гаусса

AF_HARD_SIGMOID

Жесткий сигмоид

AF_LINEAR

Линейная

AF_LRELU

Линейный выпрямитель с «утечкой» (Leaky ReLU)

AF_RELU

Усеченное линейное преобразование ReLU

AF_SELU

Масштабированная экспоненциальная линейная функция (Scaled ELU)

AF_SIGMOID

Сигмоид

AF_SOFTMAX

Softmax

AF_SOFTPLUS

Softplus

AF_SOFTSIGN

Softsign

AF_SWISH

Swish-функция

AF_TANH

Гиперболический тангенс

AF_TRELU

Линейный выпрямитель с порогом