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

Нейронная сеть на практике: График функции ReLU

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

Введение

В предыдущей статье «Нейронная сеть на практике: Появление C_Neuron» мы начали делать всё немного более организованно. Мы начали создавать класс, цель которого — содержать всё, что нам потребуется включить в нейрон. Это позволит нам в будущем соединить несколько нейронов в архитектуру, предназначенную, по сути, для создания небольшой простой нейронной сети. Ничего уровня ChatGPT или чего-то подобного. Цель здесь — показать энтузиастам нейронных сетей, как они работают изнутри. Без использования каких-либо дополнительных ресурсов. Программируя всё самостоятельно.

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

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

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

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

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


Понимание проблемы

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

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

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

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

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

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


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

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

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

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

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

В приведённом выше выражении мы имеем результат вычисления производной. Но обратите внимание на следующее: мы определяем значение для x меньше нуля и для x больше нуля. Что происходит, когда x равно нулю? Что ж, в таком случае значение будет неопределённым. Но в вычислительной практике так работать нельзя. Поэтому нам приходится прибегать к небольшой хитрости или уловке. Когда значение x равно нулю, мы сами определяем, каким будет значение производной. Обычно его определяют как ноль. Но это не означает, что оно действительно равно нулю. Это всего лишь искусственный приём, используемый в вычислениях. Итак, это была математическая часть объяснения. Но нас, как программистов, эти математические обозначения интересуют не слишком сильно. По правде говоря, некоторые из них выглядят как иероглифы — настолько они запутанны и сложны для восприятия. Прошу прощения у математиков, которые здесь присутствуют, но я не смог удержаться от этой шутки.

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


Графическое представление функций

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

Итак, учитывая это, давайте посмотрим на исходный код нашего индикатора. Он приведён ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property indicator_chart_window
004. #property indicator_plots 0
005. //+------------------------------------------------------------------+
006. #include <Canvas\Canvas.mqh>
007. //+------------------------------------------------------------------+
008. #define def_Limit   10.0
009. #define def_Step    0.5
010. //+------------------------------------------------------------------+
011. CCanvas *canvas;
012. //+------------------------------------------------------------------+
013. struct st_00
014. {
015.     int     x,
016.             y,
017.             maxX,
018.             maxY;
019.     double  Position;
020. }global;
021. //+------------------------------------------------------------------+
022. void PlotTextPosition(void)
023. {
024.     uint w, h;
025.     string sz0;
026. 
027.     sz0 = StringFormat("Position: [%.1f]", global.Position);
028.     TextGetSize(sz0, w, h);
029.     (*canvas).TextOut(global.x - (w / 2), global.maxY + 5, sz0, ColorToARGB(clrBlack)); 
030. }
031. //+------------------------------------------------------------------+
032. void Function_Curve(void)
033. {
034. }
035. //+------------------------------------------------------------------+
036. void UpdateGraphics(int direct)
037. {
038.     double value = global.Position + def_Step * direct;
039. 
040.     global.Position = MathAbs(value) <= def_Limit ? value : global.Position;
041. 
042.     (*canvas).Erase(ColorToARGB(clrWhite, 255));
043. 
044.     (*canvas).LineVertical(global.x, 0, global.maxY, ColorToARGB(clrRoyalBlue, 255));
045.     (*canvas).LineHorizontal(0, global.maxX, global.y, ColorToARGB(clrRoyalBlue, 255));
046. 
047.     PlotTextPosition();
048.     Function_Curve();
049. 
050.     (*canvas).Update(true);
051. }
052. //+------------------------------------------------------------------+
053. void Resize(void)
054. {
055.     uint w, h;
056.    
057.     global.maxX = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
058.     global.maxY = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
059. 
060.     if (canvas != NULL)
061.     {
062.         (*canvas).Destroy();
063.         delete canvas;
064.     }
065.     canvas = new CCanvas;
066.     (*canvas).CreateBitmapLabel("BL", 0, 0, global.maxX, global.maxY, COLOR_FORMAT_ARGB_NORMALIZE);
067.     global.x = global.maxX / 2;
068.     global.y = global.maxY / 2;
069.     TextGetSize("M", w, h);
070.     global.maxY -= (int)(h * 2);
071. }
072. //+------------------------------------------------------------------+
073. int OnInit()
074. {      
075.     ZeroMemory(global);
076.     canvas = NULL;
077. 
078.     return INIT_SUCCEEDED;
079. }
080. //+------------------------------------------------------------------+
081. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
082. {
083.    return rates_total;
084. }
085. //+------------------------------------------------------------------+
086. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
087. {
088.     int direct = 0;
089. 
090.     switch (id)
091.     {
092.         case CHARTEVENT_CHART_CHANGE:
093.             Resize();
094.             break;
095.         case CHARTEVENT_KEYDOWN:
096.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_LEFT)) direct = -1;
097.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_RIGHT)) direct = 1;
098.             break;
099.     }
100.     UpdateGraphics(direct);
101. }
102. //+------------------------------------------------------------------+
103. void OnDeinit(const int reason)
104. {
105.     (*canvas).Destroy();
106.     delete canvas;
107. }
108. //+------------------------------------------------------------------+

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

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

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

