Español Português
preview
Нейронная сеть на практике: Функции активации

Нейронная сеть на практике: Функции активации

MetaTrader 5 — Машинное обучение |
78 0
Daniel Jose
Daniel Jose

Введение

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

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

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


Новое приложение

Всё, что было сказано в предыдущей статье, по-прежнему остаётся в силе. Помимо того, что мы там увидели, здесь мы рассмотрим новый код. Также мы кратко объясним каждую из выбранных функций. Прежде чем начать, хочу кое-что вам напомнить, мой читатель. Существует огромное количество функций активации. Некоторые из них популярнее других. Тем не менее, у каждой из них есть своё назначение и конкретная область применения, чтобы тем или иным образом оптимизировать скорость обучения. Далее мы рассмотрим, как некоторые из этих функций используются на практике, чтобы вы могли убедиться, насколько сильно даже простое изменение выбора может повлиять на результаты, которые мы можем получить в процессе обучения сети. В любом случае, сейчас мы сосредоточимся только на действительно интересной части системы, где будем использовать функции полностью изолированно. Так будет проще понять, какой выбор может оказаться лучшим в тех или иных ситуациях. В любом случае, не беспокойтесь об этом сейчас. Хотя математическая часть довольно сложная и запутанная, в конце мы постараемся значительно её упростить. Я хочу, чтобы вы действительно смогли понять, как работает нейронная сеть. Цель не в том, чтобы сказать вам: «Сделайте это. Не делайте того». Подобные указания ничего не добавляют к вашим знаниям. Это лишь создаёт ложную иллюзию того, что вы понимаете то, чего на самом деле не понимаете.

