Files

839 lines
28 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef NEURAL_NET_MQH
#define NEURAL_NET_MQH
#include "Statistics.mqh"
class CNeuralNet {
private:
int m_inputs;
int m_hidden;
int m_outputs;
matrix m_W1;
vector m_b1;
matrix m_W2;
vector m_b2;
matrix m_mW1, m_vW1;
vector m_mb1, m_vb1;
matrix m_mW2, m_vW2;
vector m_mb2, m_vb2;
int m_t;
double m_adamBeta1;
double m_adamBeta2;
double m_adamEps;
double m_beta1T;
double m_beta2T;
double m_l2;
int m_epochsTrained;
double m_lastLoss;
bool m_initialized;
// HeInit per ReLU (NeuroBook §1.3)
double HeScale(int fanIn) const {
return MathSqrt(6.0 / MathMax(1, fanIn));
}
// Dropout (NeuroBook §6.2)
double m_dropoutRate;
double m_dropoutMask[];
// BatchNorm semplificata su hidden (NeuroBook §6.3)
// Running stats (EMA) + learnable affine
vector m_bnGamma; // scala learnable
vector m_bnBeta; // shift learnable
vector m_bnRunningMean; // media mobile
vector m_bnRunningVar; // varianza mobile
double m_bnMomentum;
double m_bnEps;
// Debug: loss history
double m_lossHistory[];
string m_lossCsvFn;
void ApplyDropout(vector &h) {
for(int i = 0; i < h.Size(); i++) {
m_dropoutMask[i] = ((double)MathRand() / 32767.0) > m_dropoutRate ? 1.0 : 0.0;
h[i] *= m_dropoutMask[i];
}
}
void ApplyBN(vector &h) {
// Normalizza h IN PLACE: per-neuron stats (NeuroBook §6.3)
for(int i = 0; i < h.Size(); i++) {
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
double standardized = (h[i] - m_bnRunningMean[i]) / denom;
h[i] = m_bnGamma[i] * standardized + m_bnBeta[i];
}
}
// Normalizza E AGGIORNA running stats (training, batch_size=1).
// Usa running stats per normalizzare (stessa modalità di inference) per
// evitare degenerazione con batch=1 (var=0). (NeuroBook §6.3, online BN)
void TrainBN(const vector &h_in, vector &h_out) {
h_out.Resize(h_in.Size());
for(int i = 0; i < h_in.Size(); i++) {
// Update running stats (per-neuron EMA)
m_bnRunningMean[i] = m_bnMomentum * m_bnRunningMean[i] + (1.0 - m_bnMomentum) * h_in[i];
double dx = h_in[i] - m_bnRunningMean[i];
m_bnRunningVar[i] = m_bnMomentum * m_bnRunningVar[i] + (1.0 - m_bnMomentum) * dx * dx;
// Normalize using running stats (same as ApplyBN)
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
double standardized = (h_in[i] - m_bnRunningMean[i]) / denom;
h_out[i] = m_bnGamma[i] * standardized + m_bnBeta[i];
}
}
// Backward attraverso BN: dh_out[i] = dh_norm[i] * gamma[i] / sqrt(runningVar[i] + eps)
void BNBackward(vector &dh_out, const vector &h_in, const vector &dh_norm) {
dh_out.Resize(h_in.Size());
for(int i = 0; i < h_in.Size(); i++) {
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
dh_out[i] = dh_norm[i] * m_bnGamma[i] / denom;
}
}
double ReLU(double x) const {
return (x > 0.0) ? x : 0.0;
}
double ReLUDeriv(double x) const {
return (x > 0.0) ? 1.0 : 0.0;
}
void Softmax(vector &v) const {
double maxVal = v[0];
for(int i = 1; i < v.Size(); i++)
if(v[i] > maxVal) maxVal = v[i];
double sum = 0.0;
for(int i = 0; i < v.Size(); i++) {
v[i] = MathExp(v[i] - maxVal);
sum += v[i];
}
double eps = DATA_EPS(sum);
if(sum > eps) {
for(int i = 0; i < v.Size(); i++)
v[i] /= sum;
} else {
double eq = 1.0 / v.Size();
for(int i = 0; i < v.Size(); i++)
v[i] = eq;
}
}
void BiasInit(vector &v) {
for(int i = 0; i < v.Size(); i++)
v[i] = 0.0;
}
void ZeroMatrix(matrix &m) {
for(int r = 0; r < (int)m.Rows(); r++)
for(int c = 0; c < (int)m.Cols(); c++)
m[r][c] = 0.0;
}
void ZeroVector(vector &v) {
for(int i = 0; i < v.Size(); i++)
v[i] = 0.0;
}
void AdamUpdate(matrix &param, matrix &m, matrix &v, matrix &grad, double lr) {
for(int r = 0; r < (int)param.Rows(); r++) {
for(int c = 0; c < (int)param.Cols(); c++) {
double g = grad[r][c] + m_l2 * param[r][c];
m[r][c] = m_adamBeta1 * m[r][c] + (1.0 - m_adamBeta1) * g;
v[r][c] = m_adamBeta2 * v[r][c] + (1.0 - m_adamBeta2) * g * g;
double mHat = m[r][c] / (1.0 - m_beta1T);
double vHat = v[r][c] / (1.0 - m_beta2T);
param[r][c] -= lr * mHat / (MathSqrt(vHat) + m_adamEps);
}
}
}
void AdamUpdate(vector &param, vector &m, vector &v, vector &grad, double lr) {
for(int i = 0; i < param.Size(); i++) {
double g = grad[i] + m_l2 * param[i];
m[i] = m_adamBeta1 * m[i] + (1.0 - m_adamBeta1) * g;
v[i] = m_adamBeta2 * v[i] + (1.0 - m_adamBeta2) * g * g;
double mHat = m[i] / (1.0 - m_beta1T);
double vHat = v[i] / (1.0 - m_beta2T);
param[i] -= lr * mHat / (MathSqrt(vHat) + m_adamEps);
}
}
public:
CNeuralNet()
: m_inputs(0), m_hidden(0), m_outputs(0),
m_t(0), m_beta1T(1.0), m_beta2T(1.0),
m_l2(0), m_epochsTrained(0), m_lastLoss(0.0),
m_initialized(false),
m_dropoutRate(0), m_bnMomentum(0), m_bnEps(0) {}
void Init(int inputs, int hidden, int outputs, double l2 = -1, double dropoutRate = -1) {
m_inputs = inputs;
m_hidden = hidden;
m_outputs = outputs;
// Iperparametri dall'architettura:
// hidden min. = 2 (un neurone non può fare computazione utile)
// β₂/β₁ ratio = 100 (Adam originale: 0.999/0.9)
// BN window = 5× hidden (momentum più lento per layer più grandi)
int totalParams = inputs * hidden + hidden + hidden * outputs + outputs;
int minHidden = MathMax(hidden, 2);
m_l2 = 1.0 / MathMax(totalParams, 1); // L2 = 1/param (1 = nessun parametro → L2=1)
m_dropoutRate = 1.0 / MathSqrt((double)minHidden); // Dropout: 1/sqrt(hidden)
m_adamBeta1 = 1.0 - 1.0 / (double)minHidden; // β₁ = 1 - 1/hidden
m_adamBeta2 = 1.0 - 1.0 / (double)MathMax(minHidden * 100, 200); // β₂ = 1 - 1/(hidden×100)
m_adamEps = DATA_EPS(1.0); // Adam ε: machine epsilon
m_bnMomentum = 1.0 - 1.0 / (double)MathMax(minHidden * 5, 10); // BN momentum = 1 - 1/(hidden×5)
m_bnEps = DATA_EPS(1.0); // BN ε: machine epsilon
// Se chiamata esterna vuole override, usa quelli
if(l2 >= 0) m_l2 = l2;
if(dropoutRate >= 0) m_dropoutRate = dropoutRate;
m_W1.Init(inputs, hidden);
m_b1.Init(hidden);
m_W2.Init(hidden, outputs);
m_b2.Init(outputs);
// He Init per ReLU (NeuroBook §1.3)
{
double scale = HeScale(inputs);
MathSrand(GetTickCount());
for(int r = 0; r < inputs; r++)
for(int c = 0; c < hidden; c++)
m_W1[r][c] = ((double)MathRand() / 32767.0 * 2.0 - 1.0) * scale;
}
BiasInit(m_b1);
{
double scale = HeScale(hidden);
for(int r = 0; r < hidden; r++)
for(int c = 0; c < outputs; c++)
m_W2[r][c] = ((double)MathRand() / 32767.0 * 2.0 - 1.0) * scale;
}
BiasInit(m_b2);
m_mW1.Init(inputs, hidden); ZeroMatrix(m_mW1);
m_vW1.Init(inputs, hidden); ZeroMatrix(m_vW1);
m_mb1.Init(hidden); ZeroVector(m_mb1);
m_vb1.Init(hidden); ZeroVector(m_vb1);
m_mW2.Init(hidden, outputs); ZeroMatrix(m_mW2);
m_vW2.Init(hidden, outputs); ZeroMatrix(m_vW2);
m_mb2.Init(outputs); ZeroVector(m_mb2);
m_vb2.Init(outputs); ZeroVector(m_vb2);
// Dropout mask
ArrayResize(m_dropoutMask, hidden);
// BatchNorm (NeuroBook §6.3)
m_bnGamma.Init(hidden);
m_bnBeta.Init(hidden);
m_bnRunningMean.Init(hidden);
m_bnRunningVar.Init(hidden);
for(int i = 0; i < hidden; i++) {
m_bnGamma[i] = 1.0;
m_bnBeta[i] = 0.0;
m_bnRunningMean[i] = 0.0;
m_bnRunningVar[i] = 1.0;
}
m_t = 0;
m_beta1T = 1.0;
m_beta2T = 1.0;
m_epochsTrained = 0;
m_lastLoss = 0.0;
m_initialized = true;
ArrayResize(m_lossHistory, 0);
m_lossCsvFn = "";
}
void ForwardPass(vector &inp, vector &output, vector &h1Cache) {
h1Cache.Resize(m_hidden);
for(int i = 0; i < m_hidden; i++) {
double sum = m_b1[i];
for(int j = 0; j < m_inputs; j++)
sum += inp[j] * m_W1[j][i];
h1Cache[i] = ReLU(sum);
}
// BatchNorm inferenza: normalizza con running stats (NeuroBook §6.3)
ApplyBN(h1Cache);
// Dropout scaling in inferenza: scale = 1 - dropoutRate (NeuroBook §6.2)
if(m_dropoutRate > 0) {
for(int i = 0; i < h1Cache.Size(); i++)
h1Cache[i] *= (1.0 - m_dropoutRate);
}
output.Resize(m_outputs);
for(int i = 0; i < m_outputs; i++) {
double sum = m_b2[i];
for(int j = 0; j < m_hidden; j++)
sum += h1Cache[j] * m_W2[j][i];
output[i] = sum;
}
Softmax(output);
}
void Forward(vector &inp, vector &output) {
vector h1Cache;
ForwardPass(inp, output, h1Cache);
}
double TrainSample(vector &inp, vector &target, double lr, double weight = 1.0) {
if(!m_initialized) return -1.0;
// ── Forward ──
// Layer 1: W1*x + b1 → z1 → ReLU → h1_raw → Dropout → h1_drop → BN → h1_norm
vector z1(m_hidden);
vector h1_raw(m_hidden);
for(int i = 0; i < m_hidden; i++) {
z1[i] = m_b1[i];
for(int j = 0; j < m_inputs; j++)
z1[i] += inp[j] * m_W1[j][i];
h1_raw[i] = ReLU(z1[i]);
}
// Dropout (NeuroBook §6.2): salva in m_dropoutMask, applica a h1_drop
vector h1_drop(m_hidden);
if(m_dropoutRate > 0.0) {
for(int i = 0; i < m_hidden; i++) {
m_dropoutMask[i] = ((double)MathRand() / 32767.0) > m_dropoutRate ? 1.0 : 0.0;
h1_drop[i] = h1_raw[i] * m_dropoutMask[i];
}
} else {
for(int i = 0; i < m_hidden; i++)
h1_drop[i] = h1_raw[i];
}
// BatchNorm (NeuroBook §6.3): normalizza h1_drop → h1_norm, aggiorna running stats
vector h1_cache = h1_drop; // copia per backward
vector h1_norm(m_hidden);
TrainBN(h1_drop, h1_norm);
// Layer 2: W2*h1_norm + b2 → z2 → Softmax → output
vector z2(m_outputs);
for(int i = 0; i < m_outputs; i++) {
z2[i] = m_b2[i];
for(int j = 0; j < m_hidden; j++)
z2[i] += h1_norm[j] * m_W2[j][i];
}
vector output(m_outputs);
for(int i = 0; i < m_outputs; i++) output[i] = z2[i];
Softmax(output);
// ── Loss: CCE pesata + L2 ──
double loss = 0.0;
for(int i = 0; i < m_outputs; i++) {
double p = MathMax(DATA_EPS(output[i]), output[i]);
loss -= weight * target[i] * MathLog(p);
}
loss += 0.5 * m_l2 * (L2Norm(m_W1) + L2Norm(m_W2));
m_lastLoss = loss;
m_t++;
m_beta1T *= m_adamBeta1;
m_beta2T *= m_adamBeta2;
// ── Backward ──
// dL/dz2 = weight * (output - target) (CCE+Softmax combinata, pesata)
vector dL_dz2(m_outputs);
for(int i = 0; i < m_outputs; i++)
dL_dz2[i] = weight * (output[i] - target[i]);
// dL/dW2 = h1_norm ⊗ dL/dz2
matrix dL_dW2(m_hidden, m_outputs);
for(int i = 0; i < m_hidden; i++)
for(int j = 0; j < m_outputs; j++)
dL_dW2[i][j] = h1_norm[i] * dL_dz2[j];
// dL/db2 = dL/dz2
vector dL_db2(m_outputs);
for(int i = 0; i < m_outputs; i++)
dL_db2[i] = dL_dz2[i];
// dL/dh1_norm = W2^T * dL/dz2
vector dL_dh1_norm(m_hidden);
for(int i = 0; i < m_hidden; i++) {
double sum = 0.0;
for(int j = 0; j < m_outputs; j++)
sum += dL_dz2[j] * m_W2[i][j];
dL_dh1_norm[i] = sum;
}
// BatchNorm backward: dL/dh1_drop = gamma/sqrt(var) * dL/dh1_norm
// + update gamma, beta
vector dL_dh1_drop(m_hidden);
for(int i = 0; i < m_hidden; i++) {
double denom = MathSqrt(m_bnRunningVar[i] + m_bnEps);
double h_std = (h1_cache[i] - m_bnRunningMean[i]) / denom;
double gOld = m_bnGamma[i]; // salva gamma pre-update per backward pass-through
m_bnGamma[i] -= lr * dL_dh1_norm[i] * h_std;
m_bnBeta[i] -= lr * dL_dh1_norm[i];
dL_dh1_drop[i] = dL_dh1_norm[i] * gOld / denom;
}
// Dropout backward: applica stessa mask
if(m_dropoutRate > 0.0) {
for(int i = 0; i < m_hidden; i++)
dL_dh1_drop[i] *= m_dropoutMask[i];
}
// dL/dz1 = dL/dh1_drop * ReLU'(z1)
vector dL_dz1(m_hidden);
for(int i = 0; i < m_hidden; i++)
dL_dz1[i] = dL_dh1_drop[i] * ReLUDeriv(z1[i]);
// dL/dW1 = inp ⊗ dL/dz1
matrix dL_dW1(m_inputs, m_hidden);
for(int i = 0; i < m_inputs; i++)
for(int j = 0; j < m_hidden; j++)
dL_dW1[i][j] = inp[i] * dL_dz1[j];
// dL/db1 = dL/dz1
vector dL_db1(m_hidden);
for(int i = 0; i < m_hidden; i++)
dL_db1[i] = dL_dz1[i];
// Adam update
AdamUpdate(m_W1, m_mW1, m_vW1, dL_dW1, lr);
AdamUpdate(m_b1, m_mb1, m_vb1, dL_db1, lr);
AdamUpdate(m_W2, m_mW2, m_vW2, dL_dW2, lr);
AdamUpdate(m_b2, m_mb2, m_vb2, dL_db2, lr);
return loss;
}
double L2Norm(matrix &m) {
double sum = 0.0;
for(int r = 0; r < (int)m.Rows(); r++)
for(int c = 0; c < (int)m.Cols(); c++)
sum += m[r][c] * m[r][c];
return sum;
}
// Overload senza pesi (compatibilità)
double Train(matrix &features, matrix &targets, int epochs, double lr) {
vector empty;
return Train(features, targets, epochs, lr, empty);
}
double Train(matrix &features, matrix &targets, int epochs, double lr, vector &weights) {
if(!m_initialized || features.Rows() == 0) return -1.0;
int n = (int)features.Rows();
double avgLoss = 0.0;
ArrayResize(m_lossHistory, epochs);
int logEvery = MathMax(1, epochs / 10);
for(int epoch = 0; epoch < epochs; epoch++) {
int indices[];
ArrayResize(indices, n);
for(int i = 0; i < n; i++) indices[i] = i;
for(int i = n - 1; i > 0; i--) {
int j = MathRand() % (i + 1);
int tmp = indices[i];
indices[i] = indices[j];
indices[j] = tmp;
}
double epochLoss = 0.0;
for(int s = 0; s < n; s++) {
int idx = indices[s];
vector inp = features.Row(idx);
vector tgt = targets.Row(idx);
double w = (weights.Size() > idx) ? weights[idx] : 1.0;
epochLoss += TrainSample(inp, tgt, lr, w);
}
epochLoss /= (double)n;
m_lossHistory[epoch] = epochLoss;
if(epoch == epochs - 1) avgLoss = epochLoss;
if(epoch == 0 || epoch == epochs - 1 || (epoch+1) % logEvery == 0) {
Print(" Epoch ", epoch+1, "/", epochs,
" | loss: ", StringFormat("%.6f", epochLoss),
" | lr: ", StringFormat("%.5f", lr));
}
}
m_epochsTrained += epochs;
m_lastLoss = avgLoss;
return avgLoss;
}
// Salva loss history in CSV nella cartella Common
bool SaveLossCsv(string filename = "") {
if(ArraySize(m_lossHistory) == 0) return false;
if(filename == "") filename = "NN_LossHistory.csv";
int fh = FileOpen(filename, FILE_TXT|FILE_WRITE|FILE_COMMON);
if(fh == INVALID_HANDLE) return false;
FileWriteString(fh, "epoch,loss\r\n");
for(int i = 0; i < ArraySize(m_lossHistory); i++) {
FileWriteString(fh, (string)(i+1) + "," + StringFormat("%.8f", m_lossHistory[i]) + "\r\n");
}
FileClose(fh);
Print("Loss history saved to ", filename, " (", ArraySize(m_lossHistory), " epochs)");
return true;
}
string LossHistorySummary() const {
if(ArraySize(m_lossHistory) == 0) return "no history";
double first = m_lossHistory[0];
double last = m_lossHistory[ArraySize(m_lossHistory)-1];
double best = first;
int bestEpoch = 0;
for(int i = 0; i < ArraySize(m_lossHistory); i++) {
if(m_lossHistory[i] < best) { best = m_lossHistory[i]; bestEpoch = i; }
}
return StringFormat("loss: %.6f → %.6f (best: %.6f @ epoch %d, %d epochs)",
first, last, best, bestEpoch+1, ArraySize(m_lossHistory));
}
int Predict(vector &inp) {
vector output;
Forward(inp, output);
int bestIdx = 0;
double bestVal = output[0];
for(int i = 1; i < m_outputs; i++) {
if(output[i] > bestVal) {
bestVal = output[i];
bestIdx = i;
}
}
return bestIdx;
}
double GetCombinedZ(vector &inp) {
vector output;
Forward(inp, output);
return output[0] - output[2];
}
bool Save(string filename) {
int fh = FileOpen(filename, FILE_WRITE | FILE_BIN | FILE_COMMON);
if(fh == INVALID_HANDLE) return false;
bool ok = Save(fh);
FileClose(fh);
return ok;
}
bool Save(int fh) {
if(fh == INVALID_HANDLE) return false;
FileWriteInteger(fh, 3);
FileWriteInteger(fh, m_inputs);
FileWriteInteger(fh, m_hidden);
FileWriteInteger(fh, m_outputs);
FileWriteInteger(fh, m_epochsTrained);
FileWriteDouble(fh, m_lastLoss);
FileWriteDouble(fh, m_l2);
FileWriteDouble(fh, m_dropoutRate);
FileWriteDouble(fh, m_bnMomentum);
FileWriteDouble(fh, m_bnEps);
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++)
FileWriteDouble(fh, m_W1[r][c]);
for(int i = 0; i < m_hidden; i++)
FileWriteDouble(fh, m_b1[i]);
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++)
FileWriteDouble(fh, m_W2[r][c]);
for(int i = 0; i < m_outputs; i++)
FileWriteDouble(fh, m_b2[i]);
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++) {
FileWriteDouble(fh, m_mW1[r][c]);
FileWriteDouble(fh, m_vW1[r][c]);
}
for(int i = 0; i < m_hidden; i++) {
FileWriteDouble(fh, m_mb1[i]);
FileWriteDouble(fh, m_vb1[i]);
}
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++) {
FileWriteDouble(fh, m_mW2[r][c]);
FileWriteDouble(fh, m_vW2[r][c]);
}
for(int i = 0; i < m_outputs; i++) {
FileWriteDouble(fh, m_mb2[i]);
FileWriteDouble(fh, m_vb2[i]);
}
FileWriteDouble(fh, m_beta1T);
FileWriteDouble(fh, m_beta2T);
FileWriteInteger(fh, m_t);
// BatchNorm (v3)
for(int i = 0; i < m_hidden; i++) {
FileWriteDouble(fh, m_bnGamma[i]);
FileWriteDouble(fh, m_bnBeta[i]);
FileWriteDouble(fh, m_bnRunningMean[i]);
FileWriteDouble(fh, m_bnRunningVar[i]);
}
return true;
}
bool Load(string filename) {
if(!FileIsExist(filename, FILE_COMMON)) return false;
int fh = FileOpen(filename, FILE_READ | FILE_BIN | FILE_COMMON);
if(fh == INVALID_HANDLE) return false;
bool ok = Load(fh);
FileClose(fh);
return ok;
}
bool Load(int fh) {
if(fh == INVALID_HANDLE) return false;
int version = FileReadInteger(fh);
int inputs = FileReadInteger(fh);
int hidden = FileReadInteger(fh);
int outputs = FileReadInteger(fh);
Init(inputs, hidden, outputs);
m_epochsTrained = FileReadInteger(fh);
m_lastLoss = FileReadDouble(fh);
if(version >= 2) m_l2 = FileReadDouble(fh);
if(version >= 3) {
m_dropoutRate = FileReadDouble(fh);
m_bnMomentum = FileReadDouble(fh);
m_bnEps = FileReadDouble(fh);
}
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++)
m_W1[r][c] = FileReadDouble(fh);
for(int i = 0; i < m_hidden; i++)
m_b1[i] = FileReadDouble(fh);
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++)
m_W2[r][c] = FileReadDouble(fh);
for(int i = 0; i < m_outputs; i++)
m_b2[i] = FileReadDouble(fh);
if(version >= 1) {
for(int r = 0; r < m_inputs; r++)
for(int c = 0; c < m_hidden; c++) {
m_mW1[r][c] = FileReadDouble(fh);
m_vW1[r][c] = FileReadDouble(fh);
}
for(int i = 0; i < m_hidden; i++) {
m_mb1[i] = FileReadDouble(fh);
m_vb1[i] = FileReadDouble(fh);
}
for(int r = 0; r < m_hidden; r++)
for(int c = 0; c < m_outputs; c++) {
m_mW2[r][c] = FileReadDouble(fh);
m_vW2[r][c] = FileReadDouble(fh);
}
for(int i = 0; i < m_outputs; i++) {
m_mb2[i] = FileReadDouble(fh);
m_vb2[i] = FileReadDouble(fh);
}
if(version >= 1) {
m_beta1T = FileReadDouble(fh);
m_beta2T = FileReadDouble(fh);
m_t = FileReadInteger(fh);
}
// BatchNorm (v3)
if(version >= 3) {
m_bnGamma.Init(m_hidden);
m_bnBeta.Init(m_hidden);
m_bnRunningMean.Init(m_hidden);
m_bnRunningVar.Init(m_hidden);
for(int i = 0; i < m_hidden; i++) {
m_bnGamma[i] = FileReadDouble(fh);
m_bnBeta[i] = FileReadDouble(fh);
m_bnRunningMean[i] = FileReadDouble(fh);
m_bnRunningVar[i] = FileReadDouble(fh);
}
}
}
return true;
}
bool IsInitialized() const { return m_initialized; }
int EpochsTrained() const { return m_epochsTrained; }
double LastLoss() const { return m_lastLoss; }
int Inputs() const { return m_inputs; }
int Hidden() const { return m_hidden; }
int Outputs() const { return m_outputs; }
string Info() const {
if(!m_initialized) return "NN: uninitialized";
return StringFormat("NN: %d→%d→%d (epoche=%d, loss=%.6f, dropout=%.2f, BN=%s)",
m_inputs, m_hidden, m_outputs, m_epochsTrained, m_lastLoss,
m_dropoutRate, m_initialized ? "on" : "off");
}
string WeightsSummary() const {
if(!m_initialized) return "NN: uninitialized";
double w1Min = 1e99, w1Max = -1e99, w1Sum = 0;
double w2Min = 1e99, w2Max = -1e99, w2Sum = 0;
int w1Count = 0, w2Count = 0;
for(int r = 0; r < m_inputs; r++) {
for(int c = 0; c < m_hidden; c++) {
double v = m_W1[r][c];
if(v < w1Min) w1Min = v; if(v > w1Max) w1Max = v;
w1Sum += v; w1Count++;
}
}
for(int r = 0; r < m_hidden; r++) {
for(int c = 0; c < m_outputs; c++) {
double v = m_W2[r][c];
if(v < w2Min) w2Min = v; if(v > w2Max) w2Max = v;
w2Sum += v; w2Count++;
}
}
return StringFormat(" W1 [%.4f, %.4f] μ=%.4f | W2 [%.4f, %.4f] μ=%.4f",
w1Min, w1Max, (w1Count>0?w1Sum/w1Count:0),
w2Min, w2Max, (w2Count>0?w2Sum/w2Count:0));
}
};
struct NNTrainSample {
double features[NN_FEATURES];
double target[NN_TARGETS];
double weight; // peso del sample: trades con grosso impatto pesano di più
};
class NNTrainBuffer {
private:
NNTrainSample m_samples[];
int m_count;
public:
NNTrainBuffer() : m_count(0) {}
void Add(double &features[], double &target[], double weight = 1.0) {
int idx = m_count;
ArrayResize(m_samples, idx + 1);
for(int i = 0; i < NN_FEATURES; i++) m_samples[idx].features[i] = features[i];
for(int i = 0; i < NN_TARGETS; i++) m_samples[idx].target[i] = target[i];
m_samples[idx].weight = weight;
m_count++;
}
int Count() const { return m_count; }
void ToMatrices(matrix &features, matrix &targets) {
if(m_count == 0) return;
features.Init(m_count, NN_FEATURES);
targets.Init(m_count, NN_TARGETS);
for(int i = 0; i < m_count; i++) {
for(int j = 0; j < NN_FEATURES; j++)
features[i][j] = m_samples[i].features[j];
for(int j = 0; j < NN_TARGETS; j++)
targets[i][j] = m_samples[i].target[j];
}
}
vector GetWeights() {
vector w(m_count);
for(int i = 0; i < m_count; i++)
w[i] = m_samples[i].weight;
return w;
}
void Clear() {
ArrayResize(m_samples, 0);
m_count = 0;
}
void Trim(int maxSamples) {
if(m_count <= maxSamples) return;
int remove = m_count - maxSamples;
for(int i = 0; i < maxSamples; i++)
m_samples[i] = m_samples[i + remove];
ArrayResize(m_samples, maxSamples);
m_count = maxSamples;
}
// Dump training samples to CSV nella cartella Common per debug
void SaveCsv(string filename = "NN_TrainingSamples.csv") {
int fh = FileOpen(filename, FILE_TXT|FILE_WRITE|FILE_COMMON);
if(fh == INVALID_HANDLE) return;
FileWriteString(fh, "sample");
for(int f = 0; f < NN_FEATURES; f++) FileWriteString(fh, ",feat_" + (string)f);
for(int t = 0; t < NN_TARGETS; t++) FileWriteString(fh, ",target_" + (string)t);
FileWriteString(fh, ",label\r\n");
for(int i = 0; i < m_count; i++) {
string line = (string)i;
for(int f = 0; f < NN_FEATURES; f++) line += "," + StringFormat("%+.6f", m_samples[i].features[f]);
for(int t = 0; t < NN_TARGETS; t++) line += "," + StringFormat("%.0f", m_samples[i].target[t]);
// Label leggibile
if(m_samples[i].target[0] == 1) line += ",BUY";
else if(m_samples[i].target[2] == 1) line += ",SELL";
else line += ",FLAT";
line += "\r\n";
FileWriteString(fh, line);
}
FileClose(fh);
Print("Training samples saved to ", filename, " (", m_count, " samples)");
}
// Stampa statistiche riassuntive del dataset
void PrintStats() {
int buyCount = 0, sellCount = 0, flatCount = 0;
for(int i = 0; i < m_count; i++) {
if(m_samples[i].target[0] == 1) buyCount++;
else if(m_samples[i].target[2] == 1) sellCount++;
else flatCount++;
}
Print(" Dataset: ", m_count, " samples | Buy: ", buyCount,
" Sell: ", sellCount, " Flat: ", flatCount);
}
// Media feature per classe (utile per vedere se le feature discriminano)
void PrintFeatureStats() {
if(m_count < 3) { Print(" Feature stats: too few samples"); return; }
double meanFeat[NN_FEATURES] = {};
double absMean[NN_FEATURES] = {};
for(int i = 0; i < m_count; i++) {
for(int f = 0; f < NN_FEATURES; f++) {
meanFeat[f] += m_samples[i].features[f];
absMean[f] += MathAbs(m_samples[i].features[f]);
}
}
Print(" Feature means (signal strength per class):");
for(int f = 0; f < NN_FEATURES; f++) {
meanFeat[f] /= m_count;
absMean[f] /= m_count;
}
string labels[NN_FEATURES] = {"Hurst","ADX","MA","Momentum","Consensus","Hunter","Agreement","TrendStr"};
for(int f = 0; f < NN_FEATURES; f++) {
Print(" [", f, "] ", labels[f], ": μ=", StringFormat("%+.4f", meanFeat[f]),
" |avg|=", StringFormat("%.4f", absMean[f]));
}
}
};
#endif