Итак, давайте быстро разберём этот код, чтобы понять другие его детали. В строке 11 я определяю переменную, которая будет использоваться для доступа к стандартной библиотеке MQL5. Обратите внимание, что я определяю эту переменную как указатель. И на это есть причина. Определив это таким образом, я смогу проверить, инициализирован ли объект.

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

Теперь, в строке 53, мы видим довольно любопытную процедуру. По сути, эта процедура служит для настройки размеров используемого растрового объекта с помощью стандартной библиотеки. Вся процедура довольно проста. Однако в строке 60 мы выполняем проверку, чтобы убедиться, был ли создан экземпляр класса CCanvas. Если экземпляр уже создан, мы удалим его с графика, а затем, в строке 65, инициализируем указатель, чтобы снова создать экземпляр класса. Именно по этой причине я использую указатель в объявлении в строке 11. Но зачем я всё это делаю? Чтобы это понять, необходимо разобраться, как MetaTrader 5 на самом деле выполняет индикатор.

Когда мы указываем MetaTrader 5, что хотим добавить индикатор на график, MetaTrader 5 начинает выполнять код с функции OnInit, расположенной в строке 73. В этой функции мы инициализируем различные элементы. Например, в строке 75 мы инициализируем глобальные переменные, обеспечивая, чтобы все они начинались с нуля. А в строке 76 мы указываем, что указатель равен нулю. То есть он не указывает ни на какой экземпляр. После этого MetaTrader 5 генерирует новое событие, которое будет обработано функцией в строке 86. Первое событие — CHARTEVENT_CHART_CHANGE, которое сообщает всем приложениям на графике, что что-то изменилось. Поскольку наше приложение обрабатывает это событие, в строке 93 мы вызовем процедуру из строки 53. И именно в этот момент мы начнём создавать экземпляр класса CCanvas из стандартной библиотеки, поскольку в строке 65 создадим этот экземпляр.

Хорошо, но действительно ли была нужна вся эта сложность? На самом деле это не усложнение. Если вы измените размеры графика, MetaTrader 5 снова сгенерирует событие CHARTEVENT_CHART_CHANGE. Благодаря этому мы можем всегда поддерживать правильные размеры, не проверяя постоянно каждый элемент. Но, возвращаясь к объяснению, в строке 81 у нас находится обработчик событий OnCalculate. Он ничего не будет делать, но его необходимо объявить, поскольку мы используем приложение типа индикатора. В строке 86 находится обработчик событий OnChartEvent. Как мы уже видели, он используется во время инициализации или при изменениях на графике. Но он также позволяет обрабатывать нажатия клавиш со стрелками вправо и влево, тем самым изменяя положение, которое мы вскоре увидим.

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

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

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

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


Отображение ReLU и её производной

Ниже показан код для отображения как функции активации ReLU, так и её производной.

31. //+------------------------------------------------------------------+
32. void Function_Curve(void)
33. {
34.     int x[], y[], d[];
35.     uint p;
36.     double fx, step;
37.     
38.     ArrayResize(x, (int)(((def_Limit * 2) / def_Step) + 1));
39.     ArrayResize(y, x.Size());
40.     ArrayResize(d, x.Size());
41. 
42.     step = MathMin(global.maxY, global.maxX) / (x.Size() * 1.0) / def_Step;
43. 
44.     fx = -def_Limit;
45.     for (uint c = 0, m = x.Size(); c < m; c++, fx += def_Step)
46.     {
47.         p = (fx <= global.Position ? c : p);
48.         x[c] = global.x + (int)(step * fx);
49.         y[c] = global.y - (int)(step * MathMax(0, fx));
50.         d[c] = global.y - (int)(step * (fx <= 0 ? 0 : 1));
51.     }
52. 
53.     (*canvas).PolylineThick(x, y, ColorToARGB(clrIndigo, 255), 3, STYLE_SOLID, LINE_END_ROUND);
54.     (*canvas).PolylineThick(x, d, ColorToARGB(clrDarkOrange, 255), 3, STYLE_SOLID, LINE_END_ROUND);
55. 
56.     (*canvas).FillCircle(x[p], y[p], 5, ColorToARGB(clrForestGreen, 255));
57.     (*canvas).FillCircle(x[p], d[p], 5, ColorToARGB(clrFireBrick, 255));
58. 
59.     ArrayFree(d);
60.     ArrayFree(y);
61.     ArrayFree(x);
62. }
63. //+------------------------------------------------------------------+

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