Итак, прежде чем рассмотреть каждую из выбранных функций, давайте быстро взглянем на код. В него были внесены небольшие изменения, и, на мой взгляд, некоторые из них заслуживают пояснения. Ниже я покажу его полностью. Так будет гораздо проще объяснить новые детали, которые были добавлены. Так что не пугайтесь с первого взгляда. Этот код гораздо проще, чем кажется.
001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property indicator_chart_window
004. #property indicator_plots 0
005. //+------------------------------------------------------------------+
006. #include <Canvas\Canvas.mqh>
007. //+------------------------------------------------------------------+
008. enum eFnActivate {
009.                     Sigmoid, 
010.                     Tangh, 
011.                     ReLU,
012.                     eLU, 
013.                     SoftSign, 
014.                     SoftPlus, 
015.                 };
016. //+------------------------------------------------------------------+
017. input eFnActivate   user_00 = Sigmoid;   //Функция активации
018. input double        user_01 = 10.0;      //Пределы
019. input double        user_02 = 0.1;       //Детализация
020. input double        user_03 = 9.0;       //Масштаб по оси Y
021. //+------------------------------------------------------------------+
022. CCanvas *canvas;
023. //+------------------------------------------------------------------+
024. struct st_00
025. {
026.     int     x,
027.             y,
028.             maxX,
029.             maxY;
030.     double  Position,
031.             dx, fx;
032. }global;
033. //+------------------------------------------------------------------+
034. void PlotTextPosition(void)
035. {
036.     uint w, h;
037.     string sz0;
038. 
039.     sz0 = StringFormat("%s Position: [%.1f] <<  fx: %.8f    dx: %.8f  >>", EnumToString(user_00), global.Position, global.fx, global.dx);
040.     TextGetSize(sz0, w, h);
041.     (*canvas).TextOut(global.x - (w / 2), global.maxY + 5, sz0, ColorToARGB(clrBlack)); 
042. }
043. //+------------------------------------------------------------------+
044. void Function_Curve(void)
045. {
046.     int x[], y[], d[];
047.     uint p;
048.     double fx, dx, cx, step, alpha;
049.     
050.     ArrayResize(x, (int)(((user_01 * 2) / user_02) + 1));
051.     ArrayResize(y, x.Size());
052.     ArrayResize(d, x.Size());
053. 
054.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / user_02;
055. 
056.     cx = -user_01;
057.     for (uint c = 0, m = x.Size(); c < m; c++, cx += user_02)
058.     {
059.         x[c] = global.x + (int)(step * cx);
060.         switch (user_00)
061.         {
062.             case Sigmoid:
063.                     fx = 1 / (1 + MathExp(-cx));
064.                     dx = MathExp(-cx) / MathPow(1 + MathExp(-cx), 2);
065.                 break;
066.             case Tangh:
067.                     fx = (2 / (1 + MathExp(-2 * cx))) - 1;
068.                     dx = (4 / MathPow(MathExp(-cx) + MathExp(cx), 2));
069.                 break;
070.             case ReLU:
071.                     fx = MathMax(0, cx);
072.                     dx = (cx <= 0 ? 0 : 1);
073.                 break;
074.             case eLU:
075.                     alpha = 0.75;
076.                     fx = (cx <= 0 ? (alpha * (MathExp(cx) - 1)) : cx);
077.                     dx = (cx <= 0 ? (alpha * MathExp(cx)) : 1);
078.                 break;
079.             case SoftSign:
080.                     fx = cx / (1 + MathAbs(cx));
081.                     dx = cx / MathPow(1 + MathAbs(cx), 2);
082.                 break;
083.             case SoftPlus:
084.                     fx = MathLog(1 + MathExp(cx));
085.                     dx = 1 / (1 + MathExp(-cx));
086.                 break;
087.             default:
088.                 fx = dx = 0;
089.                 break;
090.         }
091.         if (cx <= global.Position)
092.         {
093.             p = c;
094.             global.fx = fx;
095.             global.dx = dx;
096.         }
097.         y[c] = global.y - (int)(step * fx * user_03);
098.         d[c] = global.y - (int)(step * dx * user_03);
099.     }
100. 
101.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
102.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
103. 
104.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
105.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
106. 
107.     ArrayFree(d);
108.     ArrayFree(y);
109.     ArrayFree(x);
110. }
111. //+------------------------------------------------------------------+
112. void UpdateGraphics(int direct)
113. {
114.     double value = global.Position + user_02 * direct;
115. 
116.     global.Position = MathAbs(value) <= user_01 ? value : global.Position;
117. 
118.     (*canvas).Erase(ColorToARGB(clrWhite, 255));
119. 
120.     (*canvas).LineVertical(global.x, 0, global.maxY, ColorToARGB(clrRoyalBlue, 255));
121.     (*canvas).LineHorizontal(0, global.maxX, global.y, ColorToARGB(clrRoyalBlue, 255));
122. 
123.     Function_Curve();
124.     PlotTextPosition();
125. 
126.     (*canvas).Update(true);
127. }
128. //+------------------------------------------------------------------+
129. void Resize(void)
130. {
131.     uint w, h;
132.    
133.     global.maxX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
134.     global.maxY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
135. 
136.     if (canvas != NULL)
137.     {
138.         (*canvas).Destroy();
139.         delete canvas;
140.     }
141.     canvas = new CCanvas;
142.     (*canvas).CreateBitmapLabel("BL", 0, 0, global.maxX, global.maxY, COLOR_FORMAT_ARGB_NORMALIZE);
143.     global.x = global.maxX / 2;
144.     global.y = global.maxY / 2;
145.     TextGetSize("M", w, h);
146.     global.maxY -= (int)(h * 2);
147. }
148. //+------------------------------------------------------------------+
149. int OnInit()
150. {        
151.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false);
152.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false);
153.     ZeroMemory(global);
154.     canvas = NULL;
155. 
156.     return INIT_SUCCEEDED;
157. }
158. //+------------------------------------------------------------------+
159. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
160. {
161.     return rates_total;
162. }
163. //+------------------------------------------------------------------+
164. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
165. {
166.     int direct = 0;
167. 
168.     switch (id)
169.     {
170.         case CHARTEVENT_CHART_CHANGE:
171.             Resize();
172.             break;
173.         case CHARTEVENT_KEYDOWN:
174.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_LEFT)) direct = -1;
175.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_RIGHT)) direct = 1;
176.             break;
177.     }
178.     UpdateGraphics(direct);
179. }
180. //+------------------------------------------------------------------+
181. void OnDeinit(const int reason)
182. {
183.     (*canvas).Destroy();
184.     delete canvas;
185.     ChartSetInteger(0, CHART_SHOW_DATE_SCALE, true);
186.     ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, true);
187. }
188. //+------------------------------------------------------------------+

