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, *mseobsahuje 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_mseobsahuje 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);
#importVytvoř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_TRENDMinimá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
