Neuronové sítě bez kouzel: Occamova břitva a pohled programátora
Umělá inteligence nemusí být esoterická. Středoškolská matematika, pár derivací a čistý kód v C++/MQL5 stačí. Žádná magie – jen čísla v maticích, funkce a disciplína.

Problém bez mlhy: co skutečně potřebujeme
Texty o neuronových sítích často obsahují mnoho „kouře a zrcadel“. Použil jsem Occamovu břitvu a nechal jen to nezbytné – matematické minimum, které si každý pamatuje ze střední školy:
- násobení a sčítání čísel,
- jednoduché nelineární funkce (tanh, sigmoid, ReLU),
- základy práce s vektory a maticemi,
- a derivace pro učení (backpropagation).
To je vše. Všechno ostatní je jen komfort, ne nutnost.
Derivace: jediná „povinně volitelná“ kapitola
Bez derivací dostaneme jen výsledek dopředného průchodu. Ale učení vyžaduje vědět, jak upravit každou váhu. Tady nastupuje řetězové pravidlo: spočítat chybu, poslat ji zpět vrstvami a upravit váhy a biasy podle derivací aktivačních a lineárních částí.
- Chyba: rozdíl mezi predikcí a realitou (MSE).
- Gradient: derivace složené funkce (řetězové pravidlo).
- Aktualizace: malý krok ve směru snížení chyby (SGD).
Pokud zvládneš derivaci sigmoidu, tanh a „ReLU derivaci“, máš pokryto 99 % potřebného.
C++ a MQL5: Matematika převedená do praxe
V C++ mám odlehčenou DLL knihovnu: husté vrstvy, aktivace, dopředný průchod, backpropagation, trénink na jednotlivých vzorcích i na dávkách (batch). Všechno přes jednoduché C API (handle, žádné globální singletony). V MQL5 EA bere data přímo z grafu, normalizuje je, pošle do sítě a vykreslí výstupy.
y = f(Wx + b)
— dopředný průchod.dL/dW
,dL/db
— gradienty přes derivace aktivací.- SGD, MSE, ořezávání gradientu, He/Xavier inicializace.
- Batch API (
NN_ForwardBatch
,NN_TrainBatch
) - API pro váhy (
NN_GetWeights
,NN_SetWeights
)
Výsledek: AI přestává vypadat kouzelně. Zůstává programátorské řemeslo s pochopitelným chováním.
Occamova břitva v bodech
- Dopředný průchod: násobení matice a vektoru.
- Aktivace: tanh/sigmoid/ReLU jako obyčejné funkce.
- Ztráta (MSE): střední kvadratická chyba.
- Backpropagation: čisté řetězové pravidlo.
Složitost vzniká skládáním jednoduchých věcí, ne záhadami.
Co mi tato cesta dala
Vnímám síť jako výpočetní systém, ne jako proroctví. Chápu její limity i možnosti. A mám nástroj, který můžu rozšířit: uložit/načíst váhy, přidat Adam, více výstupů, propojit s logikou obchodování.
Středoškolská matematika, trpělivost a odstranění balastu stačí.
Závěr
Neuronové sítě nejsou černá skříňka. Jsou to čísla v maticích a pár derivací – matematický mechanismus převedený do jazyků, které mám rád: C++ a MQL5.
Stažení (EA / zdroje)
NNMQL5 Dokumentace:
Odlehčená DLL knihovna s C API pro jednoduchou MLP (stack hustých vrstev) s:
- dopřednou inferencí (
NN_Forward
,NN_ForwardBatch
) - učením na vzorku i dávkách (
NN_TrainOne
,NN_TrainBatch
) - správou více instancí sítí přes handle
- manipulací s váhami (
NN_GetWeights
,NN_SetWeights
)
Síť je stavová (váhy v RAM), neukládá se – perzistence se řeší v hostitelském kódu.
API (rozhraní C)
int NN_Create(void);
void NN_Free(int h);
bool NN_AddDense(int h, int inSz, int outSz, int act);
int NN_InputSize(int h);
int NN_OutputSize(int h);
bool NN_Forward(int h, const double* in, int in_len, double* out, int out_len);
bool NN_TrainOne(int h, const double* in, int in_len,
const double* tgt, int tgt_len, double lr, double* mse);
bool NN_ForwardBatch(int h, const double* in, int batch, int in_len,
double* out, int out_len);
bool NN_TrainBatch(int h, const double* in, int batch, int in_len,
const double* tgt, int tgt_len, double lr, double* mean_mse);
bool NN_GetWeights(int h, int i, double* W, int Wlen, double* b, int blen);
bool NN_SetWeights(int h, int i, const double* W, int Wlen, const double* b, int blen);
Ukázka použití v MQL5
#import "NNMQL5.dll"
int NN_Create(); void NN_Free(int h);
bool NN_AddDense(int h,int inSz,int outSz,int act);
bool NN_Forward(int h,const double &in[],int in_len,double &out[],int out_len);
bool NN_TrainOne(int h,const double &in[],int in_len,const double &tgt[],int tgt_len,double lr,double &mse);
int NN_InputSize(int h); int NN_OutputSize(int h);
#import
int h=-1;
int OnInit(){
h=NN_Create(); if(h<=0) return INIT_FAILED;
if(!NN_AddDense(h, 32, 64, 1)) return INIT_FAILED; // ReLU
if(!NN_AddDense(h, 64, 1, 3)) return INIT_FAILED; // Lineární
return INIT_SUCCEEDED;
}
void OnDeinit(const int r){ if(h>0) NN_Free(h); }
void OnTimer(){
static double x[32], t[1], y[1]; double mse;
// naplnění x[] a t[] daty...
NN_TrainOne(h, x, 32, t, 1, 0.01, mse);
NN_Forward(h, x, 32, y, 1);
Print("y=",y[0]," mse=",mse);
}
Rychlý přehled (návratové hodnoty)
Funkce | Úspěch (true/>0) | False/0 znamená |
---|---|---|
NN_Create | Pozitivní handle | Alokace selhala (vzácné) |
NN_Free | — | — (idempotentní, bezpečné volat opakovaně) |
NN_AddDense | Vrstva přidána | Neplatný handle / nesoulad velikostí |
NN_InputSize | Deklarovaná vstupní velikost | 0 (neplatný handle / prázdná síť) |
NN_OutputSize | Deklarovaná výstupní velikost | 0 (neplatný handle / prázdná síť) |
NN_Forward | out[] naplněno | Neplatný handle / nesoulad velikostí / prázdná síť |
NN_TrainOne | Provedena aktualizace vah, *mse obsahuje MSE | Neplatný handle / nesoulad velikostí / prázdná síť |
NN_ForwardBatch | out[] naplněno pro celou batch | Neplatný handle / nesoulad velikostí |
NN_TrainBatch | Provedena aktualizace vah pro batch, *mean_mse obsahuje průměrné MSE | Neplatný handle / nesoulad velikostí / prázdná síť |
NN_GetWeights | Váhy a biasy načteny do bufferů | Neplatný handle / index mimo rozsah / nesoulad velikostí |
NN_SetWeights | Váhy a biasy nastaveny | Neplatný handle / index mimo rozsah / nesoulad velikostí |
NNMQL5_Predictor — Příklady (Dark)
Ukázky kódu vycházející z indikátoru NNMQL5_Predictor.mq5
(MetaTrader 5 • NNMQL5.dll).
Import DLL (MQL5)
// NNMQL5.dll — importy
#import "NNMQL5.dll"
int NN_Create();
void NN_Free(int h);
bool NN_AddDense(int h,int inSz,int outSz,int act);
int NN_InputSize(int h);
int NN_OutputSize(int h);
bool NN_Forward(int h,const double &in[],int in_len,double &out[],int out_len);
bool NN_TrainOne(int h,const double &in[],int in_len,const double &tgt[],int tgt_len,double lr,double &mse);
bool NN_ForwardBatch(int h,const double &in[],int batch,int in_len,double &out[],int out_len);
bool NN_TrainBatch (int h,const double &in[],int batch,int in_len,const double &tgt[],int tgt_len,double lr,double &mean_mse);
bool NN_GetWeights(int h,int i,double &W[],int Wlen,double &b[],int blen);
bool NN_SetWeights(int h,int i,const double &W[],int Wlen,const double &b[],int blen);
#import
Vytvoření sítě (topologie)
int h = NN_Create();
int Lookback=32, Hidden=64, Depth=2, HiddenAct=2; // 2=TANH
NN_AddDense(h, Lookback, Hidden, HiddenAct);
for(int i=1; i<Depth; i++) NN_AddDense(h, Hidden, Hidden, HiddenAct);
NN_AddDense(h, Hidden, 1, 3); // 3=LINEAR (regrese)
Dataset z ceny (z‑score, sliding window)
void MeanStdSlice(const double &arr[], int first, int last, double &mean, double &stdev){
int n = last - first + 1;
if(n<=1){ mean=0.0; stdev=1.0; return; }
double s=0.0; for(int i=first;i<=last;i++) s += arr[i];
mean = s / n;
double v=0.0; for(int i=first;i<=last;i++){ double d=arr[i]-mean; v+=d*d; }
stdev = MathSqrt(v/(n-1)); if(stdev<=1e-12) stdev=1.0;
}
Mini‑batch trénink
bool TrainEpoch(int h, const double &X[], const double &T[],
int N, int Lookback, double &lr, double &mean_mse, int BatchSize){
if(BatchSize<=1) return NN_TrainBatch(h,X,N,Lookback,T,1,lr,mean_mse);
double acc=0.0; int cnt=0, B=MathMax(2,BatchSize);
for(int base=0; base<N; base+=B){
int b=MathMin(B,N-base);
double xin[]; ArrayResize(xin,b*Lookback);
double tgt[]; ArrayResize(tgt,b);
for(int bi=0; bi<b; ++bi){
int src=(base+bi)*Lookback, dst=bi*Lookback;
for(int k=0;k<Lookback;k++) xin[dst+k]=X[src+k];
tgt[bi]=T[base+bi];
}
double bmse=0.0; bool ok=NN_TrainBatch(h,xin,b,Lookback,tgt,1,lr,bmse);
if(!ok) return false; acc+=bmse; cnt++;
}
mean_mse = (cnt? acc/cnt : 0.0); return true;
}
Historické predikce +1..+5
for(int step=1; step<=FutureH && step<=5; ++step){
bool ok = NN_Forward(h, state, Lookback, y, 1);
if(!ok) break;
double den = y[0]*g_std + g_mean; // de‑norm
// ulož do bufferů Pred1..Pred5
}
Autoregresní future path
string pref = StringFormat("NNFUT_%s_%s", _Symbol, EnumToString(_Period));
// výpočet kroků dopředu a řetěz OBJ_TREND
Minimální indikátor
#property indicator_chart_window
#property indicator_buffers 5
double Pred1[],Pred2[],Pred3[],Pred4[],Pred5[];
int h=0, Lookback=32, Hidden=64, Depth=2, FutureH=5;
int OnInit(){ SetIndexBuffer(0,Pred1,INDICATOR_DATA); /* … */ return INIT_SUCCEEDED; }
void OnDeinit(const int reason){ if(h!=0) NN_Free(h); }

Autor: Tomáš Bělák — Remind