Да, этот код действительно выглядит заметно сложнее. Но не волнуйтесь. Я объясню каждую часть, чтобы вы могли это понять. На самом деле это проще, чем предыдущий код. Во-первых, в строке 8 было добавлено перечисление. Его цель — дать нам возможность выбрать нужное уравнение. Каждое из названий, включённых в это перечисление, на самом деле соответствует названию уравнения.

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

Итак, теперь, когда у нас уже есть названия уравнений, следующим изменением стал способ, которым пользователь выбирает уравнение для графического отображения. Раньше нам приходилось компилировать код каждый раз, когда мы вносили изменения — будь то в уравнение или в используемые параметры. Однако, как я уже упоминал в начале, цель здесь — дать пользователю больше интерактивности. Таким образом, вам не придётся постоянно компилировать код. Для этого были предусмотрены четыре входных параметра. Раньше у нас было два: диапазон значений и шаг построения кривой. Теперь к ним добавлены ещё два: выбор уравнения и коэффициент масштабирования по оси Y. Это связано с тем, что в некоторых случаях значения по оси Y оказываются очень близки к 1,0. В таком случае стоит предоставить пользователю возможность изменять масштаб оси Y. Таким образом, масштаб графика изменится, и построенную кривую будет проще анализировать.

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

Обратите внимание, что в отображении были изменены некоторые вещи. На самом деле изменение коснулось только той части, которая относится к отображаемому тексту. Раньше мы отображали только положение по оси X. Теперь, помимо этого значения, мы также отображаем название уравнения, его значение по оси Y в зависимости от значения по оси X, а также значение производной в той же точке по оси X. И всё это удалось сделать, изменив всего несколько строк кода. По сути, значения fx и dx, которые вы видите на изображении, отображаются там благодаря строке 39 кода. Обратите внимание, что в этой строке мы обращаемся к перечислению, чтобы получить имя функции. Мы также получаем доступ к значениям, заданным в строке 31. Эти две переменные в строке 31 раньше не существовали.

Итак, поскольку большая часть кода осталась без изменений, единственными изменениями стали добавленные строки 151, 152, 185 и 186, цель которых — сделать окно графика как можно чище. Поскольку это легко понять, мы можем сосредоточить внимание на главном: на процедуре в строке 44.

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

043. //+------------------------------------------------------------------+
044. void Function_Curve(void)
045. {
046.     int x[], y[], d[];
047.     uint p;
048.     double fx, dx, cx, step, alpha;
049.     
050.     ArrayResize(x, (int)(((user_01 * 2) / user_02) + 1));
051.     ArrayResize(y, x.Size());
052.     ArrayResize(d, x.Size());
053. 
054.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / user_02;
055. 
056.     cx = -user_01;
057.     for (uint c = 0, m = x.Size(); c < m; c++, cx += user_02)
058.     {
059.         x[c] = global.x + (int)(step * cx);
060.         switch (user_00)
061.         {
062.             case Sigmoid:
063.                     fx = 1 / (1 + MathExp(-cx));
064.                     dx = MathExp(-cx) / MathPow(1 + MathExp(-cx), 2);
065.                 break;
066.             case Tangh:
067.                     fx = (2 / (1 + MathExp(-2 * cx))) - 1;
068.                     dx = (4 / MathPow(MathExp(-cx) + MathExp(cx), 2));
069.                 break;
070.             case ReLU:
071.                     fx = MathMax(0, cx);
072.                     dx = (cx <= 0 ? 0 : 1);
073.                 break;
074.             case eLU:
075.                     alpha = 0.75;
076.                     fx = (cx <= 0 ? (alpha * (MathExp(cx) - 1)) : cx);
077.                     dx = (cx <= 0 ? (alpha * MathExp(cx)) : 1);
078.                 break;
079.             case SoftSign:
080.                     fx = cx / (1 + MathAbs(cx));
081.                     dx = cx / MathPow(1 + MathAbs(cx), 2);
082.                 break;
083.             case SoftPlus:
084.                     fx = MathLog(1 + MathExp(cx));
085.                     dx = 1 / (1 + MathExp(-cx));
086.                 break;
087.             default:
088.                 fx = dx = 0;
089.                 break;
090.         }
091.         if (cx <= global.Position)
092.         {
093.             p = c;
094.             global.fx = fx;
095.             global.dx = dx;
096.         }
097.         y[c] = global.y - (int)(step * fx * user_03);
098.         d[c] = global.y - (int)(step * dx * user_03);
099.     }
100. 
101.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
102.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
103. 
104.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
105.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
106. 
107.     ArrayFree(d);
108.     ArrayFree(y);
109.     ArrayFree(x);
110. }
111. //+------------------------------------------------------------------+