Поистине впечатляет, что мы можем сделать, когда действительно готовы за это взяться. Хорошо, но давайте разберёмся, что показывает эта анимация. Для этого нам следует вновь обратить внимание на фрагмент, показанный в начале раздела. Итак, давайте рассмотрим детали. В анимации вы можете увидеть два отрезка прямой: один цвета индиго, то есть оттенка синего, а другой — оранжевого. Также видны две круглые фигуры: одна зелёная, а другая красная. Эти объекты создаются соответственно следующими строками: 53 — индиговая линия, 54 — оранжевая линия, 56 — зелёный круг и, наконец, 57 — красный круг.

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

Итак, в строках 38–40 мы выделяем достаточно места для хранения всех значений в пределах, заданных в строке 8. В строке 42 мы вычисляем размер шага или, другими словами, разрешение, с которым будут строиться кривые. Поскольку это значение зависит как от размера графического окна, так и от значений, заданных в строках 8 и 9 кода, нам, возможно, придётся немного скорректировать некоторые параметры, чтобы получить хорошо отображаемую кривую. Если зернистость слишком высокая, вы заметите, что при отрисовке кривой появляется много неровностей. Поскольку ReLU практически представляет собой прямую, пока мы ещё не можем заметить эту проблему, но чуть позже вы ясно её увидите.

Поскольку эта часть кода не изменится, вам нужно будет лишь изменить значение в девятой строке, чтобы увеличить разрешение и добиться более плавного отображения кривой. В любом случае, в строке 44 мы инициализируем значение fx. Именно это значение будет использоваться в расчётах. Далее мы переходим к циклу for, в строке 45. Этот цикл выполняет практически всю работу, поскольку именно он отвечает за создание кривых или линий в зависимости от конкретного случая.

В строке 47 мы выполняем небольшой поиск, чтобы определить индекс в массиве. Этот индекс будет использоваться для отображения кругов на графике. Но почему это делается именно так? Причина довольно проста: чтобы избежать лишнего повторения вычислений. Без этой строки 47 нам пришлось бы выполнять вычисления дважды: один раз — чтобы построить графики функции и её производной, а второй — чтобы определить, где разместить точки на кривой, чтобы видеть, где выполняется выборка. В строке 48 находится вычисление, определяющее, где будут располагаться точки на оси X. Эти точки зависят как от предела, так и от количества делений. Оба значения объявляются в строках 8 и 9 кода.

Отлично, теперь давайте рассмотрим две единственные строки, которые нам нужно будет изменить, чтобы отобразить разные графики и функции. Я хочу, чтобы вы, уважаемый читатель, обратили особое внимание на строки 49 и 50. Обратите внимание, что они практически одинаковы, за исключением конца каждой из них. Знаете, что это значит? Итак, если вы внимательно читали статью, то заметите, что конец строки 49 точно соответствует приведённому выше вычислению для ReLU. В свою очередь, конец строки 50 содержит вычисление производной функции ReLU. Может показаться удивительным, что эти вычисления так просто выразить в коде, но это действительно так. Настолько, что абсолютно весь код останется неизменным, независимо от ситуации. Единственное, что изменится, — это содержимое строк 49 и 50.

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


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

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

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

В любом случае воспользуйтесь знаниями, изложенными в этой статье. А в качестве домашнего задания почему бы не создать сетку на декартовой плоскости? Это несложно сделать. Тем не менее, я оставлю это в качестве упражнения, чтобы каждый мог попытаться реализовать это как можно лучше. Идея заключается в том, чтобы можно было видеть значения по осям X и Y, перемещая маленькие кружки по графику.

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

Прикрепленные файлы |
Anexo.mq5 (4.29 KB)
Стресс-тестирование последовательностей сделок методом Монте-Карло в MQL5 Стресс-тестирование последовательностей сделок методом Монте-Карло в MQL5
Бэктест показывает лишь одну траекторию из множества возможных исходов. Данный MQL5-скрипт выполняет 1000 бутстрап-передискретизаций Монте-Карло для ряда P&L по сделкам, строит на графике веерную диаграмму процентилей с помощью CCanvas и выводит вероятность разорения, Value at Risk (VaR) и наихудшую просадку на уровне 95-го процентиля. В результате получается практический взгляд на траекторный риск и подверженность просадкам, выходящий за рамки одной кривой эквити.
От начального к среднему уровню: Подокна (III) От начального к среднему уровню: Подокна (III)
В этом тексте подробно рассматривается использование подокон в индикаторах MQL5: их базовое создание, обнаружение экземпляров и предотвращение дублирования. В нём также рассматриваются INDICATOR_SHORTNAME, запрос сведений об окнах и индикаторах графика, а также различие между отображением на основном графике и в отдельном окне. Здесь также показано, как задать высоту и границы подокна, чтобы стандартизировать интерфейс и упростить размещение объектов.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Нейронная сеть на практике: Рождение C_Neuron Нейронная сеть на практике: Рождение C_Neuron
В статье показано, как инкапсулировать нейрон в MQL5 с помощью класса C_Neuron, с весами, смещением и числом входов, задаваемым параметром. Мы подробно разберём вычисление функции потерь на основе метода наименьших квадратов и организацию данных обучения в массивах. Так можно легко менять входы и повторять эксперименты, не изменяя реализацию.