Если вы сравните этот код с тем, что мы рассматривали в предыдущей статье, то сразу заметите, что здесь появились две новые переменные. Они находятся в строке 48. Даже переменные fx и dx изменили своё первоначальное назначение. Теперь обе переменные будут использоваться для временного хранения вычисленных значений.

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

Предположим, вы хотите добавить в это приложение новую функцию активации, чтобы можно было графически отобразить как результат уравнения, так и его производную. Как вам следует поступить? Итак, для начала перейдите к строке 8, где указаны названия функций, и добавьте название своей новой функции. Помните, что в имени НЕЛЬЗЯ использовать пробелы. Если нужно разделить слова, используйте символ подчёркивания ( _ ).

После этого вы сможете выбрать это уравнение при использовании скомпилированного приложения в MetaTrader 5. Однако пока вы не увидите никакого графика. Чтобы построить график, вам нужно просто перейти к процедуре, показанной в предыдущем фрагменте, и сделать следующее: в строке 60 у нас есть оператор switch. Он будет выбирать, какой код нужно выполнить, в зависимости от значения, соответствующего уравнению, выбранному пользователем.

Таким образом, в строке 61 после открывающей фигурной скобки нужно нажать ENTER, ввести «case», поставить пробел и сразу после этого ввести название вашего уравнения, за которым следует двоеточие ( : ). Снова нажмите ENTER и введите «break», а затем точку с запятой ( ; ). Таким образом вы создадите структуру, очень похожую на ту, что приведена в коде. А теперь самое интересное. Между двоеточием ( : ) и словом «break» нужно написать нечто очень похожее на то, что приведено в каждой из структур между строками 62 и 86. Обратите внимание, что у нас есть fx = одно значение и dx = другое. И что это значит? Итак, в fx нужно указать выражение функции, а в dx — выражение её производной. И это всё, что вам нужно сделать. Остальное делает сам код или это выполняется во время его использования в MetaTrader 5. Если вы не поняли, как работать с оператором switch, ознакомьтесь с другой моей статьёй. В этой статье я подробно объясняю, как работать с оператором switch.

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


Сигмоидальная функция активации

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

Здесь мы следуем тем же правилам, которые были описаны в предыдущей статье. Итак, если вы посмотрите на код в строке 63, то увидите, какое уравнение там используется. Это же уравнение приведено ниже в математической записи.

Её производная, определённая в строке 64, представлена уравнением, показанным ниже.

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

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

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


Функция активации «гиперболический тангенс»

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

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

Итак, при выборе функции «гиперболический тангенс» в приложении вы увидите следующую анимацию.

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

Есть и другой способ записать это же уравнение. Его можно увидеть ниже.

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

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

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

И, конечно же, есть и тригонометрическая форма, показанная ниже.

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

Хорошо, продолжим, потому что в этой статье ещё осталось кое-что показать.


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

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

Уравнение из строки 72 можно увидеть в выражении, приведённом ниже.

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

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


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

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

Когда вы выберете eLU, то увидите нечто похожее на следующую анимацию.

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

"Ух, какое громоздкое выражение. Вы уверены, что это записывается именно так?" Да, уважаемый читатель. Так математически записывается выражение, реализованное в строке 76. Я знаю, что это выглядит жутко и пугающе. Однако это связано с самим способом представления некоторых вещей в математической нотации. Прежде чем продолжить, давайте посмотрим на выражение производной. Оно находится в строке 77, а его математическая запись показана ниже.

"Боже мой, для этих двух выражений точно придётся вызывать экзорциста. Какая жуть". Что ж, независимо от того, что вы сейчас думаете и насколько сильно вас могут пугать эти выражения, в обоих можно заметить нечто общее: греческую букву АЛЬФА. Она находится в строке 75 кода. Назначение этого параметра альфа — изменять кривизну уравнения. В зависимости от того, какое значение вы ему зададите, можно получить более крутую или более пологую кривую. Или, другими словами, радиус дуги, которую вы видите на графике, может стать немного меньше или больше. Это создаёт небольшую разницу в выходных значениях перцептрона. Однако, если посмотреть на оба выражения, можно заметить, что, в отличие от исходной функции ReLU, у этой функции eLU нет проблем с нулём, поскольку само уравнение может обрабатывать нулевое значение в производной.

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


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

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

Код в строке 80 реализует в точности то, что показано в следующем выражении.

Производная в строке 81 задаётся выражением, приведённым ниже.

Если вы внимательно присмотритесь, то заметите, что SoftSign находится где-то между сигмоидой и гиперболическим тангенсом. Однако в определённых ситуациях она может оказаться гораздо более подходящей, чем любая из них. В зависимости от задачи, которую вы хотите решить, она может помочь градиенту находить минимум несколько эффективнее, чем сигмоида или гиперболический тангенс. Кроме того, благодаря способу вычисления эта функция может вычисляться быстрее в более глубоких сетях. И это в некоторых случаях даёт заметную разницу во время обучения. Модели, использующие TensorFlow, часто отдают предпочтение этой функции вместо сигмоиды и гиперболического тангенса именно из-за этого фактора во время обучения модели.

В завершение перейдём к последней выбранной функции. Мы рассмотрим её в следующем разделе.


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

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

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

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

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

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


Заключительные замечания

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

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

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









Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/13909

Прикрепленные файлы |
Anexo.mq5 (6.26 KB)
От базового к среднему уровню: Ресурсы От базового к среднему уровню: Ресурсы
В этой статье вы познакомитесь с концепцией, которая во многих случаях может оказаться чрезвычайно полезной и значительно упростить обмен вашими приложениями и проектами. Хотя эту концепцию непросто полностью объяснить в одной статье, того, что будет здесь изложено и объяснено, уже хватит, чтобы в будущем мы смогли сделать многое, в том числе и то, что иначе было бы невозможно. Именно поэтому эта статья ещё не была опубликована: чтобы у вас был вспомогательный материал и начальная база для изучения.
Нейросети в трейдинге: Диффузионная генерация торговых планов (DPCC) Нейросети в трейдинге: Диффузионная генерация торговых планов (DPCC)
Рассматриваем адаптацию фреймворка DPCC для построения торговых планов с учётом ограничений счёта. Разбираются принципы диффузионной генерации траекторий, модельной проекции и проверки допустимости последовательности действий. Для финансовых рынков используется сценарное моделирование состояния счёта. Практическая часть посвящена OpenCL-кернелам DPCCRollout и DPCCRolloutReduce, которые рассчитывают последствия планов и определяют их допустимость по заданным ценовым сценариям.
От начального к среднему уровню: Объекты и подокна (I) От начального к среднему уровню: Объекты и подокна (I)
В статье подробно рассматриваются создание и размещение объекта OBJ_CHART в подокнах, при этом особое внимание уделяется различиям между главным окном и подокнами. Показывается, как интегрировать индикаторы с помощью ресурсов (#resource, ChartIndicatorAdd) и определять нужное подокно по краткому имени индикатора. В результате код становится более стабильным, переносимым и удобным для повторного использования.
От начального к среднему уровню: Подокна (IV) От начального к среднему уровню: Подокна (IV)
В этой статье мы увидим, что не всё так, как многим кажется поначалу. Одна из самых интересных особенностей программирования заключается в том, что мы можем гарантировать, что всё всегда будет именно так, как мы запланировали. Поэтому внимательно прочитайте эту статью, чтобы разобраться в некоторых из самых запутанных понятий, связанных с использованием подокон. Если вы поймёте то, что будет здесь объяснено, вы сможете понять многое из того, что мы будем делать дальше.